diff --git a/patches/api/0001-Purpur-API-Changes.patch b/patches/api/0001-Purpur-API-Changes.patch deleted file mode 100644 index 55c0d81..0000000 --- a/patches/api/0001-Purpur-API-Changes.patch +++ /dev/null @@ -1,4321 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: granny -Date: Wed, 25 Dec 2024 15:15:22 +0900 -Subject: [PATCH] Purpur API Changes - -PurpurMC -Copyright (C) 2024 PurpurMC - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -diff --git a/build.gradle.kts b/build.gradle.kts -index 571534b42cd9c33d6a7bb6fe3bf3a28e33f8e5de..49546dfbb4dd006b5a2419908890ba4c2a0e207a 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -66,6 +66,7 @@ dependencies { - apiAndDocs("net.kyori:adventure-text-logger-slf4j") - api("org.apache.logging.log4j:log4j-api:$log4jVersion") - api("org.slf4j:slf4j-api:$slf4jVersion") -+ api("io.sentry:sentry:5.4.0") // Pufferfish - - implementation("org.ow2.asm:asm:9.7.1") - implementation("org.ow2.asm:asm-commons:9.7.1") -@@ -150,6 +151,13 @@ val generateApiVersioningFile by tasks.registering { - } - } - -+// Pufferfish Start -+tasks.withType { -+ val compilerArgs = options.compilerArgs -+ compilerArgs.add("--add-modules=jdk.incubator.vector") -+} -+// Pufferfish End -+ - tasks.jar { - from(generateApiVersioningFile.map { it.outputs.files.singleFile }) { - into("META-INF/maven/${project.group}/${project.name}") -@@ -162,6 +170,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 157617933a772451f6c073d97afaf305769b4d40..438a9c76381ea3f5b774e2232ff56c5dc6f82586 100644 ---- a/src/main/java/co/aikar/timings/TimedEventExecutor.java -+++ b/src/main/java/co/aikar/timings/TimedEventExecutor.java -@@ -80,9 +80,9 @@ public class TimedEventExecutor implements EventExecutor { - executor.execute(listener, event); - return; - } -- try (Timing ignored = timings.startTiming()){ -+ //try (Timing ignored = timings.startTiming()){ // Purpur - executor.execute(listener, event); -- } -+ //} // Purpur - } - - @Override -diff --git a/src/main/java/co/aikar/timings/Timing.java b/src/main/java/co/aikar/timings/Timing.java -index 4195efcfe044618052bb03dea34a4fb2ca7c44f0..8709c955bac34bc546a8e022cfac808bc61ee793 100644 ---- a/src/main/java/co/aikar/timings/Timing.java -+++ b/src/main/java/co/aikar/timings/Timing.java -@@ -39,6 +39,7 @@ public interface Timing extends AutoCloseable { - * @return Timing - */ - @NotNull -+ @io.papermc.paper.annotation.DoNotUse // Purpur - Timing startTiming(); - - /** -@@ -46,6 +47,7 @@ public interface Timing extends AutoCloseable { - * - * Will automatically be called when this Timing is used with try-with-resources - */ -+ @io.papermc.paper.annotation.DoNotUse // Purpur - void stopTiming(); - - /** -@@ -56,6 +58,7 @@ public interface Timing extends AutoCloseable { - * @return Timing - */ - @NotNull -+ @io.papermc.paper.annotation.DoNotUse // Purpur - Timing startTimingIfSync(); - - /** -@@ -65,12 +68,14 @@ public interface Timing extends AutoCloseable { - * - * But only if we are on the primary thread. - */ -+ @io.papermc.paper.annotation.DoNotUse // Purpur - void stopTimingIfSync(); - - /** - * @deprecated Doesn't do anything - Removed - */ - @Deprecated -+ @io.papermc.paper.annotation.DoNotUse // Purpur - void abort(); - - /** -@@ -82,5 +87,6 @@ public interface Timing extends AutoCloseable { - TimingHandler getTimingHandler(); - - @Override -+ @io.papermc.paper.annotation.DoNotUse // Purpur - void close(); - } -diff --git a/src/main/java/co/aikar/timings/Timings.java b/src/main/java/co/aikar/timings/Timings.java -index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..27a02f0c3261067d8e4ee6169c62cecbbfe50d42 100644 ---- a/src/main/java/co/aikar/timings/Timings.java -+++ b/src/main/java/co/aikar/timings/Timings.java -@@ -124,7 +124,7 @@ public final class Timings { - @NotNull - public static Timing ofStart(@NotNull Plugin plugin, @NotNull String name, @Nullable Timing groupHandler) { - Timing timing = of(plugin, name, groupHandler); -- timing.startTiming(); -+ //timing.startTiming(); // Purpur - return timing; - } - -@@ -146,7 +146,7 @@ public final class Timings { - */ - public static void setTimingsEnabled(boolean enabled) { - if (enabled && !warnedAboutDeprecationOnEnable) { -- Bukkit.getLogger().severe(PlainTextComponentSerializer.plainText().serialize(deprecationMessage())); -+ //Bukkit.getLogger().severe(PlainTextComponentSerializer.plainText().serialize(deprecationMessage())); - warnedAboutDeprecationOnEnable = true; - } - } -diff --git a/src/main/java/co/aikar/timings/TimingsCommand.java b/src/main/java/co/aikar/timings/TimingsCommand.java -index b83e5ff7ada8771fdf27ba9807c77ba6a4ce12da..f28eec202237461cb489a2b13289d813381a25bc 100644 ---- a/src/main/java/co/aikar/timings/TimingsCommand.java -+++ b/src/main/java/co/aikar/timings/TimingsCommand.java -@@ -47,7 +47,7 @@ public class TimingsCommand extends BukkitCommand { - public TimingsCommand(@NotNull String name) { - super(name); - this.description = "Manages Spigot Timings data to see performance of the server."; -- this.usageMessage = "/timings "; -+ this.usageMessage = "/timings";// "; // Purpur - this.setPermission("bukkit.command.timings"); - } - -@@ -57,7 +57,10 @@ public class TimingsCommand extends BukkitCommand { - return true; - } - if (true) { -- sender.sendMessage(Timings.deprecationMessage()); -+ net.kyori.adventure.text.minimessage.MiniMessage mm = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage(); -+ sender.sendMessage(mm.deserialize("Purpur has removed timings to save your performance. Please use /spark instead")); -+ sender.sendMessage(mm.deserialize("For more information, view its documentation at")); -+ sender.sendMessage(mm.deserialize("https://spark.lucko.me/docs/Command-Usage")); // Purpur - return true; - } - if (args.length < 1) { -@@ -118,7 +121,7 @@ public class TimingsCommand extends BukkitCommand { - Preconditions.checkNotNull(args, "Arguments cannot be null"); - Preconditions.checkNotNull(alias, "Alias cannot be null"); - -- if (args.length == 1) { -+ if (false && args.length == 1) { // Purpur - return StringUtil.copyPartialMatches(args[0], TIMINGS_SUBCOMMANDS, - new ArrayList(TIMINGS_SUBCOMMANDS.size())); - } -diff --git a/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java -index 023cc52a9e28e1238c7452c0f3f577f2850fd861..00b3f46ddd26ae08744d3dba211f92624d4b1063 100644 ---- a/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java -+++ b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java -@@ -28,6 +28,12 @@ public interface VersionFetcher { - */ - Component getVersionMessage(String serverVersion); - -+ // Purpur start -+ default int distance() { -+ return 0; -+ } -+ // Purpur end -+ - @ApiStatus.Internal - class DummyVersionFetcher implements VersionFetcher { - -diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java -new file mode 100644 -index 0000000000000000000000000000000000000000..10310fdd53de28efb8a8250f6d3b0c8eb08fb68a ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryContext.java -@@ -0,0 +1,161 @@ -+package gg.pufferfish.pufferfish.sentry; -+ -+import com.google.gson.Gson; -+import java.lang.reflect.Field; -+import java.lang.reflect.Modifier; -+import java.util.Map; -+import java.util.TreeMap; -+import org.apache.logging.log4j.ThreadContext; -+import org.bukkit.command.Command; -+import org.bukkit.command.CommandSender; -+import org.bukkit.entity.Player; -+import org.bukkit.event.Event; -+import org.bukkit.event.player.PlayerEvent; -+import org.bukkit.plugin.Plugin; -+import org.bukkit.plugin.RegisteredListener; -+import org.jetbrains.annotations.Nullable; -+ -+public class SentryContext { -+ -+ private static final Gson GSON = new Gson(); -+ -+ public static void setPluginContext(@Nullable Plugin plugin) { -+ if (plugin != null) { -+ ThreadContext.put("pufferfishsentry_pluginname", plugin.getName()); -+ ThreadContext.put("pufferfishsentry_pluginversion", plugin.getDescription().getVersion()); -+ } -+ } -+ -+ public static void removePluginContext() { -+ ThreadContext.remove("pufferfishsentry_pluginname"); -+ ThreadContext.remove("pufferfishsentry_pluginversion"); -+ } -+ -+ public static void setSenderContext(@Nullable CommandSender sender) { -+ if (sender != null) { -+ ThreadContext.put("pufferfishsentry_playername", sender.getName()); -+ if (sender instanceof Player player) { -+ ThreadContext.put("pufferfishsentry_playerid", player.getUniqueId().toString()); -+ } -+ } -+ } -+ -+ public static void removeSenderContext() { -+ ThreadContext.remove("pufferfishsentry_playername"); -+ ThreadContext.remove("pufferfishsentry_playerid"); -+ } -+ -+ public static void setEventContext(Event event, RegisteredListener registration) { -+ setPluginContext(registration.getPlugin()); -+ -+ try { -+ // Find the player that was involved with this event -+ Player player = null; -+ if (event instanceof PlayerEvent) { -+ player = ((PlayerEvent) event).getPlayer(); -+ } else { -+ Class eventClass = event.getClass(); -+ -+ Field playerField = null; -+ -+ for (Field field : eventClass.getDeclaredFields()) { -+ if (field.getType().equals(Player.class)) { -+ playerField = field; -+ break; -+ } -+ } -+ -+ if (playerField != null) { -+ playerField.setAccessible(true); -+ player = (Player) playerField.get(event); -+ } -+ } -+ -+ if (player != null) { -+ setSenderContext(player); -+ } -+ } catch (Exception e) {} // We can't really safely log exceptions. -+ -+ ThreadContext.put("pufferfishsentry_eventdata", GSON.toJson(serializeFields(event))); -+ } -+ -+ public static void removeEventContext() { -+ removePluginContext(); -+ removeSenderContext(); -+ ThreadContext.remove("pufferfishsentry_eventdata"); -+ } -+ -+ private static Map serializeFields(Object object) { -+ Map fields = new TreeMap<>(); -+ fields.put("_class", object.getClass().getName()); -+ for (Field declaredField : object.getClass().getDeclaredFields()) { -+ try { -+ if (Modifier.isStatic(declaredField.getModifiers())) { -+ continue; -+ } -+ -+ String fieldName = declaredField.getName(); -+ if (fieldName.equals("handlers")) { -+ continue; -+ } -+ declaredField.setAccessible(true); -+ Object value = declaredField.get(object); -+ if (value != null) { -+ fields.put(fieldName, value.toString()); -+ } else { -+ fields.put(fieldName, ""); -+ } -+ } catch (Exception e) {} // We can't really safely log exceptions. -+ } -+ return fields; -+ } -+ -+ public static class State { -+ -+ private Plugin plugin; -+ private Command command; -+ private String commandLine; -+ private Event event; -+ private RegisteredListener registeredListener; -+ -+ public Plugin getPlugin() { -+ return plugin; -+ } -+ -+ public void setPlugin(Plugin plugin) { -+ this.plugin = plugin; -+ } -+ -+ public Command getCommand() { -+ return command; -+ } -+ -+ public void setCommand(Command command) { -+ this.command = command; -+ } -+ -+ public String getCommandLine() { -+ return commandLine; -+ } -+ -+ public void setCommandLine(String commandLine) { -+ this.commandLine = commandLine; -+ } -+ -+ public Event getEvent() { -+ return event; -+ } -+ -+ public void setEvent(Event event) { -+ this.event = event; -+ } -+ -+ public RegisteredListener getRegisteredListener() { -+ return registeredListener; -+ } -+ -+ public void setRegisteredListener(RegisteredListener registeredListener) { -+ this.registeredListener = registeredListener; -+ } -+ } -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3441cdad70da1bd523c5933b1a914688718c2657 ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDChecker.java -@@ -0,0 +1,40 @@ -+package gg.pufferfish.pufferfish.simd; -+ -+import java.util.logging.Level; -+import java.util.logging.Logger; -+import jdk.incubator.vector.FloatVector; -+import jdk.incubator.vector.IntVector; -+import jdk.incubator.vector.VectorSpecies; -+ -+/** -+ * Basically, java is annoying and we have to push this out to its own class. -+ */ -+@Deprecated -+public class SIMDChecker { -+ -+ @Deprecated -+ public static boolean canEnable(Logger logger) { -+ try { -+ if (SIMDDetection.getJavaVersion() < 17 || SIMDDetection.getJavaVersion() > 21) { -+ return false; -+ } else { -+ SIMDDetection.testRun = true; -+ -+ VectorSpecies ISPEC = IntVector.SPECIES_PREFERRED; -+ VectorSpecies FSPEC = FloatVector.SPECIES_PREFERRED; -+ -+ logger.log(Level.INFO, "Max SIMD vector size on this system is " + ISPEC.vectorBitSize() + " bits (int)"); -+ logger.log(Level.INFO, "Max SIMD vector size on this system is " + FSPEC.vectorBitSize() + " bits (float)"); -+ -+ if (ISPEC.elementSize() < 2 || FSPEC.elementSize() < 2) { -+ logger.log(Level.WARNING, "SIMD is not properly supported on this system!"); -+ return false; -+ } -+ -+ return true; -+ } -+ } catch (NoClassDefFoundError | Exception ignored) {} // Basically, we don't do anything. This lets us detect if it's not functional and disable it. -+ return false; -+ } -+ -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/SIMDDetection.java b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDDetection.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a84889d3e9cfc4d7ab5f867820a6484c6070711b ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/simd/SIMDDetection.java -@@ -0,0 +1,35 @@ -+package gg.pufferfish.pufferfish.simd; -+ -+import java.util.logging.Logger; -+ -+@Deprecated -+public class SIMDDetection { -+ -+ public static boolean isEnabled = false; -+ public static boolean versionLimited = false; -+ public static boolean testRun = false; -+ -+ @Deprecated -+ public static boolean canEnable(Logger logger) { -+ try { -+ return SIMDChecker.canEnable(logger); -+ } catch (NoClassDefFoundError | Exception ignored) { -+ return false; -+ } -+ } -+ -+ @Deprecated -+ public static int getJavaVersion() { -+ // https://stackoverflow.com/a/2591122 -+ String version = System.getProperty("java.version"); -+ if(version.startsWith("1.")) { -+ version = version.substring(2, 3); -+ } else { -+ int dot = version.indexOf("."); -+ if(dot != -1) { version = version.substring(0, dot); } -+ } -+ version = version.split("-")[0]; // Azul is stupid -+ return Integer.parseInt(version); -+ } -+ -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/simd/VectorMapPalette.java b/src/main/java/gg/pufferfish/pufferfish/simd/VectorMapPalette.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ae2464920c9412ac90b819a540ee58be0741465f ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/simd/VectorMapPalette.java -@@ -0,0 +1,83 @@ -+package gg.pufferfish.pufferfish.simd; -+ -+import java.awt.Color; -+import jdk.incubator.vector.FloatVector; -+import jdk.incubator.vector.IntVector; -+import jdk.incubator.vector.VectorMask; -+import jdk.incubator.vector.VectorSpecies; -+import org.bukkit.map.MapPalette; -+ -+@Deprecated -+public class VectorMapPalette { -+ -+ private static final VectorSpecies I_SPEC = IntVector.SPECIES_PREFERRED; -+ private static final VectorSpecies F_SPEC = FloatVector.SPECIES_PREFERRED; -+ -+ @Deprecated -+ public static void matchColorVectorized(int[] in, byte[] out) { -+ int speciesLength = I_SPEC.length(); -+ int i; -+ for (i = 0; i < in.length - speciesLength; i += speciesLength) { -+ float[] redsArr = new float[speciesLength]; -+ float[] bluesArr = new float[speciesLength]; -+ float[] greensArr = new float[speciesLength]; -+ int[] alphasArr = new int[speciesLength]; -+ -+ for (int j = 0; j < speciesLength; j++) { -+ alphasArr[j] = (in[i + j] >> 24) & 0xFF; -+ redsArr[j] = (in[i + j] >> 16) & 0xFF; -+ greensArr[j] = (in[i + j] >> 8) & 0xFF; -+ bluesArr[j] = (in[i + j] >> 0) & 0xFF; -+ } -+ -+ IntVector alphas = IntVector.fromArray(I_SPEC, alphasArr, 0); -+ FloatVector reds = FloatVector.fromArray(F_SPEC, redsArr, 0); -+ FloatVector greens = FloatVector.fromArray(F_SPEC, greensArr, 0); -+ FloatVector blues = FloatVector.fromArray(F_SPEC, bluesArr, 0); -+ IntVector resultIndex = IntVector.zero(I_SPEC); -+ VectorMask modificationMask = VectorMask.fromLong(I_SPEC, 0xffffffff); -+ -+ modificationMask = modificationMask.and(alphas.lt(128).not()); -+ FloatVector bestDistances = FloatVector.broadcast(F_SPEC, Float.MAX_VALUE); -+ -+ for (int c = 4; c < MapPalette.colors.length; c++) { -+ // We're using 32-bit floats here because it's 2x faster and nobody will know the difference. -+ // For correctness, the original algorithm uses 64-bit floats instead. Completely unnecessary. -+ FloatVector compReds = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getRed()); -+ FloatVector compGreens = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getGreen()); -+ FloatVector compBlues = FloatVector.broadcast(F_SPEC, MapPalette.colors[c].getBlue()); -+ -+ FloatVector rMean = reds.add(compReds).div(2.0f); -+ FloatVector rDiff = reds.sub(compReds); -+ FloatVector gDiff = greens.sub(compGreens); -+ FloatVector bDiff = blues.sub(compBlues); -+ -+ FloatVector weightR = rMean.div(256.0f).add(2); -+ FloatVector weightG = FloatVector.broadcast(F_SPEC, 4.0f); -+ FloatVector weightB = FloatVector.broadcast(F_SPEC, 255.0f).sub(rMean).div(256.0f).add(2.0f); -+ -+ FloatVector distance = weightR.mul(rDiff).mul(rDiff).add(weightG.mul(gDiff).mul(gDiff)).add(weightB.mul(bDiff).mul(bDiff)); -+ -+ // Now we compare to the best distance we've found. -+ // This mask contains a "1" if better, and a "0" otherwise. -+ VectorMask bestDistanceMask = distance.lt(bestDistances); -+ bestDistances = bestDistances.blend(distance, bestDistanceMask); // Update the best distances -+ -+ // Update the result array -+ // We also AND with the modification mask because we don't want to interfere if the alpha value isn't large enough. -+ resultIndex = resultIndex.blend(c, bestDistanceMask.cast(I_SPEC).and(modificationMask)); // Update the results -+ } -+ -+ for (int j = 0; j < speciesLength; j++) { -+ int index = resultIndex.lane(j); -+ out[i + j] = (byte) (index < 128 ? index : -129 + (index - 127)); -+ } -+ } -+ -+ // For the final ones, fall back to the regular method -+ for (; i < in.length; i++) { -+ out[i] = MapPalette.matchColor(new Color(in[i], true)); -+ } -+ } -+ -+} -diff --git a/src/main/java/io/papermc/paper/ServerBuildInfo.java b/src/main/java/io/papermc/paper/ServerBuildInfo.java -index 652ff54e7c50412503725d628bfe72ed03059790..7196594e07af19a14c320d77df893978525fe386 100644 ---- a/src/main/java/io/papermc/paper/ServerBuildInfo.java -+++ b/src/main/java/io/papermc/paper/ServerBuildInfo.java -@@ -19,6 +19,20 @@ public interface ServerBuildInfo { - */ - Key BRAND_PAPER_ID = Key.key("papermc", "paper"); - -+ // Purpur start -+ /** -+ * The brand id for Pufferfish. -+ */ -+ Key BRAND_PUFFERFISH_ID = Key.key("pufferfish", "pufferfish"); -+ // Purpur end -+ -+ // Purpur start -+ /** -+ * The brand id for Purpur. -+ */ -+ Key BRAND_PURPUR_ID = Key.key("purpurmc", "purpur"); -+ // Purpur end -+ - /** - * Gets the {@code ServerBuildInfo}. - * -diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 8ab94f8189ebd9d4158231871abdebec399deb2c..db453d04efb00baaeabb904a7bd1b99dd0a50735 100644 ---- a/src/main/java/org/bukkit/Bukkit.java -+++ b/src/main/java/org/bukkit/Bukkit.java -@@ -2968,4 +2968,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 918a045165cdcde264bc24082b7afebb407271de..e98d6321c5f2cdde91b54f8a74cbcc045eae75a8 100644 ---- a/src/main/java/org/bukkit/ChatColor.java -+++ b/src/main/java/org/bukkit/ChatColor.java -@@ -456,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); -+ java.util.regex.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 e89edabd36a6755912694d8a8700da4ebe5c5829..ba2eff0f2ecffbea4b42d5c6e4485ee0087dc981 100644 ---- a/src/main/java/org/bukkit/Material.java -+++ b/src/main/java/org/bukkit/Material.java -@@ -5811,4 +5811,40 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla - return this.asItemType().getDefaultDataTypes(); - } - // Paper end - data component API -+ -+ // 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 5622fe3165baad8138c22cfc016ed6c3834cf702..6d31b561d915180fcd473b317721064feed28f37 100644 ---- a/src/main/java/org/bukkit/OfflinePlayer.java -+++ b/src/main/java/org/bukkit/OfflinePlayer.java -@@ -573,4 +573,106 @@ public interface OfflinePlayer extends ServerOperator, AnimalTamer, Configuratio - @Override - io.papermc.paper.persistence.@NotNull PersistentDataContainerView getPersistentDataContainer(); - // Paper end - add pdc to offline player -+ -+ // 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(); -+ -+ /** -+ * 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 ad816538b30079c62d5e1eb98c6f4b61e12e8d47..fcfad39173ecf2573a1ba77236bce8d9f73e02bb 100644 ---- a/src/main/java/org/bukkit/Server.java -+++ b/src/main/java/org/bukkit/Server.java -@@ -2283,6 +2283,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 - * -@@ -2607,4 +2619,105 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi - */ - void allowPausing(@NotNull org.bukkit.plugin.Plugin plugin, boolean value); - // Paper end - API to check if the server is sleeping -+ -+ // Purpur start -+ /** -+ * Get the name of this server -+ * @return the name of the server -+ */ -+ @NotNull -+ String getServerName(); -+ -+ /** -+ * 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 bef54a6c8290e09cbaac20b03dde8dfb902c96b0..5f7de23e419175e55459df760c7190639ea39f18 100644 ---- a/src/main/java/org/bukkit/World.java -+++ b/src/main/java/org/bukkit/World.java -@@ -4246,6 +4246,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/block/EntityBlockStorage.java b/src/main/java/org/bukkit/block/EntityBlockStorage.java -index 739911cda33b373f99df627a3a378b37d7d461aa..51e78c22cd021722b963fe31d1d9175d141add1a 100644 ---- a/src/main/java/org/bukkit/block/EntityBlockStorage.java -+++ b/src/main/java/org/bukkit/block/EntityBlockStorage.java -@@ -47,6 +47,24 @@ public interface EntityBlockStorage extends TileState { - @NotNull - List releaseEntities(); - -+ // Purpur start -+ /** -+ * Releases a stored entity, and returns the entity in the world. -+ * -+ * @param entity Entity to release -+ * @return The entity which was released, or null if the stored entity is not in the hive -+ */ -+ @org.jetbrains.annotations.Nullable -+ T releaseEntity(@NotNull org.purpurmc.purpur.entity.StoredEntity entity); -+ -+ /** -+ * Gets all the entities currently stored in the block. -+ * -+ * @return List of all entities which are stored in the block -+ */ -+ @NotNull -+ List> getEntities(); -+ //Purpur end - /** - * Add an entity to the block. - * -diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java -index 5df19bd701c67506689fc7f49d91f99ebfbc83f0..7740ad53796d08584bb0110f99af5639993e4d71 100644 ---- a/src/main/java/org/bukkit/command/SimpleCommandMap.java -+++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java -@@ -153,6 +153,19 @@ public class SimpleCommandMap implements CommandMap { - return false; - } - -+ // Purpur start -+ String[] parsedArgs = Arrays.copyOfRange(args, 1, args.length); -+ org.purpurmc.purpur.event.ExecuteCommandEvent event = new org.purpurmc.purpur.event.ExecuteCommandEvent(sender, target, sentCommandLabel, parsedArgs); -+ if (!event.callEvent()) { -+ return true; // cancelled -+ } -+ -+ sender = event.getSender(); -+ target = event.getCommand(); -+ sentCommandLabel = event.getLabel(); -+ parsedArgs = event.getArgs(); -+ // Purpur end -+ - // Paper start - Plugins do weird things to workaround normal registration - if (target.timings == null) { - target.timings = co.aikar.timings.TimingsManager.getCommandTiming(null, target); -@@ -160,10 +173,10 @@ public class SimpleCommandMap implements CommandMap { - // Paper end - - try { -- try (co.aikar.timings.Timing ignored = target.timings.startTiming()) { // Paper - use try with resources -+ //try (co.aikar.timings.Timing ignored = target.timings.startTiming()) { // Paper - use try with resources // Purpur - // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false) -- target.execute(sender, sentCommandLabel, 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 e64bb57f74e6d6f78927be228825b3e0bdf41f48..c880d0010849ab733ad13bbd18fab3c864d0cf61 100644 ---- a/src/main/java/org/bukkit/command/defaults/VersionCommand.java -+++ b/src/main/java/org/bukkit/command/defaults/VersionCommand.java -@@ -215,7 +215,7 @@ public class VersionCommand extends BukkitCommand { - String version = Bukkit.getVersion(); - // Paper start - if (version.startsWith("null")) { // running from ide? -- setVersionMessage(Component.text("Unknown version, custom build?", NamedTextColor.YELLOW)); -+ setVersionMessage(Component.text("* Unknown version, custom build?", NamedTextColor.RED)); // Purpur - return; - } - setVersionMessage(getVersionFetcher().getVersionMessage(version)); -@@ -256,9 +256,11 @@ public class VersionCommand extends BukkitCommand { - // Paper start - private void setVersionMessage(final @NotNull Component msg) { - lastCheck = System.currentTimeMillis(); -- final Component message = Component.textOfChildren( -- Component.text(Bukkit.getVersionMessage(), NamedTextColor.WHITE), -- Component.newline(), -+ // Purpur start -+ int distance = getVersionFetcher().distance(); -+ final Component message = Component.join(net.kyori.adventure.text.JoinConfiguration.separator(Component.newline()), -+ ChatColor.parseMM("Current Purpur Version: %s%s*", distance == 0 ? "" : distance > 0 ? "" : "", Bukkit.getVersion()), -+ // Purpur end - msg - ); - this.versionMessage = Component.text() -diff --git a/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java b/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java -index 6fcc15d588239481136876d117ab346a8deac1dd..13b903e785a9ef5e513cb9d6483482133cc5f25b 100644 ---- a/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java -+++ b/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java -@@ -227,6 +227,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 7b379fb21e800a766ad022705a12dff6d42279ab..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(since = "1.17") - 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(since = "1.17") - 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 19272cff8d6d040e95b2644d70acdac606e06c16..076fe310d500ebb52e705a3a69e895061702f470 100644 ---- a/src/main/java/org/bukkit/entity/Entity.java -+++ b/src/main/java/org/bukkit/entity/Entity.java -@@ -1172,4 +1172,55 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent - */ - void broadcastHurtAnimation(@NotNull java.util.Collection players); - // Paper end - broadcast hurt animation -+ -+ // 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 bcc6ba95bd21c7972865838c636a03f50b6c1f1a..c3fcd8dd7dbb1e1a18e17c014c1e641149ea5960 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 d21a228bbec0302e75c4db5aa1db54f321143587..a4acc3578e935cd1174474bd1f6ff14db4294fe7 100644 ---- a/src/main/java/org/bukkit/entity/LivingEntity.java -+++ b/src/main/java/org/bukkit/entity/LivingEntity.java -@@ -1468,4 +1468,20 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource - */ - boolean canUseEquipmentSlot(org.bukkit.inventory.@NotNull EquipmentSlot slot); - // Paper end - Expose canUseSlot -+ -+ // Purpur start - API for any mob to burn daylight -+ /** -+ * If this mob will burn in the sunlight -+ * -+ * @return True if mob will burn in sunlight -+ */ -+ boolean shouldBurnInDay(); -+ -+ /** -+ * Set if this mob should burn in the sunlight -+ * -+ * @param shouldBurnInDay True to burn in sunlight -+ */ -+ void setShouldBurnInDay(boolean shouldBurnInDay); -+ // Purpur end - API for any mob to burn daylight - } -diff --git a/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 fac4aec289e07231d80a9890653432f688355afa..6f7f1fc3db0237021fa1bd0f11fe56b2d6d4f84a 100644 ---- a/src/main/java/org/bukkit/entity/Player.java -+++ b/src/main/java/org/bukkit/entity/Player.java -@@ -3911,4 +3911,123 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM - */ - void sendEntityEffect(org.bukkit.@NotNull EntityEffect effect, @NotNull Entity target); - // Paper end - entity effect API -+ -+ // 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 -+ * @deprecated Use {@link #resetIdleDuration()} instead -+ */ -+ void resetIdleTimer(); -+ -+ /** -+ * Creates debug block highlight on specified block location and show it to this player. -+ *

-+ * Clients may be inconsistent in displaying it. -+ * @param location Location to highlight -+ * @param duration Duration for highlight to show in milliseconds -+ */ -+ void sendBlockHighlight(@NotNull Location location, int duration); -+ -+ /** -+ * Creates debug block highlight on specified block location and show it to this player. -+ *

-+ * Clients may be inconsistent in displaying it. -+ * @param location Location to highlight -+ * @param duration Duration for highlight to show in milliseconds -+ * @param argb Color of the highlight. ARGB int. Will be ignored on some versions of vanilla client -+ */ -+ void sendBlockHighlight(@NotNull Location location, int duration, int argb); -+ -+ /** -+ * Creates debug block highlight on specified block location and show it to this player. -+ *

-+ * Clients may be inconsistent in displaying it. -+ * @param location Location to highlight -+ * @param duration Duration for highlight to show in milliseconds -+ * @param text Text to show above the highlight -+ */ -+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text); -+ -+ /** -+ * Creates debug block highlight on specified block location and show it to this player. -+ *

-+ * Clients may be inconsistent in displaying it. -+ * @param location Location to highlight -+ * @param duration Duration for highlight to show in milliseconds -+ * @param text Text to show above the highlight -+ * @param argb Color of the highlight. ARGB int. Will be ignored on some versions of vanilla client -+ */ -+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text, int argb); -+ -+ /** -+ * Creates debug block highlight on specified block location and show it to this player. -+ *

-+ * Clients may be inconsistent in displaying it. -+ * @param location Location to highlight -+ * @param duration Duration for highlight to show in milliseconds -+ * @param color Color of the highlight. Will be ignored on some versions of vanilla client -+ * @param transparency Transparency of the highlight -+ * @throws IllegalArgumentException If transparency is outside 0-255 range -+ */ -+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull org.bukkit.Color color, int transparency); -+ -+ /** -+ * Creates debug block highlight on specified block location and show it to this player. -+ *

-+ * Clients may be inconsistent in displaying it. -+ * @param location Location to highlight -+ * @param duration Duration for highlight to show in milliseconds -+ * @param text Text to show above the highlight -+ * @param color Color of the highlight. Will be ignored on some versions of vanilla client -+ * @param transparency Transparency of the highlight -+ * @throws IllegalArgumentException If transparency is outside 0-255 range -+ */ -+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text, @NotNull org.bukkit.Color color, int transparency); -+ -+ /** -+ * Clears all debug block highlights -+ */ -+ void clearBlockHighlights(); -+ -+ /** -+ * Sends a player the death screen with a specified death message. -+ * -+ * @param message The death message to show the player -+ */ -+ void sendDeathScreen(@NotNull net.kyori.adventure.text.Component message); -+ -+ /** -+ * Sends a player the death screen with a specified death message, -+ * along with the entity that caused the death. -+ * -+ * @param message The death message to show the player -+ * @param killer The entity that killed the player -+ * @deprecated Use {@link #sendDeathScreen(net.kyori.adventure.text.Component)} instead, as 1.20 removed the killer ID from the packet. -+ */ -+ @Deprecated(since = "1.20") -+ default void sendDeathScreen(@NotNull net.kyori.adventure.text.Component message, @Nullable Entity killer) { -+ sendDeathScreen(message); -+ } -+ // Purpur end - } -diff --git a/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 1db3742024e9cd1b70af2d52b4b756a544c019df..9c722a762c88a88bb5ef18c3b9eab8b371360dac 100644 ---- a/src/main/java/org/bukkit/entity/Villager.java -+++ b/src/main/java/org/bukkit/entity/Villager.java -@@ -367,4 +367,14 @@ public interface Villager extends AbstractVillager { - */ - public void clearReputations(); - // Paper end -+ -+ // Purpur start -+ -+ /** -+ * Check if villager is currently lobotomized -+ * -+ * @return True if lobotomized -+ */ -+ boolean isLobotomized(); -+ // Purpur end - } -diff --git a/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 c73489f4b745bc84501ce94f0227b034d9768eae..a97129e71f16ec691759add664bdfd35ab90aaed 100644 ---- a/src/main/java/org/bukkit/entity/Wolf.java -+++ b/src/main/java/org/bukkit/entity/Wolf.java -@@ -108,4 +108,20 @@ public interface Wolf extends Tameable, Sittable, io.papermc.paper.entity.Collar - return Registry.WOLF_VARIANT.getOrThrow(NamespacedKey.minecraft(key)); - } - } -+ -+ // Purpur start -+ /** -+ * Checks if this wolf is rabid -+ * -+ * @return whether the wolf is rabid -+ */ -+ public boolean isRabid(); -+ -+ /** -+ * Sets this wolf to be rabid or not -+ * -+ * @param rabid whether the wolf should be rabid -+ */ -+ public void setRabid(boolean rabid); -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java b/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java -index d1a5424ff3b289f1c82449ef6d88eb52665df41b..f23b0c250f88926c147af0314b5c4d23c5f8dbae 100644 ---- a/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java -+++ b/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java -@@ -308,7 +308,8 @@ public class EntityDamageEvent extends EntityEvent implements Cancellable { - WORLD_BORDER, - /** - * Damage caused when an entity contacts a block such as a Cactus, -- * Dripstone (Stalagmite) or Berry Bush. -+ * Dripstone (Stalagmite) or Berry Bush. (Stonecutters too if you -+ * have the Stonecutter damage Purpur feature enabled) - *

- * Damage: variable - */ -diff --git a/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java b/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java -index 8fdfcbc7d20fe0af6b220ab94516247093637621..f6a8928408e11a5ae723366e4ea1280dfcc6111e 100644 ---- a/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java -+++ b/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java -@@ -216,6 +216,12 @@ public class EntityPotionEffectEvent extends EntityEvent implements Cancellable - * When all effects are removed due to a bucket of milk. - */ - MILK, -+ // Purpur start -+ /** -+ * When a player wears full netherite armor -+ */ -+ NETHERITE_ARMOR, -+ // Purpur end - /** - * When a player gets bad omen after killing a patrol captain. - * -diff --git a/src/main/java/org/bukkit/event/inventory/InventoryType.java b/src/main/java/org/bukkit/event/inventory/InventoryType.java -index 81118a91c2e22e02a1f774d1cc4d3e97064087ce..3ac1e4a821a5b48d3936222cbfddadd3b803deef 100644 ---- a/src/main/java/org/bukkit/event/inventory/InventoryType.java -+++ b/src/main/java/org/bukkit/event/inventory/InventoryType.java -@@ -164,7 +164,7 @@ public enum InventoryType { - SMITHING_NEW(4, "Upgrade Gear", MenuType.SMITHING), - ; - -- 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 MenuType menuType; - 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 f1f97a85ec713c05c882d7588f4a3e4a017f4795..813f6cd253322538bdf96eb323dd23a7809a1c1e 100644 ---- a/src/main/java/org/bukkit/inventory/AnvilInventory.java -+++ b/src/main/java/org/bukkit/inventory/AnvilInventory.java -@@ -138,4 +138,42 @@ public interface AnvilInventory extends Inventory { - setItem(2, result); - } - // Paper end -+ -+ // Purpur start -+ /** -+ * Gets if the player viewing the anvil inventory can bypass experience cost -+ * -+ * @return whether the player viewing the anvil inventory can bypass the experience cost -+ * @deprecated use {@link AnvilView#canBypassCost()}. -+ */ -+ @Deprecated(forRemoval = true, since = "1.21") -+ boolean canBypassCost(); -+ -+ /** -+ * Set if the player viewing the anvil inventory can bypass the experience cost -+ * -+ * @param bypassCost whether the player viewing the anvil inventory can bypass the experience cost -+ * @deprecated use {@link AnvilView#setBypassCost(boolean)}. -+ */ -+ @Deprecated(forRemoval = true, since = "1.21") -+ void setBypassCost(boolean bypassCost); -+ -+ /** -+ * Gets if the player viewing the anvil inventory can do unsafe enchants -+ * -+ * @return whether the player viewing the anvil inventory can do unsafe enchants -+ * @deprecated use {@link AnvilView#canDoUnsafeEnchants()}. -+ */ -+ @Deprecated(forRemoval = true, since = "1.21") -+ boolean canDoUnsafeEnchants(); -+ -+ /** -+ * Set if the player viewing the anvil inventory can do unsafe enchants -+ * -+ * @param canDoUnsafeEnchants whether the player viewing the anvil inventory can do unsafe enchants -+ * @deprecated use {@link AnvilView#setDoUnsafeEnchants(boolean)}. -+ */ -+ @Deprecated(forRemoval = true, since = "1.21") -+ void setDoUnsafeEnchants(boolean canDoUnsafeEnchants); -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java -index 8c9654cd19af8b28fa276a55c5060eb389e60c1c..875124b06d87cd4163f0ab1d4dd75f939622f8aa 100644 ---- a/src/main/java/org/bukkit/inventory/ItemStack.java -+++ b/src/main/java/org/bukkit/inventory/ItemStack.java -@@ -19,6 +19,13 @@ 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; -+// Purpur end - - /** - * Represents a stack of items. -@@ -1318,4 +1325,482 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat - return this.craftDelegate.matchesWithoutData(item, excludeTypes, ignoreCount); - } - // Paper end - data component API -+ -+ // 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 this.craftDelegate.getDisplayName(); -+ } -+ -+ /** -+ * Sets the display name. -+ * -+ * @param name the name to set -+ */ -+ public void setDisplayName(@Nullable String name) { -+ this.craftDelegate.setDisplayName(name); -+ } -+ -+ /** -+ * Checks for existence of a display name. -+ * -+ * @return true if this has a display name -+ */ -+ public boolean hasDisplayName() { -+ return this.craftDelegate.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 this.craftDelegate.getLocalizedName(); -+ } -+ -+ /** -+ * Sets the localized name. -+ * -+ * @param name the name to set -+ */ -+ public void setLocalizedName(@Nullable String name) { -+ this.craftDelegate.setLocalizedName(name); -+ } -+ -+ /** -+ * Checks for existence of a localized name. -+ * -+ * @return true if this has a localized name -+ */ -+ public boolean hasLocalizedName() { -+ return this.craftDelegate.hasLocalizedName(); -+ } -+ -+ /** -+ * Checks for existence of lore. -+ * -+ * @return true if this has lore -+ */ -+ public boolean hasLore() { -+ return this.craftDelegate.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 this.craftDelegate.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 this.craftDelegate.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 this.craftDelegate.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) { -+ return this.craftDelegate.addEnchant(ench, level, ignoreLevelRestriction); -+ } -+ -+ /** -+ * 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) { -+ return this.craftDelegate.removeEnchant(ench); -+ } -+ -+ /** -+ * Checks for the existence of any enchantments. -+ * -+ * @return true if an enchantment exists on this meta -+ */ -+ public boolean hasEnchants() { -+ return this.craftDelegate.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 this.craftDelegate.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) { -+ this.craftDelegate.setCustomModelData(data); -+ } -+ -+ /** -+ * 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 this.craftDelegate.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 this.craftDelegate.hasCustomModelData(); -+ } -+ -+ /** -+ * Returns whether the item has block data currently attached to it. -+ * -+ * @return whether block data is already attached -+ */ -+ public boolean hasBlockData() { -+ return this.craftDelegate.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 this.craftDelegate.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) { -+ this.craftDelegate.setBlockData(blockData); -+ } -+ -+ /** -+ * Gets the repair penalty -+ * -+ * @return the repair penalty -+ */ -+ public int getRepairCost() { -+ return this.craftDelegate.getRepairCost(); -+ } -+ -+ /** -+ * Sets the repair penalty -+ * -+ * @param cost repair penalty -+ */ -+ public void setRepairCost(int cost) { -+ this.craftDelegate.setRepairCost(cost); -+ } -+ -+ /** -+ * Checks to see if this has a repair penalty -+ * -+ * @return true if this has a repair penalty -+ */ -+ public boolean hasRepairCost() { -+ return this.craftDelegate.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 this.craftDelegate.isUnbreakable(); -+ } -+ -+ /** -+ * Sets the unbreakable tag. An unbreakable item will not lose durability. -+ * -+ * @param unbreakable true if set unbreakable -+ */ -+ public void setUnbreakable(boolean unbreakable) { -+ this.craftDelegate.setUnbreakable(unbreakable); -+ } -+ -+ /** -+ * Checks for the existence of any AttributeModifiers. -+ * -+ * @return true if any AttributeModifiers exist -+ */ -+ public boolean hasAttributeModifiers() { -+ return this.craftDelegate.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 this.craftDelegate.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 this.craftDelegate.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 this.craftDelegate.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) { -+ return this.craftDelegate.addAttributeModifier(attribute, modifier); -+ } -+ -+ /** -+ * 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) { -+ this.craftDelegate.setAttributeModifiers(attributeModifiers); -+ } -+ -+ /** -+ * 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) { -+ return this.craftDelegate.removeAttributeModifier(attribute); -+ } -+ -+ /** -+ * 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) { -+ return this.craftDelegate.removeAttributeModifier(slot); -+ } -+ -+ /** -+ * 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) { -+ return this.craftDelegate.removeAttributeModifier(attribute, modifier); -+ } -+ -+ /** -+ * Checks to see if this item has damage -+ * -+ * @return true if this has damage -+ */ -+ public boolean hasDamage() { -+ return this.craftDelegate.hasDamage(); -+ } -+ -+ /** -+ * Gets the damage -+ * -+ * @return the damage -+ */ -+ public int getDamage() { -+ return this.craftDelegate.getDamage(); -+ } -+ -+ /** -+ * Sets the damage -+ * -+ * @param damage item damage -+ */ -+ public void setDamage(int damage) { -+ this.craftDelegate.setDamage(damage); -+ } -+ -+ /** -+ * 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) { -+ return this.craftDelegate.damage(amount, ignoreUnbreaking); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/inventory/RecipeChoice.java b/src/main/java/org/bukkit/inventory/RecipeChoice.java -index 922bb69b5f218e489a6dd5e0f207743c1f1d3d35..9b3e292be334d21eb978373f434bf3811ec4af2b 100644 ---- a/src/main/java/org/bukkit/inventory/RecipeChoice.java -+++ b/src/main/java/org/bukkit/inventory/RecipeChoice.java -@@ -191,6 +191,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)); -@@ -241,6 +242,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; -@@ -250,6 +252,17 @@ public interface RecipeChoice extends Predicate, Cloneable { - return false; - } - -+ // Purpur start -+ @org.jetbrains.annotations.Nullable -+ public Predicate getPredicate() { -+ return predicate; -+ } -+ -+ public void setPredicate(@org.jetbrains.annotations.Nullable Predicate predicate) { -+ this.predicate = predicate; -+ } -+ // Purpur end -+ - @Override - public int hashCode() { - int hash = 7; -diff --git a/src/main/java/org/bukkit/inventory/view/AnvilView.java b/src/main/java/org/bukkit/inventory/view/AnvilView.java -index 3c1aa1e036bee08304c1cdca59f6a5bc0ba306c0..709fb2d1c7e3253034a651a9f68c003601b598a4 100644 ---- a/src/main/java/org/bukkit/inventory/view/AnvilView.java -+++ b/src/main/java/org/bukkit/inventory/view/AnvilView.java -@@ -89,4 +89,34 @@ public interface AnvilView extends InventoryView { - */ - void bypassEnchantmentLevelRestriction(boolean bypassEnchantmentLevelRestriction); - // Paper end - bypass anvil level restrictions -+ -+ // Purpur start -+ /** -+ * Gets if the player viewing the anvil inventory can bypass experience cost -+ * -+ * @return whether the player viewing the anvil inventory can bypass the experience cost -+ */ -+ boolean canBypassCost(); -+ -+ /** -+ * Set if the player viewing the anvil inventory can bypass the experience cost -+ * -+ * @param bypassCost whether the player viewing the anvil inventory can bypass the experience cost -+ */ -+ void setBypassCost(boolean bypassCost); -+ -+ /** -+ * Gets if the player viewing the anvil inventory can do unsafe enchants -+ * -+ * @return whether the player viewing the anvil inventory can do unsafe enchants -+ */ -+ boolean canDoUnsafeEnchants(); -+ -+ /** -+ * Set if the player viewing the anvil inventory can do unsafe enchants -+ * -+ * @param canDoUnsafeEnchants whether the player viewing the anvil inventory can do unsafe enchants -+ */ -+ void setDoUnsafeEnchants(boolean canDoUnsafeEnchants); -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/map/MapPalette.java b/src/main/java/org/bukkit/map/MapPalette.java -index 6995f9cc08d162e3adcd3a28f6bfa6d329661999..b9edb96fc5bac8793477657dbb45ccf98ab17f27 100644 ---- a/src/main/java/org/bukkit/map/MapPalette.java -+++ b/src/main/java/org/bukkit/map/MapPalette.java -@@ -1,6 +1,7 @@ - package org.bukkit.map; - - import com.google.common.base.Preconditions; -+import gg.pufferfish.pufferfish.simd.SIMDDetection; // Pufferfish - import java.awt.Color; - import java.awt.Graphics2D; - import java.awt.Image; -@@ -45,7 +46,7 @@ public final class MapPalette { - } - - @NotNull -- static final Color[] colors = { -+ public static final Color[] colors = { // Pufferfish - public access - c(0, 0, 0, 0), c(0, 0, 0, 0), c(0, 0, 0, 0), c(0, 0, 0, 0), - c(89, 125, 39), c(109, 153, 48), c(127, 178, 56), c(67, 94, 29), - c(174, 164, 115), c(213, 201, 140), c(247, 233, 163), c(130, 123, 86), -@@ -216,9 +217,15 @@ public final class MapPalette { - temp.getRGB(0, 0, temp.getWidth(), temp.getHeight(), pixels, 0, temp.getWidth()); - - byte[] result = new byte[temp.getWidth() * temp.getHeight()]; -+ // Pufferfish start -+ if (!SIMDDetection.isEnabled) { - for (int i = 0; i < pixels.length; i++) { - result[i] = matchColor(new Color(pixels[i], true)); - } -+ } else { -+ gg.pufferfish.pufferfish.simd.VectorMapPalette.matchColorVectorized(pixels, result); -+ } -+ // Pufferfish end - return result; - } - -diff --git a/src/main/java/org/bukkit/map/MapRenderer.java b/src/main/java/org/bukkit/map/MapRenderer.java -index cb7040876a99a5a7e49b81684ef0f3b79584c376..22d8f31b1b8a5dbb5ab3275068642937c097abfe 100644 ---- a/src/main/java/org/bukkit/map/MapRenderer.java -+++ b/src/main/java/org/bukkit/map/MapRenderer.java -@@ -54,4 +54,12 @@ public abstract class MapRenderer { - */ - public abstract void render(@NotNull MapView map, @NotNull MapCanvas canvas, @NotNull Player player); - -+ // Purpur - start -+ /** -+ * Check if this is an explorer (aka treasure) map. -+ * -+ * @return True if explorer map -+ */ -+ public abstract boolean isExplorerMap(); -+ // Purpur - end - } -diff --git a/src/main/java/org/bukkit/permissions/PermissibleBase.java b/src/main/java/org/bukkit/permissions/PermissibleBase.java -index 75b77cc4fe189b4b6baa1af3663dc492e992a266..30b98d1645c571ba5c18e5cc93b0bec3f74b1d3b 100644 ---- a/src/main/java/org/bukkit/permissions/PermissibleBase.java -+++ b/src/main/java/org/bukkit/permissions/PermissibleBase.java -@@ -169,7 +169,7 @@ public class PermissibleBase implements Permissible { - - for (Permission perm : defaults) { - String name = perm.getName().toLowerCase(Locale.ROOT); -- permissions.put(name, new PermissionAttachmentInfo(parent, name, null, true)); -+ permissions.put(name, new PermissionAttachmentInfo(parent, name, null, perm.getDefault().getValue(isOp()))); // Purpur - Bukkit.getServer().getPluginManager().subscribeToPermission(name, parent); - calculateChildPermissions(perm.getChildren(), false, null); - } -@@ -197,7 +197,7 @@ public class PermissibleBase implements Permissible { - String name = entry.getKey(); - - Permission perm = Bukkit.getServer().getPluginManager().getPermission(name); -- boolean value = entry.getValue() ^ invert; -+ boolean value = (entry.getValue() == null && perm != null ? perm.getDefault().getValue(isOp()) : entry.getValue()) ^ invert; // Purpur - String lname = name.toLowerCase(Locale.ROOT); - - permissions.put(lname, new PermissionAttachmentInfo(parent, lname, attachment, value)); -diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index 001465eedafa51ac027a4db51cba6223edfe1171..2e6d62c4f3687e299c34e876c503b400e13be05a 100644 ---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java -+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -@@ -597,7 +597,9 @@ public final class SimplePluginManager implements PluginManager { - - // Paper start - private void handlePluginException(String msg, Throwable ex, Plugin plugin) { -+ gg.pufferfish.pufferfish.sentry.SentryContext.setPluginContext(plugin); // Pufferfish - server.getLogger().log(Level.SEVERE, msg, ex); -+ gg.pufferfish.pufferfish.sentry.SentryContext.removePluginContext(); // Pufferfish - callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerPluginEnableDisableException(msg, ex, plugin))); - } - // Paper end -@@ -667,9 +669,11 @@ public final class SimplePluginManager implements PluginManager { - )); - } - } catch (Throwable ex) { -+ gg.pufferfish.pufferfish.sentry.SentryContext.setEventContext(event, registration); // Pufferfish - // Paper start - error reporting - String msg = "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName(); - server.getLogger().log(Level.SEVERE, msg, ex); -+ gg.pufferfish.pufferfish.sentry.SentryContext.removeEventContext(); // Pufferfish - if (!(event instanceof com.destroystokyo.paper.event.server.ServerExceptionEvent)) { // We don't want to cause an endless event loop - callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerEventException(msg, ex, registration.getPlugin(), registration.getListener(), event))); - } -diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -index b412aaf08901d169ac9fc89b36f9d6ccb95c53d3..e2b631fc160f13ea6e27b69f835bbdf83d6d3dec 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 -@@ -336,7 +337,13 @@ public final class JavaPluginLoader implements PluginLoader { - try { - jPlugin.setEnabled(true); - } catch (Throwable ex) { -+ gg.pufferfish.pufferfish.sentry.SentryContext.setPluginContext(plugin); // Pufferfish - server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); -+ gg.pufferfish.pufferfish.sentry.SentryContext.removePluginContext(); // Pufferfish -+ // Paper start - Disable plugins that fail to load -+ this.server.getPluginManager().disablePlugin(jPlugin); -+ return; -+ // Paper end - } - - // Perhaps abort here, rather than continue going, but as it stands, -@@ -361,7 +368,9 @@ public final class JavaPluginLoader implements PluginLoader { - try { - jPlugin.setEnabled(false); - } catch (Throwable ex) { -+ gg.pufferfish.pufferfish.sentry.SentryContext.setPluginContext(plugin); // Pufferfish - server.getLogger().log(Level.SEVERE, "Error occurred while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); -+ gg.pufferfish.pufferfish.sentry.SentryContext.removePluginContext(); // Pufferfish - } - - if (cloader instanceof PluginClassLoader) { -diff --git a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java -index c66252802c51174bc26f266cb5cdecdd856ff220..97f580fccd06a8db5f592a53c8b95a7a6159adac 100644 ---- a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java -@@ -68,6 +68,7 @@ public class LibraryLoader - @Override - public void transferStarted(@NotNull TransferEvent event) throws TransferCancelledException - { -+ if (!JavaPluginLoader.SuppressLibraryLoaderLogger) // Purpur - logger.log( Level.INFO, "Downloading {0}", event.getResource().getRepositoryUrl() + event.getResource().getResourceName() ); - } - } ); -@@ -94,6 +95,7 @@ public class LibraryLoader - { - return null; - } -+ if (!JavaPluginLoader.SuppressLibraryLoaderLogger) // Purpur - logger.log( Level.INFO, "[{0}] Loading {1} libraries... please wait", new Object[] - { - java.util.Objects.requireNonNullElseGet(desc.getPrefix(), desc::getName), desc.getLibraries().size() // Paper - use configured log prefix -@@ -144,6 +146,7 @@ public class LibraryLoader - } - - jarFiles.add( url ); -+ if (!JavaPluginLoader.SuppressLibraryLoaderLogger) // Purpur - logger.log( Level.INFO, "[{0}] Loaded library {1}", new Object[] - { - java.util.Objects.requireNonNullElseGet(desc.getPrefix(), desc::getName), file // Paper - use configured log prefix -diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -index 7e4f7cb2afbc145e532285c793573ad107bc3033..12449e18180d604e9cbbc744da74a8b222a18e1f 100644 ---- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -@@ -50,6 +50,8 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm - private io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup classLoaderGroup; // Paper - public io.papermc.paper.plugin.provider.entrypoint.DependencyContext dependencyContext; // Paper - -+ private boolean closed = false; // Pufferfish -+ - static { - ClassLoader.registerAsParallelCapable(); - } -@@ -197,6 +199,7 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm - throw new ClassNotFoundException(name); - } - -+ public boolean _airplane_hasClass(@NotNull String name) { return this.classes.containsKey(name); } // Pufferfish - @Override - protected Class findClass(String name) throws ClassNotFoundException { - if (name.startsWith("org.bukkit.") || name.startsWith("net.minecraft.")) { -@@ -204,7 +207,7 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm - } - Class result = classes.get(name); - -- if (result == null) { -+ if (result == null && !this.closed) { // Pufferfish - String path = name.replace('.', '/').concat(".class"); - JarEntry entry = jar.getJarEntry(path); - -@@ -251,6 +254,7 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm - this.setClass(name, result); // Paper - } - -+ if (result == null) throw new ClassNotFoundException(name); // Pufferfish - return result; - } - -@@ -265,6 +269,7 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm - // Paper end - super.close(); - } finally { -+ this.closed = true; // Pufferfish - jar.close(); - } - } -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/entity/StoredEntity.java b/src/main/java/org/purpurmc/purpur/entity/StoredEntity.java -new file mode 100644 -index 0000000000000000000000000000000000000000..29540d55532197d2381a52ea9222b5785d224ef8 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/StoredEntity.java -@@ -0,0 +1,52 @@ -+package org.purpurmc.purpur.entity; -+ -+import org.bukkit.Nameable; -+import org.bukkit.block.EntityBlockStorage; -+import org.bukkit.entity.Entity; -+import org.bukkit.entity.EntityType; -+import org.bukkit.persistence.PersistentDataHolder; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+/** -+ * Represents an entity stored in a block -+ * -+ * @see org.bukkit.block.EntityBlockStorage -+ */ -+public interface StoredEntity extends PersistentDataHolder, Nameable { -+ /** -+ * Checks if this entity has been released yet -+ * -+ * @return if this entity has been released -+ */ -+ boolean hasBeenReleased(); -+ -+ /** -+ * Releases the entity from its stored block -+ * -+ * @return the released entity, or null if unsuccessful (including if this entity has already been released) -+ */ -+ @Nullable -+ T release(); -+ -+ /** -+ * Returns the block in which this entity is stored -+ * -+ * @return the EntityBlockStorage in which this entity is stored, or null if it has been released -+ */ -+ @Nullable -+ EntityBlockStorage getBlockStorage(); -+ -+ /** -+ * Gets the entity type of this stored entity -+ * -+ * @return the type of entity this stored entity represents -+ */ -+ @NotNull -+ EntityType getType(); -+ -+ /** -+ * Writes data to the block entity snapshot. {@link EntityBlockStorage#update()} must be run in order to update the block in game. -+ */ -+ void update(); -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/ExecuteCommandEvent.java b/src/main/java/org/purpurmc/purpur/event/ExecuteCommandEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..55feef2321c7d966c72a33a58cf10136a9cacfa6 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/ExecuteCommandEvent.java -@@ -0,0 +1,127 @@ -+package org.purpurmc.purpur.event; -+ -+import com.google.common.base.Preconditions; -+import org.bukkit.command.Command; -+import org.bukkit.command.CommandSender; -+import org.bukkit.event.Cancellable; -+import org.bukkit.event.Event; -+import org.bukkit.event.HandlerList; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+import org.jspecify.annotations.Nullable; -+ -+/** -+ * This event is called whenever someone runs a command -+ */ -+@NullMarked -+public class ExecuteCommandEvent extends Event implements Cancellable { -+ private static final HandlerList handlers = new HandlerList(); -+ private boolean cancel = false; -+ private CommandSender sender; -+ private Command command; -+ private String label; -+ private @Nullable String[] args; -+ -+ @ApiStatus.Internal -+ public ExecuteCommandEvent(CommandSender sender, Command command, String label, @Nullable String[] args) { -+ this.sender = sender; -+ this.command = command; -+ this.label = label; -+ this.args = args; -+ } -+ -+ /** -+ * Gets the command that the player is attempting to execute. -+ * -+ * @return Command the player is attempting to execute -+ */ -+ public Command getCommand() { -+ return command; -+ } -+ -+ /** -+ * Sets the command that the player will execute. -+ * -+ * @param command New command that the player will execute -+ * @throws IllegalArgumentException if command is null or empty -+ */ -+ public void setCommand(Command command) throws IllegalArgumentException { -+ Preconditions.checkArgument(command != null, "Command cannot be null"); -+ this.command = command; -+ } -+ -+ /** -+ * Gets the sender that this command will be executed as. -+ * -+ * @return Sender this command will be executed as -+ */ -+ public CommandSender getSender() { -+ return sender; -+ } -+ -+ /** -+ * Sets the sender that this command will be executed as. -+ * -+ * @param sender New sender which this event will execute as -+ * @throws IllegalArgumentException if the sender provided is null -+ */ -+ public void setSender(final CommandSender sender) throws IllegalArgumentException { -+ Preconditions.checkArgument(sender != null, "Sender cannot be null"); -+ this.sender = sender; -+ } -+ -+ /** -+ * Get the label used to execute this command -+ * -+ * @return Label used to execute this command -+ */ -+ public String getLabel() { -+ return label; -+ } -+ -+ /** -+ * Set the label used to execute this command -+ * -+ * @param label Label used -+ */ -+ public void setLabel(String label) { -+ this.label = label; -+ } -+ -+ /** -+ * Get the args passed to the command -+ * -+ * @return Args passed to the command -+ */ -+ public String[] getArgs() { -+ return args; -+ } -+ -+ /** -+ * Set the args passed to the command -+ * -+ * @param args Args passed to the command -+ */ -+ public void setArgs(String[] args) { -+ this.args = args; -+ } -+ -+ @Override -+ public boolean isCancelled() { -+ return cancel; -+ } -+ -+ @Override -+ public void setCancelled(boolean cancel) { -+ this.cancel = cancel; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/PlayerAFKEvent.java b/src/main/java/org/purpurmc/purpur/event/PlayerAFKEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e9637b82014fe3f4f4671b24d18f77f3d5e4b8ad ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/PlayerAFKEvent.java -@@ -0,0 +1,71 @@ -+package org.purpurmc.purpur.event; -+ -+import org.bukkit.entity.Player; -+import org.bukkit.event.Cancellable; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.player.PlayerEvent; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+import org.jspecify.annotations.Nullable; -+ -+@NullMarked -+public class PlayerAFKEvent extends PlayerEvent implements Cancellable { -+ private static final HandlerList handlers = new HandlerList(); -+ private final boolean setAfk; -+ private boolean shouldKick; -+ private @Nullable String broadcast; -+ private boolean cancel; -+ -+ @ApiStatus.Internal -+ public PlayerAFKEvent(Player player, boolean setAfk, boolean shouldKick, @Nullable String broadcast, boolean async) { -+ super(player, async); -+ this.setAfk = setAfk; -+ this.shouldKick = shouldKick; -+ this.broadcast = broadcast; -+ } -+ -+ /** -+ * Whether player is going afk or coming back -+ * -+ * @return True if going afk. False is coming back -+ */ -+ public boolean isGoingAfk() { -+ return setAfk; -+ } -+ -+ public boolean shouldKick() { -+ return shouldKick; -+ } -+ -+ public void setShouldKick(boolean shouldKick) { -+ this.shouldKick = shouldKick; -+ } -+ -+ @Nullable -+ public String getBroadcastMsg() { -+ return broadcast; -+ } -+ -+ public void setBroadcastMsg(@Nullable String broadcast) { -+ this.broadcast = broadcast; -+ } -+ -+ @Override -+ public boolean isCancelled() { -+ return cancel; -+ } -+ -+ @Override -+ public void setCancelled(boolean cancel) { -+ this.cancel = cancel; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java b/src/main/java/org/purpurmc/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..795c558b481f4e2a550925bd88b8e7d41711456f ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java -@@ -0,0 +1,83 @@ -+package org.purpurmc.purpur.event; -+ -+import org.bukkit.block.Block; -+import org.bukkit.block.CreatureSpawner; -+import org.bukkit.entity.EntityType; -+import org.bukkit.entity.Player; -+import org.bukkit.event.Cancellable; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.player.PlayerEvent; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+ -+@NullMarked -+public class PlayerSetSpawnerTypeWithEggEvent extends PlayerEvent implements Cancellable { -+ private static final HandlerList handlers = new HandlerList(); -+ private final Block block; -+ private final CreatureSpawner spawner; -+ private EntityType type; -+ private boolean cancel; -+ -+ @ApiStatus.Internal -+ public PlayerSetSpawnerTypeWithEggEvent(Player player, Block block, CreatureSpawner spawner, EntityType type) { -+ super(player); -+ this.block = block; -+ this.spawner = spawner; -+ this.type = type; -+ } -+ -+ /** -+ * Get the spawner Block in the world -+ * -+ * @return Spawner Block -+ */ -+ public Block getBlock() { -+ return block; -+ } -+ -+ /** -+ * Get the spawner state -+ * -+ * @return Spawner state -+ */ -+ public CreatureSpawner getSpawner() { -+ return spawner; -+ } -+ -+ /** -+ * Gets the EntityType being set on the spawner -+ * -+ * @return EntityType being set -+ */ -+ public EntityType getEntityType() { -+ return type; -+ } -+ -+ /** -+ * Sets the EntityType being set on the spawner -+ * -+ * @param type EntityType to set -+ */ -+ public void setEntityType(EntityType type) { -+ this.type = type; -+ } -+ -+ @Override -+ public boolean isCancelled() { -+ return cancel; -+ } -+ -+ @Override -+ public void setCancelled(boolean cancel) { -+ this.cancel = cancel; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/PlayerSetTrialSpawnerTypeWithEggEvent.java b/src/main/java/org/purpurmc/purpur/event/PlayerSetTrialSpawnerTypeWithEggEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1d4dbf60a182a2a5f93c449e387b82743d20616c ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/PlayerSetTrialSpawnerTypeWithEggEvent.java -@@ -0,0 +1,83 @@ -+package org.purpurmc.purpur.event; -+ -+import org.bukkit.block.Block; -+import org.bukkit.block.TrialSpawner; -+import org.bukkit.entity.EntityType; -+import org.bukkit.entity.Player; -+import org.bukkit.event.Cancellable; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.player.PlayerEvent; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+ -+@NullMarked -+public class PlayerSetTrialSpawnerTypeWithEggEvent extends PlayerEvent implements Cancellable { -+ private static final HandlerList handlers = new HandlerList(); -+ private final Block block; -+ private final TrialSpawner spawner; -+ private EntityType type; -+ private boolean cancel; -+ -+ @ApiStatus.Internal -+ public PlayerSetTrialSpawnerTypeWithEggEvent(Player player, Block block, TrialSpawner spawner, EntityType type) { -+ super(player); -+ this.block = block; -+ this.spawner = spawner; -+ this.type = type; -+ } -+ -+ /** -+ * Get the spawner Block in the world -+ * -+ * @return Spawner Block -+ */ -+ public Block getBlock() { -+ return block; -+ } -+ -+ /** -+ * Get the spawner state -+ * -+ * @return Spawner state -+ */ -+ public TrialSpawner getSpawner() { -+ return spawner; -+ } -+ -+ /** -+ * Gets the EntityType being set on the spawner -+ * -+ * @return EntityType being set -+ */ -+ public EntityType getEntityType() { -+ return type; -+ } -+ -+ /** -+ * Sets the EntityType being set on the spawner -+ * -+ * @param type EntityType to set -+ */ -+ public void setEntityType(EntityType type) { -+ this.type = type; -+ } -+ -+ @Override -+ public boolean isCancelled() { -+ return cancel; -+ } -+ -+ @Override -+ public void setCancelled(boolean cancel) { -+ this.cancel = cancel; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/PreBlockExplodeEvent.java b/src/main/java/org/purpurmc/purpur/event/PreBlockExplodeEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4b4d32c58224e1208f14024ca214078a37550bb5 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/PreBlockExplodeEvent.java -@@ -0,0 +1,56 @@ -+package org.purpurmc.purpur.event; -+ -+import org.bukkit.ExplosionResult; -+import org.bukkit.block.Block; -+import org.bukkit.block.BlockState; -+import org.bukkit.event.Cancellable; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.block.BlockExplodeEvent; -+import org.jetbrains.annotations.ApiStatus; -+import java.util.Collections; -+import org.jspecify.annotations.NullMarked; -+ -+/** -+ * Called before a block's explosion is processed -+ */ -+@NullMarked -+public class PreBlockExplodeEvent extends BlockExplodeEvent implements Cancellable { -+ private static final HandlerList handlers = new HandlerList(); -+ private boolean cancelled; -+ private final float yield; -+ -+ @ApiStatus.Internal -+ public PreBlockExplodeEvent(final Block what, final float yield, BlockState explodedBlockState, ExplosionResult result) { -+ super(what, explodedBlockState, Collections.emptyList(), yield, result); -+ this.yield = yield; -+ this.cancelled = false; -+ } -+ -+ /** -+ * Returns the percentage of blocks to drop from this explosion -+ * -+ * @return The yield. -+ */ -+ public float getYield() { -+ return yield; -+ } -+ -+ @Override -+ public boolean isCancelled() { -+ return this.cancelled; -+ } -+ -+ @Override -+ public void setCancelled(boolean cancel) { -+ this.cancelled = cancel; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/entity/BeeFoundFlowerEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/BeeFoundFlowerEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7f631a41abee4640a37339a7896ce96e61747735 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/entity/BeeFoundFlowerEvent.java -@@ -0,0 +1,48 @@ -+package org.purpurmc.purpur.event.entity; -+ -+import org.bukkit.Location; -+import org.bukkit.entity.Bee; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.entity.EntityEvent; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+import org.jspecify.annotations.Nullable; -+ -+/** -+ * Called when a bee targets a flower -+ */ -+@NullMarked -+public class BeeFoundFlowerEvent extends EntityEvent { -+ private static final HandlerList handlers = new HandlerList(); -+ private final Location location; -+ -+ @ApiStatus.Internal -+ public BeeFoundFlowerEvent(Bee bee, @Nullable Location location) { -+ super(bee); -+ this.location = location; -+ } -+ -+ @Override -+ public Bee getEntity() { -+ return (Bee) super.getEntity(); -+ } -+ -+ /** -+ * Returns the location of the flower that the bee targets -+ * -+ * @return The location of the flower -+ */ -+ @Nullable -+ public Location getLocation() { -+ return location; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/entity/BeeStartedPollinatingEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/BeeStartedPollinatingEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e260145d6dc556bbe9e3654296b965c4e393084d ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/entity/BeeStartedPollinatingEvent.java -@@ -0,0 +1,46 @@ -+package org.purpurmc.purpur.event.entity; -+ -+import org.bukkit.Location; -+import org.bukkit.entity.Bee; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.entity.EntityEvent; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+ -+/** -+ * Called when a bee starts pollinating -+ */ -+@NullMarked -+public class BeeStartedPollinatingEvent extends EntityEvent { -+ private static final HandlerList handlers = new HandlerList(); -+ private final Location location; -+ -+ @ApiStatus.Internal -+ public BeeStartedPollinatingEvent(Bee bee, Location location) { -+ super(bee); -+ this.location = location; -+ } -+ -+ @Override -+ public Bee getEntity() { -+ return (Bee) super.getEntity(); -+ } -+ -+ /** -+ * Returns the location of the flower that the bee pollinates -+ * -+ * @return The location of the flower -+ */ -+ public Location getLocation() { -+ return this.location; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/entity/BeeStopPollinatingEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/BeeStopPollinatingEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8b2b351d620c749cdf58d7e824b55cf55578fde6 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/entity/BeeStopPollinatingEvent.java -@@ -0,0 +1,60 @@ -+package org.purpurmc.purpur.event.entity; -+ -+import org.bukkit.Location; -+import org.bukkit.entity.Bee; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.entity.EntityEvent; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+import org.jspecify.annotations.Nullable; -+ -+/** -+ * Called when a bee stops pollinating -+ */ -+@NullMarked -+public class BeeStopPollinatingEvent extends EntityEvent { -+ private static final HandlerList handlers = new HandlerList(); -+ private final Location location; -+ private final boolean success; -+ -+ @ApiStatus.Internal -+ public BeeStopPollinatingEvent(Bee bee, @Nullable Location location, boolean success) { -+ super(bee); -+ this.location = location; -+ this.success = success; -+ } -+ -+ @Override -+ public Bee getEntity() { -+ return (Bee) super.getEntity(); -+ } -+ -+ /** -+ * Returns the location of the flower that the bee stopped pollinating -+ * -+ * @return The location of the flower -+ */ -+ @Nullable -+ public Location getLocation() { -+ return location; -+ } -+ -+ /** -+ * Returns whether the bee successfully pollinated the flower -+ * -+ * @return True if the pollination was successful -+ */ -+ public boolean wasSuccessful() { -+ return success; -+ } -+ -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/entity/EntityTeleportHinderedEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/EntityTeleportHinderedEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..daf3bbf83ee76322828a38814b483fa2b337bd60 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/entity/EntityTeleportHinderedEvent.java -@@ -0,0 +1,114 @@ -+package org.purpurmc.purpur.event.entity; -+ -+import org.bukkit.entity.Entity; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.entity.EntityEvent; -+import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+import org.jspecify.annotations.Nullable; -+ -+/** -+ * Fired when an entity is hindered from teleporting. -+ */ -+@NullMarked -+public class EntityTeleportHinderedEvent extends EntityEvent { -+ private static final HandlerList handlers = new HandlerList(); -+ -+ private final Reason reason; -+ -+ private final @Nullable TeleportCause teleportCause; -+ -+ private boolean retry = false; -+ -+ @ApiStatus.Internal -+ public EntityTeleportHinderedEvent(Entity what, Reason reason, @Nullable TeleportCause teleportCause) { -+ super(what); -+ this.reason = reason; -+ this.teleportCause = teleportCause; -+ } -+ -+ /** -+ * @return why the teleport was hindered. -+ */ -+ public Reason getReason() { -+ return reason; -+ } -+ -+ /** -+ * @return why the teleport occurred if cause was given, otherwise {@code null}. -+ */ -+ @Nullable -+ public TeleportCause getTeleportCause() { -+ return teleportCause; -+ } -+ -+ /** -+ * Whether the teleport should be retried. -+ *

-+ * Note that this can put the server in a never-ending loop of trying to teleport someone resulting in a stack -+ * overflow. Do not retry more than necessary. -+ *

-+ * -+ * @return whether the teleport should be retried. -+ */ -+ public boolean shouldRetry() { -+ return retry; -+ } -+ -+ /** -+ * Sets whether the teleport should be retried. -+ *

-+ * Note that this can put the server in a never-ending loop of trying to teleport someone resulting in a stack -+ * overflow. Do not retry more than necessary. -+ *

-+ * -+ * @param retry whether the teleport should be retried. -+ */ -+ public void setShouldRetry(boolean retry) { -+ this.retry = retry; -+ } -+ -+ /** -+ * Calls the event and tests if should retry. -+ * -+ * @return whether the teleport should be retried. -+ */ -+ @Override -+ public boolean callEvent() { -+ super.callEvent(); -+ return shouldRetry(); -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+ -+ /** -+ * Reason for hindrance in teleports. -+ */ -+ public enum Reason { -+ /** -+ * The teleported entity is a passenger of another entity. -+ */ -+ IS_PASSENGER, -+ -+ /** -+ * The teleported entity has passengers. -+ */ -+ IS_VEHICLE, -+ -+ /** -+ * The teleport event was cancelled. -+ *

-+ * This is only caused by players teleporting. -+ *

-+ */ -+ EVENT_CANCELLED, -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/entity/GoatRamEntityEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/GoatRamEntityEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f0a7fe694db145294ff93d320382d1baecc68702 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/entity/GoatRamEntityEvent.java -@@ -0,0 +1,58 @@ -+package org.purpurmc.purpur.event.entity; -+ -+import org.bukkit.entity.Goat; -+import org.bukkit.entity.LivingEntity; -+import org.bukkit.event.Cancellable; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.entity.EntityEvent; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+ -+/** -+ * Called when a goat rams an entity -+ */ -+@NullMarked -+public class GoatRamEntityEvent extends EntityEvent implements Cancellable { -+ private static final HandlerList handlers = new HandlerList(); -+ private final LivingEntity rammedEntity; -+ private boolean cancelled; -+ -+ @ApiStatus.Internal -+ public GoatRamEntityEvent(Goat goat, LivingEntity rammedEntity) { -+ super(goat); -+ this.rammedEntity = rammedEntity; -+ } -+ -+ /** -+ * Returns the entity that was rammed by the goat -+ * -+ * @return The rammed entity -+ */ -+ public LivingEntity getRammedEntity() { -+ return this.rammedEntity; -+ } -+ -+ @Override -+ public Goat getEntity() { -+ return (Goat) super.getEntity(); -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+ -+ @Override -+ public boolean isCancelled() { -+ return this.cancelled; -+ } -+ -+ @Override -+ public void setCancelled(boolean cancel) { -+ this.cancelled = cancel; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/entity/LlamaJoinCaravanEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/LlamaJoinCaravanEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e34c37579dc8a5a108c03b9eff6bb916a910d867 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/entity/LlamaJoinCaravanEvent.java -@@ -0,0 +1,60 @@ -+package org.purpurmc.purpur.event.entity; -+ -+import org.bukkit.entity.Llama; -+import org.bukkit.event.Cancellable; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.entity.EntityEvent; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+ -+/** -+ * Called when a Llama tries to join a caravan. -+ *

-+ * Cancelling the event will not let the Llama join. To prevent future attempts -+ * at joining a caravan use {@link Llama#setShouldJoinCaravan(boolean)}. -+ */ -+@NullMarked -+public class LlamaJoinCaravanEvent extends EntityEvent implements Cancellable { -+ private static final HandlerList handlers = new HandlerList(); -+ private boolean canceled; -+ private final Llama head; -+ -+ @ApiStatus.Internal -+ public LlamaJoinCaravanEvent(Llama llama, Llama head) { -+ super(llama); -+ this.head = head; -+ } -+ -+ @Override -+ public Llama getEntity() { -+ return (Llama) entity; -+ } -+ -+ /** -+ * Get the Llama that this Llama is about to follow -+ * -+ * @return Llama about to be followed -+ */ -+ public Llama getHead() { -+ return head; -+ } -+ -+ @Override -+ public boolean isCancelled() { -+ return canceled; -+ } -+ -+ @Override -+ public void setCancelled(boolean cancel) { -+ canceled = cancel; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/entity/LlamaLeaveCaravanEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/LlamaLeaveCaravanEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..23ea41ff5dc43a915a263aeb1a246705de8bf9e1 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/entity/LlamaLeaveCaravanEvent.java -@@ -0,0 +1,34 @@ -+package org.purpurmc.purpur.event.entity; -+ -+import org.bukkit.entity.Llama; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.entity.EntityEvent; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+ -+/** -+ * Called when a Llama leaves a caravan -+ */ -+@NullMarked -+public class LlamaLeaveCaravanEvent extends EntityEvent { -+ private static final HandlerList handlers = new HandlerList(); -+ -+ @ApiStatus.Internal -+ public LlamaLeaveCaravanEvent(Llama llama) { -+ super(llama); -+ } -+ -+ @Override -+ public Llama getEntity() { -+ return (Llama) entity; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/entity/PreEntityExplodeEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/PreEntityExplodeEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d56fb066455007cc710f7ba34ba722af6e89bc1d ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/entity/PreEntityExplodeEvent.java -@@ -0,0 +1,66 @@ -+package org.purpurmc.purpur.event.entity; -+ -+import org.bukkit.ExplosionResult; -+import org.bukkit.Location; -+import org.bukkit.event.Cancellable; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.entity.EntityExplodeEvent; -+import org.jetbrains.annotations.ApiStatus; -+import java.util.Collections; -+import org.jspecify.annotations.NullMarked; -+ -+/** -+ * Called before an entity's explosion is processed -+ */ -+@NullMarked -+public class PreEntityExplodeEvent extends EntityExplodeEvent implements Cancellable { -+ private static final HandlerList handlers = new HandlerList(); -+ private boolean cancelled; -+ private final float yield; -+ private final Location location; -+ -+ @ApiStatus.Internal -+ public PreEntityExplodeEvent(org.bukkit.entity.Entity what, final Location location, final float yield, ExplosionResult result) { -+ super(what, location, Collections.emptyList(), yield, result); -+ this.cancelled = false; -+ this.yield = yield; -+ this.location = location; -+ } -+ -+ /** -+ * Returns the percentage of blocks to drop from this explosion -+ * -+ * @return The yield. -+ */ -+ public float getYield() { -+ return yield; -+ } -+ -+ /** -+ * Returns the location where the explosion happened. -+ * -+ * @return The location of the explosion -+ */ -+ public Location getLocation() { -+ return location; -+ } -+ -+ @Override -+ public boolean isCancelled() { -+ return this.cancelled; -+ } -+ -+ @Override -+ public void setCancelled(boolean cancel) { -+ this.cancelled = cancel; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/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..c31a656daa3df1ab87302d8f14110a828c920102 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/entity/RidableMoveEvent.java -@@ -0,0 +1,100 @@ -+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.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+ -+/** -+ * Triggered when a ridable mob moves with a rider -+ */ -+@NullMarked -+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; -+ -+ @ApiStatus.Internal -+ public RidableMoveEvent(Mob entity, Player rider, Location from, Location to) { -+ super(entity); -+ this.rider = rider; -+ this.from = from; -+ this.to = to; -+ } -+ -+ @Override -+ public Mob getEntity() { -+ return (Mob) entity; -+ } -+ -+ 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 -+ */ -+ 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(Location from) { -+ validateLocation(from); -+ this.from = from; -+ } -+ -+ /** -+ * Gets the location this entity moved to -+ * -+ * @return Location the entity moved to -+ */ -+ 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(Location to) { -+ validateLocation(to); -+ this.to = to; -+ } -+ -+ private void validateLocation(Location loc) { -+ Preconditions.checkArgument(loc != null, "Cannot use null location!"); -+ Preconditions.checkArgument(loc.getWorld() != null, "Cannot use null location with null world!"); -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ 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..02de629f066ef7d4898b3053efa957edeea16a3f ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/entity/RidableSpacebarEvent.java -@@ -0,0 +1,38 @@ -+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.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+ -+@NullMarked -+public class RidableSpacebarEvent extends EntityEvent implements Cancellable { -+ private static final HandlerList handlers = new HandlerList(); -+ private boolean cancelled; -+ -+ @ApiStatus.Internal -+ public RidableSpacebarEvent(Entity entity) { -+ super(entity); -+ } -+ -+ @Override -+ public boolean isCancelled() { -+ return cancelled; -+ } -+ -+ @Override -+ public void setCancelled(boolean cancel) { -+ cancelled = cancel; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ 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..b2199854b5c7e74a673cbadbe584e5aaebbe3883 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/inventory/AnvilTakeResultEvent.java -@@ -0,0 +1,50 @@ -+package org.purpurmc.purpur.event.inventory; -+ -+import org.bukkit.entity.HumanEntity; -+import org.bukkit.entity.Player; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.inventory.InventoryEvent; -+import org.bukkit.inventory.AnvilInventory; -+import org.bukkit.inventory.InventoryView; -+import org.bukkit.inventory.ItemStack; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+ -+/** -+ * Called when a player takes the result item out of an anvil -+ */ -+@NullMarked -+public class AnvilTakeResultEvent extends InventoryEvent { -+ private static final HandlerList handlers = new HandlerList(); -+ private final Player player; -+ private final ItemStack result; -+ -+ @ApiStatus.Internal -+ public AnvilTakeResultEvent(HumanEntity player, InventoryView view, ItemStack result) { -+ super(view); -+ this.player = (Player) player; -+ this.result = result; -+ } -+ -+ public Player getPlayer() { -+ return player; -+ } -+ -+ public ItemStack getResult() { -+ return result; -+ } -+ -+ @Override -+ public AnvilInventory getInventory() { -+ return (AnvilInventory) super.getInventory(); -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/inventory/AnvilUpdateResultEvent.java b/src/main/java/org/purpurmc/purpur/event/inventory/AnvilUpdateResultEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4293c4a57c1c054e8248b7712e8664bd4cb1a972 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/inventory/AnvilUpdateResultEvent.java -@@ -0,0 +1,35 @@ -+package org.purpurmc.purpur.event.inventory; -+ -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.inventory.InventoryEvent; -+import org.bukkit.inventory.AnvilInventory; -+import org.bukkit.inventory.InventoryView; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+ -+/** -+ * Called when anvil slots change, triggering the result slot to be updated -+ */ -+@NullMarked -+public class AnvilUpdateResultEvent extends InventoryEvent { -+ private static final HandlerList handlers = new HandlerList(); -+ -+ @ApiStatus.Internal -+ public AnvilUpdateResultEvent(InventoryView view) { -+ super(view); -+ } -+ -+ @Override -+ public AnvilInventory getInventory() { -+ return (AnvilInventory) super.getInventory(); -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/inventory/GrindstoneTakeResultEvent.java b/src/main/java/org/purpurmc/purpur/event/inventory/GrindstoneTakeResultEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d6db2d355553c9c54b83328d237b9c75e7a8e375 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/inventory/GrindstoneTakeResultEvent.java -@@ -0,0 +1,72 @@ -+package org.purpurmc.purpur.event.inventory; -+ -+import org.bukkit.entity.HumanEntity; -+import org.bukkit.entity.Player; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.inventory.InventoryEvent; -+import org.bukkit.inventory.GrindstoneInventory; -+import org.bukkit.inventory.InventoryView; -+import org.bukkit.inventory.ItemStack; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+ -+/** -+ * Called when a player takes the result item out of a Grindstone -+ */ -+@NullMarked -+public class GrindstoneTakeResultEvent extends InventoryEvent { -+ private static final HandlerList handlers = new HandlerList(); -+ private final Player player; -+ private final ItemStack result; -+ private int experienceAmount; -+ -+ @ApiStatus.Internal -+ public GrindstoneTakeResultEvent(HumanEntity player, InventoryView view, ItemStack result, int experienceAmount) { -+ super(view); -+ this.player = (Player) player; -+ this.result = result; -+ this.experienceAmount = experienceAmount; -+ } -+ -+ public Player getPlayer() { -+ return player; -+ } -+ -+ public ItemStack getResult() { -+ return result; -+ } -+ -+ @Override -+ public GrindstoneInventory getInventory() { -+ return (GrindstoneInventory) super.getInventory(); -+ } -+ -+ /** -+ * Get the amount of experience this transaction will give -+ * (takes priority over and uses result from {@link org.bukkit.event.block.BlockExpEvent}) -+ * -+ * @return Amount of experience to give -+ */ -+ public int getExperienceAmount() { -+ return this.experienceAmount; -+ } -+ -+ /** -+ * Set the amount of experience this transaction will give -+ * (takes priority over {@link org.bukkit.event.block.BlockExpEvent}) -+ * -+ * @param experienceAmount Amount of experience to give -+ */ -+ public void setExperienceAmount(int experienceAmount) { -+ this.experienceAmount = experienceAmount; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/event/player/PlayerBookTooLargeEvent.java b/src/main/java/org/purpurmc/purpur/event/player/PlayerBookTooLargeEvent.java -new file mode 100644 -index 0000000000000000000000000000000000000000..31cce9f4e398135016114b96254376325a22ba7c ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/event/player/PlayerBookTooLargeEvent.java -@@ -0,0 +1,65 @@ -+package org.purpurmc.purpur.event.player; -+ -+import org.bukkit.Bukkit; -+import org.bukkit.entity.Player; -+import org.bukkit.event.HandlerList; -+import org.bukkit.event.player.PlayerEvent; -+import org.bukkit.inventory.ItemStack; -+import org.jetbrains.annotations.ApiStatus; -+import org.jspecify.annotations.NullMarked; -+ -+/** -+ * Called when a player tries to bypass book limitations -+ */ -+@NullMarked -+public class PlayerBookTooLargeEvent extends PlayerEvent { -+ private static final HandlerList handlers = new HandlerList(); -+ private final ItemStack book; -+ private boolean kickPlayer = true; -+ -+ /** -+ * @param player The player -+ * @param book The book -+ */ -+ @ApiStatus.Internal -+ public PlayerBookTooLargeEvent(Player player, ItemStack book) { -+ super(player, !Bukkit.isPrimaryThread()); -+ this.book = book; -+ } -+ -+ /** -+ * Get the book containing the wanted edits -+ * -+ * @return The book -+ */ -+ public ItemStack getBook() { -+ return book; -+ } -+ -+ /** -+ * Whether server should kick the player or not -+ * -+ * @return True to kick player -+ */ -+ public boolean shouldKickPlayer() { -+ return kickPlayer; -+ } -+ -+ /** -+ * Whether server should kick the player or not -+ * -+ * @param kickPlayer True to kick player -+ */ -+ public void setShouldKickPlayer(boolean kickPlayer) { -+ this.kickPlayer = kickPlayer; -+ } -+ -+ @Override -+ public HandlerList getHandlers() { -+ return handlers; -+ } -+ -+ public static HandlerList getHandlerList() { -+ return handlers; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/language/Language.java b/src/main/java/org/purpurmc/purpur/language/Language.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cbdad4cf09c170064a45644efdf7aa0b28608301 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/language/Language.java -@@ -0,0 +1,60 @@ -+package org.purpurmc.purpur.language; -+ -+import net.kyori.adventure.translation.Translatable; -+import org.jspecify.annotations.NullMarked; -+import org.jspecify.annotations.Nullable; -+ -+/** -+ * Represents a language that can translate translation keys -+ */ -+@NullMarked -+public abstract class Language { -+ private static @Nullable Language language; -+ -+ /** -+ * Returns the default language of the server -+ */ -+ @Nullable -+ public static Language getLanguage() { -+ return language; -+ } -+ -+ public static void setLanguage(Language language) { -+ if (Language.language != null) { -+ throw new UnsupportedOperationException("Cannot redefine singleton Language"); -+ } -+ Language.language = language; -+ } -+ -+ /** -+ * Checks if a certain translation key is translatable with this language -+ * @param key The translation key -+ * @return Whether this language can translate the key -+ */ -+ abstract public boolean has(String key); -+ -+ /** -+ * Checks if a certain translation key is translatable with this language -+ * @param key The translation key -+ * @return Whether this language can translate the key -+ */ -+ public boolean has(Translatable key) { -+ return has(key.translationKey()); -+ } -+ -+ /** -+ * Translates a translation key to this language -+ * @param key The translation key -+ * @return The translated key, or the translation key if it couldn't be translated -+ */ -+ abstract public String getOrDefault(String key); -+ -+ /** -+ * Translates a translation key to this language -+ * @param key The translation key -+ * @return The translated key, or the translation key if it couldn't be translated -+ */ -+ public String getOrDefault(Translatable key) { -+ return getOrDefault(key.translationKey()); -+ } -+} -diff --git a/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 5b0d26c68f6c30fd3a9125e96012a7d162afb402..c92dc62e16aec026f32c5a4739ac041e5c88ed03 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/generated-api/0001-Purpur-Generated-API-Changes.patch b/patches/generated-api/0001-Purpur-Generated-API-Changes.patch deleted file mode 100644 index 027f28d..0000000 --- a/patches/generated-api/0001-Purpur-Generated-API-Changes.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: granny -Date: Thu, 18 Jan 2024 21:01:12 +0900 -Subject: [PATCH] Purpur Generated API Changes - -PurpurMC -Copyright (C) 2024 PurpurMC - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -diff --git a/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/com/destroystokyo/paper/entity/ai/VanillaGoal.java -index 35dfd25f21ca67b7f4d69326500980f4a021ef49..a9816fbfa466b3fe3f82c19aeeeb564c660e4b6a 100644 ---- a/com/destroystokyo/paper/entity/ai/VanillaGoal.java -+++ b/com/destroystokyo/paper/entity/ai/VanillaGoal.java -@@ -441,6 +441,18 @@ public interface VanillaGoal extends Goal { - - GoalKey ZOMBIE_ATTACK_TURTLE_EGG = create("zombie_attack_turtle_egg", Zombie.class); - -+ // Purpur start -+ GoalKey MOB_HAS_RIDER = GoalKey.of(Mob.class, NamespacedKey.minecraft("has_rider")); -+ GoalKey HORSE_HAS_RIDER = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("horse_has_rider")); -+ GoalKey LLAMA_HAS_RIDER = GoalKey.of(Llama.class, NamespacedKey.minecraft("llama_has_rider")); -+ 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 -+ - private static GoalKey create(final String key, final Class type) { - return GoalKey.of(type, NamespacedKey.minecraft(key)); - } diff --git a/patches/server/0001-Purpur-Server-Changes.patch b/patches/server/0001-Purpur-Server-Changes.patch deleted file mode 100644 index d300200..0000000 --- a/patches/server/0001-Purpur-Server-Changes.patch +++ /dev/null @@ -1,28021 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: granny -Date: Wed, 25 Dec 2024 15:15:32 +0900 -Subject: [PATCH] Purpur Server Changes - -PurpurMC -Copyright (C) 2024 PurpurMC - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -diff --git a/build.gradle.kts b/build.gradle.kts -index 2da91ed6363c0851e4c459188f5e8ef5475e0c97..624588207afdc52adae14e5de3d18fe1c330d832 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -25,7 +25,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider { - // Paper end - configure mockito agent that is needed in newer java versions - - dependencies { -- implementation(project(":paper-api")) -+ implementation(project(":purpur-api")) // Pufferfish // Paper // Purpur - Rebrand - implementation("ca.spottedleaf:concurrentutil:0.0.2") // Paper - Add ConcurrentUtil dependency - // Paper start - implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+ -@@ -61,6 +61,17 @@ dependencies { - runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") - runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") - -+ // Pufferfish start -+ implementation("org.yaml:snakeyaml:1.32") -+ implementation ("com.github.carleslc.Simple-YAML:Simple-Yaml:1.8.4") { // Purpur - Fix pufferfish issues -+ exclude(group="org.yaml", module="snakeyaml") -+ } -+ // Pufferfish end -+ -+ implementation("org.mozilla:rhino-runtime:1.7.14") // Purpur - Rebrand -+ implementation("org.mozilla:rhino-engine:1.7.14") // Purpur - Rebrand -+ implementation("dev.omega24:upnp4j:1.0") // Purpur - Rebrand -+ - testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test - testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") - testImplementation("org.junit.platform:junit-platform-suite-engine:1.10.0") -@@ -87,6 +98,14 @@ paperweight { - craftBukkitPackageVersion.set("v1_21_R3") // also needs to be updated in MappingEnvironment - } - -+ -+// Pufferfish Start -+tasks.withType { -+ val compilerArgs = options.compilerArgs -+ compilerArgs.add("--add-modules=jdk.incubator.vector") -+} -+// Pufferfish End -+ - tasks.jar { - archiveClassifier.set("dev") - -@@ -100,14 +119,14 @@ tasks.jar { - val gitBranch = git("rev-parse", "--abbrev-ref", "HEAD").getText().trim() // Paper - attributes( - "Main-Class" to "org.bukkit.craftbukkit.Main", -- "Implementation-Title" to "Paper", -+ "Implementation-Title" to "Purpur", // Pufferfish // Purpur - Rebrand - "Implementation-Version" to implementationVersion, - "Implementation-Vendor" to date, // Paper -- "Specification-Title" to "Paper", -+ "Specification-Title" to "Purpur", // Pufferfish // Purpur - Rebrand - "Specification-Version" to project.version, -- "Specification-Vendor" to "Paper Team", -- "Brand-Id" to "papermc:paper", -- "Brand-Name" to "Paper", -+ "Specification-Vendor" to "Purpur Team", // Pufferfish // Purpur - Rebrand -+ "Brand-Id" to "purpurmc:purpur", // Pufferfish // Purpur - Rebrand -+ "Brand-Name" to "Purpur", // Pufferfish // Purpur - Rebrand - "Build-Number" to (build ?: ""), - "Build-Time" to Instant.now().toString(), - "Git-Branch" to gitBranch, // Paper -@@ -173,7 +192,7 @@ fun TaskContainer.registerRunTask( - name: String, - block: JavaExec.() -> Unit - ): TaskProvider = register(name) { -- group = "paper" -+ group = "paperweight" // Purpur - Rebrand - mainClass.set("org.bukkit.craftbukkit.Main") - standardInput = System.`in` - workingDir = rootProject.layout.projectDirectory -diff --git a/src/log4jPlugins/java/org/purpurmc/purpur/gui/HighlightErrorConverter.java b/src/log4jPlugins/java/org/purpurmc/purpur/gui/HighlightErrorConverter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..15a226e3854d731f7724025ea3459c8ace07630c ---- /dev/null -+++ b/src/log4jPlugins/java/org/purpurmc/purpur/gui/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/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java -index c21e00812f1aaa1279834a0562d360d6b89e146c..877d2095a066854939f260ca4b0b8c7b5abb620f 100644 ---- a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java -+++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java -@@ -18,7 +18,7 @@ public final class IteratorSafeOrderedReferenceSet { - - private final double maxFragFactor; - -- private int iteratorCount; -+ private final java.util.concurrent.atomic.AtomicInteger iteratorCount = new java.util.concurrent.atomic.AtomicInteger(); // Pufferfish - async mob spawning - - public IteratorSafeOrderedReferenceSet() { - this(16, 0.75f, 16, 0.2); -@@ -79,7 +79,7 @@ public final class IteratorSafeOrderedReferenceSet { - } - - public int createRawIterator() { -- ++this.iteratorCount; -+ this.iteratorCount.incrementAndGet(); // Pufferfish - async mob spawning - if (this.indexMap.isEmpty()) { - return -1; - } else { -@@ -100,7 +100,7 @@ public final class IteratorSafeOrderedReferenceSet { - } - - public void finishRawIterator() { -- if (--this.iteratorCount == 0) { -+ if (this.iteratorCount.decrementAndGet() == 0) { // Pufferfish - async mob spawning - if (this.getFragFactor() >= this.maxFragFactor) { - this.defrag(); - } -@@ -117,7 +117,7 @@ public final class IteratorSafeOrderedReferenceSet { - throw new IllegalStateException(); - } - this.listElements[index] = null; -- if (this.iteratorCount == 0 && this.getFragFactor() >= this.maxFragFactor) { -+ if (this.iteratorCount.get() == 0 && this.getFragFactor() >= this.maxFragFactor) { // Pufferfish - async mob spawning - this.defrag(); - } - //this.check(); -@@ -219,7 +219,7 @@ public final class IteratorSafeOrderedReferenceSet { - } - - public IteratorSafeOrderedReferenceSet.Iterator iterator(final int flags) { -- ++this.iteratorCount; -+ this.iteratorCount.incrementAndGet(); // Pufferfish - async mob spawning - return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); - } - -diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java -index 8f62879582195d8ae4f64bd23f752fa133b1c973..be1bb14dca9367b9685841985b6198376986c496 100644 ---- a/src/main/java/com/destroystokyo/paper/Metrics.java -+++ b/src/main/java/com/destroystokyo/paper/Metrics.java -@@ -592,7 +592,7 @@ public class Metrics { - boolean logFailedRequests = config.getBoolean("logFailedRequests", false); - // Only start Metrics, if it's enabled in the config - if (config.getBoolean("enabled", true)) { -- Metrics metrics = new Metrics("Paper", serverUUID, logFailedRequests, Bukkit.getLogger()); -+ Metrics metrics = new Metrics("Purpur", serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish // Purpur - Purpur config files - - metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { - String minecraftVersion = Bukkit.getVersion(); -@@ -601,16 +601,8 @@ public class Metrics { - })); - - metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size())); -- metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline")); -- final String paperVersion; -- final String implVersion = org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion(); -- if (implVersion != null) { -- final String buildOrHash = implVersion.substring(implVersion.lastIndexOf('-') + 1); -- paperVersion = "git-Paper-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash); -- } else { -- paperVersion = "unknown"; -- } -- metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> paperVersion)); -+ metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ? "bungee" : "offline"))); // Purpur - Purpur config files -+ metrics.addCustomChart(new Metrics.SimplePie("purpur_version", () -> (org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() != null) ? org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() : "unknown")); // Purpur - Purpur config files - - metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { - Map> map = new HashMap<>(); -diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -index 532306cacd52579cdf37e4aca25887b1ed3ba6a1..fe66e43c27e0798770e102d1385bacbaa90bda07 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -@@ -35,7 +35,10 @@ public class PaperVersionFetcher implements VersionFetcher { - private static final Logger LOGGER = LogUtils.getClassLogger(); - private static final int DISTANCE_ERROR = -1; - private static final int DISTANCE_UNKNOWN = -2; -- private static final String DOWNLOAD_PAGE = "https://papermc.io/downloads/paper"; -+ // Purpur start - Rebrand -+ private static final String DOWNLOAD_PAGE = "https://purpurmc.org/downloads"; -+ private static int distance = DISTANCE_UNKNOWN; public int distance() { return distance; } -+ // Purpur end - Rebrand - - @Override - public long getCacheTime() { -@@ -49,7 +52,7 @@ public class PaperVersionFetcher implements VersionFetcher { - if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) { - updateMessage = text("You are running a development version without access to version information", color(0xFF5300)); - } else { -- updateMessage = getUpdateStatusMessage("PaperMC/Paper", build); -+ updateMessage = getUpdateStatusMessage("PurpurMC/Purpur", build); // Purpur - Rebrand - } - final @Nullable Component history = this.getHistory(); - -@@ -57,7 +60,7 @@ public class PaperVersionFetcher implements VersionFetcher { - } - - private static Component getUpdateStatusMessage(final String repo, final ServerBuildInfo build) { -- int distance = DISTANCE_ERROR; -+ //int distance = DISTANCE_ERROR; // Purpur - use field - Rebrand - - final OptionalInt buildNumber = build.buildNumber(); - if (buildNumber.isPresent()) { -@@ -71,10 +74,10 @@ public class PaperVersionFetcher implements VersionFetcher { - } - - return switch (distance) { -- case DISTANCE_ERROR -> text("Error obtaining version information", NamedTextColor.YELLOW); -- case 0 -> text("You are running the latest version", NamedTextColor.GREEN); -- case DISTANCE_UNKNOWN -> text("Unknown version", NamedTextColor.YELLOW); -- default -> text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW) -+ case DISTANCE_ERROR -> text("* Error obtaining version information", NamedTextColor.RED); // Purpur - Rebrand -+ case 0 -> text("* You are running the latest version", NamedTextColor.GREEN); // Purpur - Rebrand -+ case DISTANCE_UNKNOWN -> text("* Unknown version", NamedTextColor.YELLOW); // Purpur - Rebrand -+ default -> text("* You are " + distance + " version(s) behind", NamedTextColor.YELLOW) // Purpur - Rebrand - .append(Component.newline()) - .append(text("Download the new version at: ") - .append(text(DOWNLOAD_PAGE, NamedTextColor.GOLD) -@@ -86,18 +89,15 @@ public class PaperVersionFetcher implements VersionFetcher { - private static int fetchDistanceFromSiteApi(final ServerBuildInfo build, final int jenkinsBuild) { - try { - try (final BufferedReader reader = Resources.asCharSource( -- URI.create("https://api.papermc.io/v2/projects/paper/versions/" + build.minecraftVersionId()).toURL(), -+ URI.create("https://api.purpurmc.org/v2/purpur/" + build.minecraftVersionId()).toURL(), // Purpur - Rebrand - Charsets.UTF_8 - ).openBufferedStream()) { - final JsonObject json = new Gson().fromJson(reader, JsonObject.class); -- final JsonArray builds = json.getAsJsonArray("builds"); -- final int latest = StreamSupport.stream(builds.spliterator(), false) -- .mapToInt(JsonElement::getAsInt) -- .max() -- .orElseThrow(); -+ //final JsonArray builds = json.getAsJsonArray("builds"); // Purpur - Rebrand -+ final int latest = json.getAsJsonObject("builds").getAsJsonPrimitive("latest").getAsInt(); // Purpur - Rebrand - return latest - jenkinsBuild; - } catch (final JsonSyntaxException ex) { -- LOGGER.error("Error parsing json from Paper's downloads API", ex); -+ LOGGER.error("Error parsing json from Purpur's downloads API", ex); // Purpur - Rebrand - return DISTANCE_ERROR; - } - } catch (final IOException e) { -@@ -141,6 +141,6 @@ public class PaperVersionFetcher implements VersionFetcher { - return null; - } - -- return text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); -+ return text("Previous: " + oldVersion, NamedTextColor.GRAY); // Purpur - Rebrand - } - } -diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -index 6ee39b534b8d992655bc0cef3c299d12cbae0034..bc7e4e5560708fea89c584b1d8b471f4966f311a 100644 ---- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -+++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -@@ -20,7 +20,7 @@ public final class PaperConsole extends SimpleTerminalConsole { - @Override - protected LineReader buildReader(LineReaderBuilder builder) { - builder -- .appName("Paper") -+ .appName("Purpur") // Purpur - Rebrand - .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) - .completer(new ConsoleCommandCompleter(this.server)) - .option(LineReader.Option.COMPLETE_IN_WORD, true); -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 6bdc683b5ade408ee27f1d6636b4d60c8c89cb7c..11d91f58208c1e816620f5b97c5fdfc6ce37f6c3 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/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java -index 12b327eea95e0de9e9c39b7d039badee8ec46508..46696cfe1d99e705d383a1fe4e66f5c5646053d2 100644 ---- a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java -+++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java -@@ -61,6 +61,7 @@ public class RAMDetails extends JList { - - // Follows CraftServer#getTPS - double[] tps = new double[] { -+ server.tps5s.getAverage(), // Purpur - Add 5 second tps average in /tps - server.tps1.getAverage(), - server.tps5.getAverage(), - server.tps15.getAverage() -@@ -73,7 +74,7 @@ public class RAMDetails extends JList { - vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)"); - vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb"); - vector.add("Avg tick: " + DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / (double) TimeUtil.NANOSECONDS_PER_MILLISECOND) + " ms"); -- vector.add("TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg)); -+ vector.add("TPS from last 5s, 1m, 5m, 15m: " + String.join(", ", tpsAvg)); // Purpur - Add 5 second tps average in /tps - setListData(vector); - } - -diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishCommand.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..020368da69b9a492155f6de6297f74732f4ab6ea ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishCommand.java -@@ -0,0 +1,68 @@ -+package gg.pufferfish.pufferfish; -+ -+import java.io.IOException; -+import java.util.Collections; -+import java.util.List; -+import java.util.stream.Collectors; -+import java.util.stream.Stream; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.md_5.bungee.api.ChatColor; -+import net.minecraft.server.MinecraftServer; -+import org.bukkit.Bukkit; -+import org.bukkit.Location; -+import org.bukkit.command.Command; -+import org.bukkit.command.CommandSender; -+ -+public class PufferfishCommand extends Command { -+ -+ public PufferfishCommand() { -+ super("pufferfish"); -+ this.description = "Pufferfish related commands"; -+ this.usageMessage = "/pufferfish [reload | version]"; -+ this.setPermission("bukkit.command.pufferfish"); -+ } -+ -+ public static void init() { -+ MinecraftServer.getServer().server.getCommandMap().register("pufferfish", "Pufferfish", new PufferfishCommand()); -+ } -+ -+ @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; -+ String prefix = ChatColor.of("#12fff6") + "" + ChatColor.BOLD + "Pufferfish » " + ChatColor.of("#e8f9f9"); -+ -+ if (args.length != 1) { -+ sender.sendMessage(prefix + "Usage: " + usageMessage); -+ args = new String[]{"version"}; -+ } -+ -+ if (args[0].equalsIgnoreCase("reload")) { -+ MinecraftServer console = MinecraftServer.getServer(); -+ try { -+ PufferfishConfig.load(); -+ } catch (IOException e) { -+ sender.sendMessage(Component.text("Failed to reload.", NamedTextColor.RED)); -+ e.printStackTrace(); -+ return true; -+ } -+ console.server.reloadCount++; -+ -+ Command.broadcastCommandMessage(sender, prefix + "Pufferfish configuration has been reloaded."); -+ } else if (args[0].equalsIgnoreCase("version")) { -+ Command.broadcastCommandMessage(sender, prefix + "This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")"); -+ } -+ -+ return true; -+ } -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3ff4f092a59242a8cb930c084915a774db881652 ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java -@@ -0,0 +1,281 @@ -+package gg.pufferfish.pufferfish; -+ -+import gg.pufferfish.pufferfish.simd.SIMDDetection; -+import java.io.File; -+import java.io.IOException; -+import java.util.Collections; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.server.MinecraftServer; -+import org.apache.logging.log4j.Level; -+import org.bukkit.configuration.ConfigurationSection; -+import net.minecraft.world.entity.EntityType; -+import java.lang.reflect.Method; -+import java.lang.reflect.Modifier; -+import java.util.List; -+import net.minecraft.server.MinecraftServer; -+import org.apache.logging.log4j.Level; -+import org.bukkit.configuration.ConfigurationSection; -+import org.bukkit.configuration.MemoryConfiguration; -+import org.jetbrains.annotations.Nullable; -+import org.simpleyaml.configuration.comments.CommentType; -+import org.simpleyaml.configuration.file.YamlFile; -+import org.simpleyaml.exceptions.InvalidConfigurationException; -+ -+public class PufferfishConfig { -+ -+ private static final YamlFile config = new YamlFile(); -+ private static int updates = 0; -+ public static File pufferfishFile; // Purpur - Fix pufferfish issues -+ -+ private static ConfigurationSection convertToBukkit(org.simpleyaml.configuration.ConfigurationSection section) { -+ ConfigurationSection newSection = new MemoryConfiguration(); -+ for (String key : section.getKeys(false)) { -+ if (section.isConfigurationSection(key)) { -+ newSection.set(key, convertToBukkit(section.getConfigurationSection(key))); -+ } else { -+ newSection.set(key, section.get(key)); -+ } -+ } -+ return newSection; -+ } -+ -+ public static ConfigurationSection getConfigCopy() { -+ return convertToBukkit(config); -+ } -+ -+ public static int getUpdates() { -+ return updates; -+ } -+ -+ public static void load() throws IOException { -+ File configFile = pufferfishFile; // Purpur - Fix pufferfish issues -+ -+ if (configFile.exists()) { -+ try { -+ config.load(configFile); -+ } catch (InvalidConfigurationException e) { -+ throw new IOException(e); -+ } -+ } -+ -+ getString("info.version", "1.0"); -+ setComment("info", -+ "Pufferfish Configuration", -+ "Check out Pufferfish Host for maximum performance server hosting: https://pufferfish.host", -+ "Join our Discord for support: https://discord.gg/reZw4vQV9H", -+ "Download new builds at https://ci.pufferfish.host/job/Pufferfish"); -+ -+ for (Method method : PufferfishConfig.class.getDeclaredMethods()) { -+ if (Modifier.isStatic(method.getModifiers()) && Modifier.isPrivate(method.getModifiers()) && method.getParameterCount() == 0 && -+ method.getReturnType() == Void.TYPE && !method.getName().startsWith("lambda")) { -+ method.setAccessible(true); -+ try { -+ method.invoke(null); -+ } catch (Throwable t) { -+ MinecraftServer.LOGGER.warn("Failed to load configuration option from " + method.getName(), t); -+ } -+ } -+ } -+ -+ updates++; -+ -+ config.save(configFile); -+ -+ // Attempt to detect vectorization -+ try { -+ SIMDDetection.isEnabled = SIMDDetection.canEnable(PufferfishLogger.LOGGER); -+ SIMDDetection.versionLimited = SIMDDetection.getJavaVersion() < 17 || SIMDDetection.getJavaVersion() > 21; -+ } catch (NoClassDefFoundError | Exception ignored) { -+ ignored.printStackTrace(); -+ } -+ -+ if (SIMDDetection.isEnabled) { -+ PufferfishLogger.LOGGER.info("SIMD operations detected as functional. Will replace some operations with faster versions."); -+ } else if (SIMDDetection.versionLimited) { -+ PufferfishLogger.LOGGER.warning("Will not enable SIMD! These optimizations are only safely supported on Java 17-21."); -+ } else { -+ PufferfishLogger.LOGGER.warning("SIMD operations are available for your server, but are not configured!"); -+ PufferfishLogger.LOGGER.warning("To enable additional optimizations, add \"--add-modules=jdk.incubator.vector\" to your startup flags, BEFORE the \"-jar\"."); -+ PufferfishLogger.LOGGER.warning("If you have already added this flag, then SIMD operations are not supported on your JVM or CPU."); -+ PufferfishLogger.LOGGER.warning("Debug: Java: " + System.getProperty("java.version") + ", test run: " + SIMDDetection.testRun); -+ } -+ } -+ -+ private static void setComment(String key, String... comment) { -+ if (config.contains(key)) { -+ config.setComment(key, String.join("\n", comment), CommentType.BLOCK); -+ } -+ } -+ -+ private static void ensureDefault(String key, Object defaultValue, String... comment) { -+ if (!config.contains(key)) { -+ config.set(key, defaultValue); -+ config.setComment(key, String.join("\n", comment), CommentType.BLOCK); -+ } -+ } -+ -+ private static boolean getBoolean(String key, boolean defaultValue, String... comment) { -+ return getBoolean(key, null, defaultValue, comment); -+ } -+ -+ private static boolean getBoolean(String key, @Nullable String oldKey, boolean defaultValue, String... comment) { -+ ensureDefault(key, defaultValue, comment); -+ return config.getBoolean(key, defaultValue); -+ } -+ -+ private static int getInt(String key, int defaultValue, String... comment) { -+ return getInt(key, null, defaultValue, comment); -+ } -+ -+ private static int getInt(String key, @Nullable String oldKey, int defaultValue, String... comment) { -+ ensureDefault(key, defaultValue, comment); -+ return config.getInt(key, defaultValue); -+ } -+ -+ private static double getDouble(String key, double defaultValue, String... comment) { -+ return getDouble(key, null, defaultValue, comment); -+ } -+ -+ private static double getDouble(String key, @Nullable String oldKey, double defaultValue, String... comment) { -+ ensureDefault(key, defaultValue, comment); -+ return config.getDouble(key, defaultValue); -+ } -+ -+ private static String getString(String key, String defaultValue, String... comment) { -+ return getOldString(key, null, defaultValue, comment); -+ } -+ -+ private static String getOldString(String key, @Nullable String oldKey, String defaultValue, String... comment) { -+ ensureDefault(key, defaultValue, comment); -+ return config.getString(key, defaultValue); -+ } -+ -+ private static List getStringList(String key, List defaultValue, String... comment) { -+ return getStringList(key, null, defaultValue, comment); -+ } -+ -+ private static List getStringList(String key, @Nullable String oldKey, List defaultValue, String... comment) { -+ ensureDefault(key, defaultValue, comment); -+ return config.getStringList(key); -+ } -+ -+ public static String sentryDsn; -+ private static void sentry() { -+ String sentryEnvironment = System.getenv("SENTRY_DSN"); -+ String sentryConfig = getString("sentry-dsn", "", "Sentry DSN for improved error logging, leave blank to disable", "Obtain from https://sentry.io/"); -+ -+ sentryDsn = sentryEnvironment == null ? sentryConfig : sentryEnvironment; -+ if (sentryDsn != null && !sentryDsn.isBlank()) { -+ gg.pufferfish.pufferfish.sentry.SentryManager.init(); -+ } -+ } -+ -+ public static boolean enableBooks; -+ private static void books() { -+ enableBooks = getBoolean("enable-books", true, -+ "Whether or not books should be writeable.", -+ "Servers that anticipate being a target for duping may want to consider", -+ "disabling this option.", -+ "This can be overridden per-player with the permission pufferfish.usebooks"); -+ } -+ -+ public static boolean tpsCatchup; -+ private static void tpsCatchup() { -+ tpsCatchup = getBoolean("tps-catchup", true, -+ "If this setting is true, the server will run faster after a lag spike in", -+ "an attempt to maintain 20 TPS. This option (defaults to true per", -+ "spigot/paper) can cause mobs to move fast after a lag spike."); -+ } -+ -+ public static boolean enableSuffocationOptimization; -+ private static void suffocationOptimization() { -+ enableSuffocationOptimization = getBoolean("enable-suffocation-optimization", true, -+ "Optimizes the suffocation check by selectively skipping", -+ "the check in a way that still appears vanilla. This should", -+ "be left enabled on most servers, but is provided as a", -+ "configuration option if the vanilla deviation is undesirable."); -+ } -+ -+ public static boolean enableAsyncMobSpawning; -+ public static boolean asyncMobSpawningInitialized; -+ private static void asyncMobSpawning() { -+ boolean temp = getBoolean("enable-async-mob-spawning", true, -+ "Whether or not asynchronous mob spawning should be enabled.", -+ "On servers with many entities, this can improve performance by up to 15%. You must have", -+ "paper's per-player-mob-spawns setting set to true for this to work.", -+ "One quick note - this does not actually spawn mobs async (that would be very unsafe).", -+ "This just offloads some expensive calculations that are required for mob spawning."); -+ -+ // This prevents us from changing the value during a reload. -+ if (!asyncMobSpawningInitialized) { -+ asyncMobSpawningInitialized = true; -+ enableAsyncMobSpawning = temp; -+ } -+ } -+ -+ public static int maxProjectileLoadsPerTick; -+ public static int maxProjectileLoadsPerProjectile; -+ private static void projectileLoading() { -+ maxProjectileLoadsPerTick = getInt("projectile.max-loads-per-tick", 10, "Controls how many chunks are allowed", "to be sync loaded by projectiles in a tick."); -+ maxProjectileLoadsPerProjectile = getInt("projectile.max-loads-per-projectile", 10, "Controls how many chunks a projectile", "can load in its lifetime before it gets", "automatically removed."); -+ -+ setComment("projectile", "Optimizes projectile settings"); -+ } -+ -+ -+ public static boolean dearEnabled; -+ public static int startDistance; -+ public static int startDistanceSquared; -+ public static int maximumActivationPrio; -+ public static int activationDistanceMod; -+ -+ private static void dynamicActivationOfBrains() throws IOException { -+ dearEnabled = getBoolean("dab.enabled", "activation-range.enabled", false); // Purpur - Fix pufferfish issues -+ startDistance = getInt("dab.start-distance", "activation-range.start-distance", 12, -+ "This value determines how far away an entity has to be", -+ "from the player to start being effected by DEAR."); -+ startDistanceSquared = startDistance * startDistance; -+ maximumActivationPrio = getInt("dab.max-tick-freq", "activation-range.max-tick-freq", 20, -+ "This value defines how often in ticks, the furthest entity", -+ "will get their pathfinders and behaviors ticked. 20 = 1s"); -+ activationDistanceMod = getInt("dab.activation-dist-mod", "activation-range.activation-dist-mod", 8, -+ "This value defines how much distance modifies an entity's", -+ "tick frequency. freq = (distanceToPlayer^2) / (2^value)", -+ "If you want further away entities to tick less often, use 7.", -+ "If you want further away entities to tick more often, try 9."); -+ -+ for (EntityType entityType : BuiltInRegistries.ENTITY_TYPE) { -+ entityType.dabEnabled = true; // reset all, before setting the ones to true -+ } -+ getStringList("dab.blacklisted-entities", "activation-range.blacklisted-entities", Collections.emptyList(), "A list of entities to ignore for activation") -+ .forEach(name -> EntityType.byString(name).ifPresentOrElse(entityType -> { -+ entityType.dabEnabled = false; -+ }, () -> MinecraftServer.LOGGER.warn("Unknown entity \"" + name + "\""))); -+ -+ setComment("dab", "Optimizes entity brains when", "they're far away from the player"); -+ } -+ -+ public static boolean throttleInactiveGoalSelectorTick; -+ private static void inactiveGoalSelectorThrottle() { -+ throttleInactiveGoalSelectorTick = getBoolean("inactive-goal-selector-throttle", "inactive-goal-selector-disable", false, // Purpur - Fix pufferfish issues -+ "Throttles the AI goal selector in entity inactive ticks.", -+ "This can improve performance by a few percent, but has minor gameplay implications."); -+ } -+ -+ public static boolean allowEndCrystalRespawn; -+ private static void allowEndCrystalRespawn() { -+ allowEndCrystalRespawn = getBoolean("allow-end-crystal-respawn", true, -+ "Allows end crystals to respawn the ender dragon.", -+ "On servers that expect end crystal fights in the end dimension, disabling this", -+ "will prevent the server from performing an expensive search to attempt respawning", -+ "the ender dragon whenever a player places an end crystal."); -+ } -+ -+ -+ public static boolean disableMethodProfiler; -+ private static void miscSettings() { -+ disableMethodProfiler = getBoolean("misc.disable-method-profiler", true); -+ setComment("misc", "Settings for things that don't belong elsewhere"); -+ } -+ -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishLogger.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishLogger.java -new file mode 100644 -index 0000000000000000000000000000000000000000..53f2df00c6809618a9ee3d2ea72e85e8052fbcf1 ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishLogger.java -@@ -0,0 +1,16 @@ -+package gg.pufferfish.pufferfish; -+ -+import java.util.logging.Level; -+import java.util.logging.Logger; -+import org.bukkit.Bukkit; -+ -+public class PufferfishLogger extends Logger { -+ public static final PufferfishLogger LOGGER = new PufferfishLogger(); -+ -+ private PufferfishLogger() { -+ super("Pufferfish", null); -+ -+ setParent(Bukkit.getLogger()); -+ setLevel(Level.ALL); -+ } -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishVersionFetcher.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishVersionFetcher.java -new file mode 100644 -index 0000000000000000000000000000000000000000..06323dcc745aed16123980fc559d7b65c42f1e1c ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishVersionFetcher.java -@@ -0,0 +1,132 @@ -+package gg.pufferfish.pufferfish; -+ -+import com.destroystokyo.paper.VersionHistoryManager; -+import com.destroystokyo.paper.util.VersionFetcher; -+import com.google.gson.Gson; -+import com.google.gson.JsonObject; -+import io.papermc.paper.ServerBuildInfo; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.JoinConfiguration; -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.format.TextDecoration; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.io.IOException; -+import java.net.URI; -+import java.net.http.HttpClient; -+import java.net.http.HttpRequest; -+import java.net.http.HttpResponse; -+import java.nio.charset.StandardCharsets; -+import java.util.concurrent.TimeUnit; -+import java.util.logging.Level; -+import java.util.logging.Logger; -+ -+import static net.kyori.adventure.text.Component.text; -+import static net.kyori.adventure.text.format.NamedTextColor.GREEN; -+import static net.kyori.adventure.text.format.NamedTextColor.RED; -+ -+public class PufferfishVersionFetcher implements VersionFetcher { -+ -+ private static final Logger LOGGER = Logger.getLogger("PufferfishVersionFetcher"); -+ private static final HttpClient client = HttpClient.newHttpClient(); -+ -+ private static final URI JENKINS_URI = URI.create("https://ci.pufferfish.host/job/Pufferfish-1.21/lastSuccessfulBuild/buildNumber"); -+ private static final String GITHUB_FORMAT = "https://api.github.com/repos/pufferfish-gg/Pufferfish/compare/ver/1.21...%s"; -+ -+ private static final HttpResponse.BodyHandler JSON_OBJECT_BODY_HANDLER = responseInfo -> HttpResponse.BodySubscribers -+ .mapping( -+ HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8), -+ string -> new Gson().fromJson(string, JsonObject.class) -+ ); -+ -+ @Override -+ public long getCacheTime() { -+ return TimeUnit.MINUTES.toMillis(30); -+ } -+ -+ @Override -+ public @NotNull Component getVersionMessage(final @NotNull String serverVersion) { -+ @NotNull Component component; -+ -+ if (ServerBuildInfo.buildInfo().buildNumber().isPresent()) { -+ component = this.fetchJenkinsVersion(ServerBuildInfo.buildInfo().buildNumber().getAsInt()); -+ } else if (ServerBuildInfo.buildInfo().gitCommit().isPresent()) { -+ component = this.fetchGithubVersion(ServerBuildInfo.buildInfo().gitCommit().get()); -+ } else { -+ component = text("Unknown server version.", RED); -+ } -+ -+ final @Nullable Component history = this.getHistory(); -+ return history != null ? Component -+ .join(JoinConfiguration.noSeparators(), component, Component.newline(), this.getHistory()) : component; -+ } -+ -+ private @NotNull Component fetchJenkinsVersion(final int versionNumber) { -+ final HttpRequest request = HttpRequest.newBuilder(JENKINS_URI).build(); -+ try { -+ final HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); -+ if (response.statusCode() != 200) { -+ return text("Received invalid status code (" + response.statusCode() + ") from server.", RED); -+ } -+ -+ int latestVersionNumber; -+ try { -+ latestVersionNumber = Integer.parseInt(response.body()); -+ } catch (NumberFormatException e) { -+ LOGGER.log(Level.WARNING, "Received invalid response from Jenkins \"" + response.body() + "\"."); -+ return text("Received invalid response from server.", RED); -+ } -+ -+ final int versionDiff = latestVersionNumber - versionNumber; -+ return this.getResponseMessage(versionDiff); -+ } catch (IOException | InterruptedException e) { -+ LOGGER.log(Level.WARNING, "Failed to look up version from Jenkins", e); -+ return text("Failed to retrieve version from server.", RED); -+ } -+ } -+ -+ // Based off code contributed by Techcable in Paper/GH-65 -+ private @NotNull Component fetchGithubVersion(final @NotNull String hash) { -+ final URI uri = URI.create(String.format(GITHUB_FORMAT, hash)); -+ final HttpRequest request = HttpRequest.newBuilder(uri).build(); -+ try { -+ final HttpResponse response = client.send(request, JSON_OBJECT_BODY_HANDLER); -+ if (response.statusCode() != 200) { -+ return text("Received invalid status code (" + response.statusCode() + ") from server.", RED); -+ } -+ -+ final JsonObject obj = response.body(); -+ final int versionDiff = obj.get("behind_by").getAsInt(); -+ -+ return this.getResponseMessage(versionDiff); -+ } catch (IOException | InterruptedException e) { -+ LOGGER.log(Level.WARNING, "Failed to look up version from GitHub", e); -+ return text("Failed to retrieve version from server.", RED); -+ } -+ } -+ -+ private @NotNull Component getResponseMessage(final int versionDiff) { -+ return switch (Math.max(-1, Math.min(1, versionDiff))) { -+ case -1 -> text("You are running an unsupported version of Pufferfish.", RED); -+ case 0 -> text("You are on the latest version!", GREEN); -+ default -> text("You are running " + versionDiff + " version" + (versionDiff == 1 ? "" : "s") + " behind. " + -+ "Please update your server when possible to maintain stability and security, and to receive the latest optimizations.", -+ RED); -+ }; -+ } -+ -+ private @Nullable Component getHistory() { -+ final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData(); -+ if (data == null) { -+ return null; -+ } -+ -+ final String oldVersion = data.getOldVersion(); -+ if (oldVersion == null) { -+ return null; -+ } -+ -+ return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); -+ } -+} -\ No newline at end of file -diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java -new file mode 100644 -index 0000000000000000000000000000000000000000..731ef11c7a025ae95ed8a757b530d834733d0621 ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java -@@ -0,0 +1,135 @@ -+package gg.pufferfish.pufferfish.sentry; -+ -+import com.google.common.reflect.TypeToken; -+import com.google.gson.Gson; -+import io.sentry.Breadcrumb; -+import io.sentry.Sentry; -+import io.sentry.SentryEvent; -+import io.sentry.SentryLevel; -+import io.sentry.protocol.Message; -+import io.sentry.protocol.User; -+import java.util.Map; -+import org.apache.logging.log4j.Level; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Marker; -+import org.apache.logging.log4j.core.LogEvent; -+import org.apache.logging.log4j.core.Logger; -+import org.apache.logging.log4j.core.appender.AbstractAppender; -+import org.apache.logging.log4j.core.filter.AbstractFilter; -+ -+public class PufferfishSentryAppender extends AbstractAppender { -+ -+ private static final org.apache.logging.log4j.Logger logger = LogManager.getLogger(PufferfishSentryAppender.class); -+ private static final Gson GSON = new Gson(); -+ -+ public PufferfishSentryAppender() { -+ super("PufferfishSentryAdapter", new SentryFilter(), null); -+ } -+ -+ @Override -+ public void append(LogEvent logEvent) { -+ if (logEvent.getThrown() != null && logEvent.getLevel().isMoreSpecificThan(Level.WARN)) { -+ try { -+ logException(logEvent); -+ } catch (Exception e) { -+ logger.warn("Failed to log event with sentry", e); -+ } -+ } else { -+ try { -+ logBreadcrumb(logEvent); -+ } catch (Exception e) { -+ logger.warn("Failed to log event with sentry", e); -+ } -+ } -+ } -+ -+ private void logException(LogEvent e) { -+ SentryEvent event = new SentryEvent(e.getThrown()); -+ -+ Message sentryMessage = new Message(); -+ sentryMessage.setMessage(e.getMessage().getFormattedMessage()); -+ -+ event.setThrowable(e.getThrown()); -+ event.setLevel(getLevel(e.getLevel())); -+ event.setLogger(e.getLoggerName()); -+ event.setTransaction(e.getLoggerName()); -+ event.setExtra("thread_name", e.getThreadName()); -+ -+ boolean hasContext = e.getContextData() != null; -+ -+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_playerid")) { -+ User user = new User(); -+ user.setId(e.getContextData().getValue("pufferfishsentry_playerid")); -+ user.setUsername(e.getContextData().getValue("pufferfishsentry_playername")); -+ event.setUser(user); -+ } -+ -+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_pluginname")) { -+ event.setExtra("plugin.name", e.getContextData().getValue("pufferfishsentry_pluginname")); -+ event.setExtra("plugin.version", e.getContextData().getValue("pufferfishsentry_pluginversion")); -+ event.setTransaction(e.getContextData().getValue("pufferfishsentry_pluginname")); -+ } -+ -+ if (hasContext && e.getContextData().containsKey("pufferfishsentry_eventdata")) { -+ Map eventFields = GSON.fromJson((String) e.getContextData().getValue("pufferfishsentry_eventdata"), new TypeToken>() {}.getType()); -+ if (eventFields != null) { -+ event.setExtra("event", eventFields); -+ } -+ } -+ -+ Sentry.captureEvent(event); -+ } -+ -+ private void logBreadcrumb(LogEvent e) { -+ Breadcrumb breadcrumb = new Breadcrumb(); -+ -+ breadcrumb.setLevel(getLevel(e.getLevel())); -+ breadcrumb.setCategory(e.getLoggerName()); -+ breadcrumb.setType(e.getLoggerName()); -+ breadcrumb.setMessage(e.getMessage().getFormattedMessage()); -+ -+ Sentry.addBreadcrumb(breadcrumb); -+ } -+ -+ private SentryLevel getLevel(Level level) { -+ switch (level.getStandardLevel()) { -+ case TRACE: -+ case DEBUG: -+ return SentryLevel.DEBUG; -+ case WARN: -+ return SentryLevel.WARNING; -+ case ERROR: -+ return SentryLevel.ERROR; -+ case FATAL: -+ return SentryLevel.FATAL; -+ case INFO: -+ default: -+ return SentryLevel.INFO; -+ } -+ } -+ -+ private static class SentryFilter extends AbstractFilter { -+ -+ @Override -+ public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, String msg, -+ Object... params) { -+ return this.filter(logger.getName()); -+ } -+ -+ @Override -+ public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, Object msg, Throwable t) { -+ return this.filter(logger.getName()); -+ } -+ -+ @Override -+ public Result filter(LogEvent event) { -+ return this.filter(event == null ? null : event.getLoggerName()); -+ } -+ -+ private Result filter(String loggerName) { -+ return loggerName != null && loggerName.startsWith("gg.castaway.pufferfish.sentry") ? Result.DENY -+ : Result.NEUTRAL; -+ } -+ -+ } -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1b29210ad0bbb4ada150f23357f0c80d331c996d ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java -@@ -0,0 +1,40 @@ -+package gg.pufferfish.pufferfish.sentry; -+ -+import gg.pufferfish.pufferfish.PufferfishConfig; -+import io.sentry.Sentry; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+ -+public class SentryManager { -+ -+ private static final Logger logger = LogManager.getLogger(SentryManager.class); -+ -+ private SentryManager() { -+ -+ } -+ -+ private static boolean initialized = false; -+ -+ public static synchronized void init() { -+ if (initialized) { -+ return; -+ } -+ try { -+ initialized = true; -+ -+ Sentry.init(options -> { -+ options.setDsn(PufferfishConfig.sentryDsn); -+ options.setMaxBreadcrumbs(100); -+ }); -+ -+ PufferfishSentryAppender appender = new PufferfishSentryAppender(); -+ appender.start(); -+ ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addAppender(appender); -+ logger.info("Sentry logging started!"); -+ } catch (Exception e) { -+ logger.warn("Failed to initialize sentry!", e); -+ initialized = false; -+ } -+ } -+ -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java b/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8e5323d5d9af25c8a85c4b34a6be76cfc54384cf ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java -@@ -0,0 +1,73 @@ -+package gg.pufferfish.pufferfish.util; -+ -+import com.google.common.collect.Queues; -+import gg.pufferfish.pufferfish.PufferfishLogger; -+import java.util.Queue; -+import java.util.concurrent.locks.Condition; -+import java.util.concurrent.locks.Lock; -+import java.util.concurrent.locks.ReentrantLock; -+import java.util.logging.Level; -+ -+public class AsyncExecutor implements Runnable { -+ -+ private final Queue jobs = Queues.newArrayDeque(); -+ private final Lock mutex = new ReentrantLock(); -+ private final Condition cond = mutex.newCondition(); -+ private final Thread thread; -+ private volatile boolean killswitch = false; -+ -+ public AsyncExecutor(String threadName) { -+ this.thread = new Thread(this, threadName); -+ } -+ -+ public void start() { -+ thread.start(); -+ } -+ -+ public void kill() { -+ killswitch = true; -+ cond.signalAll(); -+ } -+ -+ public void submit(Runnable runnable) { -+ mutex.lock(); -+ try { -+ jobs.offer(runnable); -+ cond.signalAll(); -+ } finally { -+ mutex.unlock(); -+ } -+ } -+ -+ @Override -+ public void run() { -+ while (!killswitch) { -+ try { -+ Runnable runnable = takeRunnable(); -+ if (runnable != null) { -+ runnable.run(); -+ } -+ } catch (InterruptedException e) { -+ Thread.currentThread().interrupt(); -+ } catch (Exception e) { -+ PufferfishLogger.LOGGER.log(Level.SEVERE, e, () -> "Failed to execute async job for thread " + thread.getName()); -+ } -+ } -+ } -+ -+ private Runnable takeRunnable() throws InterruptedException { -+ mutex.lock(); -+ try { -+ while (jobs.isEmpty() && !killswitch) { -+ cond.await(); -+ } -+ -+ if (jobs.isEmpty()) return null; // We've set killswitch -+ -+ return jobs.remove(); -+ } finally { -+ mutex.unlock(); -+ } -+ } -+ -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java b/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c1929840254a3e6d721816f4a20415bea1742580 ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java -@@ -0,0 +1,20 @@ -+package gg.pufferfish.pufferfish.util; -+ -+import java.util.Iterator; -+import org.jetbrains.annotations.NotNull; -+ -+public class IterableWrapper implements Iterable { -+ -+ private final Iterator iterator; -+ -+ public IterableWrapper(Iterator iterator) { -+ this.iterator = iterator; -+ } -+ -+ @NotNull -+ @Override -+ public Iterator iterator() { -+ return iterator; -+ } -+ -+} -diff --git a/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java b/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java -new file mode 100644 -index 0000000000000000000000000000000000000000..facd55463d44cb7e3d2ca6892982f5497b8dded1 ---- /dev/null -+++ b/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java -@@ -0,0 +1,40 @@ -+package gg.pufferfish.pufferfish.util; -+ -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import java.util.Map; -+import org.jetbrains.annotations.Nullable; -+ -+public class Long2ObjectOpenHashMapWrapper extends Long2ObjectOpenHashMap { -+ -+ private final Map backingMap; -+ -+ public Long2ObjectOpenHashMapWrapper(Map map) { -+ backingMap = map; -+ } -+ -+ @Override -+ public V put(Long key, V value) { -+ return backingMap.put(key, value); -+ } -+ -+ @Override -+ public V get(Object key) { -+ return backingMap.get(key); -+ } -+ -+ @Override -+ public V remove(Object key) { -+ return backingMap.remove(key); -+ } -+ -+ @Nullable -+ @Override -+ public V putIfAbsent(Long key, V value) { -+ return backingMap.putIfAbsent(key, value); -+ } -+ -+ @Override -+ public int size() { -+ return backingMap.size(); -+ } -+} -diff --git a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -index 790bad0494454ca12ee152e3de6da3da634d9b20..0843e7c5c335a58d955a0841f2e02a9e4ac824d9 100644 ---- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -+++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -@@ -31,6 +31,8 @@ public record ServerBuildInfoImpl( - private static final String ATTRIBUTE_GIT_COMMIT = "Git-Commit"; - - private static final String BRAND_PAPER_NAME = "Paper"; -+ private static final String BRAND_PUFFERFISH_NAME = "Pufferfish"; // Purpur - Fix pufferfish issues -+ private static final String BRAND_PURPUR_NAME = "Purpur"; // Purpur - Rebrand - - private static final String BUILD_DEV = "DEV"; - -@@ -42,9 +44,9 @@ public record ServerBuildInfoImpl( - this( - getManifestAttribute(manifest, ATTRIBUTE_BRAND_ID) - .map(Key::key) -- .orElse(BRAND_PAPER_ID), -+ .orElse(BRAND_PURPUR_ID), // Purpur - Fix pufferfish issues // Purpur - Rebrand - getManifestAttribute(manifest, ATTRIBUTE_BRAND_NAME) -- .orElse(BRAND_PAPER_NAME), -+ .orElse(BRAND_PURPUR_NAME), // Purpur - Fix pufferfish issues // Purpur - Rebrand - SharedConstants.getCurrentVersion().getId(), - SharedConstants.getCurrentVersion().getName(), - getManifestAttribute(manifest, ATTRIBUTE_BUILD_NUMBER) -@@ -61,7 +63,7 @@ public record ServerBuildInfoImpl( - - @Override - public boolean isBrandCompatible(final @NotNull Key brandId) { -- return brandId.equals(this.brandId); -+ return brandId.equals(this.brandId) || brandId.equals(BRAND_PAPER_ID) || brandId.equals(BRAND_PUFFERFISH_ID); // Purpur - Fix pufferfish issues // Purpur - Rebrand - } - - @Override -diff --git a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java -index f0fce4113fb07c64adbec029d177c236cbdcbae8..865dc183276720d54d31d2a54d1bb5c845e80598 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,13 +117,65 @@ 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()); -+ } -+ // Purpur end - - builder.append(pluginName); - - 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(); - for (String string : strings.split("\n")) { -@@ -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); -- } -+ //if (!paperPlugins.isEmpty()) { // Purpur -+ sender.sendMessage(PAPER_HEADER.append(Component.text(" (%s):".formatted(paperPlugins.size())))); // Purpur -+ //} // Purpur - -- for (Component component : formatProviders(paperPlugins)) { -+ for (Component component : formatProviders(paperPlugins, sender)) { // Purpur - sender.sendMessage(component); - } - -- if (!spigotPlugins.isEmpty()) { -- sender.sendMessage(BUKKIT_HEADER); -- } -+ //if (!spigotPlugins.isEmpty()) { // Purpur -+ sender.sendMessage(BUKKIT_HEADER.append(Component.text(" (%s):".formatted(spigotPlugins.size())))); // Purpur -+ //} // 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/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -index c5644d8d64f12073e39bc6ed79c8714f4560ff89..47a2cba0db36b11548d06ec21f7c7d7c9a962d6e 100644 ---- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -+++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -@@ -263,6 +263,7 @@ public class PaperConfigurations extends Configurations 0 || SysoutCatcher.NAG_TIMEOUT > 0) { -diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java -index b24265573fdef5d9a964bcd76146f34542c420cf..710477ae27ebc5afdf0012ef0867d05efd293c24 100644 ---- a/src/main/java/net/minecraft/CrashReport.java -+++ b/src/main/java/net/minecraft/CrashReport.java -@@ -32,6 +32,7 @@ public class CrashReport { - private boolean trackingStackTrace = true; - private StackTraceElement[] uncategorizedStackTrace = new StackTraceElement[0]; - private final SystemReport systemReport = new SystemReport(); -+ private List extraInfo = List.of("", "DO NOT REPORT THIS TO PAPER! REPORT TO PURPUR INSTEAD!", ""); // Purpur - Rebrand - - public CrashReport(String message, Throwable cause) { - io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper -@@ -144,7 +145,7 @@ public class CrashReport { - } - - public String getFriendlyReport(ReportType type) { -- return this.getFriendlyReport(type, List.of()); -+ return this.getFriendlyReport(type, extraInfo); // Purpur - Rebrand - } - - @Nullable -@@ -191,7 +192,7 @@ public class CrashReport { - } - - public boolean saveToFile(Path path, ReportType type) { -- return this.saveToFile(path, type, List.of()); -+ return this.saveToFile(path, type, extraInfo); // Purpur - Rebrand - } - - public SystemReport getSystemReport() { -diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index 13bd145b1e8006a53c22f5dc0c78f29b540c7663..8ed5e9293e80f53d741c145fa415fab6311036d7 100644 ---- a/src/main/java/net/minecraft/commands/CommandSourceStack.java -+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java -@@ -211,6 +211,19 @@ public class CommandSourceStack implements ExecutionCommandSource").replacement(bukkitPermission).build()))); -+ } -+ return false; -+ } -+ // Purpur end -+ - public Vec3 getPosition() { - return this.worldPosition; - } -@@ -312,6 +325,30 @@ public class CommandSourceStack implements ExecutionCommandSource io.papermc.paper.adventure.PaperAdventure.asVanilla(message), broadcastToOps); -+ } -+ // Purpur end - Purpur config files -+ - public void sendSuccess(Supplier feedbackSupplier, boolean broadcastToOps) { - boolean flag1 = this.source.acceptsSuccess() && !this.silent; - boolean flag2 = broadcastToOps && this.source.shouldInformAdmins() && !this.silent; -diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java -index 517cb238ec280aadd1fc54bcb675ed386e798eaf..fe9a01e19ef182fb8e9c653fc1232ec7f13037e4 100644 ---- a/src/main/java/net/minecraft/commands/Commands.java -+++ b/src/main/java/net/minecraft/commands/Commands.java -@@ -226,8 +226,8 @@ public class Commands { - JfrCommand.register(this.dispatcher); - } - -- if (SharedConstants.IS_RUNNING_IN_IDE) { -- TestCommand.register(this.dispatcher); -+ if (org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands || SharedConstants.IS_RUNNING_IN_IDE) { // Purpur -+ if (!org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands) TestCommand.register(this.dispatcher); // Purpur - RaidCommand.register(this.dispatcher, commandRegistryAccess); - DebugPathCommand.register(this.dispatcher); - DebugMobSpawningCommand.register(this.dispatcher); -@@ -255,6 +255,14 @@ public class Commands { - StopCommand.register(this.dispatcher); - TransferCommand.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 - Implement ram and rambar commands -+ org.purpurmc.purpur.command.RamCommand.register(this.dispatcher); // Purpur - Implement ram and rambar commands - } - - if (environment.includeIntegrated) { -@@ -515,6 +523,7 @@ public class Commands { - private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { - // Paper end - Perf: Async command map building - new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, true).callEvent(); // Paper - Brigadier API -+ if (PlayerCommandSendEvent.getHandlerList().getRegisteredListeners().length > 0) { // Purpur - skip all this crap if there's nothing listening - PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); - event.getPlayer().getServer().getPluginManager().callEvent(event); - -@@ -525,6 +534,7 @@ public class Commands { - } - } - // CraftBukkit end -+ } // Purpur - skip event - player.connection.send(new ClientboundCommandsPacket(rootcommandnode)); - } - -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 c8d39e6e1c570c9219f6066da273dc0130920519..d881caf99c2bad66b76bdc6ddb11a6cac1e94db6 100644 ---- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java -+++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java -@@ -198,10 +198,10 @@ public class EntitySelector { - - if (this.playerName != null) { - entityplayer = source.getServer().getPlayerList().getPlayerByName(this.playerName); -- return entityplayer == null ? List.of() : List.of(entityplayer); -+ return entityplayer == null || !canSee(source, entityplayer) ? List.of() : List.of(entityplayer); // Purpur - Hide hidden players from entity selector - } else if (this.entityUUID != null) { - entityplayer = source.getServer().getPlayerList().getPlayer(this.entityUUID); -- return entityplayer == null ? List.of() : List.of(entityplayer); -+ return entityplayer == null || !canSee(source, entityplayer) ? List.of() : List.of(entityplayer); // Purpur - Hide hidden players from entity selector - } else { - Vec3 vec3d = (Vec3) this.position.apply(source.getPosition()); - AABB axisalignedbb = this.getAbsoluteAabb(vec3d); -@@ -214,7 +214,7 @@ public class EntitySelector { - ServerPlayer entityplayer1 = (ServerPlayer) entity; - - if (predicate.test(entityplayer1)) { -- return List.of(entityplayer1); -+ return !canSee(source, entityplayer1) ? List.of() : List.of(entityplayer1); // Purpur - Hide hidden players from entity selector - } - } - -@@ -225,6 +225,7 @@ public class EntitySelector { - - if (this.isWorldLimited()) { - object = source.getLevel().getPlayers(predicate, i); -+ ((List) object).removeIf(entityplayer3 -> !canSee(source, (ServerPlayer) entityplayer3)); // Purpur - Hide hidden players from entity selector - } else { - object = new ObjectArrayList(); - Iterator iterator = source.getServer().getPlayerList().getPlayers().iterator(); -@@ -232,7 +233,7 @@ public class EntitySelector { - while (iterator.hasNext()) { - ServerPlayer entityplayer2 = (ServerPlayer) iterator.next(); - -- if (predicate.test(entityplayer2)) { -+ if (predicate.test(entityplayer2) && canSee(source, entityplayer2)) { // Purpur - Hide hidden players from entity selector - ((List) object).add(entityplayer2); - if (((List) object).size() >= i) { - return (List) object; -@@ -299,4 +300,10 @@ public class EntitySelector { - public static Component joinNames(List entities) { - return ComponentUtils.formatList(entities, Entity::getDisplayName); - } -+ -+ // Purpur start - Hide hidden players from entity selector -+ 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 - Hide hidden players from entity selector - } -diff --git a/src/main/java/net/minecraft/commands/execution/tasks/BuildContexts.java b/src/main/java/net/minecraft/commands/execution/tasks/BuildContexts.java -index b0d26b0eadb2a43924629424a6c13198aace8f69..9f5c3ec2eae9b30bdb8dbcb328d7f701cb7aeb9d 100644 ---- a/src/main/java/net/minecraft/commands/execution/tasks/BuildContexts.java -+++ b/src/main/java/net/minecraft/commands/execution/tasks/BuildContexts.java -@@ -52,7 +52,7 @@ public class BuildContexts> { - } - - RedirectModifier redirectModifier = commandContext.getRedirectModifier(); -- if (redirectModifier instanceof CustomModifierExecutor customModifierExecutor) { -+ if (redirectModifier instanceof CustomModifierExecutor customModifierExecutor) { // Purpur - decompile error - customModifierExecutor.apply(baseSource, list, contextChain, chainModifiers, ExecutionControl.create(context, frame)); - return; - } -@@ -92,11 +92,11 @@ public class BuildContexts> { - - if (list.isEmpty()) { - if (chainModifiers.isReturn()) { -- context.queueNext(new CommandQueueEntry<>(frame, FallthroughTask.instance())); -+ context.queueNext(new CommandQueueEntry<>(frame, (EntryAction) FallthroughTask.instance())); // Purpur - decompile error - } - } else { - CommandContext commandContext2 = contextChain.getTopContext(); -- if (commandContext2.getCommand() instanceof CustomCommandExecutor customCommandExecutor) { -+ if (commandContext2.getCommand() instanceof CustomCommandExecutor customCommandExecutor) { // Purpur - decompile error - ExecutionControl executionControl = ExecutionControl.create(context, frame); - - for (T executionCommandSource2 : list) { -diff --git a/src/main/java/net/minecraft/commands/synchronization/ArgumentTypeInfos.java b/src/main/java/net/minecraft/commands/synchronization/ArgumentTypeInfos.java -index 7b118a92a6eb779f800ae8f5d8f6e3c861fc4f6a..057a038e8dcacd7496a0b2373de2c20255a5c297 100644 ---- a/src/main/java/net/minecraft/commands/synchronization/ArgumentTypeInfos.java -+++ b/src/main/java/net/minecraft/commands/synchronization/ArgumentTypeInfos.java -@@ -119,10 +119,10 @@ public class ArgumentTypeInfos { - register(registry, "dimension", DimensionArgument.class, SingletonArgumentInfo.contextFree(DimensionArgument::dimension)); - register(registry, "gamemode", GameModeArgument.class, SingletonArgumentInfo.contextFree(GameModeArgument::gameMode)); - register(registry, "time", TimeArgument.class, new TimeArgument.Info()); -- register(registry, "resource_or_tag", fixClassType(ResourceOrTagArgument.class), new ResourceOrTagArgument.Info()); -- register(registry, "resource_or_tag_key", fixClassType(ResourceOrTagKeyArgument.class), new ResourceOrTagKeyArgument.Info()); -- register(registry, "resource", fixClassType(ResourceArgument.class), new ResourceArgument.Info()); -- register(registry, "resource_key", fixClassType(ResourceKeyArgument.class), new ResourceKeyArgument.Info()); -+ register(registry, "resource_or_tag", fixClassType(ResourceOrTagArgument.class), new ResourceOrTagArgument.Info<>()); // Purpur - decompile error -+ register(registry, "resource_or_tag_key", fixClassType(ResourceOrTagKeyArgument.class), new ResourceOrTagKeyArgument.Info<>()); // Purpur - decompile error -+ register(registry, "resource", fixClassType(ResourceArgument.class), new ResourceArgument.Info<>()); // Purpur - decompile error -+ register(registry, "resource_key", fixClassType(ResourceKeyArgument.class), new ResourceKeyArgument.Info<>()); // Purpur - decompile error - register(registry, "template_mirror", TemplateMirrorArgument.class, SingletonArgumentInfo.contextFree(TemplateMirrorArgument::templateMirror)); - register(registry, "template_rotation", TemplateRotationArgument.class, SingletonArgumentInfo.contextFree(TemplateRotationArgument::templateRotation)); - register(registry, "heightmap", HeightmapTypeArgument.class, SingletonArgumentInfo.contextFree(HeightmapTypeArgument::heightmap)); -diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index faffd87c357511ef00646971a16acf1009362c59..6714b4a39180affd101f1cab0d587cf2d3e6886a 100644 ---- a/src/main/java/net/minecraft/core/BlockPos.java -+++ b/src/main/java/net/minecraft/core/BlockPos.java -@@ -63,6 +63,12 @@ public class BlockPos extends Vec3i { - public static final int MAX_HORIZONTAL_COORDINATE = 33554431; - // Paper end - Optimize Bit Operations by inlining - -+ // Purpur start - Ridables -+ public BlockPos(net.minecraft.world.entity.Entity entity) { -+ super(entity.getBlockX(), entity.getBlockY(), entity.getBlockZ()); -+ } -+ // Purpur end - Ridables -+ - public BlockPos(int x, int y, int z) { - super(x, y, z); - } -diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -index c9d7ac819ce26f5301df7df56edce59b7ef377e0..cdd73bb358e309844bef576175a9026cb8563e7e 100644 ---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java -@@ -930,5 +930,22 @@ public interface DispenseItemBehavior { - DispenserBlock.registerBehavior(Items.TNT_MINECART, new MinecartDispenseItemBehavior(EntityType.TNT_MINECART)); - DispenserBlock.registerBehavior(Items.HOPPER_MINECART, new MinecartDispenseItemBehavior(EntityType.HOPPER_MINECART)); - DispenserBlock.registerBehavior(Items.COMMAND_BLOCK_MINECART, new MinecartDispenseItemBehavior(EntityType.COMMAND_BLOCK_MINECART)); -+ // Purpur start -+ DispenserBlock.registerBehavior(Items.ANVIL, (new OptionalDispenseItemBehavior() { -+ @Override -+ public ItemStack execute(BlockSource dispenser, ItemStack stack) { -+ net.minecraft.world.level.Level level = dispenser.level(); -+ if (!level.purpurConfig.dispenserPlaceAnvils) return super.execute(dispenser, stack); -+ Direction facing = dispenser.blockEntity().getBlockState().getValue(DispenserBlock.FACING); -+ BlockPos pos = dispenser.pos().relative(facing); -+ BlockState state = level.getBlockState(pos); -+ if (state.isAir()) { -+ level.setBlockAndUpdate(pos, Blocks.ANVIL.defaultBlockState().setValue(net.minecraft.world.level.block.AnvilBlock.FACING, facing.getAxis() == Direction.Axis.Y ? Direction.NORTH : facing.getClockWise())); -+ stack.shrink(1); -+ } -+ return stack; -+ } -+ })); -+ // Purpur end - } - } -diff --git a/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java -index bf8c511739265c6a9cd277752e844481598f8966..ffe2399ab6b1f311536475d8216238b5b01c5dab 100644 ---- a/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java -+++ b/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java -@@ -41,7 +41,7 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { - return false; - } else { - LivingEntity entityliving = (LivingEntity) list.getFirst(); -- EquipmentSlot enumitemslot = entityliving.getEquipmentSlotForItem(stack); -+ EquipmentSlot enumitemslot = pointer.level().purpurConfig.dispenserApplyCursedArmor ? entityliving.getEquipmentSlotForItem(stack) : entityliving.getEquipmentSlotForDispenserItem(stack); if (enumitemslot == null) return false; // Purpur - Dispenser curse of binding protection - ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event - - // CraftBukkit start -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 3c866432c8a938c677a315612f3e159bda67a2a2..8661c1b1cfe2b3db000e1f08814fd4409c4b7fab 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -617,11 +617,20 @@ public class Connection extends SimpleChannelInboundHandler> { - private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world - private static int joinAttemptsThisTick; // Paper - Buffer joins to world - private static int currTick; // Paper - Buffer joins to world -+ private static int tickSecond; // Purpur - public void tick() { - this.flushQueue(); - // Paper start - Buffer joins to world - 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 - Buffer joins to world -diff --git a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java -index 300929a406905f5ff1ede664d5b99fb0938d4d2e..a4e9ac0e07f08e0b6aa682e8c1587d9c84fc3c6c 100644 ---- a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java -+++ b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java -@@ -45,7 +45,7 @@ public class SignedMessageChain { - SignedMessageLink signedMessageLink = SignedMessageChain.this.nextLink; - if (signedMessageLink == null) { - throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.CHAIN_BROKEN); -- } else if (body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) { -+ } else if (org.purpurmc.purpur.PurpurConfig.kickForOutOfOrderChat && body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) { - this.setChainBroken(); - throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT, org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event causes - } else { -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index 300a044bb0f0e377133f24469cea1a9669de6e58..a880f4e5cf712654649ad043e58e073e9a87c0fe 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -122,6 +122,12 @@ public class Main { - JvmProfiler.INSTANCE.start(Environment.SERVER); - } - -+ // Purpur start - load config files early -+ org.bukkit.configuration.file.YamlConfiguration purpurConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("purpur-settings")); -+ org.purpurmc.purpur.PurpurConfig.clampEnchantLevels = purpurConfiguration.getBoolean("settings.enchantment.clamp-levels", true); -+ org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands = purpurConfiguration.getBoolean("settings.register-minecraft-debug-commands"); -+ // Purpur end - load config files early -+ - io.papermc.paper.plugin.PluginInitializerManager.load(optionset); // Paper - Bootstrap.bootStrap(); - Bootstrap.validate(); -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index ae4ebf509837e8d44255781c61d02873f8b74be8..fa5f7bc53f3dfa5581f7c747c732ebc7737a7820 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -314,6 +314,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); - public int autosavePeriod; - // Paper - don't store the vanilla dispatcher -@@ -324,15 +325,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping -+ public boolean lagging = false; // Purpur - Lagging threshold -+ protected boolean upnp = false; // Purpur - UPnP Port Forwarding - - public volatile Thread shutdownThread; // Paper - public volatile boolean abnormalExit = false; // Paper - public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation -+ public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("MobSpawning"); // Pufferfish - optimize mob spawning - - public static S spin(Function serverFactory) { - ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system -@@ -1055,6 +1059,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - Add EntityMoveEvent - net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers - worldserver.updateLagCompensationTick(); // Paper - lag compensation -+ worldserver.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - Ridables - - gameprofilerfiller.push(() -> { - String s = String.valueOf(worldserver); -@@ -2012,7 +2059,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { - this.executeBlocking(() -> { -diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java -index 8e2eb7b61421ceb063654826941f1a81f6f50bdf..1e85c9318ede93b8e9fe548a8945324b5b00e818 100644 ---- a/src/main/java/net/minecraft/server/PlayerAdvancements.java -+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java -@@ -199,6 +199,7 @@ public class PlayerAdvancements { - - if (advancementholder == null) { - if (!minecraftkey.getNamespace().equals("minecraft")) return; // CraftBukkit -+ if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressIgnoredAdvancementWarnings) // Purpur - PlayerAdvancements.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", minecraftkey, this.playerSavePath); - } else { - this.startProgress(advancementholder, advancementprogress); -@@ -249,6 +250,7 @@ public class PlayerAdvancements { - advancement.value().display().ifPresent((advancementdisplay) -> { - // Paper start - Add Adventure message to PlayerAdvancementDoneEvent - if (event.message() != null && this.player.serverLevel().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(event.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 cf0a5943f457c532958f40b4989fa18f967abae6..2ab8ff8ca51eb841932ccca4a348acc0141264a8 100644 ---- a/src/main/java/net/minecraft/server/commands/EnchantCommand.java -+++ b/src/main/java/net/minecraft/server/commands/EnchantCommand.java -@@ -70,7 +70,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 - Config to allow unsafe enchants - throw ERROR_LEVEL_TOO_HIGH.create(level, enchantment2.getMaxLevel()); - } else { - int i = 0; -@@ -81,7 +81,7 @@ public class EnchantCommand { - ItemStack itemStack = livingEntity.getMainHandItem(); - if (!itemStack.isEmpty()) { - if (enchantment2.canEnchant(itemStack) -- && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(itemStack).keySet(), enchantment)) { -+ && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(itemStack).keySet(), enchantment) || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && !itemStack.hasEnchantment(enchantment))) { // Purpur - Config to allow unsafe enchants - itemStack.enchant(enchantment, 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 d1da3600dc07107309b20ebe6e7c0c4da0e8de76..244b4719c689f153fa36381a60acc280bb0bd9b3 100644 ---- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java -+++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java -@@ -57,6 +57,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 0d9de4c61c7b26a6ff37c12fde629161fd0c3d5a..2f7897744f4aea718170698881773e9031a58a51 100644 ---- a/src/main/java/net/minecraft/server/commands/GiveCommand.java -+++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java -@@ -60,6 +60,7 @@ public class GiveCommand { - boolean flag = entityplayer.getInventory().add(itemstack1); - ItemEntity entityitem; - -+ if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) continue; // Purpur - add config option for toggling give command dropping - if (flag && itemstack1.isEmpty()) { - entityitem = entityplayer.drop(itemstack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event - if (entityitem != null) { -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 17a158ff6ce6520b69a5a0032ba4c05449dd0cf8..0a579af1f1ecd6e73a8440e6821c41338dd28829 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -111,6 +111,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 = DedicatedServer.this.reader; -@@ -219,6 +220,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - org.spigotmc.SpigotConfig.registerCommands(); - // Spigot end - io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc. -+ // 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 - // Paper start - initialize global and world-defaults configuration - this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess()); - this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); -@@ -235,7 +245,19 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command - this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark - com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics -+ // Purpur start - Purpur config files -+ try { -+ org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings")); -+ } catch (Exception e) { -+ DedicatedServer.LOGGER.error("Unable to load server configuration", e); -+ return false; -+ } -+ org.purpurmc.purpur.PurpurConfig.registerCommands(); -+ // Purpur end - Purpur config files - com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now -+ gg.pufferfish.pufferfish.PufferfishConfig.pufferfishFile = (java.io.File) options.valueOf("pufferfish-settings"); // Purpur - Fix pufferfish issues -+ gg.pufferfish.pufferfish.PufferfishConfig.load(); // Pufferfish -+ gg.pufferfish.pufferfish.PufferfishCommand.init(); // Pufferfish - - this.setPvpAllowed(dedicatedserverproperties.pvp); - this.setFlightAllowed(dedicatedserverproperties.allowFlight); -@@ -283,6 +305,30 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - if (true) throw new IllegalStateException("Failed to bind to port", ioexception); // Paper - Propagate failed to bind to port error - return false; - } -+ // Purpur start - UPnP Port Forwarding -+ 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 - UPnP Port Forwarding - - // CraftBukkit start - // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up -@@ -356,6 +402,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - DedicatedServer.LOGGER.info("JMX monitoring enabled"); - } - -+ if (gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) mobSpawnExecutor.start(); // Pufferfish -+ org.purpurmc.purpur.task.BossBarTask.startAll(); // Purpur -+ if (org.purpurmc.purpur.PurpurConfig.beeCountPayload) 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 c3ec370b83b895be0f03662e3884fa4a2442a2a6..05e16103af3fd276f0196ddf1a2e5b729b025c34 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -@@ -56,6 +56,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 - Improve ServerGUI -+ jframe.setName("Purpur Minecraft server"); // Paper - Improve ServerGUI // Purpur - - // Paper start - Improve ServerGUI - 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(); - } -@@ -159,7 +164,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); -@@ -171,10 +176,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() { // CraftBukkit - decompile error - public void focusGained(FocusEvent focusevent) {} - }); -@@ -210,7 +248,7 @@ public class MinecraftServerGui extends JComponent { - } - - private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper -- 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); -@@ -224,11 +262,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); -@@ -236,4 +277,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/gui/StatsComponent.java b/src/main/java/net/minecraft/server/gui/StatsComponent.java -index 096c89bd01cec2abd151bf6fffc4847d1bcd548f..735c2b3434715edf8fe40cbb5ce46ae10b73026c 100644 ---- a/src/main/java/net/minecraft/server/gui/StatsComponent.java -+++ b/src/main/java/net/minecraft/server/gui/StatsComponent.java -@@ -45,7 +45,7 @@ public class StatsComponent extends JComponent { - this.msgs[1] = "Avg tick: " - + DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / (double)TimeUtil.NANOSECONDS_PER_MILLISECOND) - + " ms"; -- this.msgs[2] = "TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg); -+ this.msgs[2] = "TPS from last 5s, 1m, 5m, 15m: " + String.join(", ", tpsAvg); // Purpur - Add 5 second tps average in /tps - // Paper end - Improve ServerGUI - this.values[this.vp++ & 0xFF] = (int)(l * 100L / Runtime.getRuntime().maxMemory()); - this.repaint(); -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 1c87904bb99cc40bafc9357fb2fc1703b759c3df..aea9a45c0916501f71018d3250b56da435f5664e 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -183,6 +183,9 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - // Paper end - chunk tick iteration optimisations - - -+ public boolean firstRunSpawnCounts = true; // Pufferfish -+ public final java.util.concurrent.atomic.AtomicBoolean _pufferfish_spawnCountsReady = new java.util.concurrent.atomic.AtomicBoolean(false); // Pufferfish - optimize countmobs -+ - public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { - this.level = world; - this.mainThreadProcessor = new ServerChunkCache.MainThreadExecutor(world); -@@ -511,6 +514,43 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - this.broadcastChangedChunks(gameprofilerfiller); - gameprofilerfiller.pop(); - } -+ -+ // Pufferfish start - optimize mob spawning -+ if (gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) { -+ for (ServerPlayer player : this.level.players) { -+ // Paper start - per player mob spawning backoff -+ for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) { -+ player.mobCounts[ii] = 0; -+ -+ int newBackoff = player.mobBackoffCounts[ii] - 1; // TODO make configurable bleed // TODO use nonlinear algorithm? -+ if (newBackoff < 0) { -+ newBackoff = 0; -+ } -+ player.mobBackoffCounts[ii] = newBackoff; -+ } -+ // Paper end - per player mob spawning backoff -+ } -+ if (firstRunSpawnCounts) { -+ firstRunSpawnCounts = false; -+ _pufferfish_spawnCountsReady.set(true); -+ } -+ if (_pufferfish_spawnCountsReady.getAndSet(false)) { -+ net.minecraft.server.MinecraftServer.getServer().mobSpawnExecutor.submit(() -> { -+ int mapped = distanceManager.getNaturalSpawnChunkCount(); -+ ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.Iterator objectiterator = -+ level.entityTickList.entities.iterator(ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); -+ try { -+ gg.pufferfish.pufferfish.util.IterableWrapper wrappedIterator = -+ new gg.pufferfish.pufferfish.util.IterableWrapper<>(objectiterator); -+ lastSpawnState = NaturalSpawner.createState(mapped, wrappedIterator, this::getFullChunk, null, true); -+ } finally { -+ objectiterator.finishedIterating(); -+ } -+ _pufferfish_spawnCountsReady.set(true); -+ }); -+ } -+ } -+ // Pufferfish end - } - - private void broadcastChangedChunks(ProfilerFiller profiler) { -@@ -560,6 +600,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - final int naturalSpawnChunkCount = j; - NaturalSpawner.SpawnState spawnercreature_d; // moved down - if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled -+ if (!gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) { // Pufferfish - moved down when async processing - // re-set mob counts - for (ServerPlayer player : this.level.players) { - // Paper start - per player mob spawning backoff -@@ -574,13 +615,17 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - } - // Paper end - per player mob spawning backoff - } -- spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); -+ lastSpawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); // Pufferfish - async mob spawning -+ } // Pufferfish - (endif) moved down when async processing - } else { -- spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); -+ // Pufferfish start - async mob spawning -+ lastSpawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); -+ _pufferfish_spawnCountsReady.set(true); -+ // Pufferfish end - } - // Paper end - Optional per player mob spawns - -- this.lastSpawnState = spawnercreature_d; -+ // this.lastSpawnState = spawnercreature_d; // Pufferfish - this is managed asynchronously - profiler.popPush("spawnAndTick"); - boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit - int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); -@@ -597,7 +642,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - // Paper end - PlayerNaturallySpawnCreaturesEvent - boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit - -- list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1, this.level); // CraftBukkit -+ list1 = NaturalSpawner.getFilteredSpawningCategories(lastSpawnState, this.spawnFriendlies, this.spawnEnemies, flag1, this.level); // CraftBukkit // Pufferfish - } else { - list1 = List.of(); - } -@@ -609,8 +654,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon - ChunkPos chunkcoordintpair = chunk.getPos(); - - chunk.incrementInhabitedTime(timeDelta); -- if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot -- NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, list1); -+ if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && (!gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning || _pufferfish_spawnCountsReady.get()) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot // Pufferfish -+ NaturalSpawner.spawnForChunk(this.level, chunk, lastSpawnState, list1); // Pufferfish - } - - if (true) { // Paper - rewrite chunk system -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 103e2c414780be66324bcb9cd4ea539bbdfe12ad..2bec5ed591e658765602379f9065b8089f5df6ea 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -81,7 +81,7 @@ public class ServerEntity { - @Nullable - private List> trackedDataValues; - // CraftBukkit start -- private final Set trackedPlayers; -+ public final Set trackedPlayers; // Purpur - private -> public - Item entity immunities - - public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer> consumer, Set trackedPlayers) { - this.trackedPlayers = trackedPlayers; -@@ -207,6 +207,7 @@ public class ServerEntity { - boolean flag5 = i < -32768L || i > 32767L || j < -32768L || j > 32767L || k < -32768L || k > 32767L; - - if (!this.forceStateResync && !flag5 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.onGround()) { // Paper - fix desync when a player is added to the tracker -+ if (flag2 || flag3 || this.entity instanceof AbstractArrow) { // Pufferfish - if ((!flag2 || !flag) && !(this.entity instanceof AbstractArrow)) { - if (flag2) { - packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) i), (short) ((int) j), (short) ((int) k), this.entity.onGround()); -@@ -220,6 +221,7 @@ public class ServerEntity { - flag3 = true; - flag4 = true; - } -+ } // Pufferfish - } else { - this.wasOnGround = this.entity.onGround(); - this.teleportDelay = 0; -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 1f898500d0e9b18a880645ceb0a8ff0fe75f4e48..fe8a1a073920b7cbbe3791ac1fcac3fccec6b9f7 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -224,6 +224,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - private final StructureManager structureManager; - private final StructureCheck structureCheck; - private final boolean tickTime; -+ private double preciseTime; // Purpur - Configurable daylight cycle -+ private boolean forceTime; // Purpur - Configurable daylight cycle - private final RandomSequences randomSequences; - - // CraftBukkit start -@@ -232,6 +234,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent - public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent - private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) -+ public boolean hasRidableMoveEvent = false; // Purpur - Ridables - - public LevelChunk getChunkIfLoaded(int x, int z) { - return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately -@@ -605,7 +608,24 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - // CraftBukkit end - 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 -@@ -677,6 +697,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler); - // Paper end - rewrite chunk system - this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit -+ this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle - } - - // Paper start -@@ -728,7 +749,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - 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()); -@@ -793,6 +814,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - - org.spigotmc.ActivationRange.activateEntities(this); // Spigot - this.entityTickList.forEach((entity) -> { -+ entity.activatedPriorityReset = false; // Pufferfish - DAB - if (!entity.isRemoved()) { - if (!tickratemanager.isEntityFrozen(entity)) { - gameprofilerfiller.push("checkDespawn"); -@@ -810,7 +832,20 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - } - - gameprofilerfiller.push("tick"); -- this.guardEntityTick(this::tickNonPassenger, entity); -+ // Pufferfish start - copied from this.guardEntityTick -+ try { -+ this.tickNonPassenger(entity); // Pufferfish - changed -+ } catch (Throwable throwable) { -+ if (throwable instanceof ThreadDeath) throw throwable; // Paper -+ // Paper start - Prevent tile entity and entity crashes -+ final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); -+ MinecraftServer.LOGGER.error(msg, throwable); -+ getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); -+ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ // Paper end -+ } -+ this.moonrise$midTickTasks(); // Paper - rewrite chunk system -+ // Pufferfish end - gameprofilerfiller.pop(); - } - } -@@ -842,6 +877,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - this.serverLevelData.getScheduledEvents().tick(this.server, i); - Profiler.get().pop(); - if (this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { -+ // Purpur start - Configurable daylight cycle -+ int incrementTicks = isDay() ? this.purpurConfig.daytimeTicks : this.purpurConfig.nighttimeTicks; -+ if (incrementTicks != 12000) { -+ this.preciseTime += 12000 / (double) incrementTicks; -+ this.setDayTime(this.preciseTime); -+ } else -+ // Purpur end - Configurable daylight cycle - this.setDayTime(this.levelData.getDayTime() + 1L); - } - -@@ -850,8 +892,22 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - - public void setDayTime(long timeOfDay) { - this.serverLevelData.setDayTime(timeOfDay); -+ // Purpur start - Configurable daylight cycle -+ this.preciseTime = timeOfDay; -+ this.forceTime = false; -+ } -+ public void setDayTime(double i) { -+ this.serverLevelData.setDayTime((long) i); -+ this.forceTime = true; -+ // Purpur end - Configurable daylight cycle - } - -+ // Purpur start - Configurable daylight cycle -+ public boolean isForceTime() { -+ return this.forceTime; -+ } -+ // Purpur end - Configurable daylight cycle -+ - public void tickCustomSpawners(boolean spawnMonsters, boolean spawnAnimals) { - Iterator iterator = this.customSpawners.iterator(); - -@@ -931,7 +987,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("thunder"); -- if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && simpleRandom.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder // Paper - optimise random ticking -+ if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && /*simpleRandom.nextInt(this.spigotConfig.thunderChance) == 0*/ chunk.shouldDoLightning(this.simpleRandom)) { // Spigot // Paper - Option to disable thunder // Paper - optimise random ticking // Pufferfish - replace random with shouldDoLightning - BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); - - if (this.isRainingAt(blockposition)) { -@@ -939,10 +995,18 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - 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 - Configurable spawn chances for skeleton horses - - if (flag1) { -- SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); -+ // Purpur start -+ net.minecraft.world.entity.animal.horse.AbstractHorse entityhorseskeleton; -+ if (purpurConfig.zombieHorseSpawnChance > 0D && random.nextDouble() <= purpurConfig.zombieHorseSpawnChance) { -+ entityhorseskeleton = EntityType.ZOMBIE_HORSE.create(this, EntitySpawnReason.EVENT); -+ } else { -+ entityhorseskeleton = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); -+ 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 -@@ -1023,7 +1087,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - 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); -@@ -1072,11 +1136,27 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - 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)); - } - -@@ -1216,6 +1296,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - @VisibleForTesting - public void resetWeatherCycle() { - // CraftBukkit start -+ if (this.purpurConfig.rainStopsAfterSleep) // Purpur - this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents - // 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.... -@@ -1223,6 +1304,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - this.serverLevelData.setRainTime(0); - } - // CraftBukkit end -+ if (this.purpurConfig.thunderStopsAfterSleep) // Purpur - this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents - // CraftBukkit start - // If we stop due to everyone sleeping we should reset the weather duration to some other random value. -@@ -2772,7 +2854,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe - // 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 - Fix merchant inventory not closing on entity removal -- 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 - Allow void trading - merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); - } - // Paper end - Fix merchant inventory not closing on entity removal -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index fc7f7a34babd095a51b5321f600aef65a2a9d123..6f39fd99ffb28d6c0267f4251c54af0c519289db 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -327,6 +327,10 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent - public @Nullable String clientBrandName = null; // Paper - Brand support - public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event -+ public boolean purpurClient = false; // Purpur - Purpur client support -+ private boolean tpsBar = false; // Purpur -+ private boolean compassBar = false; // Purpur -+ private boolean ramBar = false; // Purpur - Implement ram and rambar commands - - // Paper start - rewrite chunk system - private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; -@@ -689,6 +693,9 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - }); - } - -+ if (nbt.contains("Purpur.TPSBar")) { this.tpsBar = nbt.getBoolean("Purpur.TPSBar"); } // Purpur -+ if (nbt.contains("Purpur.CompassBar")) { this.compassBar = nbt.getBoolean("Purpur.CompassBar"); } // Purpur -+ if (nbt.contains("Purpur.RamBar")) { this.ramBar = nbt.getBoolean("Purpur.RamBar"); } // Purpur - Implement ram and rambar commands - } - - @Override -@@ -741,6 +748,9 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - } - - this.saveEnderPearls(nbt); -+ nbt.putBoolean("Purpur.RamBar", this.ramBar); // Purpur - Implement ram and rambar commands -+ nbt.putBoolean("Purpur.TPSBar", this.tpsBar); // Purpur -+ nbt.putBoolean("Purpur.CompassBar", this.compassBar); // Purpur - } - - private void saveParentVehicle(CompoundTag nbt) { -@@ -1029,6 +1039,15 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - this.trackEnteredOrExitedLavaOnVehicle(); - this.updatePlayerAttributes(); - this.advancements.flushDirty(this); -+ -+ // Purpur start - Ridables -+ if (this.level().purpurConfig.useNightVisionWhenRiding && this.getVehicle() != null && this.getVehicle().getRider() == this && this.level().getGameTime() % 100 == 0) { // 5 seconds -+ MobEffectInstance nightVision = this.getEffect(MobEffects.NIGHT_VISION); -+ if (nightVision == null || nightVision.getDuration() <= 300) { // 15 seconds -+ this.addEffect(new MobEffectInstance(MobEffects.NIGHT_VISION, 400, 0)); // 20 seconds -+ } -+ } -+ // Purpur end - Ridables - } - - private void updatePlayerAttributes() { -@@ -1326,6 +1345,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - })); - PlayerTeam scoreboardteam = this.getTeam(); - -+ if (org.purpurmc.purpur.PurpurConfig.deathMessageOnlyBroadcastToAffectedPlayer) this.sendSystemMessage(ichatbasecomponent); else // Purpur - if (scoreboardteam != null && scoreboardteam.getDeathMessageVisibility() != Team.Visibility.ALWAYS) { - if (scoreboardteam.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) { - this.server.getPlayerList().broadcastSystemToTeam(this, ichatbasecomponent); -@@ -1429,6 +1449,16 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - if (this.isInvulnerableTo(world, source)) { - return false; - } else { -+ // Purpur start - Add boat fall damage config -+ if (source.is(net.minecraft.tags.DamageTypeTags.IS_FALL)) { -+ 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 - Add boat fall damage config - Entity entity = source.getEntity(); - - if (entity instanceof net.minecraft.world.entity.player.Player) { -@@ -1655,6 +1685,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); - this.unsetRemoved(); - // CraftBukkit end -+ this.portalPos = io.papermc.paper.util.MCUtil.toBlockPosition(exit); // Purpur - Fix stuck in portals - this.setServerLevel(worldserver); - this.connection.internalTeleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); // CraftBukkit - use internal teleport without event - this.connection.resetPosition(); -@@ -1765,7 +1796,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - return entitymonster.isPreventingPlayerRest(this.serverLevel(), this); - }); - -- if (!list.isEmpty()) { -+ if (!this.level().purpurConfig.playerSleepNearMonsters && !list.isEmpty()) { // Purpur - return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_SAFE); - } - } -@@ -1805,7 +1836,19 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - }); - - if (!this.serverLevel().canSleepThroughNights()) { -- this.displayClientMessage(Component.translatable("sleep.not_possible"), true); -+ // Purpur start -+ Component clientMessage; -+ if (org.purpurmc.purpur.PurpurConfig.sleepNotPossible.isBlank()) { -+ clientMessage = null; -+ } else if (!org.purpurmc.purpur.PurpurConfig.sleepNotPossible.equalsIgnoreCase("default")) { -+ clientMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepNotPossible)); -+ } else { -+ clientMessage = Component.translatable("sleep.not_possible"); -+ } -+ if (clientMessage != null) { -+ this.displayClientMessage(clientMessage, true); -+ } -+ // Purpur end - } - - ((ServerLevel) this.level()).updateSleepingPlayerList(); -@@ -1919,6 +1962,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - - @Override - public void openTextEdit(SignBlockEntity sign, boolean front) { -+ if (level().purpurConfig.signAllowColors) this.connection.send(sign.getTranslatedUpdatePacket(textFilteringEnabled, front)); // Purpur - this.connection.send(new ClientboundBlockUpdatePacket(this.level(), sign.getBlockPos())); - this.connection.send(new ClientboundOpenSignEditorPacket(sign.getBlockPos(), front)); - } -@@ -2235,6 +2279,26 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - this.lastSentExp = -1; // CraftBukkit - Added to reset - } - -+ // Purpur start - Component related conveniences -+ public void sendActionBarMessage(@Nullable String message) { -+ if (message != null && !message.isEmpty()) { -+ sendActionBarMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message)); -+ } -+ } -+ -+ public void sendActionBarMessage(@Nullable net.kyori.adventure.text.Component message) { -+ if (message != null) { -+ sendActionBarMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message)); -+ } -+ } -+ -+ public void sendActionBarMessage(@Nullable Component message) { -+ if (message != null) { -+ displayClientMessage(message, true); -+ } -+ } -+ // Purpur end - Component related conveniences -+ - @Override - public void displayClientMessage(Component message, boolean overlay) { - this.sendSystemMessage(message, overlay); -@@ -2456,6 +2520,20 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - return new CommandSourceStack(this.commandSource(), this.position(), this.getRotationVector(), this.serverLevel(), this.getPermissionLevel(), this.getName().getString(), this.getDisplayName(), this.server, this); - } - -+ // Purpur start - Component related conveniences -+ public void sendMiniMessage(@Nullable String message) { -+ if (message != null && !message.isEmpty()) { -+ this.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message)); -+ } -+ } -+ -+ public void sendMessage(@Nullable net.kyori.adventure.text.Component message) { -+ if (message != null) { -+ this.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message)); -+ } -+ } -+ // Purpur end - Component related conveniences -+ - public void sendSystemMessage(Component message) { - this.sendSystemMessage(message, false); - } -@@ -2577,8 +2655,68 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - - public void resetLastActionTime() { - this.lastActionTime = Util.getMillis(); -+ this.setAfk(false); // Purpur - } - -+ // Purpur start - AFK API -+ private boolean isAfk = false; -+ -+ @Override -+ public void setAfk(boolean afk) { -+ if (this.isAfk == afk) { -+ return; -+ } -+ -+ String msg = afk ? org.purpurmc.purpur.PurpurConfig.afkBroadcastAway : org.purpurmc.purpur.PurpurConfig.afkBroadcastBack; -+ -+ org.purpurmc.purpur.event.PlayerAFKEvent event = new org.purpurmc.purpur.event.PlayerAFKEvent(this.getBukkitEntity(), afk, this.level().purpurConfig.idleTimeoutKick, msg, !Bukkit.isPrimaryThread()); -+ if (!event.callEvent() || event.shouldKick()) { -+ return; -+ } -+ -+ this.isAfk = afk; -+ -+ if (!afk) { -+ resetLastActionTime(); -+ } -+ -+ msg = event.getBroadcastMsg(); -+ if (msg != null && !msg.isEmpty()) { -+ String playerName = this.getGameProfile().getName(); -+ if (org.purpurmc.purpur.PurpurConfig.afkBroadcastUseDisplayName) { -+ net.kyori.adventure.text.Component playerDisplayNameComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(this.getBukkitEntity().getDisplayName()); -+ playerName = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText().serialize(playerDisplayNameComponent); -+ } -+ server.getPlayerList().broadcastMiniMessage(String.format(msg, playerName), false); -+ } -+ -+ if (this.level().purpurConfig.idleTimeoutUpdateTabList) { -+ String scoreboardName = getScoreboardName(); -+ String playerListName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().serialize(getBukkitEntity().playerListName()); -+ String[] split = playerListName.split(scoreboardName); -+ String prefix = (split.length > 0 ? split[0] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix, ""); -+ String suffix = (split.length > 1 ? split[1] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, ""); -+ if (afk) { -+ getBukkitEntity().setPlayerListName(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix + prefix + scoreboardName + suffix + org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, true); -+ } else { -+ getBukkitEntity().setPlayerListName(prefix + scoreboardName + suffix, true); -+ } -+ } -+ -+ ((ServerLevel) this.level()).updateSleepingPlayerList(); -+ } -+ -+ @Override -+ public boolean isAfk() { -+ return this.isAfk; -+ } -+ -+ @Override -+ public boolean canBeCollidedWith() { -+ return !this.isAfk() && super.canBeCollidedWith(); -+ } -+ // Purpur end - AFK API -+ - public ServerStatsCounter getStats() { - return this.stats; - } -@@ -3285,4 +3423,50 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple - 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.teleport(to); -+ } else { -+ this.server.getPlayerList().respawn(this, true, RemovalReason.KILLED, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH, to); -+ } -+ } -+ -+ 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; -+ } -+ -+ public boolean ramBar() { -+ return this.ramBar; -+ } -+ -+ public void ramBar(boolean ramBar) { -+ this.ramBar = ramBar; -+ } -+ // 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 a96f859a5d0c6ec692d4627a69f3c9ee49199dbc..88eb3774f688bcff383efa7f113bd0b1b97d8a11 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -403,6 +403,7 @@ public class ServerPlayerGameMode { - } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction - 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 - -@@ -523,6 +524,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); - boolean cancelledBlock = false; -@@ -583,7 +585,7 @@ public class ServerPlayerGameMode { - ItemStack itemstack1 = stack.copy(); - InteractionResult enuminteractionresult; - -- if (!flag1) { -+ if (!flag1 || (player.level().purpurConfig.composterBulkProcess && iblockdata.is(Blocks.COMPOSTER))) { // Purpur - InteractionResult enuminteractionresult1 = iblockdata.useItemOn(player.getItemInHand(hand), world, player, hand, hitResult); - - if (enuminteractionresult1.consumesAction()) { -@@ -631,4 +633,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/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index e4b0dc3121101d54394a0c3a413dabf8103b2ea6..a8484b9659f175cc20985bf66082616ceb31df4d 100644 ---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java -+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -@@ -336,6 +336,7 @@ public class WorldGenRegion implements WorldGenLevel { - return true; - } else { - // Paper start - Buffer OOB setBlock calls -+ if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressSetBlockFarChunk) // Purpur - if (!hasSetFarWarned) { - Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + String.valueOf(pos) + ", status: " + String.valueOf(this.generatingStep.targetStatus()) + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get())); - hasSetFarWarned = true; -diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index b0bc66dc7248aae691dcab68b925b52a1695e63f..df48c2754dc1ebd52addd8ae768cba5916ce3969 100644 ---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -80,11 +80,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - private long keepAliveChallenge; - private long closedListenerTime; - private boolean closed = false; -+ private it.unimi.dsi.fastutil.longs.LongList keepAlives = new it.unimi.dsi.fastutil.longs.LongArrayList(); // Purpur - private int latency; - private volatile boolean suspendFlushingOnServerThread = false; - public final java.util.Map packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks - private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit - protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support -+ protected static final ResourceLocation PURPUR_CLIENT = ResourceLocation.fromNamespaceAndPath("purpur", "client"); // Purpur - Purpur client support - - public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit - this.server = minecraftserver; -@@ -136,6 +138,16 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - - @Override - public void handleKeepAlive(ServerboundKeepAlivePacket packet) { -+ // Purpur start -+ if (org.purpurmc.purpur.PurpurConfig.useAlternateKeepAlive) { -+ if (this.keepAlivePending && !keepAlives.isEmpty() && keepAlives.contains(packet.getId())) { -+ int ping = (int) (Util.getMillis() - packet.getId()); -+ this.latency = (this.latency * 3 + ping) / 4; -+ this.keepAlivePending = false; -+ keepAlives.clear(); // we got a valid response, lets roll with it and forget the rest -+ } -+ } else -+ // Purpur end - //PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // CraftBukkit // Paper - handle ServerboundKeepAlivePacket async - if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) { - int i = (int) (Util.getMillis() - this.keepAliveTime); -@@ -179,6 +191,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); - this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause - } -+ // Purpur start - Purpur client support -+ } else if (identifier.equals(PURPUR_CLIENT)) { -+ try { -+ player.purpurClient = true; -+ } catch (Exception ignore) { -+ } -+ // Purpur end - Purpur client support - } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) { - try { - String channels = payload.toString(com.google.common.base.Charsets.UTF_8); -@@ -261,6 +280,21 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - long currentTime = Util.getMillis(); - long elapsedTime = currentTime - this.keepAliveTime; - -+ // Purpur start -+ if (org.purpurmc.purpur.PurpurConfig.useAlternateKeepAlive) { -+ if (elapsedTime >= 1000L) { // 1 second -+ if (this.keepAlivePending && !this.processedDisconnect && keepAlives.size() * 1000L >= KEEPALIVE_LIMIT) { -+ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); -+ } else if (this.checkIfClosed(currentTime)) { -+ this.keepAlivePending = true; -+ this.keepAliveTime = currentTime; // hijack this field for 1 second intervals -+ this.keepAlives.add(currentTime); // currentTime is ID -+ this.send(new ClientboundKeepAlivePacket(currentTime)); -+ } -+ } -+ } else -+ // Purpur end -+ - if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // Paper - use vanilla's 15000L between keep alive packets - if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // Paper - check keepalive limit, don't fire if already disconnected - this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 84fa24880d02dc7ba1ec8bda3575be38447fd4b2..c783c17d45beda8297171d0834350197808a7335 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -344,6 +344,20 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private boolean justTeleported = false; - // CraftBukkit end - -+ // Purpur start - AFK API -+ private final com.google.common.cache.LoadingCache kickPermissionCache = com.google.common.cache.CacheBuilder.newBuilder() -+ .maximumSize(1000) -+ .expireAfterWrite(1, java.util.concurrent.TimeUnit.MINUTES) -+ .build( -+ new com.google.common.cache.CacheLoader<>() { -+ @Override -+ public Boolean load(CraftPlayer player) { -+ return player.hasPermission("purpur.bypassIdleKick"); -+ } -+ } -+ ); -+ // Purpur end - AFK API -+ - @Override - public void tick() { - if (this.ackBlockChangesUpTo > -1) { -@@ -400,6 +414,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.recipeSpamPackets.tick(); // Paper - auto recipe limit - this.dropSpamThrottler.tick(); - if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits -+ // Purpur start - AFK API -+ this.player.setAfk(true); -+ if (!this.player.level().purpurConfig.idleTimeoutKick || (!Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")) && kickPermissionCache.getUnchecked(this.player.getBukkitEntity()))) { -+ return; -+ } -+ // Purpur end - AFK API - this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 - this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause - } -@@ -656,6 +676,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.lastYaw = to.getYaw(); - this.lastPitch = to.getPitch(); - -+ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur - AFK API -+ - Location oldTo = to.clone(); - PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); - this.cserver.getPluginManager().callEvent(event); -@@ -734,6 +756,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (packet.getId() == this.awaitingTeleport) { - if (this.awaitingPositionFromClient == null) { -+ ServerGamePacketListenerImpl.LOGGER.warn("Disconnected on accept teleport packet. Was not expecting position data from client at this time"); // Purpur - this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause - return; - } -@@ -1226,6 +1249,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - @Override - public void handleEditBook(ServerboundEditBookPacket packet) { -+ if (!gg.pufferfish.pufferfish.PufferfishConfig.enableBooks && !this.player.getBukkitEntity().hasPermission("pufferfish.usebooks")) return; // Pufferfish - // Paper start - Book size limits - final io.papermc.paper.configuration.type.number.IntOr.Disabled pageMax = io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.pageMax; - if (!this.cserver.isPrimaryThread() && pageMax.enabled()) { -@@ -1234,6 +1258,10 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - final int maxBookPageSize = pageMax.intValue(); - final double multiplier = Math.clamp(io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier, 0.3D, 1D); - long byteAllowed = maxBookPageSize; -+ // Purpur start -+ int slot = packet.slot(); -+ ItemStack itemstack = Inventory.isHotbarSlot(slot) || slot == Inventory.SLOT_OFFHAND ? this.player.getInventory().getItem(slot) : ItemStack.EMPTY; -+ // Purpur end - for (final String page : pageList) { - final int byteLength = page.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; - byteTotal += byteLength; -@@ -1258,7 +1286,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - if (byteTotal > byteAllowed) { -- ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); -+ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send too large of a book. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); -+ org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur - this.disconnectAsync(Component.literal("Book too large!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect - return; - } -@@ -1280,10 +1309,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - Objects.requireNonNull(list); - optional.ifPresent(list::add); - list.addAll(packet.pages()); -+ // Purpur start -+ boolean hasEditPerm = getCraftPlayer().hasPermission("purpur.book.color.edit"); -+ boolean hasSignPerm = hasEditPerm || getCraftPlayer().hasPermission("purpur.book.color.sign"); -+ // Purpur end - Consumer> consumer = optional.isPresent() ? (list1) -> { -- this.signBook((FilteredText) list1.get(0), list1.subList(1, list1.size()), i); -+ this.signBook((FilteredText) list1.get(0), list1.subList(1, list1.size()), i, hasSignPerm); // Purpur - } : (list1) -> { -- this.updateBookContents(list1, i); -+ this.updateBookContents(list1, i, hasEditPerm); // Purpur - }; - - this.filterTextPacket((List) list).thenAcceptAsync(consumer, this.server); -@@ -1291,13 +1324,18 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - private void updateBookContents(List pages, int slotId) { -+ // Purpur start -+ updateBookContents(pages, slotId, false); -+ } -+ private void updateBookContents(List pages, int slotId, boolean hasPerm) { -+ // Purpur end - // CraftBukkit start - ItemStack handItem = this.player.getInventory().getItem(slotId); - ItemStack itemstack = handItem.copy(); - // CraftBukkit end - - if (itemstack.has(DataComponents.WRITABLE_BOOK_CONTENT)) { -- List> list1 = pages.stream().map(this::filterableFromOutgoing).toList(); -+ List> list1 = pages.stream().map(filteredText -> filterableFromOutgoing(filteredText).map(s -> color(s, hasPerm))).toList(); // Purpur - - itemstack.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list1)); - this.player.getInventory().setItem(slotId, CraftEventFactory.handleEditBookEvent(this.player, slotId, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) -@@ -1305,6 +1343,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - - private void signBook(FilteredText title, List pages, int slotId) { -+ // Purpur start -+ signBook(title, pages, slotId, false); -+ } -+ private void signBook(FilteredText title, List pages, int slotId, boolean hasPerm) { -+ // Purpur end - ItemStack itemstack = this.player.getInventory().getItem(slotId); - - if (itemstack.has(DataComponents.WRITABLE_BOOK_CONTENT)) { -@@ -1312,10 +1355,10 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - itemstack1.remove(DataComponents.WRITABLE_BOOK_CONTENT); - List> list1 = (List>) (List) pages.stream().map((filteredtext1) -> { // CraftBukkit - decompile error -- return this.filterableFromOutgoing(filteredtext1).map(Component::literal); -+ return this.filterableFromOutgoing(filteredtext1).map(s -> hexColor(s, hasPerm)); // Purpur - }).toList(); - -- itemstack1.set(DataComponents.WRITTEN_BOOK_CONTENT, new WrittenBookContent(this.filterableFromOutgoing(title), this.player.getName().getString(), 0, list1, true)); -+ itemstack1.set(DataComponents.WRITTEN_BOOK_CONTENT, new WrittenBookContent(this.filterableFromOutgoing(title).map(s -> color(s, hasPerm)), this.player.getName().getString(), 0, list1, true)); // Purpur - CraftEventFactory.handleEditBookEvent(this.player, slotId, itemstack, itemstack1); // CraftBukkit - this.player.getInventory().setItem(slotId, itemstack); // CraftBukkit - event factory updates the hand book - } -@@ -1325,6 +1368,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - return this.player.isTextFilteringEnabled() ? Filterable.passThrough(message.filteredOrEmpty()) : Filterable.from(message); - } - -+ // Purpur start -+ private Component hexColor(String str, boolean hasPerm) { -+ return hasPerm ? PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().deserialize(str)) : Component.literal(str); -+ } -+ -+ private String color(String str, boolean hasPerm) { -+ return hasPerm ? org.bukkit.ChatColor.color(str, false) : str; -+ } -+ // Purpur end -+ - @Override - public void handleEntityTagQuery(ServerboundEntityTagQueryPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); -@@ -1374,7 +1427,15 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - @Override - public void handleMovePlayer(ServerboundMovePlayerPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); -- 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) { -+ 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 -+ // Purpur end - this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause - } else { - ServerLevel worldserver = this.player.serverLevel(); -@@ -1554,7 +1615,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - movedWrongly = true; - if (event.getLogWarning()) - // Paper end -- ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); -+ ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!, ({})", this.player.getName().getString(), d11); // Purpur - AFK API - } // Paper - } - -@@ -1622,6 +1683,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.lastYaw = to.getYaw(); - this.lastPitch = to.getPitch(); - -+ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur - AFK API -+ - Location oldTo = to.clone(); - PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); - this.cserver.getPluginManager().callEvent(event); -@@ -1667,6 +1730,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - this.player.tryResetCurrentImpulseContext(); - } - -+ // Purpur start - Dont run with scissors! -+ if (this.player.serverLevel().purpurConfig.dontRunWithScissors && this.player.isSprinting() && !(this.player.serverLevel().purpurConfig.ignoreScissorsInWater && this.player.isInWater()) && !(this.player.serverLevel().purpurConfig.ignoreScissorsInLava && this.player.isInLava()) && (isScissors(this.player.getItemInHand(InteractionHand.MAIN_HAND)) || isScissors(this.player.getItemInHand(InteractionHand.OFF_HAND))) && (int) (Math.random() * 10) == 0) { -+ this.player.hurtServer(this.player.serverLevel(), this.player.damageSources().scissors(), (float) this.player.serverLevel().purpurConfig.scissorsRunningDamage); -+ if (!org.purpurmc.purpur.PurpurConfig.dontRunWithScissors.isBlank()) this.player.sendActionBarMessage(org.purpurmc.purpur.PurpurConfig.dontRunWithScissors); -+ } -+ // Purpur end - Dont run with scissors! -+ - 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(); -@@ -1706,6 +1776,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - } - } - -+ // Purpur start - Dont run with scissors! -+ public boolean isScissors(ItemStack stack) { -+ if (!stack.is(Items.SHEARS)) return false; -+ -+ ResourceLocation itemModelReference = stack.get(net.minecraft.core.component.DataComponents.ITEM_MODEL); -+ if (itemModelReference != null && itemModelReference.equals(this.player.serverLevel().purpurConfig.dontRunWithScissorsItemModelReference)) return true; -+ -+ return stack.getOrDefault(DataComponents.CUSTOM_MODEL_DATA, net.minecraft.world.item.component.CustomModelData.EMPTY).equals(net.minecraft.world.item.component.CustomModelData.EMPTY); -+ } -+ // Purpur end - Dont run with scissors! -+ - // Paper start - optimise out extra getCubes - private boolean hasNewCollision(final ServerLevel world, final Entity entity, final AABB oldBox, final AABB newBox) { - final List collisionsBB = new java.util.ArrayList<>(); -@@ -2084,6 +2165,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - 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 { -@@ -2874,6 +2956,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - AABB axisalignedbb = entity.getBoundingBox(); - - if (this.player.canInteractWithEntity(axisalignedbb, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0D))) { // Paper - configurable lenience value for interact range -+ 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); -@@ -2887,6 +2970,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - - ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event); - -+ player.processClick(enumhand); // Purpur - Ridables -+ - // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a - if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { - entity.resendPossiblyDesyncedEntityData(ServerGamePacketListenerImpl.this.player); // Paper - The entire mob gets deleted, so resend it -diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 033755682c61c889723c3669b5cff4de147f637e..16069b9cbf6c7679c28a2e9a54e77d23cd10e541 100644 ---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -322,7 +322,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, - ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!"); - ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(s1)); // Spigot - } 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", s1); - } - } catch (AuthenticationUnavailableException authenticationunavailableexception) { -diff --git a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java -index 532f09089b8d6798999cf3f83e852df7479e450e..43c63d203859eaa0999937e2f9254c22510139aa 100644 ---- a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java -@@ -154,6 +154,7 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene - this.connection.send(new ClientboundStatusResponsePacket(ping)); - // CraftBukkit end - */ -+ if (MinecraftServer.getServer().getStatus().version().isEmpty()) return; // Purpur - do not respond to pings before we know the protocol version - com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(MinecraftServer.getServer(), this.connection); - // Paper end - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 9b71655a425356132afb786eff623f558e1e3498..5b1705794a8c3914cb11fdd35f75c8e0c128ecd0 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -419,6 +419,7 @@ public abstract class PlayerList { - scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); - } - // Paper end - Configurable player collision -+ org.purpurmc.purpur.task.BossBarTask.addToAll(player); // Purpur - PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); - // Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead - if (player.isDeadOrDying()) { -@@ -540,6 +541,7 @@ public abstract class PlayerList { - } - public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { - // Paper end - Fix kick event leave message not being sent -+ org.purpurmc.purpur.task.BossBarTask.removeFromAll(entityplayer.getBukkitEntity()); // Purpur - ServerLevel worldserver = entityplayer.serverLevel(); - - entityplayer.awardStat(Stats.LEAVE_GAME); -@@ -709,7 +711,7 @@ public abstract class PlayerList { - event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.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 - } - } -@@ -991,6 +993,20 @@ public abstract class PlayerList { - } - // CraftBukkit end - -+ // Purpur start - Component related conveniences -+ public void broadcastMiniMessage(@Nullable String message, boolean overlay) { -+ if (message != null && !message.isEmpty()) { -+ this.broadcastMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), overlay); -+ } -+ } -+ -+ public void broadcastMessage(@Nullable net.kyori.adventure.text.Component message, boolean overlay) { -+ if (message != null) { -+ this.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), overlay); -+ } -+ } -+ // Purpur end - Component related conveniences -+ - public void broadcastAll(Packet packet, ResourceKey dimension) { - Iterator iterator = this.players.iterator(); - -@@ -1094,6 +1110,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)); - } -@@ -1102,6 +1119,27 @@ public abstract class PlayerList { - player.getBukkitEntity().recalculatePermissions(); // CraftBukkit - this.server.getCommands().sendCommands(player); - } // Paper - Add sendOpLevel API -+ -+ // Purpur start -+ if (org.purpurmc.purpur.PurpurConfig.enderChestSixRows && org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) { -+ org.bukkit.craftbukkit.entity.CraftHumanEntity bukkit = player.getBukkitEntity(); -+ if (bukkit.hasPermission("purpur.enderchest.rows.six")) { -+ player.sixRowEnderchestSlotCount = 54; -+ } else if (bukkit.hasPermission("purpur.enderchest.rows.five")) { -+ player.sixRowEnderchestSlotCount = 45; -+ } else if (bukkit.hasPermission("purpur.enderchest.rows.four")) { -+ player.sixRowEnderchestSlotCount = 36; -+ } else if (bukkit.hasPermission("purpur.enderchest.rows.three")) { -+ player.sixRowEnderchestSlotCount = 27; -+ } else if (bukkit.hasPermission("purpur.enderchest.rows.two")) { -+ player.sixRowEnderchestSlotCount = 18; -+ } else if (bukkit.hasPermission("purpur.enderchest.rows.one")) { -+ player.sixRowEnderchestSlotCount = 9; -+ } -+ } else { -+ player.sixRowEnderchestSlotCount = -1; -+ } -+ //Purpur end - } - - public boolean isWhiteListed(GameProfile profile) { -diff --git a/src/main/java/net/minecraft/server/players/SleepStatus.java b/src/main/java/net/minecraft/server/players/SleepStatus.java -index 823efad652d8ff9e96b99375b102fef6f017716e..bdf0240840d92bf95f94c6fb1125eeaa105e303b 100644 ---- a/src/main/java/net/minecraft/server/players/SleepStatus.java -+++ b/src/main/java/net/minecraft/server/players/SleepStatus.java -@@ -19,7 +19,7 @@ public class SleepStatus { - - public boolean areEnoughDeepSleeping(int percentage, List players) { - // CraftBukkit start -- int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping; }).count(); -+ int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping || (eh.level().purpurConfig.idleTimeoutCountAsSleeping && eh.isAfk()); }).count(); // Purpur - AFK API - boolean anyDeepSleep = players.stream().anyMatch(Player::isSleepingLongEnough); - - return anyDeepSleep && j >= this.sleepersNeeded(percentage); -@@ -52,7 +52,7 @@ public class SleepStatus { - - if (!entityplayer.isSpectator()) { - ++this.activePlayers; -- if (entityplayer.isSleeping() || entityplayer.fauxSleeping) { // CraftBukkit -+ if ((entityplayer.isSleeping() || entityplayer.fauxSleeping) || (entityplayer.level().purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk())) { // CraftBukkit // Purpur - AFK API - ++this.sleepingPlayers; - } - // CraftBukkit start -diff --git a/src/main/java/net/minecraft/stats/ServerRecipeBook.java b/src/main/java/net/minecraft/stats/ServerRecipeBook.java -index 5c7484ce2850a2eb698a2183b81134b89b0bbcc7..fef09e71f73f6f24a01289ef05c20a5ffc5fadb1 100644 ---- a/src/main/java/net/minecraft/stats/ServerRecipeBook.java -+++ b/src/main/java/net/minecraft/stats/ServerRecipeBook.java -@@ -159,6 +159,7 @@ public class ServerRecipeBook extends RecipeBook { - ResourceKey> resourcekey = ResourceKey.create(Registries.RECIPE, ResourceLocation.parse(s)); - - if (!validPredicate.test(resourcekey)) { -+ if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressUnrecognizedRecipeErrors) // Purpur - ServerRecipeBook.LOGGER.error("Tried to load unrecognized recipe: {} removed now.", resourcekey); - } else { - handler.accept(resourcekey); -diff --git a/src/main/java/net/minecraft/util/StringUtil.java b/src/main/java/net/minecraft/util/StringUtil.java -index 6c33002dc8bbb3759c3156302ab7d1f26ce5e8ee..c89fc375aff548a2b03eaf4da3b6a075012df012 100644 ---- a/src/main/java/net/minecraft/util/StringUtil.java -+++ b/src/main/java/net/minecraft/util/StringUtil.java -@@ -69,6 +69,7 @@ public class StringUtil { - - // Paper start - Username validation - public static boolean isReasonablePlayerName(final String name) { -+ if (true) return org.purpurmc.purpur.PurpurConfig.usernameValidCharactersPattern.matcher(name).matches(); // Purpur - if (name.isEmpty() || name.length() > 16) { - return false; - } -diff --git a/src/main/java/net/minecraft/world/damagesource/CombatRules.java b/src/main/java/net/minecraft/world/damagesource/CombatRules.java -index 064c1e33f3feee77837bb57887877ae1ca39548d..ffd009bca3fdbfd0b14df78072ef8d472a57cd65 100644 ---- a/src/main/java/net/minecraft/world/damagesource/CombatRules.java -+++ b/src/main/java/net/minecraft/world/damagesource/CombatRules.java -@@ -15,7 +15,7 @@ public class CombatRules { - - public static float getDamageAfterAbsorb(LivingEntity armorWearer, float damageAmount, DamageSource damageSource, float armor, float armorToughness) { - float f = 2.0F + armorToughness / 4.0F; -- float g = Mth.clamp(armor - damageAmount / f, armor * 0.2F, 20.0F); -+ float g = Mth.clamp(armor - damageAmount / f, armor * 0.2F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur - float h = g / 25.0F; - ItemStack itemStack = damageSource.getWeaponItem(); - float i; -@@ -30,7 +30,7 @@ public class CombatRules { - } - - 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/CombatTracker.java b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java -index 99a7e9eb75231c15bd8bb24fbb4e296bc9fdedff..6853b91e047b92ae4042a26d6aad84874be11709 100644 ---- a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java -+++ b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java -@@ -54,7 +54,7 @@ public class CombatTracker { - - private Component getMessageForAssistedFall(Entity attacker, Component attackerDisplayName, String itemDeathTranslationKey, String deathTranslationKey) { - ItemStack itemStack = attacker instanceof LivingEntity livingEntity ? livingEntity.getMainHandItem() : ItemStack.EMPTY; -- return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME) -+ return !itemStack.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemStack.has(DataComponents.CUSTOM_NAME)) // Purpur - ? Component.translatable(itemDeathTranslationKey, this.mob.getDisplayName(), attackerDisplayName, itemStack.getDisplayName()) - : Component.translatable(deathTranslationKey, this.mob.getDisplayName(), attackerDisplayName); - } -@@ -98,6 +98,15 @@ public class CombatTracker { - Component component = ComponentUtils.wrapInSquareBrackets(Component.translatable(string + ".link")).withStyle(INTENTIONAL_GAME_DESIGN_STYLE); - return Component.translatable(string + ".message", this.mob.getDisplayName(), component); - } else { -+ // Purpur start - Dont run with scissors! -+ if (damageSource.isScissors()) { -+ return damageSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgRunWithScissors, this.mob); -+ // Purpur start - Stonecutter damage -+ } else if (damageSource.isStonecutter()) { -+ return damageSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgStonecutter, this.mob); -+ // Purpur end - Stonecutter damage -+ } -+ // Purpur end - Dont run with scissors! - return damageSource.getLocalizedDeathMessage(this.mob); - } - } -diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -index bb1a60180e58c1333e7bb33e8acf1b0225eda8a8..3781bca61379516d537650c79c614933454fdcd8 100644 ---- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java -+++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java -@@ -29,6 +29,8 @@ public class DamageSource { - private boolean sweep = false; - private boolean melting = false; - private boolean poison = false; -+ private boolean scissors = false; // Purpur - Dont run with scissors! -+ private boolean stonecutter = false; // Purpur - Stonecutter damage - @Nullable - private Entity customEventDamager = null; // This field is a helper for when causing entity damage is not set by vanilla // Paper - fix DamageSource API - -@@ -59,6 +61,27 @@ public class DamageSource { - return this.poison; - } - -+ // Purpur start - Dont run with scissors! -+ public DamageSource scissors() { -+ this.scissors = true; -+ return this; -+ } -+ -+ public boolean isScissors() { -+ return this.scissors; -+ } -+ // Purpur end - Dont run with scissors! -+ // Purpur start - - Stonecutter damage -+ public DamageSource stonecutter() { -+ this.stonecutter = true; -+ return this; -+ } -+ -+ public boolean isStonecutter() { -+ return this.stonecutter; -+ } -+ // Purpur end - Stonecutter damage -+ - // Paper start - fix DamageSource API - @Nullable - public Entity getCustomEventDamager() { -@@ -117,6 +140,8 @@ public class DamageSource { - damageSource.sweep = this.isSweep(); - damageSource.poison = this.isPoison(); - damageSource.melting = this.isMelting(); -+ damageSource.scissors = this.isScissors(); // Purpur - Dont run with scissors! -+ damageSource.stonecutter = this.isStonecutter(); // Purpur - Stonecutter damage - return damageSource; - } - // CraftBukkit end -@@ -194,10 +219,19 @@ public class DamageSource { - - ItemStack itemstack1 = itemstack; - -- return !itemstack1.isEmpty() && itemstack1.has(DataComponents.CUSTOM_NAME) ? Component.translatable(s + ".item", killed.getDisplayName(), ichatbasecomponent, itemstack1.getDisplayName()) : Component.translatable(s, killed.getDisplayName(), ichatbasecomponent); -+ return !itemstack1.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemstack1.has(DataComponents.CUSTOM_NAME)) ? Component.translatable(s + ".item", killed.getDisplayName(), ichatbasecomponent, itemstack1.getDisplayName()) : Component.translatable(s, killed.getDisplayName(), ichatbasecomponent); - } - } - -+ // Purpur start - Component related conveniences -+ public Component getLocalizedDeathMessage(String str, LivingEntity entity) { -+ net.kyori.adventure.text.Component name = io.papermc.paper.adventure.PaperAdventure.asAdventure(entity.getDisplayName()); -+ net.kyori.adventure.text.minimessage.tag.resolver.TagResolver template = net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("player", name); -+ net.kyori.adventure.text.Component component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(str, template); -+ return io.papermc.paper.adventure.PaperAdventure.asVanilla(component); -+ } -+ // Purpur end - Component related conveniences -+ - public String getMsgId() { - return this.type().msgId(); - } -diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSources.java b/src/main/java/net/minecraft/world/damagesource/DamageSources.java -index be87cb3cfa15a7d889118cdc4b87232e30749023..820533b6a13b1f8abbfe378de6b5d66ce2b79835 100644 ---- a/src/main/java/net/minecraft/world/damagesource/DamageSources.java -+++ b/src/main/java/net/minecraft/world/damagesource/DamageSources.java -@@ -46,11 +46,15 @@ public class DamageSources { - // CraftBukkit start - private final DamageSource melting; - private final DamageSource poison; -+ private final DamageSource scissors; // Purpur - Dont run with scissors! -+ private final DamageSource stonecutter; // Purpur - Stonecutter damage - - public DamageSources(RegistryAccess registryManager) { - this.damageTypes = registryManager.lookupOrThrow(Registries.DAMAGE_TYPE); - this.melting = this.source(DamageTypes.ON_FIRE).melting(); - this.poison = this.source(DamageTypes.MAGIC).poison(); -+ this.scissors = this.source(DamageTypes.MAGIC).scissors(); // Purpur - Dont run with scissors! -+ this.stonecutter = this.source(DamageTypes.MAGIC).stonecutter(); // Purpur - Stonecutter damage - // CraftBukkit end - this.inFire = this.source(DamageTypes.IN_FIRE); - this.campfire = this.source(DamageTypes.CAMPFIRE); -@@ -101,6 +105,17 @@ public class DamageSources { - } - // CraftBukkit end - -+ // Purpur start - Dont run with scissors! -+ public DamageSource scissors() { -+ return this.scissors; -+ } -+ // Purpur end - Dont run with scissors! -+ -+ // Purpur start - Stonecutter damage -+ public DamageSource stonecutter() { -+ return this.stonecutter; -+ } -+ // Purpur end - Stonecutter damage - public DamageSource inFire() { - return this.inFire; - } -diff --git a/src/main/java/net/minecraft/world/effect/HungerMobEffect.java b/src/main/java/net/minecraft/world/effect/HungerMobEffect.java -index 11d1ee8fae7670f02cb3f5d57f4774dbde77f48c..88cf1353892a7ead4e0f16822216b151726ac0e4 100644 ---- a/src/main/java/net/minecraft/world/effect/HungerMobEffect.java -+++ b/src/main/java/net/minecraft/world/effect/HungerMobEffect.java -@@ -13,7 +13,7 @@ class HungerMobEffect extends MobEffect { - @Override - public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { - if (entity instanceof Player entityhuman) { -- entityhuman.causeFoodExhaustion(0.005F * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent -+ entityhuman.causeFoodExhaustion(entity.level().purpurConfig.humanHungerExhaustionAmount * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent // Purpur - } - - return true; -diff --git a/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java b/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java -index 3f1e59229d6a71117700a56c87914bc9401f1da2..32c8474ffa88e997bb484caecb21e3dc98991e70 100644 ---- a/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java -+++ b/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java -@@ -13,8 +13,8 @@ public class PoisonMobEffect extends MobEffect { - - @Override - public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { -- if (entity.getHealth() > 1.0F) { -- entity.hurtServer(world, entity.damageSources().poison(), 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON -+ if (entity.getHealth() > entity.level().purpurConfig.entityMinimalHealthPoison) { // Purpur -+ entity.hurtServer(world, entity.damageSources().poison(), entity.level().purpurConfig.entityPoisonDegenerationAmount); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON // Purpur - } - - return true; -diff --git a/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java b/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java -index b43e573e91d1f1b407774bb2d60ee5f099f171e7..25aa932fc03eeebc5aabca6c5eae0cbfc8ad8396 100644 ---- a/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java -+++ b/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java -@@ -12,7 +12,7 @@ class RegenerationMobEffect extends MobEffect { - @Override - public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { - if (entity.getHealth() < entity.getMaxHealth()) { -- entity.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit -+ entity.heal(entity.level().purpurConfig.entityHealthRegenAmount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit // Purpur - } - - return true; -diff --git a/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java b/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java -index 98b74649a667fb9b10afef0ba5383a73022d8c71..0c7c0524e487ff32e16dd9939d92bc6441602747 100644 ---- a/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java -+++ b/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java -@@ -21,7 +21,8 @@ class SaturationMobEffect extends InstantenousMobEffect { - int oldFoodLevel = entityhuman.getFoodData().foodLevel; - org.bukkit.event.entity.FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(entityhuman, amplifier + 1 + oldFoodLevel); - if (!event.isCancelled()) { -- entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F); -+ if (entityhuman.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) entityhuman.burpDelay = entityhuman.level().purpurConfig.playerBurpDelay; // Purpur -+ entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, entity.level().purpurConfig.humanSaturationRegenAmount); // Purpur - } - - ((CraftPlayer) entityhuman.getBukkitEntity()).sendHealthUpdate(); -diff --git a/src/main/java/net/minecraft/world/effect/WitherMobEffect.java b/src/main/java/net/minecraft/world/effect/WitherMobEffect.java -index 55132e6e064ddd15b26286eca335305ed57b2f9e..1e04947995009689315352b79989e7ce4e20073c 100644 ---- a/src/main/java/net/minecraft/world/effect/WitherMobEffect.java -+++ b/src/main/java/net/minecraft/world/effect/WitherMobEffect.java -@@ -12,7 +12,7 @@ public class WitherMobEffect extends MobEffect { - - @Override - public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { -- entity.hurtServer(world, entity.damageSources().wither(), 1.0F); -+ entity.hurtServer(world, entity.damageSources().wither(), entity.level().purpurConfig.entityWitherDegenerationAmount); // Purpur - return true; - } - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 7ac7d0729705cb02f22277be3c467aed4f69ec0e..305a569f8cd83f3c67a4d4377f2881d36961dacd 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -177,7 +177,7 @@ import org.bukkit.plugin.PluginManager; - // CraftBukkit end - - public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity { // Paper - rewrite chunk system // Paper - optimise entity tracker -- -+ public static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur - Configurable entity base attributes - // CraftBukkit start - private static final int CURRENT_LEVEL = 2; - public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation -@@ -300,6 +300,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public double xOld; - public double yOld; - public double zOld; -+ public float maxUpStep; // Purpur - public boolean noPhysics; - private boolean wasOnFire; - public final RandomSource random; -@@ -340,7 +341,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - private final Set tags; - private final double[] pistonDeltas; - private long pistonDeltasGameTime; -- private EntityDimensions dimensions; -+ protected EntityDimensions dimensions; // Purpur - private -> protected - Ridables - private float eyeHeight; - public boolean isInPowderSnow; - public boolean wasInPowderSnow; -@@ -389,6 +390,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - public boolean freezeLocked = false; // Paper - Freeze Tick Lock API - public boolean fixedPose = false; // Paper - Expand Pose API - private final int despawnTime; // Paper - entity despawn time limit -+ public boolean activatedPriorityReset = false; // Pufferfish - DAB -+ public int activatedPriority = gg.pufferfish.pufferfish.PufferfishConfig.maximumActivationPrio; // Pufferfish - DAB (golf score) -+ public final BlockPos.MutableBlockPos cachedBlockPos = new BlockPos.MutableBlockPos(); // Pufferfish - reduce entity allocations -+ public @Nullable Boolean immuneToFire = null; // Purpur - Fire immune API - - public void setOrigin(@javax.annotation.Nonnull Location location) { - this.origin = location.toVector(); -@@ -568,6 +573,27 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - } - // Paper end - optimise entity tracker -+ // Purpur start - Add canSaveToDisk to Entity -+ public boolean canSaveToDisk() { -+ return true; -+ } -+ // Purpur end - Add canSaveToDisk to Entity -+ -+ // Purpur start - copied from Mob - API for any mob to burn daylight -+ public boolean isSunBurnTick() { -+ if (this.level().isDay() && !this.level().isClientSide) { -+ float f = this.getLightLevelDependentMagicValue(); -+ BlockPos blockposition = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); -+ boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; -+ -+ if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level().canSeeSky(blockposition)) { -+ return true; -+ } -+ } -+ -+ return false; -+ } -+ // Purpur end - copied from Mob - API for any mob to burn daylight - - public Entity(EntityType type, Level world) { - this.id = Entity.ENTITY_COUNTER.incrementAndGet(); -@@ -577,7 +603,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - this.bb = Entity.INITIAL_AABB; - this.stuckSpeedMultiplier = Vec3.ZERO; - this.nextStep = 1.0F; -- this.random = SHARED_RANDOM; // Paper - Share random for entities to make them more random -+ this.random = world == null || world.purpurConfig.entitySharedRandom ? SHARED_RANDOM : RandomSource.create(); // Paper - Share random for entities to make them more random // Purpur - this.remainingFireTicks = -this.getFireImmuneTicks(); - this.fluidHeight = new Object2DoubleArrayMap(2); - this.fluidOnEyes = new HashSet(); -@@ -978,6 +1004,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v) - && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) { - // Paper end - Configurable nether ceiling damage -+ if (this.level().purpurConfig.teleportOnNetherCeilingDamage && this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER && this instanceof ServerPlayer player) player.teleport(io.papermc.paper.util.MCUtil.toLocation(this.level, this.level.getSharedSpawnPos())); else // Purpur - Add option to teleport to spawn on nether ceiling damage - this.onBelowWorld(); - } - -@@ -1958,7 +1985,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - 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) { -@@ -2031,7 +2058,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return this.isInWater() || flag; - } - -- void updateInWaterStateAndDoWaterCurrentPushing() { -+ public void updateInWaterStateAndDoWaterCurrentPushing() { // Purpur - package-private -> public - Movement options for armor stands - Entity entity = this.getVehicle(); - - if (entity instanceof AbstractBoat abstractboat) { -@@ -2713,6 +2740,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - nbttagcompound.putBoolean("Paper.FreezeLock", true); - } - // Paper end -+ // Purpur start -+ if (immuneToFire != null) { -+ nbttagcompound.putBoolean("Purpur.FireImmune", immuneToFire); -+ } -+ // Purpur end - return nbttagcompound; - } catch (Throwable throwable) { - CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); -@@ -2863,6 +2895,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - 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"); -@@ -3114,6 +3151,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - if (this.isAlive() && this instanceof Leashable leashable) { - if (leashable.getLeashHolder() == player) { - if (!this.level().isClientSide()) { -+ if (hand == InteractionHand.OFF_HAND && (level().purpurConfig.villagerCanBeLeashed || level().purpurConfig.wanderingTraderCanBeLeashed) && this instanceof net.minecraft.world.entity.npc.AbstractVillager) return InteractionResult.CONSUME; // Purpur - Allow leashing villagers - // CraftBukkit start - fire PlayerUnleashEntityEvent - // Paper start - Expand EntityUnleashEvent - org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials()); -@@ -3324,6 +3362,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - this.passengers = ImmutableList.copyOf(list); - } - -+ // Purpur start - Ridables -+ if (isRidable() && this.passengers.get(0) == passenger && passenger instanceof Player player) { -+ onMount(player); -+ this.rider = player; -+ } -+ // Purpur end - Ridables -+ - this.gameEvent(GameEvent.ENTITY_MOUNT, passenger); - } - } -@@ -3363,6 +3408,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return false; - } - // CraftBukkit end -+ -+ // Purpur start - Ridables -+ if (this.rider != null && this.passengers.get(0) == this.rider) { -+ onDismount(this.rider); -+ this.rider = null; -+ } -+ // Purpur end - Ridables -+ - if (this.passengers.size() == 1 && this.passengers.get(0) == entity) { - this.passengers = ImmutableList.of(); - } else { -@@ -3443,14 +3496,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return Vec3.directionFromRotation(this.getRotationVector()); - } - -+ public BlockPos portalPos = BlockPos.ZERO; // Purpur - public void setAsInsidePortal(Portal portal, BlockPos pos) { - if (this.isOnPortalCooldown()) { -+ if (!(level().purpurConfig.playerFixStuckPortal && this instanceof Player && !pos.equals(this.portalPos))) // Purpur - Fix stuck in portals - this.setPortalCooldown(); -- } else { -+ } else if (this.level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer) { // Purpur - Entities can use portals - if (this.portalProcess != null && this.portalProcess.isSamePortal(portal)) { - if (!this.portalProcess.isInsidePortalThisTick()) { - this.portalProcess.updateEntryPosition(pos.immutable()); - this.portalProcess.setAsInsidePortalThisTick(true); -+ this.portalPos = BlockPos.ZERO; // Purpur - Fix stuck in portals - } - } else { - this.portalProcess = new PortalProcessor(portal, pos.immutable()); -@@ -3667,7 +3723,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - 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() { -@@ -4169,7 +4225,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - // CraftBukkit end - - public boolean canUsePortal(boolean allowVehicles) { -- return (allowVehicles || !this.isPassenger()) && this.isAlive(); -+ return (allowVehicles || !this.isPassenger()) && this.isAlive() && (this.level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer); // Purpur - Entities can use portals - } - - public boolean canTeleport(Level from, Level to) { -@@ -4753,6 +4809,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return Mth.lerp(delta, this.yRotO, this.yRot); - } - -+ // Purpur start -+ public AABB getAxisForFluidCheck() { -+ return this.getBoundingBox().deflate(0.001D); -+ } -+ // Purpur end -+ - // Paper start - optimise collisions - public boolean updateFluidHeightAndDoFluidPushing(final TagKey fluid, final double flowScale) { - if (this.touchingUnloadedChunk()) { -@@ -5154,7 +5216,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - - public float maxUpStep() { -- return 0.0F; -+ return maxUpStep; - } - - public void onExplosionHit(@Nullable Entity entity) {} -@@ -5355,4 +5417,44 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - return ((net.minecraft.server.level.ServerLevel) this.level).isPositionEntityTicking(this.blockPosition()); - } - // Paper end - Expose entity id counter -+ // Purpur start - Ridables -+ @Nullable -+ private Player rider = null; -+ -+ @Nullable -+ public Player getRider() { -+ return rider; -+ } -+ -+ public boolean isRidable() { -+ return false; -+ } -+ -+ public boolean isControllable() { -+ return true; -+ } -+ -+ public void onMount(Player rider) { -+ if (this instanceof Mob) { -+ ((Mob) this).setTarget(null, null, false); -+ ((Mob) this).getNavigation().stop(); -+ } -+ rider.setJumping(false); // fixes jump on mount -+ } -+ -+ public void onDismount(Player player) { -+ } -+ -+ public boolean onSpacebar() { -+ return false; -+ } -+ -+ public boolean onClick(InteractionHand hand) { -+ return false; -+ } -+ -+ public boolean processClick(InteractionHand hand) { -+ return false; -+ } -+ // Purpur end - Ridables - } -diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java -index 6bf691fcc6486bde73bae30eff09142802c29eda..d99b223be90f0c04bb9274228ad323a7c7f218b2 100644 ---- a/src/main/java/net/minecraft/world/entity/EntitySelector.java -+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java -@@ -39,6 +39,7 @@ public final class EntitySelector { - return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks; - }; - // Paper end - Ability to control player's insomnia and phantoms -+ public static Predicate notAfk = (player) -> !player.isAfk(); // Purpur - AFK API - - private EntitySelector() {} - // Paper start - Affects Spawning API -diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java -index d23914a3ab3723d532ae867db6b954c843030f75..d09664949f924b4bd240abcc4a9f96f142310aa9 100644 ---- a/src/main/java/net/minecraft/world/entity/EntityType.java -+++ b/src/main/java/net/minecraft/world/entity/EntityType.java -@@ -385,10 +385,12 @@ public class EntityType implements FeatureElement, EntityTypeT - private final int clientTrackingRange; - private final int updateInterval; - private final String descriptionId; -+ public boolean dabEnabled = false; // Pufferfish - @Nullable - private Component description; - private final Optional> lootTable; -- private final EntityDimensions dimensions; -+ private EntityDimensions dimensions; // Purpur - remove final - Short enderman height -+ public void setDimensions(EntityDimensions dimensions) { this.dimensions = dimensions; } // Purpur - Short enderman height - private final float spawnDimensionsScale; - private final FeatureFlagSet requiredFeatures; - -@@ -404,6 +406,16 @@ public class EntityType implements FeatureElement, EntityTypeT - return EntityType.register(EntityType.vanillaEntityId(id), type); - } - -+ // Purpur start -+ public static EntityType getFromBukkitType(org.bukkit.entity.EntityType bukkitType) { -+ return getFromKey(ResourceLocation.parse(bukkitType.getKey().toString())); -+ } -+ -+ public static EntityType getFromKey(ResourceLocation location) { -+ return BuiltInRegistries.ENTITY_TYPE.getValue(location); -+ } -+ // Purpur end -+ - public static ResourceLocation getKey(EntityType type) { - return BuiltInRegistries.ENTITY_TYPE.getKey(type); - } -@@ -614,6 +626,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() { - return this.descriptionId; - } -@@ -671,6 +693,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 23b47d90fd35659d9eaa661e808a7f89b29614cf..ba4fe614e2a3378f17b544c78c92a271523e8d37 100644 ---- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -+++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java -@@ -335,7 +335,7 @@ public class ExperienceOrb extends Entity { - public void playerTouch(Player player) { - if (player instanceof ServerPlayer entityplayer) { - if (player.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(entityplayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent -- player.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(player, 2, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; -+ player.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(player, this.level().purpurConfig.playerExpPickupDelay, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; // Purpur - player.take(this, 1); - int i = this.repairPlayerItems(entityplayer, this.value); - -@@ -353,7 +353,7 @@ public class ExperienceOrb extends Entity { - } - - private int repairPlayerItems(ServerPlayer player, int amount) { -- Optional optional = EnchantmentHelper.getRandomItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged); -+ Optional optional = level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player) : EnchantmentHelper.getRandomItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged); // Purpur - Add option to mend the most damaged equipment first - - if (optional.isPresent()) { - ItemStack itemstack = ((EnchantedItemInUse) optional.get()).itemStack(); -diff --git a/src/main/java/net/minecraft/world/entity/GlowSquid.java b/src/main/java/net/minecraft/world/entity/GlowSquid.java -index 397765b1547ae47b64963b3807b206c50a6650e1..97a062c48b22c34edb2a5ad8b8c2f5443e6f7556 100644 ---- a/src/main/java/net/minecraft/world/entity/GlowSquid.java -+++ b/src/main/java/net/minecraft/world/entity/GlowSquid.java -@@ -25,6 +25,42 @@ public class GlowSquid extends Squid { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.glowSquidRidable; -+ } -+ -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.glowSquidControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.glowSquidMaxHealth); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.glowSquidTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.glowSquidAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience -+ @Override -+ public boolean canFly() { -+ return this.level().purpurConfig.glowSquidsCanFly; -+ } -+ - @Override - protected ParticleOptions getInkParticle() { - return ParticleTypes.GLOW_SQUID_INK; -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 96b4fbe4a4655777ff10b32e3257e2fac2aba12a..e64ff857bf95436033baf38db1e6895f75856f9c 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -246,9 +246,9 @@ public abstract class LivingEntity extends Entity implements Attackable { - protected float rotOffs; - public float lastHurt; - public boolean jumping; -- public float xxa; -- public float yya; -- public float zza; -+ public float xxa; public float getStrafeMot() { return xxa; } public void setStrafeMot(float strafe) { xxa = strafe; } // Purpur - OBFHELPER -+ public float yya; public float getVerticalMot() { return yya; } public void setVerticalMot(float vertical) { yya = vertical; } // Purpur - OBFHELPER -+ public float zza; public float getForwardMot() { return zza; } public void setForwardMot(float forward) { zza = forward; } // Purpur - OBFHELPER - protected int lerpSteps; - protected double lerpX; - protected double lerpY; -@@ -295,6 +295,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 - Friction API -+ protected boolean shouldBurnInDay = false; public boolean shouldBurnInDay() { return this.shouldBurnInDay; } public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Purpur - API for any mob to burn daylight - - @Override - public float getBukkitYaw() { -@@ -323,7 +324,8 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.lastClimbablePos = Optional.empty(); - this.activeLocationDependentEnchantments = new EnumMap(EquipmentSlot.class); - this.appliedScale = 1.0F; -- this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type)); -+ this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type), this); // Purpur - Ridables -+ this.initAttributes(); // Purpur - Configurable entity base attributes - this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit - // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor - this.entityData.set(LivingEntity.DATA_HEALTH_ID, (float) this.getAttribute(Attributes.MAX_HEALTH).getValue()); -@@ -338,6 +340,8 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.brain = this.makeBrain(new Dynamic(dynamicopsnbt, (Tag) dynamicopsnbt.createMap((Map) ImmutableMap.of(dynamicopsnbt.createString("memories"), (Tag) dynamicopsnbt.emptyMap())))); - } - -+ protected void initAttributes() {}// Purpur - Configurable entity base attributes -+ - public Brain getBrain() { - return this.brain; - } -@@ -373,6 +377,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - public static AttributeSupplier.Builder createLivingAttributes() { - return AttributeSupplier.builder().add(Attributes.MAX_HEALTH).add(Attributes.KNOCKBACK_RESISTANCE).add(Attributes.MOVEMENT_SPEED).add(Attributes.ARMOR).add(Attributes.ARMOR_TOUGHNESS).add(Attributes.MAX_ABSORPTION).add(Attributes.STEP_HEIGHT).add(Attributes.SCALE).add(Attributes.GRAVITY).add(Attributes.SAFE_FALL_DISTANCE).add(Attributes.FALL_DAMAGE_MULTIPLIER).add(Attributes.JUMP_STRENGTH).add(Attributes.OXYGEN_BONUS).add(Attributes.BURNING_TIME).add(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE).add(Attributes.WATER_MOVEMENT_EFFICIENCY).add(Attributes.MOVEMENT_EFFICIENCY).add(Attributes.ATTACK_KNOCKBACK); - } -+ public boolean shouldSendAttribute(Attribute attribute) { return true; } // Purpur - Ridables - - @Override - protected void checkFallDamage(double heightDifference, boolean onGround, BlockState state, BlockPos landedPosition) { -@@ -465,7 +470,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - if (world1 instanceof ServerLevel) { - worldserver1 = (ServerLevel) world1; -- if (this.isInWall()) { -+ if (shouldCheckForSuffocation() && this.isInWall()) { // Pufferfish - optimize suffocation - this.hurtServer(worldserver1, this.damageSources().inWall(), 1.0F); - } else if (flag && !this.level().getWorldBorder().isWithinBounds(this.getBoundingBox())) { - double d1 = this.level().getWorldBorder().getDistanceToBorder(this) + this.level().getWorldBorder().getDamageSafeZone(); -@@ -473,6 +478,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (d1 < 0.0D) { - d0 = this.level().getWorldBorder().getDamagePerBlock(); - if (d0 > 0.0D) { -+ if (level().purpurConfig.teleportIfOutsideBorder && this instanceof ServerPlayer serverPlayer) { serverPlayer.teleport(io.papermc.paper.util.MCUtil.toLocation(level(), ((ServerLevel) level()).getSharedSpawnPos())); return; } // Purpur - this.hurtServer(worldserver1, this.damageSources().outOfBorder(), (float) Math.max(1, Mth.floor(-d1 * d0))); - } - } -@@ -484,7 +490,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(); - -@@ -496,7 +502,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.level().addParticle(ParticleTypes.BUBBLE, this.getX() + d0, this.getY() + d2, this.getZ() + d3, vec3d.x, vec3d.y, vec3d.z); - } - -- this.hurt(this.damageSources().drown(), 2.0F); -+ this.hurt(this.damageSources().drown(), (float) this.level().purpurConfig.damageFromDrowning); // Purpur - } - } else if (this.getAirSupply() < this.getMaxAirSupply()) { - this.setAirSupply(this.increaseAirSupply(this.getAirSupply())); -@@ -564,6 +570,19 @@ public abstract class LivingEntity extends Entity implements Attackable { - gameprofilerfiller.pop(); - } - -+ // Pufferfish start - optimize suffocation -+ public boolean couldPossiblyBeHurt(float amount) { -+ if ((float) this.invulnerableTime > (float) this.invulnerableDuration / 2.0F && amount <= this.lastHurt) { -+ return false; -+ } -+ return true; -+ } -+ -+ public boolean shouldCheckForSuffocation() { -+ return !gg.pufferfish.pufferfish.PufferfishConfig.enableSuffocationOptimization || (tickCount % 10 == 0 && couldPossiblyBeHurt(1.0F)); -+ } -+ // Pufferfish end -+ - @Override - protected float getBlockSpeedFactor() { - return Mth.lerp((float) this.getAttributeValue(Attributes.MOVEMENT_EFFICIENCY), super.getBlockSpeedFactor(), 1.0F); -@@ -834,6 +853,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> { - nbt.put("Brain", nbtbase); - }); -+ nbt.putBoolean("Purpur.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - API for any mob to burn daylight - } - - @Override -@@ -922,6 +942,11 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.brain = this.makeBrain(new Dynamic(NbtOps.INSTANCE, nbt.get("Brain"))); - } - -+ // Purpur start - API for any mob to burn daylight -+ if (nbt.contains("Purpur.ShouldBurnInDay")) { -+ this.shouldBurnInDay = nbt.getBoolean("Purpur.ShouldBurnInDay"); -+ } -+ // Purpur end - API for any mob to burn daylight - } - - // CraftBukkit start -@@ -1057,9 +1082,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; -@@ -1115,6 +1162,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - while (iterator.hasNext()) { - MobEffectInstance effect = iterator.next(); -+ if (cause == EntityPotionEffectEvent.Cause.MILK && !this.level().purpurConfig.milkClearsBeneficialEffects && effect.getEffect().value().isBeneficial()) continue; // Purpur - EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED); - if (event.isCancelled()) { - continue; -@@ -1450,6 +1498,24 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.stopSleeping(); - } - -+ // Purpur start -+ if (source.getEntity() instanceof net.minecraft.world.entity.player.Player player && source.getEntity().level().purpurConfig.creativeOnePunch && !source.is(DamageTypeTags.IS_PROJECTILE)) { -+ if (player.isCreative()) { -+ org.apache.commons.lang3.mutable.MutableDouble attackDamage = new org.apache.commons.lang3.mutable.MutableDouble(); -+ player.getMainHandItem().forEachModifier(EquipmentSlot.MAINHAND, (attributeHolder, attributeModifier) -> { -+ if (attributeModifier.operation() == AttributeModifier.Operation.ADD_VALUE) { -+ attackDamage.addAndGet(attributeModifier.amount()); -+ } -+ }); -+ -+ if (attackDamage.doubleValue() == 0.0D) { -+ // One punch! -+ amount = 9999F; -+ } -+ } -+ } -+ // Purpur end -+ - this.noActionTime = 0; - if (amount < 0.0F) { - amount = 0.0F; -@@ -1643,13 +1709,13 @@ public abstract class LivingEntity extends Entity implements Attackable { - Entity entity = damageSource.getEntity(); - - if (entity instanceof net.minecraft.world.entity.player.Player entityhuman) { -- this.lastHurtByPlayerTime = 100; -+ this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur - this.lastHurtByPlayer = entityhuman; - return entityhuman; - } else { - if (entity instanceof Wolf entitywolf) { - if (entitywolf.isTame()) { -- this.lastHurtByPlayerTime = 100; -+ this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur - LivingEntity entityliving = entitywolf.getOwner(); - - if (entityliving instanceof net.minecraft.world.entity.player.Player) { -@@ -1713,6 +1779,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); -@@ -1884,7 +1962,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - boolean flag = false; - - if (this.dead && adversary instanceof WitherBoss) { // Paper -- if (worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (worldserver.purpurConfig.witherBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected - BlockPos blockposition = this.blockPosition(); - BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState(); - -@@ -1921,6 +1999,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - this.dropEquipment(world); // CraftBukkit - from below - if (this.shouldDropLoot() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { -+ if (!(damageSource.is(net.minecraft.world.damagesource.DamageTypes.CRAMMING) && level().purpurConfig.disableDropsOnCrammingDeath)) { // Purpur - this.dropFromLootTable(world, damageSource, flag); - // Paper start - final boolean prev = this.clearEquipmentSlots; -@@ -1929,6 +2008,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - // Paper end - this.dropCustomDeathLoot(world, damageSource, 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, damageSource, this.drops, () -> { -@@ -2135,6 +2215,20 @@ public abstract class LivingEntity extends Entity implements Attackable { - return this.lastClimbablePos; - } - -+ -+ // Pufferfish start -+ private boolean cachedOnClimable = false; -+ private BlockPos lastClimbingPosition = null; -+ -+ public boolean onClimableCached() { -+ if (!this.blockPosition().equals(this.lastClimbingPosition)) { -+ this.cachedOnClimable = this.onClimbable(); -+ this.lastClimbingPosition = this.blockPosition(); -+ } -+ return this.cachedOnClimable; -+ } -+ // Pufferfish end -+ - public boolean onClimbable() { - if (this.isSpectator()) { - return false; -@@ -3157,6 +3251,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - if (f > 0.0F) { - this.playSound(this.getFallDamageSound((int) f), 1.0F, 1.0F); -+ if (level().purpurConfig.elytraKineticDamage) // Purpur - this.hurt(this.damageSources().flyIntoWall(), f); - } - } -@@ -3703,8 +3798,10 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.pushEntities(); - gameprofilerfiller.pop(); - // Paper start - Add EntityMoveEvent -- if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { -- if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { -+ // Purpur start - Ridables -+ if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { -+ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { -+ // Purpur end - Ridables - Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); - Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); - io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone()); -@@ -3714,6 +3811,21 @@ public abstract class LivingEntity extends Entity implements Attackable { - this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); - } - } -+ // Purpur start - Ridables -+ if (getRider() != null) { -+ getRider().resetLastActionTime(); -+ if (((ServerLevel) level()).hasRidableMoveEvent && this instanceof Mob) { -+ Location from = new Location(level().getWorld(), xo, yo, zo, this.yRotO, this.xRotO); -+ Location to = new Location(level().getWorld(), getX(), getY(), getZ(), this.getYRot(), this.getXRot()); -+ org.purpurmc.purpur.event.entity.RidableMoveEvent event = new org.purpurmc.purpur.event.entity.RidableMoveEvent((org.bukkit.entity.Mob) getBukkitLivingEntity(), (Player) getRider().getBukkitEntity(), from, to.clone()); -+ if (!event.callEvent()) { -+ absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); -+ } else if (!to.equals(event.getTo())) { -+ absMoveTo(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch()); -+ } -+ } -+ } -+ // Purpur end - Ridables - } - // Paper end - Add EntityMoveEvent - world = this.level(); -@@ -3723,6 +3835,34 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - } - -+ // Purpur start - copied from Zombie - API for any mob to burn daylight -+ if (this.isAlive()) { -+ boolean flag = this.shouldBurnInDay() && this.isSunBurnTick(); // Paper - shouldBurnInDay API // Purpur - use shouldBurnInDay() method to handle Phantoms properly - API for any mob to burn daylight -+ -+ if (flag) { -+ ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); -+ -+ if (!itemstack.isEmpty()) { -+ if (itemstack.isDamageableItem()) { -+ Item item = itemstack.getItem(); -+ -+ itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); -+ if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { -+ this.onEquippedItemBroken(item, EquipmentSlot.HEAD); -+ this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); -+ } -+ } -+ -+ flag = false; -+ } -+ -+ if (flag) { -+ if (getRider() == null || !this.isControllable()) // Purpur - ignore mobs which are uncontrollable or without rider - API for any mob to burn daylight -+ this.igniteForSeconds(8.0F); -+ } -+ } -+ } -+ // Purpur end - copied from Zombie - API for any mob to burn daylight - } - - public boolean isSensitiveToWater() { -@@ -3749,7 +3889,17 @@ public abstract class LivingEntity extends Entity implements Attackable { - }).toList(); - EquipmentSlot enumitemslot = (EquipmentSlot) Util.getRandom(list, this.random); - -- this.getItemBySlot(enumitemslot).hurtAndBreak(1, this, enumitemslot); -+ // Purpur start -+ int damage = level().purpurConfig.elytraDamagePerSecond; -+ if (level().purpurConfig.elytraDamageMultiplyBySpeed > 0) { -+ double speed = getDeltaMovement().lengthSqr(); -+ if (speed > level().purpurConfig.elytraDamageMultiplyBySpeed) { -+ damage *= (int) speed; -+ } -+ } -+ -+ this.getItemBySlot(enumitemslot).hurtAndBreak(damage, this, enumitemslot); -+ // Purpur end - } - - this.gameEvent(GameEvent.ELYTRA_GLIDE); -@@ -3758,7 +3908,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - - } - -- protected boolean canGlide() { -+ public boolean canGlide() { // Purpur - if (!this.onGround() && !this.isPassenger() && !this.hasEffect(MobEffects.LEVITATION)) { - Iterator iterator = EquipmentSlot.VALUES.iterator(); - -@@ -4676,7 +4826,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - if (equippable != null && equippable.dispensable()) { - EquipmentSlot enumitemslot = equippable.slot(); - -- return this.canUseSlot(enumitemslot) && equippable.canBeEquippedBy(this.getType()) ? this.getItemBySlot(enumitemslot).isEmpty() && this.canDispenserEquipIntoSlot(enumitemslot) : false; -+ return this.canUseSlot(enumitemslot) && equippable.canBeEquippedBy(this.getType()) && this.getItemBySlot(enumitemslot).isEmpty() && this.canDispenserEquipIntoSlot(enumitemslot); - } else { - return false; - } -@@ -4701,6 +4851,12 @@ public abstract class LivingEntity extends Entity implements Attackable { - return equippable == null ? slot == EquipmentSlot.MAINHAND && this.canUseSlot(EquipmentSlot.MAINHAND) : slot == equippable.slot() && this.canUseSlot(equippable.slot()) && equippable.canBeEquippedBy(this.getType()); - } - -+ // Purpur start - Dispenser curse of binding protection -+ public @Nullable EquipmentSlot getEquipmentSlotForDispenserItem(ItemStack itemstack) { -+ return EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.BINDING_CURSE, itemstack) > 0 ? null : this.getEquipmentSlotForItem(itemstack); -+ } -+ // Purpur end - Dispenser curse of binding protection -+ - private static SlotAccess createEquipmentSlotAccess(LivingEntity entity, EquipmentSlot slot) { - return slot != EquipmentSlot.HEAD && slot != EquipmentSlot.MAINHAND && slot != EquipmentSlot.OFFHAND ? SlotAccess.forEquipmentSlot(entity, slot, (itemstack) -> { - return itemstack.isEmpty() || entity.getEquipmentSlotForItem(itemstack) == slot; -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 5a0b51342f4a646101f4588697bcae7d1ca8a010..0be6582e50ccc94036bb6782a5f811c0f9c42f01 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -144,6 +144,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - private BlockPos restrictCenter; - private float restrictRadius; - -+ public int ticksSinceLastInteraction; // Purpur - public boolean aware = true; // CraftBukkit - - protected Mob(EntityType type, Level world) { -@@ -159,8 +160,8 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - this.restrictRadius = -1.0F; - this.goalSelector = new GoalSelector(); - this.targetSelector = new GoalSelector(); -- this.lookControl = new LookControl(this); -- this.moveControl = new MoveControl(this); -+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this); // Purpur - Ridables -+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this); // Purpur - Ridables - this.jumpControl = new JumpControl(this); - this.bodyRotationControl = this.createBodyControl(); - this.navigation = this.createNavigation(world); -@@ -231,14 +232,16 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - return this.lookControl; - } - -+ int _pufferfish_inactiveTickDisableCounter = 0; // Pufferfish - throttle inactive goal selector ticking - // Paper start - @Override - public void inactiveTick() { - super.inactiveTick(); -- if (this.goalSelector.inactiveTick()) { -+ boolean isThrottled = gg.pufferfish.pufferfish.PufferfishConfig.throttleInactiveGoalSelectorTick && _pufferfish_inactiveTickDisableCounter++ % 20 != 0; // Pufferfish - throttle inactive goal selector ticking -+ if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking - this.goalSelector.tick(); - } -- if (this.targetSelector.inactiveTick()) { -+ if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority - this.targetSelector.tick(); - } - } -@@ -330,6 +333,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - entityliving = null; - } - } -+ if (entityliving instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - this.target = entityliving; - return true; - // CraftBukkit end -@@ -374,8 +378,28 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - } - - gameprofilerfiller.pop(); -+ 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(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ } -+ } -+ // Purpur end -+ - @Override - protected void playHurtSound(DamageSource damageSource) { - this.resetAmbientSoundTime(); -@@ -544,6 +568,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - } - - nbt.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit -+ nbt.putInt("Purpur.ticksSinceLastInteraction", this.ticksSinceLastInteraction); // Purpur - } - - @Override -@@ -634,6 +659,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - this.aware = nbt.getBoolean("Bukkit.Aware"); - } - // CraftBukkit end -+ // Purpur start -+ if (nbt.contains("Purpur.ticksSinceLastInteraction")) { -+ this.ticksSinceLastInteraction = nbt.getInt("Purpur.ticksSinceLastInteraction"); -+ } -+ // Purpur end - } - - @Override -@@ -686,7 +716,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - Level world = this.level(); - - if (world instanceof ServerLevel worldserver) { -- if (this.canPickUpLoot() && this.isAlive() && !this.dead && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (this.canPickUpLoot() && this.isAlive() && !this.dead && (worldserver.purpurConfig.entitiesPickUpLootBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur - Add mobGriefing bypass to everything affected - 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(); -@@ -927,16 +957,20 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - - if (i % 2 != 0 && this.tickCount > 1) { - gameprofilerfiller.push("targetSelector"); -+ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking - this.targetSelector.tickRunningGoals(false); - gameprofilerfiller.pop(); - gameprofilerfiller.push("goalSelector"); -+ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking - this.goalSelector.tickRunningGoals(false); - gameprofilerfiller.pop(); - } else { - gameprofilerfiller.push("targetSelector"); -+ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking - this.targetSelector.tick(); - gameprofilerfiller.pop(); - gameprofilerfiller.push("goalSelector"); -+ if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking - this.goalSelector.tick(); - gameprofilerfiller.pop(); - } -@@ -1394,7 +1428,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - attributemodifiable.addPermanentModifier(new AttributeModifier(Mob.RANDOM_SPAWN_BONUS_ID, randomsource.triangle(0.0D, 0.11485000000000001D), AttributeModifier.Operation.ADD_MULTIPLIED_BASE)); - } - -- this.setLeftHanded(randomsource.nextFloat() < 0.05F); -+ this.setLeftHanded(randomsource.nextFloat() < world.getLevel().purpurConfig.entityLeftHandedChance); // Purpur - return entityData; - } - -@@ -1496,7 +1530,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - protected void onOffspringSpawnedFromEgg(Player player, Mob child) {} - - protected InteractionResult mobInteract(Player player, InteractionHand hand) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - Ridables - } - - public boolean isWithinRestriction() { -@@ -1738,23 +1772,15 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - this.playAttackSound(); - } - -+ if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - return flag; - } - - protected void playAttackSound() {} - - public boolean isSunBurnTick() { -- if (this.level().isDay() && !this.level().isClientSide) { -- float f = this.getLightLevelDependentMagicValue(); -- BlockPos blockposition = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); -- boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; -- -- if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level().canSeeSky(blockposition)) { -- return true; -- } -- } -- -- return false; -+ // Purpur - implemented in Entity - API for any mob to burn daylight -+ return super.isSunBurnTick(); - } - - @Override -@@ -1816,4 +1842,58 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab - public float[] getArmorDropChances() { - return this.armorDropChances; - } -+ -+ // Purpur start - Ridables -+ public double getMaxY() { -+ return level().getHeight(); -+ } -+ -+ public InteractionResult tryRide(Player player, InteractionHand hand) { -+ return tryRide(player, hand, InteractionResult.PASS); -+ } -+ -+ public InteractionResult tryRide(Player player, InteractionHand hand, InteractionResult result) { -+ if (!isRidable()) { -+ return result; -+ } -+ if (hand != InteractionHand.MAIN_HAND) { -+ return InteractionResult.PASS; -+ } -+ if (player.isShiftKeyDown()) { -+ return InteractionResult.PASS; -+ } -+ if (!player.getItemInHand(hand).isEmpty()) { -+ return InteractionResult.PASS; -+ } -+ if (!passengers.isEmpty() || player.isPassenger()) { -+ return InteractionResult.PASS; -+ } -+ if (this instanceof TamableAnimal tamable) { -+ if (tamable.isTame() && !tamable.isOwnedBy(player)) { -+ return InteractionResult.PASS; -+ } -+ if (!tamable.isTame() && !level().purpurConfig.untamedTamablesAreRidable) { -+ return InteractionResult.PASS; -+ } -+ } -+ if (this instanceof AgeableMob ageable) { -+ if (ageable.isBaby() && !level().purpurConfig.babiesAreRidable) { -+ return InteractionResult.PASS; -+ } -+ } -+ if (!player.getBukkitEntity().hasPermission("allow.ride." + net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getKey(getType()).getPath())) { -+ if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { -+ serverPlayer.sendMiniMessage(org.purpurmc.purpur.PurpurConfig.cannotRideMob); -+ } -+ return InteractionResult.PASS; -+ } -+ player.setYRot(this.getYRot()); -+ player.setXRot(this.getXRot()); -+ if (player.startRiding(this)) { -+ return InteractionResult.SUCCESS; -+ } else { -+ return InteractionResult.PASS; -+ } -+ } -+ // Purpur end - Ridables - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -index fb967ac7b3e7828301f08a7fe9b039441cf7da30..98fa43c8a34650795a0ae1ebc28ce17ec1ba5271 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java -@@ -23,14 +23,23 @@ public class AttributeMap { - private final Set attributesToSync = new ObjectOpenHashSet<>(); - private final Set attributesToUpdate = new ObjectOpenHashSet<>(); - private final AttributeSupplier supplier; -+ private final java.util.function.Function, AttributeInstance> createInstance; // Pufferfish -+ private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables - - public AttributeMap(AttributeSupplier defaultAttributes) { -+ // Purpur start - Ridables -+ this(defaultAttributes, null); -+ } -+ public AttributeMap(AttributeSupplier defaultAttributes, net.minecraft.world.entity.LivingEntity entity) { -+ this.entity = entity; -+ // Purpur end - Ridables - this.supplier = defaultAttributes; -+ this.createInstance = attributex -> this.supplier.createInstance(this::onAttributeModified, attributex); // Pufferfish - } - - private void onAttributeModified(AttributeInstance instance) { - this.attributesToUpdate.add(instance); -- if (instance.getAttribute().value().isClientSyncable()) { -+ if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur - Ridables - this.attributesToSync.add(instance); - } - } -@@ -44,12 +53,13 @@ public class AttributeMap { - } - - public Collection getSyncableAttributes() { -- return this.attributes.values().stream().filter(attribute -> attribute.getAttribute().value().isClientSyncable()).collect(Collectors.toList()); -+ return this.attributes.values().stream().filter(attribute -> attribute.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(attribute.getAttribute().value()))).collect(Collectors.toList()); // Purpur - Ridables - } - -+ - @Nullable - public AttributeInstance getInstance(Holder attribute) { -- return this.attributes.computeIfAbsent(attribute, attributex -> this.supplier.createInstance(this::onAttributeModified, attributex)); -+ return this.attributes.computeIfAbsent(attribute, this.createInstance); // Pufferfish - cache lambda, as for some reason java allocates it anyways - } - - public boolean hasAttribute(Holder attribute) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java -index c76438d5ce2330eca16dc0b381f97e9506f84aef..8c1ff0b273e25373c6b581613f692265d9569952 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java -+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java -@@ -131,7 +131,7 @@ public class DefaultAttributes { - .put(EntityType.OCELOT, Ocelot.createAttributes().build()) - .put(EntityType.PANDA, Panda.createAttributes().build()) - .put(EntityType.PARROT, Parrot.createAttributes().build()) -- .put(EntityType.PHANTOM, Monster.createMonsterAttributes().build()) -+ .put(EntityType.PHANTOM, net.minecraft.world.entity.monster.Phantom.createAttributes().build()) // Purpur - Ridables - .put(EntityType.PIG, Pig.createAttributes().build()) - .put(EntityType.PIGLIN, Piglin.createAttributes().build()) - .put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build()) -@@ -162,7 +162,7 @@ public class DefaultAttributes { - .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.WANDERING_TRADER, net.minecraft.world.entity.npc.WanderingTrader.createAttributes().build()) // Purpur - Villagers follow emerald blocks - .put(EntityType.WITCH, Witch.createAttributes().build()) - .put(EntityType.WITHER, WitherBoss.createAttributes().build()) - .put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build()) -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/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -index 0d177e828c2b338ce93c58aaef04df326e1eb0b2..273ba657926ce72a7c82861e880a82bf7f322a0b 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -@@ -86,7 +86,7 @@ public class AcquirePoi { - }; - // Paper start - optimise POI access - final java.util.List, BlockPos>> poiposes = new java.util.ArrayList<>(); -- io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, poiPredicate, predicate2, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); -+ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, poiPredicate, predicate2, entity.blockPosition(), world.purpurConfig.villagerAcquirePoiSearchRadius, world.purpurConfig.villagerAcquirePoiSearchRadius*world.purpurConfig.villagerAcquirePoiSearchRadius, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); - final Set, BlockPos>> set = new java.util.HashSet<>(poiposes.size()); - for (final Pair, BlockPos> poiPose : poiposes) { - if (worldPosBiPredicate.test(world, poiPose.getSecond())) { -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 2ade08d1466660ee1787fa97908002ef56389712..88b7de6c0ab5bf6ba2af7b4cee0393879c2a4fdc 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 -@@ -41,17 +41,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 - Add mobGriefing bypass to everything affected - 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(); -@@ -82,6 +84,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; - } - -@@ -107,20 +110,20 @@ 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 - if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state - world.destroyBlock(this.aboveFarmlandPos, true, entity); - } // CraftBukkit - } - -- 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) { - ItemStack itemstack = inventorysubcontainer.getItem(j); - boolean flag = false; - -- if (!itemstack.isEmpty() && itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS)) { -+ if (!itemstack.isEmpty() && (itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS) || this.clericWartFarmer && itemstack.getItem() == net.minecraft.world.item.Items.NETHER_WART)) { - Item item = itemstack.getItem(); - - if (item instanceof BlockItem) { -@@ -136,7 +139,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 3513b15f6622bfc134ecfcd9129f81a8acc2c601..2768e2a61ab7fbd82c2b8787e715163a7b0450b9 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.MOB_INTERACTABLE_DOORS, (blockbase_blockdata) -> { - return blockbase_blockdata.getBlock() instanceof DoorBlock; -- })) { -+ }) && !DoorBlock.requiresRedstone(entityliving.level(), iblockdata, blockposition)) { // Purpur - Option to make doors require redstone - DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock(); - - if (!blockdoor.isOpen(iblockdata)) { -@@ -79,7 +79,7 @@ public class InteractWithDoor { - - if (iblockdata1.is(BlockTags.MOB_INTERACTABLE_DOORS, (blockbase_blockdata) -> { - return blockbase_blockdata.getBlock() instanceof DoorBlock; -- })) { -+ }) && !DoorBlock.requiresRedstone(entityliving.level(), iblockdata, blockposition1)) { // Purpur - Option to make doors require redstone - DoorBlock blockdoor1 = (DoorBlock) iblockdata1.getBlock(); - - if (!blockdoor1.isOpen(iblockdata1)) { -@@ -122,7 +122,7 @@ public class InteractWithDoor { - - if (!iblockdata.is(BlockTags.MOB_INTERACTABLE_DOORS, (blockbase_blockdata) -> { - return blockbase_blockdata.getBlock() instanceof DoorBlock; -- })) { -+ }) || DoorBlock.requiresRedstone(entity.level(), iblockdata, blockposition)) { // Purpur - Option to make doors require redstone - 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 18dad0825616c4167a0a7555689ee64910a87e09..6945992491027d43eca4f1ca697ad45ce06ded55 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 -@@ -46,6 +46,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 8508ac7de8cda3127b73e11ff4aee62502e65ead..b1544e028d5a9b84b944e1fb5a12bb163067fb54 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 -@@ -59,6 +59,12 @@ public class TradeWithVillager extends Behavior { - throwHalfStack(entity, ImmutableSet.of(Items.WHEAT), villager); - } - -+ // Purpur start -+ if (world.purpurConfig.villagerClericsFarmWarts && world.purpurConfig.villagerClericFarmersThrowWarts && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC && entity.getInventory().countItem(Items.NETHER_WART) > Items.NETHER_WART.getDefaultMaxStackSize() / 2) { -+ throwHalfStack(entity, ImmutableSet.of(Items.NETHER_WART), villager); -+ } -+ // Purpur end -+ - if (!this.trades.isEmpty() && 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 41f4107101bcd5d753b72cdbabe7946a1975c653..4475b406dde30e5be8ce9d2ff45f8d22d242690c 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 -@@ -74,8 +74,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 0a608418f87b71d5d71706712e1f82da0d7e4d34..03e7ca83e4c28dfaa5b52bcb100bd542db105970 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 -@@ -125,8 +125,10 @@ public class VillagerMakeLove extends Behavior { - return Optional.empty(); - } - // Move age setting down -- parent.setAge(6000); -- partner.setAge(6000); -+ // Purpur start -+ parent.setAge(world.purpurConfig.villagerBreedingTicks); -+ partner.setAge(world.purpurConfig.villagerBreedingTicks); -+ // Purpur end - world.addFreshEntityWithPassengers(entityvillager2, CreatureSpawnEvent.SpawnReason.BREEDING); - // CraftBukkit end - world.broadcastEntityEvent(entityvillager2, (byte) 12); -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java -index 758f62416ca9c02351348ac0d41deeb4624abc0e..69130969c9a434ec2361e573c9a1ec9f462dfda2 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java -@@ -36,7 +36,11 @@ public class VillagerPanicTrigger extends Behavior { - - @Override - protected void tick(ServerLevel world, Villager entity, long time) { -- if (time % 100L == 0L) { -+ // Pufferfish start -+ if (entity.nextGolemPanic < 0) entity.nextGolemPanic = time + 100; -+ if (--entity.nextGolemPanic < time) { -+ entity.nextGolemPanic = -1; -+ // Pufferfish end - entity.spawnGolemIfNeeded(world, time, 3); - } - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java -index c8fd5696de7c3623cdb4f498190a5c2708cf843e..2a6e5a9b35102ef540b561ec7ef5a5f119c564fe 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java -+++ b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java -@@ -29,6 +29,20 @@ public class MoveControl implements Control { - this.mob = entity; - } - -+ // Purpur start - Ridables -+ public void setSpeedModifier(double speed) { -+ this.speedModifier = speed; -+ } -+ -+ public void setForward(float forward) { -+ this.strafeForwards = forward; -+ } -+ -+ public void setStrafe(float strafe) { -+ this.strafeRight = strafe; -+ } -+ // Purpur end - Ridables -+ - public boolean hasWanted() { - return this.operation == MoveControl.Operation.MOVE_TO; - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java -index fbfc2f2515ad709b2c1212aef9521e795547d66b..ebe941aeb959fc34372bfc59bc3a13421167b4cf 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java -+++ b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java -@@ -3,7 +3,7 @@ package net.minecraft.world.entity.ai.control; - import net.minecraft.util.Mth; - import net.minecraft.world.entity.Mob; - --public class SmoothSwimmingLookControl extends LookControl { -+public class SmoothSwimmingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables - private final int maxYRotFromCenter; - private static final int HEAD_TILT_X = 10; - private static final int HEAD_TILT_Y = 20; -@@ -14,7 +14,7 @@ public class SmoothSwimmingLookControl extends LookControl { - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - if (this.lookAtCooldown > 0) { - this.lookAtCooldown--; - this.getYRotD().ifPresent(yaw -> this.mob.yHeadRot = this.rotateTowards(this.mob.yHeadRot, yaw + 20.0F, this.yMaxRotSpeed)); -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java -index 7324da6b7dd2623ce394e3827ff77ef684a3b98b..a96f49641898aa16bdc99eed8a86497ddcd5a492 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 -@@ -33,7 +33,7 @@ public class BreakDoorGoal extends DoorInteractGoal { - - @Override - public boolean canUse() { -- return !super.canUse() ? false : (!getServerLevel((Entity) this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.isValidDifficulty(this.mob.level().getDifficulty()) && !this.isOpen()); -+ return !super.canUse() ? false : (!this.mob.level().purpurConfig.zombieBypassMobGriefing == !getServerLevel((Entity) this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.isValidDifficulty(this.mob.level().getDifficulty()) && !this.isOpen()); // Purpur - Add mobGriefing bypass to everything affected - } - - @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 32bb591371fe78ba10a2bc52389ef33978cbc0eb..6032eb2209d013b34c28eedd180583af3680fc69 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 -@@ -74,7 +74,7 @@ public class EatBlockGoal extends Goal { - - final BlockState blockState = this.level.getBlockState(blockposition); // Paper - fix wrong block state - if (EatBlockGoal.IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state -- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state -+ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).purpurConfig.sheepBypassMobGriefing == !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state // Purpur - Add mobGriefing bypass to everything affected - this.level.destroyBlock(blockposition, false); - } - -@@ -83,7 +83,7 @@ public class EatBlockGoal extends Goal { - BlockPos blockposition1 = blockposition.below(); - - if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) { -- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state -+ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).purpurConfig.sheepBypassMobGriefing == !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state // Purpur - Add mobGriefing bypass to everything affected - 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/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java -index 29ae74339a4831ccef3d01e8054931715ba192ad..5a439e3b0fdc1010884634c1e046e49d8b9aee17 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java -@@ -38,9 +38,12 @@ public class GoalSelector { - } - - // Paper start - EAR 2 -- public boolean inactiveTick() { -+ public boolean inactiveTick(int tickRate, boolean inactive) { // Pufferfish start -+ if (inactive && !gg.pufferfish.pufferfish.PufferfishConfig.dearEnabled) tickRate = 4; // reset to Paper's -+ tickRate = Math.min(tickRate, 3); - this.curRate++; -- return this.curRate % 3 == 0; // TODO newGoalRate was already unused in 1.20.4, check if this is correct -+ return this.curRate % tickRate == 0; // TODO newGoalRate was already unused in 1.20.4, check if this is correct -+ // Pufferfish end - } - public boolean hasTasks() { - for (WrappedGoal task : this.availableGoals) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java -index df695b444fa2a993d381e2f197182c3e91a68502..0f4f546cd0eda4bd82b47446ae23ac32da8a9556 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.0, 4.0, 9.0), entity -> { - EntityType entityType = entity.getType(); -@@ -71,6 +72,7 @@ public class LlamaFollowCaravanGoal extends Goal { - - @Override - public boolean canContinueToUse() { -+ if (!this.llama.shouldJoinCaravan) return false; // Purpur - if (this.llama.inCaravan() && this.llama.getCaravanHead().isAlive() && this.firstIsLeashed(this.llama, 0)) { - double d = this.llama.distanceToSqr(this.llama.getCaravanHead()); - if (d > 676.0) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -index aee0147649d458b87d92496eda0c1723ebe570d2..89e9ea999d2fbd81a1d74382ef3fcd675fc8b94e 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java -@@ -121,6 +121,7 @@ public abstract class MoveToBlockGoal extends Goal { - for (int m = 0; m <= l; m = m > 0 ? -m : 1 - m) { - for (int n = m < l && m > -l ? l : 0; n <= l; n = n > 0 ? -n : 1 - n) { - mutableBlockPos.setWithOffset(blockPos, m, k - 1, n); -+ if (!this.mob.level().hasChunkAt(mutableBlockPos)) continue; // Pufferfish - if this block isn't loaded, continue - if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level(), mutableBlockPos)) { - this.blockPos = mutableBlockPos; - this.mob.movingTarget = mutableBlockPos == BlockPos.ZERO ? null : mutableBlockPos.immutable(); // Paper -diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java -index 515c1f671cb2c3a7cc23053aedf404bbbe77af3e..42a1e5b9c08a9245dd0ce6d4025e3bb5d60feb62 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java -+++ b/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java -@@ -116,9 +116,9 @@ public class RangedBowAttackGoal extends Go - } - - this.mob.lookAt(livingEntity, 30.0F, 30.0F); -- } else { -+ } //else { // Purpur - fix MC-121706 - this.mob.getLookControl().setLookAt(livingEntity, 30.0F, 30.0F); -- } -+ //} // Purpur - - if (this.mob.isUsingItem()) { - if (!bl && this.seeTime < -60) { -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 9d245d08be61d7edee9138196ae3bf52023e3993..07e3e727e339765284095aa8fd6b5edd41dc4158 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 -@@ -41,7 +41,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal { - - @Override - public boolean canUse() { -- if (!getServerLevel((Entity) this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (!getServerLevel(this.removerMob).purpurConfig.zombieBypassMobGriefing == !getServerLevel((Entity) this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected - 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 b0944fa1f3849dd24cd010fa0a6638f5fd7179d1..d409ae987088df3d47192128401d7491aaabc87c 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 -@@ -67,7 +67,7 @@ public class RunAroundLikeCrazyGoal extends Goal { - int i = this.horse.getTemper(); - int j = this.horse.getMaxTemper(); - -- if (j > 0 && this.horse.getRandom().nextInt(j) < i && !CraftEventFactory.callEntityTameEvent(this.horse, ((CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent -+ if ((this.horse.level().purpurConfig.alwaysTameInCreative && entityhuman.hasInfiniteMaterials()) || (j > 0 && this.horse.getRandom().nextInt(j) < i && !CraftEventFactory.callEntityTameEvent(this.horse, ((CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled())) { // CraftBukkit - fire EntityTameEvent // Purpur - this.horse.tameWithName(entityhuman); - 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 137ec75ee803789deb7b1ca93dd9369c9af362b9..ca95d25af3e9a0536868b0c7fd8e7d2ff1154ee3 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.level().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 84ab90dd1fe693da71732533ccff940c1007e1b6..798d1a169ebebfbdc5e0cf71e7a1256cefcbd32b 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 -@@ -67,7 +67,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 - Villagers follow emerald blocks - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -index 92731b6b593289e9f583c9b705b219e81fcd8e73..9104d7010bda6f9f73b478c11490ef9c53f76da2 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -@@ -56,7 +56,7 @@ public class NearestBedSensor extends Sensor { - // Paper start - optimise POI access - java.util.List, BlockPos>> poiposes = new java.util.ArrayList<>(); - // don't ask me why it's unbounded. ask mojang. -- io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); -+ io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), world.purpurConfig.villagerNearestBedSensorSearchRadius, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); // Purpur - Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); - // Paper end - optimise POI access - if (path != null && path.canReach()) { -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 a0e0692d17760f440fe81d52887284c787e562db..ab9bebc07b5228dbc0d3ba4b0f7d1bbe41814c9b 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 -@@ -22,6 +22,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 - ResourceKey resourceKey = world.dimension(); - BlockPos blockPos = entity.blockPosition(); - List list = Lists.newArrayList(); -@@ -38,7 +45,7 @@ public class SecondaryPoiSensor extends Sensor { - } - } - -- Brain brain = entity.getBrain(); -+ //Brain brain = entity.getBrain(); // Purpur - moved up - if (!list.isEmpty()) { - brain.setMemory(MemoryModuleType.SECONDARY_JOB_SITE, list); - } else { -diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java -index 52982c1e6a4da36392569c791853279f5f9ac31a..ebb827e213a3ba5eeb2fe5b78f5dee99403097b6 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java -+++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java -@@ -64,6 +64,10 @@ public class TargetingConditions { - return false; - } else if (this.selector != null && !this.selector.test(target, world)) { - return false; -+ // Purpur start - AFK API -+ } else if (!world.purpurConfig.idleTimeoutTargetPlayer && target instanceof net.minecraft.server.level.ServerPlayer player && player.isAfk()) { -+ return false; -+ // Purpur end - AFK API - } else { - if (tester == null) { - if (this.isCombat && (!target.canBeSeenAsEnemy() || world.getDifficulty() == Difficulty.PEACEFUL)) { -diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java -index 60c2868f255d372226e0c1389caaa5477bbef41e..d1eb633416ff5090253e77583ec97aff3171c484 100644 ---- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java -+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java -@@ -47,12 +47,85 @@ 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 - Ridables - if (!world.isClientSide) { - this.setResting(true); - } - - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean shouldSendAttribute(net.minecraft.world.entity.ai.attributes.Attribute attribute) { return attribute != Attributes.FLYING_SPEED.value(); } // Fixes log spam on clients -+ -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.batRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.batRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.batControllable; -+ } -+ -+ @Override -+ public double getMaxY() { -+ return level().purpurConfig.batMaxY; -+ } -+ -+ @Override -+ public void onMount(Player rider) { -+ super.onMount(rider); -+ if (isResting()) { -+ setResting(false); -+ level().levelEvent(null, 1025, new BlockPos(this).above(), 0); -+ } -+ } -+ -+ @Override -+ public void travel(Vec3 vec3) { -+ super.travel(vec3); -+ if (getRider() != null && this.isControllable() && !onGround) { -+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2; -+ setSpeed(speed); -+ Vec3 mot = getDeltaMovement(); -+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.25, speed)); -+ setDeltaMovement(mot.scale(0.9D)); -+ } -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.batMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.batScale); -+ this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(this.level().purpurConfig.batFollowRange); -+ this.getAttribute(Attributes.KNOCKBACK_RESISTANCE).setBaseValue(this.level().purpurConfig.batKnockbackResistance); -+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.batMovementSpeed); -+ this.getAttribute(Attributes.FLYING_SPEED).setBaseValue(this.level().purpurConfig.batFlyingSpeed); -+ this.getAttribute(Attributes.ARMOR).setBaseValue(this.level().purpurConfig.batArmor); -+ this.getAttribute(Attributes.ARMOR_TOUGHNESS).setBaseValue(this.level().purpurConfig.batArmorToughness); -+ this.getAttribute(Attributes.ATTACK_KNOCKBACK).setBaseValue(this.level().purpurConfig.batAttackKnockback); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.batTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.batAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - public boolean isFlapping() { - return !this.isResting() && (float) this.tickCount % 10.0F == 0.0F; -@@ -102,7 +175,7 @@ public class Bat extends AmbientCreature { - protected void pushEntities() {} - - public static AttributeSupplier.Builder createAttributes() { -- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D); -+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - Ridables - } - - public boolean isResting() { -@@ -135,6 +208,14 @@ public class Bat extends AmbientCreature { - - @Override - protected void customServerAiStep(ServerLevel world) { -+ // Purpur start - Ridables -+ if (getRider() != null && this.isControllable()) { -+ Vec3 mot = getDeltaMovement(); -+ setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z()); -+ return; -+ } -+ // Purpur end - Ridables -+ - super.customServerAiStep(world); - BlockPos blockposition = this.blockPosition(); - BlockPos blockposition1 = blockposition.above(); -@@ -232,7 +313,7 @@ public class Bat extends AmbientCreature { - int i = world.getMaxLocalRawBrightness(pos); - byte b0 = 4; - -- if (Bat.isHalloween()) { -+ if (Bat.isHalloweenSeason(world.getMinecraftWorld())) { // Purpur - b0 = 7; - } else if (random.nextBoolean()) { - return false; -@@ -242,13 +323,23 @@ public class Bat extends AmbientCreature { - } - } - -+ // Pufferfish start - only check for spooky season once an hour -+ private static boolean isSpookySeason = false; -+ private static final int ONE_HOUR = 20 * 60 * 60; -+ private static int lastSpookyCheck = -ONE_HOUR; -+ public static boolean isHalloweenSeason(Level level) { return level.purpurConfig.forceHalloweenSeason || isHalloween(); } // Purpur - private static boolean isHalloween() { -+ if (net.minecraft.server.MinecraftServer.currentTick - lastSpookyCheck > ONE_HOUR) { - LocalDate localdate = LocalDate.now(); - int i = localdate.get(ChronoField.DAY_OF_MONTH); - int j = localdate.get(ChronoField.MONTH_OF_YEAR); - -- return j == 10 && i >= 20 || j == 11 && i <= 3; -+ isSpookySeason = j == 10 && i >= 20 || j == 11 && i <= 3; -+ lastSpookyCheck = net.minecraft.server.MinecraftServer.currentTick; -+ } -+ return isSpookySeason; - } -+ // Pufferfish end - - private void setupAnimationStates() { - if (this.isResting()) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java -index 9aedc62b1766f6a7db4da7eba55167d21d698791..d1fa6b6a18bd7a44e398eed17f2ff127b09f222a 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java -@@ -87,6 +87,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { - @Override - protected void registerGoals() { - super.registerGoals(); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(0, new PanicGoal(this, 1.25)); - this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 1.6, 1.4, EntitySelector.NO_SPECTATORS::test)); - this.goalSelector.addGoal(4, new AbstractFish.FishSwimGoal(this)); -@@ -100,7 +101,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { - @Override - public void travel(Vec3 movementInput) { - if (this.isControlledByLocalInstance() && this.isInWater()) { -- this.moveRelative(0.01F, movementInput); -+ this.moveRelative(getRider() != null ? getSpeed() : 0.01F, movementInput); // Purpur - Ridables - this.move(MoverType.SELF, this.getDeltaMovement()); - this.setDeltaMovement(this.getDeltaMovement().scale(0.9)); - if (this.getTarget() == null) { -@@ -161,7 +162,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { - protected void playStepSound(BlockPos pos, BlockState state) { - } - -- static class FishMoveControl extends MoveControl { -+ static class FishMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur - Ridables - private final AbstractFish fish; - - FishMoveControl(AbstractFish owner) { -@@ -169,14 +170,22 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { - this.fish = owner; - } - -+ // Purpur start - Ridables - @Override -- public void tick() { -+ public void purpurTick(Player rider) { -+ super.purpurTick(rider); -+ fish.setDeltaMovement(fish.getDeltaMovement().add(0.0D, 0.005D, 0.0D)); -+ } -+ // Purpur end - Ridables -+ -+ @Override -+ public void vanillaTick() { // Purpur - Ridables - if (this.fish.isEyeInFluid(FluidTags.WATER)) { - this.fish.setDeltaMovement(this.fish.getDeltaMovement().add(0.0, 0.005, 0.0)); - } - - if (this.operation == MoveControl.Operation.MOVE_TO && !this.fish.getNavigation().isDone()) { -- float f = (float)(this.speedModifier * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED)); -+ float f = (float)(this.getSpeedModifier() * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables - this.fish.setSpeed(Mth.lerp(0.125F, this.fish.getSpeed(), f)); - double d = this.wantedX - this.fish.getX(); - double e = this.wantedY - this.fish.getY(); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java -index 5677dc97ed83652f261100cf391883cfac7d16fe..8df1682dca61b0f771a1c08f5bcd797506badf19 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Animal.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java -@@ -49,6 +49,7 @@ public abstract class Animal extends AgeableMob { - @Nullable - public UUID loveCause; - public ItemStack breedItem; // CraftBukkit - Add breedItem variable -+ public abstract int getPurpurBreedTime(); // Purpur - Make entity breeding times configurable - - protected Animal(EntityType type, Level world) { - super(type, world); -@@ -157,7 +158,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 - final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying - this.usePlayerItem(player, hand, itemstack); - this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying -@@ -261,12 +262,20 @@ public abstract class Animal extends AgeableMob { - AgeableMob entityageable = this.getBreedOffspring(world, other); - - if (entityageable != null) { -- entityageable.setBaby(true); -- entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); -- // CraftBukkit start - call EntityBreedEvent -+ // Purpur start - ServerPlayer breeder = Optional.ofNullable(this.getLoveCause()).or(() -> { - return Optional.ofNullable(other.getLoveCause()); - }).orElse(null); -+ if (breeder != null && world.purpurConfig.animalBreedingCooldownSeconds > 0) { -+ if (world.hasBreedingCooldown(breeder.getUUID(), this.getClass())) { -+ return; -+ } -+ world.addBreedingCooldown(breeder.getUUID(), this.getClass()); -+ } -+ entityageable.setBaby(true); -+ entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); -+ // CraftBukkit start - call EntityBreedEvent -+ // Purpur end - int experience = this.getRandom().nextInt(7) + 1; - EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityageable, this, other, breeder, this.breedItem, experience); - if (entityBreedEvent.isCancelled()) { -@@ -294,8 +303,10 @@ public abstract class Animal extends AgeableMob { - entityplayer.awardStat(Stats.ANIMALS_BRED); - CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, entityanimal, entityageable); - } // Paper -- this.setAge(6000); -- entityanimal.setAge(6000); -+ // Purpur start - Make entity breeding times configurable -+ this.setAge(this.getPurpurBreedTime()); -+ entityanimal.setAge(entityanimal.getPurpurBreedTime()); -+ // Purpur end - Make entity breeding times configurable - this.resetLove(); - entityanimal.resetLove(); - worldserver.broadcastEntityEvent(this, (byte) 18); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index 0bafe14342c1acce131ad34717c18aed3718deed..d2ac2c3a2481ee216a491333b173625da3881737 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -154,6 +154,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - public Bee(EntityType type, Level world) { - super(type, world); - this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60); -+ final org.purpurmc.purpur.controller.FlyingMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.25F, 1.0F, false); // Purpur - Ridables - // Paper start - Fix MC-167279 - class BeeFlyingMoveControl extends FlyingMoveControl { - public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) { -@@ -162,22 +163,69 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - - @Override - public void tick() { -+ // Purpur start - Ridables -+ if (mob.getRider() != null && mob.isControllable()) { -+ flyingController.purpurTick(mob.getRider()); -+ return; -+ } -+ // Purpur end - Ridables - if (this.mob.getY() <= Bee.this.level().getMinY()) { - this.mob.setNoGravity(false); - } - super.tick(); - } -+ -+ // Purpur start - Ridables -+ @Override -+ public boolean hasWanted() { -+ return mob.getRider() != null || !mob.isControllable() || super.hasWanted(); -+ } -+ // Purpur end - Ridables - } - this.moveControl = new BeeFlyingMoveControl(this, 20, true); - // Paper end - Fix MC-167279 - this.lookControl = new Bee.BeeLookControl(this); - this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F); -- this.setPathfindingMalus(PathType.WATER, -1.0F); -+ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - Toggle for water sensitive mob damage - this.setPathfindingMalus(PathType.WATER_BORDER, 16.0F); - this.setPathfindingMalus(PathType.COCOA, -1.0F); - this.setPathfindingMalus(PathType.FENCE, -1.0F); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.beeRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.beeRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.beeControllable; -+ } -+ -+ @Override -+ public double getMaxY() { -+ return level().purpurConfig.beeMaxY; -+ } -+ -+ @Override -+ public void travel(Vec3 vec3) { -+ super.travel(vec3); -+ if (getRider() != null && this.isControllable() && !onGround) { -+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2; -+ setSpeed(speed); -+ Vec3 mot = getDeltaMovement(); -+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, speed, speed)); -+ setDeltaMovement(mot.scale(0.9D)); -+ } -+ } -+ // Purpur end - Ridables -+ - @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { - super.defineSynchedData(builder); -@@ -192,6 +240,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(0, new Bee.BeeAttackGoal(this, 1.399999976158142D, true)); - this.goalSelector.addGoal(1, new Bee.BeeEnterHiveGoal()); - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); -@@ -211,6 +260,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - this.goalSelector.addGoal(7, new Bee.BeeGrowCropGoal()); - this.goalSelector.addGoal(8, new Bee.BeeWanderGoal()); - this.goalSelector.addGoal(9, new FloatGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, (new Bee.BeeHurtByOtherGoal(this)).setAlertOthers(new Class[0])); - this.targetSelector.addGoal(2, new Bee.BeeBecomeAngryTargetGoal(this)); - this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true)); -@@ -380,7 +430,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - } - - public static boolean isNightOrRaining(Level world) { -- return world.dimensionType().hasSkyLight() && (world.isNight() || world.isRaining()); -+ return world.dimensionType().hasSkyLight() && ((world.isNight() && !world.purpurConfig.beeCanWorkAtNight) || (world.isRaining() && !world.purpurConfig.beeCanWorkInRain)); // Purpur - } - - public void setStayOutOfHiveCountdown(int cannotEnterHiveTicks) { -@@ -415,6 +465,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - this.hurtServer(world, 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) { -@@ -439,6 +490,30 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - return tileentitybeehive != null && tileentitybeehive.isFireNearby(); - } - -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.beeMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.beeScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.beeBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.beeAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.beeTakeDamageFromWater; -+ } -+ - @Override - public int getRemainingPersistentAngerTime() { - return (Integer) this.entityData.get(Bee.DATA_REMAINING_ANGER_TIME); -@@ -738,16 +813,16 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { - return state.is(BlockTags.BEE_ATTRACTIVE) ? ((Boolean) state.getValueOrElse(BlockStateProperties.WATERLOGGED, false) ? false : (state.is(Blocks.SUNFLOWER) ? state.getValue(DoublePlantBlock.HALF) == DoubleBlockHalf.UPPER : true)) : false; - } - -- private class BeeLookControl extends LookControl { -+ private class BeeLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables - - BeeLookControl(final Mob entity) { - super(entity); - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - if (!Bee.this.isAngry()) { -- super.tick(); -+ super.vanillaTick(); // Purpur - Ridables - } - } - -@@ -916,6 +991,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); -@@ -959,6 +1035,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 -@@ -1008,6 +1085,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; -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java -index 989b7be74eaeba7f40eac87c7ee7f252cb0c05c9..5a3348ca39b86cfea941fdfb98ca90a7a0ef908d 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java -@@ -100,12 +100,63 @@ public class Cat extends TamableAnimal implements VariantHolder { - return itemstack.is(ItemTags.CAT_FOOD); - }, true); - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new TamableAnimal.TamableAnimalPanicGoal(1.5D)); - this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this)); - this.goalSelector.addGoal(3, new Cat.CatRelaxOnOwnerGoal(this)); -@@ -118,6 +169,7 @@ public class Cat extends TamableAnimal implements VariantHolder(this, Rabbit.class, false, (TargetingConditions.Selector) null)); - this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR)); - } -@@ -317,6 +369,14 @@ public class Cat extends TamableAnimal implements VariantHolder { - return itemstack.is(ItemTags.CHICKEN_FOOD); -@@ -65,6 +113,14 @@ public class Chicken extends Animal { - 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 - Chickens can retaliate -+ 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 - Chickens can retaliate - } - - @Override -@@ -73,7 +129,7 @@ public class Chicken extends Animal { - } - - public static AttributeSupplier.Builder createAttributes() { -- return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 4.0D).add(Attributes.MOVEMENT_SPEED, 0.25D); -+ return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 4.0D).add(Attributes.MOVEMENT_SPEED, 0.25D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur - Chickens can retaliate - } - - @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..a2d946aa9784e49e628fe6ebbdcbf9ce4423520f 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,36 @@ public class Cod extends AbstractSchoolingFish { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.codRidable; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.codControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.codMaxHealth); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.codTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.codAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - public ItemStack getBucketItemStack() { - return new ItemStack(Items.COD_BUCKET); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Cow.java b/src/main/java/net/minecraft/world/entity/animal/Cow.java -index 3e00bbff266fc71b07014e7e047d77b7f809239f..845206570193682f5aca5abbb6915a64fa7f5044 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Cow.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Cow.java -@@ -37,6 +37,7 @@ import org.bukkit.event.player.PlayerBucketFillEvent; - // CraftBukkit end - - public class Cow extends Animal { -+ private boolean isNaturallyAggressiveToPlayers; // Purpur - - private static final EntityDimensions BABY_DIMENSIONS = EntityType.COW.getDimensions().scale(0.5F).withEyeHeight(0.665F); - -@@ -44,18 +45,70 @@ public class Cow extends Animal { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.cowRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.cowRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.cowControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.cowMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.cowScale); -+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.cowNaturallyAggressiveToPlayersDamage); // Purpur -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.cowBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.cowTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.cowAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience -+ @Override -+ public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.EntitySpawnReason spawnReason, net.minecraft.world.entity.SpawnGroupData entityData) { -+ this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance; -+ return super.finalizeSpawn(world, difficulty, spawnReason, entityData); -+ } -+ - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new PanicGoal(this, 2.0D)); -+ this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, (itemstack) -> { -- return itemstack.is(ItemTags.COW_FOOD); -+ return level().purpurConfig.cowFeedMushrooms > 0 && (itemstack.is(net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem()) || itemstack.is(net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem())) || itemstack.is(ItemTags.COW_FOOD); // Purpur - Cows eat mushrooms - }, 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, (ignored, ignored2) -> isNaturallyAggressiveToPlayers)); // Purpur - } - - @Override -@@ -64,7 +117,7 @@ public class Cow extends Animal { - } - - public static AttributeSupplier.Builder createAttributes() { -- return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D); -+ return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur - } - - @Override -@@ -94,6 +147,7 @@ public class Cow extends Animal { - - @Override - public InteractionResult mobInteract(Player player, InteractionHand hand) { -+ if (getRider() != null) return InteractionResult.PASS; // Purpur - Ridables - ItemStack itemstack = player.getItemInHand(hand); - - if (itemstack.is(Items.BUCKET) && !this.isBaby()) { -@@ -102,7 +156,7 @@ public class Cow extends Animal { - - if (event.isCancelled()) { - player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - Ridables - } - // CraftBukkit end - -@@ -111,6 +165,10 @@ public class Cow extends Animal { - - player.setItemInHand(hand, itemstack1); - return InteractionResult.SUCCESS; -+ // Purpur start - feed mushroom to change to mooshroom - Cows eat mushrooms -+ } else if (level().purpurConfig.cowFeedMushrooms > 0 && this.getType() != EntityType.MOOSHROOM && isMushroom(itemstack)) { -+ return this.feedMushroom(player, itemstack); -+ // Purpur end - Cows eat mushrooms - } else { - return super.mobInteract(player, hand); - } -@@ -126,4 +184,67 @@ public class Cow extends Animal { - public EntityDimensions getDefaultDimensions(Pose pose) { - return this.isBaby() ? Cow.BABY_DIMENSIONS : super.getDefaultDimensions(pose); - } -+ -+ // Purpur start - feed mushroom to change to mooshroom - Cows eat mushrooms -+ private int redMushroomsFed = 0; -+ private int brownMushroomsFed = 0; -+ -+ private boolean isMushroom(ItemStack stack) { -+ return stack.getItem() == net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem() || stack.getItem() == net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem(); -+ } -+ -+ private int incrementFeedCount(ItemStack stack) { -+ if (stack.getItem() == net.minecraft.world.level.block.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(), EntitySpawnReason.CONVERSION); -+ if (mooshroom == null) { -+ return InteractionResult.PASS; -+ } -+ if (stack.getItem() == net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem()) { -+ mooshroom.setVariant(MushroomCow.Variant.BROWN); -+ } else { -+ mooshroom.setVariant(MushroomCow.Variant.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; -+ } -+ this.level().addFreshEntity(mooshroom); -+ this.remove(RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ if (!player.getAbilities().instabuild) { -+ stack.shrink(1); -+ } -+ for (int i = 0; i < 15; ++i) { -+ ((ServerLevel) level()).sendParticlesSource(((ServerLevel) level()).players(), null, net.minecraft.core.particles.ParticleTypes.HAPPY_VILLAGER, -+ false, true, -+ getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1, -+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0); -+ } -+ return InteractionResult.SUCCESS; -+ } -+ // Purpur end - Cows eat mushrooms - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -index 5af4d590a9b0f17ba53c6959d9c18bd1269878a4..abbf13d79b4c1ed46bd486ebbd4ff5001bb096b5 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java -@@ -85,14 +85,102 @@ public class Dolphin extends AgeableWaterCreature { - return !entityitem.hasPickUpDelay() && entityitem.isAlive() && entityitem.isInWater(); - }; - public static final float BABY_SCALE = 0.65F; -+ private int spitCooldown; // Purpur - Ridables -+ private boolean isNaturallyAggressiveToPlayers; // Purpur - Dolphins naturally aggressive to players chance - - public Dolphin(EntityType type, Level world) { - super(type, world); -- this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); -+ // Purpur start - Ridables -+ class DolphinMoveControl extends SmoothSwimmingMoveControl { -+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterMoveControllerWASD; -+ private final Dolphin dolphin; -+ -+ public DolphinMoveControl(Dolphin dolphin, int pitchChange, int yawChange, float speedInWater, float speedInAir, boolean buoyant) { -+ super(dolphin, pitchChange, yawChange, speedInWater, speedInAir, buoyant); -+ this.dolphin = dolphin; -+ this.waterMoveControllerWASD = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(dolphin); -+ } -+ -+ @Override -+ public void tick() { -+ if (dolphin.getRider() != null && dolphin.isControllable()) { -+ purpurTick(dolphin.getRider()); -+ } else { -+ super.tick(); -+ } -+ } -+ -+ public void purpurTick(Player rider) { -+ if (dolphin.getAirSupply() < 150) { -+ // if drowning override player WASD controls to find air -+ super.tick(); -+ } else { -+ waterMoveControllerWASD.purpurTick(rider); -+ dolphin.setDeltaMovement(dolphin.getDeltaMovement().add(0.0D, 0.005D, 0.0D)); -+ } -+ } -+ }; -+ this.moveControl = new DolphinMoveControl(this, 85, 10, 0.02F, 0.1F, true); -+ // Purpur end - Ridables - this.lookControl = new SmoothSwimmingLookControl(this, 10); - this.setCanPickUpLoot(true); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.dolphinRidable; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.dolphinControllable; -+ } -+ -+ @Override -+ public boolean onSpacebar() { -+ if (spitCooldown == 0 && getRider() != null) { -+ spitCooldown = level().purpurConfig.dolphinSpitCooldown; -+ -+ org.bukkit.craftbukkit.entity.CraftPlayer player = (org.bukkit.craftbukkit.entity.CraftPlayer) getRider().getBukkitEntity(); -+ if (!player.hasPermission("allow.special.dolphin")) { -+ return false; -+ } -+ -+ org.bukkit.Location loc = player.getEyeLocation(); -+ loc.setPitch(loc.getPitch() - 10); -+ org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(10).add(loc.toVector()); -+ -+ org.purpurmc.purpur.entity.DolphinSpit spit = new org.purpurmc.purpur.entity.DolphinSpit(level(), this); -+ spit.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), level().purpurConfig.dolphinSpitSpeed, 5.0F); -+ -+ level().addFreshEntity(spit); -+ playSound(SoundEvents.DOLPHIN_ATTACK, 1.0F, 1.0F + (random.nextFloat() - random.nextFloat()) * 0.2F); -+ return true; -+ } -+ return false; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.dolphinMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.dolphinScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.dolphinTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.dolphinAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Nullable - @Override - public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) { -@@ -101,6 +189,7 @@ public class Dolphin extends AgeableWaterCreature { - SpawnGroupData groupdataentity1 = (SpawnGroupData) Objects.requireNonNullElseGet(entityData, () -> { - return new AgeableMob.AgeableMobGroupData(0.1F); - }); -+ this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance; // Purpur - Dolphins naturally aggressive to players chance - - return super.finalizeSpawn(world, difficulty, spawnReason, groupdataentity1); - } -@@ -177,17 +266,21 @@ public class Dolphin extends AgeableWaterCreature { - protected void registerGoals() { - this.goalSelector.addGoal(0, new BreathAirGoal(this)); - this.goalSelector.addGoal(0, new TryFindWaterGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables -+ this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur - Dolphins naturally aggressive to players chance - 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 - Dolphins naturally aggressive to players chance - this.goalSelector.addGoal(8, new Dolphin.PlayWithItemsGoal()); - this.goalSelector.addGoal(8, new FollowBoatGoal(this)); - this.goalSelector.addGoal(9, new AvoidEntityGoal<>(this, Guardian.class, 8.0F, 1.0D, 1.0D)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Guardian.class})).setAlertOthers()); -+ this.targetSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (ignored, ignored2) -> isNaturallyAggressiveToPlayers)); // Purpur - Dolphins naturally aggressive to players chance - } - - public static AttributeSupplier.Builder createAttributes() { -@@ -231,7 +324,7 @@ public class Dolphin extends AgeableWaterCreature { - - @Override - protected boolean canRide(Entity entity) { -- return true; -+ return boardingCooldown <= 0; // Purpur - make dolphin honor ride cooldown like all other non-boss mobs; - } - - @Override -@@ -264,6 +357,11 @@ public class Dolphin extends AgeableWaterCreature { - @Override - public void tick() { - super.tick(); -+ // Purpur start - Ridables -+ if (spitCooldown > 0) { -+ spitCooldown--; -+ } -+ // Purpur end - Ridables - if (this.isNoAi()) { - this.setAirSupply(this.getMaxAirSupply()); - } else { -@@ -412,6 +510,7 @@ public class Dolphin extends AgeableWaterCreature { - - @Override - public boolean canUse() { -+ if (this.dolphin.level().purpurConfig.dolphinDisableTreasureSearching) return false; // Purpur - return this.dolphin.gotFish() && this.dolphin.getAirSupply() >= 100; - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java -index d48c2bdb004c86e9e08680138fe51dc3b2975a64..fb75b983f7c522708964a9352546ae29611bf4e9 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java -@@ -144,6 +144,69 @@ public class Fox extends Animal implements VariantHolder { - this.getNavigation().setRequiredPathLength(32.0F); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.foxRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.foxRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.foxControllable; -+ } -+ -+ @Override -+ public float getJumpPower() { -+ return getRider() != null && this.isControllable() ? 0.5F : super.getJumpPower(); -+ } -+ -+ @Override -+ public void onMount(Player rider) { -+ super.onMount(rider); -+ setCanPickUpLoot(false); -+ clearStates(); -+ setIsPouncing(false); -+ spitOutItem(getItemBySlot(EquipmentSlot.MAINHAND)); -+ setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); -+ } -+ -+ @Override -+ public void onDismount(Player rider) { -+ super.onDismount(rider); -+ setCanPickUpLoot(true); -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.foxMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.foxScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.foxBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.foxTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.foxAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { - super.defineSynchedData(builder); -@@ -163,6 +226,7 @@ public class Fox extends Animal implements VariantHolder { - return entityliving instanceof AbstractSchoolingFish; - }); - this.goalSelector.addGoal(0, new Fox.FoxFloatGoal()); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(0, new ClimbOnTopOfPowderSnowGoal(this, this.level())); - this.goalSelector.addGoal(1, new Fox.FaceplantGoal()); - this.goalSelector.addGoal(2, new Fox.FoxPanicGoal(2.2D)); -@@ -189,6 +253,7 @@ public class Fox extends Animal implements VariantHolder { - this.goalSelector.addGoal(11, new Fox.FoxSearchForItemsGoal()); - this.goalSelector.addGoal(12, new Fox.FoxLookAtPlayerGoal(this, Player.class, 24.0F)); - this.goalSelector.addGoal(13, new Fox.PerchAndSearchGoal()); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(3, new Fox.DefendTrustedTargetGoal(LivingEntity.class, false, false, (entityliving, worldserver) -> { - return Fox.TRUSTED_TARGET_SELECTOR.test(entityliving) && !this.trusts(entityliving.getUUID()); - })); -@@ -338,6 +403,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.Variant.RED) { - this.targetSelector.addGoal(4, this.landTargetGoal); - this.targetSelector.addGoal(4, this.turtleEggTargetGoal); -@@ -367,6 +437,7 @@ public class Fox extends Animal implements VariantHolder { - - public void setVariant(Fox.Variant variant) { - this.entityData.set(Fox.DATA_TYPE_ID, variant.getId()); -+ this.setTargetGoals(); // Purpur - fix API bug not updating pathfinders on type change - } - - List getTrustedUUIDs() { -@@ -702,6 +773,29 @@ public class Fox extends Animal implements VariantHolder { - } - // Paper end - -+ // Purpur start -+ @Override -+ public net.minecraft.world.InteractionResult mobInteract(Player player, net.minecraft.world.InteractionHand hand) { -+ if (level().purpurConfig.foxTypeChangesWithTulips) { -+ ItemStack itemstack = player.getItemInHand(hand); -+ if (getVariant() == Variant.RED && itemstack.getItem() == Items.WHITE_TULIP) { -+ setVariant(Variant.SNOW); -+ if (!player.getAbilities().instabuild) { -+ itemstack.shrink(1); -+ } -+ return net.minecraft.world.InteractionResult.SUCCESS; -+ } else if (getVariant() == Variant.SNOW && itemstack.getItem() == Items.ORANGE_TULIP) { -+ setVariant(Variant.RED); -+ if (!player.getAbilities().instabuild) { -+ itemstack.shrink(1); -+ } -+ return net.minecraft.world.InteractionResult.SUCCESS; -+ } -+ } -+ return super.mobInteract(player, hand); -+ } -+ // Purpur end -+ - @Override - // Paper start - Cancellable death event - protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { -@@ -754,16 +848,16 @@ public class Fox extends Animal implements VariantHolder { - return new Vec3(0.0D, (double) (0.55F * this.getEyeHeight()), (double) (this.getBbWidth() * 0.4F)); - } - -- public class FoxLookControl extends LookControl { -+ public class FoxLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables - - public FoxLookControl() { - super(Fox.this); - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - if (!Fox.this.isSleeping()) { -- super.tick(); -+ super.vanillaTick(); // Purpur - Ridables - } - - } -@@ -774,16 +868,16 @@ public class Fox extends Animal implements VariantHolder { - } - } - -- private class FoxMoveControl extends MoveControl { -+ private class FoxMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables - - public FoxMoveControl() { - super(Fox.this); - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - if (Fox.this.canMove()) { -- super.tick(); -+ super.vanillaTick(); // Purpur - Ridables - } - - } -@@ -901,8 +995,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 - Make entity breeding times configurable -+ this.animal.setAge(this.animal.getPurpurBreedTime()); -+ this.partner.setAge(this.partner.getPurpurBreedTime()); -+ // Purpur end - Make entity breeding times configurable - this.animal.resetLove(); - this.partner.resetLove(); - worldserver.addFreshEntityWithPassengers(entityfox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason -@@ -1288,7 +1384,7 @@ public class Fox extends Animal implements VariantHolder { - } - - protected void onReachedTarget() { -- if (getServerLevel(Fox.this.level()).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (getServerLevel(Fox.this.level()).purpurConfig.foxBypassMobGriefing ^ getServerLevel(Fox.this.level()).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected - 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 e07b79ef172095c1800c88342b3ac8dc7703aea2..caa29df16214c60c4e0a471ad6320ea5d62ba7d0 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -+++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java -@@ -57,13 +57,63 @@ 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); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.ironGolemRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ironGolemRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.ironGolemControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ironGolemMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ironGolemScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.ironGolemTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Summoner API -+ @Nullable -+ public UUID getSummoner() { -+ return summoner; -+ } -+ -+ public void setSummoner(@Nullable UUID summoner) { -+ this.summoner = summoner; -+ } -+ // Purpur end - Summoner API -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.ironGolemAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { -+ if (level().purpurConfig.ironGolemCanSwim) this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur - Ridables -+ if (this.level().purpurConfig.ironGolemPoppyCalm) this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.ReceiveFlower(this)); // Purpur - Iron golem calm anger options -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0D, true)); - this.goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9D, 32.0F)); - this.goalSelector.addGoal(2, new MoveBackToVillageGoal(this, 0.6D, false)); -@@ -71,6 +121,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { - this.goalSelector.addGoal(5, new OfferFlowerGoal(this)); - this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 6.0F)); - this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new DefendVillageTargetGoal(this)); - this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); -@@ -135,6 +186,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 - Summoner API - this.addPersistentAngerSaveData(nbt); - } - -@@ -142,6 +194,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 - Summoner API - this.readPersistentAngerSaveData(this.level(), nbt); - } - -@@ -267,18 +320,19 @@ public class IronGolem extends AbstractGolem implements NeutralMob { - ItemStack itemstack = player.getItemInHand(hand); - - if (!itemstack.is(Items.IRON_INGOT)) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - Ridables - } else { - float f = this.getHealth(); - - this.heal(25.0F); - if (this.getHealth() == f) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - Ridables - } else { - float f1 = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F; - - this.playSound(SoundEvents.IRON_GOLEM_REPAIR, 1.0F, f1); - itemstack.consume(1, player); -+ if (this.level().purpurConfig.ironGolemHealCalm && isAngry() && getHealth() == getMaxHealth()) stopBeingAngry(); // Purpur - Iron golem calm anger options - return InteractionResult.SUCCESS; - } - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -index b04532aa04aec6ebbff74d64abb73189c2e12016..481d373d3906f35d0e8e7aeaef3b70b4d443894f 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -+++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java -@@ -64,6 +64,47 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder { - world.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(0.5D), this.getZ(), 1, 0.0D, 0.0D, 0.0D, 0.0D); -+ // Purpur start -+ entitycow.copyPosition(this); -+ entitycow.yBodyRot = this.yBodyRot; -+ entitycow.setYHeadRot(this.getYHeadRot()); -+ entitycow.yRotO = this.yRotO; -+ entitycow.xRotO = this.xRotO; -+ // Purpur end - // Paper start - custom shear drops; moved drop generation to separate method - drops.forEach(drop -> { - ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), drop); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java -index 0554ee499c452db6c1e6852f5022b1f197adb024..79db05362cddbef624e5f5e19e11faa951b6f81c 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java -@@ -65,6 +65,48 @@ public class Ocelot extends Animal { - this.reassessTrustingGoals(); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.ocelotRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ocelotRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.ocelotControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ocelotMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ocelotScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.ocelotBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.ocelotTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.ocelotAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - public boolean isTrusting() { - return (Boolean) this.entityData.get(Ocelot.DATA_TRUSTING); - } -@@ -98,12 +140,14 @@ public class Ocelot extends Animal { - return itemstack.is(ItemTags.OCELOT_FOOD); - }, true); - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(3, this.temptGoal); - this.goalSelector.addGoal(7, new LeapAtTargetGoal(this, 0.3F)); - this.goalSelector.addGoal(8, new OcelotAttackGoal(this)); - this.goalSelector.addGoal(9, new BreedGoal(this, 0.8D)); - this.goalSelector.addGoal(10, new WaterAvoidingRandomStrollGoal(this, 0.8D, 1.0000001E-5F)); - this.goalSelector.addGoal(11, new LookAtPlayerGoal(this, Player.class, 10.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Chicken.class, false)); - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, false, false, Turtle.BABY_ON_LAND_SELECTOR)); - } -@@ -244,7 +288,7 @@ public class Ocelot extends Animal { - if (world.isUnobstructed(this) && !world.containsAnyLiquid(this.getBoundingBox())) { - BlockPos blockposition = this.blockPosition(); - -- if (blockposition.getY() < world.getSeaLevel()) { -+ if (!level().purpurConfig.ocelotSpawnUnderSeaLevel && blockposition.getY() < world.getSeaLevel()) { - return false; - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java -index be753557d7ebd6f1e82b1bdb6d60ecc450f72eec..18bda7f4d0e0dfb2074b70e1369915c3a486e5cd 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java -@@ -112,6 +112,58 @@ public class Panda extends Animal { - - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.pandaRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.pandaRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.pandaControllable; -+ } -+ -+ @Override -+ public void onMount(Player rider) { -+ super.onMount(rider); -+ setForwardMot(0.0F); -+ sit(false); -+ eat(false); -+ setOnBack(false); -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pandaMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.pandaScale); -+ setAttributes(); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.pandaBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.pandaTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.pandaAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected boolean canDispenserEquipIntoSlot(EquipmentSlot slot) { - return slot == EquipmentSlot.MAINHAND && this.canPickUpLoot(); -@@ -271,6 +323,7 @@ public class Panda extends Animal { - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(2, new Panda.PandaPanicGoal(this, 2.0D)); - this.goalSelector.addGoal(2, new Panda.PandaBreedGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new Panda.PandaAttackGoal(this, 1.2000000476837158D, true)); -@@ -288,6 +341,7 @@ public class Panda extends Animal { - this.goalSelector.addGoal(12, new Panda.PandaRollGoal(this)); - this.goalSelector.addGoal(13, new FollowParentGoal(this, 1.25D)); - this.goalSelector.addGoal(14, new WaterAvoidingRandomStrollGoal(this, 1.0D)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, (new Panda.PandaHurtByTargetGoal(this, new Class[0])).setAlertOthers(new Class[0])); - } - -@@ -617,7 +671,10 @@ public class Panda extends Animal { - - public void setAttributes() { - if (this.isWeak()) { -- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(10.0D); -+ // Purpur start - Configurable entity base attributes -+ net.minecraft.world.entity.ai.attributes.AttributeInstance maxHealth = this.getAttribute(Attributes.MAX_HEALTH); -+ maxHealth.setBaseValue(maxHealth.getValue() / 2); -+ // Purpur end - Configurable entity base attributes - } - - if (this.isLazy()) { -@@ -640,7 +697,7 @@ public class Panda extends Animal { - ItemStack itemstack = player.getItemInHand(hand); - - if (this.isScared()) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - Ridables - } else if (this.isOnBack()) { - this.setOnBack(false); - return InteractionResult.SUCCESS; -@@ -679,12 +736,12 @@ public class Panda extends Animal { - } - } - -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - Ridables - } - - return InteractionResult.SUCCESS_SERVER; - } else { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - Ridables - } - } - -@@ -729,7 +786,7 @@ public class Panda extends Animal { - return itemEntity.getItem().is(ItemTags.PANDA_EATS_FROM_GROUND) && itemEntity.isAlive() && !itemEntity.hasPickUpDelay(); - } - -- private static class PandaMoveControl extends MoveControl { -+ private static class PandaMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables - - private final Panda panda; - -@@ -739,9 +796,9 @@ public class Panda extends Animal { - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - if (this.panda.canPerformAction()) { -- super.tick(); -+ super.vanillaTick(); // Purpur - Ridables - } - } - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Parrot.java b/src/main/java/net/minecraft/world/entity/animal/Parrot.java -index a2f0b79599799ad2aa85aff821d8ac76a8e650bd..724e7e77b1c781c3c91689eb7dfc3aed21133fd0 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java -@@ -125,12 +125,93 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, Level world) { - super(type, world); -- this.moveControl = new FlyingMoveControl(this, 10, false); -+ // Purpur start - Ridables -+ final org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); -+ class ParrotMoveControl extends FlyingMoveControl { -+ public ParrotMoveControl(Mob entity, int maxPitchChange, boolean noGravity) { -+ super(entity, maxPitchChange, noGravity); -+ } -+ -+ @Override -+ public void tick() { -+ if (mob.getRider() != null && mob.isControllable()) { -+ flyingController.purpurTick(mob.getRider()); -+ } else { -+ super.tick(); -+ } -+ } -+ -+ @Override -+ public boolean hasWanted() { -+ return mob.getRider() != null && mob.isControllable() ? getForwardMot() != 0 || getStrafeMot() != 0 : super.hasWanted(); -+ } -+ } -+ this.moveControl = new ParrotMoveControl(this, 10, false); -+ // Purpur end - Ridables - this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F); - this.setPathfindingMalus(PathType.DAMAGE_FIRE, -1.0F); - this.setPathfindingMalus(PathType.COCOA, -1.0F); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.parrotRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.parrotRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.parrotControllable; -+ } -+ -+ @Override -+ public double getMaxY() { -+ return level().purpurConfig.parrotMaxY; -+ } -+ -+ @Override -+ public void travel(Vec3 vec3) { -+ super.travel(vec3); -+ if (getRider() != null && this.isControllable() && !onGround) { -+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2; -+ setSpeed(speed); -+ Vec3 mot = getDeltaMovement(); -+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.25, speed)); -+ setDeltaMovement(mot.scale(0.9D)); -+ } -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.parrotMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.parrotScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return 6000; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.parrotTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.parrotAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Nullable - @Override - public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) { -@@ -149,8 +230,11 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { -@@ -292,13 +377,13 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder { -@@ -156,6 +199,17 @@ public class Pig extends Animal implements ItemSteerable, Saddleable { - public InteractionResult mobInteract(Player player, InteractionHand hand) { - boolean flag = this.isFood(player.getItemInHand(hand)); - -+ if (level().purpurConfig.pigGiveSaddleBack && 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; -+ } -+ - if (!flag && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) { - if (!this.level().isClientSide) { - player.startRiding(this); -diff --git a/src/main/java/net/minecraft/world/entity/animal/PolarBear.java b/src/main/java/net/minecraft/world/entity/animal/PolarBear.java -index cd72d8f766069796ce1fe4a83b8646692005ff8c..986fad77b0340c344ba3a3e3a3e5bc0105625439 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/PolarBear.java -+++ b/src/main/java/net/minecraft/world/entity/animal/PolarBear.java -@@ -59,11 +59,87 @@ public class PolarBear extends Animal implements NeutralMob { - private int remainingPersistentAngerTime; - @Nullable - private UUID persistentAngerTarget; -+ private int standTimer = 0; // Purpur - Ridables - - public PolarBear(EntityType type, Level world) { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.polarBearRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.polarBearRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.polarBearControllable; -+ } -+ -+ @Override -+ public boolean onSpacebar() { -+ if (!isStanding()) { -+ if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0) { -+ setStanding(true); -+ playSound(SoundEvents.POLAR_BEAR_WARNING, 1.0F, 1.0F); -+ } -+ } -+ return false; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.polarBearMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.polarBearScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Breedable Polar Bears -+ 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(); -+ } -+ } -+ // Purpur end - Breedable Polar Bears -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.polarBearBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.polarBearTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.polarBearAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Nullable - @Override - public AgeableMob getBreedOffspring(ServerLevel world, AgeableMob entity) { -@@ -72,20 +148,28 @@ 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 - Breedable Polar Bears - } - - @Override - protected void registerGoals() { - super.registerGoals(); - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new PolarBear.PolarBearMeleeAttackGoal()); - this.goalSelector - .addGoal(1, new PanicGoal(this, 2.0, polarBear -> polarBear.isBaby() ? DamageTypeTags.PANIC_CAUSES : DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES)); -+ // Purpur start - Breedable Polar Bears -+ 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 - Breedable Polar Bears - this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25)); - this.goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0)); - this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); - this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new PolarBear.PolarBearHurtByTargetGoal()); - this.targetSelector.addGoal(2, new PolarBear.PolarBearAttackPlayersGoal()); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); -@@ -204,6 +288,12 @@ public class PolarBear extends Animal implements NeutralMob { - if (!this.level().isClientSide) { - this.updatePersistentAnger((ServerLevel)this.level(), true); - } -+ -+ // Purpur start - Ridables -+ if (isStanding() && --standTimer <= 0) { -+ setStanding(false); -+ } -+ // Purpur end - Ridables - } - - @Override -@@ -223,6 +313,7 @@ public class PolarBear extends Animal implements NeutralMob { - - public void setStanding(boolean warning) { - this.entityData.set(DATA_STANDING_ID, warning); -+ standTimer = warning ? 20 : -1; // Purpur - Ridables - } - - public float getStandingAnimationScale(float tickDelta) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -index cdb74f86ee92ee143af29962a85d45ca585cee44..a612f3e8034dd20566370c846f093b50ddef904c 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java -@@ -52,6 +52,36 @@ public class Pufferfish extends AbstractFish { - this.refreshDimensions(); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.pufferfishRidable; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.pufferfishControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pufferfishMaxHealth); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.pufferfishTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.pufferfishAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { - super.defineSynchedData(builder); -diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -index 53d60d62686f9b6bc98b6b25e4315b848600a99d..080551d9b55d12ef15fa2efbde1d195bc82d065c 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java -@@ -88,6 +88,7 @@ public class Rabbit extends Animal implements VariantHolder { - private boolean wasOnGround; - private int jumpDelayTicks; - public int moreCarrotTicks; -+ private boolean actualJump; // Purpur - Ridables - - public Rabbit(EntityType type, Level world) { - super(type, world); -@@ -95,9 +96,80 @@ public class Rabbit extends Animal implements VariantHolder { - this.moveControl = new Rabbit.RabbitMoveControl(this); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.rabbitRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.rabbitRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.rabbitControllable; -+ } -+ -+ @Override -+ public boolean onSpacebar() { -+ if (onGround) { -+ actualJump = true; -+ jumpFromGround(); -+ actualJump = false; -+ } -+ return true; -+ } -+ -+ private void handleJumping() { -+ if (onGround) { -+ RabbitJumpControl jumpController = (RabbitJumpControl) jumpControl; -+ if (!wasOnGround) { -+ setJumping(false); -+ jumpController.setCanJump(false); -+ } -+ if (!jumpController.wantJump()) { -+ if (moveControl.hasWanted()) { -+ startJumping(); -+ } -+ } else if (!jumpController.canJump()) { -+ jumpController.setCanJump(true); -+ } -+ } -+ wasOnGround = onGround; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.rabbitMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.rabbitScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.rabbitBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.rabbitTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.rabbitAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - public void registerGoals() { - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level())); - this.goalSelector.addGoal(1, new Rabbit.RabbitPanicGoal(this, 2.2D)); - this.goalSelector.addGoal(2, new BreedGoal(this, 0.8D)); -@@ -114,6 +186,14 @@ public class Rabbit extends Animal implements VariantHolder { - - @Override - protected float getJumpPower() { -+ // Purpur start - Ridables -+ if (getRider() != null && this.isControllable()) { -+ if (getForwardMot() < 0) { -+ setSpeed(getForwardMot() * 2F); -+ } -+ return actualJump ? 0.5F : 0.3F; -+ } -+ // Purpur end - Ridables - float f = 0.3F; - - if (this.moveControl.getSpeedModifier() <= 0.6D) { -@@ -188,6 +268,12 @@ public class Rabbit extends Animal implements VariantHolder { - - @Override - public void customServerAiStep(ServerLevel world) { -+ // Purpur start - Ridables -+ if (getRider() != null && this.isControllable()) { -+ handleJumping(); -+ return; -+ } -+ // Purpur end - Ridables - if (this.jumpDelayTicks > 0) { - --this.jumpDelayTicks; - } -@@ -402,10 +488,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); - } - - 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); - -@@ -469,7 +568,7 @@ public class Rabbit extends Animal implements VariantHolder { - } - } - -- private static class RabbitMoveControl extends MoveControl { -+ private static class RabbitMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables - - private final Rabbit rabbit; - private double nextJumpSpeed; -@@ -480,14 +579,14 @@ public class Rabbit extends Animal implements VariantHolder { - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - if (this.rabbit.onGround() && !this.rabbit.jumping && !((Rabbit.RabbitJumpControl) this.rabbit.jumpControl).wantJump()) { - this.rabbit.setSpeedModifier(0.0D); - } else if (this.hasWanted() || this.operation == MoveControl.Operation.JUMPING) { - this.rabbit.setSpeedModifier(this.nextJumpSpeed); - } - -- super.tick(); -+ super.vanillaTick(); // Purpur - Ridables - } - - @Override -@@ -549,7 +648,7 @@ public class Rabbit extends Animal implements VariantHolder { - @Override - public boolean canUse() { - if (this.nextStartTick <= 0) { -- if (!getServerLevel((Entity) this.rabbit).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (!getServerLevel((Entity) this.rabbit).purpurConfig.rabbitBypassMobGriefing == !getServerLevel((Entity) this.rabbit).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected - 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 500259e6f297276fb3d6943c2bf88c844d4ec7e4..b0ac0fb823cb2860e301c63c4cd2d35cdf108275 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Salmon.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Salmon.java -@@ -35,6 +35,36 @@ public class Salmon extends AbstractSchoolingFish implements VariantHolder { -diff --git a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -index fd9f6c17448a4d87f940eb8f544ecb9669068582..e338e92835d665840375049ecb475345e1631f06 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -+++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java -@@ -50,17 +50,60 @@ 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; -+ @Nullable private java.util.UUID summoner; // Purpur - Summoner API - - public SnowGolem(EntityType type, Level world) { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.snowGolemRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.snowGolemRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.snowGolemControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.snowGolemMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.snowGolemScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Summoner API -+ @Nullable -+ public java.util.UUID getSummoner() { -+ return summoner; -+ } -+ -+ public void setSummoner(@Nullable java.util.UUID summoner) { -+ this.summoner = summoner; -+ } -+ // Purpur end - Summoner API -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.snowGolemAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @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 - Ridables -+ this.goalSelector.addGoal(1, new RangedAttackGoal(this, level().purpurConfig.snowGolemAttackDistance, level().purpurConfig.snowGolemSnowBallMin, level().purpurConfig.snowGolemSnowBallMax, level().purpurConfig.snowGolemSnowBallModifier)); // Purpur - Snow Golem rate of fire config - this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D, 1.0000001E-5F)); - this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F)); - this.goalSelector.addGoal(4, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Mob.class, 10, true, false, (entityliving, worldserver) -> { - return entityliving instanceof Enemy; - })); -@@ -80,6 +123,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 - Summoner API - } - - @Override -@@ -88,12 +132,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 - Summoner API - - } - - @Override - public boolean isSensitiveToWater() { -- return true; -+ return this.level().purpurConfig.snowGolemTakeDamageFromWater; // Purpur - Toggle for water sensitive mob damage - } - - @Override -@@ -106,10 +151,11 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - this.hurtServer(worldserver, this.damageSources().melting(), 1.0F); // CraftBukkit - DamageSources.ON_FIRE -> CraftEventFactory.MELTING - } - -- if (!worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (!worldserver.purpurConfig.snowGolemBypassMobGriefing == !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected - return; - } - -+ if (getRider() != null && this.isControllable() && !level().purpurConfig.snowGolemLeaveTrailWhenRidden) return; // Purpur - don't leave snow trail when being ridden - BlockState iblockdata = Blocks.SNOW.defaultBlockState(); - - for (int i = 0; i < 4; ++i) { -@@ -166,7 +212,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); - if (event != null) { - if (event.isCancelled()) { -- return InteractionResult.PASS; -+ return tryRide(player, hand); // Purpur - Ridables - } - drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); - // Paper end - custom shear drops -@@ -178,8 +224,16 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM - } - - return InteractionResult.SUCCESS; -+ // 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 - Ridables - } - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/Squid.java b/src/main/java/net/minecraft/world/entity/animal/Squid.java -index 97a3f0ab3dfca24991051395229dd4c601a66fa0..c7a7d1df79beb527ff94f876ca36a861c37c4947 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,70 @@ public class Squid extends AgeableWaterCreature { - - public Squid(EntityType type, Level world) { - super(type, world); -- //this.random.setSeed((long)this.getId()); // Paper - Share random for entities to make them more random -+ if (!world.purpurConfig.entitySharedRandom) this.random.setSeed((long)this.getId()); // Paper - Share random for entities to make them more random // Purpur - this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F; - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.squidRidable; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.squidControllable; -+ } -+ -+ protected static void rotateVectorAroundY(org.bukkit.util.Vector vector, double degrees) { -+ double rad = Math.toRadians(degrees); -+ double cos = Math.cos(rad); -+ double sine = Math.sin(rad); -+ double x = vector.getX(); -+ double z = vector.getZ(); -+ vector.setX(cos * x - sine * z); -+ vector.setZ(sine * x + cos * z); -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.squidMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.squidScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.squidTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.squidAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience -+ @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 - protected void registerGoals() { - this.goalSelector.addGoal(0, new Squid.SquidRandomMovementGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new Squid.SquidFleeGoal()); - } - -@@ -127,6 +184,7 @@ public class Squid extends AgeableWaterCreature { - } - - if (this.isInWaterOrBubble()) { -+ if (canFly()) setNoGravity(!wasTouchingWater); // Purpur - if (this.tentacleMovement < (float) Math.PI) { - float f = this.tentacleMovement / (float) Math.PI; - this.tentacleAngle = Mth.sin(f * f * (float) Math.PI) * (float) Math.PI * 0.25F; -@@ -305,10 +363,41 @@ public class Squid extends AgeableWaterCreature { - - @Override - public void tick() { -+ // Purpur start - Ridables -+ net.minecraft.world.entity.player.Player rider = squid.getRider(); -+ if (rider != null && squid.isControllable()) { -+ if (rider.jumping) { -+ squid.onSpacebar(); -+ } -+ float forward = rider.getForwardMot(); -+ float strafe = rider.getStrafeMot(); -+ float speed = (float) squid.getAttributeValue(Attributes.MOVEMENT_SPEED) * 10F; -+ if (forward < 0.0F) { -+ speed *= -0.5; -+ } -+ org.bukkit.util.Vector dir = rider.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(speed / 20.0F); -+ if (strafe != 0.0F) { -+ if (forward == 0.0F) { -+ dir.setY(0); -+ rotateVectorAroundY(dir, strafe > 0.0F ? -90 : 90); -+ } else if (forward < 0.0F) { -+ rotateVectorAroundY(dir, strafe > 0.0F ? 45 : -45); -+ } else { -+ rotateVectorAroundY(dir, strafe > 0.0F ? -45 : 45); -+ } -+ } -+ if (forward != 0.0F || strafe != 0.0F) { -+ squid.movementVector = new Vec3((float) dir.getX(), (float) dir.getY(), (float) dir.getZ()); -+ } else { -+ squid.movementVector = Vec3.ZERO; -+ } -+ return; -+ } -+ // Purpur end - Ridables - int i = this.squid.getNoActionTime(); - if (i > 100) { - this.squid.movementVector = Vec3.ZERO; -- } 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() * (float) (Math.PI * 2); - this.squid.movementVector = new Vec3( - (double)(Mth.cos(f) * 0.2F), (double)(-0.1F + this.squid.getRandom().nextFloat() * 0.2F), (double)(Mth.sin(f) * 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 8d59d606bdaaea7c64389572b2810b65414a1533..26790832db3267dbfced4ce77e1a70ac2f2b6c3d 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java -+++ b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java -@@ -67,6 +67,36 @@ public class TropicalFish extends AbstractSchoolingFish implements VariantHolder - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.tropicalFishRidable; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.tropicalFishControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.tropicalFishMaxHealth); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.tropicalFishTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.tropicalFishAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - public static String getPredefinedName(int variant) { - return "entity.minecraft.tropical_fish.predefined." + variant; - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index d6605c15111dbdb6ee61a24822bc0a9aed7198d6..2f9cceaf189b7fe9271aeb8160abe2f284acaad8 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -86,6 +86,48 @@ public class Turtle extends Animal { - this.moveControl = new Turtle.TurtleMoveControl(this); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.turtleRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.turtleRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.turtleControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.turtleMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.turtleScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.turtleBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.turtleTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.turtleAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - public void setHomePos(BlockPos pos) { - this.entityData.set(Turtle.HOME_POS, pos); - } -@@ -188,6 +230,7 @@ public class Turtle extends Animal { - - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(0, new Turtle.TurtlePanicGoal(this, 1.2D)); - this.goalSelector.addGoal(1, new Turtle.TurtleBreedGoal(this, 1.0D)); - this.goalSelector.addGoal(1, new Turtle.TurtleLayEggGoal(this, 1.0D)); -@@ -349,13 +392,15 @@ public class Turtle extends Animal { - return this.isBaby() ? Turtle.BABY_DIMENSIONS : super.getDefaultDimensions(pose); - } - -- private static class TurtleMoveControl extends MoveControl { -+ private static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables - - private final Turtle turtle; -+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur - Ridables - - TurtleMoveControl(Turtle turtle) { - super(turtle); - this.turtle = turtle; -+ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(turtle, 0.25D); // Purpur - Ridables - } - - private void updateSpeed() { -@@ -375,7 +420,7 @@ public class Turtle extends Animal { - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - this.updateSpeed(); - if (this.operation == MoveControl.Operation.MOVE_TO && !this.turtle.getNavigation().isDone()) { - double d0 = this.wantedX - this.turtle.getX(); -@@ -391,7 +436,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/WaterAnimal.java b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java -index 8c4532a250f8679d729a35c17e9b5bd339264450..2b8336bd88641cfb29e94c8f01abfbdb39938bf3 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java -+++ b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java -@@ -74,6 +74,6 @@ public abstract class WaterAnimal extends PathfinderMob { - i = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(i); - j = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(j); - // Paper end - Make water animal spawn height configurable -- return pos.getY() >= j && pos.getY() <= i && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER); -+ return ((reason == EntitySpawnReason.SPAWNER && world.getMinecraftWorld().purpurConfig.spawnerFixMC238526) || (pos.getY() >= j && pos.getY() <= i)) && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER); // Purpur - } - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/Wolf.java b/src/main/java/net/minecraft/world/entity/animal/Wolf.java -index c57fac6b5a17f39699298a58d9d25c12da929e64..30b4c09c6046c1d0843ccb8e4ff326e189b6cb95 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Wolf.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Wolf.java -@@ -103,6 +103,37 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder entity instanceof net.minecraft.server.level.ServerPlayer || entity instanceof net.minecraft.world.entity.Mob; -+ private final net.minecraft.world.entity.ai.goal.Goal PATHFINDER_VANILLA = new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR); -+ private final net.minecraft.world.entity.ai.goal.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 - Configurable chance for wolves to spawn rabid - private static final float START_HEALTH = 8.0F; - private static final float TAME_HEALTH = 40.0F; - private static final float ARMOR_REPAIR_UNIT = 0.125F; -@@ -124,12 +155,92 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder(this, Llama.class, 24.0F, 1.5D, 1.5D)); -+ this.goalSelector.addGoal(3, new AvoidRabidWolfGoal(this, 24.0F, 1.5D, 1.5D)); // Purpur - Configurable chance for wolves to spawn rabid - 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)); -@@ -138,11 +249,12 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder(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)); -@@ -191,6 +303,7 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder { - nbt.putString("variant", resourcekey.location().toString()); - }); -@@ -208,6 +321,10 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.wolfNaturalRabid; -+ this.updatePathfinders(false); -+ // Purpur end - Configurable chance for wolves to spawn rabid - return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData); - } - -@@ -269,6 +390,11 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder type, Level world) { - super(type, world); -- this.moveControl = new FlyingMoveControl(this, 20, true); -+ // Purpur start - Ridables -+ this.purpurController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.1F, 0.5F); -+ this.moveControl = new FlyingMoveControl(this, 20, true) { -+ @Override -+ public void tick() { -+ if (mob.getRider() != null && mob.isControllable()) { -+ purpurController.purpurTick(mob.getRider()); -+ } else { -+ super.tick(); -+ } -+ } -+ }; -+ // Purpur end - Ridables - this.setCanPickUpLoot(this.canPickUpLoot()); - this.vibrationUser = new Allay.VibrationUser(); - this.vibrationData = new VibrationSystem.Data(); -@@ -121,6 +134,35 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS - } - // CraftBukkit end - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.allayRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.allayRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.allayControllable; -+ } -+ -+ @Override -+ protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.allayMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.allayScale); -+ } -+ // Purpur end - Configurable entity base attributes - @Override - protected Brain.Provider brainProvider() { - return Brain.provider(Allay.MEMORY_TYPES, Allay.SENSOR_TYPES); -@@ -223,11 +265,13 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS - return 0.4F; - } - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("allayBrain"); -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); - gameprofilerfiller.pop(); - gameprofilerfiller.push("allayActivityUpdate"); -diff --git a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -index c1ef714096159608752d744b98f615cd45fe459a..30779f8a00d438972ad59372ce92e23193f99820 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -+++ b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java -@@ -82,6 +82,36 @@ public class Armadillo extends Animal { - return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 12.0D).add(Attributes.MOVEMENT_SPEED, 0.14D); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.armadilloRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.armadilloRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.armadilloControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.armadilloMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.armadilloScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.armadilloBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable - @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { - super.defineSynchedData(builder); -diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -index 31b10cd404b672d7ce21c2107d8f83e32de26ef4..4bff73b108ba9d76409baeed7c26682868e5cc1a 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -+++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -@@ -100,6 +100,48 @@ public class Axolotl extends Animal implements VariantHolder, B - this.lookControl = new Axolotl.AxolotlLookControl(this, 20); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.axolotlRidable; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.axolotlControllable; -+ } -+ -+ @Override -+ protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.axolotlMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.axolotlScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.axolotlBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.axolotlTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.axolotlAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - public float getWalkTargetValue(BlockPos pos, LevelReader world) { - return 0.0F; -@@ -292,11 +334,13 @@ public class Axolotl extends Animal implements VariantHolder, B - return true; - } - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("axolotlBrain"); -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); - gameprofilerfiller.pop(); - gameprofilerfiller.push("axolotlActivityUpdate"); -@@ -520,14 +564,22 @@ public class Axolotl extends Animal implements VariantHolder, B - private static class AxolotlMoveControl extends SmoothSwimmingMoveControl { - - private final Axolotl axolotl; -+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur - Ridables - - public AxolotlMoveControl(Axolotl axolotl) { - super(axolotl, 85, 10, 0.1F, 0.5F, false); - this.axolotl = axolotl; -+ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(axolotl, 0.5D); // Purpur - Ridables - } - - @Override - public void tick() { -+ // Purpur start - Ridables -+ if (axolotl.getRider() != null && axolotl.isControllable()) { -+ waterController.purpurTick(axolotl.getRider()); -+ return; -+ } -+ // Purpur end - Ridables - if (!this.axolotl.isPlayingDead()) { - super.tick(); - } -@@ -542,9 +594,9 @@ public class Axolotl extends Animal implements VariantHolder, B - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - if (!Axolotl.this.isPlayingDead()) { -- super.tick(); -+ super.vanillaTick(); // Purpur - Ridables - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -index f3c884ab9c09f04dd01cabf2ee9de3b5b620563d..d0023e3734bb3c625fa53077f47039dcb82d9606 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -+++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java -@@ -87,6 +87,19 @@ public class Camel extends AbstractHorse { - navigation.setCanWalkOverFences(true); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.camelRidableInWater; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.camelBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable - @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -@@ -314,6 +327,22 @@ public class Camel extends AbstractHorse { - return this.dashCooldown; - } - -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public float generateMaxHealth(net.minecraft.util.RandomSource random) { -+ return (float) generateMaxHealth(this.level().purpurConfig.camelMaxHealthMin, this.level().purpurConfig.camelMaxHealthMax); -+ } -+ -+ @Override -+ public double generateJumpStrength(net.minecraft.util.RandomSource random) { -+ return generateJumpStrength(this.level().purpurConfig.camelJumpStrengthMin, this.level().purpurConfig.camelJumpStrengthMax); -+ } -+ -+ @Override -+ public double generateSpeed(net.minecraft.util.RandomSource random) { -+ return generateSpeed(this.level().purpurConfig.camelMovementSpeedMin, this.level().purpurConfig.camelMovementSpeedMax); -+ } -+ // Purpur end - Configurable entity base attributes - @Override - protected SoundEvent getAmbientSound() { - return SoundEvents.CAMEL_AMBIENT; -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -index ca04e5d829331551a2c2f44e223ff05c6ce04e76..d09aa48e20c9a6e0d465b93e3759556638041394 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java -@@ -106,6 +106,8 @@ public class Frog extends Animal implements VariantHolder> { - public final AnimationState croakAnimationState = new AnimationState(); - public final AnimationState tongueAnimationState = new AnimationState(); - public final AnimationState swimIdleAnimationState = new AnimationState(); -+ private org.purpurmc.purpur.controller.MoveControllerWASD purpurLandController; // Purpur - Ridables -+ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurWaterController; // Purpur - Ridables - - public Frog(EntityType type, Level world) { - super(type, world); -@@ -113,8 +115,62 @@ public class Frog extends Animal implements VariantHolder> { - this.setPathfindingMalus(PathType.WATER, 4.0F); - this.setPathfindingMalus(PathType.TRAPDOOR, -1.0F); - this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); -+ // Purpur start - Ridables -+ this.purpurLandController = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.2F); -+ this.purpurWaterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F); -+ this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) { -+ @Override -+ public void tick() { -+ net.minecraft.world.entity.player.Player rider = mob.getRider(); -+ if (rider != null && mob.isControllable()) { -+ if (mob.isInWater()) { -+ purpurWaterController.purpurTick(rider); -+ mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, -0.005D, 0.0D)); -+ } else { -+ purpurLandController.purpurTick(rider); -+ } -+ } else { -+ super.tick(); -+ } -+ } -+ }; -+ // Purpur end - Ridables -+ } -+ -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.frogRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.frogRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.frogControllable; - } - -+ @Override -+ protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables -+ } -+ -+ @Override -+ public float getJumpPower() { -+ return (getRider() != null && isControllable()) ? level().purpurConfig.frogRidableJumpHeight * this.getBlockJumpFactor() : super.getJumpPower(); -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.frogBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable - @Override - protected Brain.Provider brainProvider() { - return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); -@@ -184,10 +240,12 @@ public class Frog extends Animal implements VariantHolder> { - .ifPresent(this::setVariant); - } - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { - ProfilerFiller profilerFiller = Profiler.get(); - profilerFiller.push("frogBrain"); -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); - profilerFiller.pop(); - profilerFiller.push("frogActivityUpdate"); -@@ -384,7 +442,7 @@ public class Frog extends Animal implements VariantHolder> { - return world.getBlockState(pos.below()).is(BlockTags.FROGS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos); - } - -- class FrogLookControl extends LookControl { -+ class FrogLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables - FrogLookControl(final Mob entity) { - super(entity); - } -diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -index 48ac8c3f6e00c3c2dc67b6c994be7c0ac6dfcf81..33429a9afeefce9238969b2894d0a9c033baca51 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java -@@ -51,13 +51,50 @@ public class Tadpole extends AbstractFish { - protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS); - protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING); - public boolean ageLocked; // Paper -+ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurController; // Purpur - Ridables - - public Tadpole(EntityType type, Level world) { - super(type, world); -- this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); -+ // Purpur start - Ridables -+ this.purpurController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F); -+ this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) { -+ @Override -+ public void tick() { -+ Player rider = mob.getRider(); -+ if (rider != null && mob.isControllable()) { -+ purpurController.purpurTick(rider); -+ mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, 0.002D, 0.0D)); -+ } else { -+ super.tick(); -+ } -+ } -+ }; -+ // Purpur end - Ridables - this.lookControl = new SmoothSwimmingLookControl(this, 10); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.tadpoleRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.tadpoleRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.tadpoleControllable; -+ } -+ -+ @Override -+ protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables -+ } -+ // Purpur end - Ridables -+ - @Override - protected PathNavigation createNavigation(Level world) { - return new WaterBoundPathNavigation(this, world); -@@ -83,11 +120,13 @@ public class Tadpole extends AbstractFish { - return SoundEvents.TADPOLE_FLOP; - } - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("tadpoleBrain"); -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); - gameprofilerfiller.pop(); - gameprofilerfiller.push("tadpoleActivityUpdate"); -diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index 76aca47d8638d5c37c57d3a59fa7f8ceaa5a53b4..9cd08bb4e9069bb2f701ef3825ba4c5af6f56790 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -93,6 +93,41 @@ public class Goat extends Animal { - }); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.goatRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.goatRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.goatControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.goatBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.goatTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.goatAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected Brain.Provider brainProvider() { - return Brain.provider(Goat.MEMORY_TYPES, Goat.SENSOR_TYPES); -@@ -192,11 +227,13 @@ public class Goat extends Animal { - return (Brain) super.getBrain(); // CraftBukkit - decompile error - } - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("goatBrain"); -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); - gameprofilerfiller.pop(); - gameprofilerfiller.push("goatActivityUpdate"); -@@ -399,6 +436,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 8aed30cdbbfdd42c20dcd4c8773c8a0ee21a980d..f58a0f50d04004587d342c1bb5f681cd485cf302 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -@@ -228,11 +228,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.createInventory(); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return false; // vanilla handles -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.generateMaxHealth(random)); -+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.generateSpeed(random)); -+ this.getAttribute(Attributes.JUMP_STRENGTH).setBaseValue(this.generateJumpStrength(random)); -+ } -+ -+ protected double generateMaxHealth(double min, double max) { -+ if (min == max) return min; -+ int diff = Mth.floor(max - min); -+ double base = max - diff; -+ int first = Mth.floor((double) diff / 2); -+ int rest = diff - first; -+ return base + random.nextInt(first + 1) + random.nextInt(rest + 1); -+ } -+ -+ protected double generateJumpStrength(double min, double max) { -+ if (min == max) return min; -+ return min + (max - min) * this.random.nextDouble(); -+ } -+ -+ protected double generateSpeed(double min, double max) { -+ if (min == max) return min; -+ return min + (max - min) * this.random.nextDouble(); -+ } -+ -+ protected float generateMaxHealth(RandomSource random) { -+ return 15.0F + (float) random.nextInt(8) + (float) random.nextInt(9); -+ } -+ -+ protected double generateJumpStrength(RandomSource random) { -+ return 0.4000000059604645D + random.nextDouble() * 0.2D + random.nextDouble() * 0.2D + random.nextDouble() * 0.2D; -+ } -+ -+ protected double generateSpeed(RandomSource random) { -+ return (0.44999998807907104D + random.nextDouble() * 0.3D + random.nextDouble() * 0.3D + random.nextDouble() * 0.3D) * 0.25D; -+ } -+ // Purpur end - Configurable entity base attributes - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new PanicGoal(this, 1.2D)); - this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2D)); - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D, AbstractHorse.class)); -@@ -243,6 +292,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - if (this.canPerformRearing()) { - this.goalSelector.addGoal(9, new RandomStandGoal(this)); - } -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur - Ridables - - this.addBehaviourGoals(); - } -@@ -1269,7 +1319,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, - entityData = new AgeableMob.AgeableMobGroupData(0.2F); - } - -- this.randomizeAttributes(world.getRandom()); -+ //this.randomizeAttributes(world.getRandom()); // Purpur - replaced by initAttributes() - return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData); - } - -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java -index 5cafdde956d7a5b00cd5aec5c44849639307363d..dc1ed34349a6d0d2f233d35a81f2c28e32b10210 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java -@@ -16,6 +16,47 @@ public class Donkey extends AbstractChestedHorse { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.donkeyRidableInWater; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public float generateMaxHealth(net.minecraft.util.RandomSource random) { -+ return (float) generateMaxHealth(this.level().purpurConfig.donkeyMaxHealthMin, this.level().purpurConfig.donkeyMaxHealthMax); -+ } -+ -+ @Override -+ public double generateJumpStrength(net.minecraft.util.RandomSource random) { -+ return generateJumpStrength(this.level().purpurConfig.donkeyJumpStrengthMin, this.level().purpurConfig.donkeyJumpStrengthMax); -+ } -+ -+ @Override -+ public double generateSpeed(net.minecraft.util.RandomSource random) { -+ return generateSpeed(this.level().purpurConfig.donkeyMovementSpeedMin, this.level().purpurConfig.donkeyMovementSpeedMax); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.donkeyBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.donkeyTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.donkeyAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected SoundEvent getAmbientSound() { - return SoundEvents.DONKEY_AMBIENT; -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java -index b5ec7c8ad0e482930d1a54b590b26093f4e477ea..b39cbc42acacdedecfc996dbe25b3773a9fae8b2 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java -@@ -43,6 +43,47 @@ public class Horse extends AbstractHorse implements VariantHolder { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.horseRidableInWater; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public float generateMaxHealth(RandomSource random) { -+ return (float) generateMaxHealth(this.level().purpurConfig.horseMaxHealthMin, this.level().purpurConfig.horseMaxHealthMax); -+ } -+ -+ @Override -+ public double generateJumpStrength(RandomSource random) { -+ return generateJumpStrength(this.level().purpurConfig.horseJumpStrengthMin, this.level().purpurConfig.horseJumpStrengthMax); -+ } -+ -+ @Override -+ public double generateSpeed(RandomSource random) { -+ return generateSpeed(this.level().purpurConfig.horseMovementSpeedMin, this.level().purpurConfig.horseMovementSpeedMax); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.horseBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.horseTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.horseAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void randomizeAttributes(RandomSource random) { - this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double)generateMaxHealth(random::nextInt)); -diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -index 18bd483fe46de3d9dc129bffbccfba9d4cab9550..3c265a80f55eee38406066cd02460b18fbac896d 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java -@@ -72,13 +72,92 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) { - super(type, world); - this.getNavigation().setRequiredPathLength(40.0F); - this.maxDomestication = 30; // Paper - Missing entity API; configure max temper instead of a hardcoded value -+ // Purpur start - Ridables -+ 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 - Ridables -+ } -+ -+ // Purpur start - Ridables -+ @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()); -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @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); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.llamaBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.llamaTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.llamaAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - public boolean isTraderLlama() { - return false; - } -@@ -107,6 +186,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); - public int time; - public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals -+ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms -+ private net.minecraft.world.entity.monster.Phantom targetPhantom; -+ private int phantomBeamTicks = 0; -+ private int phantomDamageCooldown = 0; -+ private int idleCooldown = 0; -+ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms - - public EndCrystal(EntityType type, Level world) { - super(type, world); -@@ -43,6 +49,22 @@ public class EndCrystal extends Entity { - this.setPos(x, y, z); - } - -+ 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; -+ } -+ - @Override - protected Entity.MovementEmission getMovementEmission() { - return Entity.MovementEmission.NONE; -@@ -80,8 +102,52 @@ public class EndCrystal extends Entity { - } - } - // Paper end - Fix invulnerable end crystals -+ 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 - Phantoms attracted to crystals and crystals shoot phantoms -+ 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; -+ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms - } - - @Override -@@ -128,16 +194,18 @@ public class EndCrystal extends Entity { - } - // CraftBukkit end - 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 = CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false); -+ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, getExplosionPower(), hasExplosionFire()); // Purpur - if (event.isCancelled()) { - return false; - } - - this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause -- world.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK); -+ world.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), getExplosionEffect()); // Purpur -+ } else this.unsetRemoved(); // Purpur - } else { - this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause - } -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 d5fc74dbc2c5f6b65fee46e7797b151144c8fd41..d3721fcd9a537ad1e3c5712cd78bd436ac3e1a8b 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 -@@ -108,6 +108,7 @@ public class EnderDragon extends Mob implements Enemy { - @Nullable - private BlockPos podium; - // Paper end - Allow changing the EnderDragon podium -+ private boolean hadRider; // Purpur - Ridables - - public EnderDragon(EntityType entitytypes, Level world) { - super(EntityType.ENDER_DRAGON, world); -@@ -129,6 +130,37 @@ public class EnderDragon extends Mob implements Enemy { - this.noPhysics = true; - this.phaseManager = new EnderDragonPhaseManager(this); - this.explosionSource = new ServerExplosion(world.getMinecraftWorld(), this, null, null, new Vec3(Double.NaN, Double.NaN, Double.NaN), Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit -+ -+ // Purpur start - Ridables -+ this.moveControl = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this) { -+ @Override -+ public void vanillaTick() { -+ // dragon doesn't use the controller. do nothing -+ } -+ }; -+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) { -+ @Override -+ public void vanillaTick() { -+ // dragon doesn't use the controller. do nothing -+ } -+ -+ @Override -+ public void purpurTick(Player rider) { -+ setYawPitch(rider.getYRot() - 180F, rider.xRotO * 0.5F); -+ } -+ }; -+ // Purpur end - Ridables -+ } -+ -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.enderDragonRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.enderDragonRidableInWater; - } - - public void setDragonFight(EndDragonFight fight) { -@@ -143,6 +175,29 @@ public class EnderDragon extends Mob implements Enemy { - return this.fightOrigin; - } - -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.enderDragonControllable; -+ } -+ -+ @Override -+ public double getMaxY() { -+ return level().purpurConfig.enderDragonMaxY; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.enderDragonMaxHealth); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.enderDragonTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage - public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D); - } -@@ -184,6 +239,37 @@ public class EnderDragon extends Mob implements Enemy { - - @Override - public void aiStep() { -+ // Purpur start - Ridables -+ boolean hasRider = getRider() != null && this.isControllable(); -+ if (hasRider) { -+ if (!hadRider) { -+ hadRider = true; -+ noPhysics = false; -+ this.dimensions = net.minecraft.world.entity.EntityDimensions.scalable(4.0F, 2.0F); -+ } -+ -+ // dragon doesn't use controllers, so must tick manually -+ moveControl.tick(); -+ lookControl.tick(); -+ -+ moveRelative((float) getAttributeValue(Attributes.MOVEMENT_SPEED) * 0.1F, new Vec3(-getStrafeMot(), getVerticalMot(), -getForwardMot())); -+ Vec3 mot = getDeltaMovement(); -+ setDeltaMovement(mot); -+ move(MoverType.PLAYER, mot); -+ -+ mot = mot.multiply(0.9F, 0.9F, 0.9F); -+ setDeltaMovement(mot); -+ -+ // control wing flap speed on client -+ phaseManager.setPhase(mot.x() * mot.x() + mot.z() * mot.z() < 0.005F ? EnderDragonPhase.HOVERING : EnderDragonPhase.HOLDING_PATTERN); -+ } else if (hadRider) { -+ hadRider = false; -+ noPhysics = true; -+ this.dimensions = net.minecraft.world.entity.EntityDimensions.scalable(16.0F, 8.0F); -+ phaseManager.setPhase(EnderDragonPhase.HOLDING_PATTERN); // HoldingPattern -+ } -+ // Purpur end - Ridables -+ - this.processFlappingMovement(); - if (this.level().isClientSide) { - this.setHealth(this.getHealth()); -@@ -210,6 +296,8 @@ public class EnderDragon extends Mob implements Enemy { - float f; - - if (this.isDeadOrDying()) { -+ if (hasRider) ejectPassengers(); // Purpur - Ridables -+ - float f1 = (this.random.nextFloat() - 0.5F) * 8.0F; - - f = (this.random.nextFloat() - 0.5F) * 4.0F; -@@ -222,9 +310,9 @@ public class EnderDragon extends Mob implements Enemy { - - f = 0.2F / ((float) vec3d.horizontalDistance() * 10.0F + 1.0F); - f *= (float) Math.pow(2.0D, vec3d.y); -- if (this.phaseManager.getCurrentPhase().isSitting()) { -+ if (!hasRider && this.phaseManager.getCurrentPhase().isSitting()) { // Purpur - Ridables - this.flapTime += 0.1F; -- } else if (this.inWall) { -+ } else if (!hasRider && this.inWall) { // Purpur - Ridables - this.flapTime += f * 0.5F; - } else { - this.flapTime += f; -@@ -240,7 +328,7 @@ public class EnderDragon extends Mob implements Enemy { - float f4; - float f5; - -- if (world1 instanceof ServerLevel) { -+ if (world1 instanceof ServerLevel && !hasRider) { // Purpur - Ridables - ServerLevel worldserver1 = (ServerLevel) world1; - DragonPhaseInstance idragoncontroller = this.phaseManager.getCurrentPhase(); - -@@ -326,7 +414,7 @@ public class EnderDragon extends Mob implements Enemy { - if (world2 instanceof ServerLevel) { - ServerLevel worldserver2 = (ServerLevel) world2; - -- if (this.hurtTime == 0) { -+ if (!hasRider && this.hurtTime == 0) { // Purpur - Ridables - this.knockBack(worldserver2, worldserver2.getEntities((Entity) this, this.wing1.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); - this.knockBack(worldserver2, worldserver2.getEntities((Entity) this, this.wing2.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); - this.hurt(worldserver2, worldserver2.getEntities((Entity) this, this.head.getBoundingBox().inflate(1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); -@@ -374,7 +462,7 @@ public class EnderDragon extends Mob implements Enemy { - if (world3 instanceof ServerLevel) { - ServerLevel worldserver3 = (ServerLevel) world3; - -- this.inWall = this.checkWalls(worldserver3, this.head.getBoundingBox()) | this.checkWalls(worldserver3, this.neck.getBoundingBox()) | this.checkWalls(worldserver3, this.body.getBoundingBox()); -+ this.inWall = !hasRider && this.checkWalls(worldserver3, this.head.getBoundingBox()) | this.checkWalls(worldserver3, this.neck.getBoundingBox()) | this.checkWalls(worldserver3, this.body.getBoundingBox()); // Purpur - Ridables - if (this.dragonFight != null) { - this.dragonFight.updateDragon(this); - } -@@ -510,7 +598,7 @@ public class EnderDragon extends Mob implements Enemy { - BlockState iblockdata = world.getBlockState(blockposition); - - if (!iblockdata.isAir() && !iblockdata.is(BlockTags.DRAGON_TRANSPARENT)) { -- if (world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) { -+ if ((world.purpurConfig.enderDragonBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) { // Purpur - Add mobGriefing bypass to everything affected - // CraftBukkit start - Add blocks to list rather than destroying them - // flag1 = worldserver.removeBlock(blockposition, false) || flag1; - flag1 = true; -@@ -651,7 +739,7 @@ public class EnderDragon extends Mob implements Enemy { - boolean flag = worldserver.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; - } - -@@ -1083,6 +1171,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; - } - -@@ -1109,6 +1198,6 @@ public class EnderDragon extends Mob implements Enemy { - - @Override - protected float sanitizeScale(float scale) { -- return 1.0F; -+ return 1.0F; // Purpur - Configurable entity base attributes - } - } -diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index bd9e10f79eaf0d23908229b3ebc2227946a14843..505c84731b4731588b4568d82a852a17121e5ec8 100644 ---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -@@ -86,20 +86,63 @@ public class WitherBoss extends Monster implements RangedAttackMob { - return !entityliving.getType().is(EntityTypeTags.WITHER_FRIENDS) && entityliving.attackable(); - }; - private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR); -+ private int shootCooldown = 0; // Purpur - Ridables -+ @Nullable private java.util.UUID summoner; // Purpur - Summoner API - // Paper start - private boolean canPortal = false; - - public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; } - // Paper end -+ private org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD purpurController; // Purpur - Ridables - - public WitherBoss(EntityType type, Level world) { - super(type, world); - this.bossEvent = (ServerBossEvent) (new ServerBossEvent(this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS)).setDarkenScreen(true); -- this.moveControl = new FlyingMoveControl(this, 10, false); -+ // Purpur start - Ridables -+ this.purpurController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.1F); -+ this.moveControl = new FlyingMoveControl(this, 10, false) { -+ @Override -+ public void tick() { -+ if (mob.getRider() != null && mob.isControllable()) { -+ purpurController.purpurTick(mob.getRider()); -+ } else { -+ super.tick(); -+ } -+ } -+ }; -+ // Purpur end - Ridables - this.setHealth(this.getMaxHealth()); - this.xpReward = 50; - } -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witherScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.witherTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Summoner API -+ @Nullable -+ public java.util.UUID getSummoner() { -+ return summoner; -+ } - -+ public void setSummoner(@Nullable java.util.UUID summoner) { -+ this.summoner = summoner; -+ } -+ // Purpur end - Summoner API -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.witherAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected PathNavigation createNavigation(Level world) { - FlyingPathNavigation navigationflying = new FlyingPathNavigation(this, world); -@@ -109,13 +152,114 @@ public class WitherBoss extends Monster implements RangedAttackMob { - return navigationflying; - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.witherRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witherRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.witherControllable; -+ } -+ -+ @Override -+ public double getMaxY() { -+ return level().purpurConfig.witherMaxY; -+ } -+ -+ @Override -+ public void travel(Vec3 vec3) { -+ super.travel(vec3); -+ if (getRider() != null && this.isControllable() && !onGround) { -+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 5F; -+ setSpeed(speed); -+ Vec3 mot = getDeltaMovement(); -+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.5, speed)); -+ setDeltaMovement(mot.scale(0.9D)); -+ } -+ } -+ -+ @Override -+ public void onMount(Player rider) { -+ super.onMount(rider); -+ this.entityData.set(DATA_TARGETS.get(0), 0); -+ this.entityData.set(DATA_TARGETS.get(1), 0); -+ this.entityData.set(DATA_TARGETS.get(2), 0); -+ getNavigation().stop(); -+ shootCooldown = 20; -+ } -+ -+ @Override -+ public boolean onClick(net.minecraft.world.InteractionHand hand) { -+ return shoot(getRider(), hand == net.minecraft.world.InteractionHand.MAIN_HAND ? new int[]{1} : new int[]{2}); -+ } -+ -+ public boolean shoot(@Nullable Player rider, int[] heads) { -+ if (shootCooldown > 0) { -+ return false; -+ } -+ -+ shootCooldown = 20; -+ if (rider == null) { -+ return false; -+ } -+ -+ org.bukkit.craftbukkit.entity.CraftHumanEntity player = rider.getBukkitEntity(); -+ if (!player.hasPermission("allow.special.wither")) { -+ return false; -+ } -+ -+ net.minecraft.world.phys.HitResult rayTrace = getRayTrace(120, net.minecraft.world.level.ClipContext.Fluid.NONE); -+ if (rayTrace == null) { -+ return false; -+ } -+ -+ Vec3 loc; -+ if (rayTrace.getType() == net.minecraft.world.phys.HitResult.Type.BLOCK) { -+ BlockPos pos = ((net.minecraft.world.phys.BlockHitResult) rayTrace).getBlockPos(); -+ loc = new Vec3(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D); -+ } else if (rayTrace.getType() == net.minecraft.world.phys.HitResult.Type.ENTITY) { -+ Entity target = ((net.minecraft.world.phys.EntityHitResult) rayTrace).getEntity(); -+ loc = new Vec3(target.getX(), target.getY() + (target.getEyeHeight() / 2), target.getZ()); -+ } else { -+ org.bukkit.block.Block block = player.getTargetBlock(null, 120); -+ loc = new Vec3(block.getX() + 0.5D, block.getY() + 0.5D, block.getZ() + 0.5D); -+ } -+ -+ for (int head : heads) { -+ shoot(head, loc.x(), loc.y(), loc.z(), rider); -+ } -+ -+ return true; // handled -+ } -+ -+ public void shoot(int head, double x, double y, double z, Player rider) { -+ level().levelEvent(null, 1024, blockPosition(), 0); -+ double headX = getHeadX(head); -+ double headY = getHeadY(head); -+ double headZ = getHeadZ(head); -+ Vec3 vec3d = new Vec3(x - headX, y - headY, z - headZ); -+ WitherSkull skull = new WitherSkull(level(), this, vec3d.normalize()); -+ skull.setPosRaw(headX, headY, headZ); -+ level().addFreshEntity(skull); -+ } -+ // Purpur end - Ridables -+ - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(0, new WitherBoss.WitherDoNothingGoal()); - this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 40, 20.0F)); - this.goalSelector.addGoal(5, new WaterAvoidingRandomFlyingGoal(this, 1.0D)); - this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); - this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 0, false, false, WitherBoss.LIVING_ENTITY_SELECTOR)); - } -@@ -133,6 +277,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); - nbt.putInt("Invul", this.getInvulnerableTicks()); -+ if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur - Summoner API - } - - @Override -@@ -142,6 +287,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { - if (this.hasCustomName()) { - this.bossEvent.setName(this.getDisplayName()); - } -+ if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur - Summoner API - - } - -@@ -151,6 +297,13 @@ public class WitherBoss extends Monster implements RangedAttackMob { - this.bossEvent.setName(this.getDisplayName()); - } - -+ // Pufferfish start - optimize suffocation -+ @Override -+ public boolean shouldCheckForSuffocation() { -+ return true; -+ } -+ // Pufferfish end -+ - @Override - protected SoundEvent getAmbientSound() { - return SoundEvents.WITHER_AMBIENT; -@@ -260,6 +413,15 @@ public class WitherBoss extends Monster implements RangedAttackMob { - - @Override - protected void customServerAiStep(ServerLevel world) { -+ // Purpur start - Ridables -+ if (getRider() != null && this.isControllable()) { -+ Vec3 mot = getDeltaMovement(); -+ setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z()); -+ } -+ if (shootCooldown > 0) { -+ shootCooldown--; -+ } -+ // Purpur end - Ridables - int i; - - if (this.getInvulnerableTicks() > 0) { -@@ -276,7 +438,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { - } - // CraftBukkit end - -- if (!this.isSilent()) { -+ if (!this.isSilent() && level().purpurConfig.witherPlaySpawnSound) { - // CraftBukkit start - Use relative location for far away sounds - // worldserver.globalLevelEvent(1023, new BlockPosition(this), 0); - int viewDistance = world.getCraftServer().getViewDistance() * 16; -@@ -301,7 +463,7 @@ public class WitherBoss extends Monster implements 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 - Configurable entity base attributes - } - - } else { -@@ -361,7 +523,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { - - if (this.destroyBlocksTick > 0) { - --this.destroyBlocksTick; -- if (this.destroyBlocksTick == 0 && world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (this.destroyBlocksTick == 0 && (world.purpurConfig.witherBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur - Add mobGriefing bypass to everything affected - boolean flag = false; - - j = Mth.floor(this.getBbWidth() / 2.0F + 1.0F); -@@ -388,8 +550,10 @@ public class WitherBoss extends Monster implements 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()); -@@ -578,11 +742,11 @@ public class WitherBoss extends Monster implements RangedAttackMob { - } - - public int getAlternativeTarget(int headIndex) { -- return (Integer) this.entityData.get((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex)); -+ return getRider() != null && this.isControllable() ? 0 : this.entityData.get(WitherBoss.DATA_TARGETS.get(headIndex)); // Purpur - Ridables - } - - public void setAlternativeTarget(int headIndex, int id) { -- this.entityData.set((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex), id); -+ if (getRider() == null || !this.isControllable()) this.entityData.set(WitherBoss.DATA_TARGETS.get(headIndex), id); // Purpur - Ridables - } - - public boolean isPowered() { -@@ -591,6 +755,7 @@ public class WitherBoss extends Monster implements 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 70b8023c3badc745f342d5b0ab54699e3923826a..572bb7e1f6ae75f6dfe53b0e100b3654e42bf4c2 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java -@@ -114,10 +114,12 @@ public class ArmorStand extends LivingEntity { - private boolean noTickPoseDirty = false; - private boolean noTickEquipmentDirty = false; - // Paper end - Allow ArmorStands not to tick -+ public boolean canMovementTick = true; // Purpur - Movement options for armor stands - - public ArmorStand(EntityType type, Level world) { - super(type, world); - if (world != null) this.canTick = world.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick -+ if (world != null) this.canMovementTick = world.purpurConfig.armorstandMovement; // Purpur - Movement options for armor stands - this.handItems = NonNullList.withSize(2, ItemStack.EMPTY); - this.armorItems = NonNullList.withSize(4, ItemStack.EMPTY); - this.headPose = ArmorStand.DEFAULT_HEAD_POSE; -@@ -126,6 +128,7 @@ public class ArmorStand extends LivingEntity { - this.rightArmPose = ArmorStand.DEFAULT_RIGHT_ARM_POSE; - this.leftLegPose = ArmorStand.DEFAULT_LEFT_LEG_POSE; - this.rightLegPose = ArmorStand.DEFAULT_RIGHT_LEG_POSE; -+ this.setShowArms(world != null && world.purpurConfig.armorstandPlaceWithArms); // Purpur - } - - public ArmorStand(Level world, double x, double y, double z) { -@@ -618,6 +621,7 @@ public class ArmorStand extends LivingEntity { - private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(ServerLevel world, DamageSource damageSource) { // Paper - ItemStack itemstack = new ItemStack(Items.ARMOR_STAND); - -+ if (this.level().purpurConfig.persistentDroppableEntityDisplayNames) - itemstack.set(DataComponents.CUSTOM_NAME, this.getCustomName()); - this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior - return this.brokenByAnything(world, damageSource); // Paper -@@ -681,6 +685,7 @@ public class ArmorStand extends LivingEntity { - - @Override - public void tick() { -+ maxUpStep = level().purpurConfig.armorstandStepHeight; - // Paper start - Allow ArmorStands not to tick - if (!this.canTick) { - if (this.noTickPoseDirty) { -@@ -1013,4 +1018,18 @@ public class ArmorStand extends LivingEntity { - } - } - // Paper end -+ -+ // Purpur start - Movement options for armor stands -+ @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 - Movement options for armor stands - } -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 7d83ad8a61f6aafbc063506c1858554f9b700b70..fd1bd4fb88d1bd4a0734db463dc1be640c736d34 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java -@@ -240,7 +240,13 @@ public class ItemFrame extends HangingEntity { - } - - if (dropSelf) { -- this.spawnAtLocation(world, this.getFrameItemStack()); -+ // Purpur start -+ final ItemStack itemFrame = this.getFrameItemStack(); -+ if (!this.level().purpurConfig.persistentDroppableEntityDisplayNames) { -+ itemFrame.set(DataComponents.CUSTOM_NAME, null); -+ } -+ this.spawnAtLocation(world, 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 fd0e78a2318e3950d011c17358245e107b38154a..0fcab828e81176323cbdf16c0ec714d9a2846ae5 100644 ---- a/src/main/java/net/minecraft/world/entity/decoration/Painting.java -+++ b/src/main/java/net/minecraft/world/entity/decoration/Painting.java -@@ -179,7 +179,13 @@ public class Painting extends HangingEntity implements VariantHolder type, Level world) { - super(type, world); -@@ -384,7 +390,16 @@ public class ItemEntity extends Entity implements TraceableEntity { - - @Override - public final boolean hurtServer(ServerLevel world, DamageSource source, float amount) { -- if (this.isInvulnerableToBase(source)) { -+ // Purpur start - Item entity immunities -+ if ( -+ (immuneToCactus && source.is(net.minecraft.world.damagesource.DamageTypes.CACTUS)) || -+ (immuneToFire && (source.is(net.minecraft.tags.DamageTypeTags.IS_FIRE) || source.is(net.minecraft.world.damagesource.DamageTypes.ON_FIRE) || source.is(net.minecraft.world.damagesource.DamageTypes.IN_FIRE))) || -+ (immuneToLightning && source.is(net.minecraft.world.damagesource.DamageTypes.LIGHTNING_BOLT)) || -+ (immuneToExplosion && source.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION)) -+ ) { -+ return false; -+ } else if (this.isInvulnerableToBase(source)) { -+ // Purpur end - Item entity immunities - return false; - } else if (!world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && source.getEntity() instanceof Mob) { - return false; -@@ -595,6 +610,12 @@ public class ItemEntity extends Entity implements TraceableEntity { - public void setItem(ItemStack stack) { - this.getEntityData().set(ItemEntity.DATA_ITEM, stack); - this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate -+ // Purpur start - Item entity immunities -+ 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 - Item entity immunities - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -index 809f5e847e2f5bb594c130cebd2cb897ea768d82..6f1e21d6c104d71fe4fc3376ed2f2273a5f3d3cc 100644 ---- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -@@ -249,4 +249,31 @@ public class PrimedTnt extends Entity implements TraceableEntity { - return !level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid(); - } - // Paper end - Option to prevent TNT from moving in water -+ // Purpur start - Shears can defuse TNT -+ @Override -+ public net.minecraft.world.InteractionResult interact(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) { -+ Level world = this.level(); -+ -+ if (world instanceof ServerLevel serverWorld && level().purpurConfig.shearsCanDefuseTnt) { -+ final net.minecraft.world.item.ItemStack inHand = player.getItemInHand(hand); -+ -+ if (!inHand.is(net.minecraft.world.item.Items.SHEARS) || !player.getBukkitEntity().hasPermission("purpur.tnt.defuse") || -+ serverWorld.random.nextFloat() > serverWorld.purpurConfig.shearsCanDefuseTntChance) return net.minecraft.world.InteractionResult.PASS; -+ -+ net.minecraft.world.entity.item.ItemEntity tntItem = new net.minecraft.world.entity.item.ItemEntity(serverWorld, getX(), getY(), getZ(), -+ new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.TNT)); -+ tntItem.setPickUpDelay(10); -+ -+ inHand.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand)); -+ serverWorld.addFreshEntity(tntItem, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CUSTOM); -+ -+ this.playSound(net.minecraft.sounds.SoundEvents.SHEEP_SHEAR); -+ -+ this.kill(serverWorld); -+ return net.minecraft.world.InteractionResult.SUCCESS; -+ } -+ -+ return super.interact(player, hand); -+ } -+ // Purpur end - Shears can defuse TNT - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -index 90b6ed81dcfd4021c7e9509da5e8725034fa07e5..26b64d83b7466863b7340c3292494091e9fb89c1 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java -@@ -70,16 +70,19 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - protected AbstractSkeleton(EntityType type, Level world) { - super(type, world); - this.reassessWeaponGoal(); -+ this.setShouldBurnInDay(true); // Purpur - API for any mob to burn daylight - } - - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(2, new RestrictSunGoal(this)); - this.goalSelector.addGoal(3, new FleeSunGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Wolf.class, 6.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D)); - this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); - this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); -@@ -98,37 +101,14 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - abstract SoundEvent getStepSound(); - - // Paper start - shouldBurnInDay API -- private boolean shouldBurnInDay = true; -+ //private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity; keep methods for ABI compatibility - API for any mob to burn daylight - public boolean shouldBurnInDay() { return shouldBurnInDay; } - public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } - // Paper end - shouldBurnInDay API - - @Override - public void aiStep() { -- boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - shouldBurnInDay API -- -- if (flag) { -- ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); -- -- if (!itemstack.isEmpty()) { -- if (itemstack.isDamageableItem()) { -- Item item = itemstack.getItem(); -- -- itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); -- if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { -- this.onEquippedItemBroken(item, EquipmentSlot.HEAD); -- this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); -- } -- } -- -- flag = false; -- } -- -- if (flag) { -- this.igniteForSeconds(8.0F); -- } -- } -- -+ // Purpur - implemented in LivingEntity - API for any mob to burn daylight - super.aiStep(); - } - -@@ -160,11 +140,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - this.reassessWeaponGoal(); - this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - Add world settings for mobs picking up loot - if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { -- LocalDate localdate = LocalDate.now(); -- int i = localdate.get(ChronoField.DAY_OF_MONTH); -- int j = localdate.get(ChronoField.MONTH_OF_YEAR); -- -- if (j == 10 && i == 31 && randomsource.nextFloat() < 0.25F) { -+ if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(world.getMinecraftWorld()) && this.random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur - this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(randomsource.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN)); - this.armorDropChances[EquipmentSlot.HEAD.getIndex()] = 0.0F; - } -@@ -223,7 +199,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - } - - if (event.getProjectile() == entityarrow.getBukkitEntity()) { -- Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - worldserver.getDifficulty().getId() * 4)); -+ Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, 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 end - } -@@ -250,7 +226,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo - super.readAdditionalSaveData(nbt); - this.reassessWeaponGoal(); - // Paper start - shouldBurnInDay API -- if (nbt.contains("Paper.ShouldBurnInDay")) { -+ if (false && nbt.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight - this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); - } - // Paper end - shouldBurnInDay API -@@ -260,7 +236,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 - API for any mob to burn daylight - } - // Paper end - shouldBurnInDay API - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Blaze.java b/src/main/java/net/minecraft/world/entity/monster/Blaze.java -index e33fa82ca1332b95bb067fd621212d3026eee1b7..000d18bdcc2d26b7cc175c4f72e32ac7a882f2a6 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Blaze.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Blaze.java -@@ -33,26 +33,76 @@ public class Blaze extends Monster { - - public Blaze(EntityType type, Level world) { - super(type, world); -- this.setPathfindingMalus(PathType.WATER, -1.0F); -+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); // Purpur - Ridables -+ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - Toggle for water sensitive mob damage - this.setPathfindingMalus(PathType.LAVA, 8.0F); - this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F); - this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F); - this.xpReward = 10; - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.blazeRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.blazeRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.blazeControllable; -+ } -+ -+ @Override -+ public double getMaxY() { -+ return level().purpurConfig.blazeMaxY; -+ } -+ -+ @Override -+ public void travel(Vec3 vec3) { -+ super.travel(vec3); -+ if (getRider() != null && this.isControllable() && !onGround) { -+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED); -+ setSpeed(speed); -+ Vec3 mot = getDeltaMovement(); -+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed)); -+ setDeltaMovement(mot.scale(0.9D)); -+ } -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.blazeMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.blazeScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.blazeAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(4, new Blaze.BlazeAttackGoal(this)); - this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0)); - this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0, 0.0F)); - this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); - this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers()); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); - } - - public static AttributeSupplier.Builder createAttributes() { -- return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0).add(Attributes.MOVEMENT_SPEED, 0.23F).add(Attributes.FOLLOW_RANGE, 48.0); -+ return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0).add(Attributes.MOVEMENT_SPEED, 0.23F).add(Attributes.FOLLOW_RANGE, 48.0).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - Ridables - } - - @Override -@@ -112,11 +162,18 @@ public class Blaze extends Monster { - - @Override - public boolean isSensitiveToWater() { -- return true; -+ return this.level().purpurConfig.blazeTakeDamageFromWater; // Purpur - Toggle for water sensitive mob damage - } - - @Override - protected void customServerAiStep(ServerLevel world) { -+ // Purpur start - Ridables -+ if (getRider() != null && this.isControllable()) { -+ Vec3 mot = getDeltaMovement(); -+ setDeltaMovement(mot.x(), getVerticalMot() > 0 ? 0.07D : -0.07D, mot.z()); -+ return; -+ } -+ // Purpur end - Ridables - this.nextHeightOffsetChangeTick--; - if (this.nextHeightOffsetChangeTick <= 0) { - this.nextHeightOffsetChangeTick = 100; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Bogged.java b/src/main/java/net/minecraft/world/entity/monster/Bogged.java -index 975477663b6d76a69c006a89e440e21471b39b89..ab6e71f0e32070b67d65645981c71da675b1ae80 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Bogged.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Bogged.java -@@ -44,6 +44,30 @@ public class Bogged extends AbstractSkeleton implements Shearable { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.boggedRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.boggedRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.boggedControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.boggedMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.boggedScale); -+ } -+ // Purpur end - Configurable entity base attributes - @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { - super.defineSynchedData(builder); -diff --git a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java -index 4e621a7f36b3d718695434a2a4e3060283667bb2..f34880530a5220b2ef73a2e4fad39445bbad73dd 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java -@@ -27,6 +27,42 @@ public class CaveSpider extends Spider { - return Spider.createAttributes().add(Attributes.MAX_HEALTH, 12.0D); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.caveSpiderRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.caveSpiderRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.caveSpiderControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.caveSpiderMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.caveSpiderScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.caveSpiderTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.caveSpiderAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - public boolean doHurtTarget(ServerLevel world, Entity target) { - if (super.doHurtTarget(world, target)) { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -index 1906dfc22af208d6e801ad4a8f2f9e9702432691..b87a961c1f69f0cf51d8e1e462915234ee74d0a9 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java -@@ -60,21 +60,106 @@ public class Creeper extends Monster { - public int explosionRadius = 3; - private int droppedSkulls; - public Entity entityIgniter; // CraftBukkit -+ // Purpur start - Ridables -+ private int spacebarCharge = 0; -+ private int prevSpacebarCharge = 0; -+ private int powerToggleDelay = 0; -+ // Purpur end - Ridables -+ private boolean exploding = false; // Purpur - Config to make Creepers explode on death - - public Creeper(EntityType type, Level world) { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.creeperRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.creeperRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.creeperControllable; -+ } -+ -+ @Override -+ protected void customServerAiStep(ServerLevel world) { -+ if (powerToggleDelay > 0) { -+ powerToggleDelay--; -+ } -+ if (getRider() != null && this.isControllable()) { -+ if (getRider().getForwardMot() != 0 || getRider().getStrafeMot() != 0) { -+ spacebarCharge = 0; -+ setIgnited(false); -+ setSwellDir(-1); -+ } -+ if (spacebarCharge == prevSpacebarCharge) { -+ spacebarCharge = 0; -+ } -+ prevSpacebarCharge = spacebarCharge; -+ } -+ super.customServerAiStep(world); -+ } -+ -+ @Override -+ public void onMount(Player rider) { -+ super.onMount(rider); -+ setIgnited(false); -+ setSwellDir(-1); -+ } -+ -+ @Override -+ public boolean onSpacebar() { -+ if (powerToggleDelay > 0) { -+ return true; // just toggled power, do not jump or ignite -+ } -+ spacebarCharge++; -+ if (spacebarCharge > maxSwell - 2) { -+ spacebarCharge = 0; -+ if (getRider() != null && getRider().getBukkitEntity().hasPermission("allow.powered.creeper")) { -+ powerToggleDelay = 20; -+ setPowered(!isPowered()); -+ setIgnited(false); -+ setSwellDir(-1); -+ return true; -+ } -+ } -+ if (!isIgnited()) { -+ if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0 && -+ getRider().getBukkitEntity().hasPermission("allow.special.creeper")) { -+ setIgnited(true); -+ setSwellDir(1); -+ return true; -+ } -+ } -+ return getForwardMot() == 0 && getStrafeMot() == 0; // do not jump if standing still -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.creeperMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.creeperScale); -+ } -+ // Purpur end - Configurable entity base attributes - @Override - protected void registerGoals() { - this.goalSelector.addGoal(1, new FloatGoal(this)); - this.goalSelector.addGoal(2, new SwellGoal(this)); -+ this.goalSelector.addGoal(3, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Ocelot.class, 6.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Cat.class, 6.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, false)); - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D)); - this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); - this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true)); - this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); - } -@@ -174,6 +259,38 @@ public class Creeper extends Monster { - } - } - -+ // Purpur start - Charged creeper naturally spawn -+ public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.EntitySpawnReason spawnReason, @Nullable net.minecraft.world.entity.SpawnGroupData entityData) { -+ double chance = world.getLevel().purpurConfig.creeperChargedChance; -+ if (chance > 0D && random.nextDouble() <= chance) { -+ setPowered(true); -+ } -+ return super.finalizeSpawn(world, difficulty, spawnReason, entityData); -+ } -+ // Purpur end - Charged creeper naturally spawn -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.creeperTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ -+ // Purpur start - Config to make Creepers explode on death -+ @Override -+ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { -+ if (!this.exploding && this.level().purpurConfig.creeperExplodeWhenKilled && damageSource.getEntity() instanceof net.minecraft.server.level.ServerPlayer) { -+ this.explodeCreeper(); -+ } -+ return super.dropAllDeathLoot(world, damageSource); -+ } -+ // Purpur end - Config to make Creepers explode on death -+ -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.creeperAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected SoundEvent getHurtSound(DamageSource source) { - return SoundEvents.CREEPER_HURT; -@@ -262,16 +379,18 @@ public class Creeper extends Monster { - - public void explodeCreeper() { - Level world = this.level(); -+ this.exploding = true; // Purpur - Config to make Creepers explode on death - - if (world instanceof ServerLevel worldserver) { - float f = this.isPowered() ? 2.0F : 1.0F; -+ float multiplier = worldserver.purpurConfig.creeperHealthRadius ? this.getHealth() / this.getMaxHealth() : 1; // Purpur - - // CraftBukkit start -- ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, this.explosionRadius * f, false); -+ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, (this.explosionRadius * f) * multiplier, false); // Purpur - if (!event.isCancelled()) { - // CraftBukkit end - this.dead = true; -- worldserver.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) -+ worldserver.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), worldserver.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) && level().purpurConfig.creeperAllowGriefing ? Level.ExplosionInteraction.MOB : Level.ExplosionInteraction.NONE); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) // Purpur - Add enderman and creeper griefing controls - this.spawnLingeringCloud(); - this.triggerOnDeathMobEffects(worldserver, Entity.RemovalReason.KILLED); - this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause -@@ -283,6 +402,7 @@ public class Creeper extends Monster { - // CraftBukkit end - } - -+ this.exploding = false; // Purpur - Config to make Creepers explode on death - } - - private void spawnLingeringCloud() { -@@ -324,6 +444,7 @@ public class Creeper extends Monster { - com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited); - if (event.callEvent()) { - this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited()); -+ if (!event.isIgnited()) setSwellDir(-1); // Purpur - Ridables - } - } - // Paper end - CreeperIgniteEvent -diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java -index c6c86913c0a48501a9109a3838a3e56685d16d79..76db5a2d27ab435fdfd1e0ec0c77ef5012e128d4 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java -@@ -75,6 +75,63 @@ public class Drowned extends Zombie implements RangedAttackMob { - return Zombie.createAttributes().add(Attributes.STEP_HEIGHT, 1.0); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.drownedRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.drownedRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.drownedControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.drownedMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.drownedScale); -+ } -+ -+ @Override -+ protected void randomizeReinforcementsChance() { -+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.drownedSpawnReinforcements); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Configurable jockey options -+ @Override -+ public boolean jockeyOnlyBaby() { -+ return level().purpurConfig.drownedJockeyOnlyBaby; -+ } -+ -+ @Override -+ public double jockeyChance() { -+ return level().purpurConfig.drownedJockeyChance; -+ } -+ -+ @Override -+ public boolean jockeyTryExistingChickens() { -+ return level().purpurConfig.drownedJockeyTryExistingChickens; -+ } -+ // Purpur end - Configurable jockey options -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.drownedTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.drownedAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void addBehaviourGoals() { - this.goalSelector.addGoal(1, new Drowned.DrownedGoToWaterGoal(this, 1.0)); -@@ -82,10 +139,23 @@ public class Drowned extends Zombie implements RangedAttackMob { - this.goalSelector.addGoal(2, new Drowned.DrownedAttackGoal(this, 1.0, false)); - this.goalSelector.addGoal(5, new Drowned.DrownedGoToBeachGoal(this, 1.0)); - this.goalSelector.addGoal(6, new Drowned.DrownedSwimUpGoal(this, 1.0, this.level().getSeaLevel())); -+ if (level().purpurConfig.drownedBreakDoors) this.goalSelector.addGoal(6, new net.minecraft.world.entity.ai.goal.MoveThroughVillageGoal(this, 1.0D, true, 4, this::canBreakDoors)); - this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0)); - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Drowned.class).setAlertOthers(ZombifiedPiglin.class)); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (target, world) -> this.okTarget(target))); -- if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper - Check drowned for villager aggression config -+ // Purpur start -+ if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false) { // Paper - Check drowned for villager aggression config -+ @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)); -@@ -398,7 +468,7 @@ public class Drowned extends Zombie implements RangedAttackMob { - } - } - -- static class DrownedMoveControl extends MoveControl { -+ static class DrownedMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables - private final Drowned drowned; - - public DrownedMoveControl(Drowned drowned) { -@@ -407,7 +477,7 @@ public class Drowned extends Zombie implements RangedAttackMob { - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - LivingEntity livingEntity = this.drowned.getTarget(); - if (this.drowned.wantsToSwim() && this.drowned.isInWater()) { - if (livingEntity != null && livingEntity.getY() > this.drowned.getY() || this.drowned.searchingForLand) { -@@ -427,7 +497,7 @@ public class Drowned extends Zombie implements RangedAttackMob { - float h = (float)(Mth.atan2(f, d) * 180.0F / (float)Math.PI) - 90.0F; - this.drowned.setYRot(this.rotlerp(this.drowned.getYRot(), h, 90.0F)); - this.drowned.yBodyRot = this.drowned.getYRot(); -- float i = (float)(this.speedModifier * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); -+ float i = (float)(this.getSpeedModifier() * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables - float j = Mth.lerp(0.125F, this.drowned.getSpeed(), i); - this.drowned.setSpeed(j); - this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add((double)j * d * 0.005, (double)j * e * 0.1, (double)j * f * 0.005)); -@@ -436,7 +506,7 @@ public class Drowned extends Zombie implements RangedAttackMob { - this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(0.0, -0.008, 0.0)); - } - -- super.tick(); -+ super.vanillaTick(); // Purpur - Ridables - } - } - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java -index 378694a38115c012978e1fea59d049d1ebd04110..16edee73b099b66f12c45062df3108127d154921 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,37 @@ public class ElderGuardian extends Guardian { - - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.elderGuardianRidable; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.elderGuardianControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.elderGuardianMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.elderGuardianScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.elderGuardianTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.elderGuardianAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - public static AttributeSupplier.Builder createAttributes() { - return Guardian.createAttributes().add(Attributes.MOVEMENT_SPEED, 0.30000001192092896D).add(Attributes.ATTACK_DAMAGE, 8.0D).add(Attributes.MAX_HEALTH, 80.0D); - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -index 2a394381a4ad46359359ba402b65c62b331480b4..6ab8abd52836a2a06496652a134dafc21a456a9e 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -@@ -91,12 +91,43 @@ public class EnderMan extends Monster implements NeutralMob { - - public EnderMan(EntityType type, Level world) { - super(type, world); -- this.setPathfindingMalus(PathType.WATER, -1.0F); -+ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - Toggle for water sensitive mob damage - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.endermanRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.endermanRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.endermanControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.endermanMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.endermanScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.endermanAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new EnderMan.EndermanFreezeWhenLookedAt(this)); - this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false)); - this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D, 0.0F)); -@@ -104,9 +135,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 - Ridables - this.targetSelector.addGoal(1, new EnderMan.EndermanLookForPlayerGoal(this, this::isAngryAt)); - this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); -- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, true, false)); -+ this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, 10, true, false, (entityliving, ignored) -> entityliving.level().purpurConfig.endermanAggroEndermites && entityliving instanceof Endermite endermite && (!entityliving.level().purpurConfig.endermanAggroEndermitesOnlyIfPlayerSpawned || endermite.isPlayerSpawned()))); // Purpur - this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal<>(this, false)); - } - -@@ -234,7 +266,7 @@ public class EnderMan extends Monster implements NeutralMob { - - boolean isBeingStaredBy(Player player) { - // Paper start - EndermanAttackPlayerEvent -- final boolean shouldAttack = isBeingStaredBy0(player); -+ final boolean shouldAttack = !this.level().purpurConfig.endermanDisableStareAggro && isBeingStaredBy0(player); // Purpur - final com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) player.getBukkitEntity()); - event.setCancelled(!shouldAttack); - return event.callEvent(); -@@ -262,12 +294,12 @@ public class EnderMan extends Monster implements NeutralMob { - - @Override - public boolean isSensitiveToWater() { -- return true; -+ return this.level().purpurConfig.endermanTakeDamageFromWater; // Purpur - Toggle for water sensitive mob damage - } - - @Override - protected void customServerAiStep(ServerLevel world) { -- if (world.isDay() && this.tickCount >= this.targetChangeTime + 600) { -+ if ((getRider() == null || !this.isControllable()) && world.isDay() && this.tickCount >= this.targetChangeTime + 600) { // Purpur - no random teleporting - float f = this.getLightLevelDependentMagicValue(); - - if (f > 0.5F && world.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper - EndermanEscapeEvent -@@ -306,11 +338,17 @@ public class EnderMan extends Monster implements NeutralMob { - private boolean teleport(double x, double y, double z) { - BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(x, y, z); - -- while (blockposition_mutableblockposition.getY() > this.level().getMinY() && !this.level().getBlockState(blockposition_mutableblockposition).blocksMotion()) { -+ // Pufferfish start - single chunk lookup -+ net.minecraft.world.level.chunk.LevelChunk chunk = this.level().getChunkIfLoaded(blockposition_mutableblockposition); -+ if (chunk == null) { -+ return false; -+ } -+ // Pufferfish end -+ while (blockposition_mutableblockposition.getY() > this.level().getMinY() && !chunk.getBlockState(blockposition_mutableblockposition).blocksMotion()) { // Pufferfish - blockposition_mutableblockposition.move(Direction.DOWN); - } - -- BlockState iblockdata = this.level().getBlockState(blockposition_mutableblockposition); -+ BlockState iblockdata = chunk.getBlockState(blockposition_mutableblockposition); // Pufferfish - boolean flag = iblockdata.blocksMotion(); - boolean flag1 = iblockdata.getFluidState().is(FluidTags.WATER); - -@@ -382,6 +420,8 @@ public class EnderMan extends Monster implements NeutralMob { - public boolean hurtServer(ServerLevel world, DamageSource source, float amount) { - if (this.isInvulnerableTo(world, source)) { - return false; -+ } else if (getRider() != null && this.isControllable()) { return super.hurtServer(world, source, amount); // Purpur - no teleporting on damage -+ } else if (org.purpurmc.purpur.PurpurConfig.endermanShortHeight && source.is(net.minecraft.world.damagesource.DamageTypes.IN_WALL)) { return false; // Purpur - no suffocation damage if short height - Short enderman height - } else { - boolean flag = source.getDirectEntity() instanceof ThrownPotion; - boolean flag1; -@@ -396,6 +436,7 @@ public class EnderMan extends Monster implements NeutralMob { - } else { - flag1 = flag && this.hurtWithCleanWater(world, source, (ThrownPotion) source.getDirectEntity(), amount); - -+ if (!flag1 && world.purpurConfig.endermanIgnoreProjectiles) return super.hurtServer(world, source, amount); // Purpur - if (this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.INDIRECT)) { // Paper - EndermanEscapeEvent - for (int i = 0; i < 64; ++i) { - if (this.teleport()) { -@@ -440,7 +481,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 { -@@ -487,7 +528,16 @@ public class EnderMan extends Monster implements NeutralMob { - - @Override - public boolean canUse() { -- return this.enderman.getCarriedBlock() == null ? false : (!getServerLevel((Entity) this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0); -+ if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur - Add enderman and creeper griefing controls -+ // Purpur start - Add mobGriefing bypass to everything affected -+ if (this.enderman.getCarriedBlock() == null) { -+ return false; -+ } -+ if (!getServerLevel((Entity) this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) == !this.enderman.level().purpurConfig.endermanBypassMobGriefing) { -+ return false; -+ } -+ return this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0; -+ // Purpur end - Add mobGriefing bypass to everything affected - } - - @Override -@@ -532,7 +582,16 @@ public class EnderMan extends Monster implements NeutralMob { - - @Override - public boolean canUse() { -- return this.enderman.getCarriedBlock() != null ? false : (!getServerLevel((Entity) this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0); -+ if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur - Add enderman and creeper griefing controls -+ // Purpur start - Add mobGriefing bypass to everything affected -+ if (this.enderman.getCarriedBlock() != null) { -+ return false; -+ } -+ if (!getServerLevel((Entity) this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) == !this.enderman.level().purpurConfig.endermanBypassMobGriefing) { -+ return false; -+ } -+ return this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0; -+ // Purpur end - Add mobGriefing bypass to everything affected - } - - @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 534e98dd7291e09dee1d0f77cbf221b15708590f..b5e957f101a1bd14501a043fb1b528de25cf2f75 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Endermite.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Endermite.java -@@ -32,20 +32,68 @@ public class Endermite extends Monster { - - private static final int MAX_LIFE = 2400; - public int life; -+ private boolean isPlayerSpawned; // Purpur - Add back player spawned endermite API - - public Endermite(EntityType type, Level world) { - super(type, world); - this.xpReward = 3; - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.endermiteRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.endermiteRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.endermiteControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.endermiteMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.endermiteScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.endermiteTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Add back player spawned endermite API -+ public boolean isPlayerSpawned() { -+ return this.isPlayerSpawned; -+ } -+ -+ public void setPlayerSpawned(boolean playerSpawned) { -+ this.isPlayerSpawned = playerSpawned; -+ } -+ // Purpur end - Add back player spawned endermite API -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.endermiteAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level())); - this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false)); - this.goalSelector.addGoal(3, new WaterAvoidingRandomStrollGoal(this, 1.0D)); - this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 8.0F)); - this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); - } -@@ -83,12 +131,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 - Add back player spawned endermite API - } - - @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); - nbt.putInt("Lifetime", this.life); -+ nbt.putBoolean("PlayerSpawned", this.isPlayerSpawned); // Purpur - Add back player spawned endermite API - } - - @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 14f23c9a248760a57b3d6fe4f2824a4a456a6d37..5e46324e9509a67debfc07614f15d282d293f717 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Evoker.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Evoker.java -@@ -53,10 +53,47 @@ public class Evoker extends SpellcasterIllager { - this.xpReward = 10; - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.evokerRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.evokerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.evokerControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.evokerMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.evokerScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.evokerTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.evokerAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { - super.registerGoals(); - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new Evoker.EvokerCastingSpellGoal()); - this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 0.6D, 1.0D)); - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 0.6D, 1.0D)); -@@ -66,6 +103,7 @@ public class Evoker extends SpellcasterIllager { - this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); - this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); - this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); - this.targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(this, Player.class, true)).setUnseenMemoryTicks(300)); - this.targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)).setUnseenMemoryTicks(300)); -@@ -342,7 +380,7 @@ public class Evoker extends SpellcasterIllager { - } else { - ServerLevel worldserver = getServerLevel(Evoker.this.level()); - -- if (!worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (!worldserver.purpurConfig.evokerBypassMobGriefing == !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected - return false; - } else { - List list = worldserver.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 a8c8c03e972aa6352843cf4c3e4aebfb8f493125..d410824d481a83d11919c045e572eaffab3d9a9f 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,66 @@ public class Ghast extends FlyingMob implements Enemy { - this.moveControl = new Ghast.GhastMoveControl(this); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.ghastRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ghastRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.ghastControllable; -+ } -+ -+ @Override -+ public double getMaxY() { -+ return level().purpurConfig.ghastMaxY; -+ } -+ -+ @Override -+ public void travel(Vec3 vec3) { -+ super.travel(vec3); -+ if (getRider() != null && this.isControllable() && !onGround) { -+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED); -+ setSpeed(speed); -+ Vec3 mot = getDeltaMovement(); -+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed)); -+ setDeltaMovement(mot.scale(0.9D)); -+ } -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ghastMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ghastScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.ghastTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.ghastAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(5, new Ghast.RandomFloatAroundGoal(this)); - this.goalSelector.addGoal(7, new Ghast.GhastLookGoal(this)); - this.goalSelector.addGoal(7, new Ghast.GhastShootFireballGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving, worldserver) -> { - return Math.abs(entityliving.getY() - this.getY()) <= 4.0D; - })); -@@ -103,7 +158,7 @@ public class Ghast extends FlyingMob implements Enemy { - } - - public static AttributeSupplier.Builder createAttributes() { -- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D); -+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - Ridables - } - - @Override -@@ -155,7 +210,7 @@ public class Ghast extends FlyingMob implements Enemy { - - } - -- private static class GhastMoveControl extends MoveControl { -+ private static class GhastMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - Ridables - - private final Ghast ghast; - private int floatDuration; -@@ -166,7 +221,7 @@ public class Ghast extends FlyingMob implements Enemy { - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - if (this.operation == MoveControl.Operation.MOVE_TO) { - if (this.floatDuration-- <= 0) { - this.floatDuration += this.ghast.getRandom().nextInt(5) + 2; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Giant.java b/src/main/java/net/minecraft/world/entity/monster/Giant.java -index 118521ae54254b0a73bb7cba7b2871c9c26f89fc..a7996b600a94f87629a26349c317b379aef4b5a7 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Giant.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Giant.java -@@ -12,12 +12,97 @@ public class Giant extends Monster { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.giantRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.giantRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.giantControllable; -+ } -+ -+ @Override -+ protected void registerGoals() { -+ if (level().purpurConfig.giantHaveAI) { -+ this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); -+ this.goalSelector.addGoal(7, new net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal(this, 1.0D)); -+ this.goalSelector.addGoal(8, new net.minecraft.world.entity.ai.goal.LookAtPlayerGoal(this, net.minecraft.world.entity.player.Player.class, 16.0F)); -+ this.goalSelector.addGoal(8, new net.minecraft.world.entity.ai.goal.RandomLookAroundGoal(this)); -+ this.goalSelector.addGoal(5, new net.minecraft.world.entity.ai.goal.MoveTowardsRestrictionGoal(this, 1.0D)); -+ if (level().purpurConfig.giantHaveHostileAI) { -+ this.goalSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.0D, false)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); -+ this.targetSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class)); -+ this.targetSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.player.Player.class, true)); -+ this.targetSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.npc.Villager.class, false)); -+ this.targetSelector.addGoal(4, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.animal.IronGolem.class, true)); -+ this.targetSelector.addGoal(5, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.animal.Turtle.class, true)); -+ } -+ } -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ protected void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.giantMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.giantScale); -+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.giantMovementSpeed); -+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.giantAttackDamage); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.giantTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.giantAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - public static AttributeSupplier.Builder createAttributes() { - return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 100.0).add(Attributes.MOVEMENT_SPEED, 0.5).add(Attributes.ATTACK_DAMAGE, 50.0); - } - -+ @Override -+ public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.EntitySpawnReason spawnReason, @org.jetbrains.annotations.Nullable net.minecraft.world.entity.SpawnGroupData entityData) { -+ net.minecraft.world.entity.SpawnGroupData groupData = super.finalizeSpawn(world, difficulty, spawnReason, entityData); -+ if (groupData == null) { -+ populateDefaultEquipmentSlots(this.random, difficulty); -+ populateDefaultEquipmentEnchantments(world, this.random, difficulty); -+ } -+ return groupData; -+ } -+ -+ @Override -+ protected void populateDefaultEquipmentSlots(net.minecraft.util.RandomSource random, net.minecraft.world.DifficultyInstance difficulty) { -+ super.populateDefaultEquipmentSlots(this.random, difficulty); -+ // TODO make configurable -+ if (random.nextFloat() < (level().getDifficulty() == net.minecraft.world.Difficulty.HARD ? 0.1F : 0.05F)) { -+ this.setItemSlot(net.minecraft.world.entity.EquipmentSlot.MAINHAND, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.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 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 951f46684623582980901c1ebc1870aa5bcf25a1..929864ee4e5e6e9c87d10bb1bb06118cd94bd5d7 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Guardian.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Guardian.java -@@ -67,15 +67,55 @@ public class Guardian extends Monster { - this.xpReward = 10; - this.setPathfindingMalus(PathType.WATER, 0.0F); - this.moveControl = new Guardian.GuardianMoveControl(this); -+ // Purpur start - Ridables -+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) { -+ @Override -+ public void setYawPitch(float yaw, float pitch) { -+ super.setYawPitch(yaw, pitch * 0.35F); -+ } -+ }; -+ // Purpur end - Ridables - this.clientSideTailAnimation = this.random.nextFloat(); - this.clientSideTailAnimationO = this.clientSideTailAnimation; - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.guardianRidable; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.guardianControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.guardianMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.guardianScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.guardianTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.guardianAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { - MoveTowardsRestrictionGoal pathfindergoalmovetowardsrestriction = new MoveTowardsRestrictionGoal(this, 1.0D); - - this.randomStrollGoal = new RandomStrollGoal(this, 1.0D, 80); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(4, this.guardianAttackGoal = new Guardian.GuardianAttackGoal(this)); // CraftBukkit - assign field - this.goalSelector.addGoal(5, pathfindergoalmovetowardsrestriction); - this.goalSelector.addGoal(7, this.randomStrollGoal); -@@ -84,6 +124,7 @@ public class Guardian extends Monster { - this.goalSelector.addGoal(9, new RandomLookAroundGoal(this)); - this.randomStrollGoal.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK)); - pathfindergoalmovetowardsrestriction.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 10, true, false, new Guardian.GuardianAttackSelector(this))); - } - -@@ -330,7 +371,7 @@ public class Guardian extends Monster { - @Override - public void travel(Vec3 movementInput) { - if (this.isControlledByLocalInstance() && this.isInWater()) { -- this.moveRelative(0.1F, movementInput); -+ this.moveRelative(getRider() != null && this.isControllable() ? getSpeed() : 0.1F, movementInput); // Purpur - Ridables - this.move(MoverType.SELF, this.getDeltaMovement()); - this.setDeltaMovement(this.getDeltaMovement().scale(0.9D)); - if (!this.isMoving() && this.getTarget() == null) { -@@ -342,7 +383,7 @@ public class Guardian extends Monster { - - } - -- private static class GuardianMoveControl extends MoveControl { -+ private static class GuardianMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur - Ridables - - private final Guardian guardian; - -@@ -351,8 +392,17 @@ public class Guardian extends Monster { - this.guardian = guardian; - } - -+ // Purpur start - Ridables - @Override -- public void tick() { -+ public void purpurTick(Player rider) { -+ super.purpurTick(rider); -+ guardian.setDeltaMovement(guardian.getDeltaMovement().add(0.0D, 0.005D, 0.0D)); -+ guardian.setMoving(guardian.getForwardMot() > 0.0F); // control tail speed -+ } -+ // Purpur end - Ridables -+ -+ @Override -+ public void vanillaTick() { // Purpur - Ridables - if (this.operation == MoveControl.Operation.MOVE_TO && !this.guardian.getNavigation().isDone()) { - Vec3 vec3d = new Vec3(this.wantedX - this.guardian.getX(), this.wantedY - this.guardian.getY(), this.wantedZ - this.guardian.getZ()); - double d0 = vec3d.length(); -@@ -363,7 +413,7 @@ public class Guardian extends Monster { - - this.guardian.setYRot(this.rotlerp(this.guardian.getYRot(), f, 90.0F)); - this.guardian.yBodyRot = this.guardian.getYRot(); -- float f1 = (float) (this.speedModifier * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); -+ float f1 = (float) (this.getSpeedModifier() * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables - float f2 = Mth.lerp(0.125F, this.guardian.getSpeed(), f1); - - this.guardian.setSpeed(f2); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Husk.java b/src/main/java/net/minecraft/world/entity/monster/Husk.java -index 184fa759db065fb345f3623752229430816d8ad3..c41a9b4bb24cc801b8460ce7e63695fdec146c5d 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Husk.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Husk.java -@@ -21,15 +21,72 @@ public class Husk extends Zombie { - - public Husk(EntityType type, Level world) { - super(type, world); -+ this.setShouldBurnInDay(false); // Purpur - API for any mob to burn daylight - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.huskRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.huskRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.huskControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.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); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Configurable jockey options -+ @Override -+ public boolean jockeyOnlyBaby() { -+ return level().purpurConfig.huskJockeyOnlyBaby; -+ } -+ -+ @Override -+ public double jockeyChance() { -+ return level().purpurConfig.huskJockeyChance; -+ } -+ -+ @Override -+ public boolean jockeyTryExistingChickens() { -+ return level().purpurConfig.huskJockeyTryExistingChickens; -+ } -+ // Purpur end - Configurable jockey options -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.huskTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.huskAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - public static boolean checkHuskSpawnRules(EntityType type, ServerLevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { - return checkMonsterSpawnRules(type, world, spawnReason, pos, random) && (EntitySpawnReason.isSpawner(spawnReason) || world.canSeeSky(pos)); - } - - @Override - public boolean isSunSensitive() { -- return false; -+ return this.shouldBurnInDay; // Purpur - moved to LivingEntity; keep methods for ABI compatibility - API for any mob to burn daylight - } - - @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 db3aac9ba711dcd18ffc35c4a745ecaec89d0166..83b64a8091a85b9eb940af33e4d44bd4a906b248 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,49 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { - - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.illusionerRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.illusionerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.illusionerControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ protected void initAttributes() { -+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.illusionerMovementSpeed); -+ this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(this.level().purpurConfig.illusionerFollowRange); -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.illusionerMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.illusionerScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.illusionerTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.illusionerAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { - super.registerGoals(); - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new SpellcasterIllager.SpellcasterCastingSpellGoal()); - this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(4, new Illusioner.IllusionerMirrorSpellGoal()); -@@ -71,6 +110,7 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { - this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); - this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); - this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); - this.targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(this, Player.class, true)).setUnseenMemoryTicks(300)); - this.targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)).setUnseenMemoryTicks(300)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java -index ae710c3fffc7840a9ff2cbc5cdacef8a2e248253..2449a960493cbd99a22ce9b8d2fe852d1ec4b20d 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java -+++ b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java -@@ -24,6 +24,61 @@ public class MagmaCube extends Slime { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.magmaCubeRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.magmaCubeRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.magmaCubeControllable; -+ } -+ -+ @Override -+ public float getJumpPower() { -+ return 0.42F * this.getBlockJumpFactor(); // from EntityLiving -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ protected String getMaxHealthEquation() { -+ return level().purpurConfig.magmaCubeMaxHealth; -+ } -+ -+ @Override -+ protected String getAttackDamageEquation() { -+ return level().purpurConfig.magmaCubeAttackDamage; -+ } -+ -+ @Override -+ protected java.util.Map getMaxHealthCache() { -+ return level().purpurConfig.magmaCubeMaxHealthCache; -+ } -+ -+ @Override -+ protected java.util.Map getAttackDamageCache() { -+ return level().purpurConfig.magmaCubeAttackDamageCache; -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.magmaCubeTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.magmaCubeAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - public static AttributeSupplier.Builder createAttributes() { - return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, 0.2F); - } -@@ -71,6 +126,7 @@ public class MagmaCube extends Slime { - float f = (float)this.getSize() * 0.1F; - this.setDeltaMovement(vec3.x, (double)(this.getJumpPower() + f), vec3.z); - this.hasImpulse = true; -+ this.actualJump = false; // Purpur - Ridables - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java -index e2de074bbe7bab0e5a7aecc1fae4c5914a203dd4..c2061f575c731ecc6071384b007517c08e0cf983 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Monster.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java -@@ -88,6 +88,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 4ff75412452649ebf106ef591cb97dc7ac8175e7..860162797972263283737e8f30d8b784955206be 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 - Phantoms attracted to crystals and crystals shoot phantoms -+ 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 - Phantoms burn in light - - public Phantom(EntityType type, Level world) { - super(type, world); -@@ -58,8 +60,97 @@ 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 - API for any mob to burn daylight - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.phantomRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.phantomRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.phantomControllable; -+ } -+ -+ @Override -+ public double getMaxY() { -+ return level().purpurConfig.phantomMaxY; -+ } -+ -+ @Override -+ public void travel(Vec3 vec3) { -+ super.travel(vec3); -+ if (getRider() != null && this.isControllable() && !onGround) { -+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED); -+ setSpeed(speed); -+ Vec3 mot = getDeltaMovement(); -+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, speed, speed)); -+ setDeltaMovement(mot.scale(0.9D)); -+ } -+ } -+ -+ public static net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder createAttributes() { -+ return Monster.createMonsterAttributes().add(Attributes.FLYING_SPEED, 3.0D); -+ } -+ -+ @Override -+ public boolean onSpacebar() { -+ if (getRider() != null && getRider().getBukkitEntity().hasPermission("allow.special.phantom")) { -+ shoot(); -+ } -+ return false; -+ } -+ -+ public boolean shoot() { -+ org.bukkit.Location loc = ((org.bukkit.entity.LivingEntity) getBukkitEntity()).getEyeLocation(); -+ loc.setPitch(-loc.getPitch()); -+ org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(100).add(loc.toVector()); -+ -+ org.purpurmc.purpur.entity.PhantomFlames flames = new org.purpurmc.purpur.entity.PhantomFlames(level(), this); -+ flames.canGrief = level().purpurConfig.phantomAllowGriefing; -+ flames.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), 1.0F, 5.0F); -+ level().addFreshEntity(flames); -+ return true; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.phantomTakeDamageFromWater; -+ } -+ // Purpur end -+ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms -+ @Override -+ protected void dropFromLootTable(ServerLevel world, DamageSource damageSource, boolean causedByPlayer) { -+ boolean dropped = false; -+ if (lastHurtByPlayer == null && damageSource.getEntity() instanceof net.minecraft.world.entity.boss.enderdragon.EndCrystal) { -+ if (random.nextInt(5) < 1) { -+ dropped = spawnAtLocation(world, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.PHANTOM_MEMBRANE)) != null; -+ } -+ } -+ if (!dropped) { -+ super.dropFromLootTable(world, damageSource, causedByPlayer); -+ } -+ } -+ -+ public boolean isCirclingCrystal() { -+ return crystalPosition != null; -+ } -+ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.phantomAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - public boolean isFlapping() { - return (this.getUniqueFlapTickOffset() + this.tickCount) % Phantom.TICKS_PER_FLAP == 0; -@@ -72,9 +163,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()); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables -+ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms -+ 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()); -+ // Purpur end - Phantoms attracted to crystals and crystals shoot phantoms -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new Phantom.PhantomAttackPlayerTargetGoal()); - } - -@@ -90,7 +189,10 @@ public class Phantom extends FlyingMob implements Enemy { - - private void updatePhantomSizeInfo() { - this.refreshDimensions(); -- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) (6 + this.getPhantomSize())); -+ // Purpur start - Configurable entity base attributes -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomMaxHealth, () -> this.level().purpurConfig.phantomMaxHealthCache, () -> 20.0D)); -+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomAttackDamage, () -> this.level().purpurConfig.phantomAttackDamageCache, () -> (double) 6 + this.getPhantomSize())); -+ // Purpur end - Configurable entity base attributes - } - - public int getPhantomSize() { -@@ -115,6 +217,22 @@ public class Phantom extends FlyingMob implements Enemy { - return true; - } - -+ // Purpur start - Configurable entity base attributes -+ private double getFromCache(java.util.function.Supplier equation, java.util.function.Supplier> cache, java.util.function.Supplier defaultValue) { -+ int size = getPhantomSize(); -+ Double value = cache.get().get(size); -+ if (value == null) { -+ try { -+ value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue(); -+ } catch (javax.script.ScriptException e) { -+ e.printStackTrace(); -+ value = defaultValue.get(); -+ } -+ cache.get().put(size, value); -+ } -+ return value; -+ } -+ // Purpur end - Configurable entity base attributes - @Override - public void tick() { - super.tick(); -@@ -135,11 +253,18 @@ public class Phantom extends FlyingMob implements Enemy { - this.level().addParticle(ParticleTypes.MYCELIUM, this.getX() - (double) f3, this.getY() + (double) f5, this.getZ() - (double) f4, 0.0D, 0.0D, 0.0D); - } - -+ if (level().purpurConfig.phantomFlamesOnSwoop && attackPhase == AttackPhase.SWOOP) shoot(); // Purpur - } - - @Override - public void aiStep() { -- if (this.isAlive() && this.shouldBurnInDay && this.isSunBurnTick()) { // Paper - shouldBurnInDay API -+ // Purpur - implemented in LivingEntity; moved down to shouldBurnInDay() - API for any mob to burn daylight -+ // Purpur start - Phantoms burn in light -+ boolean burnFromDaylight = this.shouldBurnInDay && this.isSunBurnTick() && this.level().purpurConfig.phantomBurnInDaylight; -+ boolean burnFromLightSource = this.level().purpurConfig.phantomBurnInLight > 0 && this.level().getMaxLocalRawBrightness(blockPosition()) >= this.level().purpurConfig.phantomBurnInLight; -+ if (this.isAlive() && (burnFromDaylight || burnFromLightSource)) { // Paper - shouldBurnInDay API -+ // Purpur end - Phantoms burn in light -+ if (getRider() == null || !this.isControllable()) // Purpur - Ridables - this.igniteForSeconds(8.0F); - } - -@@ -149,7 +274,11 @@ public class Phantom extends FlyingMob implements Enemy { - @Override - public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) { - 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); - } - -@@ -165,7 +294,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 - API for any mob to burn daylight - this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); - } - // Paper end -@@ -182,7 +311,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 - API for any mob to burn daylight - // Paper end - } - -@@ -242,8 +371,14 @@ public class Phantom extends FlyingMob implements Enemy { - return this.spawningEntity; - } - 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 - API for any mob to burn daylight -+ // Purpur start - API for any mob to burn daylight -+ 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 - API for any mob to burn daylight - public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } - // Paper end - -@@ -254,7 +389,124 @@ public class Phantom extends FlyingMob implements Enemy { - private AttackPhase() {} - } - -- private class PhantomMoveControl extends MoveControl { -+ // Purpur start - Phantoms attracted to crystals and crystals shoot phantoms -+ 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 - Phantoms attracted to crystals and crystals shoot phantoms -+ private class PhantomMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - Ridables - - private float speed = 0.1F; - -@@ -262,8 +514,19 @@ public class Phantom extends FlyingMob implements Enemy { - super(entity); - } - -+ // Purpur start - Ridables -+ public void purpurTick(Player rider) { -+ if (!Phantom.this.onGround) { -+ // phantom is always in motion when flying -+ // TODO - FIX THIS -+ // rider.setForward(1.0F); -+ } -+ super.purpurTick(rider); -+ } -+ // Purpur end - Ridables -+ - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - if (Phantom.this.horizontalCollision) { - Phantom.this.setYRot(Phantom.this.getYRot() + 180.0F); - this.speed = 0.1F; -@@ -309,14 +572,20 @@ public class Phantom extends FlyingMob implements Enemy { - } - } - -- private static class PhantomLookControl extends LookControl { -+ private static class PhantomLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables - - public PhantomLookControl(Mob entity) { - super(entity); - } - -+ // Purpur start - Ridables -+ public void purpurTick(Player rider) { -+ setYawPitch(rider.getYRot(), -rider.xRotO * 0.75F); -+ } -+ // Purpur end - Ridables -+ - @Override -- public void tick() {} -+ public void vanillaTick() {} // Purpur - Ridables - } - - private class PhantomBodyRotationControl extends BodyRotationControl { -@@ -403,6 +672,12 @@ public class Phantom extends FlyingMob implements Enemy { - return false; - } else if (!entityliving.isAlive()) { - return false; -+ // Purpur start - Phantoms burn in light -+ } 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 - Phantoms burn in light - } else { - if (entityliving instanceof Player) { - Player entityhuman = (Player) entityliving; -@@ -549,6 +824,7 @@ public class Phantom extends FlyingMob implements Enemy { - ServerLevel worldserver = getServerLevel(Phantom.this.level()); - List list = worldserver.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 - Phantoms burn in light - 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 91edf8767541982b8cd1be83c33a7b287ffb62fe..5b98144992c366009a2b2c48e13884ceaf72acfb 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Pillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Pillager.java -@@ -67,16 +67,54 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.pillagerRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.pillagerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.pillagerControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pillagerMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.pillagerScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.pillagerTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.pillagerAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { - super.registerGoals(); - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D)); - this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, 10.0F)); - this.goalSelector.addGoal(3, new RangedCrossbowAttackGoal<>(this, 1.0D, 8.0F)); - this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); - this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 15.0F, 1.0F)); - this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 15.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -index cfc28828a5b81563a826ae6045553e7350f67986..a5134a80a707bc69ae5826eae89e79be5ec3fa41 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java -@@ -75,14 +75,59 @@ public class Ravager extends Raider { - this.setPathfindingMalus(PathType.LEAVES, 0.0F); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.ravagerRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ravagerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.ravagerControllable; -+ } -+ -+ @Override -+ public void onMount(Player rider) { -+ super.onMount(rider); -+ getNavigation().stop(); -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ravagerMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ravagerScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.ravagerTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.ravagerAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { - super.registerGoals(); - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables -+ if (level().purpurConfig.ravagerAvoidRabbits) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.AvoidEntityGoal<>(this, net.minecraft.world.entity.animal.Rabbit.class, 6.0F, 1.0D, 1.2D)); // Purpur - option to make ravagers afraid of rabbits - this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, true)); - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.4D)); - this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); - this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(2, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true)); - this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true, (entityliving, worldserver) -> { -@@ -135,7 +180,7 @@ public class Ravager extends Raider { - @Override - public void aiStep() { - super.aiStep(); -- if (this.isAlive()) { -+ if (this.isAlive() && (getRider() == null || !this.isControllable())) { // Purpur - Ridables - if (this.isImmobile()) { - this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.0D); - } else { -@@ -150,7 +195,7 @@ public class Ravager extends Raider { - if (world instanceof ServerLevel) { - ServerLevel worldserver = (ServerLevel) world; - -- if (this.horizontalCollision && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (this.horizontalCollision && (worldserver.purpurConfig.ravagerBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur - Add mobGriefing bypass to everything affected - 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(); -@@ -160,7 +205,7 @@ public class Ravager extends Raider { - BlockState iblockdata = worldserver.getBlockState(blockposition); - Block block = iblockdata.getBlock(); - -- if (block instanceof LeavesBlock) { -+ if (this.level().purpurConfig.ravagerGriefableBlocks.contains(block)) { // Purpur - Configurable ravager griefable blocks list - // CraftBukkit start - if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state - continue; -diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -index 64d99b8b576212f754bd316343562b1ba7f604fa..78ac42c89cb768e0dfb17197a850a029937c145c 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java -@@ -84,7 +84,7 @@ public class Shulker extends AbstractGolem implements VariantHolder public - Configurable entity base attributes - private float currentPeekAmountO; - private float currentPeekAmount; - @Nullable -@@ -98,12 +98,61 @@ 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) { - Shulker entityshulker = (Shulker) EntityType.SHULKER.create(this.level(), EntitySpawnReason.BREEDING); -+ // Purpur end - - if (entityshulker != null) { - entityshulker.setVariant(this.getVariant()); -@@ -590,7 +648,7 @@ public class Shulker extends AbstractGolem implements VariantHolder variant) { -@@ -601,7 +659,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 +669,7 @@ public class Shulker extends AbstractGolem implements VariantHolder(this, Player.class, true)); - } -@@ -165,12 +205,12 @@ public class Silverfish extends Monster { - - if (block instanceof InfestedBlock) { - // CraftBukkit start -- BlockState afterState = getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state -+ BlockState afterState = (getServerLevel(world).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state // Purpur - Add mobGriefing bypass to everything affected - if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, afterState)) { // Paper - fix wrong block state - continue; - } - // CraftBukkit end -- if (getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (getServerLevel(world).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - Add mobGriefing bypass to everything affected - world.destroyBlock(blockposition1, true, this.silverfish); - } else { - world.setBlock(blockposition1, ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)), 3); -@@ -208,7 +248,7 @@ public class Silverfish extends Monster { - } else { - RandomSource randomsource = this.mob.getRandom(); - -- if (getServerLevel((Entity) this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && randomsource.nextInt(reducedTickDelay(10)) == 0) { -+ if (getServerLevel((Entity) this.mob).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel((Entity) this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && randomsource.nextInt(reducedTickDelay(10)) == 0) { // Purpur - Add mobGriefing bypass to everything affected - 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 3972e2ed0554e2550519e994888e068df0a151e5..f1a9546aaa8499b198228f6a684850f7b20e67c9 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -@@ -17,6 +17,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; -@@ -29,6 +39,41 @@ public class Skeleton extends AbstractSkeleton { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.skeletonRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.skeletonRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.skeletonControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.skeletonMaxHealth); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.skeletonTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.skeletonAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { - super.defineSynchedData(builder); -@@ -147,4 +192,64 @@ 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(), net.minecraft.world.entity.EntitySpawnReason.CONVERSION); -+ 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; -+ } -+ -+ this.level().addFreshEntity(skeleton); -+ this.remove(RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ if (!player.getAbilities().instabuild) { -+ stack.shrink(1); -+ } -+ -+ for (int i = 0; i < 15; ++i) { -+ ((ServerLevel) level()).sendParticlesSource(((ServerLevel) level()).players(), null, ParticleTypes.HAPPY_VILLAGER, -+ false, true, -+ getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1, -+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0); -+ } -+ 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 72346a7e5269c91e3143933ac37e65ad9639b791..d58f7b251d0c322d63e7e5e8ed30b41427a11227 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -65,6 +65,7 @@ public class Slime extends Mob implements Enemy { - public float squish; - public float oSquish; - private boolean wasOnGround; -+ protected boolean actualJump; // Purpur - Ridables - - public Slime(EntityType type, Level world) { - super(type, world); -@@ -72,12 +73,92 @@ public class Slime extends Mob implements Enemy { - this.moveControl = new Slime.SlimeMoveControl(this); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.slimeRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.slimeRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.slimeControllable; -+ } -+ -+ @Override -+ public float getJumpPower() { -+ float height = super.getJumpPower(); -+ return getRider() != null && this.isControllable() && actualJump ? height * 1.5F : height; -+ } -+ -+ @Override -+ public boolean onSpacebar() { -+ if (onGround && getRider() != null && this.isControllable()) { -+ actualJump = true; -+ if (getRider().getForwardMot() == 0 || getRider().getStrafeMot() == 0) { -+ jumpFromGround(); // jump() here if not moving -+ } -+ } -+ return true; // do not jump() in wasd controller, let vanilla controller handle -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ protected String getMaxHealthEquation() { -+ return level().purpurConfig.slimeMaxHealth; -+ } -+ -+ protected String getAttackDamageEquation() { -+ return level().purpurConfig.slimeAttackDamage; -+ } -+ -+ protected java.util.Map getMaxHealthCache() { -+ return level().purpurConfig.slimeMaxHealthCache; -+ } -+ -+ protected java.util.Map getAttackDamageCache() { -+ return level().purpurConfig.slimeAttackDamageCache; -+ } -+ -+ protected double getFromCache(java.util.function.Supplier equation, java.util.function.Supplier> cache, java.util.function.Supplier defaultValue) { -+ int size = getSize(); -+ Double value = cache.get().get(size); -+ if (value == null) { -+ try { -+ value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue(); -+ } catch (javax.script.ScriptException e) { -+ e.printStackTrace(); -+ value = defaultValue.get(); -+ } -+ cache.get().put(size, value); -+ } -+ return value; -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.slimeTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.slimeAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new Slime.SlimeFloatGoal(this)); - this.goalSelector.addGoal(2, new Slime.SlimeAttackGoal(this)); - this.goalSelector.addGoal(3, new Slime.SlimeRandomDirectionGoal(this)); - this.goalSelector.addGoal(5, new Slime.SlimeKeepOnJumpingGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving, worldserver) -> { - return Math.abs(entityliving.getY() - this.getY()) <= 4.0D; - })); -@@ -102,9 +183,9 @@ public class Slime extends Mob implements Enemy { - this.entityData.set(Slime.ID_SIZE, j); - this.reapplyPosition(); - this.refreshDimensions(); -- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double) (j * j)); -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(this::getMaxHealthEquation, this::getMaxHealthCache, () -> (double) size * size)); // Purpur - Configurable entity base attributes - this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue((double) (0.2F + 0.1F * (float) j)); -- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) j); -+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(this::getAttackDamageEquation, this::getAttackDamageCache, () -> (double) j)); // Purpur - Configurable entity base attributes - if (heal) { - this.setHealth(this.getMaxHealth()); - } -@@ -386,6 +467,7 @@ public class Slime extends Mob implements Enemy { - - this.setDeltaMovement(vec3d.x, (double) this.getJumpPower(), vec3d.z); - this.hasImpulse = true; -+ this.actualJump = false; // Purpur - Ridables - } - - @Nullable -@@ -419,7 +501,7 @@ public class Slime extends Mob implements Enemy { - return super.getDefaultDimensions(pose).scale((float) this.getSize()); - } - -- private static class SlimeMoveControl extends MoveControl { -+ private static class SlimeMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables - - private float yRot; - private int jumpDelay; -@@ -438,21 +520,33 @@ public class Slime extends Mob implements Enemy { - } - - public void setWantedMovement(double speed) { -- this.speedModifier = speed; -+ this.setSpeedModifier(speed); // Purpur - Ridables - this.operation = MoveControl.Operation.MOVE_TO; - } - - @Override - public void tick() { -+ // Purpur start - Ridables -+ if (slime.getRider() != null && slime.isControllable()) { -+ purpurTick(slime.getRider()); -+ if (slime.getForwardMot() != 0 || slime.getStrafeMot() != 0) { -+ if (jumpDelay > 10) { -+ jumpDelay = 6; -+ } -+ } else { -+ jumpDelay = 20; -+ } -+ } else { -+ // Purpur end - Ridables - this.mob.setYRot(this.rotlerp(this.mob.getYRot(), this.yRot, 90.0F)); - this.mob.yHeadRot = this.mob.getYRot(); - this.mob.yBodyRot = this.mob.getYRot(); -- if (this.operation != MoveControl.Operation.MOVE_TO) { -+ } if ((slime.getRider() == null || !slime.isControllable()) && this.operation != MoveControl.Operation.MOVE_TO) { // Purpur - Ridables - this.mob.setZza(0.0F); - } else { - this.operation = MoveControl.Operation.WAIT; - if (this.mob.onGround()) { -- this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED))); -+ this.mob.setSpeed((float) (this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur - Ridables - if (this.jumpDelay-- <= 0) { - this.jumpDelay = this.slime.getJumpDelay(); - if (this.isAggressive) { -@@ -469,7 +563,7 @@ public class Slime extends Mob implements Enemy { - this.mob.setSpeed(0.0F); - } - } else { -- this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED))); -+ this.mob.setSpeed((float) (this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur - Ridables - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java -index 91e521414c3ea5722aac7506b7589fbb399e9636..12bae0aa2f1cf3059b9c70f5377649cd9f8257aa 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java -@@ -51,9 +51,46 @@ public class Spider extends Monster { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.spiderRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.spiderRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.spiderControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.spiderMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.spiderScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.spiderTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.spiderAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Armadillo.class, 6.0F, 1.0D, 1.2D, (entityliving) -> { - return !((Armadillo) entityliving).isScared(); - })); -@@ -62,6 +99,7 @@ public class Spider extends Monster { - this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D)); - this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); - this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); - this.targetSelector.addGoal(2, new Spider.SpiderTargetGoal<>(this, Player.class)); - this.targetSelector.addGoal(3, new Spider.SpiderTargetGoal<>(this, IronGolem.class)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Stray.java b/src/main/java/net/minecraft/world/entity/monster/Stray.java -index baaf17107584b253d7e268749849bf5b0d0c88ab..b8b9b4316bebae4564fac4c56b303735b86563fb 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Stray.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Stray.java -@@ -22,6 +22,41 @@ public class Stray extends AbstractSkeleton { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.strayRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.strayRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.strayControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.strayMaxHealth); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.strayTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.strayAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - public static boolean checkStraySpawnRules( - EntityType type, ServerLevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random - ) { -diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java -index 0a9246241985d2d97beb865b7163f1d2198f03b8..df44d9fbb71ff252cd261fc8da6de14383e054de 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Strider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java -@@ -91,12 +91,48 @@ 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(PathType.WATER, -1.0F); -+ if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - Toggle for water sensitive mob damage - this.setPathfindingMalus(PathType.LAVA, 0.0F); - this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F); - this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.striderRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.striderRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.striderControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.striderMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.striderScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.striderBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.striderAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - public static boolean checkStriderSpawnRules(EntityType type, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { - BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); - -@@ -158,6 +194,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - @Override - protected void registerGoals() { - this.goalSelector.addGoal(1, new PanicGoal(this, 1.65D)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); - this.temptGoal = new TemptGoal(this, 1.4D, (itemstack) -> { - return itemstack.is(ItemTags.STRIDER_TEMPT_ITEMS); -@@ -412,7 +449,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - - @Override - public boolean isSensitiveToWater() { -- return true; -+ return this.level().purpurConfig.striderTakeDamageFromWater; // Purpur - Toggle for water sensitive mob damage - } - - @Override -@@ -454,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); -@@ -466,7 +516,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - if (!enuminteractionresult.consumesAction()) { - ItemStack itemstack = player.getItemInHand(hand); - -- return (InteractionResult) (itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : InteractionResult.PASS); -+ return (InteractionResult) (itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : tryRide(player, hand)); // Purpur - Ridables - } else { - if (flag && !this.isSilent()) { - this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.STRIDER_EAT, this.getSoundSource(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java -index 183a33b7d666d652b455baa7e8339e9c4a870a58..ec61bc0d9b934ceed15b721e86200abfea21a923 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Vex.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java -@@ -59,6 +59,69 @@ public class Vex extends Monster implements TraceableEntity { - this.xpReward = 3; - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.vexRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.vexRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.vexControllable; -+ } -+ -+ @Override -+ public double getMaxY() { -+ return level().purpurConfig.vexMaxY; -+ } -+ -+ @Override -+ public void travel(Vec3 vec3) { -+ super.travel(vec3); -+ if (getRider() != null && this.isControllable()) { -+ float speed; -+ if (onGround) { -+ speed = (float) getAttributeValue(Attributes.MOVEMENT_SPEED) * 0.1F; -+ } else { -+ speed = (float) getAttributeValue(Attributes.FLYING_SPEED); -+ } -+ setSpeed(speed); -+ Vec3 mot = getDeltaMovement(); -+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed)); -+ setDeltaMovement(mot.scale(0.9D)); -+ } -+ } -+ -+ @Override -+ public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { -+ return false; // no fall damage please -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vexMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.vexScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.vexTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.vexAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - public boolean isFlapping() { - return this.tickCount % Vex.TICKS_PER_FLAP == 0; -@@ -71,7 +134,7 @@ public class Vex extends Monster implements TraceableEntity { - - @Override - public void tick() { -- this.noPhysics = true; -+ this.noPhysics = getRider() == null || !this.isControllable(); // Purpur - Ridables - super.tick(); - this.noPhysics = false; - this.setNoGravity(true); -@@ -86,17 +149,19 @@ public class Vex extends Monster implements TraceableEntity { - protected void registerGoals() { - super.registerGoals(); - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(4, new Vex.VexChargeAttackGoal()); - this.goalSelector.addGoal(8, new Vex.VexRandomMoveGoal()); - this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); - this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); - this.targetSelector.addGoal(2, new Vex.VexCopyOwnerTargetGoal(this)); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true)); - } - - public static AttributeSupplier.Builder createAttributes() { -- return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D); -+ return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur; - } - - @Override -@@ -228,14 +293,14 @@ public class Vex extends Monster implements TraceableEntity { - this.setDropChance(EquipmentSlot.MAINHAND, 0.0F); - } - -- private class VexMoveControl extends MoveControl { -+ private class VexMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - Ridables - - public VexMoveControl(final Vex entityvex) { - super(entityvex); - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - if (this.operation == MoveControl.Operation.MOVE_TO) { - Vec3 vec3d = new Vec3(this.wantedX - Vex.this.getX(), this.wantedY - Vex.this.getY(), this.wantedZ - Vex.this.getZ()); - double d0 = vec3d.length(); -@@ -244,7 +309,7 @@ public class Vex extends Monster implements TraceableEntity { - this.operation = MoveControl.Operation.WAIT; - Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().scale(0.5D)); - } else { -- Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.speedModifier * 0.05D / d0))); -+ Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.getSpeedModifier() * 0.05D / d0))); // Purpur - Ridables - if (Vex.this.getTarget() == null) { - Vec3 vec3d1 = Vex.this.getDeltaMovement(); - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -index 96b105697c91314148fd1b783501389214b1a3f0..3d1cb875edfe6bf5c9e3f4b7dade7868b7dbfa93 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -@@ -55,15 +55,53 @@ public class Vindicator extends AbstractIllager { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.vindicatorRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.vindicatorRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.vindicatorControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vindicatorMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.vindicatorScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.vindicatorTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.vindicatorAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { - super.registerGoals(); - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0, 1.2)); - this.goalSelector.addGoal(2, new Vindicator.VindicatorBreakDoorGoal(this)); - this.goalSelector.addGoal(3, new AbstractIllager.RaiderOpenDoorGoal(this)); - this.goalSelector.addGoal(4, new Raider.HoldGroundAttackGoal(this, 10.0F)); - this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0, false)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers()); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true)); -@@ -132,6 +170,11 @@ public class Vindicator extends AbstractIllager { - RandomSource randomSource = world.getRandom(); - this.populateDefaultEquipmentSlots(randomSource, difficulty); - this.populateDefaultEquipmentEnchantments(world, randomSource, difficulty); -+ // Purpur start -+ 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 a03fa8a3e648532a7ffaaf523ca87c13e8af4c0a..8e7f4cb8ede8721f05c6c35e9b4d08884254853e 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,42 @@ public class Witch extends Raider implements RangedAttackMob { - super(type, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.witchRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witchRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.witchControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witchMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witchScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.witchTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.witchAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { - super.registerGoals(); -@@ -65,10 +101,12 @@ public class Witch extends Raider implements RangedAttackMob { - }); - this.attackPlayersGoal = new NearestAttackableWitchTargetGoal<>(this, Player.class, 10, true, false, (TargetingConditions.Selector) null); - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 60, 10.0F)); - this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0F)); - this.goalSelector.addGoal(3, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[]{Raider.class})); - this.targetSelector.addGoal(2, this.healRaidersGoal); - this.targetSelector.addGoal(3, this.attackPlayersGoal); -diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -index 557b4e225688416132281e9b1759d46a9b775ff9..351a35bdcb820c9c65aeddfbe0f00486d8057f7c 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -@@ -36,6 +36,42 @@ public class WitherSkeleton extends AbstractSkeleton { - this.setPathfindingMalus(PathType.LAVA, 8.0F); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.witherSkeletonRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witherSkeletonRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.witherSkeletonControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherSkeletonMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witherSkeletonScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.witherSkeletonTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.witherSkeletonAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractPiglin.class, true)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java -index 35b0c5c322864e2f5ae5a412296072f268adcd05..8aec50e7330d16bd3d0bc027c191fa1a4ce4552b 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java -@@ -85,6 +85,42 @@ public class Zoglin extends Monster implements HoglinBase { - this.xpReward = 5; - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.zoglinRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zoglinRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.zoglinControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zoglinMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zoglinScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.zoglinTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.zoglinAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected Brain.Provider brainProvider() { - return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); -@@ -250,6 +286,7 @@ public class Zoglin extends Monster implements HoglinBase { - protected void customServerAiStep(ServerLevel world) { - ProfilerFiller profilerFiller = Profiler.get(); - profilerFiller.push("zoglinBrain"); -+ if (getRider() == null || !this.isControllable()) // Purpur - only use brain if no rider - this.getBrain().tick(world, this); - profilerFiller.pop(); - this.updateActivity(); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index a12461907278cfbfa3b1c0aa74b9f07a31768b8a..867d9ddce630482a7d3fadfe5aab213a0f912ca4 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -99,22 +99,74 @@ public class Zombie extends Monster { - private int inWaterTime; - public int conversionTime; - // private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field // Paper - remove anti tick skipping measures / wall time -- private boolean shouldBurnInDay = true; // Paper - Add more Zombie API -+ //private boolean shouldBurnInDay = true; // Paper - Add more Zombie API // Purpur - implemented in LivingEntity - API for any mob to burn daylight - - 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 - Configurable door breaking difficulty -+ this.setShouldBurnInDay(true); // Purpur - API for any mob to burn daylight - } - - public Zombie(Level world) { - this(EntityType.ZOMBIE, world); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.zombieRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombieRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.zombieControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zombieScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Configurable jockey options -+ public boolean jockeyOnlyBaby() { -+ return level().purpurConfig.zombieJockeyOnlyBaby; -+ } -+ -+ public double jockeyChance() { -+ return level().purpurConfig.zombieJockeyChance; -+ } -+ -+ public boolean jockeyTryExistingChickens() { -+ return level().purpurConfig.zombieJockeyTryExistingChickens; -+ } -+ // Purpur end - Configurable jockey options -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.zombieTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.zombieAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper - Add zombie targets turtle egg config - this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); - this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables - this.addBehaviourGoals(); - } - -@@ -124,7 +176,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 ( this.level().spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Spigot -+ // Purpur start -+ if ( this.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)); - } -@@ -239,32 +303,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()) { -- Item item = itemstack.getItem(); -- -- itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); -- if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { -- this.onEquippedItemBroken(item, EquipmentSlot.HEAD); -- this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); -- } -- } -- -- flag = false; -- } -- -- if (flag) { -- this.igniteForSeconds(8.0F); -- } -- } -- } -- -+ // Purpur - implemented in LivingEntity - API for any mob to burn daylight - super.aiStep(); - } - -@@ -324,6 +363,7 @@ public class Zombie extends Monster { - // CraftBukkit end - } - -+ public boolean shouldBurnInDay() { return this.isSunSensitive(); } // Purpur - for ABI compatibility - API for any mob to burn daylight - public boolean isSunSensitive() { - return this.shouldBurnInDay; // Paper - Add more Zombie API - } -@@ -462,7 +502,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 - Add more Zombie API -+ //nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API // Purpur - implemented in LivingEntity - API for any mob to burn daylight - } - - @Override -@@ -475,7 +515,7 @@ public class Zombie extends Monster { - this.startUnderWaterConversion(nbt.getInt("DrownedConversionTime")); - } - // Paper start - Add more Zombie API -- if (nbt.contains("Paper.ShouldBurnInDay")) { -+ if (false && nbt.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight - this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); - } - // Paper end - Add more Zombie API -@@ -528,19 +568,20 @@ public class Zombie extends Monster { - } - - if (object instanceof Zombie.ZombieGroupData entityzombie_groupdatazombie) { -- 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(), EntitySpawnReason.JOCKEY); - - if (entitychicken1 != null) { -@@ -550,6 +591,7 @@ public class Zombie extends Monster { - this.startRiding(entitychicken1); - world.addFreshEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit - } -+ } // Purpur - } - } - } -@@ -562,11 +604,7 @@ public class Zombie extends Monster { - } - - if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { -- LocalDate localdate = LocalDate.now(); -- int i = localdate.get(ChronoField.DAY_OF_MONTH); -- int j = localdate.get(ChronoField.MONTH_OF_YEAR); -- -- if (j == 10 && i == 31 && randomsource.nextFloat() < 0.25F) { -+ if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(world.getMinecraftWorld()) && this.random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur - this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(randomsource.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN)); - this.armorDropChances[EquipmentSlot.HEAD.getIndex()] = 0.0F; - } -@@ -608,7 +646,7 @@ public class Zombie extends Monster { - } - - protected void randomizeReinforcementsChance() { -- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * 0.10000000149011612D); -+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieSpawnReinforcements); // Purpur - Configurable entity base attributes - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -index 30bce56a70f923b0ec77c8e3f29e435a71c71510..bba3562bf316878e7b8ba6a138889d9583a1b0f6 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -@@ -85,6 +85,62 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - }); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.zombieVillagerRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombieVillagerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.zombieVillagerControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieVillagerMaxHealth); -+ } -+ -+ @Override -+ protected void randomizeReinforcementsChance() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieVillagerSpawnReinforcements); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Configurable jockey options -+ @Override -+ public boolean jockeyOnlyBaby() { -+ return level().purpurConfig.zombieVillagerJockeyOnlyBaby; -+ } -+ -+ @Override -+ public double jockeyChance() { -+ return level().purpurConfig.zombieVillagerJockeyChance; -+ } -+ -+ @Override -+ public boolean jockeyTryExistingChickens() { -+ return level().purpurConfig.zombieVillagerJockeyTryExistingChickens; -+ } -+ // Purpur end - Configurable jockey options -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.zombieVillagerTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.zombieVillagerAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { - super.defineSynchedData(builder); -@@ -177,10 +233,10 @@ 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 - itemstack.consume(1, player); - 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 - Customizeable Zombie Villager curing times - } - - return InteractionResult.SUCCESS_SERVER; -diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -index 03e3cbe73119ca76417d4dd192e1560bdfc373ec..c0e611f3222ffacfbd0683c8c65b778f9012a2ad 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,58 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - this.setPathfindingMalus(PathType.LAVA, 8.0F); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.zombifiedPiglinRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombifiedPiglinRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.zombifiedPiglinControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombifiedPiglinMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zombifiedPiglinScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Configurable jockey options -+ @Override -+ public boolean jockeyOnlyBaby() { -+ return level().purpurConfig.zombifiedPiglinJockeyOnlyBaby; -+ } -+ -+ @Override -+ public double jockeyChance() { -+ return level().purpurConfig.zombifiedPiglinJockeyChance; -+ } -+ -+ @Override -+ public boolean jockeyTryExistingChickens() { -+ return level().purpurConfig.zombifiedPiglinJockeyTryExistingChickens; -+ } -+ // Purpur end - Configurable jockey options -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.zombifiedPiglinTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.zombifiedPiglinAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - public void setPersistentAngerTarget(@Nullable UUID angryAt) { - this.persistentAngerTarget = angryAt; -@@ -110,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; - } - -@@ -165,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); - } - -@@ -245,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 - Configurable entity base attributes - } - - @Nullable -diff --git a/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java b/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java -index 6a7e725edece3043c8523d387e2929d5ba8932cb..1f37384368c26b4bdd69533887a8e9b8456f7096 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java -+++ b/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java -@@ -106,6 +106,36 @@ public class Creaking extends Monster { - return this.getHomePos() != null; - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.creakingRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.creakingRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.creakingControllable; -+ } -+ -+ @Override -+ protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.creakingMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.creakingScale); -+ } -+ // Purpur end - Configurable entity base attributes - @Override - protected BodyRotationControl createBodyControl() { - return new Creaking.CreakingBodyRotationControl(this); -@@ -575,31 +605,31 @@ public class Creaking extends Monster { - return 0.0F; - } - -- private class CreakingLookControl extends LookControl { -+ private class CreakingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables - - public CreakingLookControl(final Creaking creaking) { - super(creaking); - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - if (Creaking.this.canMove()) { -- super.tick(); -+ super.vanillaTick(); // Purpur - Ridables - } - - } - } - -- private class CreakingMoveControl extends MoveControl { -+ private class CreakingMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables - - public CreakingMoveControl(final Creaking creaking) { - super(creaking); - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - Ridables - if (Creaking.this.canMove()) { -- super.tick(); -+ super.vanillaTick(); // Purpur - Ridables - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -index 92270912ef26924f611a1df7cb3d5b485b0a262d..e140d068b08d94c55945b30eab11adbface6fa09 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java -@@ -71,11 +71,53 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { - this.xpReward = 5; - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.hoglinRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.hoglinRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.hoglinControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.hoglinMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.hoglinScale); -+ } -+ // Purpur end - Configurable entity base attributes - @VisibleForTesting - public void setTimeInOverworld(int timeInOverworld) { - this.timeInOverworld = timeInOverworld; - } - -+ // Purpur start - Make entity breeding times configurable -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level().purpurConfig.hoglinBreedingTicks; -+ } -+ // Purpur end - Make entity breeding times configurable -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.hoglinTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.hoglinAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - public boolean canBeLeashed() { - return true; -@@ -138,11 +180,13 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { - return (Brain) super.getBrain(); // CraftBukkit - decompile error - } - -+ private int behaviorTick; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("hoglinBrain"); -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); - gameprofilerfiller.pop(); - HoglinAi.updateActivity(this); -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -index 2121d2a2e1aa1d0f0390cc515317096431f6dcb0..e6cb1f067f9920252e0bba9a493365c23b12cea7 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java -@@ -99,6 +99,42 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento - this.xpReward = 5; - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.piglinRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.piglinRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.piglinControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.piglinMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.piglinScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.piglinTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.piglinAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -@@ -307,11 +343,13 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento - return !this.cannotHunt; - } - -+ private int behaviorTick; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("piglinBrain"); -+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider - this.getBrain().tick(world, this); - gameprofilerfiller.pop(); - PiglinAi.updateActivity(this); -@@ -413,7 +451,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento - - @Override - public boolean wantsToPickUp(ServerLevel world, ItemStack stack) { -- return world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); -+ return (world.purpurConfig.piglinBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); // Purpur - Add mobGriefing bypass to everything affected - } - - protected boolean canReplaceCurrentItem(ItemStack stack) { -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -index e283b1296c1e831376bfe9491cbf02ed4b3fffe4..27a6de70530c2a1cbe2f77a7fb493038121710ea 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java -@@ -605,11 +605,18 @@ public class PiglinAi { - } - - itemstack = (ItemStack) iterator.next(); -- } while (!itemstack.is(ItemTags.PIGLIN_SAFE_ARMOR)); -+ } while (!itemstack.is(ItemTags.PIGLIN_SAFE_ARMOR) && (!entity.level().purpurConfig.piglinIgnoresArmorWithGoldTrim || !isWearingGoldTrim(itemstack.getItem()))); // Purpur - - return true; - } - -+ // Purpur start -+ private static boolean isWearingGoldTrim(Item itemstack) { -+ net.minecraft.world.item.equipment.trim.ArmorTrim armorTrim = itemstack.components().get(net.minecraft.core.component.DataComponents.TRIM); -+ return armorTrim != null && armorTrim.material().is(net.minecraft.world.item.equipment.trim.TrimMaterials.GOLD); -+ } -+ // Purpur end -+ - private static void stopWalking(Piglin piglin) { - piglin.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET); - piglin.getNavigation().stop(); -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java -index 24eaeb93284fe1a573026b85818a93a34fd9e1ec..24e198440d4841daac664dc6c5a8a3dc6825b469 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java -@@ -65,6 +65,42 @@ public class PiglinBrute extends AbstractPiglin { - this.xpReward = 20; - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.piglinBruteRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.piglinBruteRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.piglinBruteControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.piglinBruteMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.piglinBruteScale); -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.piglinBruteTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.piglinBruteAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - public static AttributeSupplier.Builder createAttributes() { - return Monster.createMonsterAttributes() - .add(Attributes.MAX_HEALTH, 50.0) -@@ -117,6 +153,7 @@ public class PiglinBrute extends AbstractPiglin { - protected void customServerAiStep(ServerLevel world) { - ProfilerFiller profilerFiller = Profiler.get(); - profilerFiller.push("piglinBruteBrain"); -+ if (getRider() == null || this.isControllable()) // Purpur - only use brain if no rider - this.getBrain().tick(world, this); - profilerFiller.pop(); - PiglinBruteAi.updateActivity(this); -diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java -index c47ed605f0822effd58df4f875297ed015e1e57e..74011f1ab7e48490109ad93d658bba216eef9e80 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java -+++ b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java -@@ -127,8 +127,32 @@ public class Warden extends Monster implements VibrationSystem { - this.setPathfindingMalus(PathType.LAVA, 8.0F); - this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F); - this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F); -+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.5F); // Purpur - Ridables - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.wardenRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wardenRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.wardenControllable; -+ } -+ -+ @Override -+ protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables -+ } -+ // Purpur end - Ridables -+ - @Override - public Packet getAddEntityPacket(ServerEntity entityTrackerEntry) { - return new ClientboundAddEntityPacket(this, entityTrackerEntry, this.hasPose(Pose.EMERGING) ? 1 : 0); -@@ -275,11 +299,13 @@ public class Warden extends Monster implements VibrationSystem { - - } - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("wardenBrain"); -+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish - this.getBrain().tick(world, this); - gameprofilerfiller.pop(); - super.customServerAiStep(world); -@@ -396,17 +422,14 @@ public class Warden extends Monster implements VibrationSystem { - - @Contract("null->false") - public boolean canTargetEntity(@Nullable Entity entity) { -- boolean flag; -- -+ if (getRider() != null && isControllable()) return false; // Purpur - Ridables - if (entity instanceof LivingEntity entityliving) { - if (this.level() == entity.level() && EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(entity) && !this.isAlliedTo(entity) && entityliving.getType() != EntityType.ARMOR_STAND && entityliving.getType() != EntityType.WARDEN && !entityliving.isInvulnerable() && !entityliving.isDeadOrDying() && this.level().getWorldBorder().isWithinBounds(entityliving.getBoundingBox())) { -- flag = true; -- return flag; -+ return true; // Purpur - wtf - } - } - -- flag = false; -- return flag; -+ return false; // Purpur - wtf - } - - public static void applyDarknessAround(ServerLevel world, Vec3 pos, @Nullable Entity entity, int range) { -diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -index 3c037cabd8331eb96a6523b37abab4e73ab79a02..94ba68e063e63f77f7951d482af7a3586ba4ffdf 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -@@ -48,6 +48,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 - Villagers follow emerald blocks - - // CraftBukkit start - @Override -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 b0236c7bf9441aa84d3795ffed05dd6099f29636..e9cbbcdcefe9acc24cf7972ae356fd590e128f56 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -@@ -27,7 +27,7 @@ public class CatSpawner implements CustomSpawner { - if (this.nextTick > 0) { - return 0; - } else { -- this.nextTick = 1200; -+ this.nextTick = world.purpurConfig.catSpawnDelay; // Purpur - Cat spawning options - Player player = world.getRandomPlayer(); - if (player == null) { - return 0; -@@ -61,8 +61,12 @@ public class CatSpawner implements CustomSpawner { - - private int spawnInVillage(ServerLevel world, BlockPos pos) { - int i = 48; -- if (world.getPoiManager().getCountInRange(entry -> entry.is(PoiTypes.HOME), pos, 48, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { -- List list = world.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(48.0, 8.0, 48.0)); -+ // Purpur start - Cat spawning options -+ int range = world.purpurConfig.catSpawnVillageScanRange; -+ if (range <= 0) return 0; -+ if (world.getPoiManager().getCountInRange(entry -> entry.is(PoiTypes.HOME), pos, range, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { -+ List list = world.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(range, 8.0, range)); -+ // Purpur end - Cat spawning options - if (list.size() < 5) { - return this.spawnCat(pos, world); - } -@@ -73,7 +77,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.0, 8.0, 16.0)); -+ // Purpur start - Cat spawning options -+ int range = world.purpurConfig.catSpawnSwampHutScanRange; -+ if (range <= 0) return 0; -+ List list = world.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(range, 8.0, range)); -+ // Purpur end - Cat spawning options - 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 2d8ba55906c8da16fde850e3412f4a6bda3d56e7..6f691a7b7e7e335cc564577854ea404f9f5e81d7 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,10 @@ 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 - Lobotomize stuck villagers -+ private int notLobotomizedCount = 0; // Purpur - Lobotomize stuck villagers -+ -+ public long nextGolemPanic = -1; // Pufferfish - - public Villager(EntityType entityType, Level world) { - this(entityType, world, VillagerType.PLAINS); -@@ -156,6 +160,98 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - this.setVillagerData(this.getVillagerData().setType(type).setProfession(VillagerProfession.NONE)); - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.villagerRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.villagerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.villagerControllable; -+ } -+ -+ @Override -+ protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); -+ if (level().purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); // Purpur - Villagers follow emerald blocks -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.villagerMaxHealth); -+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.villagerScale); -+ this.getAttribute(Attributes.TEMPT_RANGE).setBaseValue(this.level().purpurConfig.villagerTemptRange); // Purpur - Villagers follow emerald blocks -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Allow leashing villagers -+ @Override -+ public boolean canBeLeashed() { -+ return level().purpurConfig.villagerCanBeLeashed; -+ } -+ // Purpur end - Allow leashing villagers -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.villagerTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.villagerAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience -+ // Purpur start - Lobotomize stuck villagers -+ private boolean checkLobotomized() { -+ int interval = this.level().purpurConfig.villagerLobotomizeCheckInterval; -+ boolean shouldCheckForTradeLocked = this.level().purpurConfig.villagerLobotomizeWaitUntilTradeLocked; -+ 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 = !(shouldCheckForTradeLocked && this.getVillagerXp() == 0) && !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 - Lobotomize stuck villagers - @Override - public Brain getBrain() { - return (Brain) super.getBrain(); // CraftBukkit - decompile error -@@ -190,7 +286,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)); -@@ -217,7 +313,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - } - - public static AttributeSupplier.Builder createAttributes() { -- return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.5D); -+ return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.5D).add(Attributes.TEMPT_RANGE, 10.0D); // Purpur - Villagers follow emerald blocks - } - - public boolean assignProfessionWhenSpawned() { -@@ -245,17 +341,30 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - } - // Spigot End - -+ private int behaviorTick = 0; // Pufferfish - @Override - protected void customServerAiStep(ServerLevel world) { - // Paper start - EAR 2 - this.customServerAiStep(world, false); - } -- protected void customServerAiStep(ServerLevel world, final boolean inactive) { -+ protected void customServerAiStep(ServerLevel world, boolean inactive) { // Purpur - not final - // Paper end - EAR 2 - ProfilerFiller gameprofilerfiller = Profiler.get(); - - gameprofilerfiller.push("villagerBrain"); -- if (!inactive) this.getBrain().tick(world, this); -+ // Purpur start -+ if (this.level().purpurConfig.villagerLobotomizeEnabled) { -+ // treat as inactive if lobotomized -+ inactive = inactive || checkLobotomized(); -+ } else { -+ this.isLobotomized = false; -+ } -+ // Purpur end -+ // Pufferfish start -+ if (!inactive && (getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) { -+ this.getBrain().tick(world, this); // Paper // Purpur - Ridables -+ } -+ // Pufferfish end - gameprofilerfiller.pop(); - if (this.assignProfessionWhenSpawned) { - this.assignProfessionWhenSpawned = false; -@@ -312,7 +421,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - if (!itemstack.is(Items.VILLAGER_SPAWN_EGG) && this.isAlive() && !this.isTrading() && !this.isSleeping()) { - if (this.isBaby()) { - this.setUnhappy(); -- return InteractionResult.SUCCESS; -+ return tryRide(player, hand, InteractionResult.SUCCESS); // Purpur - Ridables - } else { - if (!this.level().isClientSide) { - boolean flag = this.getOffers().isEmpty(); -@@ -326,9 +435,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - } - - if (flag) { -- return InteractionResult.CONSUME; -+ return tryRide(player, hand, InteractionResult.CONSUME); // Purpur - Ridables - } - -+ if (level().purpurConfig.villagerRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur - Ridables -+ if (this.level().purpurConfig.villagerAllowTrading) // Purpur - Add config for villager trading - this.startTrading(player); - } - -@@ -493,7 +604,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 - } - - } -@@ -726,7 +837,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() { -@@ -905,6 +1016,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - - public boolean hasFarmSeeds() { - return this.getInventory().hasAnyMatching((itemstack) -> { -+ // Purpur start -+ if (this.level().purpurConfig.villagerClericsFarmWarts && this.getVillagerData().getProfession() == VillagerProfession.CLERIC) { -+ return itemstack.is(Items.NETHER_WART); -+ } -+ // Purpur end - return itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS); - }); - } -@@ -962,6 +1078,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); -@@ -1026,6 +1143,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 8734ab1bd8299bbf43906d81a349c2a13e0981a7..3ca83269311cbc18c9ef3ce62cff6a2d4dc0a683 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java -+++ b/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java -@@ -31,7 +31,7 @@ public record VillagerProfession( - 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), ImmutableSet.of(Blocks.SOUL_SAND), SoundEvents.VILLAGER_WORK_CLERIC); // Purpur - public static final VillagerProfession FARMER = register( - "farmer", - PoiTypes.FARMER, -diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -index 1e77cce428d9e53142aaa2cf780b7f862d536eca..2dc7afa79126b52be42fc986926d6a63f9994d12 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -@@ -72,6 +72,53 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - //this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader // Paper - move back to MobSpawnerTrader - Vanilla behavior is that only traders spawned by it have this value set. - } - -+ // Purpur - start -+ @Override -+ public boolean isRidable() { -+ return level().purpurConfig.wanderingTraderRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wanderingTraderRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level().purpurConfig.wanderingTraderControllable; -+ } -+ // Purpur end - Ridables -+ -+ // Purpur start - Configurable entity base attributes -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.wanderingTraderMaxHealth); -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.TEMPT_RANGE).setBaseValue(this.level().purpurConfig.wanderingTraderTemptRange); // Purpur - Villagers follow emerald blocks -+ } -+ // Purpur end - Configurable entity base attributes -+ // Purpur start - Villagers follow emerald blocks -+ public static net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder createAttributes() { -+ return Mob.createMobAttributes().add(net.minecraft.world.entity.ai.attributes.Attributes.TEMPT_RANGE, 10.0D); -+ } -+ // Purpur end - Villagers follow emerald blocks -+ // Purpur start - Allow leashing villagers -+ @Override -+ public boolean canBeLeashed() { -+ return level().purpurConfig.wanderingTraderCanBeLeashed; -+ } -+ // Purpur end - Allow leashing villagers -+ // Purpur start - Toggle for water sensitive mob damage -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level().purpurConfig.wanderingTraderTakeDamageFromWater; -+ } -+ // Purpur end - Toggle for water sensitive mob damage -+ // Purpur start - Mobs always drop experience -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level().purpurConfig.wanderingTraderAlwaysDropExp; -+ } -+ // Purpur end - Mobs always drop experience - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); -@@ -79,7 +126,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 this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API -+ return level().purpurConfig.milkClearsBeneficialEffects && this.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)); -@@ -92,6 +139,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 - Villagers follow emerald blocks - 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)); -@@ -120,11 +168,13 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - - if (!this.level().isClientSide) { - if (this.getOffers().isEmpty()) { -- return InteractionResult.CONSUME; -+ return tryRide(player, hand, InteractionResult.CONSUME); // Purpur - Ridables - } -- -+ if (level().purpurConfig.wanderingTraderRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur - Ridables -+ if (this.level().purpurConfig.wanderingTraderAllowTrading) { // Purpur - Add config for villager trading - this.setTradingPlayer(player); - this.openTradingScreen(player, this.getDisplayName(), 1); -+ } // Purpur - Add config for villager trading - } - - return InteractionResult.SUCCESS; -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 a728dcbf956f108f01c966c7531449a506a14a87..4c1378132201c1e5d1bc01f8c0cbba91629bcffa 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -@@ -160,7 +160,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 (spawnplacementtype.isSpawnPositionOk(world, blockposition2, EntityType.WANDERING_TRADER)) { - blockposition1 = blockposition2; -diff --git a/src/main/java/net/minecraft/world/entity/player/Inventory.java b/src/main/java/net/minecraft/world/entity/player/Inventory.java -index 110456deaa662bc1c0f6ba7878bb3074869a4350..58c8e8f06f5cf028b158350327bf42984fcb4d38 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Inventory.java -+++ b/src/main/java/net/minecraft/world/entity/player/Inventory.java -@@ -636,6 +636,8 @@ public class Inventory implements Container, Nameable { - } - - public boolean contains(ItemStack stack) { -+ // Pufferfish start - don't allocate iterators -+ /* - Iterator iterator = this.compartments.iterator(); - - while (iterator.hasNext()) { -@@ -650,6 +652,18 @@ public class Inventory implements Container, Nameable { - } - } - } -+ */ -+ for (int i = 0; i < this.compartments.size(); i++) { -+ List list = this.compartments.get(i); -+ for (int j = 0; j < list.size(); j++) { -+ ItemStack itemstack1 = list.get(j); -+ -+ if (!itemstack1.isEmpty() && ItemStack.isSameItemSameComponents(itemstack1, stack)) { -+ return true; -+ } -+ } -+ } -+ // Pufferfish end - - return false; - } -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 5b8b85a295a08ae495f729c595b3a78778965342..7893d257deef3c1bb0187c6a0b04659716b520f9 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -200,17 +200,40 @@ public abstract class Player extends LivingEntity { - private int currentImpulseContextResetGraceTime; - public boolean affectsSpawning = true; // Paper - Affects Spawning API - public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage -+ public int sixRowEnderchestSlotCount = -1; // Purpur -+ public int burpDelay = 0; // Purpur -+ public boolean canPortalInstant = false; // Purpur - - // CraftBukkit start - public boolean fauxSleeping; - public int oldLevel = -1; - -+ // Purpur start - AFK API -+ public abstract void setAfk(boolean afk); -+ -+ public boolean isAfk() { -+ return false; -+ } -+ // Purpur end - AFK API - @Override - public CraftHumanEntity getBukkitEntity() { - return (CraftHumanEntity) super.getBukkitEntity(); - } - // CraftBukkit end - -+ // Purpur start - Ridables -+ public abstract void resetLastActionTime(); -+ -+ @Override -+ public boolean processClick(InteractionHand hand) { -+ Entity vehicle = getRootVehicle(); -+ if (vehicle != null && vehicle.getRider() == this) { -+ return vehicle.onClick(hand); -+ } -+ return false; -+ } -+ // Purpur end - Ridables -+ - public Player(Level world, BlockPos pos, float yaw, GameProfile gameProfile) { - super(EntityType.PLAYER, world); - this.lastItemInMainHand = ItemStack.EMPTY; -@@ -255,6 +278,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.isPassenger()) { - this.setOnGround(false); -@@ -335,6 +364,17 @@ public abstract class Player extends LivingEntity { - this.turtleHelmetTick(); - } - -+ // Purpur start -+ if (this.level().purpurConfig.playerNetheriteFireResistanceDuration > 0 && this.level().getGameTime() % 20 == 0) { -+ if (this.getItemBySlot(EquipmentSlot.HEAD).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 -+ - this.cooldowns.tick(); - this.updatePlayerPose(); - if (this.currentImpulseContextResetGraceTime > 0) { -@@ -625,7 +665,7 @@ public abstract class Player extends LivingEntity { - while (iterator.hasNext()) { - Entity entity = (Entity) iterator.next(); - -- 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); -@@ -1277,7 +1317,7 @@ public abstract class Player extends LivingEntity { - flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits - if (flag2) { - damagesource = damagesource.critical(true); // Paper start - critical damage API -- f *= 1.5F; -+ f *= this.level().purpurConfig.playerCriticalDamageMultiplier; // Purpur - } - - float f3 = f + f1; -@@ -1643,7 +1683,7 @@ public abstract class Player extends LivingEntity { - } - - @Override -- protected boolean canGlide() { -+ public boolean canGlide() { // Purpur - return !this.abilities.flying && super.canGlide(); - } - -@@ -1903,7 +1943,23 @@ public abstract class Player extends LivingEntity { - - @Override - protected int getBaseExperienceReward(ServerLevel world) { -- return !world.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator() ? Math.min(this.experienceLevel * 7, 100) : 0; -+ // Purpur start -+ if (!world.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator()) { -+ 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); -+ } else { -+ return 0; -+ } -+ // Purpur end - } - - @Override -@@ -1981,6 +2037,13 @@ public abstract class Player extends LivingEntity { - return slot != EquipmentSlot.BODY; - } - -+ // Purpur start -+ @Override -+ public boolean dismountsUnderwater() { -+ return !level().purpurConfig.playerRidableInWater; -+ } -+ // Purpur end -+ - public boolean setEntityOnShoulder(CompoundTag entityNbt) { - if (!this.isPassenger() && this.onGround() && !this.isInWater() && !this.isInPowderSnow) { - if (this.getShoulderEntityLeft().isEmpty()) { -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 f4513b1887f823b088dabe425be042b8fb2bde66..e136738ed53a488ad0aa67a04237ac6243fe712c 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -82,6 +82,7 @@ public abstract class AbstractArrow extends Projectile { - public ItemStack pickupItemStack; - @Nullable - public ItemStack firedFromWeapon; -+ public net.minecraft.world.item.enchantment.ItemEnchantments actualEnchantments = net.minecraft.world.item.enchantment.ItemEnchantments.EMPTY; // Purpur - Add an option to fix MC-3304 projectile looting - - // Spigot Start - @Override -@@ -372,7 +373,7 @@ public abstract class AbstractArrow extends Projectile { - Vec3 vec3d = this.getDeltaMovement(); - - this.setDeltaMovement(vec3d.multiply((double) (this.random.nextFloat() * 0.2F), (double) (this.random.nextFloat() * 0.2F), (double) (this.random.nextFloat() * 0.2F))); -- this.life = 0; -+ if (this.level().purpurConfig.arrowMovementResetsDespawnCounter) this.life = 0; // Purpur - do not reset despawn counter - } - - public boolean isInGround() { -@@ -638,6 +639,12 @@ public abstract class AbstractArrow extends Projectile { - return this.firedFromWeapon; - } - -+ // Purpur start - Add an option to fix MC-3304 projectile looting -+ public void setActualEnchantments(net.minecraft.world.item.enchantment.ItemEnchantments actualEnchantments) { -+ this.actualEnchantments = actualEnchantments; -+ } -+ // Purpur end - Add an option to fix MC-3304 projectile looting -+ - protected SoundEvent getDefaultHitGroundSoundEvent() { - return SoundEvents.ARROW_HIT; - } -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 2f00676f62478897ae4931ea06e047567c407535..59c71183e2c4edae72623f6aa662b807ba2093f2 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java -@@ -23,13 +23,13 @@ public class LargeFireball extends Fireball { - - public LargeFireball(EntityType type, Level world) { - super(type, world); -- this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit -+ this.isIncendiary = (world instanceof ServerLevel worldserver) && (worldserver.purpurConfig.fireballsBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)); // CraftBukkit // Purpur - Add mobGriefing bypass to everything affected - } - - public LargeFireball(Level world, LivingEntity owner, Vec3 velocity, int explosionPower) { - super(EntityType.FIREBALL, owner, velocity, world); - this.explosionPower = explosionPower; -- this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit -+ this.isIncendiary = (world instanceof ServerLevel worldserver) && (worldserver.purpurConfig.fireballsBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)); // CraftBukkit // Purpur - Add mobGriefing bypass to everything affected - } - - @Override -@@ -38,7 +38,7 @@ public class LargeFireball extends Fireball { - Level world = this.level(); - - if (world instanceof ServerLevel worldserver) { -- boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); -+ boolean flag = worldserver.purpurConfig.fireballsBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - Add mobGriefing bypass to everything affected - - // 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 958ea103cc80da7366cc33dc385b76d4f5c809f2..f8ff53488d886bfd67ca3bfe4431b42010052d87 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java -@@ -33,6 +33,12 @@ public class LlamaSpit extends Projectile { - this.setPos(owner.getX() - (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(owner.yBodyRot * 0.017453292F), owner.getEyeY() - 0.10000000149011612D, owner.getZ() + (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(owner.yBodyRot * 0.017453292F)); - } - -+ // Purpur start - Ridables -+ public void super_tick() { -+ super.tick(); -+ } -+ // Purpur end - Ridables -+ - @Override - protected double getDefaultGravity() { - return 0.06D; -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index 9a7b56b653848974e1194eb4f6d40cb99a96ff57..51160e98090c031f2d6394c824a5b1d6c0fbb7f6 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -58,6 +58,36 @@ public abstract class Projectile extends Entity implements TraceableEntity { - super(type, world); - } - -+ // Pufferfish start -+ private static int loadedThisTick = 0; -+ private static int loadedTick; -+ -+ private int loadedLifetime = 0; -+ @Override -+ public void setPos(double x, double y, double z) { -+ int currentTick = net.minecraft.server.MinecraftServer.currentTick; -+ if (loadedTick != currentTick) { -+ loadedTick = currentTick; -+ loadedThisTick = 0; -+ } -+ int previousX = Mth.floor(this.getX()) >> 4, previousZ = Mth.floor(this.getZ()) >> 4; -+ int newX = Mth.floor(x) >> 4, newZ = Mth.floor(z) >> 4; -+ if (previousX != newX || previousZ != newZ) { -+ boolean isLoaded = ((net.minecraft.server.level.ServerChunkCache) this.level().getChunkSource()).getChunkAtIfLoadedImmediately(newX, newZ) != null; -+ if (!isLoaded) { -+ if (Projectile.loadedThisTick > gg.pufferfish.pufferfish.PufferfishConfig.maxProjectileLoadsPerTick) { -+ if (++this.loadedLifetime > gg.pufferfish.pufferfish.PufferfishConfig.maxProjectileLoadsPerProjectile) { -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Purpur - Fix pufferfish issues -+ } -+ return; -+ } -+ Projectile.loadedThisTick++; -+ } -+ } -+ super.setPos(x, y, z); -+ } -+ // Pufferfish end -+ - public void setOwner(@Nullable Entity entity) { - if (entity != null) { - this.ownerUUID = entity.getUUID(); -@@ -468,7 +498,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { - public boolean mayInteract(ServerLevel 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); // Purpur - Add mobGriefing bypass to everything affected - } - - public boolean mayBreak(ServerLevel world) { -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 bb159ea4baf208aab6d6fcfbbddacd5b089b55c8..f1786d17ce8ffd221674c887be01c7907f36f129 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java -@@ -30,7 +30,7 @@ public class SmallFireball extends Fireball { - super(EntityType.SMALL_FIREBALL, owner, velocity, world); - // CraftBukkit start - if (this.getOwner() != null && this.getOwner() instanceof Mob) { -- this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); -+ this.isIncendiary = (world instanceof ServerLevel worldserver) && (worldserver.purpurConfig.fireballsBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)); // Purpur - Add mobGriefing bypass to everything affected - } - // 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 048ca5232d71f07d8ba7d3eaf0236660494c6b35..6fdacf2f6934521a0dd4b25aea35a6a14123da0a 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java -@@ -58,11 +58,41 @@ 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())) { -+ 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))) { -+ 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))) { -+ 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 bd2684528157f928460f2143dd71a48e11983123..0720df603b4f89dd6aa346091b13033ad5d62907 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -@@ -152,10 +152,11 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { - return; - } - // CraftBukkit end -- if (this.random.nextFloat() < 0.05F && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { -+ if (this.random.nextFloat() < worldserver.purpurConfig.enderPearlEndermiteChance && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { // Purpur - Endermite entityendermite = (Endermite) EntityType.ENDERMITE.create(worldserver, EntitySpawnReason.TRIGGERED); - - if (entityendermite != null) { -+ entityendermite.setPlayerSpawned(true); // Purpur - entityendermite.moveTo(entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot()); - worldserver.addFreshEntity(entityendermite, CreatureSpawnEvent.SpawnReason.ENDER_PEARL); - } -@@ -170,7 +171,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { - if (entityplayer1 != null) { - entityplayer1.resetFallDistance(); - entityplayer1.resetCurrentImpulseContext(); -- entityplayer1.hurtServer(entityplayer.serverLevel(), this.damageSources().enderPearl().customEventDamager(this), 5.0F); // CraftBukkit // Paper - fix DamageSource API -+ entityplayer1.hurtServer(entityplayer.serverLevel(), this.damageSources().enderPearl().customEventDamager(this), this.level().purpurConfig.enderPearlDamage); // CraftBukkit // Paper - fix DamageSource API // Purpur - } - - this.playSound(worldserver, vec3d); -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 1e045397763e8233c2e8f9955468788374b80068..3573a6cf0632dfb6859f5a5e7ceffb947e9d3692 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -@@ -70,7 +70,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()) { - Level world = this.level(); - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -index 4c47b30867e30d84908abf93dbefc252bc8c3453..eeb91f7e744d20c1a05212308a23102a347b9c19 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -@@ -103,7 +103,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()) { -@@ -115,6 +115,20 @@ public class WitherSkull extends AbstractHurtingProjectile { - - } - -+ // Purpur start - Ridables -+ @Override -+ public boolean canHitEntity(Entity target) { -+ // do not hit rider -+ return target != this.getRider() && super.canHitEntity(target); -+ } -+ // Purpur end - Ridables -+ // Purpur start - Add canSaveToDisk to Entity -+ @Override -+ public boolean canSaveToDisk() { -+ return false; -+ } -+ // Purpur end - Add canSaveToDisk to Entity -+ - @Override - protected void defineSynchedData(SynchedEntityData.Builder builder) { - builder.define(WitherSkull.DATA_DANGEROUS, false); -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 cee1e4db2312efb4843c4b6dc18f4af10b91d304..65206bc0c3276fda449936cae88cc819a346e299 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raider.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java -@@ -345,7 +345,7 @@ public abstract class Raider extends PatrollingMonster { - } - - private boolean cannotPickUpBanner() { -- if (!getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items -+ if ((!this.mob.level().purpurConfig.pillagerBypassMobGriefing == !getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING)) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items // Purpur - Add mobGriefing bypass to everything affected - if (!this.mob.hasActiveRaid()) { - return true; - } else if (this.mob.getCurrentRaid().isOver()) { -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 439d61d8689fabe940006b9b317a6810175dccfb..6b30941a84054efb5fcccb5d9e6c80d713a23889 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raids.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raids.java -@@ -26,6 +26,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; -@@ -51,6 +52,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()) { -@@ -122,11 +134,13 @@ public class Raids extends SavedData { - */ - - if (!raid.isStarted() || (raid.isInProgress() && raid.getRaidOmenLevel() < raid.getMaxRaidOmenLevel())) { // CraftBukkit - fixed a bug with raid: players could add up Bad Omen level even when the raid had finished -+ 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(net.minecraft.world.effect.MobEffects.RAID_OMEN); - return null; - } -+ if (level.purpurConfig.raidCooldownSeconds != 0) playerCooldowns.put(player.getUUID(), level.purpurConfig.raidCooldownSeconds); // Purpur - - if (!raid.isStarted() && !this.raidMap.containsKey(raid.getId())) { - this.raidMap.put(raid.getId(), raid); -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java -index b87fd952020d88a6612429df73121422bf9ae422..1a4fb057025689a22b3dd05f531f0d8639d7e47b 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java -@@ -499,6 +499,7 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable { - - if (f > 0.0F) { - this.landFriction = f; -+ if (level().purpurConfig.boatEjectPlayersOnLand) ejectPassengers(); // Purpur - return AbstractBoat.Status.ON_LAND; - } else { - return AbstractBoat.Status.IN_AIR; -@@ -929,7 +930,13 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable { - - @Override - public final ItemStack getPickResult() { -- return new ItemStack((ItemLike) this.dropItem.get()); -+ // Purpur start -+ final ItemStack boat = new ItemStack((ItemLike) this.dropItem.get()); -+ if (!this.level().purpurConfig.persistentDroppableEntityDisplayNames) { -+ boat.set(net.minecraft.core.component.DataComponents.CUSTOM_NAME, null); -+ } -+ return boat; -+ // Purpur end - } - - public static enum Status { -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 cdc8606ffe5c75ee19d92e9f86f26b2a502d765e..d0c93f87795d7162bbfb3fdadadb6ae1c5fdaeeb 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -@@ -92,6 +92,10 @@ public abstract class AbstractMinecart extends VehicleEntity { - private double flyingY = 0.95; - private double flyingZ = 0.95; - public Double maxSpeed; -+ // Purpur start - Minecart settings and WASD controls -+ public double storedMaxSpeed; -+ public boolean isNewBehavior; -+ // Purpur end - Minecart settings and WASD controls - // CraftBukkit end - public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API - -@@ -100,8 +104,13 @@ public abstract class AbstractMinecart extends VehicleEntity { - this.blocksBuilding = true; - if (AbstractMinecart.useExperimentalMovement(world)) { - this.behavior = new NewMinecartBehavior(this); -+ this.isNewBehavior = true; // Purpur - Minecart settings and WASD controls - } else { - this.behavior = new OldMinecartBehavior(this); -+ // Purpur start - Minecart settings and WASD controls -+ this.isNewBehavior = false; -+ maxSpeed = storedMaxSpeed = world.purpurConfig.minecartMaxSpeed; -+ // Purpur end - Minecart settings and WASD controls - } - - } -@@ -289,6 +298,14 @@ public abstract class AbstractMinecart extends VehicleEntity { - - @Override - public void tick() { -+ // Purpur start - Minecart settings and WASD controls -+ if (!this.isNewBehavior) { -+ if (storedMaxSpeed != level().purpurConfig.minecartMaxSpeed) { -+ maxSpeed = storedMaxSpeed = level().purpurConfig.minecartMaxSpeed; -+ } -+ } -+ // Purpur end - Minecart settings and WASD controls -+ - // CraftBukkit start - double prevX = this.getX(); - double prevY = this.getY(); -@@ -426,16 +443,63 @@ public abstract class AbstractMinecart extends VehicleEntity { - this.behavior.moveAlongTrack(world); - } - -+ // Purpur start - Minecart settings and WASD controls -+ private Double lastSpeed; -+ -+ public double getControllableSpeed() { -+ BlockState blockState = level().getBlockState(this.blockPosition()); -+ if (!blockState.isSolid()) { -+ blockState = level().getBlockState(this.blockPosition().relative(Direction.DOWN)); -+ } -+ Double speed = level().purpurConfig.minecartControllableBlockSpeeds.get(blockState.getBlock()); -+ if (!blockState.isSolid()) { -+ speed = lastSpeed; -+ } -+ if (speed == null) { -+ speed = level().purpurConfig.minecartControllableBaseSpeed; -+ } -+ return lastSpeed = speed; -+ } -+ // Purpur end - Minecart settings and WASD controls -+ - protected void comeOffTrack(ServerLevel world) { - double d0 = this.getMaxSpeed(world); - Vec3 vec3d = this.getDeltaMovement(); - - this.setDeltaMovement(Mth.clamp(vec3d.x, -d0, d0), vec3d.y, Mth.clamp(vec3d.z, -d0, d0)); -+ -+ // Purpur start - Minecart settings and WASD controls -+ if (level().purpurConfig.minecartControllable && !isInWater() && !isInLava() && !passengers.isEmpty()) { -+ Entity passenger = passengers.get(0); -+ if (passenger instanceof net.minecraft.server.level.ServerPlayer player) { -+ net.minecraft.world.entity.player.Input lastClientInput = player.getLastClientInput(); -+ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F); -+ if (lastClientInput.jump() && this.onGround) { -+ setDeltaMovement(new Vec3(getDeltaMovement().x, level().purpurConfig.minecartControllableHopBoost, getDeltaMovement().z)); -+ } -+ if (forward != 0.0F) { -+ Vector velocity = player.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(getControllableSpeed()); -+ if (forward < 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 - Minecart settings and WASD controls -+ - 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 - Minecart settings and WASD controls - - this.move(MoverType.SELF, this.getDeltaMovement()); - if (!this.onGround()) { -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java b/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java -index f43439b31a14b9db4744512465d81134ebe5b3e1..0b9741dfebd0bb95e8c0e1f55ce18dfb353f693a 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java -@@ -426,7 +426,7 @@ public class NewMinecartBehavior extends MinecartBehavior { - private Vec3 calculateBoostTrackSpeed(Vec3 velocity, BlockPos railPos, BlockState railState) { - if (railState.is(Blocks.POWERED_RAIL) && (Boolean) railState.getValue(PoweredRailBlock.POWERED)) { - if (velocity.length() > 0.01D) { -- return velocity.normalize().scale(velocity.length() + 0.06D); -+ return velocity.normalize().scale(velocity.length() + this.level().purpurConfig.poweredRailBoostModifier); // Purpur - } else { - Vec3 vec3d1 = this.minecart.getRedstoneDirection(railPos); - -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java b/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java -index 04a622f52353ebcc21f41c233f5a0fd67690cf4a..f10ce069ef427df16fd0ce0e60b85c805ca703f0 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java -@@ -310,9 +310,9 @@ public class OldMinecartBehavior extends MinecartBehavior { - vec3d5 = this.getDeltaMovement(); - d17 = vec3d5.horizontalDistance(); - if (d17 > 0.01D) { -- double d19 = 0.06D; -+ double d19 = world.purpurConfig.poweredRailBoostModifier; // Purpur - -- this.setDeltaMovement(vec3d5.add(vec3d5.x / d17 * 0.06D, 0.0D, vec3d5.z / d17 * 0.06D)); -+ this.setDeltaMovement(vec3d5.add(vec3d5.x / d17 * world.purpurConfig.poweredRailBoostModifier, 0.0D, vec3d5.z / d17 * world.purpurConfig.poweredRailBoostModifier)); // Purpur - } else { - Vec3 vec3d6 = this.getDeltaMovement(); - double d20 = vec3d6.x; -diff --git a/src/main/java/net/minecraft/world/food/FoodData.java b/src/main/java/net/minecraft/world/food/FoodData.java -index 6a686be6a69ae890d519a54ca099d4ba14e5b9e1..26309851107fb80712b2dc7c5e6112cedf929003 100644 ---- a/src/main/java/net/minecraft/world/food/FoodData.java -+++ b/src/main/java/net/minecraft/world/food/FoodData.java -@@ -44,6 +44,7 @@ public class FoodData { - org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(entityplayer, foodinfo.nutrition() + oldFoodLevel, itemstack); - - if (!event.isCancelled()) { -+ if (entityplayer.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) entityplayer.burpDelay = entityplayer.level().purpurConfig.playerBurpDelay; // Purpur - this.add(event.getFoodLevel() - oldFoodLevel, foodinfo.saturation()); - } - -@@ -96,7 +97,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.hurtServer(worldserver, player.damageSources().starve(), 1.0F); -+ player.hurtServer(worldserver, player.damageSources().starve(), player.level().purpurConfig.hungerStarvationDamage); // Purpur - Configurable hunger starvation damage - } - - 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 882b72799ae532f4e181214d5756ec024af223e2..9a3f2a95debcf8b94f7deb375922ea09b30aabab 100644 ---- a/src/main/java/net/minecraft/world/food/FoodProperties.java -+++ b/src/main/java/net/minecraft/world/food/FoodProperties.java -@@ -33,7 +33,7 @@ public record FoodProperties(int nutrition, float saturation, boolean canAlwaysE - world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), (SoundEvent) consumable.sound().value(), SoundSource.NEUTRAL, 1.0F, randomsource.triangle(1.0F, 0.4F)); - if (user instanceof Player entityhuman) { - entityhuman.getFoodData().eat(this, stack, (ServerPlayer) entityhuman); // CraftBukkit -- world.playSound((Player) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(randomsource, 0.9F, 1.0F)); -+ //world.playSound((Player) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(randomsource, 0.9F, 1.0F)); // Purpur - moved to Player#tick() - } - - } -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index 4680f77a275d8d2b226018db89a571ac25998dd8..bfc90524bd739ed1d91fe9912e38093b3c28928f 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -80,6 +80,7 @@ public abstract class AbstractContainerMenu { - @Nullable - private ContainerSynchronizer synchronizer; - private boolean suppressRemoteUpdates; -+ @Nullable protected ItemStack activeQuickItem = null; // Purpur - Anvil API - - // 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 1240df9368855f836412b06cf564926a18bfe90d..248e2fb41df3cb1efc64921074a51a02419d8fbb 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java -@@ -114,7 +114,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 - Added the ability to add combustible items -+ if (this.isFuel(itemstack1)) { -+ if (!this.moveItemStackTo(itemstack1, 1, 2, false)) { -+ return ItemStack.EMPTY; -+ } -+ } -+ // Purpur end - Added the ability to add combustible items - } - } 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 286ae002e1711ad9e800b7f2091988d66cd572a7..5959e1df9374bcb889be1726abb83b8c58962d05 100644 ---- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -@@ -25,6 +25,12 @@ import org.slf4j.Logger; - import org.bukkit.craftbukkit.inventory.view.CraftAnvilView; - // CraftBukkit end - -+// Purpur start - Anvil API -+import net.minecraft.network.protocol.game.ClientboundContainerSetDataPacket; -+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; -+import net.minecraft.server.level.ServerPlayer; -+// Purpur end - Anvil API -+ - public class AnvilMenu extends ItemCombinerMenu { - - public static final int INPUT_SLOT = 0; -@@ -55,6 +61,10 @@ public class AnvilMenu extends ItemCombinerMenu { - private CraftAnvilView bukkitEntity; - // CraftBukkit end - public boolean bypassEnchantmentLevelRestriction = false; // Paper - bypass anvil level restrictions -+ // Purpur start - Anvil API -+ public boolean bypassCost = false; -+ public boolean canDoUnsafeEnchants = false; -+ // Purpur end - Anvil API - - public AnvilMenu(int syncId, Inventory inventory) { - this(syncId, inventory, ContainerLevelAccess.NULL); -@@ -82,12 +92,17 @@ public class AnvilMenu extends ItemCombinerMenu { - - @Override - protected boolean mayPickup(Player player, boolean present) { -- return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && present; // CraftBukkit - allow cost 0 like a free item -+ return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && (this.bypassCost || this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST) && present; // CraftBukkit - allow cost 0 like a free item // Purpur - Anvil API - } - - @Override - protected void onTake(Player player, ItemStack stack) { -+ // Purpur start - Anvil API -+ ItemStack itemstack = this.activeQuickItem != null ? this.activeQuickItem : stack; -+ 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 end - Anvil API - if (!player.getAbilities().instabuild) { -+ if (this.bypassCost) ((ServerPlayer) player).lastSentExp = -1; else // Purpur - Anvil API - player.giveExperienceLevels(-this.cost.get()); - } - -@@ -138,6 +153,12 @@ public class AnvilMenu extends ItemCombinerMenu { - - @Override - public void createResult() { -+ // Purpur start - Anvil API -+ this.bypassCost = false; -+ this.canDoUnsafeEnchants = false; -+ if (org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent(getBukkitView()).callEvent(); -+ // Purpur end - Anvil API -+ - ItemStack itemstack = this.inputSlots.getItem(0); - - this.onlyRenaming = false; -@@ -146,7 +167,7 @@ public class AnvilMenu extends ItemCombinerMenu { - long j = 0L; - byte b0 = 0; - -- if (!itemstack.isEmpty() && EnchantmentHelper.canStoreEnchantments(itemstack)) { -+ if (!itemstack.isEmpty() && this.canDoUnsafeEnchants || EnchantmentHelper.canStoreEnchantments(itemstack)) { // Purpur - Anvil API - ItemStack itemstack1 = itemstack.copy(); - ItemStack itemstack2 = this.inputSlots.getItem(1); - ItemEnchantments.Mutable itemenchantments_a = new ItemEnchantments.Mutable(EnchantmentHelper.getEnchantmentsForCrafting(itemstack1)); -@@ -213,7 +234,10 @@ public class AnvilMenu extends ItemCombinerMenu { - - i2 = l1 == i2 ? i2 + 1 : Math.max(i2, l1); - Enchantment enchantment = (Enchantment) holder.value(); -- boolean flag3 = enchantment.canEnchant(itemstack); -+ // Purpur start - Config to allow unsafe enchants -+ boolean flag3 = this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants || enchantment.canEnchant(itemstack); // whether the enchantment can be applied on specific item type -+ boolean flag4 = true; // whether two incompatible enchantments can be applied on a single item -+ // Purpur end - Config to allow unsafe enchants - - if (this.player.getAbilities().instabuild || itemstack.is(Items.ENCHANTED_BOOK)) { - flag3 = true; -@@ -225,16 +249,22 @@ public class AnvilMenu extends ItemCombinerMenu { - Holder holder1 = (Holder) iterator1.next(); - - if (!holder1.equals(holder) && !Enchantment.areCompatible(holder, holder1)) { -- flag3 = false; -+ flag4 = this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants; // Purpur - Anvil API // Purpur - flag3 -> flag4 - Config to allow unsafe enchants -+ // Purpur start - Config to allow unsafe enchants -+ if (!flag4 && org.purpurmc.purpur.PurpurConfig.replaceIncompatibleEnchants) { -+ iterator1.remove(); // replace current enchant with the incompatible one trying to be applied -+ flag4 = true; -+ } -+ // Purpur end - Config to allow unsafe enchants - ++i; - } - } - -- if (!flag3) { -+ if (!flag3 || !flag4) { // Purpur - Config to allow unsafe enchants - flag2 = true; - } else { - flag1 = true; -- if (i2 > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions -+ if (!org.purpurmc.purpur.PurpurConfig.allowHigherEnchantsLevels && i2 > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions // Purpur - Config to allow unsafe enchants - i2 = enchantment.getMaxLevel(); - } - -@@ -264,6 +294,54 @@ public class AnvilMenu extends ItemCombinerMenu { - if (!this.itemName.equals(itemstack.getHoverName().getString())) { - b0 = 1; - i += b0; -+ // 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.set(DataComponents.CUSTOM_NAME, io.papermc.paper.adventure.PaperAdventure.asVanilla(component)); -+ } -+ else -+ // Purpur end - itemstack1.set(DataComponents.CUSTOM_NAME, Component.literal(this.itemName)); - } - } else if (itemstack.has(DataComponents.CUSTOM_NAME)) { -@@ -287,6 +365,12 @@ public class AnvilMenu extends ItemCombinerMenu { - this.onlyRenaming = true; - } - -+ // Purpur start - Anvil API -+ if (this.bypassCost && this.cost.get() >= this.maximumRepairCost) { -+ this.cost.set(this.maximumRepairCost - 1); -+ } -+ // Purpur end - Anvil API -+ - if (this.cost.get() >= this.maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit - itemstack1 = ItemStack.EMPTY; - } -@@ -307,6 +391,13 @@ public class AnvilMenu extends ItemCombinerMenu { - - org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemstack1); // CraftBukkit - this.broadcastChanges(); -+ -+ // Purpur start - Anvil API -+ if ((this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants || org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants) && itemstack1 != ItemStack.EMPTY) { // Purpur - Config to allow unsafe enchants -+ ((ServerPlayer) this.player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), 2, itemstack1)); -+ ((ServerPlayer) this.player).connection.send(new ClientboundContainerSetDataPacket(this.containerId, 0, this.cost.get())); -+ } -+ // Purpur end - Anvil API - } else { - org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), ItemStack.EMPTY); // CraftBukkit - this.cost.set(AnvilMenu.DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item -@@ -315,7 +406,7 @@ public class AnvilMenu extends ItemCombinerMenu { - } - - public static int calculateIncreasedRepairCost(int cost) { -- return (int) Math.min((long) cost * 2L + 1L, 2147483647L); -+ return org.purpurmc.purpur.PurpurConfig.anvilCumulativeCost ? (int) Math.min((long) cost * 2L + 1L, 2147483647L) : 0; // Purpur - Make anvil cumulative cost configurable - } - - public boolean setItemName(String newItemName) { -diff --git a/src/main/java/net/minecraft/world/inventory/ArmorSlot.java b/src/main/java/net/minecraft/world/inventory/ArmorSlot.java -index db7caaa2e77b9b98adac8add3040131c673c036b..262d9b2507d37edf0ed9c0821059e518d1baa9e9 100644 ---- a/src/main/java/net/minecraft/world/inventory/ArmorSlot.java -+++ b/src/main/java/net/minecraft/world/inventory/ArmorSlot.java -@@ -44,7 +44,7 @@ class ArmorSlot extends Slot { - @Override - public boolean mayPickup(Player playerEntity) { - ItemStack itemStack = this.getItem(); -- return (itemStack.isEmpty() || playerEntity.isCreative() || !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE)) -+ return (itemStack.isEmpty() || playerEntity.isCreative() || (!EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE) || playerEntity.level().purpurConfig.playerRemoveBindingWithWeakness && playerEntity.hasEffect(net.minecraft.world.effect.MobEffects.WEAKNESS))) - && super.mayPickup(playerEntity); - } - -diff --git a/src/main/java/net/minecraft/world/inventory/ChestMenu.java b/src/main/java/net/minecraft/world/inventory/ChestMenu.java -index 48a6b6136ac3414ca735f93a14b1a8d76210603c..27321b07cd04814bc1ff720c65770d7755625bb6 100644 ---- a/src/main/java/net/minecraft/world/inventory/ChestMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ChestMenu.java -@@ -66,10 +66,30 @@ public class ChestMenu extends AbstractContainerMenu { - return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, 6); - } - -+ // Purpur start -+ public static ChestMenu oneRow(int syncId, Inventory playerInventory, Container inventory) { -+ return new ChestMenu(MenuType.GENERIC_9x1, syncId, playerInventory, inventory, 1); -+ } -+ -+ public static ChestMenu twoRows(int syncId, Inventory playerInventory, Container inventory) { -+ return new ChestMenu(MenuType.GENERIC_9x2, syncId, playerInventory, inventory, 2); -+ } -+ // Purpur end -+ - public static ChestMenu threeRows(int syncId, Inventory playerInventory, Container inventory) { - return new ChestMenu(MenuType.GENERIC_9x3, syncId, playerInventory, inventory, 3); - } - -+ // Purpur start -+ public static ChestMenu fourRows(int syncId, Inventory playerInventory, Container inventory) { -+ return new ChestMenu(MenuType.GENERIC_9x4, syncId, playerInventory, inventory, 4); -+ } -+ -+ public static ChestMenu fiveRows(int syncId, Inventory playerInventory, Container inventory) { -+ return new ChestMenu(MenuType.GENERIC_9x5, syncId, playerInventory, inventory, 5); -+ } -+ // Purpur end -+ - public static ChestMenu sixRows(int syncId, Inventory playerInventory, Container inventory) { - return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, inventory, 6); - } -diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -index bb9b17a058273ee1519b2abbefba97cad7feb51b..29996864b35711b29629e9071d28493ac27fbde6 100644 ---- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -@@ -41,6 +41,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.EnchantingTableBlockEntity; -+import org.bukkit.craftbukkit.entity.CraftHumanEntity; -+// Purpur end -+ - public class EnchantmentMenu extends AbstractContainerMenu { - - static final ResourceLocation EMPTY_SLOT_LAPIS_LAZULI = ResourceLocation.withDefaultNamespace("container/slot/lapis_lazuli"); -@@ -75,6 +81,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().level().purpurConfig.enchantmentTableLapisPersists) { -+ access.execute((level, pos) -> { -+ BlockEntity blockEntity = level.getBlockEntity(pos); -+ if (blockEntity instanceof EnchantingTableBlockEntity enchantmentTable) { -+ enchantmentTable.setLapis(this.getItem(1).getCount()); -+ } -+ }); -+ } -+ } -+ // Purpur end - }; - this.random = RandomSource.create(); - this.enchantmentSeed = DataSlot.standalone(); -@@ -99,6 +121,16 @@ public class EnchantmentMenu extends AbstractContainerMenu { - return EnchantmentMenu.EMPTY_SLOT_LAPIS_LAZULI; - } - }); -+ // Purpur start -+ access.execute((level, pos) -> { -+ if (level.purpurConfig.enchantmentTableLapisPersists) { -+ BlockEntity blockEntity = level.getBlockEntity(pos); -+ if (blockEntity instanceof EnchantingTableBlockEntity enchantmentTable) { -+ this.getSlot(1).set(new ItemStack(Items.LAPIS_LAZULI, enchantmentTable.getLapis())); -+ } -+ } -+ }); -+ // Purpur end - this.addStandardInventorySlots(playerInventory, 8, 84); - this.addDataSlot(DataSlot.shared(this.costs, 0)); - this.addDataSlot(DataSlot.shared(this.costs, 1)); -@@ -328,6 +360,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 5687f492fc76f699e2a388790ca5380d9b8c8d0a..111da7435f0abb5a57bd2c5fecead2380ac4347a 100644 ---- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -@@ -96,12 +96,14 @@ 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) -> { - if (world instanceof ServerLevel) { - // Paper start - Fire BlockExpEvent on grindstone use - org.bukkit.event.block.BlockExpEvent event = new org.bukkit.event.block.BlockExpEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition), this.getExperienceAmount(world)); - event.callEvent(); -- ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), event.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); -+ org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent grindstoneTakeResultEvent = new org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), event.getExpToDrop()); grindstoneTakeResultEvent.callEvent(); // Purpur -+ ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), grindstoneTakeResultEvent.getExperienceAmount(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Purpur - // Paper end - Fire BlockExpEvent on grindstone use - } - -@@ -135,7 +137,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { - Holder holder = (Holder) entry.getKey(); - int k = entry.getIntValue(); - -- if (!holder.is(EnchantmentTags.CURSE)) { -+ if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value())) { // Purpur - j += ((Enchantment) holder.value()).getMinCost(k); - } - } -@@ -222,7 +224,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { - Entry> entry = (Entry) iterator.next(); - Holder holder = (Holder) entry.getKey(); - -- if (!holder.is(EnchantmentTags.CURSE) || itemenchantments_a.getLevel(holder) == 0) { -+ if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value()) || itemenchantments_a.getLevel(holder) == 0) { // Purpur - itemenchantments_a.upgrade(holder, entry.getIntValue()); - } - } -@@ -230,10 +232,70 @@ public class GrindstoneMenu extends AbstractContainerMenu { - }); - } - -+ // Purpur start -+ private java.util.List> GRINDSTONE_REMOVE_ATTRIBUTES_REMOVAL_LIST = java.util.List.of( -+ // DataComponents.MAX_STACK_SIZE, -+ // DataComponents.DAMAGE, -+ // DataComponents.BLOCK_STATE, -+ DataComponents.CUSTOM_DATA, -+ // DataComponents.MAX_DAMAGE, -+ // DataComponents.UNBREAKABLE, -+ // DataComponents.CUSTOM_NAME, -+ // DataComponents.ITEM_NAME, -+ // DataComponents.LORE, -+ // DataComponents.RARITY, -+ // DataComponents.ENCHANTMENTS, -+ // DataComponents.CAN_PLACE_ON, -+ // DataComponents.CAN_BREAK, -+ DataComponents.ATTRIBUTE_MODIFIERS, -+ DataComponents.CUSTOM_MODEL_DATA, -+ // DataComponents.HIDE_ADDITIONAL_TOOLTIP, -+ // DataComponents.HIDE_TOOLTIP, -+ // DataComponents.REPAIR_COST, -+ // DataComponents.CREATIVE_SLOT_LOCK, -+ // DataComponents.ENCHANTMENT_GLINT_OVERRIDE, -+ // DataComponents.INTANGIBLE_PROJECTILE, -+ // DataComponents.FOOD, -+ // DataComponents.FIRE_RESISTANT, -+ // DataComponents.TOOL, -+ // DataComponents.STORED_ENCHANTMENTS, -+ DataComponents.DYED_COLOR, -+ // DataComponents.MAP_COLOR, -+ // DataComponents.MAP_ID, -+ // DataComponents.MAP_DECORATIONS, -+ // DataComponents.MAP_POST_PROCESSING, -+ // DataComponents.CHARGED_PROJECTILES, -+ // DataComponents.BUNDLE_CONTENTS, -+ // DataComponents.POTION_CONTENTS, -+ DataComponents.SUSPICIOUS_STEW_EFFECTS -+ // DataComponents.WRITABLE_BOOK_CONTENT, -+ // DataComponents.WRITTEN_BOOK_CONTENT, -+ // DataComponents.TRIM, -+ // DataComponents.DEBUG_STICK_STATE, -+ // DataComponents.ENTITY_DATA, -+ // DataComponents.BUCKET_ENTITY_DATA, -+ // DataComponents.BLOCK_ENTITY_DATA, -+ // DataComponents.INSTRUMENT, -+ // DataComponents.OMINOUS_BOTTLE_AMPLIFIER, -+ // DataComponents.RECIPES, -+ // DataComponents.LODESTONE_TRACKER, -+ // DataComponents.FIREWORK_EXPLOSION, -+ // DataComponents.FIREWORKS, -+ // DataComponents.PROFILE, -+ // DataComponents.NOTE_BLOCK_SOUND, -+ // DataComponents.BANNER_PATTERNS, -+ // DataComponents.BASE_COLOR, -+ // DataComponents.POT_DECORATIONS, -+ // DataComponents.CONTAINER, -+ // DataComponents.BEES, -+ // DataComponents.LOCK, -+ // DataComponents.CONTAINER_LOOT, -+ ); -+ // Purpur end - private ItemStack removeNonCursesFrom(ItemStack item) { - ItemEnchantments itemenchantments = EnchantmentHelper.updateEnchantments(item, (itemenchantments_a) -> { - itemenchantments_a.removeIf((holder) -> { -- return !holder.is(EnchantmentTags.CURSE); -+ return !org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value()); // Purpur - }); - }); - -@@ -248,6 +310,23 @@ public class GrindstoneMenu extends AbstractContainerMenu { - } - - item.set(DataComponents.REPAIR_COST, i); -+ -+ // Purpur start -+ net.minecraft.core.component.DataComponentPatch.Builder builder = net.minecraft.core.component.DataComponentPatch.builder(); -+ if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveAttributes) { -+ item.getComponents().forEach(typedDataComponent -> { -+ if (GRINDSTONE_REMOVE_ATTRIBUTES_REMOVAL_LIST.contains(typedDataComponent.type())) { -+ builder.remove(typedDataComponent.type()); -+ } -+ }); -+ } -+ if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveDisplay) { -+ builder.remove(DataComponents.CUSTOM_NAME); -+ builder.remove(DataComponents.LORE); -+ } -+ item.applyComponents(builder.build()); -+ // Purpur end -+ - return item; - } - -@@ -309,7 +388,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/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -index a5d53a656513ae81cc3f9fc506caf6adaba62a8e..ac9df238ef0f3d009f25976b95e0b750e963e952 100644 ---- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -@@ -164,7 +164,9 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { - return ItemStack.EMPTY; - } - -+ this.activeQuickItem = itemstack; // Purpur - Anvil API - slot1.onTake(player, itemstack1); -+ this.activeQuickItem = null; // Purpur - Anvil API - } - - 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 a15d5ff872dbd77f3c3145e0328f3d02e431ff8c..1dcf36d502990d32fc4cd3ea69c3ea334baed69a 100644 ---- a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java -+++ b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java -@@ -31,11 +31,18 @@ public class PlayerEnderChestContainer extends SimpleContainer { - } - - public PlayerEnderChestContainer(Player owner) { -- super(27); -+ super(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? 54 : 27); // Purpur - this.owner = owner; - // CraftBukkit end - } - -+ // Purpur start -+ @Override -+ public int getContainerSize() { -+ return owner.sixRowEnderchestSlotCount < 0 ? super.getContainerSize() : owner.sixRowEnderchestSlotCount; -+ } -+ // Purpur end -+ - public void setActiveChest(EnderChestBlockEntity blockEntity) { - this.activeChest = blockEntity; - } -diff --git a/src/main/java/net/minecraft/world/item/ArmorStandItem.java b/src/main/java/net/minecraft/world/item/ArmorStandItem.java -index cb4baebe22eeab17aed67a5ecc506b932fe2230b..c1a6734cc08de1c9fc413b1fa81a199ac9547ec2 100644 ---- a/src/main/java/net/minecraft/world/item/ArmorStandItem.java -+++ b/src/main/java/net/minecraft/world/item/ArmorStandItem.java -@@ -59,6 +59,14 @@ public class ArmorStandItem extends Item { - return InteractionResult.FAIL; - } - // CraftBukkit end -+ // Purpur start -+ if (!world.purpurConfig.persistentDroppableEntityDisplayNames) { -+ entityarmorstand.setCustomName(null); -+ } -+ if (world.purpurConfig.armorstandSetNameVisible && entityarmorstand.getCustomName() != null) { -+ 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 abff08f2d61014944235ffe2f5494a718a28cc10..dc2c415ab227e1357533079ada4903e9f69d4f55 100644 ---- a/src/main/java/net/minecraft/world/item/AxeItem.java -+++ b/src/main/java/net/minecraft/world/item/AxeItem.java -@@ -62,13 +62,15 @@ public class AxeItem extends DiggerItem { - if (playerHasShieldUseIntent(context)) { - return InteractionResult.PASS; - } else { -- Optional optional = this.evaluateNewBlockState(level, blockPos, player, level.getBlockState(blockPos)); -+ Optional optional = this.evaluateActionable(level, blockPos, player, level.getBlockState(blockPos)); // Purpur - if (optional.isEmpty()) { - return InteractionResult.PASS; - } else { -+ org.purpurmc.purpur.tool.Actionable actionable = optional.get(); // Purpur -+ BlockState state = actionable.into().withPropertiesOf(level.getBlockState(blockPos)); // Purpur - ItemStack itemStack = context.getItemInHand(); - // Paper start - EntityChangeBlockEvent -- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional.get())) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state)) { // Purpur - return InteractionResult.PASS; - } - // Paper end -@@ -76,8 +78,15 @@ public class AxeItem extends DiggerItem { - CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); - } - -- level.setBlock(blockPos, optional.get(), 11); -- level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, optional.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, LivingEntity.getSlotForHand(context.getHand())); - } -@@ -92,22 +101,24 @@ public class AxeItem extends DiggerItem { - return context.getHand().equals(InteractionHand.MAIN_HAND) && player.getOffhandItem().is(Items.SHIELD) && !player.isSecondaryUseActive(); - } - -- private Optional evaluateNewBlockState(Level world, BlockPos pos, @Nullable Player player, BlockState state) { -- Optional optional = this.getStripped(state); -+ private Optional evaluateActionable(Level world, BlockPos pos, @Nullable Player player, BlockState state) { // Purpur -+ Optional optional = Optional.ofNullable(world.purpurConfig.axeStrippables.get(state.getBlock())); // Purpur - if (optional.isPresent()) { -- world.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); -+ world.playSound(STRIPPABLES.containsKey(state.getBlock()) ? player : null, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound - return optional; - } else { -- Optional optional2 = WeatheringCopper.getPrevious(state); -+ Optional optional2 = Optional.ofNullable(world.purpurConfig.axeWeatherables.get(state.getBlock())); // Purpur - if (optional2.isPresent()) { -- world.playSound(player, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); -+ world.playSound(WeatheringCopper.getPrevious(state).isPresent() ? player : null, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound - world.levelEvent(player, 3005, pos, 0); - return optional2; - } else { -- Optional optional3 = Optional.ofNullable(HoneycombItem.WAX_OFF_BY_BLOCK.get().get(state.getBlock())) -- .map(block -> block.withPropertiesOf(state)); -+ // Purpur start -+ Optional optional3 = Optional.ofNullable(world.purpurConfig.axeWaxables.get(state.getBlock())); -+ // .map(block -> block.withPropertiesOf(state)); -+ // Purpur end - if (optional3.isPresent()) { -- world.playSound(player, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); -+ world.playSound(HoneycombItem.WAX_OFF_BY_BLOCK.get().containsKey(state.getBlock()) ? player : null, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound - world.levelEvent(player, 3004, pos, 0); - return optional3; - } else { -diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index 4377fa2400c4320e0023ece230090a2a3b4b2ab6..3d7a09c81b10f7a34d55670b7f2cc50b80550380 100644 ---- a/src/main/java/net/minecraft/world/item/BlockItem.java -+++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -151,7 +151,16 @@ 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.persistentTileEntityLore) { -+ BlockEntity blockEntity1 = world.getBlockEntity(pos); -+ if (blockEntity1 != null) { -+ blockEntity1.setPersistentLore(stack.getOrDefault(DataComponents.LORE, net.minecraft.world.item.component.ItemLore.EMPTY)); -+ } -+ } -+ return handled; -+ // Purpur end - } - - @Nullable -@@ -223,6 +232,7 @@ public class BlockItem extends Item { - } - - if (tileentitytypes1.onlyOpCanSetNbt() && (player == null || !(player.canUseGameMasterBlocks() || (player.getAbilities().instabuild && player.getBukkitEntity().hasPermission("minecraft.nbt.place"))))) { // Spigot - add permission -+ if (!(world.purpurConfig.silkTouchEnabled && tileentity instanceof net.minecraft.world.level.block.entity.SpawnerBlockEntity && player.getBukkitEntity().hasPermission("purpur.drop.spawners"))) - return false; - } - -@@ -273,6 +283,7 @@ public class BlockItem extends Item { - ItemContainerContents itemcontainercontents = (ItemContainerContents) entity.getItem().set(DataComponents.CONTAINER, ItemContainerContents.EMPTY); - - if (itemcontainercontents != null) { -+ if (entity.level().purpurConfig.shulkerBoxItemDropContentsWhenDestroyed && this.getBlock() instanceof ShulkerBoxBlock) // Purpur - ItemUtils.onContainerDestroyed(entity, itemcontainercontents.nonEmptyItemsCopy()); - } - -diff --git a/src/main/java/net/minecraft/world/item/BoatItem.java b/src/main/java/net/minecraft/world/item/BoatItem.java -index e51ffd6c5047ee907a58f3029f0ea7fc66aedfa7..78d41d57df9cb61b295f1f54db1e1d62c13db701 100644 ---- a/src/main/java/net/minecraft/world/item/BoatItem.java -+++ b/src/main/java/net/minecraft/world/item/BoatItem.java -@@ -71,6 +71,11 @@ public class BoatItem extends Item { - return InteractionResult.FAIL; - } else { - abstractboat.setYRot(user.getYRot()); -+ // Purpur start -+ if (!world.purpurConfig.persistentDroppableEntityDisplayNames) { -+ abstractboat.setCustomName(null); -+ } -+ // Purpur end - if (!world.noCollision(abstractboat, abstractboat.getBoundingBox())) { - return InteractionResult.FAIL; - } else { -diff --git a/src/main/java/net/minecraft/world/item/BowItem.java b/src/main/java/net/minecraft/world/item/BowItem.java -index bb593209c95c9cf1f9c5d52d52fab4a33ddbabcf..58fa528e4b2589d362eb976afd6221cd94f2623c 100644 ---- a/src/main/java/net/minecraft/world/item/BowItem.java -+++ b/src/main/java/net/minecraft/world/item/BowItem.java -@@ -28,6 +28,11 @@ public class BowItem extends ProjectileWeaponItem { - return false; - } else { - ItemStack itemStack = player.getProjectile(stack); -+ // Purpur start -+ if (world.purpurConfig.infinityWorksWithoutArrows && itemStack.isEmpty() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.INFINITY, stack) > 0) { -+ itemStack = new ItemStack(Items.ARROW); -+ } -+ // Purpur end - if (itemStack.isEmpty()) { - return false; - } else { -@@ -38,7 +43,7 @@ public class BowItem extends ProjectileWeaponItem { - } else { - List list = draw(stack, itemStack, player); - if (world instanceof ServerLevel serverLevel && !list.isEmpty()) { -- this.shoot(serverLevel, player, player.getUsedItemHand(), stack, list, f * 3.0F, 1.0F, f == 1.0F, null); -+ this.shoot(serverLevel, player, player.getUsedItemHand(), stack, list, f * 3.0F, (float) world.purpurConfig.bowProjectileOffset, f == 1.0F, null); // Purpur - } - - world.playSound( -@@ -89,7 +94,7 @@ public class BowItem extends ProjectileWeaponItem { - public InteractionResult use(Level world, Player user, InteractionHand hand) { - ItemStack itemStack = user.getItemInHand(hand); - boolean bl = !user.getProjectile(itemStack).isEmpty(); -- if (!user.hasInfiniteMaterials() && !bl) { -+ if (!user.hasInfiniteMaterials() && !bl && !(world.purpurConfig.infinityWorksWithoutArrows && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.INFINITY, itemStack) > 0)) { // Purpur - return InteractionResult.FAIL; - } 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 3bddfb6f7412ab86e0c090d0cbc6cf254b3f891c..b9190acbcb256a3072f0a5f1c4c731f1222b459f 100644 ---- a/src/main/java/net/minecraft/world/item/BucketItem.java -+++ b/src/main/java/net/minecraft/world/item/BucketItem.java -@@ -196,7 +196,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { - // CraftBukkit end - if (!flag2) { - 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 - Add allow water in end world option - int i = blockposition.getX(); - int j = blockposition.getY(); - int k = blockposition.getZ(); -@@ -204,7 +204,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).sendParticlesSource(null, ParticleTypes.LARGE_SMOKE, false, true, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 1, 0.0D, 0.0D, 0.0D, 0.0D); // Purpur - Add allow water in end world option - } - - 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 be1902a307a54434644b242b429ad47c271d2a0c..cac4de9877b91bd805a5a8f4b84d27449fc5001f 100644 ---- a/src/main/java/net/minecraft/world/item/CrossbowItem.java -+++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java -@@ -70,7 +70,7 @@ public class CrossbowItem extends ProjectileWeaponItem { - ItemStack itemStack = user.getItemInHand(hand); - ChargedProjectiles chargedProjectiles = itemStack.get(DataComponents.CHARGED_PROJECTILES); - if (chargedProjectiles != null && !chargedProjectiles.isEmpty()) { -- this.performShooting(world, user, hand, itemStack, getShootingPower(chargedProjectiles), 1.0F, null); -+ this.performShooting(world, user, hand, itemStack, getShootingPower(chargedProjectiles), (float) world.purpurConfig.crossbowProjectileOffset, null); // Purpur - return InteractionResult.CONSUME; - } else if (!user.getProjectile(itemStack).isEmpty()) { - this.startSoundPlayed = false; -diff --git a/src/main/java/net/minecraft/world/item/DyeColor.java b/src/main/java/net/minecraft/world/item/DyeColor.java -index 648e6979aa72b9d1e1ea3b40d5a876e3c690b934..d9cd4f47e9990bdd85e30f68ca3b755a953332a1 100644 ---- a/src/main/java/net/minecraft/world/item/DyeColor.java -+++ b/src/main/java/net/minecraft/world/item/DyeColor.java -@@ -123,4 +123,10 @@ public enum DyeColor implements StringRepresentable { - private static CraftingInput makeCraftColorInput(DyeColor first, DyeColor second) { - return CraftingInput.of(2, 1, List.of(new ItemStack(DyeItem.byColor(first)), new ItemStack(DyeItem.byColor(second)))); - } -+ -+ // 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 6d559fef484036194e4d899b82aaa7b5d518311e..9fb04b4e5b61ea497238e042fefa9a06f7489618 100644 ---- a/src/main/java/net/minecraft/world/item/EggItem.java -+++ b/src/main/java/net/minecraft/world/item/EggItem.java -@@ -29,7 +29,7 @@ public class EggItem extends Item implements ProjectileItem { - if (world instanceof ServerLevel worldserver) { - // CraftBukkit start - // Paper start - PlayerLaunchProjectileEvent -- final Projectile.Delayed thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, worldserver, itemstack, user, 0.0F, EggItem.PROJECTILE_SHOOT_POWER, 1.0F); -+ final Projectile.Delayed thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, worldserver, itemstack, user, 0.0F, EggItem.PROJECTILE_SHOOT_POWER, (float) worldserver.purpurConfig.eggProjectileOffset); // Purpur - 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) thrownEgg.projectile().getBukkitEntity()); - if (event.callEvent() && thrownEgg.attemptSpawn()) { - if (event.shouldConsume()) { -diff --git a/src/main/java/net/minecraft/world/item/EndCrystalItem.java b/src/main/java/net/minecraft/world/item/EndCrystalItem.java -index b62db8c7c8c57e43869ee239ebf4b02f112355d9..e75930f26604b772a141a93c6f4b77828abc4503 100644 ---- a/src/main/java/net/minecraft/world/item/EndCrystalItem.java -+++ b/src/main/java/net/minecraft/world/item/EndCrystalItem.java -@@ -27,7 +27,7 @@ public class EndCrystalItem extends Item { - BlockPos blockposition = context.getClickedPos(); - BlockState iblockdata = world.getBlockState(blockposition); - -- if (!iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) { -+ if (!world.purpurConfig.endCrystalPlaceAnywhere && !iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) { - return InteractionResult.FAIL; - } else { - BlockPos blockposition1 = blockposition.above(); final BlockPos aboveBlockPosition = blockposition1; // Paper - OBFHELPER -@@ -57,7 +57,7 @@ public class EndCrystalItem extends Item { - world.gameEvent((Entity) context.getPlayer(), (Holder) GameEvent.ENTITY_PLACE, blockposition1); - EndDragonFight enderdragonbattle = ((ServerLevel) world).getDragonFight(); - -- if (enderdragonbattle != null) { -+ if (enderdragonbattle != null && gg.pufferfish.pufferfish.PufferfishConfig.allowEndCrystalRespawn) { // Pufferfish - enderdragonbattle.tryRespawn(aboveBlockPosition); // Paper - Perf: Do crystal-portal proximity check before entity lookup - } - } -diff --git a/src/main/java/net/minecraft/world/item/EnderpearlItem.java b/src/main/java/net/minecraft/world/item/EnderpearlItem.java -index eaee34054233c8f0296b65a09f1287ba515496f2..392f2600e4fb1ff937c3ec5635156b358eb36888 100644 ---- a/src/main/java/net/minecraft/world/item/EnderpearlItem.java -+++ b/src/main/java/net/minecraft/world/item/EnderpearlItem.java -@@ -26,7 +26,7 @@ public class EnderpearlItem extends Item { - if (world instanceof ServerLevel worldserver) { - // CraftBukkit start - // Paper start - PlayerLaunchProjectileEvent -- final Projectile.Delayed thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, worldserver, itemstack, user, 0.0F, EnderpearlItem.PROJECTILE_SHOOT_POWER, 1.0F); -+ final Projectile.Delayed thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, worldserver, itemstack, user, 0.0F, EnderpearlItem.PROJECTILE_SHOOT_POWER, (float) worldserver.purpurConfig.enderPearlProjectileOffset); // Purpur - 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) thrownEnderpearl.projectile().getBukkitEntity()); - if (event.callEvent() && thrownEnderpearl.attemptSpawn()) { - if (event.shouldConsume()) { -@@ -37,6 +37,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 / (world.getRandom().nextFloat() * 0.4F + 0.8F)); - user.awardStat(Stats.ITEM_USED.get(this)); -+ user.getCooldowns().addCooldown(itemstack, user.getAbilities().instabuild ? world.purpurConfig.enderPearlCooldownCreative : world.purpurConfig.enderPearlCooldown); // Purpur - } else { - // Paper end - PlayerLaunchProjectileEvent - 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 29a048a9b09166838616ac7ba1d31625d56b0bca..184e6d9bf393188fc1f1c7acd545b4ac6d31f6a4 100644 ---- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -+++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -@@ -66,6 +66,18 @@ public class FireworkRocketItem extends Item implements ProjectileItem { - 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) delayed.projectile().getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand)); - if (event.callEvent() && delayed.attemptSpawn()) { - user.awardStat(Stats.ITEM_USED.get(this)); // Moved up from below -+ -+ // Purpur start -+ if (world.purpurConfig.elytraDamagePerFireworkBoost > 0) { -+ List list = net.minecraft.world.entity.EquipmentSlot.VALUES.stream().filter((enumitemslot) -> net.minecraft.world.entity.LivingEntity.canGlideUsing(user.getItemBySlot(enumitemslot), enumitemslot)).toList(); -+ net.minecraft.world.entity.EquipmentSlot enumitemslot = net.minecraft.Util.getRandom(list, user.random); -+ -+ ItemStack glideItem = user.getItemBySlot(enumitemslot); -+ if (user.canGlide()) { -+ glideItem.hurtAndBreak(world.purpurConfig.elytraDamagePerFireworkBoost, user, enumitemslot); -+ } -+ } -+ // Purpur end - if (event.shouldConsume() && !user.hasInfiniteMaterials()) { - itemStack.shrink(1); // Moved up from below - } 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 cdc17ad948d8ac5de62f14b1a561433d33211f32..44a7cee7df2927a923455e8cedaab59307b42506 100644 ---- a/src/main/java/net/minecraft/world/item/HangingEntityItem.java -+++ b/src/main/java/net/minecraft/world/item/HangingEntityItem.java -@@ -75,6 +75,11 @@ public class HangingEntityItem extends Item { - - if (!customdata.isEmpty()) { - EntityType.updateCustomEntityTag(world, entityhuman, (Entity) object, customdata); -+ // Purpur start -+ if (!world.purpurConfig.persistentDroppableEntityDisplayNames) { -+ ((Entity) object).setCustomName(null); -+ } -+ // 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 d2871bb4fd670ae4133d13f290b3256c9177d8e6..0936bdc945f73c7750c20a34276aead2921eeb61 100644 ---- a/src/main/java/net/minecraft/world/item/HoeItem.java -+++ b/src/main/java/net/minecraft/world/item/HoeItem.java -@@ -46,15 +46,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(clickedBlock); -+ 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) { -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index f29415b8dff7d17328e159b56ae4ad46452f018e..b43a7bf5d8f555f97cc851c2c4c01b8a729396f6 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -508,6 +508,7 @@ public final class ItemStack implements DataComponentHolder { - world.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent - for (BlockState blockstate : blocks) { - blockstate.update(true, false); -+ ((CraftBlock) blockstate.getBlock()).getNMS().getBlock().forgetPlacer(); // Purpur - Store placer on Block when placed - } - world.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent - world.preventPoiUpdated = false; -@@ -540,6 +541,7 @@ public final class ItemStack implements DataComponentHolder { - if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically - block.onPlace(world, newblockposition, oldBlock, true, context); - } -+ block.getBlock().forgetPlacer(); // Purpur - Store placer on Block when placed - - world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point - } -@@ -686,6 +688,26 @@ public final class ItemStack implements DataComponentHolder { - return this.isDamageableItem() && this.getDamageValue() > 0; - } - -+ // Purpur start - Add option to mend the most damaged equipment first -+ public float getDamagePercent() { -+ if (this.has(DataComponents.UNBREAKABLE)) { -+ return 0.0F; -+ } -+ -+ final int maxDamage = this.getOrDefault(DataComponents.MAX_DAMAGE, 0); -+ if (maxDamage == 0) { -+ return 0.0F; -+ } -+ -+ final int damage = this.getOrDefault(DataComponents.DAMAGE, 0); -+ if (damage == 0) { -+ return 0.0F; -+ } -+ -+ return (float) damage / maxDamage; -+ } -+ // Purpur end - Add option to mend the most damaged equipment first -+ - public int getDamageValue() { - return Mth.clamp((Integer) this.getOrDefault(DataComponents.DAMAGE, 0), 0, this.getMaxDamage()); - } -@@ -766,6 +788,12 @@ public final class ItemStack implements DataComponentHolder { - org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(serverPlayer, this); // Paper - Add EntityDamageItemEvent - } - // CraftBukkit end -+ // Purpur start -+ if (this.has(DataComponents.GLIDER)) { -+ setDamageValue(this.getMaxDamage() - 1); -+ return; -+ } -+ // Purpur end - - this.shrink(1); - breakCallback.accept(item); -@@ -1343,6 +1371,12 @@ public final class ItemStack implements DataComponentHolder { - return !((ItemEnchantments) this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY)).isEmpty(); - } - -+ // Purpur start - Config to allow unsafe enchants -+ public boolean hasEnchantment(Holder enchantment) { -+ return this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY).getLevel(enchantment) > 0; -+ } -+ // Purpur end - Config to allow unsafe enchants -+ - public ItemEnchantments getEnchantments() { - return (ItemEnchantments) this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); - } -diff --git a/src/main/java/net/minecraft/world/item/Items.java b/src/main/java/net/minecraft/world/item/Items.java -index 6d16b4433e79eca0ff8008941f0b9b807b1db9db..e2f1d3f349e3f82e7a565363c42818e5cf6c4d61 100644 ---- a/src/main/java/net/minecraft/world/item/Items.java -+++ b/src/main/java/net/minecraft/world/item/Items.java -@@ -367,7 +367,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(Blocks.SPAWNER, org.purpurmc.purpur.item.SpawnerItem::new, new Item.Properties().rarity(Rarity.EPIC)); // Purpur - public static final Item CREAKING_HEART = registerBlock(Blocks.CREAKING_HEART); - public static final Item CHEST = registerBlock(Blocks.CHEST, settings -> settings.component(DataComponents.CONTAINER, ItemContainerContents.EMPTY)); - public static final Item CRAFTING_TABLE = registerBlock(Blocks.CRAFTING_TABLE); -@@ -1987,7 +1987,7 @@ public class Items { - "sweet_berries", createBlockItemWithCustomItemName(Blocks.SWEET_BERRY_BUSH), new Item.Properties().food(Foods.SWEET_BERRIES) - ); - public static final Item GLOW_BERRIES = registerItem( -- "glow_berries", createBlockItemWithCustomItemName(Blocks.CAVE_VINES), new Item.Properties().food(Foods.GLOW_BERRIES) -+ "glow_berries", settings -> new org.purpurmc.purpur.item.GlowBerryItem(Blocks.CAVE_VINES, settings.useItemDescriptionPrefix()), new Item.Properties().food(Foods.GLOW_BERRIES) // Purpur - Eating glow berries adds glow effect - ); - public static final Item CAMPFIRE = registerBlock(Blocks.CAMPFIRE, settings -> settings.component(DataComponents.CONTAINER, ItemContainerContents.EMPTY)); - public static final Item SOUL_CAMPFIRE = registerBlock( -diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java -index 8ff50a4c7461bbd9f469d503f6b5ee482d2463d7..5c0a46c11003b6e154195a8ef299416cc73eae33 100644 ---- a/src/main/java/net/minecraft/world/item/MapItem.java -+++ b/src/main/java/net/minecraft/world/item/MapItem.java -@@ -194,6 +194,7 @@ public class MapItem extends Item { - public static void renderBiomePreviewMap(ServerLevel world, ItemStack map) { - MapItemSavedData mapItemSavedData = getSavedData(map, world); - if (mapItemSavedData != null) { -+ mapItemSavedData.isExplorerMap = true; // Purpur - if (world.dimension() == mapItemSavedData.dimension) { - int i = 1 << mapItemSavedData.scale; - int j = mapItemSavedData.centerX; -diff --git a/src/main/java/net/minecraft/world/item/MinecartItem.java b/src/main/java/net/minecraft/world/item/MinecartItem.java -index 7153d9ed12276a0f2d8b8a17c79734aa25ed1fa5..a82faf7f07cd71c8748979a726b1e7857d88e195 100644 ---- a/src/main/java/net/minecraft/world/item/MinecartItem.java -+++ b/src/main/java/net/minecraft/world/item/MinecartItem.java -@@ -35,8 +35,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; // Purpur - Minecart settings and WASD controls -+ if (iblockdata.isSolid()) blockposition = blockposition.relative(context.getClickedFace()); -+ } // else { // Purpur - Minecart settings and WASD controls - ItemStack itemstack = context.getItemInHand(); - RailShape blockpropertytrackposition = iblockdata.getBlock() instanceof BaseRailBlock ? (RailShape) iblockdata.getValue(((BaseRailBlock) iblockdata.getBlock()).getShapeProperty()) : RailShape.NORTH_SOUTH; - double d0 = 0.0D; -@@ -80,6 +81,6 @@ public class MinecartItem extends Item { - itemstack.shrink(1); - return InteractionResult.SUCCESS; - } -- } -+ // } // Purpur - Minecart settings and WASD controls - } - } -diff --git a/src/main/java/net/minecraft/world/item/NameTagItem.java b/src/main/java/net/minecraft/world/item/NameTagItem.java -index df9cdcb9544a171a5a07c65ba0150933fb70d5fc..793bd6392ca3c3792306a20538233e4d7fb69b86 100644 ---- a/src/main/java/net/minecraft/world/item/NameTagItem.java -+++ b/src/main/java/net/minecraft/world/item/NameTagItem.java -@@ -23,6 +23,7 @@ public class NameTagItem extends Item { - if (!event.callEvent()) return InteractionResult.PASS; - LivingEntity newEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); - newEntity.setCustomName(event.getName() != null ? io.papermc.paper.adventure.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() && newEntity instanceof Mob mob) { - // Paper end - Add PlayerNameEntityEvent - mob.setPersistenceRequired(); -diff --git a/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java b/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java -index 78ba170a83f8c026bd110eae494c52577182ed61..c2ae50872cead7202246b9cce4db6e0a81e1cf5f 100644 ---- a/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java -+++ b/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java -@@ -105,6 +105,8 @@ public abstract class ProjectileWeaponItem extends Item { - entityarrow.setCritArrow(true); - } - -+ entityarrow.setActualEnchantments(weaponStack.getEnchantments()); // Purpur - Add an option to fix MC-3304 projectile looting -+ - return entityarrow; - } - -diff --git a/src/main/java/net/minecraft/world/item/ShovelItem.java b/src/main/java/net/minecraft/world/item/ShovelItem.java -index 55c18f182166f4905d623d6f5e909eefd5ed2483..d10c4705cc9e7faabd4a5619e1da107231bdb37e 100644 ---- a/src/main/java/net/minecraft/world/item/ShovelItem.java -+++ b/src/main/java/net/minecraft/world/item/ShovelItem.java -@@ -47,9 +47,12 @@ public class ShovelItem extends DiggerItem { - BlockState blockState2 = FLATTENABLES.get(blockState.getBlock()); - BlockState blockState3 = null; - Runnable afterAction = null; // Paper -- if (blockState2 != null && level.getBlockState(blockPos.above()).isAir()) { -- afterAction = () -> level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper -- blockState3 = blockState2; -+ // Purpur start -+ var flattenable = level.purpurConfig.shovelFlattenables.get(blockState.getBlock()); -+ if (flattenable != null && level.getBlockState(blockPos.above()).isAir()) { -+ afterAction = () -> {if (!FLATTENABLES.containsKey(blockState.getBlock())) level.playSound(null, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F);}; // Paper -+ blockState3 = flattenable.into().defaultBlockState(); -+ // Purpur end - } else if (blockState.getBlock() instanceof CampfireBlock && blockState.getValue(CampfireBlock.LIT)) { - afterAction = () -> { // Paper - if (!level.isClientSide()) { -diff --git a/src/main/java/net/minecraft/world/item/SnowballItem.java b/src/main/java/net/minecraft/world/item/SnowballItem.java -index d3bba4665ae14cc279c0f937831f909f8831b12b..27499f01ef0bc89c4c3f60eb696ca07cc5984809 100644 ---- a/src/main/java/net/minecraft/world/item/SnowballItem.java -+++ b/src/main/java/net/minecraft/world/item/SnowballItem.java -@@ -29,7 +29,7 @@ public class SnowballItem extends Item implements ProjectileItem { - // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.SNOWBALL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); - if (world instanceof ServerLevel worldserver) { - // Paper start - PlayerLaunchProjectileEvent -- final Projectile.Delayed snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, worldserver, itemstack, user, 0.0F, SnowballItem.PROJECTILE_SHOOT_POWER, 1.0F); -+ final Projectile.Delayed snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, worldserver, itemstack, user, 0.0F, SnowballItem.PROJECTILE_SHOOT_POWER, (float) worldserver.purpurConfig.snowballProjectileOffset); // Purpur - 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) snowball.projectile().getBukkitEntity()); - if (event.callEvent() && snowball.attemptSpawn()) { - user.awardStat(Stats.ITEM_USED.get(this)); -diff --git a/src/main/java/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java -index cc7e9b87e919b4ef8cf77cd780c890fd9a9cfa50..a185d098175e504b7bb93d2cff03ca99eabc11eb 100644 ---- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java -+++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java -@@ -68,6 +68,23 @@ public class SpawnEggItem extends Item { - Spawner spawner = (Spawner) tileentity; - - entitytypes = this.getType(world.registryAccess(), itemstack); -+ // Purpur start -+ if (spawner instanceof net.minecraft.world.level.block.entity.SpawnerBlockEntity) { -+ 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()); -+ } else if (spawner instanceof net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity) { -+ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); -+ org.purpurmc.purpur.event.PlayerSetTrialSpawnerTypeWithEggEvent event = new org.purpurmc.purpur.event.PlayerSetTrialSpawnerTypeWithEggEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), bukkitBlock, (org.bukkit.block.TrialSpawner) bukkitBlock.getState(), org.bukkit.entity.EntityType.fromName(entitytypes.getName())); -+ if (!event.callEvent()) { -+ return InteractionResult.FAIL; -+ } -+ entitytypes = EntityType.getFromBukkitType(event.getEntityType()); -+ } -+ // Purpur end - spawner.setEntityId(entitytypes, world.getRandom()); - world.sendBlockUpdated(blockposition, iblockdata, iblockdata, 3); - world.gameEvent((Entity) context.getPlayer(), (Holder) GameEvent.BLOCK_CHANGE, blockposition); -diff --git a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java -index e422881d1ab0f1a5bb2cb741d23089a2e35de2d4..bbd65d35d91d4f3ffabeb355b82f22ddde0f868b 100644 ---- a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java -+++ b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java -@@ -23,7 +23,7 @@ public class ThrowablePotionItem extends PotionItem implements ProjectileItem { - ItemStack itemStack = user.getItemInHand(hand); - if (world instanceof ServerLevel serverLevel) { - // Paper start - PlayerLaunchProjectileEvent -- final Projectile.Delayed thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemStack, user, -20.0F, PROJECTILE_SHOOT_POWER, 1.0F); -+ final Projectile.Delayed thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemStack, user, -20.0F, PROJECTILE_SHOOT_POWER, (float) serverLevel.purpurConfig.throwablePotionProjectileOffset); // Purpur - // Paper start - PlayerLaunchProjectileEvent - 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.projectile().getBukkitEntity()); - if (event.callEvent() && thrownPotion.attemptSpawn()) { -diff --git a/src/main/java/net/minecraft/world/item/TridentItem.java b/src/main/java/net/minecraft/world/item/TridentItem.java -index 2b2af4b7cc2c8be8c3aed30885be26816c021bdc..be32255c9bd90f7de3f8f5a62d84c7dcf59fa722 100644 ---- a/src/main/java/net/minecraft/world/item/TridentItem.java -+++ b/src/main/java/net/minecraft/world/item/TridentItem.java -@@ -89,7 +89,7 @@ public class TridentItem extends Item implements ProjectileItem { - // itemstack.hurtWithoutBreaking(1, entityhuman); // CraftBukkit - moved down - if (f == 0.0F) { - // Paper start - PlayerLaunchProjectileEvent -- Projectile.Delayed tridentDelayed = Projectile.spawnProjectileFromRotationDelayed(ThrownTrident::new, worldserver, stack, entityhuman, 0.0F, 2.5F, 1.0F); -+ Projectile.Delayed tridentDelayed = Projectile.spawnProjectileFromRotationDelayed(ThrownTrident::new, worldserver, stack, entityhuman, 0.0F, 2.5F, (float) worldserver.purpurConfig.tridentProjectileOffset); // Purpur - // Paper start - PlayerLaunchProjectileEvent - 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) tridentDelayed.projectile().getBukkitEntity()); - if (!event.callEvent() || !tridentDelayed.attemptSpawn()) { -@@ -101,6 +101,9 @@ public class TridentItem extends Item implements ProjectileItem { - return false; - } - ThrownTrident entitythrowntrident = tridentDelayed.projectile(); // Paper - PlayerLaunchProjectileEvent -+ -+ entitythrowntrident.setActualEnchantments(stack.getEnchantments()); // Purpur - Add an option to fix MC-3304 projectile looting -+ - if (event.shouldConsume()) stack.hurtWithoutBreaking(1, entityhuman); // Paper - PlayerLaunchProjectileEvent - entitythrowntrident.pickupItemStack = stack.copy(); // SPIGOT-4511 update since damage call moved - // CraftBukkit end -@@ -132,6 +135,18 @@ public class TridentItem extends Item implements ProjectileItem { - f4 *= f / f6; - f5 *= f / f6; - org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerRiptideEvent(entityhuman, stack, f3, f4, f5); // CraftBukkit -+ -+ // Purpur start -+ List list = EquipmentSlot.VALUES.stream().filter((enumitemslot) -> LivingEntity.canGlideUsing(entityhuman.getItemBySlot(enumitemslot), enumitemslot)).toList(); -+ if (!list.isEmpty()) { -+ EquipmentSlot enumitemslot = net.minecraft.Util.getRandom(list, entityhuman.random); -+ ItemStack glideItem = entityhuman.getItemBySlot(enumitemslot); -+ if (glideItem.has(net.minecraft.core.component.DataComponents.GLIDER) && world.purpurConfig.elytraDamagePerTridentBoost > 0) { -+ glideItem.hurtAndBreak(world.purpurConfig.elytraDamagePerTridentBoost, entityhuman, enumitemslot); -+ } -+ } -+ // Purpur end -+ - entityhuman.push((double) f3, (double) f4, (double) f5); - entityhuman.startAutoSpinAttack(20, 8.0F, stack); - if (entityhuman.onGround()) { -diff --git a/src/main/java/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java b/src/main/java/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java -index 0651c2af040e3f248860cfb3c5effce91589380e..d884df481b4bbb978113a4ac7a1feac31cf2f951 100644 ---- a/src/main/java/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java -+++ b/src/main/java/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java -@@ -24,6 +24,12 @@ public record ClearAllStatusEffectsConsumeEffect() implements ConsumeEffect { - @Override - // CraftBukkit start - public boolean apply(Level world, ItemStack itemstack, LivingEntity entityliving, EntityPotionEffectEvent.Cause cause) { -+ // Purpur start -+ net.minecraft.world.effect.MobEffectInstance badOmen = entityliving.getEffect(net.minecraft.world.effect.MobEffects.BAD_OMEN); -+ if (!world.purpurConfig.milkCuresBadOmen && itemstack.is(net.minecraft.world.item.Items.MILK_BUCKET) && badOmen != null) { -+ return entityliving.removeAllEffects(cause) && entityliving.addEffect(badOmen); -+ } -+ // Purpur end - return entityliving.removeAllEffects(cause); - // CraftBukkit end - } -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 0b0054b3d5d56ba24e1aee0e3ab56ea5b01a82a8..2e3a834643d56543418e9b9beb9d3448bf059d22 100644 ---- a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java -+++ b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java -@@ -45,6 +45,7 @@ public final class Ingredient implements StackedContents.IngredientInfo itemStacks; // Paper - Improve exact choice recipe ingredients -+ public Predicate predicate; // Purpur - - public boolean isExact() { - return this.itemStacks != null; -@@ -100,6 +101,11 @@ public final class Ingredient implements StackedContents.IngredientInfo ingredients; - @Nullable - private PlacementInfo placementInfo; -+ private final boolean isBukkit; // Pufferfish - -+ // Pufferfish start - public ShapelessRecipe(String group, CraftingBookCategory category, ItemStack result, List ingredients) { -+ this(group, category, result, ingredients, false); -+ } -+ public ShapelessRecipe(String group, CraftingBookCategory category, ItemStack result, List ingredients, boolean isBukkit) { this.isBukkit = isBukkit; // Pufferfish end - this.group = group; - this.category = category; - this.result = result; -@@ -80,6 +85,27 @@ public class ShapelessRecipe implements CraftingRecipe { - } - - public boolean matches(CraftingInput input, Level world) { -+ // Pufferfish start -+ if (!this.isBukkit) { -+ java.util.List ingredients = com.google.common.collect.Lists.newArrayList(this.ingredients.toArray(new Ingredient[0])); -+ -+ inventory: for (int index = 0; index < input.size(); index++) { -+ ItemStack itemStack = input.getItem(index); -+ -+ if (!itemStack.isEmpty()) { -+ for (int i = 0; i < ingredients.size(); i++) { -+ if (ingredients.get(i).test(itemStack)) { -+ ingredients.remove(i); -+ continue inventory; -+ } -+ } -+ return false; -+ } -+ } -+ -+ return ingredients.isEmpty(); -+ } -+ // Pufferfish end - // Paper start - Improve exact choice recipe ingredients & unwrap ternary - if (input.ingredientCount() != this.ingredients.size()) { - return false; -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 4dd074d04c9a535f6cf24420058fd68594c59edc..2c1799e8c9009a6ab24c24e7363d5a87e41f0c35 100644 ---- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java -+++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java -@@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; - import java.util.ArrayList; - import java.util.Collection; - import java.util.List; -+import java.util.Map; - import java.util.Optional; - import java.util.function.BiConsumer; - import java.util.function.Consumer; -@@ -578,4 +579,58 @@ public class EnchantmentHelper { - interface EnchantmentVisitor { - void accept(Holder enchantment, int level); - } -+ -+ // Purpur start - Enchantment convenience methods -+ public static Holder.Reference getEnchantmentHolder(ResourceKey enchantment) { -+ return net.minecraft.server.MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT).getOrThrow(enchantment); -+ } -+ -+ public static int getItemEnchantmentLevel(ResourceKey enchantment, ItemStack stack) { -+ return getItemEnchantmentLevel(getEnchantmentHolder(enchantment), stack); -+ } -+ // Purpur end - Enchantment convenience methods -+ -+ // Purpur start - Add option to mend the most damaged equipment first -+ public static Optional getMostDamagedItemWith(DataComponentType componentType, LivingEntity entity) { -+ ItemStack maxStack = null; -+ EquipmentSlot maxSlot = null; -+ float maxPercent = 0.0F; -+ -+ equipmentSlotLoop: -+ for (EquipmentSlot equipmentSlot : EquipmentSlot.values()) { -+ ItemStack stack = entity.getItemBySlot(equipmentSlot); -+ -+ // do not even check enchantments for item with lower or equal damage percent -+ float percent = stack.getDamagePercent(); -+ if (percent <= maxPercent) { -+ continue; -+ } -+ -+ ItemEnchantments itemEnchantments = stack.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); -+ -+ for (Entry> entry : itemEnchantments.entrySet()) { -+ Enchantment enchantment = entry.getKey().value(); -+ -+ net.minecraft.core.component.DataComponentMap effects = enchantment.effects(); -+ if (!effects.has(componentType)) { -+ // try with another enchantment -+ continue; -+ } -+ -+ if (enchantment.matchingSlot(equipmentSlot)) { -+ maxStack = stack; -+ maxSlot = equipmentSlot; -+ maxPercent = percent; -+ -+ // check another slot now -+ continue equipmentSlotLoop; -+ } -+ } -+ } -+ -+ return maxStack != null -+ ? Optional.of(new EnchantedItemInUse(maxStack, maxSlot, entity)) -+ : Optional.empty(); -+ } -+ // Purpur end - Add option to mend the most damaged equipment first - } -diff --git a/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java b/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java -index cfc6a657cae92c68868a76c1b7b1febe2a16e9f4..a12c08da793139e39dc11c213c94796b83bd8240 100644 ---- a/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java -+++ b/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java -@@ -35,7 +35,7 @@ public class ItemEnchantments implements TooltipProvider { - private static final java.util.Comparator> ENCHANTMENT_ORDER = java.util.Comparator.comparing(Holder::getRegisteredName); - public static final ItemEnchantments EMPTY = new ItemEnchantments(new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), true); - // Paper end -- private static final Codec LEVEL_CODEC = Codec.intRange(1, 255); -+ private static final Codec LEVEL_CODEC = Codec.intRange(1, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)); // Purpur - private static final Codec>> LEVELS_CODEC = Codec.unboundedMap( - Enchantment.CODEC, LEVEL_CODEC - )// Paper start - sort enchantments -@@ -69,7 +69,7 @@ public class ItemEnchantments implements TooltipProvider { - - for (Entry> entry : enchantments.object2IntEntrySet()) { - int i = entry.getIntValue(); -- if (i < 0 || i > 255) { -+ if (i < 0 || i > (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)) { // Purpur - throw new IllegalArgumentException("Enchantment " + entry.getKey() + " has invalid level " + i); - } - } -@@ -164,13 +164,13 @@ public class ItemEnchantments implements TooltipProvider { - if (level <= 0) { - this.enchantments.removeInt(enchantment); - } else { -- this.enchantments.put(enchantment, Math.min(level, 255)); -+ this.enchantments.put(enchantment, Math.min(level, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767))); // Purpur - } - } - - public void upgrade(Holder enchantment, int level) { - if (level > 0) { -- this.enchantments.merge(enchantment, Math.min(level, 255), Integer::max); -+ this.enchantments.merge(enchantment, Math.min(level, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)), Integer::max); // Purpur - } - } - -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 0efc8d997b34302c3e0a5d7ec73a11a940dbeefe..af157881d440b34cfe79fbc9b03cc9ef28515eb8 100644 ---- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -+++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -@@ -131,7 +131,12 @@ public class MerchantOffer { - } - - public void updateDemand() { -- this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962 -+ // Purpur start -+ this.updateDemand(0); -+ } -+ public void updateDemand(int minimumDemand) { -+ this.demand = Math.max(minimumDemand, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962 -+ // 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 7de66aa435dd36899b80f4ecc64480680e474d94..79a8e5dd1d189c4eaf93999925ea0790eb6ce368 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -59,6 +59,7 @@ public abstract class BaseSpawner { - } - - public boolean isNearPlayer(Level world, BlockPos pos) { -+ if (world.purpurConfig.spawnerDeactivateByRedstone && world.hasNeighborSignal(pos)) return false; // Purpur - Redstone deactivates spawners - 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 5d7a6e4b73f032db356e7ec369b150013e940ee6..e164833de0c29eed9025dd4af3f2bb74c92d2250 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -184,7 +184,7 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst - - default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) { - for (Player player : this.players()) { -- if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) { -+ if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player) && EntitySelector.notAfk.test(player)) { // Purpur - AFK API - double d = player.distanceToSqr(x, y, z); - if (range < 0.0 || d < range * range) { - return true; -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 27f9d167b5ae9ce5117798ea44324107df59425f..72c9b9d8f520aa29106dd28a48dcdaf9bf8ea9e6 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -175,6 +175,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - // Paper end - add paper world config - - public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray -+ public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files - public static BlockPos lastPhysicsProblem; // Spigot - private org.spigotmc.TickLimiter entityLimiter; - private org.spigotmc.TickLimiter tileLimiter; -@@ -182,6 +183,49 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions - public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here - -+ // 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; - } -@@ -843,6 +887,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - // Paper end - getblock optimisations - cache world height/sections - this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot - this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config -+ this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur - Purpur config files -+ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur - this.generator = gen; - this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); - -@@ -1488,16 +1534,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - public void guardEntityTick(Consumer tickConsumer, T entity) { - try { - tickConsumer.accept(entity); -- } catch (Throwable throwable) { -+ } catch (Throwable throwable) { // Pufferfish - diff on change ServerLevel.tick - if (throwable instanceof ThreadDeath) throw throwable; // Paper - // Paper start - Prevent block entity and entity crashes - final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); - MinecraftServer.LOGGER.error(msg, throwable); - getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent -- entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Pufferfish - diff on change ServerLevel.tick - // Paper end - Prevent block entity and entity crashes - } -- this.moonrise$midTickTasks(); // Paper - rewrite chunk system -+ this.moonrise$midTickTasks(); // Paper - rewrite chunk system // Pufferfish - diff on change ServerLevel.tick - } - // Paper start - Option to prevent armor stands from doing entity lookups - @Override -@@ -2045,4 +2091,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - return this.id; - } - } -+ -+ // Purpur start - Add allow water in end world option -+ 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 - Add allow water in end world option - } -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index c1b76a1ebc1eea7ab70cf61d8175a31794dd122a..dc15c15951e4ca30b8341d24f813259a77f41c77 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -280,7 +280,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 = world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); -+ Player entityhuman = world.getNearestPlayer(d0, (double) i, d1, -1.0D, world.purpurConfig.mobSpawningIgnoreCreativePlayers); // Purpur - - if (entityhuman != null) { - double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); -diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java -index 685ccfb73bf7125585ef90b6a0f51b2f81daa428..4c7e4683c53afb0800b7f17c5964ba8ff31848d1 100644 ---- a/src/main/java/net/minecraft/world/level/ServerExplosion.java -+++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java -@@ -311,7 +311,7 @@ public class ServerExplosion implements Explosion { - public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) { - 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 - Config to remove explosion radius clamp - this.center = pos; - this.fire = createFire; - this.blockInteraction = destructionType; -@@ -666,10 +666,27 @@ public class ServerExplosion implements Explosion { - - public void explode() { - // CraftBukkit start -- if (this.radius < 0.1F) { -+ if ((this.level == null || this.level.purpurConfig.explosionClampRadius) && this.radius < 0.1F) { // Purpur - Config to remove explosion radius clamp - return; - } - // CraftBukkit end -+ // Purpur start - add PreExplodeEvents -+ if (this.source != null) { -+ Location location = new Location(this.level.getWorld(), this.center.x, this.center.y, this.center.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, org.bukkit.craftbukkit.CraftExplosionResult.toBukkit(getBlockInteraction())).callEvent()) { -+ this.wasCanceled = true; -+ return; -+ } -+ } else { -+ Location location = new Location(this.level.getWorld(), this.center.x, this.center.y, this.center.z); -+ org.bukkit.block.Block block = location.getBlock(); -+ org.bukkit.block.BlockState blockState = (this.damageSource.getDirectBlockState() != null) ? this.damageSource.getDirectBlockState() : block.getState(); -+ if(!new org.purpurmc.purpur.event.PreBlockExplodeEvent(location.getBlock(), this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F, blockState, org.bukkit.craftbukkit.CraftExplosionResult.toBukkit(getBlockInteraction())).callEvent()) { -+ this.wasCanceled = true; -+ return; -+ } -+ } -+ // Purpur end - // Paper start - collision optimisations - this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); - this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; -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 50c907c962f936d2035bb7550750cdbd220b29c2..f9a2d2d4f798efa0d691996ec5ff7fe00260b36c 100644 ---- a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java -@@ -59,6 +59,53 @@ public class AnvilBlock extends FallingBlock { - return this.defaultBlockState().setValue(FACING, ctx.getHorizontalDirection().getClockWise()); - } - -+ // Purpur start - repairable/damageable anvils -+ @Override -+ protected net.minecraft.world.InteractionResult useItemOn(final net.minecraft.world.item.ItemStack stack, final BlockState state, final Level world, final BlockPos pos, final Player player, final net.minecraft.world.InteractionHand hand, final BlockHitResult hit) { -+ if (world.purpurConfig.anvilRepairIngotsAmount > 0 && stack.is(net.minecraft.world.item.Items.IRON_INGOT)) { -+ if (stack.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 net.minecraft.world.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 net.minecraft.world.InteractionResult.CONSUME; -+ } -+ if (!player.getAbilities().instabuild) { -+ stack.shrink(world.purpurConfig.anvilRepairIngotsAmount); -+ } -+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_PLACE, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); -+ return net.minecraft.world.InteractionResult.CONSUME; -+ } -+ if (world.purpurConfig.anvilDamageObsidianAmount > 0 && stack.is(net.minecraft.world.item.Items.OBSIDIAN)) { -+ if (stack.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 net.minecraft.world.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) { -+ stack.shrink(world.purpurConfig.anvilDamageObsidianAmount); -+ } -+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_LAND, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); -+ return net.minecraft.world.InteractionResult.CONSUME; -+ } -+ return net.minecraft.world.InteractionResult.TRY_WITH_EMPTY_HAND; -+ } -+ // Purpur end -+ - @Override - protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { - if (!world.isClientSide) { -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 affbbf6abc6bc09ecb652c1dee92aa297458bc39..c58e07f2a99e3cbb5bd5d3693c006919e0710b7a 100644 ---- a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -@@ -50,6 +50,20 @@ public class AzaleaBlock extends BushBlock implements BonemealableBlock { - - @Override - public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { -+ // Purpur start - Chance for azalea blocks to grow into trees naturally -+ 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 - Chance for azalea blocks to grow into trees naturally - TreeGrower.AZALEA.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 d7ca7a43d2d5f8cad416156fd40588cdd6634f52..920ad0a4ecc83da82c8382a48883cefb7a4b9d11 100644 ---- a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java -@@ -39,6 +39,7 @@ public abstract class BaseCoralPlantTypeBlock extends Block implements SimpleWat - } - - protected static boolean scanForWater(BlockState state, BlockGetter world, BlockPos pos) { -+ if (!((net.minecraft.world.level.LevelAccessor) world).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur - Config to not let coral die - 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 be700995c3e7f63e0471712c3cd6fd83db583491..54836f2a1e4cc9046ba29fb71ea237b358c9cb7d 100644 ---- a/src/main/java/net/minecraft/world/level/block/BedBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java -@@ -106,7 +106,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - - Vec3 vec3d = pos.getCenter(); - -- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); -+ if (world.purpurConfig.bedExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // Purpur - return InteractionResult.SUCCESS_SERVER; - } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) { - if (!BedBlock.canSetSpawn(world)) return this.explodeBed(state, world, pos); // Paper - check explode first -@@ -159,7 +159,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - - Vec3 vec3d = blockposition.getCenter(); - -- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state -+ if (world.purpurConfig.bedExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), (ExplosionDamageCalculator) null, vec3d, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // CraftBukkit - add state // Purpur - return InteractionResult.SUCCESS; - } - } -@@ -183,7 +183,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 9e3f1441d62128535112621bf259c24f1a90595b..2535e6d71b690f8dfde41a7d9cb76b6f010f5aa7 100644 ---- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -@@ -246,7 +246,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 c0b1f903962b25d8ff6c2b4fcd2be0e45de09b35..f00526753a83f95689fad2a132bef79f4479eec6 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -88,6 +88,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 - Protect Bedrock and End Portal/Frames from being destroyed - public final boolean isDestroyable() { - return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || -@@ -303,7 +307,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, applyLoreFromTile(itemstack, blockEntity)); // Purpur - }); - state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true); - } -@@ -322,7 +326,7 @@ public class Block extends BlockBehaviour implements ItemLike { - event.setExpToDrop(block.getExpDrop(state, serverLevel, pos, net.minecraft.world.item.ItemStack.EMPTY, true)); // Paper - Properly handle xp dropping - event.callEvent(); - for (org.bukkit.inventory.ItemStack drop : event.getDrops()) { -- popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); -+ popResource(serverLevel, pos, applyLoreFromTile(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop), blockEntity)); // Purpur - } - state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping - block.popExperience(serverLevel, pos, event.getExpToDrop()); // Paper - Properly handle xp dropping -@@ -339,13 +343,32 @@ public class Block extends BlockBehaviour implements ItemLike { - // Paper end - Properly handle xp dropping - if (world instanceof ServerLevel) { - Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> { -- Block.popResource(world, pos, itemstack1); -+ Block.popResource(world, pos, applyLoreFromTile(itemstack1, blockEntity)); // Purpur - }); - state.spawnAfterBreak((ServerLevel) world, pos, tool, dropExperience); // Paper - Properly handle xp dropping - } - - } - -+ // Purpur start -+ private static ItemStack applyLoreFromTile(ItemStack stack, @Nullable BlockEntity blockEntity) { -+ if (stack.getItem() instanceof BlockItem) { -+ if (blockEntity != null && blockEntity.getLevel() instanceof ServerLevel) { -+ net.minecraft.world.item.component.ItemLore lore = blockEntity.getPersistentLore(); -+ net.minecraft.core.component.DataComponentPatch.Builder builder = net.minecraft.core.component.DataComponentPatch.builder(); -+ if (blockEntity.getLevel().purpurConfig.persistentTileEntityLore && lore != null) { -+ builder.set(net.minecraft.core.component.DataComponents.LORE, lore); -+ } -+ if (!blockEntity.getLevel().purpurConfig.persistentTileEntityDisplayName) { -+ builder.remove(net.minecraft.core.component.DataComponents.CUSTOM_NAME); -+ } -+ stack.applyComponents(builder.build()); -+ } -+ } -+ 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); -@@ -433,7 +456,17 @@ public class Block extends BlockBehaviour implements ItemLike { - } // Paper - fix drops not preventing stats/food exhaustion - } - -- public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {} -+ // Purpur start - Store placer on Block when placed -+ @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 - Store placer on Block when placed - - public boolean isPossibleToRespawnInThis(BlockState state) { - return !state.isSolid() && !state.liquid(); -@@ -444,7 +477,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 updateEntityMovementAfterFallOn(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 9bafac9b2c7b487f745fb64354f4bbc6a7ded468..c5f8227cd9631d98cc8404e3f6d6109a55c617aa 100644 ---- a/src/main/java/net/minecraft/world/level/block/Blocks.java -+++ b/src/main/java/net/minecraft/world/level/block/Blocks.java -@@ -6454,6 +6454,7 @@ public class Blocks { - BlockBehaviour.Properties.of() - .mapColor(MapColor.PLANT) - .forceSolidOff() -+ .randomTicks() // Purpur - Chance for azalea blocks to grow into trees naturally - .instabreak() - .sound(SoundType.AZALEA) - .noOcclusion() -@@ -6465,6 +6466,7 @@ public class Blocks { - BlockBehaviour.Properties.of() - .mapColor(MapColor.PLANT) - .forceSolidOff() -+ .randomTicks() // Purpur - Chance for azalea blocks to grow into trees naturally - .instabreak() - .sound(SoundType.FLOWERING_AZALEA) - .noOcclusion() -diff --git a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -index 385da0585f409ee453f10d45f5837cdc09adc21b..c65016cba376a41c267fb4b6499ec0a263851558 100644 ---- a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java -@@ -123,10 +123,10 @@ public class BubbleColumnBlock extends Block implements BucketPickup { - if (state.is(Blocks.BUBBLE_COLUMN)) { - return state; - } else if (state.is(Blocks.SOUL_SAND)) { -- return Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(false)); -+ return Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(org.purpurmc.purpur.PurpurConfig.soulSandBlockReverseBubbleColumnFlow)); // Purpur - } else { - return state.is(Blocks.MAGMA_BLOCK) -- ? Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(true)) -+ ? Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(!org.purpurmc.purpur.PurpurConfig.magmaBlockReverseBubbleColumnFlow)) // Purpur - : Blocks.WATER.defaultBlockState(); - } - } -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 eb324fda54ada3ed7941713a784ed2d686ec8c4b..09cc76f3fee4a767c9ec3fa592f2c3c6146344ec 100644 ---- a/src/main/java/net/minecraft/world/level/block/BushBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BushBlock.java -@@ -55,4 +55,24 @@ public abstract class BushBlock extends Block { - protected boolean isPathfindable(BlockState state, PathComputationType type) { - return type == PathComputationType.AIR && !this.hasCollision ? true : super.isPathfindable(state, 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 c045b1cccf0047dbef8c04d5a28d31d53389054f..9f163ed07f8e6a5370c4c355b4e910f7a49b6bcd 100644 ---- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -@@ -24,7 +24,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 MapCodec CODEC = simpleCodec(CactusBlock::new); - public static final IntegerProperty AGE = BlockStateProperties.AGE_15; -@@ -115,7 +115,7 @@ public class CactusBlock extends Block { - - enumdirection = (Direction) iterator.next(); - iblockdata1 = world.getBlockState(pos.relative(enumdirection)); -- } while (!iblockdata1.isSolid() && !world.getFluidState(pos.relative(enumdirection)).is(FluidTags.LAVA)); -+ } while ((!world.getWorldBorder().world.purpurConfig.cactusBreaksFromSolidNeighbors || !iblockdata1.isSolid()) && !world.getFluidState(pos.relative(enumdirection)).is(FluidTags.LAVA)); // Purpur - - return false; - } -@@ -135,4 +135,34 @@ public class CactusBlock extends Block { - protected boolean isPathfindable(BlockState state, PathComputationType type) { - return false; - } -+ -+ // Purpur start -+ @Override -+ public boolean isValidBonemealTarget(final LevelReader world, final BlockPos pos, final BlockState state) { -+ 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/CakeBlock.java b/src/main/java/net/minecraft/world/level/block/CakeBlock.java -index 648c2510beb162e73aed236a3169d0bbb8fc5050..3563a241c0b697dc0167cf7b1aa73fef7d1e7934 100644 ---- a/src/main/java/net/minecraft/world/level/block/CakeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CakeBlock.java -@@ -119,6 +119,7 @@ public class CakeBlock extends Block { - org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, 2 + oldFoodLevel); - - if (!event.isCancelled()) { -+ if (player.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) player.burpDelay = player.level().purpurConfig.playerBurpDelay; // Purpur - player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 0.1F); - } - -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 1b94f26e78db062f80d806b82f714a815b4710ff..b6ba6ebe6ac15cbcb5d3a6221b47762e37c4a56f 100644 ---- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -@@ -140,7 +140,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 - Campfire option for lit when placed - } - - @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 22242d11e009acab4c9738a1c6ada8b9ba678a0c..49b7565c26ce8bf217ae60d233d5963331ce6696 100644 ---- a/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java -@@ -72,7 +72,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { - SnowGolem entitysnowman = (SnowGolem) EntityType.SNOW_GOLEM.create(world, EntitySpawnReason.TRIGGERED); - - 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 - Summoner API - } - } else { - BlockPattern.BlockPatternMatch shapedetector_shapedetectorcollection1 = this.getOrCreateIronGolemFull().find(world, pos); -@@ -82,7 +82,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { - - 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 - Summoner API - } - } - } -@@ -90,6 +90,16 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { - } - - private static void spawnGolemInWorld(Level world, BlockPattern.BlockPatternMatch patternResult, Entity entity, BlockPos pos) { -+ // Purpur start - Summoner API -+ 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 - Summoner API - // 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 c9968934f4ecaa8d81e545f279b3001c7b1ce545..03e4fce6f8226451365fc2831b5bf1e5e6091730 100644 ---- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -@@ -37,7 +37,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 81e572783157926383dd9baa58d30f5419c1616f..84d6ae4acf80f6ff4f418739a0228e740993f950 100644 ---- a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -@@ -94,4 +94,11 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements CaveVines { - 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/ChangeOverTimeBlock.java b/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java -index daae7fd6e0148cfba8e359d990748a0c83a3376e..0e06b1bcd906e92c083dc74d56d6d0a2a36f62a7 100644 ---- a/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java -@@ -67,7 +67,7 @@ public interface ChangeOverTimeBlock> { - } - - float f = (float) (k + 1) / (float) (k + j + 1); -- float f1 = f * f * this.getChanceModifier(); -+ float f1 = world.purpurConfig.disableOxidationProximityPenalty ? this.getChanceModifier() : f * f * this.getChanceModifier(); // Purpur - - return random.nextFloat() < f1 ? this.getNext(state) : Optional.empty(); - } -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 ca69a9fbd4942f9079aeaab7cead2d7a2c3b8659..54f351f1cbb50a5b1aa3167e3a0b10bb0456c1cf 100644 ---- a/src/main/java/net/minecraft/world/level/block/ChestBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ChestBlock.java -@@ -336,6 +336,7 @@ public class ChestBlock extends AbstractChestBlock implements - } - - public 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/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -index 9264ba58188a7a682eeb8eb449b89ff8e60f91d6..809a820dd8eec3e48dd3263335c62fbea4cd4f2c 100644 ---- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -@@ -243,18 +243,27 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - int i = (Integer) state.getValue(ComposterBlock.LEVEL); - - if (i < 8 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) { -- if (i < 7 && !world.isClientSide) { -- BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, stack); -- // 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(stack.getItem())); -- stack.consume(1, player); -+ // Purpur start - sneak to bulk process composter -+ BlockState newState = process(i, player, state, world, pos, stack); -+ 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 = stack.getCount(); -+ oldLevel = oldState.getValue(ComposterBlock.LEVEL); -+ newState = process(oldLevel, player, oldState, world, pos, stack); -+ if (newState == null) { -+ return InteractionResult.PASS; -+ } -+ newCount = stack.getCount(); -+ newLevel = newState.getValue(ComposterBlock.LEVEL); -+ } while (newCount > 0 && (newCount != oldCount || newLevel != oldLevel || newState != oldState)); -+ } -+ // Purpur end - - return InteractionResult.SUCCESS; - } else { -@@ -262,6 +271,25 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - } - } - -+ // Purpur start - sneak to bulk process composter -+ private static @Nullable BlockState process(int level, Player player, BlockState state, Level world, BlockPos pos, ItemStack stack) { -+ if (level < 7 && !world.isClientSide) { -+ BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, stack); -+ // Paper start - handle cancelled events -+ if (iblockdata1 == null) { -+ return null; -+ } -+ // Paper end -+ -+ world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); -+ player.awardStat(Stats.ITEM_USED.get(stack.getItem())); -+ stack.consume(1, player); -+ return iblockdata1; -+ } -+ return state; -+ } -+ // Purpur end -+ - @Override - protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { - 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 a59b23f4062fa896836dec72cbd5097411774ad1..c87b90041825172afd079202241b7f9a206816c6 100644 ---- a/src/main/java/net/minecraft/world/level/block/CoralBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CoralBlock.java -@@ -60,6 +60,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 - Config to not let coral die - 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 1967ff3fcb94988be85985c4754904f0077de066..34f338a246824dbabc7bc386b74cb62c78a8f1b6 100644 ---- a/src/main/java/net/minecraft/world/level/block/CropBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java -@@ -180,7 +180,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { - protected 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 - Add EntityInsideBlockEvent - if (world instanceof ServerLevel worldserver) { -- if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit -+ if (entity instanceof Ravager && world.purpurConfig.ravagerGriefableBlocks.contains(world.getBlockState(pos).getBlock()) && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !world.purpurConfig.ravagerBypassMobGriefing == !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Purpur - Add mobGriefing bypass to everything affected // Purpur - Configurable ravager griefable blocks list - worldserver.destroyBlock(pos, true, entity); - } - } -@@ -216,4 +216,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, boolean includeDrops, boolean dropExp) { -+ 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, includeDrops, dropExp); -+ } -+ } -+ // 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 077b99caf0ec0ee098786d23194d88e1dc4481ce..f8356e468841137dcc92b2fe5db1cafa24619eaf 100644 ---- a/src/main/java/net/minecraft/world/level/block/DoorBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DoorBlock.java -@@ -200,6 +200,7 @@ public class DoorBlock extends Block { - protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { - if (!this.type.canOpenByHand()) { - return InteractionResult.PASS; -+ } else if (requiresRedstone(world, state, pos)) { return InteractionResult.CONSUME; // Purpur - Option to make doors require redstone - } else { - state = (BlockState) state.cycle(DoorBlock.OPEN); - world.setBlock(pos, state, 10); -@@ -301,4 +302,18 @@ public class DoorBlock extends Block { - flag = false; - return flag; - } -+ -+ // Purpur start - Option to make doors require redstone -+ 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 - Option to make doors require redstone - } -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 30d15686b1a81de7ac28feb0c6188eb007c6f2fd..b6799db00e157892dd4339a01d2ca36092c8e491 100644 ---- a/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java -@@ -48,8 +48,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/EnchantingTableBlock.java b/src/main/java/net/minecraft/world/level/block/EnchantingTableBlock.java -index 4c4e6290035710480cd5c1d7399f2443df01a5a6..248039ac7eab85b29ae3c525a986d91aa8d177fe 100644 ---- a/src/main/java/net/minecraft/world/level/block/EnchantingTableBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EnchantingTableBlock.java -@@ -118,4 +118,18 @@ public class EnchantingTableBlock extends BaseEntityBlock { - protected boolean isPathfindable(BlockState state, 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 EnchantingTableBlockEntity enchantmentTable) { -+ net.minecraft.world.Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), new net.minecraft.world.item.ItemStack(net.minecraft.world.item.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/EndGatewayBlock.java b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java -index af46f2885ead1e3ec1734504d8ba134c886e04fb..47ee0538c8ea94136b2416c324c8a264e54d2c09 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java -@@ -104,6 +104,13 @@ public class EndGatewayBlock extends BaseEntityBlock implements Portal { - TheEndGatewayBlockEntity tileentityendgateway = (TheEndGatewayBlockEntity) tileentity; - - if (!tileentityendgateway.isCoolingDown()) { -+ // Purpur start -+ if (world.purpurConfig.imposeTeleportRestrictionsOnGateways && (entity.isVehicle() || entity.isPassenger())) { -+ 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_GATEWAY).callEvent()) { -+ return; -+ } -+ } -+ // Purpur end - entity.setAsInsidePortal(this, pos); - TheEndGatewayBlockEntity.triggerCooldown(world, pos, state, tileentityendgateway); - } -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 8cb4142562db0be1f1a7d961ec5a10d4abf31692..84ecb012cb0a47e47799dc73c7fadc75f462f47a 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -@@ -70,6 +70,13 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { - protected 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 - Add EntityInsideBlockEvent - if (entity.canUsePortal(false)) { -+ // Purpur start -+ if (world.purpurConfig.imposeTeleportRestrictionsOnEndPortals && (entity.isVehicle() || entity.isPassenger())) { -+ 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()) { -+ 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()), org.bukkit.PortalType.ENDER); // Paper - add portal type - world.getCraftServer().getPluginManager().callEvent(event); -diff --git a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java -index 9b6ab617ab7f503cf0b2d4e29333c706ffe95f46..bfe79431dc5707677671df5c0787817c6e14a676 100644 ---- a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java -@@ -84,7 +84,7 @@ public class EnderChestBlock extends AbstractChestBlock i - // Paper start - Fix InventoryOpenEvent cancellation - moved up; - playerEnderChestContainer.setActiveChest(enderChestBlockEntity); // Needs to happen before ChestMenu.threeRows as it is required for opening animations - if (world instanceof ServerLevel serverLevel && player.openMenu( -- new SimpleMenuProvider((i, inventory, playerx) -> ChestMenu.threeRows(i, inventory, playerEnderChestContainer), CONTAINER_TITLE) -+ new SimpleMenuProvider((i, inventory, playerx) -> org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? getEnderChestSixRows(i, inventory, player, playerEnderChestContainer) : ChestMenu.threeRows(i, inventory, playerEnderChestContainer), CONTAINER_TITLE) // Purpur - ).isPresent()) { - // Paper end - Fix InventoryOpenEvent cancellation - moved up; - // Paper - Fix InventoryOpenEvent cancellation - moved up; -@@ -99,6 +99,35 @@ public class EnderChestBlock extends AbstractChestBlock i - } - } - -+ // Purpur start -+ private ChestMenu getEnderChestSixRows(int syncId, net.minecraft.world.entity.player.Inventory inventory, Player player, PlayerEnderChestContainer playerEnderChestContainer) { -+ if (org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) { -+ org.bukkit.craftbukkit.entity.CraftHumanEntity bukkitPlayer = player.getBukkitEntity(); -+ if (bukkitPlayer.hasPermission("purpur.enderchest.rows.six")) { -+ player.sixRowEnderchestSlotCount = 54; -+ return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer); -+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.five")) { -+ player.sixRowEnderchestSlotCount = 45; -+ return ChestMenu.fiveRows(syncId, inventory, playerEnderChestContainer); -+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.four")) { -+ player.sixRowEnderchestSlotCount = 36; -+ return ChestMenu.fourRows(syncId, inventory, playerEnderChestContainer); -+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.three")) { -+ player.sixRowEnderchestSlotCount = 27; -+ return ChestMenu.threeRows(syncId, inventory, playerEnderChestContainer); -+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.two")) { -+ player.sixRowEnderchestSlotCount = 18; -+ return ChestMenu.twoRows(syncId, inventory, playerEnderChestContainer); -+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.one")) { -+ player.sixRowEnderchestSlotCount = 9; -+ return ChestMenu.oneRow(syncId, inventory, playerEnderChestContainer); -+ } -+ } -+ player.sixRowEnderchestSlotCount = -1; -+ return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer); -+ } -+ // Purpur end -+ - @Override - public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { - return new EnderChestBlockEntity(pos, state); -diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -index c3dba0c2c94f3804338f86621dc42405e380a6b3..f1ef2eda3282b3bcd99e388dc56d5542cd93bedb 100644 ---- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -@@ -112,7 +112,7 @@ public class FarmBlock extends Block { - 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 instanceof ServerLevel worldserver) { -- if (world.random.nextFloat() < fallDistance - 0.5F && entity instanceof LivingEntity && (entity instanceof Player || worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { -+ if ((worldserver.purpurConfig.farmlandTrampleHeight >= 0D ? fallDistance >= worldserver.purpurConfig.farmlandTrampleHeight : world.random.nextFloat() < fallDistance - 0.5F) && entity instanceof LivingEntity && (entity instanceof Player || worldserver.purpurConfig.farmlandBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { // Purpur - Add mobGriefing bypass to everything affected // Purpur - Configurable farmland trample height - // CraftBukkit start - Interact soil - org.bukkit.event.Cancellable cancellable; - if (entity instanceof Player) { -@@ -126,6 +126,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 = ((LivingEntity) entity).getArmorSlots().iterator(); -+ if (armor.hasNext() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FEATHER_FALLING, armor.next()) >= (int) entity.fallDistance) { -+ return; -+ } -+ } -+ // Purpur end - if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) { - return; - } -@@ -174,7 +190,7 @@ public class FarmBlock extends Block { - } - } - -- return false; -+ return ((ServerLevel) world).purpurConfig.farmlandGetsMoistFromBelow && world.getFluidState(pos.relative(Direction.DOWN)).is(FluidTags.WATER); // Purpur; - // Paper end - Perf: remove abstract block iteration - } - -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 9b424d7661fedf8ee1eb9f3167c62e563f04d4d1..2af311847a085a8073e9bcb26c762d1bbe1eae2c 100644 ---- a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -@@ -34,12 +34,12 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements - - @Override - public BlockState getStateForPlacement(RandomSource random) { -- return (BlockState) this.defaultBlockState().setValue(GrowingPlantHeadBlock.AGE, random.nextInt(25)); -+ return (BlockState) this.defaultBlockState().setValue(GrowingPlantHeadBlock.AGE, random.nextInt(getMaxGrowthAge())); // Purpur - } - - @Override - protected boolean isRandomlyTicking(BlockState state) { -- return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25; -+ return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) < getMaxGrowthAge(); // Purpur - } - - @Override -@@ -55,7 +55,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); - -@@ -77,11 +77,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) { -@@ -123,13 +123,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 - } - - } -@@ -142,4 +142,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 ef364aa171a48482a45bc18cfe730ec20c3f7be6..74971d90506aa253d5ee821b5390fb2551a3a393 100644 ---- a/src/main/java/net/minecraft/world/level/block/HayBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/HayBlock.java -@@ -23,6 +23,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/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java -index a94762e65853ccad38cf90b0049ca256106c0c9f..aa93b5041b65dbcdc5bbea65567eaf5d8c433a0d 100644 ---- a/src/main/java/net/minecraft/world/level/block/IceBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java -@@ -42,7 +42,7 @@ public class IceBlock extends HalfTransparentBlock { - public void afterDestroy(Level world, BlockPos pos, ItemStack tool) { - // Paper end - Improve Block#breakNaturally API - if (!EnchantmentHelper.hasTag(tool, EnchantmentTags.PREVENTS_ICE_MELTING)) { -- if (world.dimensionType().ultraWarm()) { -+ if (world.isNether() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur - Add allow water in end world option - world.removeBlock(pos, false); - return; - } -@@ -70,7 +70,7 @@ public class IceBlock extends HalfTransparentBlock { - return; - } - // CraftBukkit end -- if (world.dimensionType().ultraWarm()) { -+ if (world.isNether() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur - Add allow water in end world option - world.removeBlock(pos, false); - } else { - world.setBlockAndUpdate(pos, IceBlock.meltsInto()); -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 784b19bc78c8ad9476b6dac37b6778a409a7c675..d49dd8b20d3785cc9482ed2a34fbd7aed4c9e537 100644 ---- a/src/main/java/net/minecraft/world/level/block/KelpBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/KelpBlock.java -@@ -72,4 +72,11 @@ public class KelpBlock extends GrowingPlantHeadBlock implements LiquidBlockConta - protected 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 a2d023ff011f71f80032f02430a53d6a08a23623..8c1c24e98f79888faee204129145746279d5d159 100644 ---- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java -@@ -140,7 +140,7 @@ public class LiquidBlock extends Block implements BucketPickup { - - @Override - protected 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 - Tick fluids config - world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava - } - -@@ -168,7 +168,7 @@ public class LiquidBlock extends Block implements BucketPickup { - - @Override - protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { -- if (state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { -+ if (world.getWorldBorder().world.purpurConfig.tickFluids && state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { // Purpur - Tick fluids config - tickView.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world)); - } - -@@ -177,7 +177,7 @@ public class LiquidBlock extends Block implements BucketPickup { - - @Override - protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) { -- if (this.shouldSpreadLiquid(world, pos, state)) { -+ if (world.purpurConfig.tickFluids && this.shouldSpreadLiquid(world, pos, state)) { // Purpur - Tick fluids config - world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava - } - -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 7ffdcf18bf4bd8b5325c76945b2d80ca3fe52958..4adbbd27c5b2e4cef630c6c8aae38d3f2b94c11e 100644 ---- a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java -@@ -29,7 +29,7 @@ public class MagmaBlock extends Block { - - @Override - public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) { -- if (!entity.isSteppingCarefully() && entity instanceof LivingEntity) { -+ if ((!entity.isSteppingCarefully() || world.purpurConfig.magmaBlockDamageWhenSneaking) && entity instanceof LivingEntity) { // Purpur - Configurable damage settings for magma blocks - entity.hurt(world.damageSources().hotFloor().directBlock(world, pos), 1.0F); // 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 3e9642e5236d9a1cc8e8f3b375d76810f4bc7c6c..6c10860aee8b8fac4089fb7f1506c890fb3f5308 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -78,7 +78,7 @@ public class NetherPortalBlock extends Block implements Portal { - - @Override - protected 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(); - } -@@ -117,6 +117,13 @@ public class NetherPortalBlock extends Block implements Portal { - protected 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 - Add EntityInsideBlockEvent - if (entity.canUsePortal(false)) { -+ // Purpur start -+ if (world.purpurConfig.imposeTeleportRestrictionsOnNetherPortals && (entity.isVehicle() || entity.isPassenger())) { -+ 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()) { -+ 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()), org.bukkit.PortalType.NETHER); // Paper - add portal type - world.getCraftServer().getPluginManager().callEvent(event); -@@ -130,7 +137,7 @@ public class NetherPortalBlock extends Block implements Portal { - @Override - public int getPortalTransitionTime(ServerLevel world, Entity entity) { - if (entity instanceof Player entityhuman) { -- return Math.max(0, world.getGameRules().getInt(entityhuman.getAbilities().invulnerable ? GameRules.RULE_PLAYERS_NETHER_PORTAL_CREATIVE_DELAY : GameRules.RULE_PLAYERS_NETHER_PORTAL_DEFAULT_DELAY)); -+ return Math.max(0, entityhuman.canPortalInstant ? 1 : world.getGameRules().getInt(entityhuman.getAbilities().invulnerable ? GameRules.RULE_PLAYERS_NETHER_PORTAL_CREATIVE_DELAY : GameRules.RULE_PLAYERS_NETHER_PORTAL_DEFAULT_DELAY)); // Purpur - } else { - return 0; - } -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 7bb4994d6474c8ea59c102009253552020691b8f..264692baa4a20b66910d8ff379fa72acb99e27f8 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java -@@ -16,7 +16,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 MapCodec CODEC = simpleCodec(NetherWartBlock::new); - public static final int MAX_AGE = 3; -@@ -68,4 +68,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, boolean includeDrops, boolean dropExp) { -+ 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, includeDrops, dropExp); -+ } -+ } -+ -+ @Override -+ public boolean isValidBonemealTarget(final net.minecraft.world.level.LevelReader world, final BlockPos pos, final BlockState state) { -+ 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 6582db84c5307257f16c321453491cf24e40c9c7..f9015d4e478efeec8a796b7a897638f76064db20 100644 ---- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java -@@ -97,7 +97,7 @@ public class NoteBlock extends Block { - } - - private void playNote(@Nullable Entity entity, BlockState state, Level world, BlockPos pos) { -- if (((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || world.getBlockState(pos.above()).isAir()) { -+ if (world.purpurConfig.noteBlockIgnoreAbove || ((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || 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 93ed9406c34804831b86d006dbd6087db9948f08..26cb9990b91991e0a2eadc2dcbbf229e2e88fb2d 100644 ---- a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java -@@ -75,6 +75,7 @@ public class ObserverBlock extends DirectionalBlock { - @Override - protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { - if (state.getValue(ObserverBlock.FACING) == direction && !(Boolean) state.getValue(ObserverBlock.POWERED)) { -+ if (!world.getWorldBorder().world.purpurConfig.disableObserverClocks || !(neighborState.getBlock() instanceof ObserverBlock) || neighborState.getValue(ObserverBlock.FACING).getOpposite() != direction) // Purpur - this.startSignal(world, tickView, 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 53cea36ec931de89e0060613acf87beb51dc16ec..fd5489993dca0f940da69e9163f78e5c2e6ee063 100644 ---- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -@@ -194,7 +194,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); - -@@ -203,13 +203,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 53f1a7ed6b4bd6e2d8460531226aabf249994c02..f91c845061fa632e53efb31c63cf0c67c9c2e86a 100644 ---- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -@@ -76,7 +76,7 @@ public class PowderSnowBlock extends Block implements BucketPickup { - if (world instanceof ServerLevel worldserver) { - // CraftBukkit start - if (entity.isOnFire() && entity.mayInteract(worldserver, pos)) { -- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !((worldserver.purpurConfig.powderSnowBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) || entity instanceof Player))) { // Purpur - Add mobGriefing bypass to everything affected - 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 b763361a8f0f1b46093d5dd9afe8dba0cadf9c78..bd14c08defe8afc5ceca59d16a5b1dbad178f594 100644 ---- a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -@@ -30,7 +30,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 9117c035d5a6ff114b028fad3380ceb1fc2b9691..2c5e394156dbf76107adb4913a094dfd4a598dd7 100644 ---- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -@@ -149,7 +149,7 @@ public class RespawnAnchorBlock extends Block { - }; - Vec3 vec3d = explodedPos.getCenter(); - -- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state -+ if (world.purpurConfig.respawnAnchorExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), explosiondamagecalculator, vec3d, (float) world.purpurConfig.respawnAnchorExplosionPower, world.purpurConfig.respawnAnchorExplosionFire, world.purpurConfig.respawnAnchorExplosionEffect); // CraftBukkit - add state // 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 7990a6c225c27845ccada0df91cf5ed7e4315a88..db2b21d5842fafa48dbde25a461505d03aa9b955 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/SlabBlock.java b/src/main/java/net/minecraft/world/level/block/SlabBlock.java -index 9274fd639c22e305dda567b303f9b01068adb52c..4433e432ea0ee8d11045b87e68dac3ed43e8cf82 100644 ---- a/src/main/java/net/minecraft/world/level/block/SlabBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SlabBlock.java -@@ -150,4 +150,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 9908a0b5b1fec5f9de518a733f7abbbff7e1a9f9..0ad444cf7f798f63e9140a42c5d5d8cab0fcf14f 100644 ---- a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java -@@ -88,6 +88,12 @@ public class SnowLayerBlock extends Block { - protected 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 d751b280a8bf2066d458f8eb548d7aa123fa69c9..fb88b5c1d9e764bf1211d601527133ea8e8268b3 100644 ---- a/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java -@@ -42,6 +42,57 @@ 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, @Nullable BlockEntity blockEntity, ItemStack stack, boolean includeDrops, boolean dropExp) { -+ if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.drop.spawners") && isSilkTouch(level, stack)) { -+ ItemStack item = new ItemStack(Blocks.SPAWNER.asItem()); -+ -+ net.minecraft.world.level.SpawnData nextSpawnData = blockEntity instanceof SpawnerBlockEntity spawnerBlock ? spawnerBlock.getSpawner().nextSpawnData : null; -+ java.util.Optional> type = java.util.Optional.empty(); -+ if (nextSpawnData != null) { -+ type = net.minecraft.world.entity.EntityType.by(nextSpawnData.getEntityToSpawn()); -+ net.minecraft.world.level.SpawnData.CODEC.encodeStart(net.minecraft.nbt.NbtOps.INSTANCE, nextSpawnData).result().ifPresent(tag -> item.set(net.minecraft.core.component.DataComponents.CUSTOM_DATA, net.minecraft.world.item.component.CustomData.EMPTY.update(compoundTag -> compoundTag.put("Purpur.SpawnData", tag)))); -+ } -+ -+ if (type.isPresent()) { -+ final net.kyori.adventure.text.Component mobName = io.papermc.paper.adventure.PaperAdventure.asAdventure(type.get().getDescription()); -+ -+ 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); -+ } -+ item.set(net.minecraft.core.component.DataComponents.CUSTOM_NAME, io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName)); -+ } -+ -+ List lore = level.purpurConfig.silkTouchSpawnerLore; -+ if (lore != null && !lore.isEmpty()) { -+ -+ List loreComponentList = new java.util.ArrayList<>(); -+ 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); -+ } -+ loreComponentList.add(io.papermc.paper.adventure.PaperAdventure.asVanilla(lineComponent)); -+ } -+ -+ item.set(net.minecraft.core.component.DataComponents.LORE, new net.minecraft.world.item.component.ItemLore(loreComponentList, loreComponentList)); -+ } -+ item.set(net.minecraft.core.component.DataComponents.HIDE_ADDITIONAL_TOOLTIP, net.minecraft.util.Unit.INSTANCE); -+ } -+ popResource(level, pos, item); -+ } -+ super.playerDestroy(level, player, pos, state, blockEntity, stack, includeDrops, dropExp); -+ } -+ -+ 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 - protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { - super.spawnAfterBreak(state, world, pos, tool, dropExperience); -@@ -50,6 +101,7 @@ public class SpawnerBlock extends BaseEntityBlock { - - @Override - public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) { -+ if (worldserver.purpurConfig.silkTouchEnabled && 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 e9a77c1ae09af42d2d444ad6b5f6c8ac395044e1..295c1439645c8da7f84f0a72abb3235ee879e89c 100644 ---- a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java -@@ -61,7 +61,7 @@ public class SpongeBlock extends Block { - - private boolean removeWaterBreadthFirstSearch(Level world, BlockPos pos) { - BlockStateListPopulator blockList = new BlockStateListPopulator(world); // CraftBukkit - Use BlockStateListPopulator -- BlockPos.breadthFirstTraversal(pos, 6, 65, (blockposition1, consumer) -> { -+ BlockPos.breadthFirstTraversal(pos, world.purpurConfig.spongeAbsorptionRadius, world.purpurConfig.spongeAbsorptionArea, (blockposition1, consumer) -> { // Purpur - Configurable sponge absorption - Direction[] aenumdirection = SpongeBlock.ALL_DIRECTIONS; - int i = aenumdirection.length; - -@@ -80,7 +80,7 @@ public class SpongeBlock extends Block { - FluidState fluid = blockList.getFluidState(blockposition1); - // CraftBukkit end - -- if (!fluid.is(FluidTags.WATER)) { -+ if (!fluid.is(FluidTags.WATER) && (!world.purpurConfig.spongeAbsorbsLava || !fluid.is(FluidTags.LAVA)) && (!world.purpurConfig.spongeAbsorbsWaterFromMud || !iblockdata.is(Blocks.MUD))) { // Purpur - return BlockPos.TraversalNodeStatus.SKIP; - } else { - Block block = iblockdata.getBlock(); -@@ -95,6 +95,10 @@ public class SpongeBlock extends Block { - - if (iblockdata.getBlock() instanceof LiquidBlock) { - blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit -+ // Purpur start -+ } else if (iblockdata.is(Blocks.MUD)) { -+ blockList.setBlock(blockposition1, Blocks.CLAY.defaultBlockState(), 3); -+ // Purpur end - } else { - if (!iblockdata.is(Blocks.KELP) && !iblockdata.is(Blocks.KELP_PLANT) && !iblockdata.is(Blocks.SEAGRASS) && !iblockdata.is(Blocks.TALL_SEAGRASS)) { - return BlockPos.TraversalNodeStatus.SKIP; -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 f1366aea49206afcd64bf058ee673d6a562315c5..e93d1eba4fcd02e15287a1a66da94e695806a470 100644 ---- a/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java -@@ -93,4 +93,14 @@ public class StonecutterBlock extends Block { - protected boolean isPathfindable(BlockState state, PathComputationType type) { - return false; - } -+ -+ // Purpur start - Stonecutter damage -+ @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) { -+ entity.hurtServer((net.minecraft.server.level.ServerLevel) level, entity.damageSources().stonecutter().directBlock(level, pos), level.purpurConfig.stonecutterDamage); -+ } -+ super.stepOn(level, pos, state, entity); -+ } -+ // Purpur end - Stonecutter damage - } -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 547ea09ed84595286c97c128b3b96f6d387ae25f..d0f8a13f27132257ece6dadf736c2dc6b1e5720e 100644 ---- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java -@@ -20,7 +20,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 MapCodec CODEC = simpleCodec(SugarCaneBlock::new); - public static final IntegerProperty AGE = BlockStateProperties.AGE_15; -@@ -113,4 +113,34 @@ public class SugarCaneBlock extends Block { - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - builder.add(SugarCaneBlock.AGE); - } -+ -+ // Purpur start -+ @Override -+ public boolean isValidBonemealTarget(final LevelReader world, final BlockPos pos, final BlockState state) { -+ 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 953ddb2ea6fd48e57712e30a6addf23e188e5312..cf86448e2067712863d30c9aecc48daedefd227f 100644 ---- a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java -@@ -171,7 +171,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 -@@ -204,6 +204,31 @@ public class TurtleEggBlock extends Block { - } - - private boolean canDestroyEgg(ServerLevel 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 - Add turtle egg block options -+ 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; -+ } -+ // Purpur start - Option to disable turtle egg trampling with feather falling -+ if (world.purpurConfig.turtleEggsTramplingFeatherFalling) { -+ java.util.Iterator armor = ((LivingEntity) entity).getArmorSlots().iterator(); -+ return !armor.hasNext() || net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FEATHER_FALLING, armor.next()) < (int) entity.fallDistance; -+ } -+ // Purpur end - Option to disable turtle egg trampling with feather falling -+ if (entity instanceof Player) return true; -+ -+ return world.purpurConfig.turtleEggsBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - Add mobGriefing bypass to everything affected -+ // Purpur end - Add turtle egg block options - } - } -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 6342bb11a162b9e6d9475c5989b1670d77e8f0fb..f8be92512446d3f0e5f0f21222bbefd04ab2838a 100644 ---- a/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java -@@ -34,4 +34,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 3dec5a082606ee35a8c8d7f746480262d6a189c5..b2f6ccae9576c176263e51a232e17a08d54543b3 100644 ---- a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java -@@ -34,4 +34,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 0fbe66cc02bd3d95c0a5dcd55380a1b4a2f17ca6..11d6427682d8778d1cf668ee4c1d5760af2c185e 100644 ---- a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java -@@ -80,6 +80,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 - Summoner API - // 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 a9809c18233d82f910735e59363a49de488defcd..affa084b6bd4a6792d1c55719f88f3fb16627761 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 -@@ -218,6 +218,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)) { -+ net.minecraft.world.level.material.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 - ItemStack itemstack1 = (ItemStack) blockEntity.items.get(0); - boolean flag2 = !itemstack1.isEmpty(); - boolean flag3 = !itemstack.isEmpty(); -@@ -303,6 +318,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 dynamicRegistryManager, @Nullable RecipeHolder recipe, SingleRecipeInput input, NonNullList inventory, int maxCount) { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java -index 618552afbdacc919c33b30a6bf4834fb71ab3d5b..7a059d20abdcc0073a314311d78f63ae4bd0365e 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java -@@ -68,7 +68,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { - - public BarrelBlockEntity(BlockPos pos, BlockState state) { - super(BlockEntityType.BARREL, pos, state); -- this.items = NonNullList.withSize(27, ItemStack.EMPTY); -+ // Purpur start -+ this.items = NonNullList.withSize(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { -+ case 6 -> 54; -+ case 5 -> 45; -+ case 4 -> 36; -+ case 2 -> 18; -+ case 1 -> 9; -+ default -> 27; -+ }, ItemStack.EMPTY); -+ // Purpur end - this.openersCounter = new ContainerOpenersCounter() { - @Override - protected void onOpen(Level world, BlockPos pos, BlockState state) { -@@ -119,7 +128,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { - - @Override - public int getContainerSize() { -- return 27; -+ // Purpur start -+ return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { -+ case 6 -> 54; -+ case 5 -> 45; -+ case 4 -> 36; -+ case 2 -> 18; -+ case 1 -> 9; -+ default -> 27; -+ }; -+ // Purpur end - } - - @Override -@@ -139,7 +157,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { - - @Override - protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { -- return ChestMenu.threeRows(syncId, playerInventory, this); -+ // Purpur start -+ return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { -+ case 6 -> ChestMenu.sixRows(syncId, playerInventory, this); -+ case 5 -> ChestMenu.fiveRows(syncId, playerInventory, this); -+ case 4 -> ChestMenu.fourRows(syncId, playerInventory, this); -+ case 2 -> ChestMenu.twoRows(syncId, playerInventory, this); -+ case 1 -> ChestMenu.oneRow(syncId, playerInventory, this); -+ default -> ChestMenu.threeRows(syncId, playerInventory, this); -+ }; -+ // Purpur end - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index 0e0d178f2793ab014358f534c8dc53218b89f083..98e3d0a6f1be8cef8678b4048a3a484012092f08 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 -@@ -92,6 +92,16 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - - public double getEffectRange() { - if (this.effectRange < 0) { -+ // Purpur start - Beacon Activation Range Configurable -+ 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 - Beacon Activation Range Configurable - return this.levels * 10 + 10; - } else { - return effectRange; -@@ -168,6 +178,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; -@@ -201,6 +212,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() >= 15 && !iblockdata1.is(Blocks.BEDROCK)) { - blockEntity.checkingBeamSections.clear(); - blockEntity.lastCheckY = l; -@@ -220,7 +234,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 - Custom beacon ranges - 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 65a85b4a4e159cfe55e435ed342a87bcc07b21d5..fdebf45a27b903f4abb0bf55e1d79d78b7cc3d32 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 -@@ -60,7 +60,7 @@ public class BeehiveBlockEntity extends BlockEntity { - private 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); -@@ -147,11 +147,33 @@ public class BeehiveBlockEntity extends BlockEntity { - return list; - } - -+ // Purpur start - Stored Bee API -+ public List releaseBee(BlockState iblockdata, BeehiveBlockEntity.BeeData data, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, boolean force) { -+ List list = Lists.newArrayList(); -+ -+ BeehiveBlockEntity.releaseOccupant(this.level, this.worldPosition, iblockdata, data.occupant, list, tileentitybeehive_releasestatus, this.savedFlowerPos, force); -+ -+ if (!list.isEmpty()) { -+ stored.remove(data); -+ -+ super.setChanged(); -+ } -+ -+ return list; -+ } -+ // Purpur end - Stored Bee API -+ - @VisibleForDebug - public int getOccupantCount() { - return this.stored.size(); - } - -+ // Purpur start - Stored Bee API -+ public List getStored() { -+ return stored; -+ } -+ // Purpur end - Stored Bee API -+ - // Paper start - Add EntityBlockStorage clearEntities - public void clearBees() { - this.stored.clear(); -@@ -467,9 +489,9 @@ public class BeehiveBlockEntity extends BlockEntity { - } - } - -- private static class BeeData { -+ public static class BeeData { // Purpur - change from private to public - Stored Bee API - -- private final BeehiveBlockEntity.Occupant occupant; -+ public final BeehiveBlockEntity.Occupant occupant; // Purpur - make public - Stored Bee API - private int exitTickCounter; // Paper - Fix bees aging inside hives; separate counter for checking if bee should exit to reduce exit attempts - private int ticksInHive; - -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 1f664c10138a6e19bdc0051fa80575516d5602e7..5c5cc77ff2e050e80dc9f6f62ede68d177a0015f 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 -@@ -94,6 +94,12 @@ public abstract class BlockEntity { - if (persistentDataTag instanceof CompoundTag) { - this.persistentDataContainer.putAll((CompoundTag) persistentDataTag); - } -+ // Purpur start -+ if (nbt.contains("Purpur.persistentLore")) { -+ net.minecraft.world.item.component.ItemLore.CODEC.decode(net.minecraft.nbt.NbtOps.INSTANCE, nbt.getCompound("Purpur.persistentLore")).result() -+ .ifPresent(tag -> this.persistentLore = tag.getFirst()); -+ } -+ // Purpur end - } - // CraftBukkit end - -@@ -110,6 +116,15 @@ public abstract class BlockEntity { - this.loadAdditional(nbt, registries); - } - -+ // Purpur start -+ protected void saveAdditional(CompoundTag nbt) { -+ if (this.persistentLore != null) { -+ net.minecraft.world.item.component.ItemLore.CODEC.encodeStart(net.minecraft.nbt.NbtOps.INSTANCE, this.persistentLore).result() -+ .ifPresent(tag -> nbt.put("Purpur.persistentLore", tag)); -+ } -+ } -+ // Purpur end -+ - protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) {} - - public final CompoundTag saveWithFullMetadata(HolderLookup.Provider registries) { -@@ -406,4 +421,16 @@ public abstract class BlockEntity { - - T getOrDefault(DataComponentType type, T fallback); - } -+ // Purpur start -+ @Nullable -+ private net.minecraft.world.item.component.ItemLore persistentLore = null; -+ -+ public void setPersistentLore(net.minecraft.world.item.component.ItemLore lore) { -+ this.persistentLore = lore; -+ } -+ -+ public @org.jetbrains.annotations.Nullable net.minecraft.world.item.component.ItemLore 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 d354c2ad41533ec8d52f7c5f63f8f74a0b1ce235..a43c41a26c1e34a2f4eda1c498a562664a783c77 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 -@@ -169,7 +169,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) { -@@ -189,13 +189,13 @@ public class ConduitBlockEntity extends BlockEntity { - - private static void applyEffects(Level world, BlockPos pos, List activatingBlocks) { - // CraftBukkit start -- ConduitBlockEntity.applyEffects(world, pos, ConduitBlockEntity.getRange(activatingBlocks)); -+ ConduitBlockEntity.applyEffects(world, pos, ConduitBlockEntity.getRange(activatingBlocks, world)); // Purpur - } - -- public static int getRange(List list) { -+ public static int getRange(List list, Level world) { // Purpur - // CraftBukkit end - int i = list.size(); -- int j = i / 7 * 16; -+ int j = i / 7 * world.purpurConfig.conduitDistance; // Purpur - // CraftBukkit start - return j; - } -@@ -238,20 +238,20 @@ public class ConduitBlockEntity extends BlockEntity { - tileentityconduit.destroyTarget = ConduitBlockEntity.findDestroyTarget(world, blockposition, tileentityconduit.destroyTargetUUID); - tileentityconduit.destroyTargetUUID = null; - } else if (tileentityconduit.destroyTarget == null) { -- List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(blockposition), (entityliving1) -> { -+ List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(blockposition, world), (entityliving1) -> { // Purpur - return entityliving1 instanceof Enemy && entityliving1.isInWaterOrRain(); - }); - - if (!list1.isEmpty()) { - tileentityconduit.destroyTarget = (LivingEntity) list1.get(world.random.nextInt(list1.size())); - } -- } else if (!tileentityconduit.destroyTarget.isAlive() || !blockposition.closerThan(tileentityconduit.destroyTarget.blockPosition(), 8.0D)) { -+ } else if (!tileentityconduit.destroyTarget.isAlive() || !blockposition.closerThan(tileentityconduit.destroyTarget.blockPosition(), world.purpurConfig.conduitDamageDistance)) { // Purpur - tileentityconduit.destroyTarget = null; - } - - // CraftBukkit start - if (damageTarget && tileentityconduit.destroyTarget != null) { -- if (tileentityconduit.destroyTarget.hurtServer((ServerLevel) world, world.damageSources().magic().directBlock(world, blockposition), 4.0F)) { -+ if (tileentityconduit.destroyTarget.hurtServer((ServerLevel) world, world.damageSources().magic().directBlock(world, blockposition), world.purpurConfig.conduitDamageAmount)) { // Purpur - world.playSound(null, tileentityconduit.destroyTarget.getX(), tileentityconduit.destroyTarget.getY(), tileentityconduit.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F); - } - // CraftBukkit end -@@ -276,16 +276,22 @@ public class ConduitBlockEntity extends BlockEntity { - } - - public 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/EnchantingTableBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java -index 39aac959775afeaeea211f21d498cb0ddf0a3fcb..6349a342c023f378af431a73a62fb017882e257d 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java -@@ -28,6 +28,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable - private static final RandomSource RANDOM = RandomSource.create(); - @Nullable - private Component name; -+ private int lapis = 0; // Purpur - - public EnchantingTableBlockEntity(BlockPos pos, BlockState state) { - super(BlockEntityType.ENCHANTING_TABLE, pos, state); -@@ -39,6 +40,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable - if (this.hasCustomName()) { - nbt.putString("CustomName", Component.Serializer.toJson(this.name, registries)); - } -+ nbt.putInt("Purpur.Lapis", this.lapis); // Purpur - } - - @Override -@@ -47,6 +49,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable - if (nbt.contains("CustomName", 8)) { - this.name = parseCustomNameSafe(nbt.getString("CustomName"), registries); - } -+ this.lapis = nbt.getInt("Purpur.Lapis"); // Purpur - } - - public static void bookAnimationTick(Level world, BlockPos pos, BlockState state, EnchantingTableBlockEntity blockEntity) { -@@ -138,4 +141,14 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable - public void removeComponentsFromTag(CompoundTag nbt) { - nbt.remove("CustomName"); - } -+ -+ // 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/FuelValues.java b/src/main/java/net/minecraft/world/level/block/entity/FuelValues.java -index 61ef08ac941b1e8988d001241780d3a1582f7a2d..6ceb938d6f6a4f2bc5b786c7af7bd291f7486340 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/FuelValues.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/FuelValues.java -@@ -17,7 +17,7 @@ import net.minecraft.world.level.ItemLike; - import net.minecraft.world.level.block.Blocks; - - public class FuelValues { -- private final Object2IntSortedMap values; -+ public final Object2IntSortedMap values; // Purpur - private -> public - Added the ability to add combustible items - - FuelValues(Object2IntSortedMap fuelValues) { - this.values = fuelValues; -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 7a1d9a718dc57b5f630ca8e5358120cebaeefb9c..b51b0b0f48b1da6187387d6ec025681e10ed584d 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 -@@ -201,16 +201,31 @@ public class SignBlockEntity extends BlockEntity { - return this.setText((SignText) textChanger.apply(signtext), front); - } - -+ // Purpur start -+ private Component translateColors(org.bukkit.entity.Player player, String line, Style style) { -+ if (level.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"); -+ -+ return io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(line)); -+ } else { -+ return Component.literal(line).setStyle(style); -+ } -+ } -+ // Purpur end -+ - private SignText setMessages(net.minecraft.world.entity.player.Player entityhuman, List list, SignText signtext, boolean front) { // CraftBukkit - SignText originalText = signtext; // CraftBukkit - for (int i = 0; i < list.size(); ++i) { - FilteredText filteredtext = (FilteredText) list.get(i); - Style chatmodifier = signtext.getMessage(i, entityhuman.isTextFilteringEnabled()).getStyle(); - -+ org.bukkit.entity.Player player = (org.bukkit.craftbukkit.entity.CraftPlayer) entityhuman.getBukkitEntity(); // Purpur - if (entityhuman.isTextFilteringEnabled()) { -- signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only -+ signtext = signtext.setMessage(i, translateColors(player, net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty()), chatmodifier)); // Paper - filter sign text to chat only // Purpur - } else { -- signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.raw())).setStyle(chatmodifier), Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only -+ signtext = signtext.setMessage(i, translateColors(player, net.minecraft.util.StringUtil.filterText(filteredtext.raw()), chatmodifier), translateColors(player, net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty()), chatmodifier)); // Paper - filter sign text to chat only // Purpur - } - } - -@@ -348,6 +363,28 @@ public class SignBlockEntity extends BlockEntity { - return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player); // Paper - Fix commands from signs not firing command events - } - -+ // Purpur start -+ public ClientboundBlockEntityDataPacket getTranslatedUpdatePacket(boolean filtered, boolean front) { -+ final CompoundTag nbt = new CompoundTag(); -+ this.saveAdditional(nbt, this.getLevel().registryAccess()); -+ final Component[] lines = front ? frontText.getMessages(filtered) : backText.getMessages(filtered); -+ final String side = front ? "front_text" : "back_text"; -+ 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); -+ if (!nbt.contains(side)) nbt.put(side, new CompoundTag()); -+ final CompoundTag sideNbt = nbt.getCompound(side); -+ if (!sideNbt.contains("messages")) sideNbt.put("messages", new net.minecraft.nbt.ListTag()); -+ final net.minecraft.nbt.ListTag messagesNbt = sideNbt.getList("messages", Tag.TAG_STRING); -+ messagesNbt.set(i, net.minecraft.nbt.StringTag.valueOf(json)); -+ } -+ nbt.putString("PurpurEditor", "true"); -+ return ClientboundBlockEntityDataPacket.create(this, (blockEntity, registryAccess) -> nbt); -+ } -+ // Purpur end -+ - @Override - public ClientboundBlockEntityDataPacket getUpdatePacket() { - return ClientboundBlockEntityDataPacket.create(this); -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 205e223c356634bd6bc6bd58c6f0b7fda61a6f5f..bea05cb928d540a2f19b51bb7352d032b2dd69cd 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 -@@ -81,7 +81,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)) { -@@ -95,7 +95,7 @@ public class PistonStructureResolver { - break; - } - -- if (++i + this.toPush.size() > 12) { -+ if (++i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - return false; - } - } -@@ -140,7 +140,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 8c0f332a1a0918f60226d969918ae7fe4fe74166..c8ae6e4cd74549f753ec04def5d882de1ab72308 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 -@@ -91,7 +91,7 @@ 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}; - 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; -@@ -99,7 +99,7 @@ public abstract class BlockBehaviour implements FeatureElement { - protected final float jumpFactor; - protected final boolean dynamicShape; - protected final FeatureFlagSet requiredFeatures; -- protected final BlockBehaviour.Properties properties; -+ public final BlockBehaviour.Properties properties; // Purpur - protected -> public - protected final Optional> drops; - protected final String descriptionId; - -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 97937e3bd211997f0a0a3e9e671a1c59712d0003..00ea1c2037c7c7780764bfcc3e07b6554e910db2 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -89,6 +89,18 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p - private final LevelChunkTicks fluidTicks; - private LevelChunk.UnsavedListener unsavedListener; - -+ // Pufferfish start - instead of using a random every time the chunk is ticked, define when lightning strikes preemptively -+ private int lightningTick; -+ // 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; -+ return true; -+ } -+ return false; -+ } -+ // Pufferfish end -+ - public LevelChunk(Level world, ChunkPos pos) { - this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, (LevelChunkSection[]) null, (LevelChunk.PostLoadProcessor) null, (BlendingData) null); - } -@@ -122,6 +134,8 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p - this.debug = !empty && this.level.isDebug(); - this.defaultBlockState = empty ? VOID_AIR_BLOCKSTATE : AIR_BLOCKSTATE; - // Paper end - get block chunk optimisation -+ -+ this.lightningTick = java.util.concurrent.ThreadLocalRandom.current().nextInt(100000) << 1; // Pufferfish - initialize lightning tick // Purpur - any random will do - Fix pufferfish issues - } - - // 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 356d010506fd21f3c752e4aa86c46c1106fdde3b..8573d4dbb45db6510d1a4deccb3e5a257504f7d5 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 -@@ -106,6 +106,7 @@ public class EntityStorage implements EntityPersistentStorage { - } - // Paper end - Entity load/save limit per chunk - CompoundTag compoundTagx = new CompoundTag(); -+ if (!entity.canSaveToDisk()) return; // Purpur - Add canSaveToDisk to Entity - if (entity.save(compoundTagx)) { - listTag.add(compoundTagx); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index e40665cead218502b44dd49051a53326ed94f061..a68f27288604b6f6755efe3c8ea612e295cb1656 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -289,7 +289,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise - - // Paper start - private static void printOversizedLog(String msg, Path file, int x, int z) { -- org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed."); -+ org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PURPUR - You may ask for help on Discord, but do not file an issue. These error messages can not be removed."); // Purpur - Rebrand - } - - private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { -diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -index d8b4196adf955f8d414688dc451caac2d9c609d9..80a43def4912a3228cd95117d5c2aac68798b4ec 100644 ---- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -@@ -9,7 +9,7 @@ import javax.annotation.Nullable; - import net.minecraft.world.entity.Entity; - - public class EntityTickList { -- private final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system -+ public final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system // Pufferfish - private->public - - private void ensureActiveIsNotIterated() { - // Paper - rewrite chunk system -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 021221da5d0315f6e371380a705ac6b3f6ac18d3..27eb9a365006884c85603dc6d9dd8eee009c98b3 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -@@ -48,7 +48,7 @@ public class PhantomSpawner implements CustomSpawner { - int spawnAttemptMaxSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds; - this.nextTick += (spawnAttemptMinSeconds + randomsource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; - // Paper end - Ability to control player's insomnia and phantoms -- if (world.getSkyDarken() < 5 && world.dimensionType().hasSkyLight()) { -+ if (world.getSkyDarken() < world.purpurConfig.phantomSpawnMinSkyDarkness && world.dimensionType().hasSkyLight()) { // Purpur - return 0; - } else { - int i = 0; -@@ -60,10 +60,10 @@ public class PhantomSpawner implements CustomSpawner { - if (!entityplayer.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityplayer.isCreative())) { // Paper - Add phantom creative and insomniac controls - BlockPos blockposition = entityplayer.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 = entityplayer.getStats(); - int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); - boolean flag2 = true; -@@ -75,7 +75,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 - PhantomPreSpawnEvent -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 f4fbcbb8ff6d2677af1a02a0801a323c06dce9b1..6f024a29e8824a604ff0c74f88522d23423beb5c 100644 ---- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -@@ -266,7 +266,7 @@ public abstract class FlowingFluid extends Fluid { - } - } - -- if (j >= 2 && this.canConvertToSource(world)) { -+ if (j >= getRequiredSources(world) && this.canConvertToSource(world)) { // Purpur - BlockState iblockdata2 = world.getBlockState(blockposition_mutableblockposition.setWithOffset(pos, Direction.DOWN)); - FluidState fluid1 = iblockdata2.getFluidState(); - -@@ -356,6 +356,12 @@ public abstract class FlowingFluid extends Fluid { - - protected abstract boolean canConvertToSource(ServerLevel 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) { - Block block = state.getBlock(); - -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 884db3e64cb22ed765beec8f11ea309fcf810207..6e643c1a7f7e71cfd20603facaf224985ee81716 100644 ---- a/src/main/java/net/minecraft/world/level/material/LavaFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/LavaFluid.java -@@ -181,7 +181,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 -@@ -199,6 +199,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(ServerLevel world) { - return world.getGameRules().getBoolean(GameRules.RULE_LAVA_SOURCE_CONVERSION); -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 552925ba47c7475e2e1ec2ded0966f28ed3e50a5..1e741f36b79585f33abe413beafe00cf5205d54f 100644 ---- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java -@@ -81,6 +81,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 - Add BlockBreakBlockEvent - @Override - protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { -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 c84fd369d92932903c76bb2012602617d3e2d213..b65512f65e06865cc4d2964bd4ca2806784be738 100644 ---- a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java -+++ b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java -@@ -240,7 +240,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { - if ((node == null || node.costMalus < 0.0F) - && maxYStep > 0 - && (pathType != PathType.FENCE || this.canWalkOverFences()) -- && pathType != PathType.UNPASSABLE_RAIL -+ && (this.mob.level().purpurConfig.mobsIgnoreRails || pathType != PathType.UNPASSABLE_RAIL) // Purpur - Config to allow mobs to pathfind over rails - && pathType != PathType.TRAPDOOR - && pathType != PathType.POWDER_SNOW) { - node = this.tryJumpOn(x, y, z, maxYStep, prevFeetY, direction, nodeType, mutableBlockPos); -@@ -491,7 +491,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { - return PathType.TRAPDOOR; - } else if (blockState.is(Blocks.POWDER_SNOW)) { - return PathType.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 - Stonecutter damage - return PathType.DAMAGE_OTHER; - } else if (blockState.is(Blocks.HONEY_BLOCK)) { - return PathType.STICKY_HONEY; -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 90056822cd17f3d33d14b3f94b34750ee522a0a9..acdff7b4a00d563739fd301c3633a266875296fa 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalShape.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalShape.java -@@ -35,7 +35,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/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -index ae321b3b8d98e42ef07fd1f0f738c1a2b428f6db..26da9e7c25ef6a89482838010d8ed6bcf8c87511 100644 ---- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -+++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java -@@ -81,6 +81,7 @@ public class MapItemSavedData extends SavedData { - private final Map frameMarkers = Maps.newHashMap(); - private int trackedDecorationCount; - private org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper -+ public boolean isExplorerMap; // Purpur - - // CraftBukkit start - public final CraftMapView mapView; -diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java -index 5f27e1ce23f2ed68e4c8af1986fafce940dbf826..d8cf49cbd82ed12d23fa10a81a88cc4bcf1c0f10 100644 ---- a/src/main/java/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java -+++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java -@@ -66,6 +66,11 @@ public class EnchantedCountIncreaseFunction extends LootItemConditionalFunction - Entity entity = context.getOptionalParameter(LootContextParams.ATTACKING_ENTITY); - if (entity instanceof LivingEntity livingEntity) { - int i = EnchantmentHelper.getEnchantmentLevel(this.enchantment, livingEntity); -+ // Purpur start - Add an option to fix MC-3304 projectile looting -+ if (org.purpurmc.purpur.PurpurConfig.fixProjectileLootingTransfer && context.getOptionalParameter(LootContextParams.DIRECT_ATTACKING_ENTITY) instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) { -+ i = arrow.actualEnchantments.getLevel(this.enchantment); -+ } -+ // Purpur end - Add an option to fix MC-3304 projectile looting - if (i == 0) { - return stack; - } -diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java -index 6cf6d4ec7b9e43c7b2b4c0e2fb080964ff588130..e74866e5195a5eeae7666ad7be750edac5947094 100644 ---- a/src/main/java/net/minecraft/world/phys/AABB.java -+++ b/src/main/java/net/minecraft/world/phys/AABB.java -@@ -551,4 +551,10 @@ public class AABB { - public static AABB ofSize(Vec3 center, double dx, double dy, double dz) { - return new AABB(center.x - dx / 2.0, center.y - dy / 2.0, center.z - dz / 2.0, center.x + dx / 2.0, center.y + dy / 2.0, center.z + dz / 2.0); - } -+ -+ // 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 94ca0407303c4493ab4928b12ec6ecc75aaca549..a138e1b6b66d99f2035de054137a607aa6b7f0b9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -@@ -363,14 +363,26 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - - @Override - public Location getLocation() { -+ // Purpur start -+ if (this.isOnline()) { -+ return this.getPlayer().getLocation(); -+ } -+ // Purpur end -+ - CompoundTag data = this.getData(); - if (data == null) { - return null; - } - -- if (data.contains("Pos") && data.contains("Rotation")) { -- ListTag position = (ListTag) data.get("Pos"); -- ListTag rotation = (ListTag) data.get("Rotation"); -+ // Purpur start - OfflinePlayer API -+ //if (data.contains("Pos") && data.contains("Rotation")) { -+ ListTag position = data.getList("Pos", net.minecraft.nbt.Tag.TAG_DOUBLE); -+ ListTag rotation = data.getList("Rotation", net.minecraft.nbt.Tag.TAG_FLOAT); -+ -+ if (position.isEmpty() && rotation.isEmpty()) { -+ return null; -+ } -+ // Purpur end - OfflinePlayer API - - UUID uuid = new UUID(data.getLong("WorldUUIDMost"), data.getLong("WorldUUIDLeast")); - -@@ -381,9 +393,9 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - rotation.getFloat(0), - rotation.getFloat(1) - ); -- } -+ //} // Purpur - OfflinePlayer API - -- return null; -+ //return null; // Purpur - OfflinePlayer API - } - - @Override -@@ -626,4 +638,191 @@ 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 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.toPath()); -+ File playerDataFile = new File(playerDir, this.getUniqueId()+".dat"); -+ File playerDataFileOld = new File(playerDir, this.getUniqueId()+".dat_old"); -+ net.minecraft.Util.safeReplaceFile(playerDataFile.toPath(), tempFile.toPath(), playerDataFileOld.toPath()); -+ } 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 97b5d6ba2b19a7c730730c74175a29157aed1840..badf280a6b01b06e8148c552330872d64e6256b7 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -426,6 +426,20 @@ public final class CraftServer implements Server { - this.paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager); - this.pluginManager.paperPluginManager = this.paperPluginManager; - // Paper end -+ // 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 - - CraftRegistry.setMinecraftRegistry(console.registryAccess()); - -@@ -1086,6 +1100,7 @@ public final class CraftServer implements Server { - - org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot - this.console.paperConfigurations.reloadConfigs(this.console); -+ org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur - Purpur config files - for (ServerLevel world : this.console.getAllLevels()) { - // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty - world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) -@@ -1101,6 +1116,7 @@ public final class CraftServer implements Server { - } - } - world.spigotConfig.init(); // Spigot -+ world.purpurConfig.init(); // Purpur - Purpur config files - } - - Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper -@@ -1118,6 +1134,7 @@ public final class CraftServer implements Server { - org.spigotmc.SpigotConfig.registerCommands(); // Spigot - io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper - this.spark.registerCommandBeforePlugins(this); // Paper - spark -+ org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur - Purpur config files - this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); - this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); - -@@ -1626,6 +1643,59 @@ public final class CraftServer implements Server { - return true; - } - -+ // Purpur start - Added the ability to add combustible items -+ @Override -+ public void addFuel(org.bukkit.Material material, int burnTime) { -+ Preconditions.checkArgument(burnTime > 0, "BurnTime must be greater than 0"); -+ -+ net.minecraft.world.item.ItemStack itemStack = net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)); -+ MinecraftServer.getServer().fuelValues().values.put(itemStack.getItem(), burnTime); -+ } -+ -+ @Override -+ public void removeFuel(org.bukkit.Material material) { -+ net.minecraft.world.item.ItemStack itemStack = net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)); -+ MinecraftServer.getServer().fuelValues().values.keySet().removeIf(itemStack::is); -+ } -+ // Purpur end - Added the ability to add combustible items -+ // Purpur start - Debug Marker API -+ @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 - Debug Marker API -+ - @Override - public List getRecipesFor(ItemStack result) { - Preconditions.checkArgument(result != null, "ItemStack cannot be null"); -@@ -3031,6 +3101,18 @@ public final class CraftServer implements Server { - return CraftServer.this.console.paperConfigurations.createLegacyObject(CraftServer.this.console); - } - -+ // Purpur start - Purpur config files -+ @Override -+ public YamlConfiguration getPurpurConfig() { -+ return org.purpurmc.purpur.PurpurConfig.config; -+ } -+ -+ @Override -+ public java.util.Properties getServerProperties() { -+ return getProperties().properties; -+ } -+ // Purpur end - Purpur config files -+ - @Override - public void restart() { - org.spigotmc.RestartCommand.restart(); -@@ -3060,6 +3142,7 @@ public final class CraftServer implements Server { - @Override - public double[] getTPS() { - return new double[] { -+ net.minecraft.server.MinecraftServer.getServer().tps5s.getAverage(), // Purpur - Add 5 second tps average in /tps - net.minecraft.server.MinecraftServer.getServer().tps1.getAverage(), - net.minecraft.server.MinecraftServer.getServer().tps5.getAverage(), - net.minecraft.server.MinecraftServer.getServer().tps15.getAverage() -@@ -3270,4 +3353,17 @@ public final class CraftServer implements Server { - this.console.addPluginAllowingSleep(plugin.getName(), value); - } - // Paper end - API to check if the server is sleeping -+ -+ // Purpur start -+ @Override -+ public String getServerName() { -+ return this.getProperties().serverName; -+ } -+ // Purpur end -+ // Purpur start - Lagging threshold -+ @Override -+ public boolean isLagging() { -+ return getServer().lagging; -+ } -+ // Purpur end - Lagging threshold - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 92d9f0ea8f7810ae20d3996f49aefa539b4bcb69..5d7af6c1ec557d2a2813b87a64b8c8a99d2f87e0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -2374,6 +2374,49 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return (this.getHandle().getDragonFight() == null) ? null : new CraftDragonBattle(this.getHandle().getDragonFight()); - } - -+ // Purpur start - Add local difficulty api -+ public float getLocalDifficultyAt(Location location) { -+ return getHandle().getCurrentDifficultyAt(io.papermc.paper.util.MCUtil.toBlockPosition(location)).getEffectiveDifficulty(); -+ } -+ // Purpur end - Add local difficulty api -+ // Purpur start - Debug Marker API -+ @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 - Debug Marker API -+ - @Override - public Collection getStructures(int x, int z) { - return this.getStructures(x, z, struct -> true); -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 1c2439ffc1e407ff69286817d22f127470ce07ba..10aa600fab7146b330d46b5fd2fe596da222a70a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -176,6 +176,20 @@ public class Main { - .describedAs("Jar file"); - // Paper end - -+ // Purpur start - Fix pufferfish issues -+ acceptsAll(asList("pufferfish", "pufferfish-settings"), "File for pufferfish settings") -+ .withRequiredArg() -+ .ofType(File.class) -+ .defaultsTo(new File("pufferfish.yml")) -+ .describedAs("Yml file"); -+ // Purpur end - Fix pufferfish issues -+ // Purpur start - Purpur config files -+ acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings") -+ .withRequiredArg() -+ .ofType(File.class) -+ .defaultsTo(new File("purpur.yml")) -+ .describedAs("Yml file"); -+ // Purpur end - Purpur config files - // Paper start - acceptsAll(asList("server-name"), "Name of the server") - .withRequiredArg() -@@ -259,7 +273,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/block/CraftBeehive.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java -index 1a2a05160ba51d9c75f1ae6ae61d944d81428722..a86b026f2f420637d125cf697bcd07bf314c98aa 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java -@@ -16,8 +16,15 @@ import org.bukkit.entity.Bee; - - public class CraftBeehive extends CraftBlockEntityState implements Beehive { - -+ private final List> storage = new ArrayList<>(); // Purpur - Stored Bee API -+ - public CraftBeehive(World world, BeehiveBlockEntity tileEntity) { - super(world, tileEntity); -+ // Purpur start - load bees to be able to modify them individually - Stored Bee API -+ for(BeehiveBlockEntity.BeeData data : tileEntity.getStored()) { -+ storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(data, this)); -+ } -+ // Purpur end - Stored Bee API - } - - protected CraftBeehive(CraftBeehive state, Location location) { -@@ -76,14 +83,54 @@ public class CraftBeehive extends CraftBlockEntityState impl - } - } - -+ storage.clear(); // Purpur - Stored Bee API - return bees; - } - -+ // Purpur start - Stored Bee API -+ @Override -+ public Bee releaseEntity(org.purpurmc.purpur.entity.StoredEntity entity) { -+ ensureNoWorldGeneration(); -+ -+ if(!getEntities().contains(entity)) { -+ return null; -+ } -+ -+ if(isPlaced()) { -+ BeehiveBlockEntity beehive = ((BeehiveBlockEntity) this.getTileEntityFromWorld()); -+ BeehiveBlockEntity.BeeData data = ((org.purpurmc.purpur.entity.PurpurStoredBee) entity).getHandle(); -+ -+ List list = beehive.releaseBee(getHandle(), data, BeeReleaseStatus.BEE_RELEASED, true); -+ -+ if (list.size() == 1) { -+ storage.remove(entity); -+ -+ return (Bee) list.get(0).getBukkitEntity(); -+ } -+ } -+ -+ return null; -+ } -+ -+ @Override -+ public List> getEntities() { -+ return new ArrayList<>(storage); -+ } -+ // Purpur end - Stored Bee API -+ - @Override - public void addEntity(Bee entity) { - Preconditions.checkArgument(entity != null, "Entity must not be null"); - -+ int length = this.getSnapshot().getStored().size(); // Purpur - Stored Bee API - this.getSnapshot().addOccupant(((CraftBee) entity).getHandle()); -+ -+ // Purpur start - check if new bee was added, and if yes, add to stored bees - Stored Bee API -+ List storedBeeData = this.getSnapshot().getStored(); -+ if(length < storedBeeData.size()) { -+ storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(storedBeeData.getLast(), this)); -+ } -+ // Purpur end - Stored Bee API - } - - @Override -@@ -100,6 +147,7 @@ public class CraftBeehive extends CraftBlockEntityState impl - @Override - public void clearEntities() { - getSnapshot().clearBees(); -+ storage.clear(); // Purpur - Stored Bee API - } - // Paper end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java -index c1759aeb3e6ad0e4eb66cba3da1b120dd1dce812..1a91bc2e422db0eba65694ac046f1b362c6b0cd6 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java -@@ -73,7 +73,7 @@ public class CraftConduit extends CraftBlockEntityState impl - public int getRange() { - this.ensureNoWorldGeneration(); - ConduitBlockEntity conduit = (ConduitBlockEntity) this.getTileEntityFromWorld(); -- return (conduit != null) ? ConduitBlockEntity.getRange(conduit.effectBlocks) : 0; -+ return (conduit != null) ? ConduitBlockEntity.getRange(conduit.effectBlocks, this.world.getHandle()) : 0; // Purpur - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java -index 4e56018b64d11f76c8da43fd8f85c6de72204e36..36cec3ed39807e85013e4e3b98c979d7af37ce58 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 - Rebrand -+ String[] parts = message.split("\n"); -+ for (String part : parts) { -+ this.sendRawMessage(part); -+ } -+ // Purpur end - Rebrand - } - - @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 - Rebrand - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -index d657fd2c507a5b215aeab0a5f3e9c2ee892a27c8..3604d92c122b5c8be823098ce7b91e57e976589c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -@@ -21,12 +21,12 @@ public class CraftEndermite extends CraftMonster implements Endermite { - - @Override - public boolean isPlayerSpawned() { -- return false; -+ return getHandle().isPlayerSpawned(); // Purpur - Add back player spawned endermite API - } - - @Override - public void setPlayerSpawned(boolean playerSpawned) { -- // Nop -+ getHandle().setPlayerSpawned(playerSpawned); // Purpur - Add back player spawned endermite API - } - // 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 b25b10c24a379097233e61bcc10add841b6a7115..0e1b3f64d1a828b9c69efe45c511582880bdcb92 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -87,6 +87,23 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - this.entityType = CraftEntityType.minecraftToBukkit(entity.getType()); - } - -+ // Purpur start - API for any mob to burn daylight -+ @Override -+ public boolean isImmuneToFire() { -+ return getHandle().fireImmune(); -+ } -+ -+ @Override -+ public void setImmuneToFire(Boolean fireImmune) { -+ getHandle().immuneToFire = fireImmune; -+ } -+ -+ @Override -+ public boolean isInDaylight() { -+ return getHandle().isSunBurnTick(); -+ } -+ // Purpur end - API for any mob to burn daylight -+ - public static CraftEntity getEntity(CraftServer server, T entity) { - Preconditions.checkArgument(entity != null, "Unknown entity"); - -@@ -246,6 +263,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - boolean ignorePassengers = flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS); - // Don't allow teleporting between worlds while keeping passengers - if (flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS) && this.entity.isVehicle() && location.getWorld() != this.getWorld()) { -+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur - return false; - } - -@@ -1306,4 +1324,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - } - } - // Paper end - broadcast hurt animation -+ -+ // Purpur start - Ridables -+ @Override -+ public org.bukkit.entity.Player getRider() { -+ net.minecraft.world.entity.player.Player rider = getHandle().getRider(); -+ return rider != null ? (org.bukkit.entity.Player) rider.getBukkitEntity() : null; -+ } -+ -+ @Override -+ public boolean hasRider() { -+ return getHandle().getRider() != null; -+ } -+ -+ @Override -+ public boolean isRidable() { -+ return getHandle().isRidable(); -+ } -+ -+ @Override -+ public boolean isRidableInWater() { -+ return !getHandle().dismountsUnderwater(); -+ } -+ // Purpur end - Ridables - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index e345cdbfab44a0f5da80d738798dbb4424b7ab5c..856f12eb276c214f2f57a58a89a4da9eea34db2d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -273,6 +273,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 63cae1a2e95d8da17c45c4404a8dd0ca6a413c39..464a3713845548473a357ea66c6147b10ff2cb16 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java -@@ -27,4 +27,17 @@ public class CraftIronGolem extends CraftGolem implements IronGolem { - public void setPlayerCreated(boolean playerCreated) { - this.getHandle().setPlayerCreated(playerCreated); - } -+ -+ // Purpur start - Summoner API -+ @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 - Summoner API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -index 30d62ee4d5cd2ddacb8783b5bbbf475d592b3e02..762be3721419bfe33ea6577d3c6961204fdb0037 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -@@ -151,4 +151,51 @@ public class CraftItem extends CraftEntity implements Item { - public String toString() { - return "CraftItem"; - } -+ -+ // Purpur start - Item entity immunities -+ @Override -+ public void setImmuneToCactus(boolean immuneToCactus) { -+ this.getHandle().immuneToCactus = immuneToCactus; -+ } -+ -+ @Override -+ public boolean isImmuneToCactus() { -+ return this.getHandle().immuneToCactus; -+ } -+ -+ @Override -+ public void setImmuneToExplosion(boolean immuneToExplosion) { -+ this.getHandle().immuneToExplosion = immuneToExplosion; -+ } -+ -+ @Override -+ public boolean isImmuneToExplosion() { -+ return this.getHandle().immuneToExplosion; -+ } -+ -+ @Override -+ public void setImmuneToFire(@org.jetbrains.annotations.Nullable Boolean immuneToFire) { -+ this.getHandle().immuneToFire = (immuneToFire != null && immuneToFire); -+ } -+ -+ @Override -+ public void setImmuneToFire(boolean immuneToFire) { -+ this.setImmuneToFire((Boolean) immuneToFire); -+ } -+ -+ @Override -+ public boolean isImmuneToFire() { -+ return this.getHandle().immuneToFire; -+ } -+ -+ @Override -+ public void setImmuneToLightning(boolean immuneToLightning) { -+ this.getHandle().immuneToLightning = immuneToLightning; -+ } -+ -+ @Override -+ public boolean isImmuneToLightning() { -+ return this.getHandle().immuneToLightning; -+ } -+ // Purpur end - Item entity immunities - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index 4f98d138a275a6c34528b7a5148ef265bc38d6b5..72498e233ece886941cca268e729336d66042402 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -523,7 +523,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 - -@@ -1211,4 +1211,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - return this.getHandle().canUseSlot(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); - } - // Paper end - Expose canUseSlot -+ -+ // Purpur start - API for any mob to burn daylight -+ @Override -+ public boolean shouldBurnInDay() { -+ return this.getHandle().shouldBurnInDay(); -+ } -+ -+ @Override -+ public void setShouldBurnInDay(final boolean shouldBurnInDay) { -+ this.getHandle().setShouldBurnInDay(shouldBurnInDay); -+ } -+ // Purpur end - API for any mob to burn daylight - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -index 351f42842b780d053cd2e5bad9ae299449141b10..4860574e7fad7a9527dda599703c573c5b4b234b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -@@ -90,4 +90,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 6a647cab8b2e476987931486e290703b8726f2c7..c2df3c38f58d8dcb5e3d62077655af56a3bffd65 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -584,10 +584,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void setPlayerListName(String name) { -+ // Purpur start - AFK API -+ setPlayerListName(name, false); -+ } -+ public void setPlayerListName(String name, boolean useMM) { -+ // Purpur end - AFK API - if (name == null) { - name = this.getName(); - } -- this.getHandle().listName = name.equals(this.getName()) ? null : CraftChatMessage.fromStringOrNull(name); -+ this.getHandle().listName = name.equals(this.getName()) ? null : useMM ? io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name)) : CraftChatMessage.fromStringOrNull(name); // Purpur - AFK API - if (this.getHandle().connection == null) return; // Paper - Updates are possible before the player has fully joined - for (ServerPlayer player : (List) this.server.getHandle().players) { - if (player.getBukkitEntity().canSee(this)) { -@@ -1444,6 +1449,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - // Paper start - Teleport passenger API - // Don't allow teleporting between worlds while keeping passengers - if (ignorePassengers && entity.isVehicle() && location.getWorld() != this.getWorld()) { -+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur start - return false; - } - -@@ -1465,6 +1471,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API -+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur start - return false; - } - -@@ -2763,6 +2770,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) { - Preconditions.checkArgument(value <= 1f && value >= -1f, "Speed value (%s) need to be between -1f and 1f", value); - } -@@ -3565,4 +3594,73 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundEntityEventPacket(((CraftEntity) target).getHandle(), effect.getData())); - } - // Paper end - entity effect API -+ -+ // Purpur start - Purpur client support -+ @Override -+ public boolean usesPurpurClient() { -+ return getHandle().purpurClient; -+ } -+ // Purpur end - Purpur client support -+ // Purpur start - AFK API -+ @Override -+ public boolean isAfk() { -+ return getHandle().isAfk(); -+ } -+ -+ @Override -+ public void setAfk(boolean setAfk) { -+ getHandle().setAfk(setAfk); -+ } -+ -+ @Override -+ public void resetIdleTimer() { -+ getHandle().resetLastActionTime(); -+ } -+ // Purpur end - AFK API -+ // Purpur start - Debug Marker API -+ @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; -+ this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket(new net.minecraft.network.protocol.common.custom.GameTestAddMarkerDebugPayload(io.papermc.paper.util.MCUtil.toBlockPosition(location), argb, text, 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() { -+ if (this.getHandle().connection == null) return; -+ this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket(new net.minecraft.network.protocol.common.custom.GameTestClearMarkersDebugPayload())); -+ } -+ // Purpur end - Debug Marker API -+ // Purpur start - Add death screen API -+ @Override -+ public void sendDeathScreen(net.kyori.adventure.text.Component message) { -+ if (this.getHandle().connection == null) return; -+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket(getEntityId(), io.papermc.paper.adventure.PaperAdventure.asVanilla(message))); -+ } -+ // Purpur end - Add death screen API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -index 4ce2373ff71c3c1b8951646e057587a3ab09e145..997b8e5059569de4ee8e70127c5d6019ce53afe3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -@@ -28,4 +28,17 @@ public class CraftSnowman extends CraftGolem implements Snowman, com.destroystok - public String toString() { - return "CraftSnowman"; - } -+ -+ // Purpur start - Summoner API -+ @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 - Summoner API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -index 8e895d6f84f7d84b219f2424909dd42e5f08dec4..e5597563a6ed620ab9c9e81be4bad56fd5308305 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -@@ -375,4 +375,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { - getHandle().getGossips().gossips.clear(); - } - // Paper end -+ -+ // Purpur start - Lobotomize stuck villagers -+ @Override -+ public boolean isLobotomized() { -+ return getHandle().isLobotomized(); -+ } -+ // Purpur end - Lobotomize stuck villagers - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -index 7881c6253c1d652c0c0d54a9a8accdf0a1ff0f3e..fe8be71121324f64346174922c7bc7f5d3a9de69 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -@@ -99,4 +99,17 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok - this.getHandle().makeInvulnerable(); - } - // Paper end -+ -+ // Purpur start - Summoner API -+ @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 - Summoner API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java -index ecd33b4add46acbe4e4f8879c0601220423d66ca..001f131117c277e46f4a94f73da36d1b219fe3cd 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java -@@ -146,4 +146,15 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf { - return this.getKey().hashCode(); - } - } -+ // Purpur start - Configurable chance for wolves to spawn rabid -+ @Override -+ public boolean isRabid() { -+ return getHandle().isRabid(); -+ } -+ -+ @Override -+ public void setRabid(boolean isRabid) { -+ getHandle().setRabid(isRabid); -+ } -+ // Purpur end - Configurable chance for wolves to spawn rabid - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index e37aaf77f94b97b736cc20ef070cefdff0400188..d52d41d8c56e017f95914da19b05c3d79f8f1640 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -602,6 +602,15 @@ public class CraftEventFactory { - // Paper end - craftServer.getPluginManager().callEvent(event); - -+ // Purpur start - Ridables -+ if (who != null) { -+ switch (action) { -+ case LEFT_CLICK_BLOCK, LEFT_CLICK_AIR -> who.processClick(InteractionHand.MAIN_HAND); -+ case RIGHT_CLICK_BLOCK, RIGHT_CLICK_AIR -> who.processClick(InteractionHand.OFF_HAND); -+ } -+ } -+ // Purpur end - Ridables -+ - return event; - } - -@@ -1131,7 +1140,7 @@ public class CraftEventFactory { - return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.LAVA, bukkitDamageSource, modifiers, modifierFunctions, cancelled); - } else if (source.getDirectBlock() != null) { - DamageCause cause; -- if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL)) { -+ if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL) || source.isStonecutter()) { // Purpur - Stonecutter damage - cause = DamageCause.CONTACT; - } else if (source.is(DamageTypes.HOT_FLOOR)) { - cause = DamageCause.HOT_FLOOR; -@@ -1191,6 +1200,7 @@ public class CraftEventFactory { - EntityDamageEvent event; - if (damager != null) { - event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, critical); -+ damager.processClick(InteractionHand.MAIN_HAND); // Purpur - Ridables - } else { - event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java -index 6d3f9d5dab6c9a2860ae31cae24310aa2d62da7c..4f29c579f94efe59a8c78520d75676fc4875e2f0 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java -@@ -145,8 +145,19 @@ public class CraftContainer extends AbstractContainerMenu { - case PLAYER: - case CHEST: - case ENDER_CHEST: -+ // Purpur start -+ this.delegate = new ChestMenu(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? net.minecraft.world.inventory.MenuType.GENERIC_9x6 : net.minecraft.world.inventory.MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9); -+ break; - case BARREL: -- this.delegate = new ChestMenu(net.minecraft.world.inventory.MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9); -+ this.delegate = new ChestMenu(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { -+ case 6 -> net.minecraft.world.inventory.MenuType.GENERIC_9x6; -+ case 5 -> net.minecraft.world.inventory.MenuType.GENERIC_9x5; -+ case 4 -> net.minecraft.world.inventory.MenuType.GENERIC_9x4; -+ case 2 -> net.minecraft.world.inventory.MenuType.GENERIC_9x2; -+ case 1 -> net.minecraft.world.inventory.MenuType.GENERIC_9x1; -+ default -> net.minecraft.world.inventory.MenuType.GENERIC_9x3; -+ }, windowId, bottom, top, top.getContainerSize() / 9); -+ // Purpur end - break; - case DISPENSER: - case DROPPER: -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -index c6159c70f7a37b9bffe268b91905ce848d1d2927..d02adaaa6fbdc1c0eff44cb4a1f1642f9575a821 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -@@ -84,7 +84,7 @@ public class CraftInventory implements Inventory { - - @Override - public void setContents(ItemStack[] items) { -- Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize()); -+ // Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize()); // Purpur - - for (int i = 0; i < this.getSize(); i++) { - if (i >= items.length) { -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java -index 792cb6adf0c7a6335cc5985fce8bed2e0f1149af..5734c5caffda79383ae30df20c3defb51b87f39e 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java -@@ -19,6 +19,10 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn - private int repairCost; - private int repairCostAmount; - private int maximumRepairCost; -+ // Purpur start - Anvil API -+ private boolean bypassCost; -+ private boolean canDoUnsafeEnchants; -+ // Purpur end - Anvil API - - public CraftInventoryAnvil(Location location, Container inventory, Container resultInventory) { - super(inventory, resultInventory); -@@ -27,6 +31,10 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn - this.repairCost = CraftInventoryAnvil.DEFAULT_REPAIR_COST; - this.repairCostAmount = CraftInventoryAnvil.DEFAULT_REPAIR_COST_AMOUNT; - this.maximumRepairCost = CraftInventoryAnvil.DEFAULT_MAXIMUM_REPAIR_COST; -+ // Purpur start - Anvil API -+ this.bypassCost = false; -+ this.canDoUnsafeEnchants = false; -+ // Purpur end - Anvil API - } - - @Override -@@ -113,4 +121,30 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn - consumer.accept(cav); - } - } -+ -+ // Purpur start - Anvil API -+ @Override -+ public boolean canBypassCost() { -+ this.syncWithArbitraryViewValue((cav) -> this.bypassCost = cav.canBypassCost()); -+ return this.bypassCost; -+ } -+ -+ @Override -+ public void setBypassCost(boolean bypassCost) { -+ this.bypassCost = bypassCost; -+ this.syncViews((cav) -> cav.setBypassCost(bypassCost)); -+ } -+ -+ @Override -+ public boolean canDoUnsafeEnchants() { -+ this.syncWithArbitraryViewValue((cav) -> this.canDoUnsafeEnchants = cav.canDoUnsafeEnchants()); -+ return this.canDoUnsafeEnchants; -+ } -+ -+ @Override -+ public void setDoUnsafeEnchants(boolean canDoUnsafeEnchants) { -+ this.canDoUnsafeEnchants = canDoUnsafeEnchants; -+ this.syncViews((cav) -> cav.setDoUnsafeEnchants(canDoUnsafeEnchants)); -+ } -+ // Purpur end - Anvil API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -index 78975412da0f0c2b802bfce6d30d56b26d8023e2..4ec6a07796023aab2f8f84f131f48108c235c852 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java -@@ -658,4 +658,285 @@ public final class CraftItemStack extends ItemStack { - } - - // Paper end - data component API -+ -+ // Purpur start -+ @Override -+ public String getDisplayName() { -+ return getItemMeta().getDisplayName(); -+ } -+ -+ @Override -+ public void setDisplayName(String name) { -+ ItemMeta itemMeta = getItemMeta(); -+ itemMeta.setDisplayName(name); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public boolean hasDisplayName() { -+ return hasItemMeta() && getItemMeta().hasDisplayName(); -+ } -+ -+ @Override -+ public String getLocalizedName() { -+ return getItemMeta().getLocalizedName(); -+ } -+ -+ @Override -+ public void setLocalizedName(String name) { -+ ItemMeta itemMeta = getItemMeta(); -+ itemMeta.setLocalizedName(name); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public boolean hasLocalizedName() { -+ return hasItemMeta() && getItemMeta().hasLocalizedName(); -+ } -+ -+ @Override -+ public boolean hasLore() { -+ return hasItemMeta() && getItemMeta().hasLore(); -+ } -+ -+ @Override -+ public boolean hasEnchant(Enchantment ench) { -+ return hasItemMeta() && getItemMeta().hasEnchant(ench); -+ } -+ -+ @Override -+ public int getEnchantLevel(Enchantment ench) { -+ return getItemMeta().getEnchantLevel(ench); -+ } -+ -+ @Override -+ public Map getEnchants() { -+ return getItemMeta().getEnchants(); -+ } -+ -+ @Override -+ public boolean addEnchant(Enchantment ench, int level, boolean ignoreLevelRestriction) { -+ ItemMeta itemMeta = getItemMeta(); -+ boolean result = itemMeta.addEnchant(ench, level, ignoreLevelRestriction); -+ setItemMeta(itemMeta); -+ return result; -+ } -+ -+ @Override -+ public boolean removeEnchant(Enchantment ench) { -+ ItemMeta itemMeta = getItemMeta(); -+ boolean result = itemMeta.removeEnchant(ench); -+ setItemMeta(itemMeta); -+ return result; -+ } -+ -+ @Override -+ public boolean hasEnchants() { -+ return hasItemMeta() && getItemMeta().hasEnchants(); -+ } -+ -+ @Override -+ public boolean hasConflictingEnchant(Enchantment ench) { -+ return hasItemMeta() && getItemMeta().hasConflictingEnchant(ench); -+ } -+ -+ @Override -+ public void setCustomModelData(Integer data) { -+ ItemMeta itemMeta = getItemMeta(); -+ itemMeta.setCustomModelData(data); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public int getCustomModelData() { -+ return getItemMeta().getCustomModelData(); -+ } -+ -+ @Override -+ public boolean hasCustomModelData() { -+ return hasItemMeta() && getItemMeta().hasCustomModelData(); -+ } -+ -+ @Override -+ public boolean hasBlockData() { -+ return hasItemMeta() && ((org.bukkit.inventory.meta.BlockDataMeta) getItemMeta()).hasBlockData(); -+ } -+ -+ @Override -+ public org.bukkit.block.data.BlockData getBlockData(Material material) { -+ return ((org.bukkit.inventory.meta.BlockDataMeta) getItemMeta()).getBlockData(material); -+ } -+ -+ @Override -+ public void setBlockData(org.bukkit.block.data.BlockData blockData) { -+ ItemMeta itemMeta = getItemMeta(); -+ ((org.bukkit.inventory.meta.BlockDataMeta) itemMeta).setBlockData(blockData); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public int getRepairCost() { -+ return ((org.bukkit.inventory.meta.Repairable) getItemMeta()).getRepairCost(); -+ } -+ -+ @Override -+ public void setRepairCost(int cost) { -+ ItemMeta itemMeta = getItemMeta(); -+ ((org.bukkit.inventory.meta.Repairable) itemMeta).setRepairCost(cost); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public boolean hasRepairCost() { -+ return hasItemMeta() && ((org.bukkit.inventory.meta.Repairable) getItemMeta()).hasRepairCost(); -+ } -+ -+ @Override -+ public boolean isUnbreakable() { -+ return hasItemMeta() && getItemMeta().isUnbreakable(); -+ } -+ -+ @Override -+ public void setUnbreakable(boolean unbreakable) { -+ ItemMeta itemMeta = getItemMeta(); -+ itemMeta.setUnbreakable(unbreakable); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public boolean hasAttributeModifiers() { -+ return hasItemMeta() && getItemMeta().hasAttributeModifiers(); -+ } -+ -+ @Override -+ public com.google.common.collect.Multimap getAttributeModifiers() { -+ return getItemMeta().getAttributeModifiers(); -+ } -+ -+ @Override -+ public com.google.common.collect.Multimap getAttributeModifiers(org.bukkit.inventory.EquipmentSlot slot) { -+ return getItemMeta().getAttributeModifiers(slot); -+ } -+ -+ @Override -+ public java.util.Collection getAttributeModifiers(org.bukkit.attribute.Attribute attribute) { -+ return getItemMeta().getAttributeModifiers(attribute); -+ } -+ -+ @Override -+ public boolean addAttributeModifier(org.bukkit.attribute.Attribute attribute, org.bukkit.attribute.AttributeModifier modifier) { -+ ItemMeta itemMeta = getItemMeta(); -+ boolean result = itemMeta.addAttributeModifier(attribute, modifier); -+ setItemMeta(itemMeta); -+ return result; -+ } -+ -+ @Override -+ public void setAttributeModifiers(com.google.common.collect.Multimap attributeModifiers) { -+ ItemMeta itemMeta = getItemMeta(); -+ itemMeta.setAttributeModifiers(attributeModifiers); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public boolean removeAttributeModifier(org.bukkit.attribute.Attribute attribute) { -+ ItemMeta itemMeta = getItemMeta(); -+ boolean result = itemMeta.removeAttributeModifier(attribute); -+ setItemMeta(itemMeta); -+ return result; -+ } -+ -+ @Override -+ public boolean removeAttributeModifier(org.bukkit.inventory.EquipmentSlot slot) { -+ ItemMeta itemMeta = getItemMeta(); -+ boolean result = itemMeta.removeAttributeModifier(slot); -+ setItemMeta(itemMeta); -+ return result; -+ } -+ -+ @Override -+ public boolean removeAttributeModifier(org.bukkit.attribute.Attribute attribute, org.bukkit.attribute.AttributeModifier modifier) { -+ ItemMeta itemMeta = getItemMeta(); -+ boolean result = itemMeta.removeAttributeModifier(attribute, modifier); -+ setItemMeta(itemMeta); -+ return result; -+ } -+ -+ @Override -+ public boolean hasDamage() { -+ return hasItemMeta() && ((org.bukkit.inventory.meta.Damageable) getItemMeta()).hasDamage(); -+ } -+ -+ @Override -+ public int getDamage() { -+ return ((org.bukkit.inventory.meta.Damageable) getItemMeta()).getDamage(); -+ } -+ -+ @Override -+ public void setDamage(int damage) { -+ ItemMeta itemMeta = getItemMeta(); -+ ((org.bukkit.inventory.meta.Damageable) itemMeta).setDamage(damage); -+ setItemMeta(itemMeta); -+ } -+ -+ @Override -+ public void repair() { -+ repair(1); -+ } -+ -+ @Override -+ public boolean damage() { -+ return damage(1); -+ } -+ -+ @Override -+ public void repair(int amount) { -+ damage(-amount); -+ } -+ -+ @Override -+ public boolean damage(int amount) { -+ return damage(amount, false); -+ } -+ -+ @Override -+ public boolean damage(int amount, boolean ignoreUnbreaking) { -+ org.bukkit.inventory.meta.Damageable damageable = (org.bukkit.inventory.meta.Damageable) getItemMeta(); -+ if (amount > 0) { -+ int unbreaking = getEnchantLevel(Enchantment.UNBREAKING); -+ 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); -+ } -+ -+ private 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/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -index 4864e2016cb1d377425297fd1c52b383632cb59e..fc0e93a936dadb0dca758207297f92a22f3955d4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -@@ -36,6 +36,7 @@ public interface CraftRecipe extends Recipe { - stack = Ingredient.of(((RecipeChoice.MaterialChoice) bukkit).getChoices().stream().map((mat) -> CraftItemType.bukkitToMinecraft(mat))); - } else if (bukkit instanceof RecipeChoice.ExactChoice) { - stack = Ingredient.ofStacks(((RecipeChoice.ExactChoice) bukkit).getChoices().stream().map((mat) -> CraftItemStack.asNMSCopy(mat)).toList()); -+ stack.predicate = ((RecipeChoice.ExactChoice) bukkit).getPredicate(); // Purpur - // Paper start - support "empty" choices - legacy method that spigot might incorrectly call - // Their impl of Ingredient.of() will error, ingredients need at least one entry. - // Callers running into this exception may have passed an incorrect empty() recipe choice to a non-empty slot or -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java -index 7c989318dc7ad89bb0d9143fcaac1e4bba6f5907..143a4d4efcc989ed4a4c73cc304e1978ad8f0699 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java -@@ -44,6 +44,6 @@ public class CraftShapelessRecipe extends ShapelessRecipe implements CraftRecipe - data.add(this.toNMS(i, true)); - } - -- MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftRecipe.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.ShapelessRecipe(this.getGroup(), CraftRecipe.getCategory(this.getCategory()), CraftItemStack.asNMSCopy(this.getResult()), data))); -+ MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftRecipe.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.ShapelessRecipe(this.getGroup(), CraftRecipe.getCategory(this.getCategory()), CraftItemStack.asNMSCopy(this.getResult()), data, true))); // Pufferfish - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java b/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java -index f86c95a13dff012de5db3e41ac261e9e8d44d9f3..1db0b790d824e419bb5fb6ab1f3003e120f9763b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java -@@ -75,4 +75,26 @@ public class CraftAnvilView extends CraftInventoryView experience - 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..7690441b5059ae6c7ca8519875ea8a515c5c5e93 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java -@@ -0,0 +1,606 @@ -+package org.purpurmc.purpur; -+ -+import com.google.common.base.Throwables; -+import com.google.common.collect.ImmutableMap; -+import com.mojang.datafixers.util.Pair; -+import net.kyori.adventure.bossbar.BossBar; -+import net.kyori.adventure.text.minimessage.MiniMessage; -+import net.minecraft.core.Registry; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.world.effect.MobEffect; -+import net.minecraft.world.effect.MobEffectInstance; -+import net.minecraft.world.entity.EntityDimensions; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.food.FoodProperties; -+import net.minecraft.world.food.Foods; -+import net.minecraft.world.item.enchantment.Enchantment; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockBehaviour; -+import org.bukkit.Bukkit; -+import org.bukkit.command.Command; -+import org.bukkit.configuration.ConfigurationSection; -+import org.bukkit.configuration.InvalidConfigurationException; -+import org.bukkit.configuration.file.YamlConfiguration; -+import org.purpurmc.purpur.command.PurpurCommand; -+import org.purpurmc.purpur.task.TPSBarTask; -+ -+import java.io.File; -+import java.io.IOException; -+import java.lang.reflect.InvocationTargetException; -+import java.lang.reflect.Method; -+import java.lang.reflect.Modifier; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.List; -+import java.util.Map; -+import java.util.Set; -+import java.util.logging.Level; -+ -+@SuppressWarnings("unused") -+public class PurpurConfig { -+ private static final String HEADER = "This is the main configuration file for Purpur.\n" -+ + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n" -+ + "with caution, and make sure you know what each option does before configuring.\n" -+ + "\n" -+ + "If you need help with the configuration or have any questions related to Purpur,\n" -+ + "join us in our Discord guild.\n" -+ + "\n" -+ + "Website: https://purpurmc.org \n" -+ + "Docs: https://purpurmc.org/docs \n"; -+ private static File CONFIG_FILE; -+ public static YamlConfiguration config; -+ -+ private static Map commands; -+ -+ public static int version; -+ static boolean verbose; -+ -+ public static void init(File configFile) { -+ CONFIG_FILE = configFile; -+ config = new YamlConfiguration(); -+ try { -+ config.load(CONFIG_FILE); -+ } catch (IOException ignore) { -+ } catch (InvalidConfigurationException ex) { -+ Bukkit.getLogger().log(Level.SEVERE, "Could not load purpur.yml, please correct your syntax errors", ex); -+ throw Throwables.propagate(ex); -+ } -+ config.options().header(HEADER); -+ config.options().copyDefaults(true); -+ verbose = getBoolean("verbose", false); -+ -+ commands = new HashMap<>(); -+ commands.put("purpur", new PurpurCommand("purpur")); -+ -+ version = getInt("config-version", 38); -+ set("config-version", 38); -+ -+ readConfig(PurpurConfig.class, null); -+ -+ Block.BLOCK_STATE_REGISTRY.forEach(BlockBehaviour.BlockStateBase::initCache); -+ } -+ -+ protected static void log(String s) { -+ if (verbose) { -+ log(Level.INFO, s); -+ } -+ } -+ -+ protected static void log(Level level, String s) { -+ Bukkit.getLogger().log(level, s); -+ } -+ -+ public static void registerCommands() { -+ for (Map.Entry entry : commands.entrySet()) { -+ MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Purpur", entry.getValue()); -+ } -+ } -+ -+ static void readConfig(Class clazz, Object instance) { -+ for (Method method : clazz.getDeclaredMethods()) { -+ if (Modifier.isPrivate(method.getModifiers())) { -+ if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) { -+ try { -+ method.setAccessible(true); -+ method.invoke(instance); -+ } catch (InvocationTargetException ex) { -+ throw Throwables.propagate(ex.getCause()); -+ } catch (Exception ex) { -+ Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex); -+ } -+ } -+ } -+ } -+ -+ try { -+ config.save(CONFIG_FILE); -+ } catch (IOException ex) { -+ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex); -+ } -+ } -+ -+ private static void set(String path, Object val) { -+ config.addDefault(path, val); -+ config.set(path, val); -+ } -+ -+ private static String getString(String path, String def) { -+ config.addDefault(path, def); -+ return config.getString(path, config.getString(path)); -+ } -+ -+ private static boolean getBoolean(String path, boolean def) { -+ config.addDefault(path, def); -+ return config.getBoolean(path, config.getBoolean(path)); -+ } -+ -+ private static double getDouble(String path, double def) { -+ config.addDefault(path, def); -+ return config.getDouble(path, config.getDouble(path)); -+ } -+ -+ private static int getInt(String path, int def) { -+ config.addDefault(path, def); -+ return config.getInt(path, config.getInt(path)); -+ } -+ -+ private static List getList(String path, T def) { -+ config.addDefault(path, def); -+ return config.getList(path, config.getList(path)); -+ } -+ -+ static Map getMap(String path, Map def) { -+ if (def != null && config.getConfigurationSection(path) == null) { -+ config.addDefault(path, def); -+ return def; -+ } -+ return toMap(config.getConfigurationSection(path)); -+ } -+ -+ private static Map toMap(ConfigurationSection section) { -+ ImmutableMap.Builder builder = ImmutableMap.builder(); -+ if (section != null) { -+ for (String key : section.getKeys(false)) { -+ Object obj = section.get(key); -+ if (obj != null) { -+ builder.put(key, obj instanceof ConfigurationSection val ? toMap(val) : obj); -+ } -+ } -+ } -+ return builder.build(); -+ } -+ -+ 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 boolean afkBroadcastUseDisplayName = false; -+ 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"; -+ public static String sleepNotPossible = "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); -+ afkBroadcastUseDisplayName = getBoolean("settings.messages.afk-broadcast-use-display-name", afkBroadcastUseDisplayName); -+ 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); -+ sleepNotPossible = getString("settings.messages.sleep-not-possible", sleepNotPossible); -+ } -+ -+ 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 = io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); -+ 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; -+ public static boolean magmaBlockReverseBubbleColumnFlow = false; -+ public static boolean soulSandBlockReverseBubbleColumnFlow = false; -+ private static void blockSettings() { -+ if (version < 3) { -+ boolean oldValue = getBoolean("settings.barrel.packed-barrels", true); -+ set("settings.blocks.barrel.six-rows", oldValue); -+ set("settings.packed-barrels", null); -+ oldValue = getBoolean("settings.large-ender-chests", true); -+ set("settings.blocks.ender_chest.six-rows", oldValue); -+ set("settings.large-ender-chests", null); -+ } -+ if (version < 20) { -+ boolean oldValue = getBoolean("settings.blocks.barrel.six-rows", false); -+ set("settings.blocks.barrel.rows", oldValue ? 6 : 3); -+ set("settings.blocks.barrel.six-rows", null); -+ } -+ barrelRows = getInt("settings.blocks.barrel.rows", barrelRows); -+ if (barrelRows < 1 || barrelRows > 6) { -+ Bukkit.getLogger().severe("settings.blocks.barrel.rows must be 1-6, resetting to default"); -+ barrelRows = 3; -+ } -+ org.bukkit.event.inventory.InventoryType.BARREL.setDefaultSize(switch (barrelRows) { -+ case 6 -> 54; -+ case 5 -> 45; -+ case 4 -> 36; -+ case 2 -> 18; -+ case 1 -> 9; -+ default -> 27; -+ }); -+ enderChestSixRows = getBoolean("settings.blocks.ender_chest.six-rows", enderChestSixRows); -+ org.bukkit.event.inventory.InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27); -+ enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); -+ 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 -> { -+ Registry registry = MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT); -+ Enchantment enchantment = registry.getValue(ResourceLocation.parse(key.toString())); -+ if (enchantment == null) return; -+ 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"); -+ } -+ magmaBlockReverseBubbleColumnFlow = getBoolean("settings.blocks.magma-block.reverse-bubble-column-flow", magmaBlockReverseBubbleColumnFlow); -+ soulSandBlockReverseBubbleColumnFlow = getBoolean("settings.blocks.soul-sand.reverse-bubble-column-flow", soulSandBlockReverseBubbleColumnFlow); -+ } -+ -+ public static boolean allowInapplicableEnchants = false; -+ public static boolean allowIncompatibleEnchants = false; -+ public static boolean allowHigherEnchantsLevels = false; -+ public static boolean allowUnsafeEnchantCommand = false; -+ public static boolean replaceIncompatibleEnchants = false; -+ public static boolean clampEnchantLevels = true; -+ private static void enchantmentSettings() { -+ 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); -+ } -+ if (version < 37) { -+ boolean allowUnsafeEnchants = getBoolean("settings.enchantment.anvil.allow-unsafe-enchants", false); -+ if (!allowUnsafeEnchants) { -+ set("settings.enchantment.anvil.allow-inapplicable-enchants", false); -+ set("settings.enchantment.anvil.allow-incompatible-enchants", false); -+ set("settings.enchantment.anvil.allow-higher-enchants-levels", false); -+ } -+ set("settings.enchantment.anvil.allow-unsafe-enchants", null); -+ } -+ 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", allowUnsafeEnchantCommand); -+ replaceIncompatibleEnchants = getBoolean("settings.enchantment.anvil.replace-incompatible-enchants", replaceIncompatibleEnchants); -+ 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 beeCountPayload = false; -+ private static void beeCountPayload() { -+ beeCountPayload = getBoolean("settings.bee-count-payload", beeCountPayload); -+ } -+ -+ 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); -+ } -+ -+ 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.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(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(); -+ }); -+ } -+ -+ public static boolean playerDeathsAlwaysShowItem = false; -+ private static void playerDeathsAlwaysShowItem() { -+ playerDeathsAlwaysShowItem = getBoolean("settings.player-deaths-always-show-item", playerDeathsAlwaysShowItem); -+ } -+ -+ public static boolean registerMinecraftDebugCommands = false; -+ private static void registerMinecraftDebugCommands() { -+ registerMinecraftDebugCommands = getBoolean("settings.register-minecraft-debug-commands", registerMinecraftDebugCommands); -+ } -+ -+ public static List startupCommands = new ArrayList<>(); -+ private static void startupCommands() { -+ startupCommands.clear(); -+ getList("settings.startup-commands", new ArrayList()).forEach(line -> { -+ String command = line.toString(); -+ if (command.startsWith("/")) { -+ command = command.substring(1); -+ } -+ startupCommands.add(command); -+ }); -+ } -+} -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..0d5a0e14cbaacc63eeced78a6c28cc64ad918522 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -@@ -0,0 +1,3447 @@ -+package org.purpurmc.purpur; -+ -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.monster.Shulker; -+import net.minecraft.world.item.DyeColor; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.item.Items; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.properties.Tilt; -+import org.purpurmc.purpur.tool.Flattenable; -+import org.purpurmc.purpur.tool.Strippable; -+import org.purpurmc.purpur.tool.Tillable; -+import org.purpurmc.purpur.tool.Waxable; -+import org.purpurmc.purpur.tool.Weatherable; -+import org.apache.commons.lang.BooleanUtils; -+import org.bukkit.ChatColor; -+import org.bukkit.World; -+import org.bukkit.configuration.ConfigurationSection; -+import java.util.ArrayList; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Map; -+import java.util.function.Predicate; -+import java.util.logging.Level; -+import static org.purpurmc.purpur.PurpurConfig.log; -+ -+@SuppressWarnings("unused") -+public class PurpurWorldConfig { -+ -+ private final String worldName; -+ private final World.Environment environment; -+ -+ public PurpurWorldConfig(String worldName, World.Environment environment) { -+ this.worldName = worldName; -+ this.environment = environment; -+ init(); -+ } -+ -+ public void init() { -+ log("-------- World Settings For [" + worldName + "] --------"); -+ PurpurConfig.readConfig(PurpurWorldConfig.class, this); -+ } -+ -+ private void set(String path, Object val) { -+ PurpurConfig.config.addDefault("world-settings.default." + path, val); -+ PurpurConfig.config.set("world-settings.default." + path, val); -+ if (PurpurConfig.config.get("world-settings." + worldName + "." + path) != null) { -+ PurpurConfig.config.addDefault("world-settings." + worldName + "." + path, val); -+ PurpurConfig.config.set("world-settings." + worldName + "." + path, val); -+ } -+ } -+ -+ private ConfigurationSection getConfigurationSection(String path) { -+ ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + "." + path); -+ return section != null ? section : PurpurConfig.config.getConfigurationSection("world-settings.default." + path); -+ } -+ -+ private String getString(String path, String def) { -+ PurpurConfig.config.addDefault("world-settings.default." + path, def); -+ return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path)); -+ } -+ -+ private boolean getBoolean(String path, boolean def) { -+ PurpurConfig.config.addDefault("world-settings.default." + path, def); -+ return PurpurConfig.config.getBoolean("world-settings." + worldName + "." + path, PurpurConfig.config.getBoolean("world-settings.default." + path)); -+ } -+ -+ private 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 = false; -+ 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 milkCuresBadOmen = true; -+ public double tridentLoyaltyVoidReturnHeight = 0.0D; -+ public boolean entitiesCanUsePortals = true; -+ public int raidCooldownSeconds = 0; -+ public int animalBreedingCooldownSeconds = 0; -+ public boolean persistentDroppableEntityDisplayNames = true; -+ public boolean entitiesPickUpLootBypassMobGriefing = false; -+ public boolean fireballsBypassMobGriefing = false; -+ public boolean projectilesBypassMobGriefing = false; -+ public boolean noteBlockIgnoreAbove = false; -+ public boolean imposeTeleportRestrictionsOnGateways = false; -+ public boolean imposeTeleportRestrictionsOnNetherPortals = false; -+ public boolean imposeTeleportRestrictionsOnEndPortals = false; -+ public boolean tickFluids = true; -+ public double mobsBlindnessMultiplier = 1; -+ public boolean mobsIgnoreRails = false; -+ public boolean rainStopsAfterSleep = true; -+ public boolean thunderStopsAfterSleep = true; -+ public boolean persistentTileEntityLore = false; -+ public boolean persistentTileEntityDisplayName = true; -+ public int mobLastHurtByPlayerTime = 100; -+ public boolean milkClearsBeneficialEffects = true; -+ public boolean disableOxidationProximityPenalty = false; -+ 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); -+ milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); -+ tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); -+ entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); -+ raidCooldownSeconds = getInt("gameplay-mechanics.raid-cooldown-seconds", raidCooldownSeconds); -+ animalBreedingCooldownSeconds = getInt("gameplay-mechanics.animal-breeding-cooldown-seconds", animalBreedingCooldownSeconds); -+ persistentDroppableEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-droppable-entity-display-names", persistentDroppableEntityDisplayNames); -+ entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); -+ fireballsBypassMobGriefing = getBoolean("gameplay-mechanics.fireballs-bypass-mob-griefing", fireballsBypassMobGriefing); -+ projectilesBypassMobGriefing = getBoolean("gameplay-mechanics.projectiles-bypass-mob-griefing", projectilesBypassMobGriefing); -+ noteBlockIgnoreAbove = getBoolean("gameplay-mechanics.note-block-ignore-above", noteBlockIgnoreAbove); -+ imposeTeleportRestrictionsOnGateways = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-gateways", imposeTeleportRestrictionsOnGateways); -+ imposeTeleportRestrictionsOnNetherPortals = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-nether-portals", imposeTeleportRestrictionsOnNetherPortals); -+ imposeTeleportRestrictionsOnEndPortals = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-end-portals", imposeTeleportRestrictionsOnEndPortals); -+ tickFluids = getBoolean("gameplay-mechanics.tick-fluids", tickFluids); -+ mobsBlindnessMultiplier = getDouble("gameplay-mechanics.entity-blindness-multiplier", mobsBlindnessMultiplier); -+ 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); -+ if (PurpurConfig.version < 35) { -+ boolean oldVal = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityLore); -+ set("gameplay-mechanics.persistent-tileentity-display-names-and-lore", null); -+ set("gameplay-mechanics.persistent-tileentity-lore", oldVal); -+ set("gameplay-mechanics.persistent-tileentity-display-name", !oldVal); -+ } -+ persistentTileEntityLore = getBoolean("gameplay-mechanics.persistent-tileentity-lore", persistentTileEntityLore); -+ persistentTileEntityDisplayName = getBoolean("gameplay-mechanics.persistent-tileentity-display-name", persistentTileEntityDisplayName); -+ mobLastHurtByPlayerTime = getInt("gameplay-mechanics.mob-last-hurt-by-player-time", mobLastHurtByPlayerTime); -+ milkClearsBeneficialEffects = getBoolean("gameplay-mechanics.milk-clears-beneficial-effects", milkClearsBeneficialEffects); -+ disableOxidationProximityPenalty = getBoolean("gameplay-mechanics.disable-oxidation-proximity-penalty", disableOxidationProximityPenalty); -+ } -+ -+ 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 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); -+ 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 infinityWorksWithoutArrows = false; -+ private void infinityArrowsSettings() { -+ infinityWorksWithoutArrows = getBoolean("gameplay-mechanics.infinity-bow.works-without-arrows", infinityWorksWithoutArrows); -+ } -+ -+ public boolean explosionClampRadius = true; -+ private void explosionSettings() { -+ explosionClampRadius = getBoolean("gameplay-mechanics.clamp-explosion-radius", explosionClampRadius); -+ } -+ -+ 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 ResourceLocation dontRunWithScissorsItemModelReference = ResourceLocation.parse("purpurmc:scissors"); -+ 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.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(key.toString())); -+ if (item != Items.AIR) itemImmuneToLightning.add(item); -+ }); -+ dontRunWithScissors = getBoolean("gameplay-mechanics.item.shears.damage-if-sprinting", dontRunWithScissors); -+ dontRunWithScissorsItemModelReference = ResourceLocation.parse(getString("gameplay-mechanics.item.shears.damage-if-sprinting-item-model", "purpurmc:scissors")); -+ 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.getValue(ResourceLocation.parse(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 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 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); -+ 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); -+ 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); -+ } -+ -+ 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.getValue(ResourceLocation.parse(key.toString())); -+ if (item != Items.AIR) silkTouchTools.add(item); -+ }); -+ } -+ -+ 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 Map axeStrippables = new HashMap<>(); -+ public Map axeWaxables = new HashMap<>(); -+ public Map axeWeatherables = new HashMap<>(); -+ public Map hoeTillables = new HashMap<>(); -+ public Map shovelFlattenables = new HashMap<>(); -+ public boolean hoeReplantsCrops = false; -+ public boolean hoeReplantsNetherWarts = false; -+ private void toolSettings() { -+ axeStrippables.clear(); -+ axeWaxables.clear(); -+ axeWeatherables.clear(); -+ hoeTillables.clear(); -+ shovelFlattenables.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())); -+ } -+ if (PurpurConfig.version < 33) { -+ 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 -> { -+ PurpurConfig.config.set("world-settings.default.tools.shovel.flattenables." + key.toString(), Map.of("into", "minecraft:dirt_path", "drops", new HashMap())); -+ }); -+ set("gameplay-mechanics.shovel-turns-block-to-grass-path", null); -+ } -+ if (PurpurConfig.version < 34) { -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_chiseled_copper", Map.of("into", "minecraft:oxidized_chiseled_copper", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_door", Map.of("into", "minecraft:oxidized_copper_door", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_trapdoor", Map.of("into", "minecraft:oxidized_copper_trapdoor", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_grate", Map.of("into", "minecraft:oxidized_copper_grate", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_bulb", Map.of("into", "minecraft:oxidized_copper_bulb", "drops", new HashMap())); -+ -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap())); -+ } -+ if (PurpurConfig.version < 38) { -+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:pale_oak_wood", Map.of("into", "minecraft:stripped_pale_oak_wood", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:pale_oak_log", Map.of("into", "minecraft:stripped_pale_oak_log", "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:pale_oak_wood", Map.of("into", "minecraft:stripped_pale_oak_wood", "drops", new HashMap())), -+ Map.entry("minecraft:pale_oak_log", Map.of("into", "minecraft:stripped_pale_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:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap())), -+ Map.entry("minecraft:cherry_log", Map.of("into", "minecraft:stripped_cherry_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: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())), -+ 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:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap())) -+ ) -+ ).forEach((blockId, obj) -> { -+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(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())), -+ Map.entry("minecraft:waxed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_exposed_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_weathered_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_oxidized_chiseled_copper", Map.of("into", "minecraft:oxidized_chiseled_copper", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_exposed_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_weathered_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_oxidized_copper_door", Map.of("into", "minecraft:oxidized_copper_door", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_exposed_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_weathered_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_oxidized_copper_trapdoor", Map.of("into", "minecraft:oxidized_copper_trapdoor", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_exposed_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_weathered_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_oxidized_copper_grate", Map.of("into", "minecraft:oxidized_copper_grate", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_exposed_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_weathered_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_oxidized_copper_bulb", Map.of("into", "minecraft:oxidized_copper_bulb", "drops", new HashMap()))) -+ ).forEach((blockId, obj) -> { -+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(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_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap())), -+ Map.entry("minecraft:weathered_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap())), -+ Map.entry("minecraft:oxidized_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_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())), -+ Map.entry("minecraft:exposed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap())), -+ Map.entry("minecraft:weathered_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap())), -+ Map.entry("minecraft:oxidized_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap())), -+ Map.entry("minecraft:exposed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap())), -+ Map.entry("minecraft:weathered_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap())), -+ Map.entry("minecraft:oxidized_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap())), -+ Map.entry("minecraft:exposed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap())), -+ Map.entry("minecraft:weathered_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap())), -+ Map.entry("minecraft:oxidized_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap())), -+ Map.entry("minecraft:exposed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap())), -+ Map.entry("minecraft:weathered_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())), -+ Map.entry("minecraft:oxidized_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap()))) -+ ).forEach((blockId, obj) -> { -+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(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.getValue(ResourceLocation.parse(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)); -+ }); -+ getMap("tools.shovel.flattenables", Map.ofEntries( -+ Map.entry("minecraft:grass_block", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), -+ Map.entry("minecraft:dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), -+ Map.entry("minecraft:podzol", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), -+ Map.entry("minecraft:coarse_dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), -+ Map.entry("minecraft:mycelium", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), -+ Map.entry("minecraft:rooted_dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap()))) -+ ).forEach((blockId, obj) -> { -+ Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId)); -+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.shovel.flattenables`: " + blockId); return; } -+ if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.shovel.flattenables." + blockId + "`"); return; } -+ String intoId = (String) map.get("into"); -+ Block into = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(intoId)); -+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.shovel.flattenables." + blockId + ".into`: " + intoId); return; } -+ Object dropsObj = map.get("drops"); -+ if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.shovel.flattenables." + blockId + ".drops`"); return; } -+ Map drops = new HashMap<>(); -+ dropsMap.forEach((itemId, chance) -> { -+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(itemId.toString())); -+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.shovel.flattenables." + blockId + ".drops`: " + itemId); return; } -+ drops.put(item, (double) chance); -+ }); -+ shovelFlattenables.put(block, new Flattenable(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 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.getValue(ResourceLocation.parse(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; -+ public boolean endCrystalPlaceAnywhere = false; -+ 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); -+ endCrystalPlaceAnywhere = getBoolean("gameplay-mechanics.item.end-crystal.place-anywhere", endCrystalPlaceAnywhere); -+ } -+ -+ 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 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; -+ private void magmaBlockSettings() { -+ magmaBlockDamageWhenSneaking = getBoolean("blocks.magma-block.damage-when-sneaking", magmaBlockDamageWhenSneaking); -+ } -+ -+ 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 sculkShriekerCanSummonDefault = false; -+ private void sculkShriekerSettings() { -+ sculkShriekerCanSummonDefault = getBoolean("blocks.sculk_shrieker.can-summon-default", sculkShriekerCanSummonDefault); -+ } -+ -+ public boolean signAllowColors = false; -+ private void signSettings() { -+ 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 = 65; -+ public int spongeAbsorptionRadius = 6; -+ public boolean spongeAbsorbsLava = false; -+ public boolean spongeAbsorbsWaterFromMud = 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); -+ spongeAbsorbsWaterFromMud = getBoolean("blocks.sponge.absorbs-water-from-mud", spongeAbsorbsWaterFromMud); -+ } -+ -+ public float stonecutterDamage = 0.0F; -+ private void stonecutterSettings() { -+ stonecutterDamage = (float) getDouble("blocks.stonecutter.damage", stonecutterDamage); -+ } -+ -+ public boolean turtleEggsBreakFromExpOrbs = false; -+ public boolean turtleEggsBreakFromItems = false; -+ public boolean turtleEggsBreakFromMinecarts = false; -+ 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 double allayMaxHealth = 20.0D; -+ public double allayScale = 1.0D; -+ private void allaySettings() { -+ allayRidable = getBoolean("mobs.allay.ridable", allayRidable); -+ allayRidableInWater = getBoolean("mobs.allay.ridable-in-water", allayRidableInWater); -+ allayControllable = getBoolean("mobs.allay.controllable", allayControllable); -+ allayMaxHealth = getDouble("mobs.allay.attributes.max_health", allayMaxHealth); -+ allayScale = Mth.clamp(getDouble("mobs.allay.attributes.scale", allayScale), 0.0625D, 16.0D); -+ } -+ -+ public boolean armadilloRidable = false; -+ public boolean armadilloRidableInWater = true; -+ public boolean armadilloControllable = true; -+ public double armadilloMaxHealth = 12.0D; -+ public double armadilloScale = 1.0D; -+ public int armadilloBreedingTicks = 6000; -+ private void armadilloSettings() { -+ armadilloRidable = getBoolean("mobs.armadillo.ridable", armadilloRidable); -+ armadilloRidableInWater = getBoolean("mobs.armadillo.ridable-in-water", armadilloRidableInWater); -+ armadilloControllable = getBoolean("mobs.armadillo.controllable", armadilloControllable); -+ armadilloMaxHealth = getDouble("mobs.armadillo.attributes.max_health", armadilloMaxHealth); -+ armadilloScale = Mth.clamp(getDouble("mobs.armadillo.attributes.scale", armadilloScale), 0.0625D, 16.0D); -+ armadilloBreedingTicks = getInt("mobs.armadillo.breeding-delay-ticks", armadilloBreedingTicks); -+ } -+ -+ public boolean axolotlRidable = false; -+ public boolean axolotlControllable = true; -+ public double axolotlMaxHealth = 14.0D; -+ public double axolotlScale = 1.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); -+ axolotlScale = Mth.clamp(getDouble("mobs.axolotl.attributes.scale", axolotlScale), 0.0625D, 16.0D); -+ 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 batScale = 1.0D; -+ public double batFollowRange = 16.0D; -+ public double batKnockbackResistance = 0.0D; -+ public double batMovementSpeed = 0.6D; -+ public double batFlyingSpeed = 0.6D; -+ public double batArmor = 0.0D; -+ public double batArmorToughness = 0.0D; -+ public double batAttackKnockback = 0.0D; -+ 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); -+ batScale = Mth.clamp(getDouble("mobs.bat.attributes.scale", batScale), 0.0625D, 16.0D); -+ batFollowRange = getDouble("mobs.bat.attributes.follow_range", batFollowRange); -+ batKnockbackResistance = getDouble("mobs.bat.attributes.knockback_resistance", batKnockbackResistance); -+ batMovementSpeed = getDouble("mobs.bat.attributes.movement_speed", batMovementSpeed); -+ batFlyingSpeed = getDouble("mobs.bat.attributes.flying_speed", batFlyingSpeed); -+ batArmor = getDouble("mobs.bat.attributes.armor", batArmor); -+ batArmorToughness = getDouble("mobs.bat.attributes.armor_toughness", batArmorToughness); -+ batAttackKnockback = getDouble("mobs.bat.attributes.attack_knockback", batAttackKnockback); -+ 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 double beeScale = 1.0D; -+ public int beeBreedingTicks = 6000; -+ public boolean beeTakeDamageFromWater = true; -+ 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); -+ beeScale = Mth.clamp(getDouble("mobs.bee.attributes.scale", beeScale), 0.0625D, 16.0D); -+ 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 double blazeScale = 1.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); -+ blazeScale = Mth.clamp(getDouble("mobs.blaze.attributes.scale", blazeScale), 0.0625D, 16.0D); -+ blazeTakeDamageFromWater = getBoolean("mobs.blaze.takes-damage-from-water", blazeTakeDamageFromWater); -+ blazeAlwaysDropExp = getBoolean("mobs.blaze.always-drop-exp", blazeAlwaysDropExp); -+ } -+ -+ public boolean boggedRidable = false; -+ public boolean boggedRidableInWater = true; -+ public boolean boggedControllable = true; -+ public double boggedMaxHealth = 16.0D; -+ public double boggedScale = 1.0D; -+ private void boggedSettings() { -+ boggedRidable = getBoolean("mobs.bogged.ridable", boggedRidable); -+ boggedRidableInWater = getBoolean("mobs.bogged.ridable-in-water", boggedRidableInWater); -+ boggedControllable = getBoolean("mobs.bogged.controllable", boggedControllable); -+ boggedMaxHealth = getDouble("mobs.bogged.attributes.max_health", boggedMaxHealth); -+ boggedScale = Mth.clamp(getDouble("mobs.bogged.attributes.scale", boggedScale), 0.0625D, 16.0D); -+ } -+ -+ public boolean camelRidableInWater = false; -+ public double camelMaxHealthMin = 32.0D; -+ public double camelMaxHealthMax = 32.0D; -+ public double camelJumpStrengthMin = 0.42D; -+ public double camelJumpStrengthMax = 0.42D; -+ public double camelMovementSpeedMin = 0.09D; -+ public double camelMovementSpeedMax = 0.09D; -+ public int camelBreedingTicks = 6000; -+ private void camelSettings() { -+ camelRidableInWater = getBoolean("mobs.camel.ridable-in-water", camelRidableInWater); -+ camelMaxHealthMin = getDouble("mobs.camel.attributes.max_health.min", camelMaxHealthMin); -+ camelMaxHealthMax = getDouble("mobs.camel.attributes.max_health.max", camelMaxHealthMax); -+ camelJumpStrengthMin = getDouble("mobs.camel.attributes.jump_strength.min", camelJumpStrengthMin); -+ camelJumpStrengthMax = getDouble("mobs.camel.attributes.jump_strength.max", camelJumpStrengthMax); -+ camelMovementSpeedMin = getDouble("mobs.camel.attributes.movement_speed.min", camelMovementSpeedMin); -+ camelMovementSpeedMax = getDouble("mobs.camel.attributes.movement_speed.max", camelMovementSpeedMax); -+ 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 double catScale = 1.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); -+ catScale = Mth.clamp(getDouble("mobs.cat.attributes.scale", catScale), 0.0625D, 16.0D); -+ 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", catDefaultCollarColor.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 double caveSpiderScale = 1.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); -+ caveSpiderScale = Mth.clamp(getDouble("mobs.cave_spider.attributes.scale", caveSpiderScale), 0.0625D, 16.0D); -+ 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 double chickenScale = 1.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); -+ chickenScale = Mth.clamp(getDouble("mobs.chicken.attributes.scale", chickenScale), 0.0625D, 16.0D); -+ 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 double codScale = 1.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); -+ codScale = Mth.clamp(getDouble("mobs.cod.attributes.scale", codScale), 0.0625D, 16.0D); -+ 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 double cowScale = 1.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); -+ cowScale = Mth.clamp(getDouble("mobs.cow.attributes.scale", cowScale), 0.0625D, 16.0D); -+ 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 creakingRidable = false; -+ public boolean creakingRidableInWater = true; -+ public boolean creakingControllable = true; -+ public double creakingMaxHealth = 1.0D; -+ public double creakingScale = 1.0D; -+ private void creakingSettings() { -+ creakingRidable = getBoolean("mobs.creaking.ridable", creakingRidable); -+ creakingRidableInWater = getBoolean("mobs.creaking.ridable-in-water", creakingRidableInWater); -+ creakingControllable = getBoolean("mobs.creaking.controllable", creakingControllable); -+ creakingMaxHealth = getDouble("mobs.creaking.attributes.max_health", creakingMaxHealth); -+ creakingScale = Mth.clamp(getDouble("mobs.creaking.attributes.scale", creakingScale), 0.0625D, 16.0D); -+ } -+ -+ public boolean creeperRidable = false; -+ public boolean creeperRidableInWater = true; -+ public boolean creeperControllable = true; -+ public double creeperMaxHealth = 20.0D; -+ public double creeperScale = 1.0D; -+ 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); -+ creeperScale = Mth.clamp(getDouble("mobs.creeper.attributes.scale", creeperScale), 0.0625D, 16.0D); -+ 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 double dolphinScale = 1.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); -+ dolphinScale = Mth.clamp(getDouble("mobs.dolphin.attributes.scale", dolphinScale), 0.0625D, 16.0D); -+ 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 drownedScale = 1.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); -+ drownedScale = Mth.clamp(getDouble("mobs.drowned.attributes.scale", drownedScale), 0.0625D, 16.0D); -+ 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 double elderGuardianScale = 1.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); -+ elderGuardianScale = Mth.clamp(getDouble("mobs.elder_guardian.attributes.scale", elderGuardianScale), 0.0625D, 16.0D); -+ 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 double endermanScale = 1.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 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); -+ endermanScale = Mth.clamp(getDouble("mobs.enderman.attributes.scale", endermanScale), 0.0625D, 16.0D); -+ 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); -+ 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 double endermiteScale = 1.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); -+ endermiteScale = Mth.clamp(getDouble("mobs.endermite.attributes.scale", endermiteScale), 0.0625D, 16.0D); -+ 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 double evokerScale = 1.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); -+ evokerScale = Mth.clamp(getDouble("mobs.evoker.attributes.scale", evokerScale), 0.0625D, 16.0D); -+ 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 double foxScale = 1.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); -+ foxScale = Mth.clamp(getDouble("mobs.fox.attributes.scale", foxScale), 0.0625D, 16.0D); -+ 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 double ghastScale = 1.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); -+ ghastScale = Mth.clamp(getDouble("mobs.ghast.attributes.scale", ghastScale), 0.0625D, 16.0D); -+ 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 double giantScale = 1.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); -+ giantScale = Mth.clamp(getDouble("mobs.giant.attributes.scale", giantScale), 0.0625D, 16.0D); -+ 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 double glowSquidScale = 1.0D; -+ public boolean glowSquidsCanFly = false; -+ public boolean glowSquidTakeDamageFromWater = false; -+ public boolean glowSquidAlwaysDropExp = false; -+ private void glowSquidSettings() { -+ glowSquidRidable = getBoolean("mobs.glow_squid.ridable", glowSquidRidable); -+ glowSquidControllable = getBoolean("mobs.glow_squid.controllable", glowSquidControllable); -+ glowSquidMaxHealth = getDouble("mobs.glow_squid.attributes.max_health", glowSquidMaxHealth); -+ glowSquidScale = Mth.clamp(getDouble("mobs.glow_squid.attributes.scale", glowSquidScale), 0.0625D, 16.0D); -+ 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); -+ } -+ -+ public boolean goatRidable = false; -+ public boolean goatRidableInWater = true; -+ public boolean goatControllable = true; -+ public double goatMaxHealth = 10.0D; -+ public double goatScale = 1.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); -+ goatScale = Mth.clamp(getDouble("mobs.goat.attributes.scale", goatScale), 0.0625D, 16.0D); -+ 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 double guardianScale = 1.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); -+ guardianScale = Mth.clamp(getDouble("mobs.guardian.attributes.scale", guardianScale), 0.0625D, 16.0D); -+ 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 double hoglinScale = 1.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); -+ hoglinScale = Mth.clamp(getDouble("mobs.hoglin.attributes.scale", hoglinScale), 0.0625D, 16.0D); -+ 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 huskScale = 1.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); -+ huskScale = Mth.clamp(getDouble("mobs.husk.attributes.scale", huskScale), 0.0625D, 16.0D); -+ 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 double illusionerScale = 1.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); -+ illusionerScale = Mth.clamp(getDouble("mobs.illusioner.attributes.scale", illusionerScale), 0.0625D, 16.0D); -+ 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 double ironGolemScale = 1.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); -+ ironGolemScale = Mth.clamp(getDouble("mobs.iron_golem.attributes.scale", ironGolemScale), 0.0625D, 16.0D); -+ 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 double mooshroomScale = 1.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); -+ mooshroomScale = Mth.clamp(getDouble("mobs.mooshroom.attributes.scale", mooshroomScale), 0.0625D, 16.0D); -+ 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 double ocelotScale = 1.0D; -+ public int ocelotBreedingTicks = 6000; -+ public boolean ocelotTakeDamageFromWater = false; -+ public boolean ocelotAlwaysDropExp = false; -+ public boolean ocelotSpawnUnderSeaLevel = 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); -+ ocelotScale = Mth.clamp(getDouble("mobs.ocelot.attributes.scale", ocelotScale), 0.0625D, 16.0D); -+ 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); -+ ocelotSpawnUnderSeaLevel = getBoolean("mobs.ocelot.spawn-below-sea-level", ocelotSpawnUnderSeaLevel); -+ } -+ -+ public boolean pandaRidable = false; -+ public boolean pandaRidableInWater = true; -+ public boolean pandaControllable = true; -+ public double pandaMaxHealth = 20.0D; -+ public double pandaScale = 1.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); -+ pandaScale = Mth.clamp(getDouble("mobs.panda.attributes.scale", pandaScale), 0.0625D, 16.0D); -+ 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 double parrotScale = 1.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); -+ parrotScale = Mth.clamp(getDouble("mobs.parrot.attributes.scale", parrotScale), 0.0625D, 16.0D); -+ 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 double pigScale = 1.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); -+ pigScale = Mth.clamp(getDouble("mobs.pig.attributes.scale", pigScale), 0.0625D, 16.0D); -+ 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 double piglinScale = 1.0D; -+ public boolean piglinBypassMobGriefing = false; -+ public boolean piglinTakeDamageFromWater = false; -+ public int piglinPortalSpawnModifier = 2000; -+ public boolean piglinAlwaysDropExp = false; -+ public double piglinHeadVisibilityPercent = 0.5D; -+ public boolean piglinIgnoresArmorWithGoldTrim = false; -+ private void piglinSettings() { -+ piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable); -+ piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater); -+ piglinControllable = getBoolean("mobs.piglin.controllable", piglinControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.piglin.attributes.max-health", piglinMaxHealth); -+ set("mobs.piglin.attributes.max-health", null); -+ set("mobs.piglin.attributes.max_health", oldValue); -+ } -+ piglinMaxHealth = getDouble("mobs.piglin.attributes.max_health", piglinMaxHealth); -+ piglinScale = Mth.clamp(getDouble("mobs.piglin.attributes.scale", piglinScale), 0.0625D, 16.0D); -+ 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); -+ piglinIgnoresArmorWithGoldTrim = getBoolean("mobs.piglin.ignores-armor-with-gold-trim", piglinIgnoresArmorWithGoldTrim); -+ } -+ -+ public boolean piglinBruteRidable = false; -+ public boolean piglinBruteRidableInWater = true; -+ public boolean piglinBruteControllable = true; -+ public double piglinBruteMaxHealth = 50.0D; -+ public double piglinBruteScale = 1.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); -+ piglinBruteScale = Mth.clamp(getDouble("mobs.piglin_brute.attributes.scale", piglinBruteScale), 0.0625D, 16.0D); -+ 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 double pillagerScale = 1.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); -+ pillagerScale = Mth.clamp(getDouble("mobs.pillager.attributes.scale", pillagerScale), 0.0625D, 16.0D); -+ 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 double polarBearScale = 1.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); -+ polarBearScale = Mth.clamp(getDouble("mobs.polar_bear.attributes.scale", polarBearScale), 0.0625D, 16.0D); -+ polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString); -+ Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(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 double pufferfishScale = 1.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); -+ pufferfishScale = Mth.clamp(getDouble("mobs.pufferfish.attributes.scale", pufferfishScale), 0.0625D, 16.0D); -+ 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 rabbitScale = 1.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); -+ rabbitScale = Mth.clamp(getDouble("mobs.rabbit.attributes.scale", rabbitScale), 0.0625D, 16.0D); -+ 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 double ravagerScale = 1.0D; -+ public boolean ravagerBypassMobGriefing = false; -+ public boolean ravagerTakeDamageFromWater = false; -+ public List ravagerGriefableBlocks = new ArrayList<>(); -+ public boolean ravagerAlwaysDropExp = false; -+ public boolean ravagerAvoidRabbits = 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); -+ ravagerScale = Mth.clamp(getDouble("mobs.ravager.attributes.scale", ravagerScale), 0.0625D, 16.0D); -+ 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.getValue(ResourceLocation.parse(key.toString())); -+ if (!block.defaultBlockState().isAir()) { -+ ravagerGriefableBlocks.add(block); -+ } -+ }); -+ ravagerAlwaysDropExp = getBoolean("mobs.ravager.always-drop-exp", ravagerAlwaysDropExp); -+ ravagerAvoidRabbits = getBoolean("mobs.ravager.avoid-rabbits", ravagerAvoidRabbits); -+ } -+ -+ public boolean salmonRidable = false; -+ public boolean salmonControllable = true; -+ public double salmonMaxHealth = 3.0D; -+ public double salmonScale = 1.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); -+ salmonScale = Mth.clamp(getDouble("mobs.salmon.attributes.scale", salmonScale), 0.0625D, 16.0D); -+ 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 double sheepScale = 1.0D; -+ public int sheepBreedingTicks = 6000; -+ public boolean sheepBypassMobGriefing = false; -+ public boolean sheepTakeDamageFromWater = false; -+ public boolean sheepAlwaysDropExp = 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); -+ sheepScale = Mth.clamp(getDouble("mobs.sheep.attributes.scale", sheepScale), 0.0625D, 16.0D); -+ 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); -+ } -+ -+ public boolean shulkerRidable = false; -+ public boolean shulkerRidableInWater = true; -+ public boolean shulkerControllable = true; -+ public double shulkerMaxHealth = 30.0D; -+ public double shulkerScale = 1.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); -+ shulkerScale = Mth.clamp(getDouble("mobs.shulker.attributes.scale", shulkerScale), 0.0625D, Shulker.MAX_SCALE); -+ 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 double silverfishScale = 1.0D; -+ public double silverfishMovementSpeed = 0.25D; -+ public double silverfishAttackDamage = 1.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); -+ silverfishScale = Mth.clamp(getDouble("mobs.silverfish.attributes.scale", silverfishScale), 0.0625D, 16.0D); -+ silverfishMovementSpeed = getDouble("mobs.silverfish.attributes.movement_speed", silverfishMovementSpeed); -+ silverfishAttackDamage = getDouble("mobs.silverfish.attributes.attack_damage", silverfishAttackDamage); -+ 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 double skeletonScale = 1.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); -+ skeletonScale = Mth.clamp(getDouble("mobs.skeleton.attributes.scale", skeletonScale), 0.0625D, 16.0D); -+ 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 skeletonHorseRidable = false; -+ public boolean skeletonHorseRidableInWater = true; -+ public boolean skeletonHorseCanSwim = false; -+ public double skeletonHorseMaxHealthMin = 15.0D; -+ public double skeletonHorseMaxHealthMax = 15.0D; -+ public double skeletonHorseJumpStrengthMin = 0.4D; -+ public double skeletonHorseJumpStrengthMax = 1.0D; -+ public double skeletonHorseMovementSpeedMin = 0.2D; -+ public double skeletonHorseMovementSpeedMax = 0.2D; -+ public boolean skeletonHorseTakeDamageFromWater = false; -+ public boolean skeletonHorseAlwaysDropExp = false; -+ private void skeletonHorseSettings() { -+ skeletonHorseRidable = getBoolean("mobs.skeleton_horse.ridable", skeletonHorseRidable); -+ skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater); -+ skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim); -+ 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 double snowGolemScale = 1.0D; -+ 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); -+ snowGolemScale = Mth.clamp(getDouble("mobs.snow_golem.attributes.scale", snowGolemScale), 0.0625D, 16.0D); -+ 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 double snifferScale = 1.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); -+ snifferScale = Mth.clamp(getDouble("mobs.sniffer.attributes.scale", snifferScale), 0.0625D, 16.0D); -+ snifferBreedingTicks = getInt("mobs.sniffer.breeding-delay-ticks", snifferBreedingTicks); -+ } -+ -+ public boolean squidRidable = false; -+ public boolean squidControllable = true; -+ public double squidMaxHealth = 10.0D; -+ public double squidScale = 1.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); -+ squidScale = Mth.clamp(getDouble("mobs.squid.attributes.scale", squidScale), 0.0625D, 16.0D); -+ 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 double spiderScale = 1.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); -+ spiderScale = Mth.clamp(getDouble("mobs.spider.attributes.scale", spiderScale), 0.0625D, 16.0D); -+ 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 double strayScale = 1.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); -+ strayScale = Mth.clamp(getDouble("mobs.stray.attributes.scale", strayScale), 0.0625D, 16.0D); -+ 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 double striderScale = 1.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); -+ striderScale = Mth.clamp(getDouble("mobs.strider.attributes.scale", striderScale), 0.0625D, 16.0D); -+ 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 double tropicalFishScale = 1.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); -+ tropicalFishScale = Mth.clamp(getDouble("mobs.tropical_fish.attributes.scale", tropicalFishScale), 0.0625D, 16.0D); -+ 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 double turtleScale = 1.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); -+ turtleScale = Mth.clamp(getDouble("mobs.turtle.attributes.scale", turtleScale), 0.0625D, 16.0D); -+ 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 double vexScale = 1.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); -+ vexScale = Mth.clamp(getDouble("mobs.vex.attributes.scale", vexScale), 0.0625D, 16.0D); -+ 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 double villagerScale = 1.0D; -+ public boolean villagerFollowEmeraldBlock = false; -+ public double villagerTemptRange = 10.0D; -+ 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 villagerLobotomizeWaitUntilTradeLocked = false; -+ public boolean villagerDisplayTradeItem = true; -+ public int villagerSpawnIronGolemRadius = 0; -+ public int villagerSpawnIronGolemLimit = 0; -+ public int villagerAcquirePoiSearchRadius = 48; -+ public int villagerNearestBedSensorSearchRadius = 48; -+ private void villagerSettings() { -+ villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); -+ villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); -+ villagerControllable = getBoolean("mobs.villager.controllable", villagerControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.villager.attributes.max-health", villagerMaxHealth); -+ set("mobs.villager.attributes.max-health", null); -+ set("mobs.villager.attributes.max_health", oldValue); -+ } -+ villagerMaxHealth = getDouble("mobs.villager.attributes.max_health", villagerMaxHealth); -+ villagerScale = Mth.clamp(getDouble("mobs.villager.attributes.scale", villagerScale), 0.0625D, 16.0D); -+ villagerFollowEmeraldBlock = getBoolean("mobs.villager.follow-emerald-blocks", villagerFollowEmeraldBlock); -+ villagerTemptRange = getDouble("mobs.villager.attributes.tempt_range", villagerTemptRange); -+ 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); -+ villagerLobotomizeWaitUntilTradeLocked = getBoolean("mobs.villager.lobotomize.wait-until-trade-locked", villagerLobotomizeWaitUntilTradeLocked); -+ 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); -+ villagerAcquirePoiSearchRadius = getInt("mobs.villager.search-radius.acquire-poi", villagerAcquirePoiSearchRadius); -+ villagerNearestBedSensorSearchRadius = getInt("mobs.villager.search-radius.nearest-bed-sensor", villagerNearestBedSensorSearchRadius); -+ } -+ -+ public boolean vindicatorRidable = false; -+ public boolean vindicatorRidableInWater = true; -+ public boolean vindicatorControllable = true; -+ public double vindicatorMaxHealth = 24.0D; -+ public double vindicatorScale = 1.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); -+ vindicatorScale = Mth.clamp(getDouble("mobs.vindicator.attributes.scale", vindicatorScale), 0.0625D, 16.0D); -+ 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 double wanderingTraderScale = 1.0D; -+ public boolean wanderingTraderFollowEmeraldBlock = false; -+ public double wanderingTraderTemptRange = 10.0D; -+ 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); -+ wanderingTraderScale = Mth.clamp(getDouble("mobs.wandering_trader.attributes.scale", wanderingTraderScale), 0.0625D, 16.0D); -+ wanderingTraderFollowEmeraldBlock = getBoolean("mobs.wandering_trader.follow-emerald-blocks", wanderingTraderFollowEmeraldBlock); -+ wanderingTraderTemptRange = getDouble("mobs.wandering_trader.attributes.tempt_range", wanderingTraderTemptRange); -+ 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 double witchScale = 1.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); -+ witchScale = Mth.clamp(getDouble("mobs.witch.attributes.scale", witchScale), 0.0625D, 16.0D); -+ 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 double witherScale = 1.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); -+ witherScale = Mth.clamp(getDouble("mobs.wither.attributes.scale", witherScale), 0.0625D, 16.0D); -+ 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 double witherSkeletonScale = 1.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); -+ witherSkeletonScale = Mth.clamp(getDouble("mobs.wither_skeleton.attributes.scale", witherSkeletonScale), 0.0625D, 16.0D); -+ 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 double wolfScale = 1.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); -+ wolfScale = Mth.clamp(getDouble("mobs.wolf.attributes.scale", wolfScale), 0.0625D, 16.0D); -+ 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 double zoglinScale = 1.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); -+ zoglinScale = Mth.clamp(getDouble("mobs.zoglin.attributes.scale", zoglinScale), 0.0625D, 16.0D); -+ 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 zombieScale = 1.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); -+ zombieScale = Mth.clamp(getDouble("mobs.zombie.attributes.scale", zombieScale), 0.0625D, 16.0D); -+ 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 zombieHorseRidable = false; -+ public boolean zombieHorseRidableInWater = false; -+ public boolean zombieHorseCanSwim = false; -+ public double zombieHorseMaxHealthMin = 15.0D; -+ public double zombieHorseMaxHealthMax = 15.0D; -+ public double zombieHorseJumpStrengthMin = 0.4D; -+ public double zombieHorseJumpStrengthMax = 1.0D; -+ public double zombieHorseMovementSpeedMin = 0.2D; -+ public double zombieHorseMovementSpeedMax = 0.2D; -+ public double zombieHorseSpawnChance = 0.0D; -+ public boolean zombieHorseTakeDamageFromWater = false; -+ public boolean zombieHorseAlwaysDropExp = false; -+ private void zombieHorseSettings() { -+ zombieHorseRidable = getBoolean("mobs.zombie_horse.ridable", zombieHorseRidable); -+ zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater); -+ zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim); -+ 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 zombieVillagerScale = 1.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); -+ zombieVillagerScale = Mth.clamp(getDouble("mobs.zombie_villager.attributes.scale", zombieVillagerScale), 0.0625D, 16.0D); -+ 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 zombifiedPiglinScale = 1.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); -+ zombifiedPiglinScale = Mth.clamp(getDouble("mobs.zombified_piglin.attributes.scale", zombifiedPiglinScale), 0.0625D, 16.0D); -+ 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.getValue(ResourceLocation.parse(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); -+ } -+ -+ public float shearsCanDefuseTntChance = 0.00F; -+ public boolean shearsCanDefuseTnt = false; -+ private void shearsCanDefuseTntSettings() { -+ shearsCanDefuseTntChance = (float) getDouble("gameplay-mechanics.item.shears.defuse-tnt-chance", 0.00D); -+ shearsCanDefuseTnt = shearsCanDefuseTntChance > 0.00F; -+ } -+} -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..f202b98a194604e39798fdb8e417c6d2835f71c8 ---- /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.connection.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..992f8dfc628c7485e335191e1308cdfd4eedfbe8 ---- /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/configuration/transformation/VoidDamageHeightMigration.java b/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a04d23bd98075cd65a24d4de8d18281d1668480f ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java -@@ -0,0 +1,67 @@ -+package org.purpurmc.purpur.configuration.transformation; -+ -+import io.papermc.paper.configuration.Configurations; -+import io.papermc.paper.configuration.PaperConfigurations; -+import io.papermc.paper.configuration.type.number.DoubleOr; -+import java.util.OptionalDouble; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.purpurmc.purpur.PurpurConfig; -+import org.spongepowered.configurate.ConfigurateException; -+import org.spongepowered.configurate.ConfigurationNode; -+import org.spongepowered.configurate.NodePath; -+import org.spongepowered.configurate.transformation.ConfigurationTransformation; -+import org.spongepowered.configurate.transformation.TransformAction; -+ -+import static org.spongepowered.configurate.NodePath.path; -+ -+public class VoidDamageHeightMigration implements TransformAction { -+ -+ public static boolean HAS_BEEN_REGISTERED = false; -+ -+ public static final String ENVIRONMENT_KEY = "environment"; -+ public static final String VOID_DAMAGE_KEY = "void-damage-amount"; -+ public static final String VOID_DAMAGE_MIN_HEIGHT_OFFSET_KEY = "void-damage-min-build-height-offset"; -+ public static final double DEFAULT_VOID_DAMAGE_HEIGHT = -64.0D; -+ public static final double DEFAULT_VOID_DAMAGE = 4.0D; -+ -+ private final String worldName; -+ -+ private VoidDamageHeightMigration(String worldName) { -+ this.worldName = PaperConfigurations.WORLD_DEFAULTS.equals(worldName) ? "default" : worldName; -+ } -+ -+ @Override -+ public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException { -+ String purpurVoidDamageHeightPath = "world-settings." + this.worldName + ".gameplay-mechanics.void-damage-height"; -+ ConfigurationNode voidDamageMinHeightOffsetNode = value.node(ENVIRONMENT_KEY, VOID_DAMAGE_MIN_HEIGHT_OFFSET_KEY); -+ if (PurpurConfig.config.contains(purpurVoidDamageHeightPath)) { -+ double purpurVoidDamageHeight = PurpurConfig.config.getDouble(purpurVoidDamageHeightPath); -+ if (purpurVoidDamageHeight != DEFAULT_VOID_DAMAGE_HEIGHT && (voidDamageMinHeightOffsetNode.empty() || voidDamageMinHeightOffsetNode.getDouble() == DEFAULT_VOID_DAMAGE_HEIGHT)) { -+ voidDamageMinHeightOffsetNode.raw(null); -+ voidDamageMinHeightOffsetNode.set(purpurVoidDamageHeight); -+ } -+ PurpurConfig.config.set(purpurVoidDamageHeightPath, null); -+ } -+ -+ String purpurVoidDamagePath = "world-settings." + this.worldName + ".gameplay-mechanics.void-damage-dealt"; -+ ConfigurationNode voidDamageNode = value.node(ENVIRONMENT_KEY, VOID_DAMAGE_KEY); -+ if (PurpurConfig.config.contains(purpurVoidDamagePath)) { -+ double purpurVoidDamage = PurpurConfig.config.getDouble(purpurVoidDamagePath); -+ if (purpurVoidDamage != DEFAULT_VOID_DAMAGE && (voidDamageNode.empty() || voidDamageNode.getDouble() == DEFAULT_VOID_DAMAGE)) { -+ voidDamageNode.raw(null); -+ voidDamageNode.set(new DoubleOr.Disabled(OptionalDouble.of(purpurVoidDamage))); -+ } -+ PurpurConfig.config.set(purpurVoidDamagePath, null); -+ } -+ -+ return null; -+ } -+ -+ public static void apply(final ConfigurationTransformation.Builder builder, final Configurations.ContextMap contextMap) { -+ if (PurpurConfig.version < 36) { -+ HAS_BEEN_REGISTERED = true; -+ builder.addAction(path(), new VoidDamageHeightMigration(contextMap.require(Configurations.WORLD_NAME))); -+ } -+ } -+ -+} -diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..940bcc6f79b59cb3cce578912eb789efd394f456 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java -@@ -0,0 +1,74 @@ -+package org.purpurmc.purpur.controller; -+ -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.attributes.Attributes; -+import net.minecraft.world.entity.player.Input; -+import net.minecraft.world.entity.player.Player; -+ -+public class FlyingMoveControllerWASD extends MoveControllerWASD { -+ protected final float groundSpeedModifier; -+ protected final float flyingSpeedModifier; -+ protected int tooHighCooldown = 0; -+ protected boolean setNoGravityFlag; -+ -+ public FlyingMoveControllerWASD(Mob entity) { -+ this(entity, 1.0F); -+ } -+ -+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier) { -+ this(entity, groundSpeedModifier, 1.0F, true); -+ } -+ -+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, float flyingSpeedModifier) { -+ this(entity, groundSpeedModifier, flyingSpeedModifier, true); -+ } -+ -+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, float flyingSpeedModifier, boolean setNoGravityFlag) { -+ super(entity); -+ this.groundSpeedModifier = groundSpeedModifier; -+ this.flyingSpeedModifier = flyingSpeedModifier; -+ this.setNoGravityFlag = setNoGravityFlag; -+ } -+ -+ @Override -+ public void purpurTick(Player rider) { -+ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput(); -+ float forward = lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : 0.0F; -+ float vertical = forward == 0.0F ? 0.0F : -(rider.xRotO / 45.0F); -+ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F); -+ -+ if (lastClientInput.jump() && spacebarEvent(entity)) { -+ entity.onSpacebar(); -+ } -+ -+ if (entity.getY() >= entity.getMaxY() || --tooHighCooldown > 0) { -+ if (tooHighCooldown <= 0) { -+ tooHighCooldown = 20; -+ } -+ entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.05D, 0.0D)); -+ vertical = 0.0F; -+ } -+ -+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED)); -+ float speed = (float) getSpeedModifier(); -+ -+ if (entity.onGround) { -+ speed *= groundSpeedModifier; // TODO = fix this! -+ } else { -+ speed *= flyingSpeedModifier; -+ } -+ -+ if (setNoGravityFlag) { -+ entity.setNoGravity(forward > 0); -+ } -+ -+ entity.setSpeed(speed); -+ entity.setVerticalMot(vertical); -+ entity.setStrafeMot(strafe); -+ entity.setForwardMot(forward); -+ -+ setForward(entity.getForwardMot()); -+ setStrafe(entity.getStrafeMot()); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e0bbaec05afa0ae67ed486b14ea1fbadbbe90d9b ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java -@@ -0,0 +1,66 @@ -+package org.purpurmc.purpur.controller; -+ -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.attributes.Attributes; -+import net.minecraft.world.entity.player.Input; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.phys.Vec3; -+ -+public class FlyingWithSpacebarMoveControllerWASD extends FlyingMoveControllerWASD { -+ public FlyingWithSpacebarMoveControllerWASD(Mob entity) { -+ super(entity); -+ } -+ -+ public FlyingWithSpacebarMoveControllerWASD(Mob entity, float groundSpeedModifier) { -+ super(entity, groundSpeedModifier); -+ } -+ -+ @Override -+ public void purpurTick(Player rider) { -+ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput(); -+ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F); -+ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F) * 0.5F; -+ float vertical = 0; -+ -+ if (forward < 0.0F) { -+ forward *= 0.5F; -+ strafe *= 0.5F; -+ } -+ -+ float speed = (float) entity.getAttributeValue(Attributes.MOVEMENT_SPEED); -+ -+ if (entity.onGround) { -+ speed *= groundSpeedModifier; -+ } -+ -+ if (lastClientInput.jump() && spacebarEvent(entity) && !entity.onSpacebar()) { -+ entity.setNoGravity(true); -+ vertical = 1.0F; -+ } else { -+ entity.setNoGravity(false); -+ } -+ -+ if (entity.getY() >= entity.getMaxY() || --tooHighCooldown > 0) { -+ if (tooHighCooldown <= 0) { -+ tooHighCooldown = 20; -+ } -+ entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.2D, 0.0D)); -+ vertical = 0.0F; -+ } -+ -+ setSpeedModifier(speed); -+ entity.setSpeed((float) getSpeedModifier()); -+ entity.setVerticalMot(vertical); -+ entity.setStrafeMot(strafe); -+ entity.setForwardMot(forward); -+ -+ setForward(entity.getForwardMot()); -+ setStrafe(entity.getStrafeMot()); -+ -+ Vec3 mot = entity.getDeltaMovement(); -+ if (mot.y > 0.2D) { -+ entity.setDeltaMovement(mot.x, 0.2D, mot.z); -+ } -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..dd219518150ca90f89ad238904fd4095efe032d8 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java -@@ -0,0 +1,79 @@ -+package org.purpurmc.purpur.controller; -+ -+ -+import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.control.LookControl; -+import net.minecraft.world.entity.player.Player; -+ -+public class LookControllerWASD extends LookControl { -+ protected final Mob entity; -+ private float yOffset = 0; -+ private float xOffset = 0; -+ -+ public LookControllerWASD(Mob entity) { -+ super(entity); -+ this.entity = entity; -+ } -+ -+ // tick -+ @Override -+ public void tick() { -+ if (entity.getRider() != null && entity.isControllable()) { -+ purpurTick(entity.getRider()); -+ } else { -+ vanillaTick(); -+ } -+ } -+ -+ protected void purpurTick(Player rider) { -+ setYawPitch(rider.getYRot(), rider.getXRot()); -+ } -+ -+ public void vanillaTick() { -+ super.tick(); -+ } -+ -+ public void setYawPitch(float yRot, float xRot) { -+ entity.setXRot(normalizePitch(xRot + xOffset)); -+ entity.setYRot(normalizeYaw(yRot + yOffset)); -+ entity.setYHeadRot(entity.getYRot()); -+ entity.xRotO = entity.getXRot(); -+ entity.yRotO = entity.getYRot(); -+ -+ ClientboundMoveEntityPacket.PosRot entityPacket = new ClientboundMoveEntityPacket.PosRot( -+ entity.getId(), -+ (short) 0, (short) 0, (short) 0, -+ (byte) Mth.floor(entity.getYRot() * 256.0F / 360.0F), -+ (byte) Mth.floor(entity.getXRot() * 256.0F / 360.0F), -+ entity.onGround -+ ); -+ ((ServerLevel) entity.level()).getChunkSource().broadcast(entity, entityPacket); -+ } -+ -+ public void setOffsets(float yaw, float pitch) { -+ yOffset = yaw; -+ xOffset = pitch; -+ } -+ -+ public float normalizeYaw(float yaw) { -+ yaw %= 360.0f; -+ if (yaw >= 180.0f) { -+ yaw -= 360.0f; -+ } else if (yaw < -180.0f) { -+ yaw += 360.0f; -+ } -+ return yaw; -+ } -+ -+ public float normalizePitch(float pitch) { -+ if (pitch > 90.0f) { -+ pitch = 90.0f; -+ } else if (pitch < -90.0f) { -+ pitch = -90.0f; -+ } -+ return pitch; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..34f3c43fa16e950326ac5e3d93faee0466ffedc6 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java -@@ -0,0 +1,92 @@ -+package org.purpurmc.purpur.controller; -+ -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.attributes.Attributes; -+import net.minecraft.world.entity.ai.control.MoveControl; -+import net.minecraft.world.entity.player.Input; -+import net.minecraft.world.entity.player.Player; -+import org.purpurmc.purpur.event.entity.RidableSpacebarEvent; -+ -+public class MoveControllerWASD extends MoveControl { -+ protected final Mob entity; -+ private final double speedModifier; -+ -+ public MoveControllerWASD(Mob entity) { -+ this(entity, 1.0D); -+ } -+ -+ public MoveControllerWASD(Mob entity, double speedModifier) { -+ super(entity); -+ this.entity = entity; -+ this.speedModifier = speedModifier; -+ } -+ -+ @Override -+ public boolean hasWanted() { -+ return entity.getRider() != null ? strafeForwards != 0 || strafeRight != 0 : super.hasWanted(); -+ } -+ -+ @Override -+ public void tick() { -+ if (entity.getRider() != null && entity.isControllable()) { -+ purpurTick(entity.getRider()); -+ } else { -+ vanillaTick(); -+ } -+ } -+ -+ public void vanillaTick() { -+ super.tick(); -+ } -+ -+ public void purpurTick(Player rider) { -+ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput(); -+ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F) * 0.5F; -+ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F) * 0.25F; -+ -+ if (forward <= 0.0F) { -+ forward *= 0.5F; -+ } -+ -+ float yawOffset = 0; -+ if (strafe != 0) { -+ if (forward == 0) { -+ yawOffset += strafe > 0 ? -90 : 90; -+ forward = Math.abs(strafe * 2); -+ } else { -+ yawOffset += strafe > 0 ? -30 : 30; -+ strafe /= 2; -+ if (forward < 0) { -+ yawOffset += strafe > 0 ? -110 : 110; -+ forward *= -1; -+ } -+ } -+ } else if (forward < 0) { -+ yawOffset -= 180; -+ forward *= -1; -+ } -+ -+ ((LookControllerWASD) entity.getLookControl()).setOffsets(yawOffset, 0); -+ -+ if (lastClientInput.jump() && spacebarEvent(entity) && !entity.onSpacebar() && entity.onGround) { -+ entity.jumpFromGround(); -+ } -+ -+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED) * speedModifier); -+ -+ entity.setSpeed((float) getSpeedModifier()); -+ entity.setForwardMot(forward); -+ -+ setForward(entity.getForwardMot()); -+ setStrafe(entity.getStrafeMot()); -+ } -+ -+ public static boolean spacebarEvent(Mob entity) { -+ if (RidableSpacebarEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ return new RidableSpacebarEvent(entity.getBukkitEntity()).callEvent(); -+ } else { -+ return true; -+ } -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..922e48799c43ca322a8f550c98a26e1e2959439c ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java -@@ -0,0 +1,53 @@ -+package org.purpurmc.purpur.controller; -+ -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.attributes.Attributes; -+import net.minecraft.world.entity.player.Input; -+import net.minecraft.world.entity.player.Player; -+ -+public class WaterMoveControllerWASD extends MoveControllerWASD { -+ private final double speedModifier; -+ -+ public WaterMoveControllerWASD(Mob entity) { -+ this(entity, 1.0D); -+ } -+ -+ public WaterMoveControllerWASD(Mob entity, double speedModifier) { -+ super(entity); -+ this.speedModifier = speedModifier; -+ } -+ -+ @Override -+ public void purpurTick(Player rider) { -+ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput(); -+ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F); -+ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F) * 0.5F; // strafe slower by default -+ float vertical = -(rider.xRotO / 90); -+ -+ if (forward == 0.0F) { -+ // strafe slower if not moving forward -+ strafe *= 0.5F; -+ // do not move vertically if not moving forward -+ vertical = 0.0F; -+ } else if (forward < 0.0F) { -+ // water animals can't swim backwards -+ forward = 0.0F; -+ vertical = 0.0F; -+ } -+ -+ if (rider.jumping && spacebarEvent(entity)) { -+ entity.onSpacebar(); -+ } -+ -+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED) * speedModifier); -+ entity.setSpeed((float) getSpeedModifier() * 0.1F); -+ -+ entity.setForwardMot(forward * (float) speedModifier); -+ entity.setStrafeMot(strafe * (float) speedModifier); -+ entity.setVerticalMot(vertical * (float) speedModifier); -+ -+ setForward(entity.getForwardMot()); -+ setStrafe(entity.getStrafeMot()); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java -new file mode 100644 -index 0000000000000000000000000000000000000000..477ae2cd8ded25023976e3b7525e0c3b0e8259d9 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java -@@ -0,0 +1,108 @@ -+package org.purpurmc.purpur.entity; -+ -+import net.minecraft.core.particles.ParticleTypes; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.damagesource.DamageSource; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.LivingEntity; -+import net.minecraft.world.entity.animal.Dolphin; -+import net.minecraft.world.entity.projectile.LlamaSpit; -+import net.minecraft.world.entity.projectile.ProjectileUtil; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.phys.BlockHitResult; -+import net.minecraft.world.phys.EntityHitResult; -+import net.minecraft.world.phys.HitResult; -+import net.minecraft.world.phys.Vec3; -+import org.bukkit.event.entity.EntityRemoveEvent; -+ -+public class DolphinSpit extends LlamaSpit { -+ public LivingEntity dolphin; -+ public int ticksLived; -+ -+ public DolphinSpit(EntityType 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)); -+ } -+ -+ // Purpur start - Add canSaveToDisk to Entity -+ @Override -+ public boolean canSaveToDisk() { -+ return false; -+ } -+ // Purpur end - Add canSaveToDisk to Entity -+ -+ public void tick() { -+ super_tick(); -+ -+ Vec3 mot = this.getDeltaMovement(); -+ HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); -+ -+ this.preHitTargetOrDeflectSelf(hitResult); -+ -+ double x = this.getX() + mot.x; -+ double y = this.getY() + mot.y; -+ double z = this.getZ() + mot.z; -+ -+ this.updateRotation(); -+ -+ Vec3 motDouble = mot.scale(2.0); -+ for (int i = 0; i < 5; i++) { -+ ((ServerLevel) level()).sendParticlesSource(null, ParticleTypes.BUBBLE, -+ false, true, -+ getX() + random.nextFloat() / 2 - 0.25F, -+ getY() + random.nextFloat() / 2 - 0.25F, -+ getZ() + random.nextFloat() / 2 - 0.25F, -+ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D); -+ } -+ -+ if (++ticksLived > 20) { -+ this.discard(EntityRemoveEvent.Cause.DISCARD); -+ } else { -+ this.setDeltaMovement(mot.scale(0.99D)); -+ if (!this.isNoGravity()) { -+ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); -+ } -+ -+ this.setPos(x, y, z); -+ } -+ } -+ -+ @Override -+ public void shoot(double x, double y, double z, float speed, float inaccuracy) { -+ setDeltaMovement(new Vec3(x, y, z).normalize().add( -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) -+ .scale(speed)); -+ } -+ -+ @Override -+ protected void onHitEntity(EntityHitResult entityHitResult) { -+ Entity shooter = this.getOwner(); -+ if (shooter instanceof LivingEntity) { -+ entityHitResult.getEntity().hurt(entityHitResult.getEntity().damageSources().mobProjectile(this, (LivingEntity) shooter), level().purpurConfig.dolphinSpitDamage); -+ } -+ } -+ -+ @Override -+ protected void onHitBlock(BlockHitResult blockHitResult) { -+ if (this.hitCancelled) { -+ return; -+ } -+ BlockState state = this.level().getBlockState(blockHitResult.getBlockPos()); -+ state.onProjectileHit(this.level(), state, blockHitResult, this); -+ this.discard(EntityRemoveEvent.Cause.DISCARD); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f57d77c0cab0174e67c1fdda6ac56f408ad6a902 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java -@@ -0,0 +1,130 @@ -+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)); -+ } -+ -+ // Purpur start - Add canSaveToDisk to Entity -+ @Override -+ public boolean canSaveToDisk() { -+ return false; -+ } -+ // Purpur end - Add canSaveToDisk to Entity -+ -+ public void tick() { -+ super_tick(); -+ -+ Vec3 mot = this.getDeltaMovement(); -+ HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); -+ -+ this.preHitTargetOrDeflectSelf(hitResult); -+ -+ double x = this.getX() + mot.x; -+ double y = this.getY() + mot.y; -+ double z = this.getZ() + mot.z; -+ -+ this.updateRotation(); -+ -+ Vec3 motDouble = mot.scale(2.0); -+ for (int i = 0; i < 5; i++) { -+ ((ServerLevel) level()).sendParticlesSource(null, ParticleTypes.FLAME, -+ false, true, -+ getX() + random.nextFloat() / 2 - 0.25F, -+ getY() + random.nextFloat() / 2 - 0.25F, -+ getZ() + random.nextFloat() / 2 - 0.25F, -+ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D); -+ } -+ -+ if (++ticksLived > 20) { -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ } else if (this.level().getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) { -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ } else if (this.isInWaterOrBubble()) { -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ } else { -+ this.setDeltaMovement(mot.scale(0.99D)); -+ if (!this.isNoGravity()) { -+ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); -+ } -+ -+ this.setPos(x, y, z); -+ } -+ } -+ -+ @Override -+ public void shoot(double x, double y, double z, float speed, float inaccuracy) { -+ setDeltaMovement(new Vec3(x, y, z).normalize().add( -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) -+ .scale(speed)); -+ } -+ -+ @Override -+ protected void onHitEntity(EntityHitResult entityHitResult) { -+ Level world = this.level(); -+ -+ if (world instanceof ServerLevel worldserver) { -+ Entity shooter = this.getOwner(); -+ if (shooter instanceof LivingEntity) { -+ Entity target = entityHitResult.getEntity(); -+ if (canGrief || (target instanceof LivingEntity && !(target instanceof ArmorStand))) { -+ boolean hurt = target.hurtServer(worldserver, target.damageSources().mobProjectile(this, (LivingEntity) shooter), worldserver.purpurConfig.phantomFlameDamage); -+ if (hurt && worldserver.purpurConfig.phantomFlameFireTime > 0) { -+ target.igniteForSeconds(worldserver.purpurConfig.phantomFlameFireTime); -+ } -+ } -+ } -+ } -+ } -+ -+ @Override -+ protected void onHitBlock(BlockHitResult blockHitResult) { -+ Level world = this.level(); -+ -+ if (world instanceof ServerLevel worldserver) { -+ if (this.hitCancelled) { -+ return; -+ } -+ if (this.canGrief) { -+ BlockState state = worldserver.getBlockState(blockHitResult.getBlockPos()); -+ state.onProjectileHit(worldserver, state, blockHitResult, this); -+ } -+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); -+ } -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java b/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7608bf0981fa0d37031e51e57e4086cb5ec4c88b ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java -@@ -0,0 +1,106 @@ -+package org.purpurmc.purpur.entity; -+ -+import io.papermc.paper.adventure.PaperAdventure; -+import net.kyori.adventure.text.Component; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.Tag; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.world.item.component.CustomData; -+import net.minecraft.world.level.block.entity.BeehiveBlockEntity; -+import org.bukkit.block.EntityBlockStorage; -+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; -+import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry; -+import org.bukkit.entity.Bee; -+import org.bukkit.entity.EntityType; -+import org.bukkit.persistence.PersistentDataContainer; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.Locale; -+ -+public class PurpurStoredBee implements StoredEntity { -+ private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); -+ -+ private final EntityBlockStorage blockStorage; -+ private final BeehiveBlockEntity.BeeData handle; -+ private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(PurpurStoredBee.DATA_TYPE_REGISTRY); -+ -+ private Component customName; -+ -+ public PurpurStoredBee(BeehiveBlockEntity.BeeData data, EntityBlockStorage blockStorage) { -+ this.handle = data; -+ this.blockStorage = blockStorage; -+ -+ CompoundTag customData = handle.occupant.entityData().copyTag(); -+ this.customName = customData.contains("CustomName") -+ ? PaperAdventure.asAdventure(net.minecraft.network.chat.Component.Serializer.fromJson(customData.getString("CustomName"), MinecraftServer.getDefaultRegistryAccess())) -+ : null; -+ -+ if(customData.contains("BukkitValues", Tag.TAG_COMPOUND)) { -+ this.persistentDataContainer.putAll(customData.getCompound("BukkitValues")); -+ } -+ } -+ -+ public BeehiveBlockEntity.BeeData getHandle() { -+ return handle; -+ } -+ -+ @Override -+ public @Nullable Component customName() { -+ return customName; -+ } -+ -+ @Override -+ public void customName(@Nullable Component customName) { -+ this.customName = customName; -+ } -+ -+ @Override -+ public @Nullable String getCustomName() { -+ return PaperAdventure.asPlain(customName, Locale.US); -+ } -+ -+ @Override -+ public void setCustomName(@Nullable String name) { -+ customName(name != null ? Component.text(name) : null); -+ } -+ -+ @Override -+ public @NotNull PersistentDataContainer getPersistentDataContainer() { -+ return persistentDataContainer; -+ } -+ -+ @Override -+ public boolean hasBeenReleased() { -+ return !blockStorage.getEntities().contains(this); -+ } -+ -+ @Override -+ public @Nullable Bee release() { -+ return blockStorage.releaseEntity(this); -+ } -+ -+ @Override -+ public @Nullable EntityBlockStorage getBlockStorage() { -+ if(hasBeenReleased()) { -+ return null; -+ } -+ -+ return blockStorage; -+ } -+ -+ @Override -+ public @NotNull EntityType getType() { -+ return EntityType.BEE; -+ } -+ -+ @Override -+ public void update() { -+ handle.occupant.entityData().copyTag().put("BukkitValues", this.persistentDataContainer.toTagCompound()); -+ if(customName == null) { -+ handle.occupant.entityData().copyTag().remove("CustomName"); -+ } else { -+ handle.occupant.entityData().copyTag().putString("CustomName", net.minecraft.network.chat.Component.Serializer.toJson(PaperAdventure.asVanilla(customName), MinecraftServer.getDefaultRegistryAccess())); -+ } -+ } -+} -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..9660716f4162a4441c6e1b0baddef8f5086566c5 ---- /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..550222758bf0e7deff26a6e813a860b7be365e87 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java -@@ -0,0 +1,58 @@ -+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 ChatColor getChatColor() { -+ return chat; -+ } -+ -+ 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..d75fb5e77eff27d86135ed7d605dbc250b660f7d ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java -@@ -0,0 +1,83 @@ -+package org.purpurmc.purpur.gui; -+ -+import com.google.common.collect.Sets; -+import javax.swing.UIManager; -+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; -+ static { -+ DEFAULT_COLOR = UIManager.getSystemLookAndFeelClassName().equals("com.sun.java.swing.plaf.gtk.GTKLookAndFeel") -+ ? GUIColor.WHITE : GUIColor.BLACK; -+ } -+ -+ -+ public void append(String msg) { -+ // TODO: update to use adventure instead -+ BaseComponent[] components = TextComponent.fromLegacyText(DEFAULT_COLOR.getCode() + msg, DEFAULT_COLOR.getChatColor()); -+ 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/item/GlowBerryItem.java b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b257f35caa13b660854cf17f41fd8fba1d56c458 ---- /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.BlockItem; -+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 BlockItem { -+ 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..ed50cb2115401c9039df4136caf5a087a5f5991c ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java -@@ -0,0 +1,40 @@ -+package org.purpurmc.purpur.item; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.component.DataComponents; -+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.item.component.CustomData; -+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 blockEntity = level.getBlockEntity(pos); -+ if (blockEntity instanceof SpawnerBlockEntity spawner) { -+ CompoundTag customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag(); -+ if (customData.contains("Purpur.mob_type")) { -+ EntityType.byString(customData.getString("Purpur.mob_type")).ifPresent(type -> spawner.getSpawner().setEntityId(type, level, level.random, pos)); -+ } else if (customData.contains("Purpur.SpawnData")) { -+ net.minecraft.world.level.SpawnData.CODEC.parse(net.minecraft.nbt.NbtOps.INSTANCE, customData.getCompound("Purpur.SpawnData")).result() -+ .ifPresent(spawnData -> spawner.getSpawner().nextSpawnData = spawnData); -+ } -+ } -+ } -+ return handled; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/network/ClientboundBeehivePayload.java b/src/main/java/org/purpurmc/purpur/network/ClientboundBeehivePayload.java -new file mode 100644 -index 0000000000000000000000000000000000000000..793a3ea45fe04e84725926f17615c26e008b0ce4 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/network/ClientboundBeehivePayload.java -@@ -0,0 +1,27 @@ -+package org.purpurmc.purpur.network; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+ -+public record ClientboundBeehivePayload(BlockPos pos, int numOfBees) implements CustomPacketPayload { -+ public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundBeehivePayload::write, ClientboundBeehivePayload::new); -+ public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath("purpur", "beehive_s2c")); -+ -+ public ClientboundBeehivePayload(FriendlyByteBuf friendlyByteBuf) { -+ this(friendlyByteBuf.readBlockPos(), friendlyByteBuf.readInt()); -+ } -+ -+ private void write(FriendlyByteBuf friendlyByteBuf) { -+ friendlyByteBuf.writeBlockPos(this.pos); -+ friendlyByteBuf.writeInt(this.numOfBees); -+ } -+ -+ @Override -+ public @NotNull Type type() { -+ return TYPE; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/network/ServerboundBeehivePayload.java b/src/main/java/org/purpurmc/purpur/network/ServerboundBeehivePayload.java -new file mode 100644 -index 0000000000000000000000000000000000000000..fa72769e06061609e1e658a0250e99c8cb026c0e ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/network/ServerboundBeehivePayload.java -@@ -0,0 +1,26 @@ -+package org.purpurmc.purpur.network; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.codec.StreamCodec; -+import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+ -+public record ServerboundBeehivePayload(BlockPos pos) implements CustomPacketPayload { -+ public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundBeehivePayload::write, ServerboundBeehivePayload::new); -+ public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath("purpur", "beehive_c2s")); -+ -+ public ServerboundBeehivePayload(FriendlyByteBuf friendlyByteBuf) { -+ this(friendlyByteBuf.readBlockPos()); -+ } -+ -+ private void write(FriendlyByteBuf friendlyByteBuf) { -+ friendlyByteBuf.writeBlockPos(this.pos); -+ } -+ -+ @Override -+ public @NotNull Type type() { -+ return TYPE; -+ } -+} -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..664f9d5e1ce5e2787bf699bd11758b9e3aa8ed3a ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java -@@ -0,0 +1,67 @@ -+package org.purpurmc.purpur.task; -+ -+import io.netty.buffer.Unpooled; -+import net.minecraft.network.FriendlyByteBuf; -+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.entity.Player; -+import org.bukkit.plugin.PluginBase; -+import org.bukkit.plugin.messaging.PluginMessageListener; -+import org.jetbrains.annotations.NotNull; -+import org.purpurmc.purpur.network.ClientboundBeehivePayload; -+import org.purpurmc.purpur.network.ServerboundBeehivePayload; -+import org.purpurmc.purpur.util.MinecraftInternalPlugin; -+ -+public class BeehiveTask implements PluginMessageListener { -+ -+ 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, ClientboundBeehivePayload.TYPE.id().toString()); -+ Bukkit.getMessenger().registerIncomingPluginChannel(this.plugin, ServerboundBeehivePayload.TYPE.id().toString(), this); -+ } -+ -+ public void unregister() { -+ Bukkit.getMessenger().unregisterOutgoingPluginChannel(this.plugin, ClientboundBeehivePayload.TYPE.id().toString()); -+ Bukkit.getMessenger().unregisterIncomingPluginChannel(this.plugin, ServerboundBeehivePayload.TYPE.id().toString()); -+ } -+ -+ @Override -+ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte[] bytes) { -+ FriendlyByteBuf byteBuf = new FriendlyByteBuf(Unpooled.copiedBuffer(bytes)); -+ ServerboundBeehivePayload payload = ServerboundBeehivePayload.STREAM_CODEC.decode(byteBuf); -+ -+ ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); -+ -+ // targeted block info max range specified in client at net.minecraft.client.gui.hud.DebugHud#render -+ if (!payload.pos().getCenter().closerThan(serverPlayer.position(), 20)) return; // Targeted Block info max range is 20 -+ if (serverPlayer.level().getChunkIfLoaded(payload.pos()) == null) return; -+ -+ BlockEntity blockEntity = serverPlayer.level().getBlockEntity(payload.pos()); -+ if (!(blockEntity instanceof BeehiveBlockEntity beehive)) { -+ return; -+ } -+ -+ ClientboundBeehivePayload customPacketPayload = new ClientboundBeehivePayload(payload.pos(), beehive.getOccupantCount()); -+ FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ ClientboundBeehivePayload.STREAM_CODEC.encode(friendlyByteBuf, customPacketPayload); -+ byte[] byteArray = new byte[friendlyByteBuf.readableBytes()]; -+ friendlyByteBuf.readBytes(byteArray); -+ player.sendPluginMessage(this.plugin, customPacketPayload.type().id().toString(), byteArray); -+ } -+} -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..3c3d4cd52db93b97a40321030a70ebc282c9636b ---- /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.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; -+import org.purpurmc.purpur.util.MinecraftInternalPlugin; -+ -+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/Flattenable.java b/src/main/java/org/purpurmc/purpur/tool/Flattenable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..345d4ee4ff0b78bd1050959711a4f5d16a5e8aee ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/tool/Flattenable.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 Flattenable extends Actionable { -+ public Flattenable(Block into, Map drops) { -+ super(into, 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/purpurmc/purpur/util/MinecraftInternalPlugin.java b/src/main/java/org/purpurmc/purpur/util/MinecraftInternalPlugin.java -new file mode 100644 -index 0000000000000000000000000000000000000000..129acb8ad139decc6b1c023cb10bc32dc91d64d1 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/util/MinecraftInternalPlugin.java -@@ -0,0 +1,152 @@ -+package org.purpurmc.purpur.util; -+ -+import org.bukkit.Server; -+import org.bukkit.command.Command; -+import org.bukkit.command.CommandSender; -+import org.bukkit.configuration.file.FileConfiguration; -+import org.bukkit.generator.BiomeProvider; -+import org.bukkit.generator.ChunkGenerator; -+import org.bukkit.plugin.PluginBase; -+import org.bukkit.plugin.PluginDescriptionFile; -+import org.bukkit.plugin.PluginLoader; -+import org.bukkit.plugin.PluginLogger; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.io.File; -+import java.io.InputStream; -+import java.util.List; -+ -+public class MinecraftInternalPlugin extends PluginBase { -+ private boolean enabled = true; -+ -+ private final String pluginName; -+ private PluginDescriptionFile pdf; -+ -+ public MinecraftInternalPlugin() { -+ this.pluginName = "Minecraft"; -+ pdf = new PluginDescriptionFile(pluginName, "1.0", "nms"); -+ } -+ -+ public void setEnabled(boolean enabled) { -+ this.enabled = enabled; -+ } -+ -+ @Override -+ public File getDataFolder() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public PluginDescriptionFile getDescription() { -+ return pdf; -+ } -+ // Paper start -+ @Override -+ public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() { -+ return pdf; -+ } -+ // Paper end -+ -+ @Override -+ public FileConfiguration getConfig() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public InputStream getResource(String filename) { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public void saveConfig() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public void saveDefaultConfig() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public void saveResource(String resourcePath, boolean replace) { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public void reloadConfig() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public PluginLogger getLogger() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public PluginLoader getPluginLoader() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public Server getServer() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public boolean isEnabled() { -+ return enabled; -+ } -+ -+ @Override -+ public void onDisable() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public void onLoad() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public void onEnable() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public boolean isNaggable() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public void setNaggable(boolean canNag) { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public @Nullable BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ @Override -+ public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ -+ // Paper start - lifecycle events -+ @Override -+ public @NotNull io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager getLifecycleManager() { -+ throw new UnsupportedOperationException("Not supported."); -+ } -+ // Paper end - lifecycle events -+} -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 1d438ef44cbe4d1eedfba36d8fe5d2ad53464921..9b1a16747aa23b18e4cff986efaac6ce64b6ddb9 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -37,6 +37,10 @@ import net.minecraft.world.entity.projectile.ThrownTrident; - import net.minecraft.world.entity.raid.Raider; - import net.minecraft.world.level.Level; - import net.minecraft.world.phys.AABB; -+// Pufferfish start -+import net.minecraft.world.phys.Vec3; -+import java.util.List; -+// Pufferfish end - - public class ActivationRange - { -@@ -199,6 +203,8 @@ 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 ); -@@ -221,6 +227,25 @@ public class ActivationRange - } - // Paper end - Configurable marker ticking - ActivationRange.activateEntity(entity); -+ -+ // Pufferfish start -+ if (gg.pufferfish.pufferfish.PufferfishConfig.dearEnabled && entity.getType().dabEnabled) { -+ if (!entity.activatedPriorityReset) { -+ entity.activatedPriorityReset = true; -+ entity.activatedPriority = gg.pufferfish.pufferfish.PufferfishConfig.maximumActivationPrio; -+ } -+ Vec3 playerVec = player.position(); -+ Vec3 entityVec = entity.position(); -+ double diffX = playerVec.x - entityVec.x, diffY = playerVec.y - entityVec.y, diffZ = playerVec.z - entityVec.z; -+ int squaredDistance = (int) (diffX * diffX + diffY * diffY + diffZ * diffZ); -+ entity.activatedPriority = squaredDistance > gg.pufferfish.pufferfish.PufferfishConfig.startDistanceSquared ? -+ Math.max(1, Math.min(squaredDistance >> gg.pufferfish.pufferfish.PufferfishConfig.activationDistanceMod, entity.activatedPriority)) : -+ 1; -+ } else { -+ entity.activatedPriority = 1; -+ } -+ // Pufferfish end -+ - } - // Paper end - } -@@ -236,12 +261,12 @@ public class ActivationRange - if ( MinecraftServer.currentTick > entity.activatedTick ) - { - if ( entity.defaultActivationState ) -- { -+ { // Pufferfish - diff on change - entity.activatedTick = MinecraftServer.currentTick; - return; - } - if ( entity.activationType.boundingBox.intersects( entity.getBoundingBox() ) ) -- { -+ { // Pufferfish - diff on change - entity.activatedTick = MinecraftServer.currentTick; - } - } -@@ -295,7 +320,7 @@ public class ActivationRange - if ( entity instanceof LivingEntity ) - { - LivingEntity living = (LivingEntity) entity; -- if ( living.onClimbable() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 || living.isFreezing()) // Paper -+ if ( living.onClimableCached() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 || living.isFreezing() ) // Paper // Pufferfish - use cached - { - return 1; // Paper - } -@@ -376,6 +401,7 @@ public class ActivationRange - */ - public static boolean checkIfActive(Entity entity) - { -+ if (entity.level().purpurConfig.squidImmuneToEAR && entity instanceof net.minecraft.world.entity.animal.Squid) return true; // Purpur - // Never safe to skip fireworks or item gravity - if (entity instanceof FireworkRocketEntity || (entity instanceof ItemEntity && (entity.tickCount + entity.getId()) % 4 == 0)) { // Paper - Needed for item gravity, see ItemEntity tick - return true; -diff --git a/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/src/main/java/org/spigotmc/TicksPerSecondCommand.java -index 9eb2823cc8f83bad2626fc77578b0162d9ed5782..d24d094d33c840052ace18e0385e0ffd3544fd82 100644 ---- a/src/main/java/org/spigotmc/TicksPerSecondCommand.java -+++ b/src/main/java/org/spigotmc/TicksPerSecondCommand.java -@@ -39,7 +39,7 @@ public class TicksPerSecondCommand extends Command - } - - net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text(); -- builder.append(net.kyori.adventure.text.Component.text("TPS from last 1m, 5m, 15m: ", net.kyori.adventure.text.format.NamedTextColor.GOLD)); -+ builder.append(net.kyori.adventure.text.Component.text("TPS from last 5s, 1m, 5m, 15m: ", net.kyori.adventure.text.format.NamedTextColor.GOLD)); // Purpur - Add 5 second tps average in /tps - builder.append(net.kyori.adventure.text.Component.join(net.kyori.adventure.text.JoinConfiguration.commas(true), tpsAvg)); - sender.sendMessage(builder.asComponent()); - if (args.length > 0 && args[0].equals("mem") && sender.hasPermission("bukkit.command.tpsmemory")) { -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index f7a4fee9bb25ff256dc2e5ea26bfbceca6a49167..47f168b2d62c9a0eebdd8ab678afd857e7622571 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -96,7 +96,7 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre - - private WatchdogThread(long timeoutTime, boolean restart) - { -- super( "Paper Watchdog Thread" ); -+ super( "Watchdog Thread" ); // Purpur - use a generic name - Rebrand - this.timeoutTime = timeoutTime; - this.restart = restart; - earlyWarningEvery = Math.min(io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.earlyWarningEvery, timeoutTime); // Paper -@@ -155,14 +155,14 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre - if (isLongTimeout) { - // Paper end - log.log( Level.SEVERE, "------------------------------" ); -- log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper -+ log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Purpur bug." ); // Paper // Purpur - Rebrand - log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); - log.log( Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring" ); - log.log( Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once" ); - log.log( Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes" ); -- log.log( Level.SEVERE, "If you are unsure or still think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues" ); -+ log.log( Level.SEVERE, "If you are unsure or still think this is a Purpur bug, please report this to https://github.com/PurpurMC/Purpur/issues" ); // Purpur - Rebrand - log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" ); -- log.log( Level.SEVERE, "Paper version: " + Bukkit.getServer().getVersion() ); -+ log.log( Level.SEVERE, "Purpur version: " + Bukkit.getServer().getVersion() ); // Purpur - Rebrand - // - if ( net.minecraft.world.level.Level.lastPhysicsProblem != null ) - { -@@ -184,12 +184,12 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre - // Paper end - } else - { -- log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); -+ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PURPUR - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); // Purpur - Rebrand - log.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump"); - } - // Paper end - Different message for short timeout - log.log( Level.SEVERE, "------------------------------" ); -- log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper -+ log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Purpur!):" ); // Paper // Purpur - Rebrand - ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(MinecraftServer.getServer(), isLongTimeout); // Paper - rewrite chunk system - this.dumpTickingInfo(); // Paper - log detailed tick information - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); -@@ -205,7 +205,7 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre - WatchdogThread.dumpThread( thread, log ); - } - } else { -- log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---"); -+ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PURPUR - THIS IS NOT A BUG OR A CRASH ---"); // Purpur - Rebrand - } - - log.log( Level.SEVERE, "------------------------------" ); -diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml -index d2a75850af9c6ad2aca66a5f994f1b587d73eac4..a056aa167887abef9e6d531a9edd2cda433567d2 100644 ---- a/src/main/resources/log4j2.xml -+++ b/src/main/resources/log4j2.xml -@@ -2,7 +2,16 @@ - - - -- -+ -+ -+ -+ -+ -+ -+ -+ -+ - - - -diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png -index 8b924977b7886df9ab8790b1e4ff9b1c04a2af45..518591dd83289e041a16e2c2e7d7e7640d4b2e1b 100644 -GIT binary patch -literal 9260 -zcmWksbyU<{5M5yDS~|W3b}1=IK|*5bk`9p$r5ov78WvbWX=y?AQ0}Yp0<$+ -z2n70|0E9aM1Hym>3UG)5o+kstyg;EopqB~EvfpJ`0IyyF`Z>TV(_M%s;1&uXoq-)H -z;F}=ODg(TT1R4>5S0IpY2)Km+#_2%4GEk`jbcq9_JOJ7f=obdWYJprMphypx-~uK& -zfGKuhnfb0%2k;C6T*B}FJWm2#LxEc5`?y0ASYiPxHGw7sut5i8nE^^g_qFCpK(#86 -zU=R5D0q-;c>o{N$dUr?$K&}+{{t)O91>PFm^B>^>UPJ-mZueCg=74SV -z-76o!H4G@%0&L>$C3pt{V-J8Y;`e|##z2YAwaY<&?5%eA%O$(`;HN=zzH!R+z9BrzEdm$hWPHMEnNJY_6!DIn*qN5 -z_nap=0k432lC>)L25=4s0)2p&k@rF>wC=4^sR6w51u*=8Z46)^eeaD_OQ2KYesLu_ -z_bnO}fnYCS27b@YJPAcFIq_k-G5l_q3J8e2Q;oLp0f9h`eg-;*AYux9f;(z{1P3K; -zAc-1FB9Ge>Zs~zfp~6f|1AZ>jJ+E!xyI=H$h0b;xyj={nw3zKSrn7|FQR-*=+fyUr -z=Su_V?H60K!yReo|fvWvfRwHX!FDE%^}p;&O(2o^WU?* -zrjt*BXMa}vSHgZ%pMyX!K@DYuQQ*RKzN>boDdTtRg-Ly9JO}n{XVhl_+MZ?HDs@~A -z5j=z*z2Hk^FS|?cBMa?*B1y~c$eczr6DJ<~8sS%-R$89I?xCD+Tq0C5SucGjXE7xnV*8Gc?TRT3SfozC^6$@Qj6@!n%l| -z#bi{is#y61XyM%?*IpJew5-hAwU2LJMPXt&j8*u}Z=i(xqCeej&2w>uP~qD*iWsjN -z!^cTPWqL1jBqhDw+#HnmHv8-3f_{;eCN)dJ4f@ki&gLx-FPHc-{eE0r?C;w+3p_At -zj%=5ztJvcEFqBgI$80>PjARKtETWa7n%NSZx{*B42;KZ(L&33iNW$szF`A{7nN#aO -z!V)P|HjhBCPMyimc<&?YyQwLsG>62*2y&d?S65du|u$!}x%`h`aUbq!#y -zFJooT^6+RQZLQ=5o5>q$YfX$7eU6?=eW=*nR52euv~h1X;xm4AE`BJhVSpkv$QsV& -z5Anq6bv=fRwwKeUSOrdWo?{Hzm@>0e5gE{qRIP3&NzQr(q7Etqj1H49H#Y2Wj%OfEqb+NY+_Q)$A9W% -ziZ4N&q~k<@NrBE1YnNCqW)_#6WvSET^sZ9>^_ynuGtnQ3^(sekadGnT -z^>b!Fik{$Ps1dI64bk`JaF@vx6&2zKkQqSu&X3*}8yQ`s=c_OT2MRCRkz=TQQ{*Uc -zF{j%dt9izpO|6l3{-2cp@WtX4QR-xg)hrbh?7uA#@TKwh+MeNv*Gy%bwT=1XICtaO -zdAW4DJ$8Mq8$R@f+>+13la0|ysO?O3AywkY!KB^qO9(Y5C*4P5>hH!6{E|8X#C$sS -zhuNeT(~_1xZ7?=JDrbDQgsXXr1J2q*mKZdSVndXGEOhL3MuXbGVy8lQq^8z{6%n2&KBdn+3_ -z&fJ28J|^Y%x{?)O&DR)OKmIZC|}&|+yY8pDZ;rYZco-tVpA`GB25dU;`C@mgTO -zPNlccf4c73T?xggH}``wr#<}dwp}6eKZ7)6H>Bek1w6cls#R0HR?L?c${5sxgP#oR -z9QQ!09_5zgQxiz-P`-Rk2HySc6XH7+EoEcPyUca*kH*9@1?CfNtj+0VWLzleVfEVl -zm{CNN%(n4itXxD?t4u{{72YA~cRe{QS7SpjXRRb0RimgmqN@v1R~ZO+x60R?@xAyj -zZplHM`TR5wPhYOwhVUwjMoX@4_%C6h!Cp(QdA52*GM*rOjs10VoV?4S1!dnS-?jof -zRoHLj+oh5|e>F_H=8piE&n#$mL*?jwwCWrIL~oATnM4@|z>K&(=qf7vWc5-p*05@V5yp)ru>~B&CjM_ -zp@e}3@nub7uR<3^VDH}(%W}}0q(RgYrQYXfQlgzcCc>#L2o`N_;>pX&=H}w&y|}g9 -zUN0CK8u|u|362&|5^;<`$%0we%4S^5ONkLbqig^w!N+1j3`cW;4AW<^J+3L#Faie@3G|MeLH5~(uqg+ciGU2r|lYeHHXShGO{ZMWnGgOMu -zsi;e+SMqcrDyb1wvA&pD!cR?+->7KbLy?XRk{-4pRQ!EpDiX&RnEAJVJjXWrmG!cx -z2`lhfMp3H=4HiXxD{FJBMWjw!1}5ILmDC^pcy*Oe$>;x}LLf1pAmzXRV!o+y5H1=U -zwAU~zWSLqHZkz3!bSgGEq0&t*dA`&UAVw2*%j)aP%IyD>CwiryV1VMp!;epqs-Zxk -zaMVDz1U(uq*ltddXYYIOqPY&Q?c-qdVLk&1CKjRZ$a!5R<$D{#LLgXYkP6p!?>#>*GrvVaAktyZH~PWR6(;*(A)Si4vXe8hKd5xsE^FC_X?C5>8pvdE -z3cq}tKvjQb^O;y?;JjL(#1h>iP;9#&Tk5JXrF571-#Ki~OtYWJUNB9Yam5ozZO#7} -zaiy@4m&`~GM;B;L>&ZKfWgZrU#U%Nare)SK9=~mQ{*F~c(&uR9%cnfB^dhxlVxOyQ -zCEn$=ryRuVaw9%WIFr48kM{UwuwdA1JaicPw|)BO=eHXTIkPnB}DIayQbG1ZXcXn -z9>Pdlk_eG4f8Qlok2QRCjhS&;>_|?vXyC@qodyqfXh;&tgw5xO<&TtGbw(Ps2!w~L -z@%!Tv%yB4b%G$PYsb@Y|yDHD6UD(8WODIH!wpLg3?HJ4Y)TT@RZA#oX)p&0mvRu^rySUXHpw#8Zm+?FRf0;0A0Ak3wMz~p -z71!ai+@;rh{M=93{pyE0M2C6iskBfTX$T44%ES+nQMt^MntnsKz$xUS<^`TgGfDgA -zqkIkg!usCGe>sIa7b~g>Vaz>HqqL=g^J6@+O@((j4owc9i|k9LOoKDuv3V!PP;IN$ -z%HZJ5a;(l{{Dr88ag^}XNN?sSpmbegRO%CL|0MSa^BZx9Z=*KfAfJR@M1`Yww-k2b -z<-O3pg=SpYCzjwry~c2~FfPhNWasR_{a|-qD>lhk8Na`y?R~1GU5#xa2vQ98U~&(3 -z0y$V%kLygOS?CJ5MK{n8Tp~*M2x*>%zM|@1LR2=){lSoZ<_OCde4&6&5iDRu%g^S6 -zdJ{{BoQ4N=sxI2DrG26JB$x*6hzzF#{n(RNE#D}=C&)F7)JnD|FW^0{U`5NbGE*vM -z6c*(vmVH1b73#>8tl*QiNez$xYUpM$rVD9JS~r+ijre@N>+U|T9{ZazOyc$r;cAjX -z*nsKAWR5Q%4V+%G<&xT0c9q!pS{8y7y1Inp7xm->JW+xTQ4 -zw=Rn$XY-83dWSFy+(#{kj<}nk+@LfrEh#P>tts2*=VU&m*7A*T`hAg@WUH5yV7XT^ -zM|s7R@c!}bY0WN-b4>YJhH~m4FNnel-ND4@Z)J>GmytL5P~Fpm6LuVZGlYjbBj~6n -zCtMe1%pMPR5-7R#B9{=i{8t4czvk{v?*J)sZxde5gra{~+PATL$o&yjCiWkQzY_Z3 -zz4hYpnygr5za*1m2wD2QYPz$degfc*(^yH5SElj+n*S8nMMg- -z;K6}$aFF#=ga0;GJmfN+?EYJwGHy8&4Wlwy3urP1iyMHNsTA_SQ8$jDazma3G5XG+ -zhaiKap-ClkI@w_&8XLN7bpg5>9dP2S56@9dod&Lcu#bug;nq=f6B|k0K49OT`me|LMUc?#wYlk -zEdi#ai%S>$Hho=2H1I#AUpsi>aojup7QdIdao^K`Px9wsIN%L3H6bK=KXZr0tj&X4 -zO^DHOl*NLOzcvT`peJNhYs2QLwn3phMRYLh&p(jt$V{waoz%{9VHmA9j4%B9`vxMlF; -zkyzxtl%jG#gk6!za`YPPLc{ayX5f;!G&>!-yO&_dh3ZRzEUSzBWgC{9CTGNkpzLrc{p58?BKd{ftze%bJ})v*q7@nFCJ-$|8i9 -zKQ1_Z_nJA|o0+2yD}3`&Xt&qsZJ@&(bu;f9XsTU)@2_u!fEUYlG${&}k`k~MvFJ#g -zep=B -zEenK};%y~yqKax9SNTs6wV8mq@#jKW_hR0oG#RT?=gg*;^ -zrZoFk7<``g{Sm&pyi#b_s~#Cb_J?o3uht$O9%^oe`_zP@_f>;l??|$7c6j%ouQ@rV -znI}Donku9gd(;PKoo`ZnB|lSfL(CcmcK9h{4K2yfdveiX*F?w{S=8l;c_!AY_HaJ* -z0oiU2PE+{7^8nA-0?K-qZg~hyU}@*EIx{)+mOG!6$h4 -z6F#K#O21)wZNhZ9b@y{kq2@|7=0~envCA>0*#=!k0tuBTLY~L#MV{d{kN#XAgk+}U -z5Wt2Zwq_K{0d5|_|K{og?TJK@O?!fe+q%nRmRs{|kh!%&5@&miK0apQ-51|j2S4pt -z&VI=fC!wW&d0H7BKK@5d!ESD*%_88DBubno!~bRugU2H@82>E4qSRJcVDvE}ZZ=i4 -z5X8r%T8*m5$HSX_B@44s4P;WjPQ2c)nsl}mmyn?M#7`%zeqZ(@ugCSL?i -z$~Ug1S`kg9`+<$RurHPIKg#y^=lz&ozJ6xp*qZXC$s2MndW9}aq@>qntRZa9QE!Li -zSrJXSG5JOrD)7n3(!7}A;^MmcNS2|M@NbqUQns5pi|&Sz$2-?FY#%!HySVWPk)MaQ -z+af41N6D0bs5twkv>_pXxdGUoXwOeo -zQC$|98>a_F%Te2+j3@t0@G;H|2x&SPdnF!37VmuFM$t~veq)fKhm==s)&>5HoXXVB -zQtO3|U_-3jWL)<@CB-|Fr~-ct^=#_?Xq3aFG8-+5c_}wff-j2Wl14dG=*Ir9` -z;vz2(O;5Unr5LHNTB*VR&471*8|-qBnLX}C_xgEw_{cs><2@q7jH8*rt35VpFCY2j -zo{CNJWmlFb!fB -z+VoPrGl(o^jqOM?eXroBfTsHID*ndD;oV>?t@L*bcmz1sE7!M3!1u)Tg_tsY?&*yt -z{-g%_mIRW%x&)2xcX+vo4UJ)XO{WNsMdc`kKl(hxy=qIm^e!9ykOZys0+Uqm>(@qN -zZb-1Lj8wG=5eFuL7^|j46t_`%IbWswhBS4nhHL_=6N} -zg#88*wIk^{8^Vsyzmz;?T4HZC#VV;-$K)tUbCnN}WVDwtrQ%2pCXEG1<7o7lhTTE> -zJ~wyXM6LZ`OAuO9z`-gRs+(rVKt*F|`vWoQ8G}y+;(qGCesq_I=;THh4U4k78ic03 -z_yCm4t=c1=4uP8^yGG0;ab -zwAc{o@me2=dAu+9sU(&HWXSXObGX&CD=$ZhdG;RmbJTB_D)n6gG`*O*1MzIMSJvKi -ze{aZF2T{fr)a?Fi2&+vT73&Pu*HcesDu2=t4HLmX -z!$E0!OY_r{p4>*vGR-674`asP7qMmjE3Xe~PfSf;ZVr_I6P}a9nWQpoH=T{EY0~4~ -z#U6eC9jnyEzz1>Q%!0>TcL~KDX?RPKsEnGSmB@N3+{1pwhgLM;rDu=G)7qD+?>(;_ -z!Oz_2=8El2)Ef>-?%HT*gKaZ52~5aN;nwVE7a)vslJ@1QkAa}tuz&* -z{}ialFW|fv&c}3_0)s!=?^0~-489@6VeM=7|Aqi5P!PZt&PFG+F45(Ws(78Sz*3L2+Z&?ga)D?d0Fr8GSgtNgP{e(x)4TO9Efqd-1b -z^^JZ31`-ob|M -zLa;{8};>3e1J*hWDD{Oc^N}f6Pq4vbrb}w-WJ$t;JoFlzdKQmA`yLEOm`=H=CHW~?J -zK?0qp%gJ-+g0LOde;XF7RK)Y)=ESrK1|9oQbwZPv3tQZcF-)lf#XikxG|WUcdS#;B -z+Dhu{3m0&4t2svA?8N`pSfV6UN6#vHZD`Bebk_#_f -zomix$3ThRJ9kUvAF{6-o7-lvI8G<(JGEr00_6E3Jn&AO^l8Jpm_!j|HKUFV&MkoA& -zvidl_aG*eY@vG+9o6|BS91B8-oh -z{*HvN+rWmjS8XHl7EAM$hGDYd>O~?k+09-yEu|E;*?T>yK2@j#8b~V>XV7CYe{OT= -z>LUB&z}7rO9^<4V1;Snvd$h(ZKs1js -z@$uQ*bA^~Ug$YN5&!;pcKb=+%JmIhhd;h0&CMpaOCsyR@%J}z8j#wPI&qXAt^9+He -z9j1Ov+{Ci?JipP-F~v;b(}BbHLx=MRS|G%XjaC&?e+4KpCuZ)UmM0Cmq!=9-vE3bE -zl(uDl!TRICJOw`YvN6tq0LJQ?@TrFkX(FBwO7>>{HC0sRw8dSZ=Ybn$MNYxbw1&A~ -z4U*;<|8AirF7Bn$*+Sty&*Z?8JwK2eN7J?_E^D>Jkr4MPOL6Mtk_aiCs;No_w|y0j -ztG;ThhU2Suqk+D5nR0zmvibT -zRpibrQ#xeY)wgm?O_F7(uo6SZbj$kr=PBOsJ7?4 -z9G04pG&Mz?Y6l0W1i01Fr|wXPET~N10Q+lLgS^HWxZsd_kk*^;~xkae4sW9p)Hr-Rq9-khPzow4)vQQ -zWLpI?ynx6^s2&+dNg9il9{dSTr~2X)ko&aY%Q%Itxe?BWpMfefGcD}Z%Ae(pS~bpt -z^+c*S=m)cQQPeBsXQR+GjKR6(P -z$aaour7~Lxl5JGp8P4|f+;D8h>&9*bn<+IBuE{c2yhv -zRJ;;TW>1S(^yFg6RT~Lc&ew0yV1}z1vaK#Yby?7*^JB*MM|;Y{8h?G`raA -zqna1<#l7B=aj1hP!|gRcx5mU%u2TNXVD|!gN_9b{3=>ZKRu;6ZR;RCaT8tf1l27DN -z6ioSFzet(1MS&w#`tLO&g>Or=Dq5~`rS$EuZD3Vt>fho{(|e9^`>7p13RWjHIVNh) -zZmVdlHYQItN;{ic9A1iG2`FVtKJi%MRn@xWbNhJ{Y(`MWo6`SbN#O^9Kp+hj9p!pO -G8|43b__*%? - -literal 16900 -zcmaf)RZtymu&!}kXmDLfg1h^|-QC?ixG&s2I0Sch5AG1$U4m2?7EFMOsQs1p)$M^xuU52LS<5tyS|A0z!B~T1;5Y)8HZp -zUh9YE!*I`C!>au1!lt}?^7%)ymXa9F0E8%U6cA@Hs@r2|t2YjT?MMEK&sF!xR;P(1 -zJxFf8OgT_&`%_^18f74-j~9B>_v*F_4QG7P$=~I&{g0k-!dKZ;dhG_Yv84aKQ7`JU -zJ^ehid=1+b=_P#o97{5v??~H!^zyIS&U_=f-+Z&XS28Q#IuJUNE}ApzE+z8$!_(s%I3_!)=jTdGmXzz2p&3&czvSwVkj_PR|SM`xDjT-m<)@wFKtJ!fY -z+A9f&c$RQF&Z%Ui9@S9nRjlxMs@)Z5_OxNu^|5JS^tNFPeEv!Mp+fj^Yc}Scf482J -z_jv2_UYgabd?1AMePOH(|ApkUIjM`|sON7?4||4r>}#l#)Nj}LPNV67U-a5cAqgk9 -z4hA)b1i?G`_{?Is2NgH3=G*Y_oV4G*#y>w?4I7fSpx2h|vD&hsqdFVmofnVkNpM8o -zEDOkF7WVse0CrXXeH^X&Y+X5Ugeg(@8XVq_7ngH%kQ4q8to@(w`VD%+t{VjBlZzMA{89 -z;%$e2aiD==VT$}%!%lBbY3xicyog$jB!Djxd7vpR6bXArR{Oqv(5MfWsJg3Yy -zcUpf*M1f-z9ik)^?H|-}` -zxbJl0Xc<(adaW`;Xc^eA&$kJ4EZWH)dOO+mFzw;MBfNjA5<1ZP>E3RWzD|&L1WdK! -z2k&T-AdM3|);yD$reQ{x9G{_#6R5f}9%tdjf-W#_wS$qa(*X;ot*Gkja`g1Q_eN^= -z`0%;Ho3r-6zU-m(+)f%v8KxzXfn20UBXua$j&hd^L+a{0lv^F@IS92IL#!_sffCl2&zHVp_~j(J1np!W5n69+~xPAJ6}_zBa%4jtFt9W -z{@f*=wRJ|ZitBopGm@A{J`xa&M -z)PY`TF0^X2?f!}827nOWNuI-}Ne-gU_A_rT89Qjihq3d_{Ugx}ge|kRq}v@?<-}sM1htR5<=} -zI1L1)$lG(bP|&c#@>`Np6h0xGHe-S%SWq_O*_rH`M&)M5xj9Un#*HS!PqE5 -zISo-XF(NX8c$<8iK|uH&>qt?Q&-b}D+Tgr7t>MFp&WJTZFnPZ1>|RTVqu7iauEwTX -zVJi3CHpH3>2eq__Ox+k#@Bzl=K|7STdhX7MT{c8Ce71~q9Y&PXH}*iaRuCUgMZj4H -z)QyHub -z_qnc(rzc$MCNk878`Sofx_>n{BwDNL?TS=$RO_S6!R*Ey=`(aG@LbB{HGQ+@MqP=h -zu&0VvO0ab!36xlai&*>Xc+6_xPmdSo9TasQ3?*TY!)%lYzD(AZ0HWie+au=#fiLo& -zU+O6Y`-6UchQAZ*C2TI_f~f(2hrMt6KE)jP36+(ZZfle23Dx>Inkk_7xY0&pkp)+N -z%^^0b-mA7bkD<)a8%J{cvSRJ2S;}#v9g(doR}TQ3QGy%7T$YWkQuW{|T0eu$!D%Gg -zhIpru$xwR_h!F-%c~|@zigH-C2m=8{D8VNnCdFPc6Rfz(8f#dDmuUW@`u=TQn?l6ex-ha;(`` -zrS1uS-(@|j8cS+#fW*WdM9k{Fbp6f|!@JL%Gh}@yEWnTjE-DYfVpx0s5?hF9Qzi@Lf>~6Pm?DX{;HP^Q242(r1D1_=jrbppWF;PQk_!Ls -zS?3Zy6SOYNhA^`C9Gr`$aM+kF+PqIpNc~b)YOTag^;@K{!LHyR#-D?kKh>QZn&JHs -z(S}LQ;l-T8IWrlT$vDeig`Pf3fs);`cyZgTesw;vUk*#=1ZlB5zS``R@)U;`I^|DW -z?`Wu5^KI6hZo2(M-a~zF#>3kiX?zjyY=f@)xk3s24jF8WN!RqnV5qMC{5IS-?p~l` -z*Od<2Atam`NRWyKlq2%T>WdXRFci|p)_QD!{us*BG6#&@1J>-ygf`d(+Yt%AR?$|m -zG2&h}ZNhe;3iL&t-&Bo~bSQvwc_uqFF*q*u<%r&3Io&Jc -z8X3Bs8jXqH@NHmV7BRmCYCHHs=Nrep*-}>qojz9eD&96O%Es8n$%gaSnOL~VE%6i@ -z&N;!@pfy%G7dw?+2y1|uMDE?45uzNTNB_7>aX);UvtG>N2^CK4jXJOIypMJdF8LKU -zTYqIdp7&|wl19M2-A~xsFLDE9e-nocdK3)_YdtcQ)W%k7bx|ihJbIc=Z5ZyZ^yh9L -zz(%H87tSJzNkw!4yq5hajBkYU#kO&cksLk7!K-`GO(iyvT=U{|HBlNQU1VB|)w$-~ -z!`vE~Br`P8J<1%ly9{1OIZc%XlCTOPAdcit!jhpR;%=Zn+J^5sT)?#vtC4a+pY5iB -zJDz5Ru-Z>~+fH$VWPdd~FVQ(AT}O25HPC_wANYArttZij2ISLx>m75xSQO6+R*;0g -zmeuq!90F_}HX%kFZpuj4@q)SDa3k?+Bb2PrSZjTt%acFjLT3$4HPduPZ4Sfv?#~)_ -z*x>rvxpNnXh2P;_1YzBnVcqa9VK{mn1MhEaK>}|FhPXm?dB28(cqh2Ag&XIAnbGh%w38mufD688Vg0{`stk3i+PA1e~X7W%o(N09G -z(V+dK5Ra`6>fQc$6V4g$Mc;jTrbmt|ZcfPDi&luFxnBGk{2GGnMACo~C5VWy9A^BK -z%9O|VK>O{=o7e@%H==p}Gh9?4J3)S(^K@|@-bpGMlMM#a6u}N>;hDZ{$m0w+?{P+i -zv!bb`WN0Gnx5bB0s;!iJeK(?O@&xo_Yr==8dbs9N^gw0u(XKa5#%g4gLt%5d9^x&bUp+ -zI*CuQXb^F)LGcsTq00ke&-aZbA7b?Ow}kNZFJJuWYsoo#JJ -zd^|iHd;0^2Lk8)L=de&2-C9OWIvMMW>WH|w6peAk$qJ4MH%Wu;|h=~A6+4h{@J3knK0*pJ@vag9^60=vvWcI&Lb_(VX2 -zy)N7VOA=(g{REg_f)&_ekDo9i1vl8j0R0zl47}1}4kDqz)m%np1-97YCtxX^_8Eb1U&2>fjdHvFw8)9n=PT=mS{*wNJdpIN5Au>lfU5v4<160teocH-d>QHxOk-7@IW}47m1u$uA~w=(B0jA`kk+l2DCPaOxmP~ndvI$ -zYkm8H%IFn;s^>pUrvz6NLyr<`Ro3Korg8A+&kfO!G6vn2h>XJTf5yvnnk!b`Vn06= -zO|u}x(#U)>eRZq|c{Ep6$&^P+2{n)IUvm+$hJWpRp@dc$Pc5;};;;?#x;>0!Q&lV! -z`h5GsX89Y7O)`a6U8!1!!`XBAGrQC|6pr$y?Yi~{n@H;dTYvsSV}Guzrbl=`^4UoI -z8~S7M#L3iCl4D&LZY7p{pxhZgK`flMwzluNP~zogXL!BoNYnrwRFOn1!FLoBg%hgK -zT2%$)cYHjmbW$l?<>3q586J5ELJKn1OZfwK6zZEGypC8YxWSi_nBA+Z_&{j*y_tMb -z6C6(s<>8a1YQkTymwXrrI?Xm2Z(XHsp-_~6s;*Hc@MZxKw?mh=jIMvB--jM9zQDT5 -z_##%J(qN!>z*rOmA{Oc8*IOL7NzRt42R1uBo;?F>^ndx{qY!eko1xoqPknBbx`jeg -zBK1!If?!CHwgxmCjWr7V)0^wAxV{-lm1HGp@U)MCwN_MeX3LZ*jEL+Um3h1ahneA%41;uV#JudJYWnF4<o}yV;v9^YzeZ9DJPbxV -zCaJz8JMuzS|;y@^GISocc73^ZoFw_q)lcpJX%zS -z?3#&5BtAW>(BMlU0{VA<|F{5pf0gcm5ueT^9u0&(YN^<63?O&=!S{pn(` -zLg_%W?ebF_1IK2E8}fXKJRN7Sd1NEd3=zE}{Ff-55EeRtg*n1;E66aMQp_*vt;2W-BHy(2b;Flg4sLL8j`MDJ -zAbfu?@{0+Il12eRII46kiNKmt05>iU=h -z$)Irsw!hHw5wf7*gjxln_O`c8!(m4}pSsbqKLIVrd=!}5jW}+WPlzQ;+_e-& -z?Dy<48J&+h3*@LUmFxqzh_g>rb^`iEl)hiDf5($dZZJpaL!%i&d@Buf3+M~(|w0IKfQ -za3X0Srk%nLvE~Ab9|gBtt2_H<(fw_Zha@}t^K>=dbE+8{uYX2|#N=bmI)Wc;T*rwV -zwd5A@i2kamPB6hHF1AG?W!pUo_~vz+3wdlN<%QSGe!5}^qJ59h?#udS@qUf7 -zv-9ZWcl%ZgYEV62Ov?klP4Ypq+COVB -zzbpQbKJ4p#FTFlCeU?M~M)FWg!L^__)A~q8ym6&0c0f4_^d1Qsf;q;YQPHwFTKQaY -z@}^_vfdLrw7oSN5$O~22BEUPFgd#kF2FBsIH_Toz2Nw=v^=tZBu!NT|Lp7qp(fZ&&7q@7C0rFJD6; -z(%|4PztN>6GF#&@{I1tbNIIaALQ8ulFL8_Y1vGk-QMPKSZe0HpMtxgqkoct%kuq`w -z#x-}Cb*!ytPr?%+STtAMUu{{K-N%@g062$UWI7UOQm3=mc9wknbhD2qEj-!b^P -z%oYhuwx~lumz_3B^a7bYyyq-71@Fw*7ULPhNJocwr5CvLRsE<~sh>maF=R1p!hO** -zh+7MfH?17kb@`xEls`270@5OICG>$(UstdYt%lJ^wwiJK8I1@$5SE2?UF-^CtL8@; -zs4{#zGZBM@8f^QW&S9I{2Bl9>kdJkdQs6??R6c{5q%l*?6D-aNSM(>Zc4);q<1&7n -zVSb1AZyvYG&77Xtb`dpP1hGZw+U_-uc%-;be&gSUcbi*hJ9V!?LnI5O4d&1TOrlrE -z11&b|=uC!5&O5GB^zm!T#ncJ-bmy8|`YuXV_zy3)jPFmR0m -zFLy&N`z42~p5XU|+fn|GAIE2AfPi3JbxB>QXQ+7$3m_ug7v}~qfMAh#5*_)0mSKO^ -z)R>_thix1PNC=^T>X5@o5Ik^s!>_0nb%0+Qu?l@fMu||fRMI8(eq@a06~$a6goXp4 -zTc(!CW&GU`Z?7*~C%0!|`Po;Y-B>bq8(=^Pt0w>CW3cOKf|^OmN3o|I)zb~mlpR!VZRWgf3r$DjB6U@% -zJ!v9xOZ<+LBarT*ahaknq^miC#W^ANPQ%<$&RHDpEBCU_M(sbvsugC-mYh-fO{Sw9 -z2eEARzci;On#5;xRA{kHL-zc9^rxh(B6&XXZ*i0bo|+5(tR}B*i$>CjH@i(J`<5N< -zm*!QawcKB`2qVVWN|!2bmCj+qMz_>lyQe41Uc6GYo8|ZmgRouOWH<`fPtitAzEwsVe{gYe@!;OmfY1hA^J^GP2Zh7jc0#tW -zV;K{f-a2?ll{FjAo&kmu**_ByBXvrN+H7%pUgwrk*v>}T<%nfg$(O1#f`vAf;$Wwj -zK4OU>ekZ7*cXG`zK^{1Jk?6U1Z!$nXMaDUqNo}Oc<%5yn3pWZ=j1+|nlh9DXMmgJp -zw$>=#X^n__>Lz7RpGg`FbOM{jMF-I&Mx~Gtq{nwcJ*VwE0OFOdSNksknPO9!AjUy9a^u}; -zl{GfA#HVPd@8C*|vf;gcdLXrJL?MukrGr%c^ -z`dR^O=T^5*G@CU0fpX=d2?dv}l#Z}rvrURI+yrK9#ndWZg69>4-LW#tEa5!`s{Zgq -z8R@zhQOojaXAAXjJW6}a5>uV1LhgG$u5JQ_EBF0C=A-S5S2BuoH^CBy68!ST^VMKp -z5t!x0xnCI*Lk$t%?=aM?bAC5Sk&8&Qiu@hZj7DiJ;6#WZd1Z764c#+#;>O(U9%lfW -z>suxqZ)SVz&lYoFmEAcgM7u2vPU$2e-Hjzv>AJy1PeOk$DMk`K`~^i^seLl#HX2s@ -z&vS?_kECyji(-+eKdk1750r)$2U(RhTgkZT@l<$kC`GSck-TzG(h{pKG1aJhxkqgZ -zItykNw;mTU?xiP8Q;PAKW4yNPGkd;&0<^_8y4rHh6AzZ1@@Og1z$t3+RoVK`LOEWpvj)dqZ+bn-ZI_R@g2TDm -zUOXS$8{AioF8c*Kd%YqEKoqkyqA= -z;h>9H=F|lLAffO3sj^3_YLHV~t7o60Afgf+&g?fx9El~tAP}$YS=MFe#gI{HMPF+3A4XgD2y6V7pZ8*{ -zm8;APEKL9wC2F|aO=CXGJo^TSmQpb}X_X3Im%nsfn-Yr)Ip(;&N*#Ay_m3?ila&Xh -zA6V?kP!$WD1kP``H7hg@QY|w7?54~1UuB*oXqD_ePJg`i3GPV0EM`;%joWPh;8C{7 -zYdmIemNAl|da??P+nTE06i%eXK303w@_~!CLz4QEZFdnUm~0^2U*Dh4GePdBsTQhV -zsihVr6*e(LETK(_Y=c5vXJenfn3=4BLe-LG|E6?ccR#tlx)pG=|6cC;SaBt^!li5R -zcPgX&c2MsDL}~N-O+3=a0$|oiwZm$c)<&SyI4_0A@|JEcP=7FY3^?#Of0zNSfD^&A$%$p{mSW|9&i*6 -zj(_qDpxvBQ=^ptttH-vj$9~Va*80<33lpe5w3*6)d5BABGb>2&T7!J8KM%t$O}n*W -zJo+7yk8gR<_bN{XJ|u{lon5UfZc>HMFjulERk&KL*jqG{qadfz)xhuQcg|aymb_Y? -zVYhel3JJX|M+K*)DQMX1IZ`*_*vfscZkpLiT)9gL=cKs}uA(KzRP<4d8#RxOLcE#D -zJP9@OB>kt#JaeOXaqm4Kc^GYiehxcy4(-(f*^`-a9<3OAl0lXjMU1hK=(Co4O{$8%UM|E#&*;l(B?QsiT24bqlr*B{Z7V`VuFjMMHlGAysOT^==1z=5qZQ_2R -z-1qLb)#p6A6j}B#jg`CWg~=F5uJg4=mk4 -zMbEFlbNEc>OXUCT5piF*6@<3E+@D1YQ`=LOmdxBa$alJ^s;X9Vnwl%91RCi4CyD~~ -zEfY~;r~^+zF4y_)m=yu>0s3+B%|pI?8RS^ct^$kP#XRzE>S#R+#~GhIc~p)Cii4cW -z9H*m(D~n23;e5HIw0*7&$Qv-cSkS?#GB%E4^9a4Zdg>n0VB=s|P>wkUFR@1Py;++p -zX;6LW6tT+67ZSct6f1(Z{;9<&8!$Q%dsr@?heJCLyu$kE{QNwMcpba!S7M2R5q^_F -z1m`x@%z~KAD3f^-kF`7BW3BU>kYEeLw+hLBNDUxD$O}Z7ySX3c -zb)wd!i4k24wW1_C2@tbyw%f-8qhJmP`&caiZ`w$^LAjZS5Sn#m^9yX>OCkC~g$Cc7>RooBAs^cU -zIlTk#)OXQ82-Til1rliQ85sNCA>X3OzZ4b&8XPSua_&2S?i?lbG}vKCBGdB|nXS>g -zJ0*+o--w^syM8BpvQ@ycNTP2WG0U^*#8-MC0N=cB;m&`}!LXiv%vI8XM1O%D865l( -z{g6XRuw=jeOMjz9WK|@yzj!yA9i}KA0|SHG1bi12L)S{x7e=_kAN~FN)m7xbSP^arS9Rd{|t-bdQUEl`8{54zNMvQ -zmVu~1GPeH>P7JxwZV*CX5cIQzmo3E{siDMziZ%E7Tl9Q4KN4`#}D9_*vX?k}pO!=)gn7_4Bb4bJT -zqDaOnV(7U1_j;to@cwADU9mBc-@BdBUmAHSzyI{7YGVPi_y~b*r-e;$%CQnDe?9;8 -zfw~{4mSb>(|FgeRQE<@@i1>JZxfuACaFBgBIQO3(xsqo~Se?tnEhWOPgi|!6k69%H -zBXMEw6q@;gX1q%5b}P&*(QhwjwHm7%kJPg>aV1XSsKm6t<)rE6*j;x$hUQ|hu`kT) -zV+}ADC0AEh_W-HRr1Y}-%^FExK~@Y^t(ANZuuEJ`p#^k<`-MWnN77L2@X=9jV+>R; -zXOQ`#-WMm65hugihkOgXY4OID(WpNU{=B$ZDs8X^hCKKCdranviTkKK>$2J_;-Ga6 -z>WBEX7GD$0K(CoP7J96eYCwj_U5&HrOXJSWm=N0MQ^#7X5>(8zV6XiWLH3_ZhnV9@qF1Eb95#jw+CTK -zcnC_X6?w!ouwb8!t?ZeXbU*`_*tn=L1`tKaPq~o#XH-LT(pdaeEr(+5o7_BF^YP^9 -z=s=xqrRelm)Z^rj$VCV;RnXkG!NaMn=)gAL=kN77LMYwzIqFtY;-;Q!9U{UKkl*Z; -zxmwdAck=k)YYlsT2UM!0spVa*x7IFL)Qt{!?hIJJcZxQPc`eiw~~Oj@Tz_oM0xtx3Lb{5kxu -zyBD?uz>WN#g_E*U&crG80;MCX-DnFuJuz_nIeOw6$6c?&s+F|L2zU?5G!ekeS!llo -zFPgW-3Pcj`}O?5W?ab_h%Gy97f=v~(o -zy&qFFhNcAIGR5-l!~O!ti+&6tBv?y$VCZ!G*COZC^Rd=v3DD{VK&YZV`0rM0q-=5@nOTtcx@ -z-`GfyVTF_)=xoTY-xG)BHAl-#;@k>0Kap5G)B~X77JGh`U;(W#+Xleny2|+?3X~v9 -z@j4(Oa(GxV=hv@n1U4Y(PY6pg$c&Ot;)efq)~zTw>;uHy`pS!hYaNUHxEYhbgRg4R -z+}+}7o`g)4OPEQ|;tiYeawTA$%HmQyClOH{QqjoI$3uxnpv;6|Hoy*8NC^3e-^$N* -zqqby_w*0S5T>t%`@v?z_`@m;FByBE`COSJ7m_~uq^-Bim*HTzq_chCA9jeHpXN(2n -zwRqW7h)`1w=SY~Q#F+#wWc43wU)ql>D-{W#MMi*+Rc<(sqj$1IsI?*Vo~~JX4iGFY -zSjVn{Ia}(<$;mhGkK6li&$laGUX5+PgyS=U#yks+rN3QUeb1{R0P)Mr;duDNP0Yns -zOl80yG--mz(9cLJmrW%6skc}}J*KYlL*%B2MMfm>8W3{uoeA1tCC=;U0l+}4z>%rz1`1Gu3qlk(DUqGWSub-M#qTbUB+d9M069OLgJ6ct8Id?;aM)g-r9s^V6BrQ}Q;SCiP`udh7DC -zQX$nG;n1i3pom{#4@R?{E?z&>^3sL?I2rH<%HigVl9la73e4N^TR>PE}F -zsi3VDlCxI}2NOm!ndIQSbW~gNZ4rN(jki^a>Fbq! -zqTN5 -zzb`nx8&_h%Jrt7lQxR^o;6yE0jUGfj6BHagGKnEIbC?*Yeh-mN_p6 -zlPomN>R(3=k&0Ki-xElR=54S -ziifTvyozV0-H|T?}miG^F_wtBpw#IDTI~O&zZ=pp6zI7~U;(eX9v~ -z%_Rrklp$gbO-9{o@iq>QY$8+WLWjtqUprlw=!9l&&i<-B;;B?gDuUYF04x={Q|PYo -z11qyPuIW6^msVN_PE8KdAMXa}bHL6LC^fQ9sh369#H1cfF?JZ}v`b#V$&6F1HA?9- -z8rMp!9QAw;KUupJE(75s%Q_j;=twh?gcLwR?pti!=J%3LhEmj*cmxEL#xOjNHpVeK -zJkF%}PF#r=gweO>TUjCt`~eJ7()chG!YE-`x^-8vG;ltjSQ*{>Exm{gthe@Wqr_;) -z0wt5sLc;HhZgRcM=_rjYuGPk6qTcdMHcs}#u#-NnrJ>ijEn2POpi%bVAyH$%NC@JW -z!9x#~LZ0#)=w{X8oW39GR&eJl^`<7%yQQ1IMRYe1(f#2jGXHCzX6=QT%WeN8>DptC -zHdSdtJVzrAI(JAmUV3k0>(|f-Xp$15@*N%7K>n%=8xkhRkB3QAUtf=ah{(e3zoSSq -z_gFfN{zLz`jCqlr&;1O+r(+F_Z0oUu;MXftO1`Y -z9;O;>OCXbj;jbt_S7jVfllzmVYhq*#nMM~j1j#8VFg%#?vdErxSYKI2XR#z#^jrF| -z60VzCe$K!`P7W(fGZ`zDbu=Gj|Fluc!xb3b3?KS{Jm5T)ZILV)F5q8zrZN3x1!?Fl -zj24#65txQAH>pypq52gcF^Lw8LkW1LoMwVHld&c-soCEOJ`7#g5|?z#rkMgkK7BD? -zv5)5fIFMR6Y+7b6;Ou);_P~PlRc2e$)>HPum(WG>M&1%61LbYx=>T1OuOHP=A_2Ml -zUJa0_6*NB&eSM@;e}$dm67YWg_RVCo!)>o6Rkzh4GG20Rk8#RK+5)kj -zy-EvI3s#yE&SmNou7&*UrnmOiQ_-c!M98x?rSX}WW}0I7mNW&~u)Vo_w^FUmMKUp> -z8k)=i)!=z!;!K+sl(Lhz*Vd$PEuCsaMon#-vS>REEy6K+i5a(<$=x(75fa)xG@=%o -zR1GQOG=s!5g(EG{JieFH*|kCh7=CEGG~xLSIY@MDuIiAnc`G2Ge{P@B(m6G -z!ibqfOA9p9!7d%@C@Z6Oj!J3yF%OjokW8F{Y}gut?pbEsRm(4LL??RqN~(!1fsK#O -zx=08kW?ie)L(K_4s$=P~flB)?RT~0m>*m4)K}Eg&^ysOF8lN -zUHK0Q90oI&{$mRN4;~O7eZ2)RYWiOSNUxax118K@axIASH?!$hWNTeE-m< -z9|d2NgO0N~GYHc0yR$}HApgS_*(Si(CRarIKe5r*$mNd^46r(X7=_aLFxEIYsbgvH -znrzh|J#4IY*379ZTcw$}tG6{QE7PnE4Rz`3%NtHKG_U~aaSk@Sx>k@6ej8+~c|Yc1 -zO&By$u_ANpr;D`5D&%W#Fdl9`BhvdZ5`UZwLv{1Hpy9lA>ut_To&@^KV8eBInUrO5 -zh52}zCbS}L24&AucCm_(e?;?E_ef|R)nd$k+BOlW94v|UU~blRLjQ0ZP-4r2%|3Mn -z>o)c|#p9v2Mo^cLhvg|xC^b2rL@3y2qYKOs?@M}`_`2q==9(4=kTk_PZBIhX-PBrZ -zr~4n>by9I>XJ-@K+>o8|G;Pd^cW)?r)m~#|TJhd}QmeX{jlF$FjtCHM4zkU<60>Zs -z%Hys(I^Fbs!^Li)<*(mdMDF~twQ_bC9DMnUdoO<&F-3^A3(9GW4BH?t7v#5rXa(*RMsaFXpk@rOtgnql#(8*wa~=>H&U=;wN0Q<% -z=~0Qf+>{j>5oC?u{)Ajpd<^k1GgPzB2_X=HLz81#(ra*Wz}OO-za_H(wqrv+ -zz0y`v?a{P}lFJz=@BUy(A)pd~b{ghG$JJ7r@AlNxR+>kt6nYMil7}v99Ja?<@UZ3T -zeP1&kMRSM{5+R84Gc9j#ct$;ly+^es_n)lXhG>_EcB%`8iYCWLpCv8)3~8MaxHrGc -zWH36`!Flt6p(JJsBEPqrE&gHZ?iZtxyx?@fgPx*GS)go(Nf7>mW01|;SB);nF)3s-o4wF7D!)tc}@L$K1~NQb -z#Yr2Tunx+&wm$nlh2CtbCg8*b5`pf^Y+pukoQ>UqK#>9GQek~Po6Bh899xq^a4oaN -z9sDuzXNM=T^ExFYu?oyxI-}8y%D;slDQ*$`e5+$KKq!pF-*Qc6*L_WdXk^K$IEown -zc`-GIjJQlcjeh%cL}$EWcnajL%v-BuW@70M(BzzY-<$NJlWQg2rNIpwvZO(rx6c;G -z_UBD)7Cj+GP^Rw?)N_Nikx*qcIPWa%MBy?TL-6TWG;|5d3z|f%7il*;Ac9f={o-sOWXfH2ow& -z+F%t%kw65bbTBi=kaCFWiK~y^%2iyKohma@H`Ql2SmxhqN|;zqx3*C&8_FG(8wPqn -z80VlP%Gr)DvZLLlMf_OsXDo*4wm909tv4{~9yF8NS%e&sJ#dPsVWzk;(a?)XK|KjU -zmKml2h#A~>%-bFxM<7`wSX>`I?z8$Ca++fy%HqqV=8QYYD~bl^Hz_tBVxFF`f!?T6 -z!OC4>a={OOkbTFV#ew8k2GrG${kNo(rcXXA_NWUqI*@?8f!7jnu3$bUg%u}bhY5Q));;cCAc0gS -zHeTk{K!dAfJ6}@_zTLUn#qe2z2>ZdYkePg(!A+9v>R>`9$hU~PuED*xe7hANwC~xc -zzMc7YMmZr7Iy0P~w;nbL>dml8nV{FL;+xs}qjWkGV5~K1I6DPEC&#K9z5E?@9vBT9 -zvo#L&#*9687;Wd0D#-M4&3F8(7sFq)qrj$;8%`og3#@6+hSB^|^UtXtP#SOBvdY^G -znH{rdVJq%P=QxzF3KPx3etK`XD%|1rVH#OZ35YdYtyAh8`vg5MWd_yI4)jPAmo4j_!3e<(RzCocf`Y+$BAs988hq->eMY}S?z>|#t_c*3a#|wBAi{r! -z{dW0rO)!J3t~1!Um(@E{ATxo*eW!!k$}$1~;aQ@PPH8`A%_SDxVmA)U!Hw)$KXA7$ -zqdYbaY15D(b-OI$URl0+-TYf+!wm}Fwiu|ApRr6t288`7`y!ZbjuZgH`OF0cwYAez -zqSFt2_caUMO6L4Xk>1Be-C -zNHX4bgVd8eU&h^$7@$FnL%%Qp$e+fZ(4dAotr#y7!RrZ>u)-F!F&*jOE8kA5rqU|| -zkg!kLmDxaol3FWgZ=YGJwHYE#cf7j}8z24RotEJKC)?r64}@x}^89m-1nf0~ -z*_bz@JP+Ml3hd*HWuOR@sNVh74L-Sw?LaYKwKo+rvy}#|aFc!~jT1o|KFR)cI+)Bn -z7Gg?S>|0zfR!%gT?WBBV*!!8j6W>|lJse7F&5>m#;ooWbRQhP@izf8nK?3n;AClTI -z6#M340iQgXluMzeY19}<(0FQ=O$wq@ql-?Y9&<2A-S>4Nw#z1Z-(eOaL`iCMI&1sAHP4i1wfU%}{TH10ti=qYU -zDz0-eM;$$6p`VP-jrsMCM}MB&A!(m3WE#f9F_{oZS)?8^0-?^g11w23S(X*2v9^k* -zKU)$N`g+?WLff<~UDFNP<{vgF`ivNrY!#|!D3|%nvN;axyiKQdEH}n|UAvW!JH2@P -z6TzqIA6}zL<{b4pMQp)4ffz(REKD*y3@}3^xF7ui{>6upo0Tm&dOIrt&>JXx_VQrn -z?-nhZdK!u;m!%$urT+S%i$tA>aj?l1`%j#jH%{|kdYyAos^NAM25jH<6TePW$nes= -z1!HeZ;$v}XEv4u4(RnINU~U}A*?wwCqU9m|k--nxsx^vf5AeRn$pVh?0Mnuc`C!uLu2;Vg{7)wfw`U -zX;*Wa;BHUq-TwcZ=lb=LAJ!W;9=uX-aQwln)6svvPf%SQ<1JdhQIPpzpQJ_KDid+% -z!(xv_|7G`-+M9p4uk#~(Mx)cG<|di_EPs|%wf~Qb+;nQM%q9N4&z7FNB6Z+G+^+MR -zj)|=QD8BER{dSeRdtd$LH*1N?VC!qI+*y}-;)|d!tvt|0N -zm5Vlct%>!zH1~C#{PXnRXO%R17-p8ep7o3QUvcWms)tYH$`9TvKIF>&@BVzZ?VI=h -zpErH+ydwvGOs@R*Hs)&kpXVngrCvTXb;0-d@xj~oPM7_l|L;HY`3dlY6p$IBU;poR -XXyugpZ1#qMfq}u()z4*}Q$iB}^Bg|U - -diff --git a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java -index 75ed5050f72c001d6eab117a2c0b352a413548bd..180c0a532bbac10a8280b63eb7aa783a1bfbb237 100644 ---- a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java -+++ b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java -@@ -46,6 +46,7 @@ public class MinecraftCommandPermissionsTest { - 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 { -@@ -58,6 +59,25 @@ public class MinecraftCommandPermissionsTest { - } - - 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/patches/server/0002-mc-dev-fixes.patch b/patches/server/0002-mc-dev-fixes.patch deleted file mode 100644 index 48e9403..0000000 --- a/patches/server/0002-mc-dev-fixes.patch +++ /dev/null @@ -1,71 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: AlphaKR93 -Date: Sat, 2 Nov 2024 15:26:27 +0900 -Subject: [PATCH] mc dev fixes - - -diff --git a/src/main/java/net/minecraft/server/commands/DebugCommand.java b/src/main/java/net/minecraft/server/commands/DebugCommand.java -index 06568cc308e06b16f43ec7facd5e2c4e36f3fee9..c3374d11753d8cd152784727bf7ed08d18931136 100644 ---- a/src/main/java/net/minecraft/server/commands/DebugCommand.java -+++ b/src/main/java/net/minecraft/server/commands/DebugCommand.java -@@ -271,5 +271,12 @@ public class DebugCommand { - public void close() { - IOUtils.closeQuietly((Writer)this.output); - } -+ -+ // Plazma start - Decompile fixes -+ @Override -+ public org.bukkit.command.CommandSender getBukkitSender(final CommandSourceStack wrapper) { -+ return wrapper.getBukkitSender(); -+ } -+ // Plazma end - Decompile fixes - } - } -diff --git a/src/main/java/net/minecraft/server/commands/ReturnCommand.java b/src/main/java/net/minecraft/server/commands/ReturnCommand.java -index 9f82ca1fee2a319d52a4106c3581f5e9a9554a9e..f5c7748117342dedd9d600881143c206e429fe5b 100644 ---- a/src/main/java/net/minecraft/server/commands/ReturnCommand.java -+++ b/src/main/java/net/minecraft/server/commands/ReturnCommand.java -@@ -16,18 +16,18 @@ import net.minecraft.commands.execution.tasks.BuildContexts; - import net.minecraft.commands.execution.tasks.FallthroughTask; - - public class ReturnCommand { -- public static > void register(CommandDispatcher dispatcher) { -- dispatcher.register( -- (LiteralArgumentBuilder)LiteralArgumentBuilder.literal("return") -- .requires(source -> source.hasPermission(2)) -- .then( -- RequiredArgumentBuilder.argument("value", IntegerArgumentType.integer()) -- .executes(new ReturnCommand.ReturnValueCustomExecutor<>()) -- ) -- .then(LiteralArgumentBuilder.literal("fail").executes(new ReturnCommand.ReturnFailCustomExecutor<>())) -- .then(LiteralArgumentBuilder.literal("run").forward(dispatcher.getRoot(), new ReturnCommand.ReturnFromCommandCustomModifier<>(), false)) -+ -+ // Plazma start - Decompile fixes -+ public static void register(CommandDispatcher dispatcher) { -+ dispatcher.register(net.minecraft.commands.Commands.literal("return") -+ .requires(source -> source.hasPermission(2)) -+ .then(net.minecraft.commands.Commands.argument("value", IntegerArgumentType.integer()) -+ .executes(new ReturnCommand.ReturnValueCustomExecutor<>())) -+ .then(net.minecraft.commands.Commands.literal("fail").executes(new ReturnCommand.ReturnFailCustomExecutor<>())) -+ .then(net.minecraft.commands.Commands.literal("run").forward(dispatcher.getRoot(), new ReturnCommand.ReturnFromCommandCustomModifier<>(), false)) - ); - } -+ // Plazma end - Decompile fixes - - static class ReturnFailCustomExecutor> implements CustomCommandExecutor.CommandAdapter { - @Override -diff --git a/src/main/java/net/minecraft/world/entity/animal/allay/AllayAi.java b/src/main/java/net/minecraft/world/entity/animal/allay/AllayAi.java -index 3fc1ec01e1a77a169ec762a23f15b97f040ce5f8..b5464708c1fa949e7df8aed71126ccad72d66ee3 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/allay/AllayAi.java -+++ b/src/main/java/net/minecraft/world/entity/animal/allay/AllayAi.java -@@ -62,8 +62,8 @@ public class AllayAi { - Activity.CORE, - 0, - ImmutableList.of( -- new Swim<>(0.8F), -- new AnimalPanic(2.5F), -+ new Swim(0.8F), // Plazma - mc dev fixes -+ new AnimalPanic(2.5F), // Plazma - mc dev fixes - new LookAtTargetSink(45, 90), - new MoveToTargetSink(), - new CountDownCooldownTicks(MemoryModuleType.LIKED_NOTEBLOCK_COOLDOWN_TICKS), diff --git a/patches/server/0005-Fork-friendly-Rebranding.patch b/patches/server/0005-Fork-friendly-Rebranding.patch deleted file mode 100644 index bbcde1d..0000000 --- a/patches/server/0005-Fork-friendly-Rebranding.patch +++ /dev/null @@ -1,425 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: AlphaKR93 -Date: Wed, 21 Dec 2022 19:31:24 +0900 -Subject: [PATCH] Fork-friendly Rebranding - - -diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java -index be1bb14dca9367b9685841985b6198376986c496..b58f4eb26ce6bd6696b3289b5f199ab8f0ff28b6 100644 ---- a/src/main/java/com/destroystokyo/paper/Metrics.java -+++ b/src/main/java/com/destroystokyo/paper/Metrics.java -@@ -592,7 +592,7 @@ public class Metrics { - boolean logFailedRequests = config.getBoolean("logFailedRequests", false); - // Only start Metrics, if it's enabled in the config - if (config.getBoolean("enabled", true)) { -- Metrics metrics = new Metrics("Purpur", serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish // Purpur - Purpur config files -+ Metrics metrics = new Metrics(io.papermc.paper.ServerBrandConstants.BRAND_NAME, serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish // Purpur - Purpur config files // Plazma - Fork-friendly Rebranding - - metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { - String minecraftVersion = Bukkit.getVersion(); -diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -index fe66e43c27e0798770e102d1385bacbaa90bda07..1ad562a95809cf7d503f5446f8645ba8c2680914 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -@@ -36,7 +36,7 @@ public class PaperVersionFetcher implements VersionFetcher { - private static final int DISTANCE_ERROR = -1; - private static final int DISTANCE_UNKNOWN = -2; - // Purpur start - Rebrand -- private static final String DOWNLOAD_PAGE = "https://purpurmc.org/downloads"; -+ // private static final String DOWNLOAD_PAGE = "https://purpurmc.org/downloads"; // Plazma - Fork-friendly Rebranding - private static int distance = DISTANCE_UNKNOWN; public int distance() { return distance; } - // Purpur end - Rebrand - -@@ -52,7 +52,7 @@ public class PaperVersionFetcher implements VersionFetcher { - if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) { - updateMessage = text("You are running a development version without access to version information", color(0xFF5300)); - } else { -- updateMessage = getUpdateStatusMessage("PurpurMC/Purpur", build); // Purpur - Rebrand -+ updateMessage = getUpdateStatusMessage("PlazmaMC/Plazma", build); // Purpur - Rebrand // Plazma - Fork-friendly Rebranding - } - final @Nullable Component history = this.getHistory(); - -@@ -63,8 +63,12 @@ public class PaperVersionFetcher implements VersionFetcher { - //int distance = DISTANCE_ERROR; // Purpur - use field - Rebrand - - final OptionalInt buildNumber = build.buildNumber(); -- if (buildNumber.isPresent()) { -- distance = fetchDistanceFromSiteApi(build, buildNumber.getAsInt()); -+ // Plazma start - TODO: CI Checking -+ //noinspection PointlessBooleanExpression -+ if (false && buildNumber.isPresent()) { -+ throw new UnsupportedOperationException("Version fetching from CI is not supported yet"); -+ // distance = fetchDistanceFromSiteApi(build, buildNumber.getAsInt()); -+ // Plazma end - TODO: CI Checking - } else { - final Optional gitBranch = build.gitBranch(); - final Optional gitCommit = build.gitCommit(); -@@ -80,12 +84,13 @@ public class PaperVersionFetcher implements VersionFetcher { - default -> text("* You are " + distance + " version(s) behind", NamedTextColor.YELLOW) // Purpur - Rebrand - .append(Component.newline()) - .append(text("Download the new version at: ") -- .append(text(DOWNLOAD_PAGE, NamedTextColor.GOLD) -+ .append(text(io.papermc.paper.ServerBrandConstants.DOWNLOAD_PAGE, NamedTextColor.GOLD) // Plazma - Fork-friendly Rebranding - .hoverEvent(text("Click to open", NamedTextColor.WHITE)) -- .clickEvent(ClickEvent.openUrl(DOWNLOAD_PAGE)))); -+ .clickEvent(ClickEvent.openUrl(io.papermc.paper.ServerBrandConstants.DOWNLOAD_PAGE)))); // Plazma - Fork-friendly Rebranding - }; - } - -+ /* // Plazma - TODO: CI Checking - private static int fetchDistanceFromSiteApi(final ServerBuildInfo build, final int jenkinsBuild) { - try { - try (final BufferedReader reader = Resources.asCharSource( -@@ -105,6 +110,7 @@ public class PaperVersionFetcher implements VersionFetcher { - return DISTANCE_ERROR; - } - } -+ */ // Plazma - TODO: CI Checking - - // Contributed by Techcable in GH-65 - private static int fetchDistanceFromGitHub(final String repo, final String branch, final String hash) { -diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -index bc7e4e5560708fea89c584b1d8b471f4966f311a..986be14b664e55111a590f5c1fded5a799578da0 100644 ---- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -+++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java -@@ -20,7 +20,7 @@ public final class PaperConsole extends SimpleTerminalConsole { - @Override - protected LineReader buildReader(LineReaderBuilder builder) { - builder -- .appName("Purpur") // Purpur - Rebrand -+ .appName(io.papermc.paper.ServerBrandConstants.BRAND_NAME) // Purpur - Rebrand // Plazma - Fork-friendly Rebranding - .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) - .completer(new ConsoleCommandCompleter(this.server)) - .option(LineReader.Option.COMPLETE_IN_WORD, true); -diff --git a/src/main/java/io/papermc/paper/ServerBrandConstants.java b/src/main/java/io/papermc/paper/ServerBrandConstants.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3af005ce2bbd30601917987d8c831db23c733ab8 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/ServerBrandConstants.java -@@ -0,0 +1,29 @@ -+package io.papermc.paper; -+ -+import org.jetbrains.annotations.Nullable; -+ -+public interface ServerBrandConstants { -+ -+ // Basic brand informations -+ String BRAND_NAME = "Plazma"; -+ String RESOURCE_PATH = "META-INF/maven/org.plazmamc.plazma/plazma-api/pom.properties"; -+ -+ @Nullable -+ String ASCII_LOGO = """ -+ -+ \033[38;2;215;23;133m█\033[38;2;212;24;134m█\033[38;2;209;26;134m█\033[38;2;205;27;135m█\033[38;2;202;28;136m█\033[38;2;199;30;136m█\033[38;2;196;31;137m╗\033[38;2;193;33;138m \033[38;2;190;34;138m█\033[38;2;186;35;139m█\033[38;2;183;37;140m╗\033[38;2;180;38;140m \033[38;2;177;39;141m \033[38;2;174;41;142m \033[38;2;170;42;142m \033[38;2;167;43;143m \033[38;2;164;45;144m \033[38;2;161;46;144m█\033[38;2;158;47;145m█\033[38;2;155;49;146m█\033[38;2;151;50;146m█\033[38;2;148;52;147m█\033[38;2;145;53;148m╗\033[38;2;142;54;148m \033[38;2;139;56;149m█\033[38;2;135;57;149m█\033[38;2;132;58;150m█\033[38;2;129;60;151m█\033[38;2;126;61;151m█\033[38;2;123;62;152m█\033[38;2;120;64;153m█\033[38;2;116;65;153m╗ \033[38;2;113;67;154m█\033[38;2;110;68;155m█\033[38;2;107;69;155m█\033[38;2;104;71;156m╗\033[38;2;101;72;157m \033[38;2;97;73;157m \033[38;2;94;75;158m \033[38;2;91;76;159m█\033[38;2;88;77;159m█\033[38;2;85;79;160m█\033[38;2;81;80;161m╗ \033[38;2;78;81;161m \033[38;2;75;83;162m█\033[38;2;72;84;163m█\033[38;2;69;86;163m█\033[38;2;66;87;164m█\033[38;2;62;88;165m█\033[38;2;59;90;165m╗\033[38;2;56;91;166m\s -+ \033[38;2;215;23;133m█\033[38;2;212;24;134m█\033[38;2;209;26;134m╔\033[38;2;205;27;135m═\033[38;2;202;28;136m═\033[38;2;199;30;136m█\033[38;2;196;31;137m█\033[38;2;193;33;138m╗ \033[38;2;190;34;138m█\033[38;2;186;35;139m█\033[38;2;183;37;140m║\033[38;2;180;38;140m \033[38;2;177;39;141m \033[38;2;174;41;142m \033[38;2;170;42;142m \033[38;2;167;43;143m \033[38;2;164;45;144m█\033[38;2;161;46;144m█\033[38;2;158;47;145m╔\033[38;2;155;49;146m═\033[38;2;151;50;146m═\033[38;2;148;52;147m█\033[38;2;145;53;148m█\033[38;2;142;54;148m╗ \033[38;2;139;56;149m╚\033[38;2;135;57;149m═\033[38;2;132;58;150m═\033[38;2;129;60;151m█\033[38;2;126;61;151m█\033[38;2;123;62;152m█\033[38;2;120;64;153m╔\033[38;2;116;65;153m╝ \033[38;2;113;67;154m█\033[38;2;110;68;155m█\033[38;2;107;69;155m█\033[38;2;104;71;156m█\033[38;2;101;72;157m╗\033[38;2;97;73;157m \033[38;2;94;75;158m█\033[38;2;91;76;159m█\033[38;2;88;77;159m█\033[38;2;85;79;160m█\033[38;2;81;80;161m║ \033[38;2;78;81;161m█\033[38;2;75;83;162m█\033[38;2;72;84;163m╔\033[38;2;69;86;163m═\033[38;2;66;87;164m═\033[38;2;62;88;165m█\033[38;2;59;90;165m█\033[38;2;56;91;166m╗ -+ \033[38;2;215;23;133m█\033[38;2;212;24;134m█\033[38;2;209;26;134m█\033[38;2;205;27;135m█\033[38;2;202;28;136m█\033[38;2;199;30;136m█\033[38;2;196;31;137m╔\033[38;2;193;33;138m╝ \033[38;2;190;34;138m█\033[38;2;186;35;139m█\033[38;2;183;37;140m║\033[38;2;180;38;140m \033[38;2;177;39;141m \033[38;2;174;41;142m \033[38;2;170;42;142m \033[38;2;167;43;143m \033[38;2;164;45;144m█\033[38;2;161;46;144m█\033[38;2;158;47;145m█\033[38;2;155;49;146m█\033[38;2;151;50;146m█\033[38;2;148;52;147m█\033[38;2;145;53;148m█\033[38;2;142;54;148m║ \033[38;2;139;56;149m \033[38;2;135;57;149m \033[38;2;132;58;150m█\033[38;2;129;60;151m█\033[38;2;126;61;151m█\033[38;2;123;62;152m╔\033[38;2;120;64;153m╝\033[38;2;116;65;153m \033[38;2;113;67;154m█\033[38;2;110;68;155m█\033[38;2;107;69;155m╔\033[38;2;104;71;156m█\033[38;2;101;72;157m█\033[38;2;97;73;157m█\033[38;2;94;75;158m█\033[38;2;91;76;159m╔\033[38;2;88;77;159m█\033[38;2;85;79;160m█\033[38;2;81;80;161m║ \033[38;2;78;81;161m█\033[38;2;75;83;162m█\033[38;2;72;84;163m█\033[38;2;69;86;163m█\033[38;2;66;87;164m█\033[38;2;62;88;165m█\033[38;2;59;90;165m█\033[38;2;56;91;166m║ -+ \033[38;2;215;23;133m█\033[38;2;212;24;134m█\033[38;2;209;26;134m╔\033[38;2;205;27;135m═\033[38;2;202;28;136m═\033[38;2;199;30;136m═\033[38;2;196;31;137m╝\033[38;2;193;33;138m \033[38;2;190;34;138m█\033[38;2;186;35;139m█\033[38;2;183;37;140m║\033[38;2;180;38;140m \033[38;2;177;39;141m \033[38;2;174;41;142m \033[38;2;170;42;142m \033[38;2;167;43;143m \033[38;2;164;45;144m█\033[38;2;161;46;144m█\033[38;2;158;47;145m╔\033[38;2;155;49;146m═\033[38;2;151;50;146m═\033[38;2;148;52;147m█\033[38;2;145;53;148m█\033[38;2;142;54;148m║ \033[38;2;139;56;149m \033[38;2;135;57;149m█\033[38;2;132;58;150m█\033[38;2;129;60;151m█\033[38;2;126;61;151m╔\033[38;2;123;62;152m╝\033[38;2;120;64;153m \033[38;2;116;65;153m \033[38;2;113;67;154m█\033[38;2;110;68;155m█\033[38;2;107;69;155m║\033[38;2;104;71;156m╚\033[38;2;101;72;157m█\033[38;2;97;73;157m█\033[38;2;94;75;158m╔\033[38;2;91;76;159m╝\033[38;2;88;77;159m█\033[38;2;85;79;160m█\033[38;2;81;80;161m║ \033[38;2;78;81;161m█\033[38;2;75;83;162m█\033[38;2;72;84;163m╔\033[38;2;69;86;163m═\033[38;2;66;87;164m═\033[38;2;62;88;165m█\033[38;2;59;90;165m█\033[38;2;56;91;166m║ -+ \033[38;2;215;23;133m█\033[38;2;212;24;134m█\033[38;2;209;26;134m║\033[38;2;205;27;135m \033[38;2;202;28;136m \033[38;2;199;30;136m \033[38;2;196;31;137m \033[38;2;193;33;138m \033[38;2;190;34;138m█\033[38;2;186;35;139m█\033[38;2;183;37;140m█\033[38;2;180;38;140m█\033[38;2;177;39;141m█\033[38;2;174;41;142m█\033[38;2;170;42;142m█\033[38;2;167;43;143m╗ \033[38;2;164;45;144m█\033[38;2;161;46;144m█\033[38;2;158;47;145m║\033[38;2;155;49;146m \033[38;2;151;50;146m \033[38;2;148;52;147m█\033[38;2;145;53;148m█\033[38;2;142;54;148m║ \033[38;2;139;56;149m█\033[38;2;135;57;149m█\033[38;2;132;58;150m█\033[38;2;129;60;151m█\033[38;2;126;61;151m█\033[38;2;123;62;152m█\033[38;2;120;64;153m█\033[38;2;116;65;153m╗ \033[38;2;113;67;154m█\033[38;2;110;68;155m█\033[38;2;107;69;155m║\033[38;2;104;71;156m \033[38;2;101;72;157m╚\033[38;2;97;73;157m═\033[38;2;94;75;158m╝\033[38;2;91;76;159m \033[38;2;88;77;159m█\033[38;2;85;79;160m█\033[38;2;81;80;161m║ \033[38;2;78;81;161m█\033[38;2;75;83;162m█\033[38;2;72;84;163m║\033[38;2;69;86;163m \033[38;2;66;87;164m \033[38;2;62;88;165m█\033[38;2;59;90;165m█\033[38;2;56;91;166m║ -+ \033[38;2;215;23;133m╚\033[38;2;212;24;134m═\033[38;2;209;26;134m╝\033[38;2;205;27;135m \033[38;2;202;28;136m \033[38;2;199;30;136m \033[38;2;196;31;137m \033[38;2;193;33;138m \033[38;2;190;34;138m╚\033[38;2;186;35;139m═\033[38;2;183;37;140m═\033[38;2;180;38;140m═\033[38;2;177;39;141m═\033[38;2;174;41;142m═\033[38;2;170;42;142m═\033[38;2;167;43;143m╝ \033[38;2;164;45;144m╚\033[38;2;161;46;144m═\033[38;2;158;47;145m╝\033[38;2;155;49;146m \033[38;2;151;50;146m \033[38;2;148;52;147m╚\033[38;2;145;53;148m═\033[38;2;142;54;148m╝ \033[38;2;139;56;149m╚\033[38;2;135;57;149m═\033[38;2;132;58;150m═\033[38;2;129;60;151m═\033[38;2;126;61;151m═\033[38;2;123;62;152m═\033[38;2;120;64;153m═\033[38;2;116;65;153m╝ \033[38;2;113;67;154m╚\033[38;2;110;68;155m═\033[38;2;107;69;155m╝\033[38;2;104;71;156m \033[38;2;101;72;157m \033[38;2;97;73;157m \033[38;2;94;75;158m \033[38;2;91;76;159m \033[38;2;88;77;159m╚\033[38;2;85;79;160m═\033[38;2;81;80;161m╝ \033[38;2;78;81;161m╚\033[38;2;75;83;162m═\033[38;2;72;84;163m╝\033[38;2;69;86;163m \033[38;2;66;87;164m \033[38;2;62;88;165m╚\033[38;2;59;90;165m═\033[38;2;56;91;166m╝\033[0m -+ """; -+ -+ // Support URLs -+ String DOWNLOAD_PAGE = "https://plazmamc.org/downloads"; -+ String CONFIG_REFERENCE = "https://docs.plazmamc.org/plazma/administration/reference/configurations"; -+ String START_GUIDE = "https://docs.plazmamc.org/plazma/administration/getting-started"; -+ String USAGE_GUIDE = "https://docs.plazmamc.org/plazma/administration/getting-started/next-step"; -+ String SUPPORT_PAGE = "https://github.com/PlazmaMC/Plazma/issues"; -+ -+} -diff --git a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -index 0843e7c5c335a58d955a0841f2e02a9e4ac824d9..f7c15d0a63f948c4f52f6e6bbcbc4dc096b3281f 100644 ---- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -+++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java -@@ -30,10 +30,6 @@ public record ServerBuildInfoImpl( - private static final String ATTRIBUTE_GIT_BRANCH = "Git-Branch"; - private static final String ATTRIBUTE_GIT_COMMIT = "Git-Commit"; - -- private static final String BRAND_PAPER_NAME = "Paper"; -- private static final String BRAND_PUFFERFISH_NAME = "Pufferfish"; // Purpur - Fix pufferfish issues -- private static final String BRAND_PURPUR_NAME = "Purpur"; // Purpur - Rebrand -- - private static final String BUILD_DEV = "DEV"; - - public ServerBuildInfoImpl() { -@@ -44,9 +40,9 @@ public record ServerBuildInfoImpl( - this( - getManifestAttribute(manifest, ATTRIBUTE_BRAND_ID) - .map(Key::key) -- .orElse(BRAND_PURPUR_ID), // Purpur - Fix pufferfish issues // Purpur - Rebrand -+ .orElse(BRAND_ID), // Purpur - Fix pufferfish issues // Purpur - Rebrand // Plazma - Fork-friendly Rebranding - getManifestAttribute(manifest, ATTRIBUTE_BRAND_NAME) -- .orElse(BRAND_PURPUR_NAME), // Purpur - Fix pufferfish issues // Purpur - Rebrand -+ .orElse(ServerBrandConstants.BRAND_NAME), // Purpur - Fix pufferfish issues // Purpur - Rebrand // Plazma - Fork-friendly Rebranding - SharedConstants.getCurrentVersion().getId(), - SharedConstants.getCurrentVersion().getName(), - getManifestAttribute(manifest, ATTRIBUTE_BUILD_NUMBER) -@@ -63,7 +59,7 @@ public record ServerBuildInfoImpl( - - @Override - public boolean isBrandCompatible(final @NotNull Key brandId) { -- return brandId.equals(this.brandId) || brandId.equals(BRAND_PAPER_ID) || brandId.equals(BRAND_PUFFERFISH_ID); // Purpur - Fix pufferfish issues // Purpur - Rebrand -+ return brandId.equals(this.brandId) || SUPPORTED_BRANDS.contains(brandId); // Purpur - Fix pufferfish issues // Purpur - Rebrand // Plazma - Fork-friendly Rebranding - } - - @Override -diff --git a/src/main/java/io/papermc/paper/configuration/Configurations.java b/src/main/java/io/papermc/paper/configuration/Configurations.java -index 8cf720f08514e8e4f62f4ad196f1277bd761c6b2..cda2ca940e30506807a5d12b84dbf69529051e26 100644 ---- a/src/main/java/io/papermc/paper/configuration/Configurations.java -+++ b/src/main/java/io/papermc/paper/configuration/Configurations.java -@@ -112,7 +112,7 @@ public abstract class Configurations { - loader.save(node); - } catch (ConfigurateException ex) { - if (ex.getCause() instanceof AccessDeniedException) { -- LOGGER.warn("Could not save {}: Paper could not persist the full set of configuration settings in the configuration file. Any setting missing from the configuration file will be set with its default value in memory. Admins should make sure to review the configuration documentation at https://docs.papermc.io/paper/configuration for more details.", filename, ex); -+ LOGGER.warn("Could not save {}: {} could not persist the full set of configuration settings in the configuration file. Any setting missing from the configuration file will be set with its default value in memory. Admins should make sure to review the configuration documentation at {} for more details.", filename, io.papermc.paper.ServerBrandConstants.BRAND_NAME, io.papermc.paper.ServerBrandConstants.CONFIG_REFERENCE, ex); // Plazma - Fork-friendly Rebranding - } else throw ex; - } - } -diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java -index 710477ae27ebc5afdf0012ef0867d05efd293c24..3a5e7546c5cc1fcec880cece3f0d0b04ec23cc18 100644 ---- a/src/main/java/net/minecraft/CrashReport.java -+++ b/src/main/java/net/minecraft/CrashReport.java -@@ -32,13 +32,13 @@ public class CrashReport { - private boolean trackingStackTrace = true; - private StackTraceElement[] uncategorizedStackTrace = new StackTraceElement[0]; - private final SystemReport systemReport = new SystemReport(); -- private List extraInfo = List.of("", "DO NOT REPORT THIS TO PAPER! REPORT TO PURPUR INSTEAD!", ""); // Purpur - Rebrand -+ private List extraInfo = List.of("", "DO NOT REPORT THIS TO PAPER OR PURPUR! REPORT TO " + io.papermc.paper.ServerBrandConstants.BRAND_NAME.toUpperCase() + " INSTEAD!", ""); // Purpur - Rebrand // Plazma - Fork-friendly Rebranding - - public CrashReport(String message, Throwable cause) { - io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper - this.title = message; - this.exception = cause; -- this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit -+ this.systemReport.setDetail(io.papermc.paper.ServerBrandConstants.BRAND_NAME + " CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit // Plazma - Fork-friendly Rebranding - } - - public String getTitle() { -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index a880f4e5cf712654649ad043e58e073e9a87c0fe..c3bce9206aee05b18ddd8b2788f892a972146737 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -110,6 +110,11 @@ public class Main { - */ // CraftBukkit end - - try { -+ // Plazma start - Fork-friendly Rebranding -+ //noinspection ConstantValue -+ if (io.papermc.paper.ServerBrandConstants.ASCII_LOGO != null) -+ System.out.println(io.papermc.paper.ServerBrandConstants.ASCII_LOGO); -+ // Plazma end - Fork-friendly Rebranding - - Path path = (Path) optionset.valueOf("pidFile"); // CraftBukkit - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index fa5f7bc53f3dfa5581f7c747c732ebc7737a7820..17afd1c5b9984b19b4d31711bfd7f8cc120d89fd 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1281,7 +1281,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && this.server.getQueryPlugins()) { -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -index 05e16103af3fd276f0196ddf1a2e5b729b025c34..8f7e922ceca286b1a590181c301fbe9bff55c024 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -@@ -56,10 +56,10 @@ public class DedicatedServerProperties extends Settings -Date: Wed, 25 Dec 2024 18:16:50 +0900 -Subject: [PATCH] Enable CI version tracking - - -diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -index 1ad562a95809cf7d503f5446f8645ba8c2680914..21a3761f075ace896c981936b2810fccb0b5d610 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -@@ -90,19 +90,18 @@ public class PaperVersionFetcher implements VersionFetcher { - }; - } - -- /* // Plazma - TODO: CI Checking - private static int fetchDistanceFromSiteApi(final ServerBuildInfo build, final int jenkinsBuild) { - try { - try (final BufferedReader reader = Resources.asCharSource( -- URI.create("https://api.purpurmc.org/v2/purpur/" + build.minecraftVersionId()).toURL(), // Purpur - Rebrand -+ URI.create("https://ci.codemc.io/job/PlazmaMC/job/Plazma/job/" + build.gitBranch().orElseThrow().replace("/", "%2F") + "/api/json").toURL(), // Purpur - Rebrand // Plazma - Rebrand - Charsets.UTF_8 - ).openBufferedStream()) { - final JsonObject json = new Gson().fromJson(reader, JsonObject.class); - //final JsonArray builds = json.getAsJsonArray("builds"); // Purpur - Rebrand -- final int latest = json.getAsJsonObject("builds").getAsJsonPrimitive("latest").getAsInt(); // Purpur - Rebrand -+ final int latest = json.getAsJsonObject("lastSuccessfulBuild").getAsJsonPrimitive("number").getAsInt(); // Purpur - Rebrand // Plazma - Rebrand - return latest - jenkinsBuild; - } catch (final JsonSyntaxException ex) { -- LOGGER.error("Error parsing json from Purpur's downloads API", ex); // Purpur - Rebrand -+ LOGGER.error("Error parsing json from CI", ex); // Purpur - Rebrand // Plazma - Rebrand - return DISTANCE_ERROR; - } - } catch (final IOException e) { -@@ -110,7 +109,6 @@ public class PaperVersionFetcher implements VersionFetcher { - return DISTANCE_ERROR; - } - } -- */ // Plazma - TODO: CI Checking - - // Contributed by Techcable in GH-65 - private static int fetchDistanceFromGitHub(final String repo, final String branch, final String hash) { diff --git a/patches/server/0008-Plazma-Configurations.patch b/patches/server/0008-Plazma-Configurations.patch index cc6d738..842cf61 100644 --- a/patches/server/0008-Plazma-Configurations.patch +++ b/patches/server/0008-Plazma-Configurations.patch @@ -4,6 +4,19 @@ Date: Fri, 3 Nov 2023 00:11:50 +0900 Subject: [PATCH] Plazma Configurations +diff --git a/src/main/java/io/papermc/paper/configuration/Configurations.java b/src/main/java/io/papermc/paper/configuration/Configurations.java +index 8cf720f08514e8e4f62f4ad196f1277bd761c6b2..cda2ca940e30506807a5d12b84dbf69529051e26 100644 +--- a/src/main/java/io/papermc/paper/configuration/Configurations.java ++++ b/src/main/java/io/papermc/paper/configuration/Configurations.java +@@ -112,7 +112,7 @@ public abstract class Configurations { + loader.save(node); + } catch (ConfigurateException ex) { + if (ex.getCause() instanceof AccessDeniedException) { +- LOGGER.warn("Could not save {}: Paper could not persist the full set of configuration settings in the configuration file. Any setting missing from the configuration file will be set with its default value in memory. Admins should make sure to review the configuration documentation at https://docs.papermc.io/paper/configuration for more details.", filename, ex); ++ LOGGER.warn("Could not save {}: {} could not persist the full set of configuration settings in the configuration file. Any setting missing from the configuration file will be set with its default value in memory. Admins should make sure to review the configuration documentation at {} for more details.", filename, io.papermc.paper.ServerBrandConstants.BRAND_NAME, io.papermc.paper.ServerBrandConstants.CONFIG_REFERENCE, ex); // Plazma - Fork-friendly Rebranding + } else throw ex; + } + } diff --git a/src/main/java/io/papermc/paper/configuration/Configurations.java b/src/main/java/io/papermc/paper/configuration/Configurations.java index cda2ca940e30506807a5d12b84dbf69529051e26..150556ba594eae2214e4d1f0243ee97a7beb1e99 100644 --- a/src/main/java/io/papermc/paper/configuration/Configurations.java diff --git a/patches/server/0004-Build-System-Changes.patch b/patches/unapplied/server/0004-Build-System-Changes.patch similarity index 100% rename from patches/server/0004-Build-System-Changes.patch rename to patches/unapplied/server/0004-Build-System-Changes.patch diff --git a/patches/work/.gitkeep b/patches/work/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/plazma-server/minecraft-patches/sources/net/minecraft/CrashReport.java.patch b/plazma-server/minecraft-patches/sources/net/minecraft/CrashReport.java.patch new file mode 100644 index 0000000..d7c0e9a --- /dev/null +++ b/plazma-server/minecraft-patches/sources/net/minecraft/CrashReport.java.patch @@ -0,0 +1,95 @@ +--- a/net/minecraft/CrashReport.java ++++ b/net/minecraft/CrashReport.java +@@ -22,15 +_,16 @@ + public class CrashReport { + private static final Logger LOGGER = LogUtils.getLogger(); + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ROOT); ++ private static final List extraInfo = List.of("", "DO NOT REPORT THIS TO PAPER OR PURPUR! REPORT TO PLAZMA INSTEAD!", ""); // Purpur - Rebrand // Plazma - Use modern method // Plazma - Rebrand + private final String title; + private final Throwable exception; + private final List details = Lists.newArrayList(); + @Nullable + private Path saveFile; + private boolean trackingStackTrace = true; ++ @Nullable // Plazma - Fix IDE warning + private StackTraceElement[] uncategorizedStackTrace = new StackTraceElement[0]; + private final SystemReport systemReport = new SystemReport(); +- private List extraInfo = List.of("", "DO NOT REPORT THIS TO PAPER! REPORT TO PURPUR INSTEAD!", ""); // Purpur - Rebrand + + public CrashReport(String title, Throwable exception) { + io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); // Paper +@@ -54,8 +_,8 @@ + } + + public void getDetails(StringBuilder builder) { +- if ((this.uncategorizedStackTrace == null || this.uncategorizedStackTrace.length <= 0) && !this.details.isEmpty()) { +- this.uncategorizedStackTrace = ArrayUtils.subarray(this.details.get(0).getStacktrace(), 0, 1); ++ if ((this.uncategorizedStackTrace == null || this.uncategorizedStackTrace.length == 0) && !this.details.isEmpty()) { ++ this.uncategorizedStackTrace = ArrayUtils.subarray(this.details.getFirst().getStacktrace(), 0, 1); // Plazma - Use modern method + } + + if (this.uncategorizedStackTrace != null && this.uncategorizedStackTrace.length > 0) { +@@ -84,13 +_,14 @@ + PrintWriter printWriter = null; + Throwable throwable = this.exception; + if (throwable.getMessage() == null) { +- if (throwable instanceof NullPointerException) { +- throwable = new NullPointerException(this.title); +- } else if (throwable instanceof StackOverflowError) { +- throwable = new StackOverflowError(this.title); +- } else if (throwable instanceof OutOfMemoryError) { +- throwable = new OutOfMemoryError(this.title); ++ // Plazma start - Use modern method ++ switch (throwable) { ++ case NullPointerException ignore -> throwable = new NullPointerException(this.title); ++ case StackOverflowError ignore -> throwable = new StackOverflowError(this.title); ++ case OutOfMemoryError ignore -> throwable = new OutOfMemoryError(this.title); ++ default -> {} + } ++ // Plazma end - Use modern method + + throwable.setStackTrace(this.exception.getStackTrace()); + } +@@ -102,8 +_,8 @@ + throwable.printStackTrace(printWriter); + var4 = stringWriter.toString(); + } finally { +- IOUtils.closeQuietly((Writer)stringWriter); +- IOUtils.closeQuietly((Writer)printWriter); ++ IOUtils.closeQuietly(stringWriter); // Plazma - Remove unnecessary type cast ++ IOUtils.closeQuietly(printWriter); // Plazma - Remove unnecessary type cast + } + + return var4; +@@ -120,12 +_,7 @@ + stringBuilder.append("\n\n"); + stringBuilder.append(this.getExceptionMessage()); + stringBuilder.append("\n\nA detailed walkthrough of the error, its code path and all known details is as follows:\n"); +- +- for (int i = 0; i < 87; i++) { +- stringBuilder.append("-"); +- } +- +- stringBuilder.append("\n\n"); ++ stringBuilder.repeat("-", 87).append("\n\n"); // Plazma - Use modern method + this.getDetails(stringBuilder); + return stringBuilder.toString(); + } +@@ -185,7 +_,7 @@ + LOGGER.error("Negative index in crash report handler ({}/{})", stackTrace.length, i); + } + +- if (stackTrace != null && 0 <= i1 && i1 < stackTrace.length) { ++ if (0 <= i1 && i1 < stackTrace.length) { + stackTraceElement = stackTrace[i1]; + if (stackTrace.length + 1 - i < stackTrace.length) { + stackTraceElement1 = stackTrace[stackTrace.length + 1 - i]; +@@ -193,7 +_,7 @@ + } + + this.trackingStackTrace = crashReportCategory.validateStackTrace(stackTraceElement, stackTraceElement1); +- if (stackTrace != null && stackTrace.length >= i && 0 <= i1 && i1 < stackTrace.length) { ++ if (stackTrace.length >= i && 0 <= i1 && i1 < stackTrace.length) { + this.uncategorizedStackTrace = new StackTraceElement[i1]; + System.arraycopy(stackTrace, 0, this.uncategorizedStackTrace, 0, this.uncategorizedStackTrace.length); + } else { diff --git a/plazma-server/minecraft-patches/sources/net/minecraft/CrashReportCategory.java.patch b/plazma-server/minecraft-patches/sources/net/minecraft/CrashReportCategory.java.patch new file mode 100644 index 0000000..33d5102 --- /dev/null +++ b/plazma-server/minecraft-patches/sources/net/minecraft/CrashReportCategory.java.patch @@ -0,0 +1,74 @@ +--- a/net/minecraft/CrashReportCategory.java ++++ b/net/minecraft/CrashReportCategory.java +@@ -133,7 +_,7 @@ + + public int fillInStackTrace(int size) { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); +- if (stackTrace.length <= 0) { ++ if (stackTrace.length == 0) { // Plazma - Fix IDE warning + return 0; + } else { + this.stackTrace = new StackTraceElement[stackTrace.length - 3 - size]; +@@ -143,28 +_,32 @@ + } + } + +- public boolean validateStackTrace(StackTraceElement s1, StackTraceElement s2) { +- if (this.stackTrace.length != 0 && s1 != null) { +- StackTraceElement stackTraceElement = this.stackTrace[0]; +- if (stackTraceElement.isNativeMethod() == s1.isNativeMethod() +- && stackTraceElement.getClassName().equals(s1.getClassName()) +- && stackTraceElement.getFileName().equals(s1.getFileName()) +- && stackTraceElement.getMethodName().equals(s1.getMethodName())) { +- if (s2 != null != this.stackTrace.length > 1) { +- return false; +- } else if (s2 != null && !this.stackTrace[1].equals(s2)) { +- return false; +- } else { +- this.stackTrace[0] = s1; +- return true; +- } +- } else { +- return false; +- } +- } else { +- return false; +- } ++ // Plazma start - Improve code style/safety ++ public boolean validateStackTrace(@Nullable StackTraceElement s1, @Nullable StackTraceElement s2) { ++ if (this.stackTrace.length == 0 || s1 == null) { ++ return false; ++ } ++ ++ StackTraceElement element = this.stackTrace[0]; ++ if (element.isNativeMethod() != s1.isNativeMethod() ++ || !element.getClassName().equals(s1.getClassName()) ++ || (element.getFileName() != null && !element.getFileName().equals(s1.getFileName())) ++ || !element.getMethodName().equals(s1.getMethodName())) { ++ return false; ++ } ++ ++ if (s2 == null == this.stackTrace.length > 1) { ++ return false; ++ } ++ ++ if (s2 != null && !this.stackTrace[1].equals(s2)) { ++ return false; ++ } ++ ++ this.stackTrace[0] = s1; ++ return true; + } ++ // Plazma end - Improve code style/safety + + public void trimStacktrace(int amount) { + StackTraceElement[] stackTraceElements = new StackTraceElement[this.stackTrace.length - amount]; +@@ -183,7 +_,7 @@ + builder.append(entry.getValue()); + } + +- if (this.stackTrace != null && this.stackTrace.length > 0) { ++ if (this.stackTrace.length > 0) { // Plazma - Remove unnecessary null check + builder.append("\nStacktrace:"); + + for (StackTraceElement stackTraceElement : this.stackTrace) { diff --git a/plazma-server/minecraft-patches/sources/net/minecraft/server/Main.java.patch b/plazma-server/minecraft-patches/sources/net/minecraft/server/Main.java.patch new file mode 100644 index 0000000..91d0f5b --- /dev/null +++ b/plazma-server/minecraft-patches/sources/net/minecraft/server/Main.java.patch @@ -0,0 +1,146 @@ +--- a/net/minecraft/server/Main.java ++++ b/net/minecraft/server/Main.java +@@ -63,40 +_,24 @@ + public class Main { + private static final Logger LOGGER = LogUtils.getLogger(); + +- @SuppressForbidden( +- reason = "System.out needed before bootstrap" +- ) ++ @SuppressWarnings("ConfusingMainMethod") // Plazma - replaced with org.bukkit.craftbukkit.Main#main ++ @SuppressForbidden(reason = "System.out needed before bootstrap") + @DontObfuscate + public static void main(final OptionSet optionSet) { // CraftBukkit - replaces main(String[] args) + io.papermc.paper.util.LogManagerShutdownThread.hook(); // Paper - Improved watchdog support + SharedConstants.tryDetectVersion(); +- /* CraftBukkit start - Replace everything +- OptionParser optionParser = new OptionParser(); +- OptionSpec optionSpec = optionParser.accepts("nogui"); +- OptionSpec optionSpec1 = optionParser.accepts("initSettings", "Initializes 'server.properties' and 'eula.txt', then quits"); +- OptionSpec optionSpec2 = optionParser.accepts("demo"); +- OptionSpec optionSpec3 = optionParser.accepts("bonusChest"); +- OptionSpec optionSpec4 = optionParser.accepts("forceUpgrade"); +- OptionSpec optionSpec5 = optionParser.accepts("eraseCache"); +- OptionSpec optionSpec6 = optionParser.accepts("recreateRegionFiles"); +- OptionSpec optionSpec7 = optionParser.accepts("safeMode", "Loads level with vanilla datapack only"); +- OptionSpec optionSpec8 = optionParser.accepts("help").forHelp(); +- OptionSpec optionSpec9 = optionParser.accepts("universe").withRequiredArg().defaultsTo("."); +- OptionSpec optionSpec10 = optionParser.accepts("world").withRequiredArg(); +- OptionSpec optionSpec11 = optionParser.accepts("port").withRequiredArg().ofType(Integer.class).defaultsTo(-1); +- OptionSpec optionSpec12 = optionParser.accepts("serverId").withRequiredArg(); +- OptionSpec optionSpec13 = optionParser.accepts("jfrProfile"); +- OptionSpec optionSpec14 = optionParser.accepts("pidFile").withRequiredArg().withValuesConvertedBy(new PathConverter()); +- OptionSpec optionSpec15 = optionParser.nonOptions(); + + try { +- OptionSet optionSet = optionParser.parse(args); +- if (optionSet.has(optionSpec8)) { +- optionParser.printHelpOn(System.err); +- return; +- } +- */ // CraftBukkit end +- try { ++ // Plazma start - Branding ++ System.out.println(""" ++ \\033[38;2;215;23;133m█\\033[38;2;212;24;134m█\\033[38;2;209;26;134m█\\033[38;2;205;27;135m█\\033[38;2;202;28;136m█\\033[38;2;199;30;136m█\\033[38;2;196;31;137m╗\\033[38;2;193;33;138m \\033[38;2;190;34;138m█\\033[38;2;186;35;139m█\\033[38;2;183;37;140m╗\\033[38;2;180;38;140m \\033[38;2;177;39;141m \\033[38;2;174;41;142m \\033[38;2;170;42;142m \\033[38;2;167;43;143m \\033[38;2;164;45;144m \\033[38;2;161;46;144m█\\033[38;2;158;47;145m█\\033[38;2;155;49;146m█\\033[38;2;151;50;146m█\\033[38;2;148;52;147m█\\033[38;2;145;53;148m╗\\033[38;2;142;54;148m \\033[38;2;139;56;149m█\\033[38;2;135;57;149m█\\033[38;2;132;58;150m█\\033[38;2;129;60;151m█\\033[38;2;126;61;151m█\\033[38;2;123;62;152m█\\033[38;2;120;64;153m█\\033[38;2;116;65;153m╗ \\033[38;2;113;67;154m█\\033[38;2;110;68;155m█\\033[38;2;107;69;155m█\\033[38;2;104;71;156m╗\\033[38;2;101;72;157m \\033[38;2;97;73;157m \\033[38;2;94;75;158m \\033[38;2;91;76;159m█\\033[38;2;88;77;159m█\\033[38;2;85;79;160m█\\033[38;2;81;80;161m╗ \\033[38;2;78;81;161m \\033[38;2;75;83;162m█\\033[38;2;72;84;163m█\\033[38;2;69;86;163m█\\033[38;2;66;87;164m█\\033[38;2;62;88;165m█\\033[38;2;59;90;165m╗\\033[38;2;56;91;166m\\s ++ \\033[38;2;215;23;133m█\\033[38;2;212;24;134m█\\033[38;2;209;26;134m╔\\033[38;2;205;27;135m═\\033[38;2;202;28;136m═\\033[38;2;199;30;136m█\\033[38;2;196;31;137m█\\033[38;2;193;33;138m╗ \\033[38;2;190;34;138m█\\033[38;2;186;35;139m█\\033[38;2;183;37;140m║\\033[38;2;180;38;140m \\033[38;2;177;39;141m \\033[38;2;174;41;142m \\033[38;2;170;42;142m \\033[38;2;167;43;143m \\033[38;2;164;45;144m█\\033[38;2;161;46;144m█\\033[38;2;158;47;145m╔\\033[38;2;155;49;146m═\\033[38;2;151;50;146m═\\033[38;2;148;52;147m█\\033[38;2;145;53;148m█\\033[38;2;142;54;148m╗ \\033[38;2;139;56;149m╚\\033[38;2;135;57;149m═\\033[38;2;132;58;150m═\\033[38;2;129;60;151m█\\033[38;2;126;61;151m█\\033[38;2;123;62;152m█\\033[38;2;120;64;153m╔\\033[38;2;116;65;153m╝ \\033[38;2;113;67;154m█\\033[38;2;110;68;155m█\\033[38;2;107;69;155m█\\033[38;2;104;71;156m█\\033[38;2;101;72;157m╗\\033[38;2;97;73;157m \\033[38;2;94;75;158m█\\033[38;2;91;76;159m█\\033[38;2;88;77;159m█\\033[38;2;85;79;160m█\\033[38;2;81;80;161m║ \\033[38;2;78;81;161m█\\033[38;2;75;83;162m█\\033[38;2;72;84;163m╔\\033[38;2;69;86;163m═\\033[38;2;66;87;164m═\\033[38;2;62;88;165m█\\033[38;2;59;90;165m█\\033[38;2;56;91;166m╗ ++ \\033[38;2;215;23;133m█\\033[38;2;212;24;134m█\\033[38;2;209;26;134m█\\033[38;2;205;27;135m█\\033[38;2;202;28;136m█\\033[38;2;199;30;136m█\\033[38;2;196;31;137m╔\\033[38;2;193;33;138m╝ \\033[38;2;190;34;138m█\\033[38;2;186;35;139m█\\033[38;2;183;37;140m║\\033[38;2;180;38;140m \\033[38;2;177;39;141m \\033[38;2;174;41;142m \\033[38;2;170;42;142m \\033[38;2;167;43;143m \\033[38;2;164;45;144m█\\033[38;2;161;46;144m█\\033[38;2;158;47;145m█\\033[38;2;155;49;146m█\\033[38;2;151;50;146m█\\033[38;2;148;52;147m█\\033[38;2;145;53;148m█\\033[38;2;142;54;148m║ \\033[38;2;139;56;149m \\033[38;2;135;57;149m \\033[38;2;132;58;150m█\\033[38;2;129;60;151m█\\033[38;2;126;61;151m█\\033[38;2;123;62;152m╔\\033[38;2;120;64;153m╝\\033[38;2;116;65;153m \\033[38;2;113;67;154m█\\033[38;2;110;68;155m█\\033[38;2;107;69;155m╔\\033[38;2;104;71;156m█\\033[38;2;101;72;157m█\\033[38;2;97;73;157m█\\033[38;2;94;75;158m█\\033[38;2;91;76;159m╔\\033[38;2;88;77;159m█\\033[38;2;85;79;160m█\\033[38;2;81;80;161m║ \\033[38;2;78;81;161m█\\033[38;2;75;83;162m█\\033[38;2;72;84;163m█\\033[38;2;69;86;163m█\\033[38;2;66;87;164m█\\033[38;2;62;88;165m█\\033[38;2;59;90;165m█\\033[38;2;56;91;166m║ ++ \\033[38;2;215;23;133m█\\033[38;2;212;24;134m█\\033[38;2;209;26;134m╔\\033[38;2;205;27;135m═\\033[38;2;202;28;136m═\\033[38;2;199;30;136m═\\033[38;2;196;31;137m╝\\033[38;2;193;33;138m \\033[38;2;190;34;138m█\\033[38;2;186;35;139m█\\033[38;2;183;37;140m║\\033[38;2;180;38;140m \\033[38;2;177;39;141m \\033[38;2;174;41;142m \\033[38;2;170;42;142m \\033[38;2;167;43;143m \\033[38;2;164;45;144m█\\033[38;2;161;46;144m█\\033[38;2;158;47;145m╔\\033[38;2;155;49;146m═\\033[38;2;151;50;146m═\\033[38;2;148;52;147m█\\033[38;2;145;53;148m█\\033[38;2;142;54;148m║ \\033[38;2;139;56;149m \\033[38;2;135;57;149m█\\033[38;2;132;58;150m█\\033[38;2;129;60;151m█\\033[38;2;126;61;151m╔\\033[38;2;123;62;152m╝\\033[38;2;120;64;153m \\033[38;2;116;65;153m \\033[38;2;113;67;154m█\\033[38;2;110;68;155m█\\033[38;2;107;69;155m║\\033[38;2;104;71;156m╚\\033[38;2;101;72;157m█\\033[38;2;97;73;157m█\\033[38;2;94;75;158m╔\\033[38;2;91;76;159m╝\\033[38;2;88;77;159m█\\033[38;2;85;79;160m█\\033[38;2;81;80;161m║ \\033[38;2;78;81;161m█\\033[38;2;75;83;162m█\\033[38;2;72;84;163m╔\\033[38;2;69;86;163m═\\033[38;2;66;87;164m═\\033[38;2;62;88;165m█\\033[38;2;59;90;165m█\\033[38;2;56;91;166m║ ++ \\033[38;2;215;23;133m█\\033[38;2;212;24;134m█\\033[38;2;209;26;134m║\\033[38;2;205;27;135m \\033[38;2;202;28;136m \\033[38;2;199;30;136m \\033[38;2;196;31;137m \\033[38;2;193;33;138m \\033[38;2;190;34;138m█\\033[38;2;186;35;139m█\\033[38;2;183;37;140m█\\033[38;2;180;38;140m█\\033[38;2;177;39;141m█\\033[38;2;174;41;142m█\\033[38;2;170;42;142m█\\033[38;2;167;43;143m╗ \\033[38;2;164;45;144m█\\033[38;2;161;46;144m█\\033[38;2;158;47;145m║\\033[38;2;155;49;146m \\033[38;2;151;50;146m \\033[38;2;148;52;147m█\\033[38;2;145;53;148m█\\033[38;2;142;54;148m║ \\033[38;2;139;56;149m█\\033[38;2;135;57;149m█\\033[38;2;132;58;150m█\\033[38;2;129;60;151m█\\033[38;2;126;61;151m█\\033[38;2;123;62;152m█\\033[38;2;120;64;153m█\\033[38;2;116;65;153m╗ \\033[38;2;113;67;154m█\\033[38;2;110;68;155m█\\033[38;2;107;69;155m║\\033[38;2;104;71;156m \\033[38;2;101;72;157m╚\\033[38;2;97;73;157m═\\033[38;2;94;75;158m╝\\033[38;2;91;76;159m \\033[38;2;88;77;159m█\\033[38;2;85;79;160m█\\033[38;2;81;80;161m║ \\033[38;2;78;81;161m█\\033[38;2;75;83;162m█\\033[38;2;72;84;163m║\\033[38;2;69;86;163m \\033[38;2;66;87;164m \\033[38;2;62;88;165m█\\033[38;2;59;90;165m█\\033[38;2;56;91;166m║ ++ \\033[38;2;215;23;133m╚\\033[38;2;212;24;134m═\\033[38;2;209;26;134m╝\\033[38;2;205;27;135m \\033[38;2;202;28;136m \\033[38;2;199;30;136m \\033[38;2;196;31;137m \\033[38;2;193;33;138m \\033[38;2;190;34;138m╚\\033[38;2;186;35;139m═\\033[38;2;183;37;140m═\\033[38;2;180;38;140m═\\033[38;2;177;39;141m═\\033[38;2;174;41;142m═\\033[38;2;170;42;142m═\\033[38;2;167;43;143m╝ \\033[38;2;164;45;144m╚\\033[38;2;161;46;144m═\\033[38;2;158;47;145m╝\\033[38;2;155;49;146m \\033[38;2;151;50;146m \\033[38;2;148;52;147m╚\\033[38;2;145;53;148m═\\033[38;2;142;54;148m╝ \\033[38;2;139;56;149m╚\\033[38;2;135;57;149m═\\033[38;2;132;58;150m═\\033[38;2;129;60;151m═\\033[38;2;126;61;151m═\\033[38;2;123;62;152m═\\033[38;2;120;64;153m═\\033[38;2;116;65;153m╝ \\033[38;2;113;67;154m╚\\033[38;2;110;68;155m═\\033[38;2;107;69;155m╝\\033[38;2;104;71;156m \\033[38;2;101;72;157m \\033[38;2;97;73;157m \\033[38;2;94;75;158m \\033[38;2;91;76;159m \\033[38;2;88;77;159m╚\\033[38;2;85;79;160m═\\033[38;2;81;80;161m╝ \\033[38;2;78;81;161m╚\\033[38;2;75;83;162m═\\033[38;2;72;84;163m╝\\033[38;2;69;86;163m \\033[38;2;66;87;164m \\033[38;2;62;88;165m╚\\033[38;2;59;90;165m═\\033[38;2;56;91;166m╝\\033[0m ++ """); ++ // Plazma end - Branding + + Path path = (Path) optionSet.valueOf("pidFile"); // CraftBukkit + if (path != null) { +@@ -243,12 +_,12 @@ + } + File mcMeta = new File(bukkitDataPackFolder, "pack.mcmeta"); + try { +- com.google.common.io.Files.write("{\n" +- + " \"pack\": {\n" +- + " \"description\": \"Data pack for resources provided by Bukkit plugins\",\n" +- + " \"pack_format\": " + SharedConstants.getCurrentVersion().getPackVersion(net.minecraft.server.packs.PackType.SERVER_DATA) + "\n" +- + " }\n" +- + "}\n", mcMeta, com.google.common.base.Charsets.UTF_8); ++ com.google.common.io.Files.asCharSink(mcMeta, com.google.common.base.Charsets.UTF_8).write("{\n" ++ + " \"pack\": {\n" ++ + " \"description\": \"Data pack for resources provided by Bukkit plugins\",\n" ++ + " \"pack_format\": " + SharedConstants.getCurrentVersion().getPackVersion(net.minecraft.server.packs.PackType.SERVER_DATA) + "\n" ++ + " }\n" ++ + "}\n"); + } catch (java.io.IOException ex) { + throw new RuntimeException("Could not initialize Bukkit datapack", ex); + } +@@ -309,24 +_,11 @@ + ) + ) + .get(); +- } catch (Exception var39) { +- LOGGER.warn( +- "Failed to load datapacks, can't proceed with server load. You can either fix your datapacks or reset to vanilla with --safeMode", +- (Throwable)var39 +- ); ++ } catch (final Exception e) { // Plazma ++ LOGGER.warn("Failed to load datapacks, can't proceed with server load. You can either fix your datapacks or reset to vanilla with --safeMode", e); // Plazma + return; + } + +- /* +- RegistryAccess.Frozen frozen = worldStem.registries().compositeAccess(); +- boolean hasOptionSpec1 = optionSet.has(optionSpec6); +- if (optionSet.has(optionSpec4) || hasOptionSpec1) { +- forceUpgrade(levelStorageAccess, DataFixers.getDataFixer(), optionSet.has(optionSpec5), () -> true, frozen, hasOptionSpec1); +- } +- +- WorldData worldData = worldStem.worldData(); +- levelStorageAccess.saveDataTag(frozen, worldData); +- */ + Class.forName(net.minecraft.world.entity.npc.VillagerTrades.class.getName()); // Paper - load this sync so it won't fail later async + final DedicatedServer dedicatedServer = MinecraftServer.spin( + thread1 -> { +@@ -343,18 +_,12 @@ + services, + LoggerChunkProgressListener::createFromGameruleRadius + ); +- /* +- dedicatedServer1.setPort(optionSet.valueOf(optionSpec11)); +- */ + // Paper start + if (optionSet.has("serverId")) { + dedicatedServer1.setId((String) optionSet.valueOf("serverId")); + } + dedicatedServer1.setDemo(optionSet.has("demo")); + // Paper end +- /* +- dedicatedServer1.setId(optionSet.valueOf(optionSpec12)); +- */ + boolean flag = !optionSet.has("nogui") && !optionSet.nonOptionArguments().contains("nogui"); + if (flag && !GraphicsEnvironment.isHeadless()) { + dedicatedServer1.showGui(); +@@ -370,16 +_,6 @@ + return dedicatedServer1; + } + ); +- /* CraftBukkit start +- Thread thread = new Thread("Server Shutdown Thread") { +- @Override +- public void run() { +- dedicatedServer.halt(true); +- } +- }; +- thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)); +- Runtime.getRuntime().addShutdownHook(thread); +- */ // CraftBukkit end + } catch (Exception var42) { + LOGGER.error(LogUtils.FATAL_MARKER, "Failed to start the minecraft server", (Throwable)var42); + } +@@ -443,7 +_,7 @@ + } else { + try { + Thread.sleep(1000L); +- } catch (InterruptedException var12) { ++ } catch (InterruptedException ignore) { // Plazma - Fix IDE warning + } + } + } diff --git a/plazma-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/plazma-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch new file mode 100644 index 0000000..a03c05e --- /dev/null +++ b/plazma-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch @@ -0,0 +1,177 @@ +--- a/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/net/minecraft/server/dedicated/DedicatedServer.java +@@ -105,47 +_,12 @@ + public void run() { + // CraftBukkit start + if (!org.bukkit.craftbukkit.Main.useConsole) return; ++ // Plazma start - Improve code quality + // Paper start - Use TerminalConsoleAppender +- if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - GUI Improvements - has no GUI or has console (did not double-click) ++ if (DedicatedServer.this.gui != null && System.console() == null) return; + new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start(); +- /* +- jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader; +- // MC-33041, SPIGOT-5538: if System.in is not valid due to javaw, then return +- try { +- System.in.available(); +- } catch (IOException ex) { +- return; +- } +- // CraftBukkit end +- String string1; +- try { +- // CraftBukkit start - JLine disabling compatibility +- while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning()) { +- if (org.bukkit.craftbukkit.Main.useJline) { +- string1 = bufferedreader.readLine(">", null); +- } else { +- string1 = bufferedreader.readLine(); +- } +- +- // SPIGOT-5220: Throttle if EOF (ctrl^d) or stdin is /dev/null +- if (string1 == null) { +- try { +- Thread.sleep(50L); +- } catch (InterruptedException ex) { +- Thread.currentThread().interrupt(); +- } +- continue; +- } +- if (string1.trim().length() > 0) { // Trim to filter lines which are just spaces +- DedicatedServer.this.issueCommand(s, DedicatedServer.this.getServerCommandListener()); +- } +- // CraftBukkit end +- } +- } catch (IOException var4) { +- DedicatedServer.LOGGER.error("Exception handling console input", (Throwable)var4); +- } +- */ +- // Paper end ++ // Paper end - Use TerminalConsoleAppender ++ // Plazma end - Improve code quality + } + }; + // CraftBukkit start - TODO: handle command-line logging arguments +@@ -156,21 +_,7 @@ + } + global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler()); + +- // Paper start - Not needed with TerminalConsoleAppender +- final org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getRootLogger(); +- /* +- final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()); +- for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) { +- if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) { +- logger.removeAppender(appender); +- } +- } +- +- TerminalConsoleWriterThread writerThread = new TerminalConsoleWriterThread(System.out, this.reader); +- this.reader.setCompletionHandler(new TerminalCompletionHandler(writerThread, this.reader.getCompletionHandler())); +- writerThread.start(); +- */ +- // Paper end - Not needed with TerminalConsoleAppender ++ final org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getRootLogger(); // Paper - Not needed with TCA // Plazma - Remove commented codes + + System.setOut(org.apache.logging.log4j.io.IoBuilder.forLogger(logger).setLevel(org.apache.logging.log4j.Level.INFO).buildPrintStream()); + System.setErr(org.apache.logging.log4j.io.IoBuilder.forLogger(logger).setLevel(org.apache.logging.log4j.Level.WARN).buildPrintStream()); +@@ -208,6 +_,7 @@ + org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings")); + org.spigotmc.SpigotConfig.registerCommands(); + // Spigot end ++ // noinspection ResultOfMethodCallIgnored // Plazma - For preloading + io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc. + // Purpur start - Configurable void damage height and damage + try { +@@ -234,15 +_,7 @@ + io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command + this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics +- /*// Purpur start - Purpur config files // Purpur - Configurable void damage height and damage +- try { +- org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings")); +- } catch (Exception e) { +- DedicatedServer.LOGGER.error("Unable to load server configuration", e); +- return false; +- } +- org.purpurmc.purpur.PurpurConfig.registerCommands(); +- */// Purpur end - Purpur config files // Purpur - Configurable void damage height and damage ++ // noinspection ResultOfMethodCallIgnored // Plazma - For preloading + com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now + + this.setPvpAllowed(properties.pvp); +@@ -287,8 +_,7 @@ + LOGGER.warn("**** FAILED TO BIND TO PORT!"); + LOGGER.warn("The exception was: {}", var10.toString()); + LOGGER.warn("Perhaps a server is already running on that port?"); +- if (true) throw new IllegalStateException("Failed to bind to port", var10); // Paper - Propagate failed to bind to port error +- return false; ++ throw new IllegalStateException("Failed to bind to port", var10); // Paper - Propagate failed to bind to port error + } + // Purpur start - UPnP Port Forwarding + if (org.purpurmc.purpur.PurpurConfig.useUPnP) { +@@ -380,14 +_,6 @@ + this.rconThread = RconThread.create(this); + } + +- if (false && this.getMaxTickLength() > 0L) { // Spigot - disable +- Thread thread1 = new Thread(new ServerWatchdog(this)); +- thread1.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(LOGGER)); +- thread1.setName("Server Watchdog"); +- thread1.setDaemon(true); +- thread1.start(); +- } +- + if (properties.enableJmxMonitoring) { + MinecraftServerStatistics.registerJmxMonitoring(this); + LOGGER.info("JMX monitoring enabled"); +@@ -459,10 +_,6 @@ + this.rconThread.stopNonBlocking(); // Paper - don't wait for remote connections + } + +- if (this.queryThreadGs4 != null) { +- // this.remoteStatusListener.stop(); // Paper - don't wait for remote connections +- } +- + this.hasFullyShutdown = true; // Paper - Improved watchdog support + System.exit(this.abnormalExit ? 70 : 0); // CraftBukkit // Paper - Improved watchdog support + } +@@ -728,7 +_,7 @@ + private void waitForRetry() { + try { + Thread.sleep(5000L); +- } catch (InterruptedException var2) { ++ } catch (InterruptedException ignore) { // Plazma - Fix IDE warning + } + } + +@@ -748,7 +_,7 @@ + org.bukkit.plugin.Plugin[] plugins = this.server.getPluginManager().getPlugins(); + + result.append(this.server.getName()); +- result.append(" on Bukkit "); ++ result.append(" on Plazma "); // Plazma - Rebrand + result.append(this.server.getBukkitVersion()); + + if (plugins.length > 0 && this.server.getQueryPlugins()) { +@@ -759,9 +_,9 @@ + result.append("; "); + } + +- result.append(plugins[i].getDescription().getName()); ++ result.append(plugins[i].getPluginMeta().getName()); // Plazma - Use modern method + result.append(" "); +- result.append(plugins[i].getDescription().getVersion().replaceAll(";", ",")); ++ result.append(plugins[i].getPluginMeta().getVersion().replaceAll(";", ",")); // Plazma - Use modern method + } + } + +@@ -876,7 +_,7 @@ + + private static ServerLinks createServerLinks(DedicatedServerSettings settings) { + Optional optional = parseBugReportLink(settings.getProperties()); +- return optional.map(uri -> new ServerLinks(List.of(ServerLinks.KnownLinkType.BUG_REPORT.create(uri)))).orElse(ServerLinks.EMPTY); ++ return optional.map(uri -> new ServerLinks(List.of(ServerLinks.KnownLinkType.BUG_REPORT.create(uri)))).orElse(ServerLinks.EMPTY); // Plazma - Remove unnecessary type parameter + } + + private static Optional parseBugReportLink(DedicatedServerProperties properties) { diff --git a/plazma-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch b/plazma-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch new file mode 100644 index 0000000..61525aa --- /dev/null +++ b/plazma-server/minecraft-patches/sources/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/server/dedicated/DedicatedServerProperties.java ++++ b/net/minecraft/server/dedicated/DedicatedServerProperties.java +@@ -49,11 +_,11 @@ + public final boolean onlineMode = this.get("online-mode", true); + public final boolean preventProxyConnections = this.get("prevent-proxy-connections", false); + public final String serverIp = this.get("server-ip", ""); +- public final String serverName = this.get("server-name", "Unknown Server"); // Purpur - Bring back server name ++ public final String serverName = this.get("server-name", "A Plazma Server"); // Purpur - Bring back server name // Plazma - no + public final boolean pvp = this.get("pvp", true); + public final boolean allowFlight = this.get("allow-flight", false); +- public final String motd = this.get("motd", "A Minecraft Server"); +- public final String bugReportLink = this.get("bug-report-link", ""); ++ public final String motd = this.get("motd", "A Plazma Server"); // Plazma - Rebrand ++ public final String bugReportLink = this.get("bug-report-link", "https://github.com/PlazmaMC/PlazmaBukkit/issues"); // Plazma - Rebrand + public final boolean forceGameMode = this.get("force-gamemode", false); + public final boolean enforceWhitelist = this.get("enforce-whitelist", false); + public final Difficulty difficulty = this.get( diff --git a/plazma-server/minecraft-patches/sources/net/minecraft/server/gui/MinecraftServerGui.java.patch b/plazma-server/minecraft-patches/sources/net/minecraft/server/gui/MinecraftServerGui.java.patch new file mode 100644 index 0000000..9fc9d1c --- /dev/null +++ b/plazma-server/minecraft-patches/sources/net/minecraft/server/gui/MinecraftServerGui.java.patch @@ -0,0 +1,137 @@ +--- a/net/minecraft/server/gui/MinecraftServerGui.java ++++ b/net/minecraft/server/gui/MinecraftServerGui.java +@@ -3,25 +_,14 @@ + import com.google.common.collect.Lists; + import com.mojang.logging.LogQueues; + import com.mojang.logging.LogUtils; +-import java.awt.BorderLayout; +-import java.awt.Dimension; +-import java.awt.Font; ++import java.awt.*; // Plazma - Improve code quality + import java.awt.event.FocusAdapter; + import java.awt.event.FocusEvent; + import java.awt.event.WindowAdapter; + import java.awt.event.WindowEvent; + import java.util.Collection; + import java.util.concurrent.atomic.AtomicBoolean; +-import javax.swing.JComponent; +-import javax.swing.JFrame; +-import javax.swing.JList; +-import javax.swing.JPanel; +-import javax.swing.JScrollBar; +-import javax.swing.JScrollPane; +-import javax.swing.JTextArea; +-import javax.swing.JTextField; +-import javax.swing.SwingUtilities; +-import javax.swing.UIManager; ++import javax.swing.*; // Plazma - Improve code quality + import javax.swing.border.EtchedBorder; + import javax.swing.border.TitledBorder; + import javax.swing.text.BadLocationException; +@@ -31,11 +_,10 @@ + import org.slf4j.Logger; + + public class MinecraftServerGui extends JComponent { +- private static final Font MONOSPACED = new Font("Monospaced", 0, 12); ++ private static final Font MONOSPACED = new Font("Monospaced", Font.PLAIN, 12); // Plazma - Improve code quality + private static final Logger LOGGER = LogUtils.getLogger(); +- private static final String TITLE = "Minecraft server"; +- private static final String SHUTDOWN_TITLE = "Minecraft server - shutting down!"; + private final DedicatedServer server; ++ @org.jspecify.annotations.Nullable // Plazma - Improve code quality + private Thread logAppenderThread; + private final Collection finalizers = Lists.newArrayList(); + final AtomicBoolean isClosing = new AtomicBoolean(); +@@ -48,12 +_,12 @@ + public static MinecraftServerGui showFrameFor(final DedicatedServer server) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); +- } catch (Exception var3) { ++ } catch (Exception ignore) { // Plazma - Fix IDE warning + } + +- final JFrame jFrame = new JFrame("Purpur Minecraft server"); // Purpur - Improve GUI ++ final JFrame jFrame = new JFrame("Plazma Minecraft server"); // Purpur - Improve GUI // Plazma - Rebrand + final MinecraftServerGui minecraftServerGui = new MinecraftServerGui(server); +- jFrame.setDefaultCloseOperation(2); ++ jFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); // Plazma - Improve code quality + jFrame.add(minecraftServerGui); + jFrame.pack(); + jFrame.setLocationRelativeTo(null); +@@ -110,7 +_,7 @@ + + private JComponent buildPlayerPanel() { + JList jList = new PlayerListComponent(this.server); +- JScrollPane jScrollPane = new JScrollPane(jList, 22, 30); ++ JScrollPane jScrollPane = new JScrollPane(jList, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); // Plazma - Improve code quality + jScrollPane.setBorder(new TitledBorder(new EtchedBorder(), "Players")); + return jScrollPane; + } +@@ -118,7 +_,7 @@ + private JComponent buildChatPanel() { + JPanel jPanel = new JPanel(new BorderLayout()); + org.purpurmc.purpur.gui.JColorTextPane jTextArea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur - GUI Improvements +- JScrollPane jScrollPane = new JScrollPane(jTextArea, 22, 30); ++ JScrollPane jScrollPane = new JScrollPane(jTextArea, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); // Plazma - Improve code quality + jTextArea.setEditable(false); + jTextArea.setFont(MONOSPACED); + JTextField jTextField = new JTextField(); +@@ -163,11 +_,6 @@ + } + }); + // Purpur end - GUI Improvements +- jTextArea.addFocusListener(new FocusAdapter() { +- @Override +- public void focusGained(FocusEvent event) { +- } +- }); + jPanel.add(jScrollPane, "Center"); + jPanel.add(jTextField, "South"); + jPanel.setBorder(new TitledBorder(new EtchedBorder(), "Log and chat")); +@@ -183,7 +_,7 @@ + } + + public void start() { +- this.logAppenderThread.start(); ++ java.util.Objects.requireNonNull(this.logAppenderThread).start(); // Plazma - Improve code quality + } + + public void close() { +@@ -196,22 +_,16 @@ + this.finalizers.forEach(Runnable::run); + } + +- private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper + public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String line) { // Purpur - GUI Improvements + if (!SwingUtilities.isEventDispatchThread()) { + SwingUtilities.invokeLater(() -> this.print(textArea, scrollPane, line)); + } else { +- Document document = textArea.getDocument(); + JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar(); + boolean flag = false; + if (scrollPane.getViewport().getView() == textArea) { + flag = verticalScrollBar.getValue() + verticalScrollBar.getSize().getHeight() + MONOSPACED.getSize() * 4 > verticalScrollBar.getMaximum(); + } + +- /*try { // Purpur - GUI Improvements +- document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(line).replaceAll(""), null); // CraftBukkit +- } catch (BadLocationException var8) { +- }*/ // Purpur - GUI Improvements + textArea.append(line); // Purpur - GUI Improvements + + if (flag) { +@@ -249,11 +_,11 @@ + try { + java.awt.Desktop.getDesktop().browse(java.net.URI.create(onboardingLink)); + } catch (java.io.IOException exception) { +- LOGGER.error("Unable to find a default browser. Please manually visit the website: " + onboardingLink, exception); ++ LOGGER.error("Unable to find a default browser. Please manually visit the website: {}", onboardingLink, exception); // Plazma - Improve code quality + } catch (UnsupportedOperationException exception) { +- LOGGER.error("This platform does not support the BROWSE action. Please manually visit the website: " + onboardingLink, exception); ++ LOGGER.error("This platform does not support the BROWSE action. Please manually visit the website: {}", onboardingLink, exception); // Plazma - Improve code quality + } catch (SecurityException exception) { +- LOGGER.error("This action has been denied by the security manager. Please manually visit the website: " + onboardingLink, exception); ++ LOGGER.error("This action has been denied by the security manager. Please manually visit the website: {}", onboardingLink, exception); // Plazma - Improve code quality + } + } + }); diff --git a/plazma-server/minecraft-patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch b/plazma-server/minecraft-patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch new file mode 100644 index 0000000..c308604 --- /dev/null +++ b/plazma-server/minecraft-patches/sources/net/minecraft/world/damagesource/DamageSource.java.patch @@ -0,0 +1,29 @@ +--- a/net/minecraft/world/damagesource/DamageSource.java ++++ b/net/minecraft/world/damagesource/DamageSource.java +@@ -56,7 +_,7 @@ + } + // Purpur end - Dont run with scissors! + +- // Purpur start - - Stonecutter damage ++ // Purpur start - Stonecutter damages // Plazma - Fix typo ;) + public DamageSource stonecutter() { + this.knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.CONTACT); + this.stonecutter = true; +@@ -75,7 +_,7 @@ + + public DamageSource eventEntityDamager(final Entity entity) { + if (this.directEntity != null) { +- throw new IllegalStateException("Cannot set an event damager when a direct entity is already set (report a bug to Paper)"); ++ throw new IllegalStateException("Cannot set an event damager when a direct entity is already set (report a bug to Plazma)"); // Plazma - Rebrand + } + final DamageSource damageSource = this.copy(); + damageSource.eventEntityDamager = entity; +@@ -104,7 +_,7 @@ + + public DamageSource causingBlockSnapshot(final @Nullable org.bukkit.block.BlockState blockState) { + if (this.eventBlockDamager != null) { +- throw new IllegalStateException("Cannot set a block snapshot when an event block damager is already set (report a bug to Paper)"); ++ throw new IllegalStateException("Cannot set a block snapshot when an event block damager is already set (report a bug to Plazma)"); // Plazma - Rebrand + } + final DamageSource damageSource = this.copy(); + damageSource.fromBlockSnapshot = blockState; diff --git a/plazma-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch b/plazma-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch new file mode 100644 index 0000000..6132077 --- /dev/null +++ b/plazma-server/minecraft-patches/sources/net/minecraft/world/level/Level.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/level/Level.java ++++ b/net/minecraft/world/level/Level.java +@@ -171,6 +_,7 @@ + + public final io.papermc.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files ++ @Nullable // Plazma - Null safety + public static BlockPos lastPhysicsProblem; // Spigot + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; diff --git a/plazma-server/minecraft-patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch b/plazma-server/minecraft-patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch new file mode 100644 index 0000000..edd1f77 --- /dev/null +++ b/plazma-server/minecraft-patches/sources/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java ++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +@@ -16,8 +_,6 @@ + + public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.io.ChunkSystemRegionFileStorage { // Paper - rewrite chunk system + private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper +- public static final String ANVIL_EXTENSION = ".mca"; +- private static final int MAX_CACHE_SIZE = 256; + public final Long2ObjectLinkedOpenHashMap regionCache = new Long2ObjectLinkedOpenHashMap<>(); + private final RegionStorageInfo info; + private final Path folder; +@@ -283,7 +_,7 @@ + + // Paper start + private static void printOversizedLog(String msg, Path file, int x, int z) { +- org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PURPUR - You may ask for help on Discord, but do not file an issue. These error messages can not be removed."); // Purpur - Rebrand ++ org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PLAZMA - You may ask for help on Discord, but do not file an issue. These error messages can not be removed."); // Purpur - Rebrand // Plazma - Rebrand + } + + private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { diff --git a/plazma-server/paper-patches/files/src/main/java/com/destroystokyo/paper/Metrics.java.patch b/plazma-server/paper-patches/files/src/main/java/com/destroystokyo/paper/Metrics.java.patch new file mode 100644 index 0000000..f57b294 --- /dev/null +++ b/plazma-server/paper-patches/files/src/main/java/com/destroystokyo/paper/Metrics.java.patch @@ -0,0 +1,100 @@ +--- a/src/main/java/com/destroystokyo/paper/Metrics.java ++++ b/src/main/java/com/destroystokyo/paper/Metrics.java +@@ -109,7 +_,7 @@ + } + + /** +- * Gets the plugin specific data. ++ * Gets the plugin-specific data. + * + * @return The plugin specific data. + */ +@@ -132,7 +_,7 @@ + } + + /** +- * Gets the server specific data. ++ * Gets the server-specific data. + * + * @return The server specific data. + */ +@@ -186,7 +_,7 @@ + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } +- HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); ++ HttpsURLConnection connection = (HttpsURLConnection) new java.net.URI(URL).toURL().openConnection(); // Plazma - Use modern method + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); +@@ -211,10 +_,10 @@ + } + + /** +- * Gzips the given String. ++ * Compresses the given String using Gzip. + * +- * @param str The string to gzip. +- * @return The gzipped String. ++ * @param str The string to compress. ++ * @return The compressed String. + * @throws IOException If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { +@@ -223,7 +_,7 @@ + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); +- gzip.write(str.getBytes("UTF-8")); ++ gzip.write(str.getBytes(java.nio.charset.StandardCharsets.UTF_8)); // Plazma - Use modern method + gzip.close(); + return outputStream.toByteArray(); + } +@@ -396,7 +_,7 @@ + } + + /** +- * Represents a custom single line chart. ++ * Represents a custom single-line chart. + */ + public static class SingleLineChart extends CustomChart { + +@@ -576,12 +_,14 @@ + config.addDefault("logFailedRequests", false); + + // Inform the server owners about bStats +- config.options().header( +- "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + +- "To honor their work, you should not disable it.\n" + +- "This has nearly no effect on the server performance!\n" + +- "Check out https://bStats.org/ to learn more :)" +- ).copyDefaults(true); ++ // Plazma start - Use modern method ++ config.options().setHeader(List.of( ++ "bStats collects some data for plugin authors like how many servers are using their plugins.", ++ "To honor their work, you should not disable it.", ++ "This has nearly no effect on the server performance!", ++ "Check out https://bStats.org/ to learn more :)" ++ )).copyDefaults(true); ++ // Plazma end - Use modern method + try { + config.save(configFile); + } catch (IOException ignored) { +@@ -592,7 +_,7 @@ + boolean logFailedRequests = config.getBoolean("logFailedRequests", false); + // Only start Metrics, if it's enabled in the config + if (config.getBoolean("enabled", true)) { +- Metrics metrics = new Metrics("Purpur", serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish // Purpur - Purpur config files ++ Metrics metrics = new Metrics("Plazma", serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish // Purpur - Purpur config files // Plazma - Rebrand + + metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { + String minecraftVersion = Bukkit.getVersion(); +@@ -602,7 +_,7 @@ + + metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size())); + metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ? "bungee" : "offline"))); // Purpur - Purpur config files +- metrics.addCustomChart(new Metrics.SimplePie("purpur_version", () -> (org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() != null) ? org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() : "unknown")); // Purpur - Purpur config files ++ metrics.addCustomChart(new Metrics.SimplePie("plazma_version", () -> (org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() != null) ? org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() : "unknown")); // Purpur - Purpur config files // Plazma - Rebrand + + metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { + Map> map = new HashMap<>(); diff --git a/plazma-server/paper-patches/files/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java.patch b/plazma-server/paper-patches/files/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java.patch new file mode 100644 index 0000000..c9166df --- /dev/null +++ b/plazma-server/paper-patches/files/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java.patch @@ -0,0 +1,53 @@ +--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java ++++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +@@ -36,7 +_,7 @@ + private static final int DISTANCE_ERROR = -1; + private static final int DISTANCE_UNKNOWN = -2; + // Purpur start - Rebrand +- private static final String DOWNLOAD_PAGE = "https://purpurmc.org/downloads"; ++ private static final String DOWNLOAD_PAGE = "https://plazmamc.org/downloads"; + private static int distance = DISTANCE_UNKNOWN; public int distance() { return distance; } + // Purpur end - Rebrand + +@@ -52,7 +_,7 @@ + if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) { + updateMessage = text("You are running a development version without access to version information", color(0xFF5300)); + } else { +- updateMessage = getUpdateStatusMessage("PurpurMC/Purpur", build); // Purpur - Rebrand ++ updateMessage = getUpdateStatusMessage("PlazmaMC/Plazma", build); // Purpur - Rebrand // Plazma - Configurable Plazma + } + final @Nullable Component history = this.getHistory(); + +@@ -87,23 +_,23 @@ + } + + private static int fetchDistanceFromSiteApi(final ServerBuildInfo build, final int jenkinsBuild) { +- try { +- try (final BufferedReader reader = Resources.asCharSource( +- URI.create("https://api.purpurmc.org/v2/purpur/" + build.minecraftVersionId()).toURL(), // Purpur - Rebrand +- Charsets.UTF_8 +- ).openBufferedStream()) { ++ // Plazma start - Configurable Plazma ++ final String branch = build.gitBranch().orElse("ver/" + build.minecraftVersionId()); ++ ++ try (final BufferedReader reader = Resources.asCharSource(URI.create(branch).toURL(), java.nio.charset.StandardCharsets.UTF_8).openBufferedStream()) { ++ try { + final JsonObject json = new Gson().fromJson(reader, JsonObject.class); +- //final JsonArray builds = json.getAsJsonArray("builds"); // Purpur - Rebrand +- final int latest = json.getAsJsonObject("builds").getAsJsonPrimitive("latest").getAsInt(); // Purpur - Rebrand ++ final int latest = json.getAsJsonObject("lastSuccessfulBuild").getAsJsonPrimitive("number").getAsInt(); + return latest - jenkinsBuild; +- } catch (final JsonSyntaxException ex) { +- LOGGER.error("Error parsing json from Purpur's downloads API", ex); // Purpur - Rebrand ++ } catch (final JsonSyntaxException e) { ++ LOGGER.error("Error parsing json from Jenkins API", e); + return DISTANCE_ERROR; + } + } catch (final IOException e) { + LOGGER.error("Error while parsing version", e); + return DISTANCE_ERROR; + } ++ // Plazma end - Configurable Plazma + } + + // Contributed by Techcable in GH-65 diff --git a/plazma-server/paper-patches/files/src/main/java/com/destroystokyo/paper/console/PaperConsole.java.patch b/plazma-server/paper-patches/files/src/main/java/com/destroystokyo/paper/console/PaperConsole.java.patch new file mode 100644 index 0000000..21b969a --- /dev/null +++ b/plazma-server/paper-patches/files/src/main/java/com/destroystokyo/paper/console/PaperConsole.java.patch @@ -0,0 +1,11 @@ +--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java ++++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +@@ -20,7 +_,7 @@ + @Override + protected LineReader buildReader(LineReaderBuilder builder) { + builder +- .appName("Purpur") // Purpur - Rebrand ++ .appName("Plazma") // Purpur - Rebrand // Plazma - Configurable Plazma + .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history")) + .completer(new ConsoleCommandCompleter(this.server)) + .option(LineReader.Option.COMPLETE_IN_WORD, true); diff --git a/plazma-server/paper-patches/files/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java.patch b/plazma-server/paper-patches/files/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java.patch new file mode 100644 index 0000000..83f5a20 --- /dev/null +++ b/plazma-server/paper-patches/files/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java.patch @@ -0,0 +1,38 @@ +--- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java ++++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java +@@ -30,8 +_,12 @@ + private static final String ATTRIBUTE_GIT_BRANCH = "Git-Branch"; + private static final String ATTRIBUTE_GIT_COMMIT = "Git-Commit"; + +- private static final String BRAND_PAPER_NAME = "Paper"; +- private static final String BRAND_PURPUR_NAME = "Purpur"; // Purpur - Rebrand ++ // Plazma start - Rebrand ++ private static final java.util.Set COMPATIBLE = new java.util.HashSet<>() {{ ++ add(BRAND_PAPER_ID); ++ add(BRAND_PURPUR_ID); ++ }}; ++ // Plazma end - Rebrand + + private static final String BUILD_DEV = "DEV"; + +@@ -43,9 +_,9 @@ + this( + getManifestAttribute(manifest, ATTRIBUTE_BRAND_ID) + .map(Key::key) +- .orElse(BRAND_PURPUR_ID), // Purpur - Fix pufferfish issues // Purpur - Rebrand ++ .orElse(BRAND_PLAZMA_ID), // Purpur - Fix pufferfish issues // Purpur - Rebrand + getManifestAttribute(manifest, ATTRIBUTE_BRAND_NAME) +- .orElse(BRAND_PURPUR_NAME), // Purpur - Fix pufferfish issues // Purpur - Rebrand ++ .orElse("Plazma"), // Purpur - Fix pufferfish issues // Purpur - Rebrand // Plazma - Rebrand + SharedConstants.getCurrentVersion().getId(), + SharedConstants.getCurrentVersion().getName(), + getManifestAttribute(manifest, ATTRIBUTE_BUILD_NUMBER) +@@ -62,7 +_,7 @@ + + @Override + public boolean isBrandCompatible(final @NotNull Key brandId) { +- return brandId.equals(this.brandId) || brandId.equals(BRAND_PAPER_ID); // Purpur - Fix pufferfish issues // Purpur - Rebrand ++ return brandId.equals(this.brandId) || COMPATIBLE.contains(brandId); // Purpur - Fix pufferfish issues // Purpur - Rebrand // Plazma - Rebrand + } + + @Override diff --git a/plazma-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/Main.java.patch b/plazma-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/Main.java.patch new file mode 100644 index 0000000..6ea9773 --- /dev/null +++ b/plazma-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/Main.java.patch @@ -0,0 +1,92 @@ +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -26,13 +_,6 @@ + // Paper end - Reset loggers after shutdown + + public static void main(String[] args) { +- // Paper start +- final String warnWhenLegacyFormattingDetected = String.join(".", "net", "kyori", "adventure", "text", "warnWhenLegacyFormattingDetected"); +- if (false && System.getProperty(warnWhenLegacyFormattingDetected) == null) { +- System.setProperty(warnWhenLegacyFormattingDetected, String.valueOf(true)); +- } +- // Paper end +- // Todo: Installation script + if (System.getProperty("jdk.nio.maxCachedBufferSize") == null) System.setProperty("jdk.nio.maxCachedBufferSize", "262144"); // Paper - cap per-thread NIO cache size; https://www.evanjones.ca/java-bytebuffer-leak.html + OptionParser parser = new OptionParser() { + { +@@ -143,7 +_,7 @@ + + this.acceptsAll(Main.asList("noconsole"), "Disables the console"); + +- this.acceptsAll(Main.asList("v", "version"), "Show the CraftBukkit Version"); ++ this.acceptsAll(Main.asList("v", "version"), "Show the Plazma Version"); // Plazma - Rebrand + + this.acceptsAll(Main.asList("demo"), "Demo mode"); + +@@ -183,13 +_,6 @@ + .defaultsTo(new File("purpur.yml")) + .describedAs("Yml file"); + // Purpur end - Purpur config files +- // Paper start +- acceptsAll(asList("server-name"), "Name of the server") +- .withRequiredArg() +- .ofType(String.class) +- .defaultsTo("Unknown Server") +- .describedAs("Name"); +- // Paper end + } + }; + +@@ -234,26 +_,6 @@ + + try { + // Paper start - Handled by TerminalConsoleAppender +- /* +- // This trick bypasses Maven Shade's clever rewriting of our getProperty call when using String literals +- String jline_UnsupportedTerminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 'U', 'n', 's', 'u', 'p', 'p', 'o', 'r', 't', 'e', 'd', 'T', 'e', 'r', 'm', 'i', 'n', 'a', 'l'}); +- String jline_terminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 't', 'e', 'r', 'm', 'i', 'n', 'a', 'l'}); +- +- Main.useJline = !(jline_UnsupportedTerminal).equals(System.getProperty(jline_terminal)); +- +- if (options.has("nojline")) { +- System.setProperty("user.language", "en"); +- Main.useJline = false; +- } +- +- if (Main.useJline) { +- AnsiConsole.systemInstall(); +- } else { +- // This ensures the terminal literal will always match the jline implementation +- System.setProperty(jline.TerminalFactory.JLINE_TERMINAL, jline.UnsupportedTerminal.class.getName()); +- } +- */ +- + if (options.has("nojline")) { + System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); + useJline = false; +@@ -266,25 +_,8 @@ + System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper + } + +- if (false && Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { // Purpur - Disable outdated build check +- Date buildDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(Main.class.getPackage().getImplementationVendor()); // Paper +- +- Calendar deadline = Calendar.getInstance(); +- deadline.add(Calendar.DAY_OF_YEAR, -14); +- if (buildDate.before(deadline.getTime())) { +- // Paper start - This is some stupid bullshit +- System.err.println("*** Warning, you've not updated in a while! ***"); +- System.err.println("*** Please download a new build from https://papermc.io/downloads/paper ***"); // Paper +- //System.err.println("*** Server will start in 20 seconds ***"); +- //Thread.sleep(TimeUnit.SECONDS.toMillis(20)); +- // Paper end +- } +- } +- + System.setProperty("library.jansi.version", "Paper"); // Paper - set meaningless jansi version to prevent git builds from crashing on Windows + System.setProperty("jdk.console", "java.base"); // Paper - revert default console provider back to java.base so we can have our own jline +- //System.out.println("Loading libraries, please wait..."); +- //net.minecraft.server.Main.main(options); + io.papermc.paper.PaperBootstrap.boot(options); + } catch (Throwable t) { + t.printStackTrace(); diff --git a/plazma-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java.patch b/plazma-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java.patch new file mode 100644 index 0000000..e53cfc6 --- /dev/null +++ b/plazma-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java.patch @@ -0,0 +1,436 @@ +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +@@ -23,6 +_,7 @@ + import org.bukkit.scheduler.BukkitScheduler; + import org.bukkit.scheduler.BukkitTask; + import org.bukkit.scheduler.BukkitWorker; ++import org.jspecify.annotations.NonNull; // Plazma - Null safety + + /** + * The fundamental concepts for this implementation: +@@ -34,7 +_,7 @@ + * Adding to the tail is atomic and very efficient; utility method is {@link #handle(CraftTask, long)} or {@link #addTask(CraftTask)}. + *

  • Changing the period on a task is delicate. + * Any future task needs to notify waiting threads. +- * Async tasks must be synchronized to make sure that any thread that's finishing will remove itself from {@link #runners}. ++ * Async tasks must be synchronised to make sure that any thread that's finishing will remove itself from {@link #runners}. + * Another utility method is provided for this, {@link #cancelTask(int)}
  • + *
  • {@link #runners} provides a moderately up-to-date view of active tasks. + * If the linked head to tail set is read, all remaining tasks that were active at the time execution started will be located in runners.
  • +@@ -65,56 +_,31 @@ + */ + private final AtomicInteger ids = new AtomicInteger(CraftScheduler.START_ID); + /** +- * Current head of linked-list. This reference is always stale, {@link CraftTask#next} is the live reference. ++ * Current head of a linked-list. This reference is always stale, {@link CraftTask#getNext} is the live reference. + */ + private volatile CraftTask head = new CraftTask(); + /** +- * Tail of a linked-list. AtomicReference only matters when adding to queue +- */ +- private final AtomicReference tail = new AtomicReference(this.head); +- /** +- * Main thread logic only +- */ +- final PriorityQueue pending = new PriorityQueue(10, // Paper +- new Comparator() { +- @Override +- public int compare(final CraftTask o1, final CraftTask o2) { +- int value = Long.compare(o1.getNextRun(), o2.getNextRun()); +- +- // If the tasks should run on the same tick they should be run FIFO +- return value != 0 ? value : Long.compare(o1.getCreatedAt(), o2.getCreatedAt()); +- } +- }); +- /** +- * Main thread logic only +- */ +- private final List temp = new ArrayList(); ++ * Tail of a linked-list. AtomicReference only matters when adding to a queue ++ */ ++ private final AtomicReference tail = new AtomicReference<>(this.head); // Plazma - Remove unnecessary type parameter ++ /** ++ * Main thread logic only ++ */ ++ final PriorityQueue pending = new PriorityQueue<>(10, Comparator.comparingLong(CraftTask::getNextRun).thenComparingLong(CraftTask::getCreatedAt)); // Plazma - Remove unnecessary type parameter ++ /** ++ * Main thread logic only ++ */ ++ private final List temp = new ArrayList<>(); // Plazma - Remove unnecessary type parameter + /** + * These are tasks that are currently active. It's provided for 'viewing' the current state. + */ +- final ConcurrentHashMap runners = new ConcurrentHashMap(); // Paper ++ final ConcurrentHashMap runners = new ConcurrentHashMap<>(); // Paper // Plazma - Remove unnecessary type parameter + /** + * The sync task that is currently running on the main thread. + */ + private volatile CraftTask currentTask = null; + // Paper start - Improved Async Task Scheduler +- volatile int currentTick = -1;/* +- private final Executor executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %d").build()); +- private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) { +- @Override +- StringBuilder debugTo(StringBuilder string) { +- return string; +- } +- }; +- private CraftAsyncDebugger debugTail = this.debugHead; +- +- */ // Paper end +- private static final int RECENT_TICKS; +- +- static { +- RECENT_TICKS = 30; +- } +- ++ volatile int currentTick = -1; // Plazma + + // Paper start + private final CraftScheduler asyncScheduler; +@@ -125,96 +_,98 @@ + + public CraftScheduler(boolean isAsync) { + this.isAsyncScheduler = isAsync; +- if (isAsync) { +- this.asyncScheduler = this; +- } else { +- this.asyncScheduler = new CraftAsyncScheduler(); +- } ++ this.asyncScheduler = isAsync ? this : new CraftAsyncScheduler(); // Plazma - Improve code quality + } + // Paper end + @Override +- public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task) { ++ public int scheduleSyncDelayedTask(final @NonNull Plugin plugin, final @NonNull Runnable task) { // Plazma - Null safety + return this.scheduleSyncDelayedTask(plugin, task, 0L); + } + + @Override +- public BukkitTask runTask(Plugin plugin, Runnable runnable) { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTask(final @NonNull Plugin plugin, final @NonNull Runnable runnable) { // Plazma - Null safety + return this.runTaskLater(plugin, runnable, 0L); + } + + @Override +- public void runTask(Plugin plugin, Consumer task) throws IllegalArgumentException { ++ public void runTask(final @NonNull Plugin plugin, final @NonNull Consumer task) throws IllegalArgumentException { // Plazma - Null safety + this.runTaskLater(plugin, task, 0L); + } + + @Deprecated + @Override +- public int scheduleAsyncDelayedTask(final Plugin plugin, final Runnable task) { ++ public int scheduleAsyncDelayedTask(final @NonNull Plugin plugin, final @NonNull Runnable task) { // Plazma - Null safety + return this.scheduleAsyncDelayedTask(plugin, task, 0L); + } + + @Override +- public BukkitTask runTaskAsynchronously(Plugin plugin, Runnable runnable) { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTaskAsynchronously(final @NonNull Plugin plugin, final @NonNull Runnable runnable) { // Plazma - Null safety + return this.runTaskLaterAsynchronously(plugin, runnable, 0L); + } + + @Override +- public void runTaskAsynchronously(Plugin plugin, Consumer task) throws IllegalArgumentException { ++ public void runTaskAsynchronously(final @NonNull Plugin plugin, final @NonNull Consumer task) throws IllegalArgumentException { // Plazma - Null safety + this.runTaskLaterAsynchronously(plugin, task, 0L); + } + + @Override +- public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task, final long delay) { ++ public int scheduleSyncDelayedTask(final @NonNull Plugin plugin, final @NonNull Runnable task, long delay) { // Plazma - Null safety + return this.scheduleSyncRepeatingTask(plugin, task, delay, CraftTask.NO_REPEATING); + } + + @Override +- public BukkitTask runTaskLater(Plugin plugin, Runnable runnable, long delay) { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTaskLater(final @NonNull Plugin plugin, final @NonNull Runnable runnable, long delay) { // Plazma - Null safety + return this.runTaskTimer(plugin, runnable, delay, CraftTask.NO_REPEATING); + } + + @Override +- public void runTaskLater(Plugin plugin, Consumer task, long delay) throws IllegalArgumentException { ++ public void runTaskLater(final @NonNull Plugin plugin, final @NonNull Consumer task, long delay) throws IllegalArgumentException { // Plazma - Null safety + this.runTaskTimer(plugin, task, delay, CraftTask.NO_REPEATING); + } + + @Deprecated + @Override +- public int scheduleAsyncDelayedTask(final Plugin plugin, final Runnable task, final long delay) { ++ public int scheduleAsyncDelayedTask(final @NonNull Plugin plugin, final @NonNull Runnable task, long delay) { // Plazma - Null safety + return this.scheduleAsyncRepeatingTask(plugin, task, delay, CraftTask.NO_REPEATING); + } + + @Override +- public BukkitTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTaskLaterAsynchronously(final @NonNull Plugin plugin, final @NonNull Runnable runnable, long delay) { // Plazma - Null safety + return this.runTaskTimerAsynchronously(plugin, runnable, delay, CraftTask.NO_REPEATING); + } + + @Override +- public void runTaskLaterAsynchronously(Plugin plugin, Consumer task, long delay) throws IllegalArgumentException { ++ public void runTaskLaterAsynchronously(final @NonNull Plugin plugin, final @NonNull Consumer task, long delay) throws IllegalArgumentException { // Plazma - Null safety + this.runTaskTimerAsynchronously(plugin, task, delay, CraftTask.NO_REPEATING); + } + + @Override +- public void runTaskTimerAsynchronously(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException { ++ public void runTaskTimerAsynchronously(final @NonNull Plugin plugin, final @NonNull Consumer task, long delay, long period) throws IllegalArgumentException { // Plazma - Null safety + this.runTaskTimerAsynchronously(plugin, (Object) task, delay, period); // Paper + } + + @Override +- public int scheduleSyncRepeatingTask(final Plugin plugin, final Runnable runnable, long delay, long period) { ++ public int scheduleSyncRepeatingTask(final @NonNull Plugin plugin, final @NonNull Runnable runnable, long delay, long period) { // Plazma - Null safety + return this.runTaskTimer(plugin, runnable, delay, period).getTaskId(); + } + + @Override +- public BukkitTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTaskTimer(final @NonNull Plugin plugin, final @NonNull Runnable runnable, long delay, long period) { // Plazma - Null safety + return this.runTaskTimer(plugin, (Object) runnable, delay, period); + } + + @Override +- public void runTaskTimer(Plugin plugin, Consumer task, long delay, long period) throws IllegalArgumentException { ++ public void runTaskTimer(final @NonNull Plugin plugin, final @NonNull Consumer task, long delay, long period) throws IllegalArgumentException { // Plazma - Null safety + this.runTaskTimer(plugin, (Object) task, delay, period); + } + +- public BukkitTask runTaskTimer(Plugin plugin, Object runnable, long delay, long period) { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTaskTimer(final @NonNull Plugin plugin, final @NonNull Object runnable, long delay, long period) { // Plazma - Null safety + CraftScheduler.validate(plugin, runnable); + if (delay < 0L) { + delay = 0; +@@ -229,16 +_,18 @@ + + @Deprecated + @Override +- public int scheduleAsyncRepeatingTask(final Plugin plugin, final Runnable runnable, long delay, long period) { ++ public int scheduleAsyncRepeatingTask(final @NonNull Plugin plugin, final @NonNull Runnable runnable, long delay, long period) { // Plazma - Null safety + return this.runTaskTimerAsynchronously(plugin, runnable, delay, period).getTaskId(); + } + + @Override +- public BukkitTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTaskTimerAsynchronously(final @NonNull Plugin plugin, final @NonNull Runnable runnable, long delay, long period) { // Plazma - Null safety + return this.runTaskTimerAsynchronously(plugin, (Object) runnable, delay, period); + } + +- public BukkitTask runTaskTimerAsynchronously(Plugin plugin, Object runnable, long delay, long period) { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTaskTimerAsynchronously(final @NonNull Plugin plugin, final @NonNull Object runnable, long delay, long period) { // Plazma - Null safety + CraftScheduler.validate(plugin, runnable); + if (delay < 0L) { + delay = 0; +@@ -252,9 +_,10 @@ + } + + @Override +- public Future callSyncMethod(final Plugin plugin, final Callable task) { ++ @NonNull // Plazma - Null safety ++ public Future callSyncMethod(final @NonNull Plugin plugin, final @NonNull Callable task) { // Plazma - Null safety + CraftScheduler.validate(plugin, task); +- final CraftFuture future = new CraftFuture(task, plugin, this.nextId()); ++ final CraftFuture future = new CraftFuture<>(task, plugin, this.nextId()); // Plazma - Remove unnecessary type parameter + this.handle(future, 0L); + return future; + } +@@ -309,8 +_,7 @@ + } + + @Override +- public void cancelTasks(final Plugin plugin) { +- Preconditions.checkArgument(plugin != null, "Cannot cancel tasks of null plugin"); ++ public void cancelTasks(final @NonNull Plugin plugin) { // Plazma - Null safety + // Paper start + if (!this.isAsyncScheduler) { + this.asyncScheduler.cancelTasks(plugin); +@@ -395,6 +_,7 @@ + } + + @Override ++ @NonNull // Plazma - Null safety + public List getActiveWorkers() { + // Paper start + if (!isAsyncScheduler) { +@@ -402,7 +_,7 @@ + return this.asyncScheduler.getActiveWorkers(); + } + // Paper end +- final ArrayList workers = new ArrayList(); ++ final ArrayList workers = new ArrayList<>(); // Plazma - Remove unnecessary type parameter + for (final CraftTask taskObj : this.runners.values()) { + // Iterator will be a best-effort (may fail to grab very new values) if called from an async thread + if (taskObj.isSync()) { +@@ -418,8 +_,9 @@ + } + + @Override ++ @NonNull // Plazma - Null safety + public List getPendingTasks() { +- final ArrayList truePending = new ArrayList(); ++ final ArrayList truePending = new ArrayList<>(); // Plazma - Remove unnecessary type parameter + for (CraftTask task = this.head.getNext(); task != null; task = task.getNext()) { + if (task.getTaskId() != -1) { + // -1 is special code +@@ -427,7 +_,7 @@ + } + } + +- final ArrayList pending = new ArrayList(); ++ final ArrayList pending = new ArrayList<>(); // Plazma - Remove unnecessary type parameter + for (CraftTask task : this.runners.values()) { + if (task.getPeriod() >= CraftTask.NO_REPEATING) { + pending.add(task); +@@ -477,7 +_,7 @@ + final String logMessage = String.format( + "Task #%s for %s generated an exception", + task.getTaskId(), +- task.getOwner().getDescription().getFullName()); ++ task.getOwner().getPluginMeta().getDisplayName()); // Plazma - Use modern method + task.getOwner().getLogger().log( + Level.WARNING, + logMessage, +@@ -491,7 +_,7 @@ + this.parsePending(); + } else { + // this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(this.currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper +- task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Purpur"); // Paper // Purpur - Rebrand ++ task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Plazma"); // Paper // Purpur - Rebrand // Plazma - Rebrand + // We don't need to parse pending + // (async tasks must live with race-conditions if they attempt to cancel between these few lines of code) + } +@@ -508,12 +_,13 @@ + //this.debugHead = this.debugHead.getNextHead(this.currentTick); // Paper + } + +- protected void addTask(final CraftTask task) { ++ protected void addTask(final @NonNull CraftTask task) { // Plazma - Null safety + final CraftTask tailTask = this.tail.getAndSet(task); + tailTask.setNext(task); + } + +- protected CraftTask handle(final CraftTask task, final long delay) { // Paper ++ @NonNull // Plazma - Null safety ++ protected CraftTask handle(final @NonNull CraftTask task, final long delay) { // Paper // Plazma - Null safety + // Paper start + if (!this.isAsyncScheduler && !task.isSync()) { + this.asyncScheduler.handle(task, delay); +@@ -525,8 +_,7 @@ + return task; + } + +- private static void validate(final Plugin plugin, final Object task) { +- Preconditions.checkArgument(plugin != null, "Plugin cannot be null"); ++ private static void validate(final @NonNull Plugin plugin, final @NonNull Object task) { // Plazma - Null safety + Preconditions.checkArgument(task instanceof Runnable || task instanceof Consumer || task instanceof Callable, "Task must be Runnable, Consumer, or Callable"); + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); +@@ -569,79 +_,74 @@ + + @Override + public String toString() { +- // Paper start +- return ""; +- /* +- int debugTick = this.currentTick; +- StringBuilder string = new StringBuilder("Recent tasks from ").append(debugTick - CraftScheduler.RECENT_TICKS).append('-').append(debugTick).append('{'); +- this.debugHead.debugTo(string); +- return string.append('}').toString(); +- */ +- // Paper end ++ return ""; // Paper // Plazma + } + + @Deprecated + @Override +- public int scheduleSyncDelayedTask(Plugin plugin, BukkitRunnable task, long delay) { ++ public int scheduleSyncDelayedTask(final @NonNull Plugin plugin, final @NonNull BukkitRunnable task, long delay) { // Plazma - Null safety + throw new UnsupportedOperationException("Use BukkitRunnable#runTaskLater(Plugin, long)"); + } + + @Deprecated + @Override +- public int scheduleSyncDelayedTask(Plugin plugin, BukkitRunnable task) { ++ public int scheduleSyncDelayedTask(final @NonNull Plugin plugin, final @NonNull BukkitRunnable task) { // Plazma - Null safety + throw new UnsupportedOperationException("Use BukkitRunnable#runTask(Plugin)"); + } + + @Deprecated + @Override +- public int scheduleSyncRepeatingTask(Plugin plugin, BukkitRunnable task, long delay, long period) { ++ public int scheduleSyncRepeatingTask(final @NonNull Plugin plugin, final @NonNull BukkitRunnable task, long delay, long period) { // Plazma - Null safety + throw new UnsupportedOperationException("Use BukkitRunnable#runTaskTimer(Plugin, long, long)"); + } + + @Deprecated + @Override +- public BukkitTask runTask(Plugin plugin, BukkitRunnable task) throws IllegalArgumentException { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTask(final @NonNull Plugin plugin, final @NonNull BukkitRunnable task) throws IllegalArgumentException { // Plazma - Null safety + throw new UnsupportedOperationException("Use BukkitRunnable#runTask(Plugin)"); + } + + @Deprecated + @Override +- public BukkitTask runTaskAsynchronously(Plugin plugin, BukkitRunnable task) throws IllegalArgumentException { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTaskAsynchronously(final @NonNull Plugin plugin, final @NonNull BukkitRunnable task) throws IllegalArgumentException { // Plazma - Null safety + throw new UnsupportedOperationException("Use BukkitRunnable#runTaskAsynchronously(Plugin)"); + } + + @Deprecated + @Override +- public BukkitTask runTaskLater(Plugin plugin, BukkitRunnable task, long delay) throws IllegalArgumentException { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTaskLater(final @NonNull Plugin plugin, final @NonNull BukkitRunnable task, long delay) throws IllegalArgumentException { // Plazma - Null safety + throw new UnsupportedOperationException("Use BukkitRunnable#runTaskLater(Plugin, long)"); + } + + @Deprecated + @Override +- public BukkitTask runTaskLaterAsynchronously(Plugin plugin, BukkitRunnable task, long delay) throws IllegalArgumentException { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTaskLaterAsynchronously(final @NonNull Plugin plugin, final @NonNull BukkitRunnable task, long delay) throws IllegalArgumentException { // Plazma - Null safety + throw new UnsupportedOperationException("Use BukkitRunnable#runTaskLaterAsynchronously(Plugin, long)"); + } + + @Deprecated + @Override +- public BukkitTask runTaskTimer(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTaskTimer(final @NonNull Plugin plugin, final @NonNull BukkitRunnable task, long delay, long period) throws IllegalArgumentException { // Plazma - Null safety + throw new UnsupportedOperationException("Use BukkitRunnable#runTaskTimer(Plugin, long, long)"); + } + + @Deprecated + @Override +- public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException { ++ @NonNull // Plazma - Null safety ++ public BukkitTask runTaskTimerAsynchronously(final @NonNull Plugin plugin, final @NonNull BukkitRunnable task, long delay, long period) throws IllegalArgumentException { // Plazma - Null safety + throw new UnsupportedOperationException("Use BukkitRunnable#runTaskTimerAsynchronously(Plugin, long, long)"); + } + + // Paper start - add getMainThreadExecutor + @Override +- public Executor getMainThreadExecutor(Plugin plugin) { +- Preconditions.checkArgument(plugin != null, "Plugin cannot be null"); +- return command -> { +- Preconditions.checkArgument(command != null, "Command cannot be null"); +- this.runTask(plugin, command); +- }; ++ @NonNull // Plazma - Null safety ++ public Executor getMainThreadExecutor(final @NonNull Plugin plugin) { // Plazma - Null safety ++ return command -> this.runTask(plugin, command); // Plazma - Null safety + } + // Paper end + } diff --git a/plazma-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/Versioning.java.patch b/plazma-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/Versioning.java.patch new file mode 100644 index 0000000..66336ca --- /dev/null +++ b/plazma-server/paper-patches/files/src/main/java/org/bukkit/craftbukkit/util/Versioning.java.patch @@ -0,0 +1,20 @@ +--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +@@ -11,7 +_,7 @@ + public static String getBukkitVersion() { + String result = "Unknown-Version"; + +- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.purpurmc.purpur/purpur-api/pom.properties"); // Pufferfish // Purpur - Rebrand ++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.plazmamc.plazma/plazma-api/pom.properties"); // Pufferfish // Purpur - Rebrand // Plazma - Rebrand + Properties properties = new Properties(); + + if (stream != null) { +@@ -20,7 +_,7 @@ + + result = properties.getProperty("version"); + } catch (IOException ex) { +- Logger.getLogger(Versioning.class.getName()).log(Level.SEVERE, "Could not get Bukkit version!", ex); ++ Logger.getLogger(Versioning.class.getName()).log(Level.SEVERE, "Could not get Plazma version!", ex); // Plazma - Rebrand + } + } + diff --git a/plazma-server/paper-patches/files/src/main/java/org/spigotmc/WatchdogThread.java.patch b/plazma-server/paper-patches/files/src/main/java/org/spigotmc/WatchdogThread.java.patch new file mode 100644 index 0000000..4b2e297 --- /dev/null +++ b/plazma-server/paper-patches/files/src/main/java/org/spigotmc/WatchdogThread.java.patch @@ -0,0 +1,47 @@ +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -77,14 +_,14 @@ + if (isLongTimeout) { + // Paper end + logger.log(Level.SEVERE, "------------------------------"); +- logger.log(Level.SEVERE, "The server has stopped responding! This is (probably) not a Purpur bug."); // Paper // Purpur - Rebrand ++ logger.log(Level.SEVERE, "The server has stopped responding! This is (probably) not a Plazma bug."); // Paper // Purpur - Rebrand // Plazma - Rebrand + logger.log(Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author"); + logger.log(Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring"); + logger.log(Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once"); + logger.log(Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes"); +- logger.log(Level.SEVERE, "If you are unsure or still think this is a Purpur bug, please report this to https://github.com/PurpurMC/Purpur/issues"); // Purpur - Rebrand ++ logger.log(Level.SEVERE, "If you are unsure or still think this is a Plazma bug, please report this to https://github.com/PlazmaMC/PlazmaBukkit/issues"); // Purpur - Rebrand // Plazma - Rebrand + logger.log(Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports"); +- logger.log(Level.SEVERE, "Purpur version: " + Bukkit.getServer().getVersion()); // Purpur - Rebrand ++ logger.log(Level.SEVERE, "Plazma version: " + Bukkit.getServer().getVersion()); // Purpur - Rebrand // Plazma - Rebrand + + if (net.minecraft.world.level.Level.lastPhysicsProblem != null) { + logger.log(Level.SEVERE, "------------------------------"); +@@ -104,14 +_,14 @@ + } + // Paper end + } else { +- logger.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PURPUR - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); // Purpur - Rebrand ++ logger.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PLAZMA - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); // Purpur - Rebrand // Plazma - Rebrand + logger.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump"); + } + // Paper end - Different message for short timeout + logger.log(Level.SEVERE, "------------------------------"); +- logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Purpur!):" ); // Paper // Purpur - Rebrand ++ logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Plazma!):" ); // Paper // Purpur - Rebrand // Plazma - Rebrand + FeatureHooks.dumpAllChunkLoadInfo(MinecraftServer.getServer(), isLongTimeout); // Paper - log detailed tick information +- WatchdogThread.dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE), logger); ++ WatchdogThread.dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().serverThread.threadId(), Integer.MAX_VALUE), logger); // Plazma - Use modern method + logger.log(Level.SEVERE, "------------------------------"); + + // Paper start - Only print full dump on long timeouts +@@ -122,7 +_,7 @@ + WatchdogThread.dumpThread(thread, logger); + } + } else { +- logger.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PURPUR - THIS IS NOT A BUG OR A CRASH ---"); // Purpur - Rebrand ++ logger.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PLAZMA - THIS IS NOT A BUG OR A CRASH ---"); // Purpur - Rebrand // Plazma - Rebrand + } + + logger.log(Level.SEVERE, "------------------------------");