From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: AlphaKR93 Date: Sat, 23 Sep 2023 10:28:30 +0000 Subject: [PATCH] Purpur Server Changes Original: PurpurMC Copyright (C) 2023 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 4d89c46c7ee559acf986ff5081a42fe91cb5a257..3d5397e2e7edf2ea1e1687c58126fd9a5d5d5736 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,12 +13,12 @@ configurations.named(log4jPlugins.compileClasspathConfigurationName) { val alsoShade: Configuration by configurations.creating dependencies { - implementation(project(":pufferfish-api")) // Pufferfish // Paper - // Pufferfish start - implementation("io.papermc.paper:paper-mojangapi:1.19.2-R0.1-SNAPSHOT") { + // Purpur start + implementation(project(":purpur-api")) + implementation("io.papermc.paper:paper-mojangapi:${project.version}") { exclude("io.papermc.paper", "paper-api") } - // Pufferfish end + // Purpur end // Paper start implementation("org.jline:jline-terminal-jansi:3.21.0") implementation("net.minecrell:terminalconsoleappender:1.3.0") @@ -56,6 +56,10 @@ dependencies { runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.7.3") runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.7.3") + implementation("org.mozilla:rhino-runtime:1.7.14") // Purpur + implementation("org.mozilla:rhino-engine:1.7.14") // Purpur + implementation("dev.omega24:upnp4j:1.0") // Purpur + // Pufferfish start implementation("org.yaml:snakeyaml:1.32") implementation ("com.github.carleslc.Simple-YAML:Simple-Yaml:1.8.4") { @@ -91,7 +95,7 @@ tasks.jar { attributes( "Main-Class" to "org.bukkit.craftbukkit.Main", "Implementation-Title" to "CraftBukkit", - "Implementation-Version" to "git-Pufferfish-$implementationVersion", // Pufferfish + "Implementation-Version" to "git-Purpur-$implementationVersion", // Pufferfish // Purpur "Implementation-Vendor" to date, // Paper "Specification-Title" to "Bukkit", "Specification-Version" to project.version, @@ -168,7 +172,7 @@ fun TaskContainer.registerRunTask( name: String, block: JavaExec.() -> Unit ): TaskProvider = register(name) { - group = "paper" + group = "paperweight" // Purpur 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/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java index 692c962193cf9fcc6801fc93f3220bdc673d527b..8cde30544e14f8fc2dac32966ae3c21f8cf3a551 100644 --- a/src/main/java/com/destroystokyo/paper/Metrics.java +++ b/src/main/java/com/destroystokyo/paper/Metrics.java @@ -593,7 +593,7 @@ public class Metrics { boolean logFailedRequests = config.getBoolean("logFailedRequests", false); // Only start Metrics, if it's enabled in the config if (config.getBoolean("enabled", true)) { - Metrics metrics = new Metrics("Pufferfish", serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish + Metrics metrics = new Metrics("Purpur", serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish // Purpur metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { String minecraftVersion = Bukkit.getVersion(); @@ -602,16 +602,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-Pufferfish-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash); // Pufferfish - } else { - paperVersion = "unknown"; - } - metrics.addCustomChart(new Metrics.SimplePie("pufferfish_version", () -> paperVersion)); // Pufferfish + metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ? "bungee" : "offline"))); // Purpur + metrics.addCustomChart(new Metrics.SimplePie("purpur_version", () -> (org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() != null) ? org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() : "unknown")); // Purpur metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { Map> map = new HashMap<>(); diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java index 9d687da5bdf398bb3f6c84cdf1249a7213d09f2e..462a6eed350fd660ddaf25d567bb6e97b77d0b2b 100644 --- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java @@ -19,8 +19,10 @@ import java.util.stream.StreamSupport; public class PaperVersionFetcher implements VersionFetcher { private static final java.util.regex.Pattern VER_PATTERN = java.util.regex.Pattern.compile("^([0-9\\.]*)\\-.*R"); // R is an anchor, will always give '-R' at end - private static final String GITHUB_BRANCH_NAME = "master"; - private static final String DOWNLOAD_PAGE = "https://papermc.io/downloads/paper"; + // Purpur start + private static final String DOWNLOAD_PAGE = "https://purpurmc.org/downloads"; + private static int distance = -2; public int distance() { return distance; } + // Purpur end private static @Nullable String mcVer; @Override @@ -31,11 +33,11 @@ public class PaperVersionFetcher implements VersionFetcher { @Nonnull @Override public Component getVersionMessage(@Nonnull String serverVersion) { - String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); - final Component updateMessage = getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); + String[] parts = serverVersion.substring("git-Purpur-".length()).split("[-\\s]"); // Purpur + final Component updateMessage = getUpdateStatusMessage("PurpurMC/Purpur", "ver/" + getMinecraftVersion(), parts[0]); // Purpur final Component history = getHistory(); - return history != null ? TextComponent.ofChildren(updateMessage, Component.newline(), history) : updateMessage; + return history != null ? Component.join(net.kyori.adventure.text.JoinConfiguration.separator(Component.newline()), history, updateMessage) : updateMessage; // Purpur } private static @Nullable String getMinecraftVersion() { @@ -45,7 +47,7 @@ public class PaperVersionFetcher implements VersionFetcher { String result = matcher.group(); mcVer = result.substring(0, result.length() - 2); // strip 'R' anchor and trailing '-' } else { - org.bukkit.Bukkit.getLogger().warning("Unable to match version to pattern! Report to PaperMC!"); + org.bukkit.Bukkit.getLogger().warning("Unable to match version to pattern! Report to Purpur!"); // Purpur org.bukkit.Bukkit.getLogger().warning("Pattern: " + VER_PATTERN.toString()); org.bukkit.Bukkit.getLogger().warning("Version: " + org.bukkit.Bukkit.getBukkitVersion()); } @@ -55,7 +57,7 @@ public class PaperVersionFetcher implements VersionFetcher { } private static Component getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) { - int distance; + //int distance; // Purpur - use field try { int jenkinsBuild = Integer.parseInt(versionInfo); distance = fetchDistanceFromSiteApi(jenkinsBuild, getMinecraftVersion()); @@ -66,13 +68,13 @@ public class PaperVersionFetcher implements VersionFetcher { switch (distance) { case -1: - return Component.text("Error obtaining version information", NamedTextColor.YELLOW); + return Component.text("* Error obtaining version information", NamedTextColor.RED); // Purpur case 0: - return Component.text("You are running the latest version", NamedTextColor.GREEN); + return Component.text("* You are running the latest version", NamedTextColor.GREEN); // Purpur case -2: - return Component.text("Unknown version", NamedTextColor.YELLOW); + return Component.text("* Unknown version", NamedTextColor.YELLOW); // Purpur default: - return Component.text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW) + return Component.text("* You are " + distance + " version(s) behind", NamedTextColor.YELLOW) // Purpur .append(Component.newline()) .append(Component.text("Download the new version at: ") .append(Component.text(DOWNLOAD_PAGE, NamedTextColor.GOLD) @@ -85,15 +87,11 @@ public class PaperVersionFetcher implements VersionFetcher { if (siteApiVersion == null) { return -1; } try { try (BufferedReader reader = Resources.asCharSource( - new URL("https://api.papermc.io/v2/projects/paper/versions/" + siteApiVersion), + new URL("https://api.purpurmc.org/v2/purpur/" + siteApiVersion), // Purpur Charsets.UTF_8 ).openBufferedStream()) { JsonObject json = new Gson().fromJson(reader, JsonObject.class); - JsonArray builds = json.getAsJsonArray("builds"); - int latest = StreamSupport.stream(builds.spliterator(), false) - .mapToInt(e -> e.getAsInt()) - .max() - .getAsInt(); + int latest = json.getAsJsonObject("builds").getAsJsonPrimitive("latest").getAsInt(); // Purpur return latest - jenkinsBuild; } catch (JsonSyntaxException ex) { ex.printStackTrace(); @@ -144,6 +142,6 @@ public class PaperVersionFetcher implements VersionFetcher { return null; } - return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); + return org.bukkit.ChatColor.parseMM("Previous: %s", oldVersion); // Purpur } } diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java index c5d5648f4ca603ef2b1df723b58f9caf4dd3c722..3cb56595822799926a8141e60a42f5d1edfc6de5 100644 --- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java @@ -17,7 +17,7 @@ public final class PaperConsole extends SimpleTerminalConsole { @Override protected LineReader buildReader(LineReaderBuilder builder) { builder - .appName("Paper") + .appName("Purpur") // Purpur .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 a08c00b8c0488d18be5e182f7892e5ab71d12247..338f693d098b6ab507c30f6411c9a952c34ba8e3 100644 --- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java @@ -136,6 +136,10 @@ public class MobGoalHelper { static { // TODO these kinda should be checked on each release, in case obfuscation changes deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); + // Purpur start + deobfuscationMap.put("zombie_1", "zombie_attack_villager"); + deobfuscationMap.put("drowned_1", "drowned_attack_villager"); + // Purpur end ignored.add("goal_selector_1"); ignored.add("goal_selector_2"); diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java index fa56cd09102a89692b42f1d14257990508c5c720..f9251183df72ddc56662fd3f02acf21641a2200c 100644 --- a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java @@ -58,7 +58,7 @@ public class RAMDetails extends JList { GraphData data = RAMGraph.DATA.peekLast(); Vector vector = new Vector<>(); - double[] tps = new double[] {server.tps1.getAverage(), server.tps5.getAverage(), server.tps15.getAverage()}; + double[] tps = new double[] {server.tps5s.getAverage(), server.tps1.getAverage(), server.tps5.getAverage(), server.tps15.getAverage()}; // Purpur String[] tpsAvg = new String[tps.length]; for ( int g = 0; g < tps.length; g++) { @@ -67,7 +67,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(getAverage(server.tickTimes)) + " 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 setListData(vector); } diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java index 95d1a8a5b349f7849c040026bfa3469d03d92bfd..3e36958365bc136516bafbaad0c168f7956406f1 100644 --- a/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java +++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java @@ -28,6 +28,7 @@ public class PufferfishConfig { private static final YamlFile config = new YamlFile(); private static int updates = 0; + public static File pufferfishFile; // Purpur private static ConfigurationSection convertToBukkit(org.simpleyaml.configuration.ConfigurationSection section) { ConfigurationSection newSection = new MemoryConfiguration(); @@ -50,7 +51,7 @@ public class PufferfishConfig { } public static void load() throws IOException { - File configFile = new File("pufferfish.yml"); + File configFile = pufferfishFile; // Purpur if (configFile.exists()) { try { @@ -224,7 +225,7 @@ public class PufferfishConfig { public static int activationDistanceMod; private static void dynamicActivationOfBrains() throws IOException { - dearEnabled = getBoolean("dab.enabled", "activation-range.enabled", true); + dearEnabled = getBoolean("dab.enabled", "activation-range.enabled", false); // Purpur 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."); @@ -268,7 +269,7 @@ public class PufferfishConfig { public static boolean throttleInactiveGoalSelectorTick; private static void inactiveGoalSelectorThrottle() { - throttleInactiveGoalSelectorTick = getBoolean("inactive-goal-selector-throttle", "inactive-goal-selector-disable", true, + throttleInactiveGoalSelectorTick = getBoolean("inactive-goal-selector-throttle", "inactive-goal-selector-disable", false, // Purpur "Throttles the AI goal selector in entity inactive ticks.", "This can improve performance by a few percent, but has minor gameplay implications."); } diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java index abd0217cf0bff183c8e262edc173a53403797c1a..2519ad2884b6c09b312432b933c31476b369e599 100644 --- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java +++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java @@ -1315,9 +1315,9 @@ public final class ChunkHolderManager { } public boolean processTicketUpdates() { - co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager + //co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager // Purpur return this.processTicketUpdates(true, true, null); - } finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Paper - add timings for distance manager + //} finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Paper - add timings for distance manager // Purpur } private static final ThreadLocal> CURRENT_TICKET_UPDATE_SCHEDULING = new ThreadLocal<>(); diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java index 51304c5cf4b0ac7646693ef97ef4a3847d3342b5..535ab99585cd4463d051334681bc80b5d20df7c0 100644 --- a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java +++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java @@ -1779,7 +1779,7 @@ public final class NewChunkHolder { boolean canSavePOI = !(chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave) && (poi != null && poi.isDirty()); boolean canSaveEntities = entities != null; - try (co.aikar.timings.Timing ignored = this.world.timings.chunkSave.startTiming()) { // Paper + //try (co.aikar.timings.Timing ignored = this.world.timings.chunkSave.startTiming()) { // Paper // Purpur if (canSaveChunk) { canSaveChunk = this.saveChunk(chunk, unloading); } @@ -1793,7 +1793,7 @@ public final class NewChunkHolder { this.lastEntityUnload = null; } } - } + //} // Purpur return executedUnloadTask | canSaveChunk | canSaveEntities | canSavePOI ? new SaveStat(executedUnloadTask || canSaveChunk, canSaveEntities, canSavePOI): null; } diff --git a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java index f0fce4113fb07c64adbec029d177c236cbdcbae8..e94224ed280247ee69dfdff8dc960f2b8729be33 100644 --- a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java +++ b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java @@ -78,10 +78,10 @@ public class PaperPluginsCommand extends BukkitCommand { this.setAliases(Arrays.asList("pl")); } - private static List formatProviders(TreeMap> plugins) { + private static List formatProviders(TreeMap> plugins, @NotNull CommandSender sender) { // Purpur List components = new ArrayList<>(plugins.size()); for (PluginProvider entry : plugins.values()) { - components.add(formatProvider(entry)); + components.add(formatProvider(entry, sender)); // Purpur } boolean isFirst = true; @@ -109,7 +109,7 @@ public class PaperPluginsCommand extends BukkitCommand { return formattedSublists; } - private static Component formatProvider(PluginProvider provider) { + private static Component formatProvider(PluginProvider provider, @NotNull CommandSender sender) { // Purpur TextComponent.Builder builder = Component.text(); if (provider instanceof SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) { builder.append(LEGACY_PLUGIN_STAR); @@ -117,12 +117,64 @@ public class PaperPluginsCommand extends BukkitCommand { String name = provider.getMeta().getName(); Component pluginName = Component.text(name, fromStatus(provider)) - .clickEvent(ClickEvent.runCommand("/version " + name)); + // Purpur start + .clickEvent(ClickEvent.suggestCommand("/version " + name)); + + if (sender instanceof org.bukkit.entity.Player && sender.hasPermission("bukkit.command.version")) { + // Event components + String description = provider.getMeta().getDescription(); + TextComponent.Builder hover = Component.text(); + hover.append(Component.text("Version: ", NamedTextColor.WHITE)).append(Component.text(provider.getMeta().getVersion(), NamedTextColor.GREEN)); + + if (description != null) { + hover.append(Component.newline()) + .append(Component.text("Description: ", NamedTextColor.WHITE)) + .append(Component.text(description, NamedTextColor.GREEN)); + } + + if (provider.getMeta().getWebsite() != null) { + hover.append(Component.newline()) + .append(Component.text("Website: ", NamedTextColor.WHITE)) + .append(Component.text(provider.getMeta().getWebsite(), NamedTextColor.GREEN)); + } + + if (!provider.getMeta().getAuthors().isEmpty()) { + hover.append(Component.newline()); + if (provider.getMeta().getAuthors().size() == 1) { + hover.append(Component.text("Author: ")); + } else { + hover.append(Component.text("Authors: ")); + } + + hover.append(getAuthors(provider.getMeta())); + } + + pluginName.hoverEvent(hover.build()); + } builder.append(pluginName); + // Purpur end + + return builder.build(); + } + + // Purpur start + @NotNull + private static TextComponent getAuthors(@NotNull final PluginMeta pluginMeta) { + TextComponent.Builder builder = Component.text(); + List authors = pluginMeta.getAuthors(); + + for (int i = 0; i < authors.size(); i++) { + if (i > 0) { + builder.append(Component.text(i < authors.size() - 1 ? ", " : " and ", NamedTextColor.WHITE)); + } + + builder.append(Component.text(authors.get(i), NamedTextColor.GREEN)); + } return builder.build(); } + // Purpur end private static Component asPlainComponents(String strings) { net.kyori.adventure.text.TextComponent.Builder builder = Component.text(); @@ -182,24 +234,24 @@ public class PaperPluginsCommand extends BukkitCommand { } } - Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE); + //Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE); //.append(INFO_ICON_START.hoverEvent(SERVER_PLUGIN_INFO)); TODO: Add docs - sender.sendMessage(infoMessage); + //sender.sendMessage(infoMessage); // Purpur if (!paperPlugins.isEmpty()) { - sender.sendMessage(PAPER_HEADER); + sender.sendMessage(PAPER_HEADER.append(Component.text(" (%s):".formatted(paperPlugins.size())))); // Purpur } - for (Component component : formatProviders(paperPlugins)) { + for (Component component : formatProviders(paperPlugins, sender)) { // Purpur sender.sendMessage(component); } if (!spigotPlugins.isEmpty()) { - sender.sendMessage(BUKKIT_HEADER); + sender.sendMessage(BUKKIT_HEADER.append(Component.text(" (%s):".formatted(spigotPlugins.size())))); // Purpur } - for (Component component : formatProviders(spigotPlugins)) { + for (Component component : formatProviders(spigotPlugins, sender)) { // Purpur sender.sendMessage(component); } diff --git a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java index a8e813ca89b033f061e695288b3383bdcf128531..1ab65af9359d19530bba7f985a604d2a430ee234 100644 --- a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java +++ b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java @@ -54,9 +54,9 @@ public final class SysoutCatcher { final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(clazz); // Instead of just printing the message, send it to the plugin's logger - plugin.getLogger().log(this.level, this.prefix + line); + plugin.getLogger().log(this.level, /*this.prefix +*/ line); // Purpur - prefix not needed - if (SysoutCatcher.SUPPRESS_NAGS) { + if (true || SysoutCatcher.SUPPRESS_NAGS) { // Purpur - nagging is annoying return; } if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) { diff --git a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java index 89bf48fd581ee6580b91e2eb31dd532cb622df5e..e35da199be67e04c34df6bc09afd8d8122cb0487 100644 --- a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java +++ b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java @@ -102,6 +102,7 @@ public class PluginInitializerManager { java.util.List files = (java.util.List) optionSet.valuesOf("add-plugin"); // Register plugins from the flag io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.PluginFlagProviderSource.INSTANCE, files); + io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.SparkProviderSource.INSTANCE, new File("cache", "spark.jar").toPath()); // Purpur } // This will be the end of me... diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/SparkProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/SparkProviderSource.java new file mode 100644 index 0000000000000000000000000000000000000000..a7d1ae53eac94bc2dcf8bc78ef1da0d3b8554736 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/provider/source/SparkProviderSource.java @@ -0,0 +1,102 @@ +package io.papermc.paper.plugin.provider.source; + +import com.mojang.logging.LogUtils; +import io.papermc.paper.plugin.entrypoint.Entrypoint; +import io.papermc.paper.plugin.entrypoint.EntrypointHandler; +import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; +import io.papermc.paper.plugin.provider.PluginProvider; +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.math.BigInteger; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.security.MessageDigest; +import java.util.stream.Collectors; +import org.bukkit.plugin.java.JavaPlugin; +import org.slf4j.Logger; + +public class SparkProviderSource extends FileProviderSource { + public static final SparkProviderSource INSTANCE = new SparkProviderSource(); + + private static final Logger LOGGER = LogUtils.getClassLogger(); + + public SparkProviderSource() { + super("File '%s' specified by Purpur"::formatted); + } + + @Override + public void registerProviders(EntrypointHandler entrypointHandler, Path context) throws Exception { + // first, check if user doesn't want spark at all + if (Boolean.getBoolean("Purpur.IReallyDontWantSpark")) { + return; // boo! + } + + // second, check if user has their own spark + if (hasSpark()) { + LOGGER.info("Purpur: Using user-provided spark plugin instead of our own."); + return; // let's hope it's at least the modern version :3 + } + + // you can't have errors in your code if you wrap the entire codebase in a try/catch block + try { + + // make sure the directory exists where we want to keep spark + File file = context.toFile(); + file.getParentFile().mkdirs(); + + boolean shouldDownload; + + // check if our spark exists + if (!file.exists()) { + // it does not, so let's download it + shouldDownload = true; + } else { + // we have a spark file, let's see if it's up-to-date by comparing shas + String fileSha1 = String.format("%040x", new BigInteger(1, MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(file.toPath())))); + String sparkSha1; + + // luck has a nifty endpoint containing the sha of the newest version + URLConnection urlConnection = new URL("https://sparkapi.lucko.me/download/bukkit/sha1").openConnection(); + + // set a reasonable timeout to prevent servers without internet from hanging for 60+ seconds on startup + urlConnection.setReadTimeout(5000); + urlConnection.setConnectTimeout(5000); + + // read it + try (BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()))) { + sparkSha1 = reader.lines().collect(Collectors.joining("")); + } + + // compare; we only download a new spark if the shas don't match + shouldDownload = !fileSha1.equals(sparkSha1); + } + + // ok, finally we can download spark if we need it + if (shouldDownload) { + URLConnection urlConnection = new URL("https://sparkapi.lucko.me/download/bukkit").openConnection(); + urlConnection.setReadTimeout(5000); + urlConnection.setConnectTimeout(5000); + Files.copy(urlConnection.getInputStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + + // register the spark, newly downloaded or existing + super.registerProviders(entrypointHandler, context); + + } catch (Throwable e) { + LOGGER.error("Purpur: Failed to download and install spark plugin", e); + } + } + + private static boolean hasSpark() { + for (PluginProvider provider : LaunchEntryPointHandler.INSTANCE.get(Entrypoint.PLUGIN).getRegisteredProviders()) { + if (provider.getMeta().getName().equalsIgnoreCase("spark")) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java index abe37c7c3c6f5ab73afd738ec78f06d7e4d2ed96..b5b6657e52e4f7a630229bd3ba433438af293e22 100644 --- a/src/main/java/net/minecraft/CrashReport.java +++ b/src/main/java/net/minecraft/CrashReport.java @@ -123,6 +123,10 @@ public class CrashReport { StringBuilder stringbuilder = new StringBuilder(); stringbuilder.append("---- Minecraft Crash Report ----\n"); + // Purpur start + stringbuilder.append("// "); + stringbuilder.append("// DO NOT REPORT THIS TO PAPER! REPORT TO PURPUR INSTEAD!"); + // Purpur end stringbuilder.append("// "); stringbuilder.append(CrashReport.getErrorComment()); stringbuilder.append("\n\n"); diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java index 0de2eae2d448ac9e269a4edf48406d5ea8af8059..873659dcf8c8431f21bd2b23b8d7bdb4ea7e03e8 100644 --- a/src/main/java/net/minecraft/commands/CommandSourceStack.java +++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java @@ -227,6 +227,19 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy } // CraftBukkit end + // Purpur start + public boolean testPermission(int i, String bukkitPermission) { + if (hasPermission(i, bukkitPermission)) { + return true; + } + net.kyori.adventure.text.Component permissionMessage = getLevel().getServer().server.permissionMessage(); + if (!permissionMessage.equals(net.kyori.adventure.text.Component.empty())) { + sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(permissionMessage.replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("").replacement(bukkitPermission).build()))); + } + return false; + } + // Purpur end + public Vec3 getPosition() { return this.worldPosition; } @@ -336,6 +349,30 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy } } + // Purpur start + public void sendSuccess(@Nullable String message) { + sendSuccess(message, false); + } + + public void sendSuccess(@Nullable String message, boolean broadcastToOps) { + if (message == null) { + return; + } + sendSuccess(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), broadcastToOps); + } + + public void sendSuccess(@Nullable net.kyori.adventure.text.Component message) { + sendSuccess(message, false); + } + + public void sendSuccess(@Nullable net.kyori.adventure.text.Component message, boolean broadcastToOps) { + if (message == null) { + return; + } + sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(message), broadcastToOps); + } + // Purpur end + public void sendSuccess(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 80c2c8d565f03ae0ea24fbdecdbe2bc5b9aa4b82..5c0085589b08f199c75ceeab8d0cf25e970a0533 100644 --- a/src/main/java/net/minecraft/commands/Commands.java +++ b/src/main/java/net/minecraft/commands/Commands.java @@ -150,7 +150,7 @@ public class Commands { DamageCommand.register(this.dispatcher, commandRegistryAccess); DataCommands.register(this.dispatcher); DataPackCommand.register(this.dispatcher); - DebugCommand.register(this.dispatcher); + //DebugCommand.register(this.dispatcher); // Purpur DefaultGameModeCommands.register(this.dispatcher); DifficultyCommand.register(this.dispatcher); EffectCommands.register(this.dispatcher, commandRegistryAccess); @@ -224,6 +224,14 @@ public class Commands { SetPlayerIdleTimeoutCommand.register(this.dispatcher); StopCommand.register(this.dispatcher); WhitelistCommand.register(this.dispatcher); + org.purpurmc.purpur.command.CreditsCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.DemoCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.PingCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.UptimeCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.TPSBarCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.CompassCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.RamBarCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.RamCommand.register(this.dispatcher); // Purpur } if (environment.includeIntegrated) { @@ -311,9 +319,9 @@ public class Commands { public int performCommand(ParseResults parseresults, String s, String label) { // CraftBukkit CommandSourceStack commandlistenerwrapper = (CommandSourceStack) parseresults.getContext().getSource(); - commandlistenerwrapper.getServer().getProfiler().push(() -> { + /*commandlistenerwrapper.getServer().getProfiler().push(() -> { // Purpur return "/" + s; - }); + });*/ // Purpur byte b0; @@ -396,7 +404,7 @@ public class Commands { b0 = 0; } } finally { - commandlistenerwrapper.getServer().getProfiler().pop(); + //commandlistenerwrapper.getServer().getProfiler().pop(); // Purpur } return b0; @@ -456,6 +464,7 @@ public class Commands { private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { // Paper end - Async command map building new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper + 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); @@ -466,6 +475,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 f25b9330e068c7d9e12cb57a7761cfef9ebaf7bc..7e66aaa960ce7b6dda7c064d4c6856cc4b368b58 100644 --- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java +++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java @@ -200,10 +200,10 @@ public class EntitySelector { if (this.playerName != null) { entityplayer = source.getServer().getPlayerList().getPlayerByName(this.playerName); - return (List) (entityplayer == null ? Collections.emptyList() : Lists.newArrayList(new ServerPlayer[]{entityplayer})); + return entityplayer == null || !canSee(source, entityplayer) ? Collections.emptyList() : Lists.newArrayList(entityplayer); // Purpur } else if (this.entityUUID != null) { entityplayer = source.getServer().getPlayerList().getPlayer(this.entityUUID); - return (List) (entityplayer == null ? Collections.emptyList() : Lists.newArrayList(new ServerPlayer[]{entityplayer})); + return entityplayer == null || !canSee(source, entityplayer) ? Collections.emptyList() : Lists.newArrayList(entityplayer); // Purpur } else { Vec3 vec3d = (Vec3) this.position.apply(source.getPosition()); Predicate predicate = this.getPredicate(vec3d); @@ -213,7 +213,7 @@ public class EntitySelector { ServerPlayer entityplayer1 = (ServerPlayer) source.getEntity(); if (predicate.test(entityplayer1)) { - return Lists.newArrayList(new ServerPlayer[]{entityplayer1}); + return !canSee(source, entityplayer1) ? Collections.emptyList() : Lists.newArrayList(entityplayer1); // Purpur } } @@ -224,6 +224,7 @@ public class EntitySelector { if (this.isWorldLimited()) { object = source.getLevel().getPlayers(predicate, i); + ((List) object).removeIf(entityplayer3 -> !canSee(source, (ServerPlayer) entityplayer3)); // Purpur } else { object = Lists.newArrayList(); Iterator iterator = source.getServer().getPlayerList().getPlayers().iterator(); @@ -231,7 +232,7 @@ public class EntitySelector { while (iterator.hasNext()) { ServerPlayer entityplayer2 = (ServerPlayer) iterator.next(); - if (predicate.test(entityplayer2)) { + if (predicate.test(entityplayer2) && canSee(source, entityplayer2)) { // Purpur ((List) object).add(entityplayer2); if (((List) object).size() >= i) { return (List) object; @@ -276,4 +277,10 @@ public class EntitySelector { public static Component joinNames(List entities) { return ComponentUtils.formatList(entities, Entity::getDisplayName); } + + // Purpur start + private boolean canSee(CommandSourceStack sender, ServerPlayer target) { + return !org.purpurmc.purpur.PurpurConfig.hideHiddenPlayersFromEntitySelector || !(sender.getEntity() instanceof ServerPlayer player) || player.getBukkitEntity().canSee(target.getBukkitEntity()); + } + // Purpur end } diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java index 83cab746d1d6fe25c043c8aee28c39412b90c127..ec6b58dae525c81bbb1c0e2d96fbded6f00a45b5 100644 --- a/src/main/java/net/minecraft/core/BlockPos.java +++ b/src/main/java/net/minecraft/core/BlockPos.java @@ -48,6 +48,12 @@ public class BlockPos extends Vec3i { private static final int X_OFFSET = 38; // Paper end + // Purpur start + public BlockPos(net.minecraft.world.entity.Entity entity) { + super(entity.getBlockX(), entity.getBlockY(), entity.getBlockZ()); + } + // Purpur end + public BlockPos(int x, int y, int z) { super(x, y, z); } diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java index d0a8092bf57a29ab7c00ec0ddf52a9fdb2a33267..defe5951938ce3a7b7f83017d4af36bb49ff5be0 100644 --- a/src/main/java/net/minecraft/core/Direction.java +++ b/src/main/java/net/minecraft/core/Direction.java @@ -238,6 +238,12 @@ public enum Direction implements StringRepresentable { case EAST: var10000 = SOUTH; break; + // Purpur start + case UP: + return UP; + case DOWN: + return DOWN; + // Purpur end default: throw new IllegalStateException("Unable to get Y-rotated facing of " + this); } @@ -350,6 +356,12 @@ public enum Direction implements StringRepresentable { case EAST: var10000 = NORTH; break; + // Purpur start + case UP: + return UP; + case DOWN: + return DOWN; + // Purpur end default: throw new IllegalStateException("Unable to get CCW facing of " + this); } diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java index 8c2750de3be202122ccf9923a9fa32fcb8bdb743..7961a69d3e70a8ae1d74c732cd97a9cbd824a8d0 100644 --- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java @@ -1187,6 +1187,23 @@ public interface DispenseItemBehavior { } } }); + // Purpur start + DispenserBlock.registerBehavior(Items.ANVIL, (new OptionalDispenseItemBehavior() { + @Override + public ItemStack execute(BlockSource dispenser, ItemStack stack) { + Level level = dispenser.getLevel(); + if (!level.purpurConfig.dispenserPlaceAnvils) return super.execute(dispenser, stack); + Direction facing = dispenser.getBlockState().getValue(DispenserBlock.FACING); + BlockPos pos = dispenser.getPos().relative(facing); + BlockState state = level.getBlockState(pos); + if (state.isAir()) { + level.setBlockAndUpdate(pos, Blocks.ANVIL.defaultBlockState().setValue(net.minecraft.world.level.block.AnvilBlock.FACING, facing.getAxis() == Direction.Axis.Y ? Direction.NORTH : facing.getClockWise())); + stack.shrink(1); + } + return stack; + } + })); + // Purpur end } static void setEntityPokingOutOfBlock(BlockSource pointer, Entity entity, Direction direction) { diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java index 9b0049dfeaec9b688bf276f2ac2b18943b5696b2..d7563904232353cbf3b9255cedfb75920e35220c 100644 --- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java @@ -107,7 +107,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { continue; } // CraftBukkit end - ishearable.shear(SoundSource.BLOCKS); + ishearable.shear(SoundSource.BLOCKS, net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MOB_LOOTING, CraftItemStack.asNMSCopy(craftItem))); // Purpur worldserver.gameEvent((Entity) null, GameEvent.SHEAR, blockposition); return true; } diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java index 52cb6476604c98465f81ff9e8d8344d7536a8868..13144ea01bc27db95a13b267d39529b012a4c9c4 100644 --- a/src/main/java/net/minecraft/network/Connection.java +++ b/src/main/java/net/minecraft/network/Connection.java @@ -579,11 +579,20 @@ public class Connection extends SimpleChannelInboundHandler> { private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper private static int joinAttemptsThisTick; // Paper private static int currTick; // Paper + private static int tickSecond; // Purpur public void tick() { this.flushQueue(); // Paper start if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) { Connection.currTick = net.minecraft.server.MinecraftServer.currentTick; + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.maxJoinsPerSecond) { + if (++Connection.tickSecond > 20) { + Connection.tickSecond = 0; + Connection.joinAttemptsThisTick = 0; + } + } else + // Purpur end Connection.joinAttemptsThisTick = 0; } // Paper end diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java index 9938bb90bef84cf784f9a1ceb02a1a45aa8b48a1..1f4b64a5f812376c499c98cb4be62469bd0b7dbe 100644 --- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java +++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java @@ -98,6 +98,8 @@ public class FriendlyByteBuf extends ByteBuf { private static final int MAX_PUBLIC_KEY_LENGTH = 512; private static final Gson GSON = new Gson(); + public static boolean hasItemSerializeEvent = false; // Purpur + public FriendlyByteBuf(ByteBuf parent) { this.source = parent; } @@ -679,6 +681,17 @@ public class FriendlyByteBuf extends ByteBuf { this.writeBoolean(false); } else { this.writeBoolean(true); + // Purpur start + if (hasItemSerializeEvent) { + var event = new org.purpurmc.purpur.event.packet.NetworkItemSerializeEvent(stack.asBukkitCopy()); + event.callEvent(); + ItemStack newStack = ItemStack.fromBukkitCopy(event.getItemStack()); + if (org.purpurmc.purpur.PurpurConfig.fixNetworkSerializedItemsInCreative && !ItemStack.matches(stack, newStack)) { + stack.save(newStack.getOrCreateTagElement("Purpur.OriginalItem")); + } + stack = newStack; + } + // Purpur end Item item = stack.getItem(); this.writeId(BuiltInRegistries.ITEM, item); diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java index d2f0a0755317f5fa9a1ccf7db346aa77fd287d80..03852e7d21d9470a4469676367463fefb38acdc6 100644 --- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java +++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java @@ -47,7 +47,7 @@ public class PacketUtils { if (MinecraftServer.getServer().hasStopped() || (listener instanceof ServerGamePacketListenerImpl && ((ServerGamePacketListenerImpl) listener).processedDisconnect)) return; // CraftBukkit, MC-142590 if (listener.isAcceptingMessages()) { co.aikar.timings.Timing timing = co.aikar.timings.MinecraftTimings.getPacketTiming(packet); // Paper - timings - try (co.aikar.timings.Timing ignored = timing.startTiming()) { // Paper - timings + try { // Paper - timings // Purpur packet.handle(listener); } catch (Exception exception) { if (listener.shouldPropagateHandlingExceptions()) { diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java index 9ec6145fe04ec64bbee8ec6a837719caebdbc6f5..358d610ad020cada1bb83e393deeeaaec05a2791 100644 --- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java +++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java @@ -5,7 +5,7 @@ import net.minecraft.network.protocol.Packet; public class ClientboundSetTimePacket implements Packet { private final long gameTime; - private final long dayTime; + private long dayTime; public void setDayTime(long dayTime) { this.dayTime = dayTime; } // Purpur public ClientboundSetTimePacket(long time, long timeOfDay, boolean doDaylightCycle) { this.gameTime = time; diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 67cba5cf510e4a602121e10c015a491650127752..e6ddf69ade7ae51640569150f7db10d1afa63691 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -293,6 +293,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); public int autosavePeriod; public Commands vanillaCommandDispatcher; @@ -302,15 +303,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { @@ -337,13 +340,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { + //this.metricsRecorder = InactiveMetricsRecorder.INSTANCE; // Purpur + //this.profiler = this.metricsRecorder.getProfiler(); // Purpur + /*this.onMetricsRecordingStopped = (methodprofilerresults) -> { // Purpur this.stopRecordingMetrics(); - }; - this.onMetricsRecordingFinished = (path) -> { - }; + };*/ // Purpur + //this.onMetricsRecordingFinished = (path) -> { // Purpur + //}; // Purpur this.random = RandomSource.create(); this.port = -1; this.levels = Maps.newLinkedHashMap(); @@ -922,7 +925,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { return !this.canOversleep(); }); - isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); + //isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); // Purpur // Paper end new com.destroystokyo.paper.event.server.ServerTickStartEvent(this.tickCount+1).callEvent(); // Paper @@ -1403,7 +1428,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0 && this.tickCount % autosavePeriod == 0; try { this.isSaving = true; @@ -1418,20 +1443,20 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { @@ -1495,22 +1520,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Purpur Iterator iterator = this.getAllLevels().iterator(); // Paper - move down while (iterator.hasNext()) { ServerLevel worldserver = (ServerLevel) iterator.next(); worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper + worldserver.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - this.profiler.push(() -> { + /*this.profiler.push(() -> { // Purpur return worldserver + " " + worldserver.dimension().location(); - }); + });*/ // Purpur /* Drop global time updates if (this.tickCount % 20 == 0) { - this.profiler.push("timeSync"); + //this.profiler.push("timeSync"); // Purpur this.synchronizeTime(worldserver); - this.profiler.pop(); + //this.profiler.pop(); // Purpur } // CraftBukkit end */ - this.profiler.push("tick"); + //this.profiler.push("tick"); // Purpur try { - worldserver.timings.doTick.startTiming(); // Spigot + //worldserver.timings.doTick.startTiming(); // Spigot // Purpur worldserver.tick(shouldKeepTicking); // Paper start for (final io.papermc.paper.chunk.SingleThreadChunkRegionManager regionManager : worldserver.getChunkSource().chunkMap.regionManagers) { regionManager.recalculateRegions(); } // Paper end - worldserver.timings.doTick.stopTiming(); // Spigot + //worldserver.timings.doTick.stopTiming(); // Spigot // Purpur } catch (Throwable throwable) { // Spigot Start CrashReport crashreport; @@ -1577,33 +1604,33 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! + return org.purpurmc.purpur.PurpurConfig.serverModName; // Purpur - Purpur > // Pufferfish - Pufferfish > // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! } public SystemReport fillSystemReport(SystemReport details) { @@ -2273,7 +2300,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { this.executeBlocking(() -> { this.saveDebugReport(path.resolve("server")); @@ -2523,40 +2550,40 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop resultConsumer, Consumer dumpConsumer) { - this.onMetricsRecordingStopped = (methodprofilerresults) -> { + /*this.onMetricsRecordingStopped = (methodprofilerresults) -> { // Purpur this.stopRecordingMetrics(); resultConsumer.accept(methodprofilerresults); }; this.onMetricsRecordingFinished = dumpConsumer; - this.willStartRecordingMetrics = true; + this.willStartRecordingMetrics = true;*/ // Purpur } public void stopRecordingMetrics() { - this.metricsRecorder = InactiveMetricsRecorder.INSTANCE; + //this.metricsRecorder = InactiveMetricsRecorder.INSTANCE; // Purpur } public void finishRecordingMetrics() { - this.metricsRecorder.end(); + //this.metricsRecorder.end(); // Purpur } public void cancelRecordingMetrics() { - this.metricsRecorder.cancel(); - this.profiler = this.metricsRecorder.getProfiler(); + //this.metricsRecorder.cancel(); // Purpur + //this.profiler = this.metricsRecorder.getProfiler(); // Purpur } public Path getWorldPath(LevelResource worldSavePath) { @@ -2605,15 +2632,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { final io.papermc.paper.adventure.ChatDecorationProcessor processor = new io.papermc.paper.adventure.ChatDecorationProcessor(this, sender, commandSourceStack, message); @@ -2779,7 +2815,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop functions, ResourceLocation label) { - ProfilerFiller gameprofilerfiller = this.server.getProfiler(); + //ProfilerFiller gameprofilerfiller = this.server.getProfiler(); // Purpur Objects.requireNonNull(label); - gameprofilerfiller.push(label::toString); + //gameprofilerfiller.push(label::toString); // Purpur Iterator iterator = functions.iterator(); while (iterator.hasNext()) { @@ -70,7 +70,7 @@ public class ServerFunctionManager { this.execute(customfunction, this.getGameLoopSender()); } - this.server.getProfiler().pop(); + //this.server.getProfiler().pop(); // Purpur } public int execute(CommandFunction function, CommandSourceStack source) { @@ -89,7 +89,7 @@ public class ServerFunctionManager { } else { int i; - try (co.aikar.timings.Timing timing = function.getTiming().startTiming()) { // Paper + try /*(co.aikar.timings.Timing timing = function.getTiming().startTiming())*/ { // Paper // Purpur this.context = new ServerFunctionManager.ExecutionContext(tracer); i = this.context.runTopCommand(function, source); } finally { @@ -187,10 +187,10 @@ public class ServerFunctionManager { try { ServerFunctionManager.QueuedCommand customfunctiondata_queuedcommand = (ServerFunctionManager.QueuedCommand) this.commandQueue.removeFirst(); - ProfilerFiller gameprofilerfiller = ServerFunctionManager.this.server.getProfiler(); + //ProfilerFiller gameprofilerfiller = ServerFunctionManager.this.server.getProfiler(); // Purpur Objects.requireNonNull(customfunctiondata_queuedcommand); - gameprofilerfiller.push(customfunctiondata_queuedcommand::toString); + //gameprofilerfiller.push(customfunctiondata_queuedcommand::toString); // Purpur this.depth = customfunctiondata_queuedcommand.depth; customfunctiondata_queuedcommand.execute(ServerFunctionManager.this, this.commandQueue, i, this.tracer); if (this.abortCurrentDepth) { @@ -209,7 +209,7 @@ public class ServerFunctionManager { this.nestedCalls.clear(); } finally { - ServerFunctionManager.this.server.getProfiler().pop(); + //ServerFunctionManager.this.server.getProfiler().pop(); // Purpur } ++j; diff --git a/src/main/java/net/minecraft/server/commands/EnchantCommand.java b/src/main/java/net/minecraft/server/commands/EnchantCommand.java index 664cbce2e06fcb95d3d3d6c5302fc9119f938925..bc9778c705d23acd84fa1cdeff6b403b4cda3686 100644 --- a/src/main/java/net/minecraft/server/commands/EnchantCommand.java +++ b/src/main/java/net/minecraft/server/commands/EnchantCommand.java @@ -48,7 +48,7 @@ public class EnchantCommand { private static int enchant(CommandSourceStack source, Collection targets, Holder enchantment, int level) throws CommandSyntaxException { Enchantment enchantment2 = enchantment.value(); - if (level > enchantment2.getMaxLevel()) { + if (!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && level > enchantment2.getMaxLevel()) { // Purpur throw ERROR_LEVEL_TOO_HIGH.create(level, enchantment2.getMaxLevel()); } else { int i = 0; @@ -58,7 +58,7 @@ public class EnchantCommand { LivingEntity livingEntity = (LivingEntity)entity; ItemStack itemStack = livingEntity.getMainHandItem(); if (!itemStack.isEmpty()) { - if (enchantment2.canEnchant(itemStack) && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantments(itemStack).keySet(), enchantment2)) { + if ((enchantment2.canEnchant(itemStack) && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantments(itemStack).keySet(), enchantment2)) || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && !itemStack.hasEnchantment(enchantment2))) { // Purpur itemStack.enchant(enchantment2, level); ++i; } else if (targets.size() == 1) { diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java index 5cb15e2209d7b315904a1fc6d650ce1e75584271..7e21db60f3ace2a19686d6ea04b994ec3a793ec8 100644 --- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java +++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java @@ -45,6 +45,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 d601d287e94a59ff93b8a83a44dac02544d211df..0ff3b06a98b2f4514b2d861b92dd70fe678ae86c 100644 --- a/src/main/java/net/minecraft/server/commands/GiveCommand.java +++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java @@ -59,6 +59,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()) { itemstack1.setCount(1); entityitem = entityplayer.drop(itemstack1, false, false, false); // SPIGOT-2942: Add boolean to call event diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java index 10501842887c1ffdb6bb667480682b7174fd89c6..2a70abb9e0af502885593df1e732887cd9d2ce4d 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -99,6 +99,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface return; } // Paper start - Use TerminalConsoleAppender + if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - has no GUI or has console (did not double-click) new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start(); /* jline.console.ConsoleReader bufferedreader = reader; @@ -218,9 +219,19 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface thread.start(); // Paper - start console thread after MinecraftServer.console & PaperConfig are initialized io.papermc.paper.command.PaperCommands.registerCommands(this); com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); + // Purpur start + try { + org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings")); + } catch (Exception e) { + DedicatedServer.LOGGER.error("Unable to load server configuration", e); + return false; + } + org.purpurmc.purpur.PurpurConfig.registerCommands(); + // Purpur end com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider // Paper end + gg.pufferfish.pufferfish.PufferfishConfig.pufferfishFile = (java.io.File) options.valueOf("pufferfish-settings"); // Purpur gg.pufferfish.pufferfish.PufferfishConfig.load(); // Pufferfish gg.pufferfish.pufferfish.PufferfishCommand.init(); // Pufferfish @@ -269,6 +280,30 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface DedicatedServer.LOGGER.warn("Perhaps a server is already running on that port?"); return false; } + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.useUPnP) { + LOGGER.info("[UPnP] Attempting to start UPnP port forwarding service..."); + if (dev.omega24.upnp4j.UPnP4J.isUPnPAvailable()) { + if (dev.omega24.upnp4j.UPnP4J.isOpen(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) { + this.upnp = false; + LOGGER.info("[UPnP] Port {} is already open", this.getPort()); + } else if (dev.omega24.upnp4j.UPnP4J.open(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) { + this.upnp = true; + LOGGER.info("[UPnP] Successfully opened port {}", this.getPort()); + } else { + this.upnp = false; + LOGGER.info("[UPnP] Failed to open port {}", this.getPort()); + } + + if (upnp) { + LOGGER.info("[UPnP] {}:{}", dev.omega24.upnp4j.UPnP4J.getExternalIP(), this.getPort()); + } + } else { + this.upnp = false; + LOGGER.error("[UPnP] Service is unavailable"); + } + } + // Purpur end // CraftBukkit start // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up @@ -341,6 +376,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface } if (gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) mobSpawnExecutor.start(); // Pufferfish + org.purpurmc.purpur.task.BossBarTask.startAll(); // Purpur + org.purpurmc.purpur.task.BeehiveTask.instance().register(); // Purpur return true; } } @@ -487,7 +524,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface } public void handleConsoleInputs() { - MinecraftTimings.serverCommandTimer.startTiming(); // Spigot + //MinecraftTimings.serverCommandTimer.startTiming(); // Spigot // Purpur // Paper start - use proper queue ConsoleInput servercommand; while ((servercommand = this.serverCommandQueue.poll()) != null) { @@ -504,7 +541,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface // CraftBukkit end } - MinecraftTimings.serverCommandTimer.stopTiming(); // Spigot + //MinecraftTimings.serverCommandTimer.stopTiming(); // Spigot // Purpur } @Override diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java index 818289e831e3dad29345c43265e2efd7689bc500..1ea3012995c738c67b31e997c138f824f9e69ba1 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java @@ -58,6 +58,7 @@ public class DedicatedServerProperties extends Settings finalizers = Lists.newArrayList(); final AtomicBoolean isClosing = new AtomicBoolean(); + // Purpur start + private final CommandHistory history = new CommandHistory(); + private String currentCommand = ""; + private int historyIndex = 0; + // Purpur end public static MinecraftServerGui showFrameFor(final DedicatedServer server) { try { @@ -51,7 +56,7 @@ public class MinecraftServerGui extends JComponent { ; } - final JFrame jframe = new JFrame("Minecraft server"); + final JFrame jframe = new JFrame("Purpur Minecraft server"); // Purpur final MinecraftServerGui servergui = new MinecraftServerGui(server); jframe.setDefaultCloseOperation(2); @@ -59,7 +64,7 @@ public class MinecraftServerGui extends JComponent { jframe.pack(); jframe.setLocationRelativeTo((Component) null); jframe.setVisible(true); - jframe.setName("Minecraft server"); // Paper + jframe.setName("Purpur Minecraft server"); // Paper // Purpur // Paper start - Add logo as frame image try { @@ -71,7 +76,7 @@ public class MinecraftServerGui extends JComponent { jframe.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent windowevent) { if (!servergui.isClosing.getAndSet(true)) { - jframe.setTitle("Minecraft server - shutting down!"); + jframe.setTitle("Purpur Minecraft server - shutting down!"); // Purpur server.halt(true); servergui.runFinalizers(); } @@ -125,7 +130,7 @@ public class MinecraftServerGui extends JComponent { private JComponent buildChatPanel() { JPanel jpanel = new JPanel(new BorderLayout()); - JTextArea jtextarea = new JTextArea(); + org.purpurmc.purpur.gui.JColorTextPane jtextarea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur JScrollPane jscrollpane = new JScrollPane(jtextarea, 22, 30); jtextarea.setEditable(false); @@ -137,10 +142,43 @@ public class MinecraftServerGui extends JComponent { if (!s.isEmpty()) { this.server.handleConsoleInput(s, this.server.createCommandSourceStack()); + // Purpur start + history.add(s); + historyIndex = -1; + // Purpur end } jtextfield.setText(""); }); + // Purpur start + jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up"); + jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down"); + jtextfield.getActionMap().put("up", new javax.swing.AbstractAction() { + @Override + public void actionPerformed(java.awt.event.ActionEvent actionEvent) { + if (historyIndex < 0) { + currentCommand = jtextfield.getText(); + } + if (historyIndex < history.size() - 1) { + jtextfield.setText(history.get(historyIndex)); + } + } + }); + jtextfield.getActionMap().put("down", new javax.swing.AbstractAction() { + @Override + public void actionPerformed(java.awt.event.ActionEvent actionEvent) { + if (historyIndex >= 0) { + if (historyIndex == 0) { + --historyIndex; + jtextfield.setText(currentCommand); + } else { + --historyIndex; + jtextfield.setText(history.get(historyIndex)); + } + } + } + }); + // Purpur end jtextarea.addFocusListener(new FocusAdapter() { public void focusGained(FocusEvent focusevent) {} }); @@ -176,7 +214,7 @@ public class MinecraftServerGui extends JComponent { } private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\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); @@ -190,11 +228,14 @@ public class MinecraftServerGui extends JComponent { flag = (double) jscrollbar.getValue() + jscrollbar.getSize().getHeight() + (double) (MinecraftServerGui.MONOSPACED.getSize() * 4) > (double) jscrollbar.getMaximum(); } + /* // Purpur try { document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(message).replaceAll(""), (AttributeSet) null); // CraftBukkit } catch (BadLocationException badlocationexception) { ; } + */ // Purpur + textArea.append(message); // Purpur if (flag) { jscrollbar.setValue(Integer.MAX_VALUE); @@ -202,4 +243,16 @@ public class MinecraftServerGui extends JComponent { } } + + // Purpur start + public static class CommandHistory extends java.util.LinkedList { + @Override + public boolean add(String command) { + if (size() > 1000) { + remove(); + } + return super.offerFirst(command); + } + } + // Purpur end } diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java index d60ae9f8d3afe3e996a66331ee510cfa5841388c..3a7c694ff0e248df3f4d168a5e2e883c0e3cb167 100644 --- a/src/main/java/net/minecraft/server/level/ChunkMap.java +++ b/src/main/java/net/minecraft/server/level/ChunkMap.java @@ -640,20 +640,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider } protected void tick(BooleanSupplier shouldKeepTicking) { - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); + //ProfilerFiller gameprofilerfiller = this.level.getProfiler(); // Purpur - try (Timing ignored = this.level.timings.poiUnload.startTiming()) { // Paper - gameprofilerfiller.push("poi"); + //try (Timing ignored = this.level.timings.poiUnload.startTiming()) { // Paper // Purpur + //gameprofilerfiller.push("poi"); // Purpur this.poiManager.tick(shouldKeepTicking); - } // Paper - gameprofilerfiller.popPush("chunk_unload"); + //} // Paper // Purpur + //gameprofilerfiller.popPush("chunk_unload"); // Purpur if (!this.level.noSave()) { - try (Timing ignored = this.level.timings.chunkUnload.startTiming()) { // Paper + //try (Timing ignored = this.level.timings.chunkUnload.startTiming()) { // Paper // Purpur this.processUnloads(shouldKeepTicking); - } // Paper + //} // Paper // Purpur } - gameprofilerfiller.pop(); + //gameprofilerfiller.pop(); // Purpur } public boolean hasWork() { @@ -1272,24 +1272,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider // Paper start - optimised tracker private final void processTrackQueue() { - this.level.timings.tracker1.startTiming(); + //this.level.timings.tracker1.startTiming(); // Purpur try { for (TrackedEntity tracker : this.entityMap.values()) { // update tracker entry tracker.updatePlayers(tracker.entity.getPlayersInTrackRange()); } } finally { - this.level.timings.tracker1.stopTiming(); + //this.level.timings.tracker1.stopTiming(); // Purpur } - this.level.timings.tracker2.startTiming(); + //this.level.timings.tracker2.startTiming(); // Purpur try { for (TrackedEntity tracker : this.entityMap.values()) { tracker.serverEntity.sendChanges(); } } finally { - this.level.timings.tracker2.stopTiming(); + //this.level.timings.tracker2.stopTiming(); Purpur } } // Paper end - optimised tracker @@ -1304,7 +1304,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider List list = Lists.newArrayList(); List list1 = this.level.players(); ObjectIterator objectiterator = this.entityMap.values().iterator(); - level.timings.tracker1.startTiming(); // Paper + //level.timings.tracker1.startTiming(); // Paper // Purpur ChunkMap.TrackedEntity playerchunkmap_entitytracker; @@ -1329,17 +1329,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider playerchunkmap_entitytracker.serverEntity.sendChanges(); } } - level.timings.tracker1.stopTiming(); // Paper + //level.timings.tracker1.stopTiming(); // Paper // Purpur if (!list.isEmpty()) { objectiterator = this.entityMap.values().iterator(); - level.timings.tracker2.startTiming(); // Paper + //level.timings.tracker2.startTiming(); // Paper // Purpur while (objectiterator.hasNext()) { playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next(); playerchunkmap_entitytracker.updatePlayers(list); } - level.timings.tracker2.stopTiming(); // Paper + //level.timings.tracker2.stopTiming(); // Paper // Purpur } } diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java index fe1a9c646b09d11e7fa2186afbeb70b680ad2b57..2d5b0f293bb30243e774bfb62b8100c01e7eb383 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -280,16 +280,16 @@ public class ServerChunkCache extends ChunkSource { return ifLoaded; } // Paper end - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); + //ProfilerFiller gameprofilerfiller = this.level.getProfiler(); // Purpur - gameprofilerfiller.incrementCounter("getChunk"); + //gameprofilerfiller.incrementCounter("getChunk"); // Purpur long k = ChunkPos.asLong(x, z); ChunkAccess ichunkaccess; // Paper - rewrite chunk system - there are no correct callbacks to remove items from cache in the new chunk system - gameprofilerfiller.incrementCounter("getChunkCacheMiss"); + //gameprofilerfiller.incrementCounter("getChunkCacheMiss"); // Purpur CompletableFuture> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create, true); // Paper ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor; @@ -299,10 +299,10 @@ public class ServerChunkCache extends ChunkSource { io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.pushChunkWait(this.level, x1, z1); // Paper - rewrite chunk system // Paper end com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info - this.level.timings.syncChunkLoad.startTiming(); // Paper + //this.level.timings.syncChunkLoad.startTiming(); // Paper // Purpur chunkproviderserver_b.managedBlock(completablefuture::isDone); io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - async chunk debug // Paper - rewrite chunk system - this.level.timings.syncChunkLoad.stopTiming(); // Paper + //this.level.timings.syncChunkLoad.stopTiming(); // Paper // Purpur } // Paper ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> { return ichunkaccess1; @@ -451,17 +451,17 @@ public class ServerChunkCache extends ChunkSource { public void save(boolean flush) { this.runDistanceManagerUpdates(); - try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings + //try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings // Purpur this.chunkMap.saveAllChunks(flush); - } // Paper - Timings + //} // Paper - Timings // Purpur } // Paper start - duplicate save, but call incremental public void saveIncrementally() { this.runDistanceManagerUpdates(); - try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings + //try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings // Purpur this.chunkMap.saveIncrementally(); - } // Paper - Timings + //} // Paper - Timings // Purpur } // Paper end @@ -478,36 +478,36 @@ public class ServerChunkCache extends ChunkSource { // CraftBukkit start - modelled on below public void purgeUnload() { if (true) return; // Paper - tickets will be removed later, this behavior isn't really well accounted for by the chunk system - this.level.getProfiler().push("purge"); + //this.level.getProfiler().push("purge"); // Purpur this.distanceManager.purgeStaleTickets(); this.runDistanceManagerUpdates(); - this.level.getProfiler().popPush("unload"); + //this.level.getProfiler().popPush("unload"); // Purpur this.chunkMap.tick(() -> true); - this.level.getProfiler().pop(); + //this.level.getProfiler().pop(); // Purpur this.clearCache(); } // CraftBukkit end @Override public void tick(BooleanSupplier shouldKeepTicking, boolean tickChunks) { - this.level.getProfiler().push("purge"); - this.level.timings.doChunkMap.startTiming(); // Spigot + //this.level.getProfiler().push("purge"); // Purpur + //this.level.timings.doChunkMap.startTiming(); // Spigot // Purpur this.distanceManager.purgeStaleTickets(); this.runDistanceManagerUpdates(); - this.level.timings.doChunkMap.stopTiming(); // Spigot - this.level.getProfiler().popPush("chunks"); + //this.level.timings.doChunkMap.stopTiming(); // Spigot // Purpur + //this.level.getProfiler().popPush("chunks"); // Purpur if (tickChunks) { - this.level.timings.chunks.startTiming(); // Paper - timings + //this.level.timings.chunks.startTiming(); // Paper - timings // Purpur this.chunkMap.level.playerChunkLoader.tick(); // Paper - replace player chunk loader - this is mostly required to account for view distance changes this.tickChunks(); - this.level.timings.chunks.stopTiming(); // Paper - timings + //this.level.timings.chunks.stopTiming(); // Paper - timings // Purpur } - this.level.timings.doChunkUnload.startTiming(); // Spigot - this.level.getProfiler().popPush("unload"); + //this.level.timings.doChunkUnload.startTiming(); // Spigot // Purpur + //this.level.getProfiler().popPush("unload"); // Purpur this.chunkMap.tick(shouldKeepTicking); - this.level.timings.doChunkUnload.stopTiming(); // Spigot - this.level.getProfiler().pop(); + //this.level.timings.doChunkUnload.stopTiming(); // Spigot // Purpur + //this.level.getProfiler().pop(); // Purpur this.clearCache(); } @@ -553,15 +553,15 @@ public class ServerChunkCache extends ChunkSource { } // Paper end - optimize isOutisdeRange LevelData worlddata = this.level.getLevelData(); - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); + //ProfilerFiller gameprofilerfiller = this.level.getProfiler(); // Purpur - gameprofilerfiller.push("pollingChunks"); + //gameprofilerfiller.push("pollingChunks"); // Purpur this.level.resetIceAndSnowTick(); // Pufferfish - reset ice & snow tick random int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); boolean flag1 = level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && worlddata.getGameTime() % level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit - gameprofilerfiller.push("naturalSpawnCount"); - this.level.timings.countNaturalMobs.startTiming(); // Paper - timings + //gameprofilerfiller.push("naturalSpawnCount"); // Purpur + //this.level.timings.countNaturalMobs.startTiming(); // Paper - timings // Purpur int l = this.distanceManager.getNaturalSpawnChunkCount(); // Paper start - per player mob spawning NaturalSpawner.SpawnState spawnercreature_d; // moved down @@ -592,16 +592,16 @@ public class ServerChunkCache extends ChunkSource { // Pufferfish end } // Paper end - this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings + //this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings // Purpur //this.lastSpawnState = spawnercreature_d; // Pufferfish - this is managed asynchronously - gameprofilerfiller.popPush("filteringLoadedChunks"); + //gameprofilerfiller.popPush("filteringLoadedChunks"); // Purpur // Paper - moved down - this.level.timings.chunkTicks.startTiming(); // Paper + //this.level.timings.chunkTicks.startTiming(); // Paper // Purpur // Paper - moved down - gameprofilerfiller.popPush("spawnAndTick"); + //gameprofilerfiller.popPush("spawnAndTick"); // Purpur boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit // Paper - only shuffle if per-player mob spawning is disabled @@ -652,17 +652,17 @@ public class ServerChunkCache extends ChunkSource { } } // Paper end - optimise chunk tick iteration - this.level.timings.chunkTicks.stopTiming(); // Paper - gameprofilerfiller.popPush("customSpawners"); + //this.level.timings.chunkTicks.stopTiming(); // Paper // Purpur + //gameprofilerfiller.popPush("customSpawners"); // Purpur if (flag2) { - try (co.aikar.timings.Timing ignored = this.level.timings.miscMobSpawning.startTiming()) { // Paper - timings + //try (co.aikar.timings.Timing ignored = this.level.timings.miscMobSpawning.startTiming()) { // Paper - timings // Purpur this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies); - } // Paper - timings + //} // Paper - timings // Purpur } - gameprofilerfiller.pop(); + //gameprofilerfiller.pop(); // Purpur // Paper start - use set of chunks requiring updates, rather than iterating every single one loaded - gameprofilerfiller.popPush("broadcast"); - this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing + //gameprofilerfiller.popPush("broadcast"); // Purpur + //this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing // Purpur if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) { ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone(); this.chunkMap.needsChangeBroadcasting.clear(); @@ -674,8 +674,8 @@ public class ServerChunkCache extends ChunkSource { } } } - this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing - gameprofilerfiller.pop(); + //this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing // Purpur + //gameprofilerfiller.pop(); // Purpur // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded // Paper start - controlled flush for entity tracker packets List disabledFlushes = new java.util.ArrayList<>(this.level.players.size()); @@ -900,7 +900,7 @@ public class ServerChunkCache extends ChunkSource { @Override protected void doRunTask(Runnable task) { - ServerChunkCache.this.level.getProfiler().incrementCounter("runTask"); + //ServerChunkCache.this.level.getProfiler().incrementCounter("runTask"); // Purpur super.doRunTask(task); } diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java index 8ab959dd588b5154b63e133b2e937fa2d0ab8e52..fb11b020d52988360562db23d8568e5ef37e21c8 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -70,7 +70,7 @@ public class ServerEntity { @Nullable private List> trackedDataValues; // CraftBukkit start - final Set trackedPlayers; // Paper - private -> package + public final Set trackedPlayers; // Paper - private -> package // Purpur - package -> public public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer> consumer, Set trackedPlayers) { this.trackedPlayers = trackedPlayers; diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index eac31c3fcc9161711328588ac852fcae1116d8ef..221d1d0e1b4b46de6ebca5faac09bbda875fae17 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -214,6 +214,8 @@ public class ServerLevel extends Level implements WorldGenLevel { private final StructureManager structureManager; private final StructureCheck structureCheck; private final boolean tickTime; + private double preciseTime; // Purpur + private boolean forceTime; // Purpur private final RandomSequences randomSequences; public long lastMidTickExecuteFailure; // Paper - execute chunk tasks mid tick @@ -223,6 +225,7 @@ public class ServerLevel extends Level implements WorldGenLevel { public boolean hasPhysicsEvent = true; // Paper public boolean hasEntityMoveEvent = false; // Paper private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) + public boolean hasRidableMoveEvent = false; // Purpur public static Throwable getAddToWorldStackTrace(Entity entity) { final Throwable thr = new Throwable(entity + " Added to world at " + new java.util.Date()); io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(thr); @@ -662,7 +665,24 @@ public class ServerLevel extends Level implements WorldGenLevel { this.dragonParts = new Int2ObjectOpenHashMap(); this.tickTime = flag1; this.server = minecraftserver; - this.customSpawners = list; + // Purpur start - enable/disable MobSpawners per world + this.customSpawners = Lists.newArrayList(); + if (purpurConfig.phantomSpawning) { + customSpawners.add(new net.minecraft.world.level.levelgen.PhantomSpawner()); + } + if (purpurConfig.patrolSpawning) { + customSpawners.add(new net.minecraft.world.level.levelgen.PatrolSpawner()); + } + if (purpurConfig.catSpawning) { + customSpawners.add(new net.minecraft.world.entity.npc.CatSpawner()); + } + if (purpurConfig.villageSiegeSpawning) { + customSpawners.add(new net.minecraft.world.entity.ai.village.VillageSiege()); + } + if (purpurConfig.villagerTraderSpawning) { + customSpawners.add(new net.minecraft.world.entity.npc.WanderingTraderSpawner(iworlddataserver)); + } + // Purpur end this.serverLevelData = iworlddataserver; ChunkGenerator chunkgenerator = worlddimension.generator(); // CraftBukkit start @@ -732,6 +752,7 @@ public class ServerLevel extends Level implements WorldGenLevel { this.chunkTaskScheduler = new io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler(this, io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.workerThreads); // Paper - rewrite chunk system this.entityLookup = new io.papermc.paper.chunk.system.entity.EntityLookup(this, new EntityCallbacks()); // Paper - rewrite chunk system + this.preciseTime = this.serverLevelData.getDayTime(); // Purpur } // Paper start @@ -774,17 +795,17 @@ public class ServerLevel extends Level implements WorldGenLevel { } } // Paper end - optimise checkDespawn - ProfilerFiller gameprofilerfiller = this.getProfiler(); + //ProfilerFiller gameprofilerfiller = this.getProfiler(); // Purpur this.handlingTick = true; - gameprofilerfiller.push("world border"); + //gameprofilerfiller.push("world border"); // Purpur this.getWorldBorder().tick(); - gameprofilerfiller.popPush("weather"); + //gameprofilerfiller.popPush("weather"); // Purpur this.advanceWeatherCycle(); 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()); @@ -806,32 +827,32 @@ public class ServerLevel extends Level implements WorldGenLevel { this.updateSkyBrightness(); this.tickTime(); - gameprofilerfiller.popPush("tickPending"); - timings.scheduledBlocks.startTiming(); // Paper + //gameprofilerfiller.popPush("tickPending"); // Purpur + //timings.scheduledBlocks.startTiming(); // Paper // Purpur if (!this.isDebug()) { j = this.getGameTime(); - gameprofilerfiller.push("blockTicks"); + //gameprofilerfiller.push("blockTicks"); // Purpur this.blockTicks.tick(j, 65536, this::tickBlock); - gameprofilerfiller.popPush("fluidTicks"); + //gameprofilerfiller.popPush("fluidTicks"); // Purpur this.fluidTicks.tick(j, 65536, this::tickFluid); - gameprofilerfiller.pop(); + //gameprofilerfiller.pop(); // Purpur } - timings.scheduledBlocks.stopTiming(); // Paper + //timings.scheduledBlocks.stopTiming(); // Paper // Purpur - gameprofilerfiller.popPush("raid"); - this.timings.raids.startTiming(); // Paper - timings + //gameprofilerfiller.popPush("raid"); // Purpur + //this.timings.raids.startTiming(); // Paper - timings // Purpur this.raids.tick(); - this.timings.raids.stopTiming(); // Paper - timings - gameprofilerfiller.popPush("chunkSource"); - this.timings.chunkProviderTick.startTiming(); // Paper - timings + //this.timings.raids.stopTiming(); // Paper - timings // Purpur + //gameprofilerfiller.popPush("chunkSource"); // Purpur + //this.timings.chunkProviderTick.startTiming(); // Paper - timings // Purpur this.getChunkSource().tick(shouldKeepTicking, true); - this.timings.chunkProviderTick.stopTiming(); // Paper - timings - gameprofilerfiller.popPush("blockEvents"); - timings.doSounds.startTiming(); // Spigot + //this.timings.chunkProviderTick.stopTiming(); // Paper - timings // Purpur + //gameprofilerfiller.popPush("blockEvents"); // Purpur + //timings.doSounds.startTiming(); // Spigot // Purpur this.runBlockEvents(); - timings.doSounds.stopTiming(); // Spigot + //timings.doSounds.stopTiming(); // Spigot // Purpur this.handlingTick = false; - gameprofilerfiller.pop(); + //gameprofilerfiller.pop(); // Purpur boolean flag = true || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players if (flag) { @@ -839,25 +860,25 @@ public class ServerLevel extends Level implements WorldGenLevel { } if (flag || this.emptyTime++ < 300) { - gameprofilerfiller.push("entities"); - timings.tickEntities.startTiming(); // Spigot + //gameprofilerfiller.push("entities"); // Purpur + //timings.tickEntities.startTiming(); // Spigot // Purpur if (this.dragonFight != null) { - gameprofilerfiller.push("dragonFight"); + //gameprofilerfiller.push("dragonFight"); // Purpur this.dragonFight.tick(); - gameprofilerfiller.pop(); + //gameprofilerfiller.pop(); // Purpur } org.spigotmc.ActivationRange.activateEntities(this); // Spigot - timings.entityTick.startTiming(); // Spigot + //timings.entityTick.startTiming(); // Spigot // Purpur this.entityTickList.forEach((entity) -> { entity.activatedPriorityReset = false; // Pufferfish - DAB if (!entity.isRemoved()) { if (false && this.shouldDiscardEntity(entity)) { // CraftBukkit - We prevent spawning in general, so this butchering is not needed entity.discard(); } else { - gameprofilerfiller.push("checkDespawn"); + //gameprofilerfiller.push("checkDespawn"); // Purpur entity.checkDespawn(); - gameprofilerfiller.pop(); + //gameprofilerfiller.pop(); // Purpur if (true || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { // Paper - now always true if in the ticking list Entity entity1 = entity.getVehicle(); @@ -869,7 +890,7 @@ public class ServerLevel extends Level implements WorldGenLevel { entity.stopRiding(); } - gameprofilerfiller.push("tick"); + //gameprofilerfiller.push("tick"); // Purpur // Pufferfish start - copied from this.guardEntityTick try { this.tickNonPassenger(entity); // Pufferfish - changed @@ -884,20 +905,19 @@ public class ServerLevel extends Level implements WorldGenLevel { // Paper end } // Pufferfish end - gameprofilerfiller.pop(); + //gameprofilerfiller.pop(); // Purpur } } } }); - timings.entityTick.stopTiming(); // Spigot - timings.tickEntities.stopTiming(); // Spigot - gameprofilerfiller.pop(); + //timings.entityTick.stopTiming(); // Spigot // Purpur + //timings.tickEntities.stopTiming(); // Spigot // Purpur + //gameprofilerfiller.pop(); // Purpur this.tickBlockEntities(); } - gameprofilerfiller.push("entityManagement"); + //gameprofilerfiller.push("entityManagement"); // Purpur //this.entityManager.tick(); // Paper - rewrite chunk system - gameprofilerfiller.pop(); } @Override @@ -915,6 +935,13 @@ public class ServerLevel extends Level implements WorldGenLevel { this.serverLevelData.setGameTime(i); this.serverLevelData.getScheduledEvents().tick(this.server, i); if (this.levelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { + // Purpur start + int incrementTicks = isDay() ? this.purpurConfig.daytimeTicks : this.purpurConfig.nighttimeTicks; + if (incrementTicks != 12000) { + this.preciseTime += 12000 / (double) incrementTicks; + this.setDayTime(this.preciseTime); + } else + // Purpur end this.setDayTime(this.levelData.getDayTime() + 1L); } @@ -923,7 +950,21 @@ public class ServerLevel extends Level implements WorldGenLevel { public void setDayTime(long timeOfDay) { this.serverLevelData.setDayTime(timeOfDay); + // Purpur start + this.preciseTime = timeOfDay; + this.forceTime = false; } + public void setDayTime(double i) { + this.serverLevelData.setDayTime((long) i); + this.forceTime = true; + // Purpur end + } + + // Purpur start + public boolean isForceTime() { + return this.forceTime; + } + // Purpur end public void tickCustomSpawners(boolean spawnMonsters, boolean spawnAnimals) { Iterator iterator = this.customSpawners.iterator(); @@ -948,7 +989,7 @@ public class ServerLevel extends Level implements WorldGenLevel { } // Paper start - optimise random block ticking private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos(); - // private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(); // Pufferfish - moved to super + private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(this.random.nextLong()); public net.minecraft.util.RandomSource getThreadUnsafeRandom() { return this.randomTickRandom; } // Pufferfish - moved to super // Purpur - dont break ABI // Paper end private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.randomTickRandom.nextInt(16); } // Pufferfish @@ -958,9 +999,9 @@ public class ServerLevel extends Level implements WorldGenLevel { boolean flag = this.isRaining(); int j = chunkcoordintpair.getMinBlockX(); int k = chunkcoordintpair.getMinBlockZ(); - ProfilerFiller gameprofilerfiller = this.getProfiler(); + //ProfilerFiller gameprofilerfiller = this.getProfiler(); // Purpur - gameprofilerfiller.push("thunder"); + //gameprofilerfiller.push("thunder"); // Purpur final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && /*this.random.nextInt(this.spigotConfig.thunderChance) == 0 &&*/ chunk.shouldDoLightning(this.random)) { // Spigot // Paper - disable thunder // Pufferfish - replace random with shouldDoLightning @@ -970,10 +1011,18 @@ public class ServerLevel extends Level implements WorldGenLevel { boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper if (flag1) { - SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this); + // Purpur start + net.minecraft.world.entity.animal.horse.AbstractHorse entityhorseskeleton; + if (purpurConfig.zombieHorseSpawnChance > 0D && random.nextDouble() <= purpurConfig.zombieHorseSpawnChance) { + entityhorseskeleton = EntityType.ZOMBIE_HORSE.create(this); + } else { + entityhorseskeleton = EntityType.SKELETON_HORSE.create(this); + if (entityhorseskeleton != null) ((SkeletonHorse) entityhorseskeleton).setTrap(true); + } + // Purpur end if (entityhorseskeleton != null) { - entityhorseskeleton.setTrap(true); + //entityhorseskeleton.setTrap(true); // Purpur - moved up entityhorseskeleton.setAge(0); entityhorseskeleton.setPos((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()); this.addFreshEntity(entityhorseskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit @@ -990,7 +1039,7 @@ public class ServerLevel extends Level implements WorldGenLevel { } } - gameprofilerfiller.popPush("iceandsnow"); + //gameprofilerfiller.popPush("iceandsnow"); // Purpur int l; int i1; @@ -1042,8 +1091,8 @@ public class ServerLevel extends Level implements WorldGenLevel { } // Paper start - optimise random block ticking - gameprofilerfiller.popPush("randomTick"); - timings.chunkTicksBlocks.startTiming(); // Paper + //gameprofilerfiller.popPush("randomTick"); // Purpur + //timings.chunkTicksBlocks.startTiming(); // Paper // Purpur if (randomTickSpeed > 0) { LevelChunkSection[] sections = chunk.getSections(); int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this); @@ -1077,8 +1126,8 @@ public class ServerLevel extends Level implements WorldGenLevel { } } // Paper end - optimise random block ticking - timings.chunkTicksBlocks.stopTiming(); // Paper - gameprofilerfiller.pop(); + //timings.chunkTicksBlocks.stopTiming(); // Paper // Purpur + //gameprofilerfiller.pop(); // Purpur } public Optional findLightningRod(BlockPos pos) { @@ -1086,7 +1135,7 @@ public class ServerLevel extends Level implements WorldGenLevel { return holder.is(PoiTypes.LIGHTNING_ROD); }, (blockposition1) -> { return blockposition1.getY() == this.getHeight(Heightmap.Types.WORLD_SURFACE, blockposition1.getX(), blockposition1.getZ()) - 1; - }, pos, 128, PoiManager.Occupancy.ANY); + }, pos, org.purpurmc.purpur.PurpurConfig.lightningRodRange, PoiManager.Occupancy.ANY); return optional.map((blockposition1) -> { return blockposition1.above(1); @@ -1135,11 +1184,27 @@ public class ServerLevel extends Level implements WorldGenLevel { if (this.canSleepThroughNights()) { if (!this.getServer().isSingleplayer() || this.getServer().isPublished()) { int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); - MutableComponent ichatmutablecomponent; + Component ichatmutablecomponent; if (this.sleepStatus.areEnoughSleeping(i)) { + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.isBlank()) { + return; + } + if (!org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.equalsIgnoreCase("default")) { + ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepSkippingNight)); + } else ichatmutablecomponent = Component.translatable("sleep.skipping_night"); } else { + if (org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.isBlank()) { + return; + } + if (!org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.equalsIgnoreCase("default")) { + ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent, + net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("count", Integer.toString(this.sleepStatus.amountSleeping())), + net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("total", Integer.toString(this.sleepStatus.sleepersNeeded(i))))); + } else + // Purpur end ichatmutablecomponent = Component.translatable("sleep.players_sleeping", this.sleepStatus.amountSleeping(), this.sleepStatus.sleepersNeeded(i)); } @@ -1278,6 +1343,7 @@ public class ServerLevel extends Level implements WorldGenLevel { private void resetWeatherCycle() { // CraftBukkit start + if (this.purpurConfig.rainStopsAfterSleep) // Purpur this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - when passing the night // If we stop due to everyone sleeping we should reset the weather duration to some other random value. // Not that everyone ever manages to get the whole server to sleep at the same time.... @@ -1285,6 +1351,7 @@ public class ServerLevel extends Level implements WorldGenLevel { this.serverLevelData.setRainTime(0); } // CraftBukkit end + if (this.purpurConfig.thunderStopsAfterSleep) // Purpur this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - when passing the night // CraftBukkit start // If we stop due to everyone sleeping we should reset the weather duration to some other random value. @@ -1352,24 +1419,24 @@ public class ServerLevel extends Level implements WorldGenLevel { // Spigot end // Paper start- timings final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity); - timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper - try { + //timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper // Purpur + //try { // Purpur // Paper end - timings entity.setOldPosAndRot(); - ProfilerFiller gameprofilerfiller = this.getProfiler(); + //ProfilerFiller gameprofilerfiller = this.getProfiler(); // Purpur ++entity.tickCount; - this.getProfiler().push(() -> { + /*this.getProfiler().push(() -> { // Purpur return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString(); - }); - gameprofilerfiller.incrementCounter("tickNonPassenger"); + });*/ // Purpur + //gameprofilerfiller.incrementCounter("tickNonPassenger"); // Purpur if (isActive) { // Paper - EAR 2 TimingHistory.activatedEntityTicks++; entity.tick(); entity.postTick(); // CraftBukkit } else { entity.inactiveTick(); } // Paper - EAR 2 - this.getProfiler().pop(); - } finally { timer.stopTiming(); } // Paper - timings + //this.getProfiler().pop(); // Purpur + //} finally { timer.stopTiming(); } // Paper - timings // Purpur Iterator iterator = entity.getPassengers().iterator(); while (iterator.hasNext()) { @@ -1392,17 +1459,17 @@ public class ServerLevel extends Level implements WorldGenLevel { if (passenger instanceof Player || this.entityTickList.contains(passenger)) { // Paper - EAR 2 final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(passenger); - co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper - try { + //co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper // Purpur + //try { // Purpur // Paper end passenger.setOldPosAndRot(); ++passenger.tickCount; - ProfilerFiller gameprofilerfiller = this.getProfiler(); + //ProfilerFiller gameprofilerfiller = this.getProfiler(); // Purpur - gameprofilerfiller.push(() -> { + /*gameprofilerfiller.push(() -> { // Purpur return BuiltInRegistries.ENTITY_TYPE.getKey(passenger.getType()).toString(); - }); - gameprofilerfiller.incrementCounter("tickPassenger"); + });*/ // Purpur + //gameprofilerfiller.incrementCounter("tickPassenger"); // Purpur // Paper start - EAR 2 if (isActive) { passenger.rideTick(); @@ -1414,7 +1481,7 @@ public class ServerLevel extends Level implements WorldGenLevel { vehicle.positionRider(passenger); } // Paper end - EAR 2 - gameprofilerfiller.pop(); + //gameprofilerfiller.pop(); // Purpur Iterator iterator = passenger.getPassengers().iterator(); while (iterator.hasNext()) { @@ -1423,7 +1490,7 @@ public class ServerLevel extends Level implements WorldGenLevel { this.tickPassenger(passenger, entity2); } - } finally { timer.stopTiming(); }// Paper - EAR2 timings + //} finally { timer.stopTiming(); }// Paper - EAR2 timings // Purpur } } else { passenger.stopRiding(); @@ -1443,14 +1510,14 @@ public class ServerLevel extends Level implements WorldGenLevel { org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); } - try (co.aikar.timings.Timing ignored = this.timings.worldSave.startTiming()) { + //try (co.aikar.timings.Timing ignored = this.timings.worldSave.startTiming()) { // Purpur if (doFull) { this.saveLevelData(); } - this.timings.worldSaveChunks.startTiming(); // Paper + //this.timings.worldSaveChunks.startTiming(); // Paper // Purpur if (!this.noSave()) chunkproviderserver.saveIncrementally(); - this.timings.worldSaveChunks.stopTiming(); // Paper + //this.timings.worldSaveChunks.stopTiming(); // Paper // Purpur // Copied from save() // CraftBukkit start - moved from MinecraftServer.saveChunks @@ -1462,7 +1529,7 @@ public class ServerLevel extends Level implements WorldGenLevel { this.convertable.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData()); } // CraftBukkit end - } + //} // Purpur } // Paper end @@ -1476,7 +1543,7 @@ public class ServerLevel extends Level implements WorldGenLevel { if (!savingDisabled) { org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit - try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper + //try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper // Purpur // Purpur if (progressListener != null) { progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel")); } @@ -1486,11 +1553,11 @@ public class ServerLevel extends Level implements WorldGenLevel { progressListener.progressStage(Component.translatable("menu.savingChunks")); } - timings.worldSaveChunks.startTiming(); // Paper + //timings.worldSaveChunks.startTiming(); // Paper // Purpur if (!close) chunkproviderserver.save(flush); // Paper - rewrite chunk system if (close) chunkproviderserver.close(true); // Paper - rewrite chunk system - timings.worldSaveChunks.stopTiming(); // Paper - }// Paper + //timings.worldSaveChunks.stopTiming(); // Paper // Purpur + //}// Paper // Purpur // Paper - rewrite chunk system - entity saving moved into ChunkHolder } else if (close) { chunkproviderserver.close(false); } // Paper - rewrite chunk system @@ -2769,7 +2836,7 @@ public class ServerLevel extends Level implements WorldGenLevel { // Spigot Start if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message // Paper start - if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { + if (!entity.level().purpurConfig.playerVoidTrading && entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { // Purpur merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); } // Paper end diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java index b382da838acc04a1c5d89064b4fa43bcdd38ae71..0249eeb3937cf48cea13846a7e39b248947e21a4 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -278,6 +278,11 @@ public class ServerPlayer extends Player { public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - there are a lot of changes to do if we change all methods leading to the event + public boolean purpurClient = false; // Purpur + public boolean acceptingResourcePack = false; // Purpur + private boolean ramBar = false; // Purpur + private boolean tpsBar = false; // Purpur + private boolean compassBar = false; // Purpur private final java.util.concurrent.atomic.AtomicReference viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1)); public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; @@ -418,6 +423,7 @@ public class ServerPlayer extends Player { this.bukkitPickUpLoot = true; this.maxHealthCache = this.getMaxHealth(); this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper + this.spawnInvulnerableTime = world.purpurConfig.playerSpawnInvulnerableTicks; // Purpur } // Yes, this doesn't match Vanilla, but it's the best we can do for now. @@ -557,6 +563,9 @@ public class ServerPlayer extends Player { } } + if (nbt.contains("Purpur.RamBar")) { this.ramBar = nbt.getBoolean("Purpur.RamBar"); } // Purpur + if (nbt.contains("Purpur.TPSBar")) { this.tpsBar = nbt.getBoolean("Purpur.TPSBar"); } // Purpur + if (nbt.contains("Purpur.CompassBar")) { this.compassBar = nbt.getBoolean("Purpur.CompassBar"); } // Purpur } @Override @@ -623,6 +632,9 @@ public class ServerPlayer extends Player { } this.getBukkitEntity().setExtraData(nbt); // CraftBukkit + nbt.putBoolean("Purpur.RamBar", this.ramBar); // Purpur + nbt.putBoolean("Purpur.TPSBar", this.tpsBar); // Purpur + nbt.putBoolean("Purpur.CompassBar", this.compassBar); // Purpur } // CraftBukkit start - World fallback code, either respawn location or global spawn @@ -751,6 +763,15 @@ public class ServerPlayer extends Player { this.trackStartFallingPosition(); this.trackEnteredOrExitedLavaOnVehicle(); this.advancements.flushDirty(this); + + // Purpur start + if (this.level().purpurConfig.useNightVisionWhenRiding && this.getVehicle() != null && this.getVehicle().getRider() == this && this.level().getGameTime() % 100 == 0) { // 5 seconds + MobEffectInstance nightVision = this.getEffect(MobEffects.NIGHT_VISION); + if (nightVision == null || nightVision.getDuration() <= 300) { // 15 seconds + this.addEffect(new MobEffectInstance(MobEffects.NIGHT_VISION, 400, 0)); // 20 seconds + } + } + // Purpur end } public void doTick() { @@ -989,6 +1010,7 @@ public class ServerPlayer extends Player { })); Team scoreboardteambase = this.getTeam(); + if (org.purpurmc.purpur.PurpurConfig.deathMessageOnlyBroadcastToAffectedPlayer) this.sendSystemMessage(ichatbasecomponent); else // Purpur if (scoreboardteambase != null && scoreboardteambase.getDeathMessageVisibility() != Team.Visibility.ALWAYS) { if (scoreboardteambase.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) { this.server.getPlayerList().broadcastSystemToTeam(this, ichatbasecomponent); @@ -1090,14 +1112,30 @@ public class ServerPlayer extends Player { } + // Purpur start + public boolean isSpawnInvulnerable() { + return spawnInvulnerableTime > 0 || frozen; + } + // Purpur end + @Override public boolean hurt(DamageSource source, float amount) { if (this.isInvulnerableTo(source)) { return false; } else { + // Purpur start + if (source.is(DamageTypeTags.IS_FALL)) { // Purpur + if (getRootVehicle() instanceof net.minecraft.world.entity.vehicle.AbstractMinecart && level().purpurConfig.minecartControllable && !level().purpurConfig.minecartControllableFallDamage) { + return false; + } + if (getRootVehicle() instanceof net.minecraft.world.entity.vehicle.Boat && !level().purpurConfig.boatsDoFallDamage) { + return false; + } + } + // Purpur end boolean flag = this.server.isDedicatedServer() && this.isPvpAllowed() && source.is(DamageTypeTags.IS_FALL); - if (!flag && this.spawnInvulnerableTime > 0 && !source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) { + if (!flag && isSpawnInvulnerable() && !source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) { // Purpur return false; } else { Entity entity = source.getEntity(); @@ -1206,7 +1244,7 @@ public class ServerPlayer extends Player { PortalInfo shapedetectorshape = this.findDimensionEntryPoint(worldserver); if (shapedetectorshape != null) { - worldserver1.getProfiler().push("moving"); + //worldserver1.getProfiler().push("moving"); // Purpur worldserver = shapedetectorshape.world; // CraftBukkit if (worldserver == null) { } else // CraftBukkit - empty to fall through to null to event if (resourcekey == LevelStem.OVERWORLD && worldserver.getTypeKey() == LevelStem.NETHER) { // CraftBukkit @@ -1229,8 +1267,8 @@ public class ServerPlayer extends Player { worldserver = ((CraftWorld) exit.getWorld()).getHandle(); // CraftBukkit end - worldserver1.getProfiler().pop(); - worldserver1.getProfiler().push("placing"); + //worldserver1.getProfiler().pop(); // Purpur + //worldserver1.getProfiler().push("placing"); // Purpur if (true) { // CraftBukkit this.isChangingDimension = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds @@ -1241,13 +1279,14 @@ public class ServerPlayer extends Player { playerlist.sendPlayerPermissionLevel(this); worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); this.unsetRemoved(); + this.portalPos = io.papermc.paper.util.MCUtil.toBlockPosition(exit); // Purpur // CraftBukkit end this.setServerLevel(worldserver); this.connection.teleport(exit); // CraftBukkit - use internal teleport without event this.connection.resetPosition(); worldserver.addDuringPortalTeleport(this); - worldserver1.getProfiler().pop(); + //worldserver1.getProfiler().pop(); // Purpur this.triggerDimensionChangeTriggers(worldserver1); this.connection.send(new ClientboundPlayerAbilitiesPacket(this.getAbilities())); playerlist.sendLevelInfo(this, worldserver); @@ -1276,6 +1315,7 @@ public class ServerPlayer extends Player { } // Paper end + this.spawnInvulnerableTime = worldserver.purpurConfig.playerSpawnInvulnerableTicks; // Purpur return this; } } @@ -1397,7 +1437,7 @@ public class ServerPlayer extends Player { return entitymonster.isPreventingPlayerRest(this); }); - if (!list.isEmpty()) { + if (!this.level().purpurConfig.playerSleepNearMonsters && !list.isEmpty()) { // Purpur return Either.left(Player.BedSleepingProblem.NOT_SAFE); } } @@ -1437,7 +1477,19 @@ public class ServerPlayer extends Player { }); 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(); @@ -1534,6 +1586,7 @@ public class ServerPlayer extends Player { @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)); } @@ -1779,6 +1832,26 @@ public class ServerPlayer extends Player { this.lastSentExp = -1; // CraftBukkit - Added to reset } + // Purpur start + public void sendActionBarMessage(@Nullable String message) { + if (message != null && !message.isEmpty()) { + sendActionBarMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message)); + } + } + + public void sendActionBarMessage(@Nullable net.kyori.adventure.text.Component message) { + if (message != null) { + sendActionBarMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message)); + } + } + + public void sendActionBarMessage(@Nullable Component message) { + if (message != null) { + displayClientMessage(message, true); + } + } + // Purpur end + @Override public void displayClientMessage(Component message, boolean overlay) { this.sendSystemMessage(message, overlay); @@ -2078,6 +2151,7 @@ public class ServerPlayer extends Player { } public void sendTexturePack(String url, String hash, boolean required, @Nullable Component resourcePackPrompt) { + this.acceptingResourcePack = true; // Purpur this.connection.send(new ClientboundResourcePackPacket(url, hash, required, resourcePackPrompt)); } @@ -2092,8 +2166,68 @@ public class ServerPlayer extends Player { public void resetLastActionTime() { this.lastActionTime = Util.getMillis(); + this.setAfk(false); // Purpur + } + + // Purpur Start + private boolean isAfk = false; + + @Override + public void setAfk(boolean afk) { + if (this.isAfk == afk) { + return; + } + + String msg = afk ? org.purpurmc.purpur.PurpurConfig.afkBroadcastAway : org.purpurmc.purpur.PurpurConfig.afkBroadcastBack; + + org.purpurmc.purpur.event.PlayerAFKEvent event = new org.purpurmc.purpur.event.PlayerAFKEvent(this.getBukkitEntity(), afk, this.level().purpurConfig.idleTimeoutKick, msg, !Bukkit.isPrimaryThread()); + if (!event.callEvent() || event.shouldKick()) { + return; + } + + this.isAfk = afk; + + if (!afk) { + resetLastActionTime(); + } + + msg = event.getBroadcastMsg(); + if (msg != null && !msg.isEmpty()) { + 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); + } + } + + ((ServerLevel) this.level()).updateSleepingPlayerList(); + } + + @Override + public boolean isAfk() { + return this.isAfk; } + @Override + public boolean canBeCollidedWith() { + return !this.isAfk() && super.canBeCollidedWith(); + } + // Purpur End + public ServerStatsCounter getStats() { return this.stats; } @@ -2605,9 +2739,17 @@ public class ServerPlayer extends Player { @Override public boolean isImmobile() { - return super.isImmobile() || (this.connection != null && this.connection.isDisconnected()); // Paper + return super.isImmobile() || frozen || (this.connection != null && this.connection.isDisconnected()); // Paper // Purpur } + // Purpur start + private boolean frozen = false; + + public void setFrozen(boolean frozen) { + this.frozen = frozen; + } + // Purpur end + @Override public Scoreboard getScoreboard() { return this.getBukkitEntity().getScoreboard().getHandle(); @@ -2655,4 +2797,50 @@ public class ServerPlayer extends Player { return (CraftPlayer) super.getBukkitEntity(); } // CraftBukkit end + + // Purpur start + public void teleport(Location to) { + this.ejectPassengers(); + this.stopRiding(true); + + if (this.isSleeping()) { + this.stopSleepInBed(true, false); + } + + if (this.containerMenu != this.inventoryMenu) { + this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); + } + + ServerLevel toLevel = ((CraftWorld) to.getWorld()).getHandle(); + if (this.level() == toLevel) { + this.connection.internalTeleport(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch(), java.util.EnumSet.noneOf(net.minecraft.world.entity.RelativeMovement.class)); + } else { + this.server.getPlayerList().respawn(this, toLevel, true, to, !toLevel.paperConfig().environment.disableTeleportationSuffocationCheck, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH); + } + } + + public boolean ramBar() { + return this.ramBar; + } + + public void ramBar(boolean ramBar) { + this.ramBar = ramBar; + } + + public boolean tpsBar() { + return this.tpsBar; + } + + public void tpsBar(boolean tpsBar) { + this.tpsBar = tpsBar; + } + + public boolean compassBar() { + return this.compassBar; + } + + public void compassBar(boolean compassBar) { + this.compassBar = compassBar; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java index 618ab9a2903f6d4139acd4aaa2e6db0a26e88ba9..f3bac087856f420aa6c14707e90288f4962967bf 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java @@ -398,6 +398,7 @@ public class ServerPlayerGameMode { } else {capturedBlockEntity = true;} // Paper end return false; } + if (this.player.level().purpurConfig.slabHalfBreak && this.player.isShiftKeyDown() && iblockdata.getBlock() instanceof net.minecraft.world.level.block.SlabBlock && ((net.minecraft.world.level.block.SlabBlock) iblockdata.getBlock()).halfBreak(iblockdata, pos, this.player)) return true; // Purpur } // CraftBukkit end @@ -428,7 +429,7 @@ public class ServerPlayerGameMode { ItemStack mainHandStack = null; // Paper boolean isCorrectTool = false; // Paper - if (this.isCreative()) { + if (this.isCreative() || (this.level.purpurConfig.shulkerBoxAllowOversizedStacks && block instanceof net.minecraft.world.level.block.ShulkerBoxBlock)) { // Purpur // return true; // CraftBukkit } else { ItemStack itemstack = this.player.getMainHandItem(); @@ -517,6 +518,7 @@ public class ServerPlayerGameMode { public InteractionHand interactHand; public ItemStack interactItemStack; public InteractionResult useItemOn(ServerPlayer player, Level world, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) { + if (shiftClickMended(stack)) return InteractionResult.SUCCESS; // Purpur BlockPos blockposition = hitResult.getBlockPos(); BlockState iblockdata = world.getBlockState(blockposition); InteractionResult enuminteractionresult = InteractionResult.PASS; @@ -578,7 +580,7 @@ public class ServerPlayerGameMode { boolean flag1 = player.isSecondaryUseActive() && flag; ItemStack itemstack1 = stack.copy(); - if (!flag1) { + if (!flag1 || (player.level().purpurConfig.composterBulkProcess && iblockdata.is(Blocks.COMPOSTER))) { // Purpur enuminteractionresult = iblockdata.use(world, player, hand, hitResult); if (enuminteractionresult.consumesAction()) { @@ -619,4 +621,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 877498729c66de9aa6a27c9148f7494d7895615c..acd7468ee3c86d3456e96e4ec3d7e6a4c612e89d 100644 --- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java +++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java @@ -297,6 +297,7 @@ public class WorldGenRegion implements WorldGenLevel { return true; } else { // Paper start + if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressSetBlockFarChunk) // Purpur if (!hasSetFarWarned) { Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + pos + ", status: " + this.generatingStatus + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get())); hasSetFarWarned = true; diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java index 6cafc20ce006872bedd28a34db11204c3c1fef58..255b422078880b46dfa6f5140f6c92fef057d09e 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -265,6 +265,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic private long keepAliveTime = Util.getMillis(); private boolean keepAlivePending; private long keepAliveChallenge; + private it.unimi.dsi.fastutil.longs.LongList keepAlives = new it.unimi.dsi.fastutil.longs.LongArrayList(); // Purpur // CraftBukkit start - multithreaded fields private final AtomicInteger chatSpamTickCount = new AtomicInteger(); private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits @@ -342,6 +343,20 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic private boolean justTeleported = false; private boolean hasMoved; // Spigot + // Purpur start + private final com.google.common.cache.LoadingCache kickPermissionCache = com.google.common.cache.CacheBuilder.newBuilder() + .maximumSize(1000) + .expireAfterWrite(1, java.util.concurrent.TimeUnit.MINUTES) + .build( + new com.google.common.cache.CacheLoader<>() { + @Override + public Boolean load(CraftPlayer player) { + return player.hasPermission("purpur.bypassIdleKick"); + } + } + ); + // Purpur end + public CraftPlayer getCraftPlayer() { return (this.player == null) ? null : (CraftPlayer) this.player.getBukkitEntity(); } @@ -397,12 +412,27 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.aboveGroundVehicleTickCount = 0; } - this.server.getProfiler().push("keepAlive"); + //this.server.getProfiler().push("keepAlive"); // Purpur // Paper Start - give clients a longer time to respond to pings as per pre 1.12.2 timings // This should effectively place the keepalive handling back to "as it was" before 1.12.2 long currentTime = Util.getMillis(); long elapsedTime = currentTime - this.keepAliveTime; + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.useAlternateKeepAlive) { + if (elapsedTime >= 1000L) { // 1 second + if (!processedDisconnect && keepAlives.size() * 1000L >= KEEPALIVE_LIMIT) { + LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName()); + disconnect(Component.translatable("disconnect.timeout"), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); + } else { + keepAliveTime = currentTime; // hijack this field for 1 second intervals + keepAlives.add(currentTime); // currentTime is ID + send(new ClientboundKeepAlivePacket(currentTime)); + } + } + } else + // Purpur end + if (this.keepAlivePending) { if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName()); // more info @@ -418,7 +448,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } // Paper end - this.server.getProfiler().pop(); + //this.server.getProfiler().pop(); // Purpur // CraftBukkit start for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !this.chatSpamTickCount.compareAndSet(spam, spam - 1); ) ; if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - split to seperate variable @@ -435,6 +465,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits. + // Purpur start + this.player.setAfk(true); + if (!this.player.level().purpurConfig.idleTimeoutKick || (!Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")) && kickPermissionCache.getUnchecked(this.player.getBukkitEntity()))) { + return; + } + // Purpur end this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause } @@ -753,6 +789,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.lastYaw = to.getYaw(); this.lastPitch = to.getPitch(); + if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur + // Skip the first time we do this if (true) { // Spigot - don't skip any move events Location oldTo = to.clone(); @@ -829,6 +867,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (packet.getId() == this.awaitingTeleport) { if (this.awaitingPositionFromClient == null) { this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + ServerGamePacketListenerImpl.LOGGER.warn("Disconnected on accept teleport packet. Was not expecting position data from client at this time"); // Purpur return; } @@ -1241,10 +1280,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic int maxBookPageSize = io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.pageMax; double multiplier = Math.max(0.3D, Math.min(1D, io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier)); long byteAllowed = maxBookPageSize; + ItemStack itemstack = this.player.getInventory().getItem(packet.getSlot()); // Purpur for (String testString : pageList) { int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; if (byteLength > 256 * 4) { ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); + org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause return; } @@ -1268,6 +1309,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (byteTotal > byteAllowed) { ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); + org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause return; } @@ -1321,13 +1363,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic itemstack1.setTag(nbttagcompound.copy()); } + // Purpur start + boolean hasPerm = getCraftPlayer().hasPermission("purpur.book.color.edit") || getCraftPlayer().hasPermission("purpur.book.color.sign"); itemstack1.addTagElement("author", StringTag.valueOf(this.player.getName().getString())); if (this.player.isTextFilteringEnabled()) { - itemstack1.addTagElement("title", StringTag.valueOf(title.filteredOrEmpty())); + itemstack1.addTagElement("title", StringTag.valueOf(color(title.filteredOrEmpty(), hasPerm))); } else { - itemstack1.addTagElement("filtered_title", StringTag.valueOf(title.filteredOrEmpty())); - itemstack1.addTagElement("title", StringTag.valueOf(title.raw())); + itemstack1.addTagElement("filtered_title", StringTag.valueOf(color(title.filteredOrEmpty(), hasPerm))); + itemstack1.addTagElement("title", StringTag.valueOf(color(title.raw(), hasPerm))); } + // Purpur end this.updateBookPages(pages, (s) -> { return Component.Serializer.toJson(Component.literal(s)); @@ -1339,10 +1384,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic private void updateBookPages(List list, UnaryOperator unaryoperator, ItemStack itemstack, int slot, ItemStack handItem) { // CraftBukkit ListTag nbttaglist = new ListTag(); + // Purpur start + boolean hasPerm = getCraftPlayer().hasPermission("purpur.book.color.edit"); if (this.player.isTextFilteringEnabled()) { - Stream stream = list.stream().map((filteredtext) -> { // CraftBukkit - decompile error - return StringTag.valueOf((String) unaryoperator.apply(filteredtext.filteredOrEmpty())); + Stream stream = list.stream().map(s -> color(s.filteredOrEmpty(), hasPerm, false)).map((s) -> { // CraftBukkit - decompile error + return StringTag.valueOf((String) unaryoperator.apply(s)); }); + // Purpur end Objects.requireNonNull(nbttaglist); stream.forEach(nbttaglist::add); @@ -1352,11 +1400,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic for (int j = list.size(); i < j; ++i) { FilteredText filteredtext = (FilteredText) list.get(i); - String s = filteredtext.raw(); + String s = color(filteredtext.raw(), hasPerm, false); // Purpur nbttaglist.add(StringTag.valueOf((String) unaryoperator.apply(s))); if (filteredtext.isFiltered()) { - nbttagcompound.putString(String.valueOf(i), (String) unaryoperator.apply(filteredtext.filteredOrEmpty())); + nbttagcompound.putString(String.valueOf(i), (String) unaryoperator.apply((String) color(filteredtext.filteredOrEmpty(), hasPerm, false))); // Purpur } } @@ -1369,6 +1417,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.player.getInventory().setItem(slot, CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) } + // Purpur start + private String color(String str, boolean hasPerm) { + return color(str, hasPerm, true); + } + + private String color(String str, boolean hasPerm, boolean parseHex) { + return hasPerm ? org.bukkit.ChatColor.color(str, parseHex) : str; + } + // Purpur end + @Override public void handleEntityTagQuery(ServerboundEntityTagQuery packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); @@ -1398,8 +1456,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic @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) { this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + ServerGamePacketListenerImpl.LOGGER.warn(String.format("Disconnected on move player packet. Invalid data: x=%b, y=%b, z=%b, yaw=%b, pitch=%b", invalidX, invalidY, invalidZ, invalidYaw, invalidPitch)); + // Purpur end } else { ServerLevel worldserver = this.player.serverLevel(); @@ -1582,7 +1648,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic if (!event.isAllowed()) { flag2 = true; // Paper - diff on change, this should be moved wrongly if (event.getLogWarning()) - ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); + ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!, ({})", this.player.getName().getString(), d11); // Purpur } // Paper end } @@ -1644,6 +1710,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.lastYaw = to.getYaw(); this.lastPitch = to.getPitch(); + if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur + // Skip the first time we do this if (from.getX() != Double.MAX_VALUE) { Location oldTo = to.clone(); @@ -1682,6 +1750,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.player.resetFallDistance(); } + // Purpur Start + if (this.player.level().purpurConfig.dontRunWithScissors && this.player.isSprinting() && !(this.player.level().purpurConfig.ignoreScissorsInWater && this.player.isInWater()) && !(this.player.level().purpurConfig.ignoreScissorsInLava && this.player.isInLava()) && (isScissor(this.player.getItemInHand(InteractionHand.MAIN_HAND)) || isScissor(this.player.getItemInHand(InteractionHand.OFF_HAND))) && (int) (Math.random() * 10) == 0) { + this.player.hurt(this.player.damageSources().scissors(), (float) this.player.level().purpurConfig.scissorsRunningDamage); + if (!org.purpurmc.purpur.PurpurConfig.dontRunWithScissors.isBlank()) this.player.sendActionBarMessage(org.purpurmc.purpur.PurpurConfig.dontRunWithScissors); + } + // Purpur End + this.player.checkMovementStatistics(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5); this.lastGoodX = this.player.getX(); this.lastGoodY = this.player.getY(); @@ -1726,6 +1801,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } } // Paper end - optimise out extra getCubes + + // Purpur start + public boolean isScissor(ItemStack stack) { + return stack.is(Items.SHEARS) && (stack.getTag() == null || stack.getTag().getInt("CustomModelData") == 0); + } + // Purpur end + private boolean isPlayerCollidingWithAnythingNew(LevelReader world, AABB box, double newX, double newY, double newZ) { AABB axisalignedbb1 = this.player.getBoundingBox().move(newX - this.player.getX(), newY - this.player.getY(), newZ - this.player.getZ()); Iterable iterable = world.getCollisions(this.player, axisalignedbb1.deflate(9.999999747378752E-6D)); @@ -2071,6 +2153,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic boolean cancelled; if (movingobjectposition == null || movingobjectposition.getType() != HitResult.Type.BLOCK) { + if (this.player.gameMode.shiftClickMended(itemstack)) return; // Purpur org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack, enumhand); cancelled = event.useItemInHand() == Event.Result.DENY; } else { @@ -2125,12 +2208,21 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic @Override public void handleResourcePackResponse(ServerboundResourcePackPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + // Purpur start + if (player.level().purpurConfig.playerInvulnerableWhileAcceptingResourcePack && !this.player.acceptingResourcePack) { + ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack packet exploitation attempt", this.player.getName()); + this.disconnect(Component.translatable("multiplayer.texturePrompt.failure.line1"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // "Server resource pack couldn't be applied" + return; + } + // Purpur end if (packet.getAction() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack rejection", this.player.getGameProfile().getName()); // Paper - Don't print component in resource pack rejection message this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - add cause } // Paper start PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action.ordinal()]; + if (player.level().purpurConfig.playerInvulnerableWhileAcceptingResourcePack) player.setFrozen(packStatus == PlayerResourcePackStatusEvent.Status.ACCEPTED); // Purpur + this.player.acceptingResourcePack = packStatus == PlayerResourcePackStatusEvent.Status.ACCEPTED; // Purpur player.getBukkitEntity().setResourcePackStatus(packStatus); this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packStatus)); // CraftBukkit // Paper end @@ -2427,7 +2519,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic do { instant1 = (Instant) this.lastChatTimeStamp.get(); if (timestamp.isBefore(instant1)) { - return false; + return !org.purpurmc.purpur.PurpurConfig.kickForOutOfOrderChat; // Purpur } } while (!this.lastChatTimeStamp.compareAndSet(instant1, timestamp)); @@ -2537,7 +2629,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic public void handleCommand(String s) { // Paper - private -> public org.spigotmc.AsyncCatcher.catchOp("Command Dispatched Async: " + s); // Paper - Add async catcher - co.aikar.timings.MinecraftTimings.playerCommandTimer.startTiming(); // Paper + //co.aikar.timings.MinecraftTimings.playerCommandTimer.startTiming(); // Paper // Purpur if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s); @@ -2547,7 +2639,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic this.cserver.getPluginManager().callEvent(event); if (event.isCancelled()) { - co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper + //co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper // Purpur return; } @@ -2560,7 +2652,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic java.util.logging.Logger.getLogger(ServerGamePacketListenerImpl.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); return; } finally { - co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper + //co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper // Purpur } } // CraftBukkit end @@ -2836,6 +2928,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic AABB axisalignedbb = entity.getBoundingBox(); if (axisalignedbb.distanceToSqr(this.player.getEyePosition()) < ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE) { + 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); @@ -2849,6 +2942,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event); + player.processClick(enumhand); // Purpur + // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { entity.getEntityData().resendPossiblyDesyncedEntity(player); // Paper - The entire mob gets deleted, so resend it. @@ -3429,6 +3524,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } } } + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.fixNetworkSerializedItemsInCreative) { + var tag = itemstack.getTagElement("Purpur.OriginalItem"); + if (tag != null) itemstack = ItemStack.of(tag); + } + // Purpur end boolean flag1 = packet.getSlotNum() >= 1 && packet.getSlotNum() <= 45; boolean flag2 = itemstack.isEmpty() || itemstack.getDamageValue() >= 0 && itemstack.getCount() <= 64 && !itemstack.isEmpty(); @@ -3524,6 +3625,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic @Override public void handleKeepAlive(ServerboundKeepAlivePacket packet) { + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.useAlternateKeepAlive) { + long id = packet.getId(); + if (keepAlives.size() > 0 && keepAlives.contains(id)) { + int ping = (int) (Util.getMillis() - id); + player.latency = (player.latency * 3 + ping) / 4; + 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 - This shouldn't be on the main thread if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) { int i = (int) (Util.getMillis() - this.keepAliveTime); @@ -3574,6 +3685,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic private static final ResourceLocation CUSTOM_UNREGISTER = new ResourceLocation("unregister"); private static final ResourceLocation MINECRAFT_BRAND = new ResourceLocation("brand"); // Paper - Brand support + private static final ResourceLocation PURPUR_CLIENT = new ResourceLocation("purpur", "client"); // Purpur @Override public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { @@ -3598,6 +3710,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex); this.disconnect("Invalid payload UNREGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause } + // Purpur start + } else if (packet.identifier.equals(PURPUR_CLIENT)) { + try { + player.purpurClient = true; + } catch (Exception ignore) { + } + // Purpur end } else { try { byte[] data = new byte[packet.data.readableBytes()]; diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java index 878001928327d92423d5f7f6d5ce8772d6fa477f..01d5fa265fb2818465b5a71a2e2efeec751a7a05 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -221,6 +221,8 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, return false; } + if (true) return org.purpurmc.purpur.PurpurConfig.usernameValidCharactersPattern.matcher(in).matches(); // Purpur + for (int i = 0, len = in.length(); i < len; ++i) { char c = in.charAt(i); @@ -340,7 +342,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, ServerLoginPacketListenerImpl.this.gameProfile = gameprofile; ServerLoginPacketListenerImpl.this.state = ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT; } else { - ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username")); + ServerLoginPacketListenerImpl.this.disconnect(org.purpurmc.purpur.PurpurConfig.unverifiedUsername.equals("default") ? Component.translatable("multiplayer.disconnect.unverified_username") : io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.unverifiedUsername))); // Purpur ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", gameprofile.getName()); } } catch (AuthenticationUnavailableException authenticationunavailableexception) { diff --git a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java index 2c13147bc063a09bb7907d6f90c3a1e811a09eb1..3edb0c392cec7fdabebad81da1a8b06a700fbfca 100644 --- a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java @@ -153,6 +153,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/packs/resources/ResourceManagerReloadListener.java b/src/main/java/net/minecraft/server/packs/resources/ResourceManagerReloadListener.java index 9ddbfcf80d9a381dace78a62880f85a4d767e0eb..7383c7d3820dce06108eaafd236a7c6c06a10a42 100644 --- a/src/main/java/net/minecraft/server/packs/resources/ResourceManagerReloadListener.java +++ b/src/main/java/net/minecraft/server/packs/resources/ResourceManagerReloadListener.java @@ -9,11 +9,11 @@ public interface ResourceManagerReloadListener extends PreparableReloadListener @Override default CompletableFuture reload(PreparableReloadListener.PreparationBarrier synchronizer, ResourceManager manager, ProfilerFiller prepareProfiler, ProfilerFiller applyProfiler, Executor prepareExecutor, Executor applyExecutor) { return synchronizer.wait(Unit.INSTANCE).thenRunAsync(() -> { - applyProfiler.startTick(); - applyProfiler.push("listener"); + //applyProfiler.startTick(); // Purpur + //applyProfiler.push("listener"); // Purpur this.onResourceManagerReload(manager); - applyProfiler.pop(); - applyProfiler.endTick(); + //applyProfiler.pop(); // Purpur + //applyProfiler.endTick(); // Purpur }, applyExecutor); } diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index f097ec5b4e3ad6b1a7c464a8cff4f8b2568fcf4f..824f31b68b38f2f8642fb9d59a123cfdaffbb7b2 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -484,6 +484,7 @@ public abstract class PlayerList { scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); } // Paper end + org.purpurmc.purpur.task.BossBarTask.addToAll(player); // Purpur // CraftBukkit - Moved from above, added world PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); } @@ -593,6 +594,7 @@ public abstract class PlayerList { } public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { // Paper end + org.purpurmc.purpur.task.BossBarTask.removeFromAll(entityplayer.getBukkitEntity()); // Purpur ServerLevel worldserver = entityplayer.serverLevel(); entityplayer.awardStat(Stats.LEAVE_GAME); @@ -747,7 +749,7 @@ public abstract class PlayerList { event.disallow(PlayerLoginEvent.Result.KICK_BANNED, PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure } else { // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null; - if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile)) { + if (this.players.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameprofile))) { // Purpur event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure } } @@ -985,6 +987,8 @@ public abstract class PlayerList { } // Paper end + entityplayer1.spawnInvulnerableTime = entityplayer1.level().purpurConfig.playerSpawnInvulnerableTicks; // Purpur + // CraftBukkit end return entityplayer1; } @@ -1045,6 +1049,20 @@ public abstract class PlayerList { } // CraftBukkit end + // Purpur Start + public void broadcastMiniMessage(@Nullable String message, boolean overlay) { + if (message != null && !message.isEmpty()) { + this.broadcastMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), overlay); + } + } + + public void broadcastMessage(@Nullable net.kyori.adventure.text.Component message, boolean overlay) { + if (message != null) { + this.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), overlay); + } + } + // Purpur end + public void broadcastAll(Packet packet, ResourceKey dimension) { Iterator iterator = this.players.iterator(); @@ -1148,6 +1166,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)); } @@ -1156,6 +1175,27 @@ public abstract class PlayerList { player.getBukkitEntity().recalculatePermissions(); // CraftBukkit this.server.getCommands().sendCommands(player); } // Paper + + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.enderChestSixRows && org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) { + org.bukkit.craftbukkit.entity.CraftHumanEntity bukkit = player.getBukkitEntity(); + if (bukkit.hasPermission("purpur.enderchest.rows.six")) { + player.sixRowEnderchestSlotCount = 54; + } else if (bukkit.hasPermission("purpur.enderchest.rows.five")) { + player.sixRowEnderchestSlotCount = 45; + } else if (bukkit.hasPermission("purpur.enderchest.rows.four")) { + player.sixRowEnderchestSlotCount = 36; + } else if (bukkit.hasPermission("purpur.enderchest.rows.three")) { + player.sixRowEnderchestSlotCount = 27; + } else if (bukkit.hasPermission("purpur.enderchest.rows.two")) { + player.sixRowEnderchestSlotCount = 18; + } else if (bukkit.hasPermission("purpur.enderchest.rows.one")) { + player.sixRowEnderchestSlotCount = 9; + } + } else { + player.sixRowEnderchestSlotCount = -1; + } + //Purpur end } public boolean isWhiteListed(GameProfile profile) { @@ -1217,7 +1257,7 @@ public abstract class PlayerList { public void saveAll(int interval) { io.papermc.paper.util.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main - MinecraftTimings.savePlayers.startTiming(); // Paper + //MinecraftTimings.savePlayers.startTiming(); // Paper // Purpur int numSaved = 0; long now = MinecraftServer.currentTick; for (int i = 0; i < this.players.size(); ++i) { @@ -1228,7 +1268,7 @@ public abstract class PlayerList { } // Paper end } - MinecraftTimings.savePlayers.stopTiming(); // Paper + //MinecraftTimings.savePlayers.stopTiming(); // Paper // Purpur return null; }); // Paper - ensure main } diff --git a/src/main/java/net/minecraft/server/players/SleepStatus.java b/src/main/java/net/minecraft/server/players/SleepStatus.java index 823efad652d8ff9e96b99375b102fef6f017716e..caa8a69bde0c212c36dd990a67836ac2f95548c0 100644 --- a/src/main/java/net/minecraft/server/players/SleepStatus.java +++ b/src/main/java/net/minecraft/server/players/SleepStatus.java @@ -19,7 +19,7 @@ public class SleepStatus { public boolean areEnoughDeepSleeping(int percentage, List players) { // CraftBukkit start - int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping; }).count(); + int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping || (eh.level().purpurConfig.idleTimeoutCountAsSleeping && eh.isAfk()); }).count(); // Purpur boolean anyDeepSleep = players.stream().anyMatch(Player::isSleepingLongEnough); return anyDeepSleep && j >= this.sleepersNeeded(percentage); @@ -52,7 +52,7 @@ public class SleepStatus { if (!entityplayer.isSpectator()) { ++this.activePlayers; - if (entityplayer.isSleeping() || entityplayer.fauxSleeping) { // CraftBukkit + if ((entityplayer.isSleeping() || entityplayer.fauxSleeping) || (entityplayer.level().purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk())) { // CraftBukkit // Purpur ++this.sleepingPlayers; } // CraftBukkit start diff --git a/src/main/java/net/minecraft/stats/ServerRecipeBook.java b/src/main/java/net/minecraft/stats/ServerRecipeBook.java index ea29e07a105f3ba6a878bdccf36e7eaf66280280..d5dce6c8d85938d61a57a78f82381d26daf8f87a 100644 --- a/src/main/java/net/minecraft/stats/ServerRecipeBook.java +++ b/src/main/java/net/minecraft/stats/ServerRecipeBook.java @@ -125,6 +125,7 @@ public class ServerRecipeBook extends RecipeBook { Optional> optional = recipeManager.byKey(minecraftkey); if (!optional.isPresent()) { + if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressUnrecognizedRecipeErrors) // Purpur ServerRecipeBook.LOGGER.error("Tried to load unrecognized recipe: {} removed now.", minecraftkey); } else { handler.accept((Recipe) optional.get()); diff --git a/src/main/java/net/minecraft/util/profiling/ActiveProfiler.java b/src/main/java/net/minecraft/util/profiling/ActiveProfiler.java index 196c7331138fee2822c76aacd136f9da040e0049..c6c30d99399c5cde2b0ec2f320d81d952b422d78 100644 --- a/src/main/java/net/minecraft/util/profiling/ActiveProfiler.java +++ b/src/main/java/net/minecraft/util/profiling/ActiveProfiler.java @@ -55,7 +55,7 @@ public class ActiveProfiler implements ProfileCollector { this.started = true; this.path = ""; this.paths.clear(); - this.push("root"); + //this.push("root"); // Purpur } } @@ -64,7 +64,7 @@ public class ActiveProfiler implements ProfileCollector { if (!this.started) { LOGGER.error("Profiler tick already ended - missing startTick()?"); } else { - this.pop(); + //this.pop(); // Purpur this.started = false; if (!this.path.isEmpty()) { LOGGER.error("Profiler tick ended before path was fully popped (remainder: '{}'). Mismatched push/pop?", LogUtils.defer(() -> { @@ -93,7 +93,7 @@ public class ActiveProfiler implements ProfileCollector { @Override public void push(Supplier locationGetter) { - this.push(locationGetter.get()); + //this.push(locationGetter.get()); // Purpur } @Override @@ -132,14 +132,14 @@ public class ActiveProfiler implements ProfileCollector { @Override public void popPush(String location) { - this.pop(); - this.push(location); + //this.pop(); // Purpur + //this.push(location); // Purpur } @Override public void popPush(Supplier locationGetter) { - this.pop(); - this.push(locationGetter); + //this.pop(); // Purpur + //this.push(locationGetter); // Purpur } private ActiveProfiler.PathEntry getCurrentEntry() { diff --git a/src/main/java/net/minecraft/util/profiling/ProfilerFiller.java b/src/main/java/net/minecraft/util/profiling/ProfilerFiller.java index 2e6e8eac987c4ef6b2dcd3de592d8a51d2b29792..863343a87fe34d72f04af89d75268b477b2adc7a 100644 --- a/src/main/java/net/minecraft/util/profiling/ProfilerFiller.java +++ b/src/main/java/net/minecraft/util/profiling/ProfilerFiller.java @@ -6,32 +6,44 @@ import net.minecraft.util.profiling.metrics.MetricCategory; public interface ProfilerFiller { String ROOT = "root"; + @io.papermc.paper.annotation.DoNotUse // Purpur void startTick(); + @io.papermc.paper.annotation.DoNotUse // Purpur void endTick(); + @io.papermc.paper.annotation.DoNotUse // Purpur void push(String location); + @io.papermc.paper.annotation.DoNotUse // Purpur void push(Supplier locationGetter); + @io.papermc.paper.annotation.DoNotUse // Purpur void pop(); + @io.papermc.paper.annotation.DoNotUse // Purpur void popPush(String location); + @io.papermc.paper.annotation.DoNotUse // Purpur void popPush(Supplier locationGetter); + @io.papermc.paper.annotation.DoNotUse // Purpur void markForCharting(MetricCategory type); + @io.papermc.paper.annotation.DoNotUse // Purpur default void incrementCounter(String marker) { - this.incrementCounter(marker, 1); + //this.incrementCounter(marker, 1); // Purpur } + @io.papermc.paper.annotation.DoNotUse // Purpur void incrementCounter(String marker, int num); + @io.papermc.paper.annotation.DoNotUse // Purpur default void incrementCounter(Supplier markerGetter) { - this.incrementCounter(markerGetter, 1); + //this.incrementCounter(markerGetter, 1); // Purpur } + @io.papermc.paper.annotation.DoNotUse // Purpur void incrementCounter(Supplier markerGetter, int num); static ProfilerFiller tee(final ProfilerFiller a, final ProfilerFiller b) { @@ -41,62 +53,62 @@ public interface ProfilerFiller { return b == InactiveProfiler.INSTANCE ? a : new ProfilerFiller() { @Override public void startTick() { - a.startTick(); - b.startTick(); + //a.startTick(); // Purpur + //b.startTick(); // Purpur } @Override public void endTick() { - a.endTick(); - b.endTick(); + //a.endTick(); // Purpur + //b.endTick(); // Purpur } @Override public void push(String location) { - a.push(location); - b.push(location); + //a.push(location); // Purpur + //b.push(location); // Purpur } @Override public void push(Supplier locationGetter) { - a.push(locationGetter); - b.push(locationGetter); + //a.push(locationGetter); // Purpur + //b.push(locationGetter); // Purpur } @Override public void markForCharting(MetricCategory type) { - a.markForCharting(type); - b.markForCharting(type); + //a.markForCharting(type); // Purpur + //b.markForCharting(type); // Purpur } @Override public void pop() { - a.pop(); - b.pop(); + //a.pop(); // Purpur + //b.pop(); // Purpur } @Override public void popPush(String location) { - a.popPush(location); - b.popPush(location); + //a.popPush(location); // Purpur + //b.popPush(location); // Purpur } @Override public void popPush(Supplier locationGetter) { - a.popPush(locationGetter); - b.popPush(locationGetter); + //a.popPush(locationGetter); // Purpur + //b.popPush(locationGetter); // Purpur } @Override public void incrementCounter(String marker, int num) { - a.incrementCounter(marker, num); - b.incrementCounter(marker, num); + //a.incrementCounter(marker, num); // Purpur + //b.incrementCounter(marker, num); // Purpur } @Override public void incrementCounter(Supplier markerGetter, int num) { - a.incrementCounter(markerGetter, num); - b.incrementCounter(markerGetter, num); + //a.incrementCounter(markerGetter, num); // Purpur + //b.incrementCounter(markerGetter, num); // Purpur } }; } diff --git a/src/main/java/net/minecraft/world/damagesource/CombatRules.java b/src/main/java/net/minecraft/world/damagesource/CombatRules.java index ccbfcef3e83b1bef364447657bfd08a92d615cf6..aa2331c6df4e79d4bb0add071a0b11d2a3a08b88 100644 --- a/src/main/java/net/minecraft/world/damagesource/CombatRules.java +++ b/src/main/java/net/minecraft/world/damagesource/CombatRules.java @@ -11,12 +11,12 @@ public class CombatRules { public static float getDamageAfterAbsorb(float damage, float armor, float armorToughness) { float f = 2.0F + armorToughness / 4.0F; - float g = Mth.clamp(armor - damage / f, armor * 0.2F, 20.0F); + float g = Mth.clamp(armor - damage / f, armor * 0.2F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur return damage * (1.0F - g / 25.0F); } public static float getDamageAfterMagicAbsorb(float damageDealt, float protection) { - float f = Mth.clamp(protection, 0.0F, 20.0F); + float f = Mth.clamp(protection, 0.0F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur return damageDealt * (1.0F - f / 25.0F); } } diff --git a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java index c12d7bacf2c54f268b1bc5e46250a083ca041415..45a1cbe958c1a626df97022afe22484f2b387233 100644 --- a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java +++ b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java @@ -59,7 +59,7 @@ public class CombatTracker { } ItemStack itemStack = var10000; - return !itemStack.isEmpty() && itemStack.hasCustomHoverName() ? Component.translatable(itemDeathTranslationKey, this.mob.getDisplayName(), attackerDisplayName, itemStack.getDisplayName()) : Component.translatable(deathTranslationKey, this.mob.getDisplayName(), attackerDisplayName); + return !itemStack.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemStack.hasCustomHoverName()) ? Component.translatable(itemDeathTranslationKey, this.mob.getDisplayName(), attackerDisplayName, itemStack.getDisplayName()) : Component.translatable(deathTranslationKey, this.mob.getDisplayName(), attackerDisplayName); } private Component getFallMessage(CombatEntry damageRecord, @Nullable Entity attacker) { @@ -99,6 +99,13 @@ 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 + if (damageSource.isScissors) { + return damageSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgRunWithScissors, this.mob); + } else if (damageSource.isStoneCutter) { + return damageSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgStonecutter, this.mob); + } + // Purpur end 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 25a5a3b949a0eb632611355e74ccd4865be108ca..c8860f20956a2819da001e93938249452bf7cb49 100644 --- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java +++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java @@ -54,6 +54,10 @@ public class DamageSource { // CraftBukkit end public @Nullable org.bukkit.block.BlockState explodedBlockState; // Paper - add exploded state + public boolean isScissors; // Purpur + + public boolean isStoneCutter; // Purpur + public String toString() { return "DamageSource (" + this.type().msgId() + ")"; } @@ -122,10 +126,19 @@ public class DamageSource { ItemStack itemstack1 = itemstack; - return !itemstack1.isEmpty() && itemstack1.hasCustomHoverName() ? Component.translatable(s + ".item", killed.getDisplayName(), ichatbasecomponent, itemstack1.getDisplayName()) : Component.translatable(s, killed.getDisplayName(), ichatbasecomponent); + return !itemstack1.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemstack1.hasCustomHoverName()) ? Component.translatable(s + ".item", killed.getDisplayName(), ichatbasecomponent, itemstack1.getDisplayName()) : Component.translatable(s, killed.getDisplayName(), ichatbasecomponent); } } + // Purpur start + public Component getLocalizedDeathMessage(String str, LivingEntity entity) { + net.kyori.adventure.text.Component name = io.papermc.paper.adventure.PaperAdventure.asAdventure(entity.getDisplayName()); + net.kyori.adventure.text.minimessage.tag.resolver.TagResolver template = net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("player", name); + net.kyori.adventure.text.Component component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(str, template); + return io.papermc.paper.adventure.PaperAdventure.asVanilla(component); + } + // Purpur end + public String getMsgId() { return this.type().msgId(); } diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSources.java b/src/main/java/net/minecraft/world/damagesource/DamageSources.java index 4604f8b38460e9113e966889a679d4547f24aff6..4a5d2e263d2bbee96bde7012d3385fa33860bc1b 100644 --- a/src/main/java/net/minecraft/world/damagesource/DamageSources.java +++ b/src/main/java/net/minecraft/world/damagesource/DamageSources.java @@ -267,4 +267,17 @@ public class DamageSources { public DamageSource genericKill() { return this.genericKill; } + // Purpur start + public DamageSource scissors() { + DamageSource source = new DamageSource(this.damageTypes.getHolderOrThrow(DamageTypes.MAGIC)); + source.isScissors = true; + return source; + } + + public DamageSource stonecutter() { + DamageSource source = new DamageSource(this.damageTypes.getHolderOrThrow(DamageTypes.MAGIC)); + source.isStoneCutter = true; + return source; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/effect/MobEffect.java b/src/main/java/net/minecraft/world/effect/MobEffect.java index 53cc6befb752affcfec65e18365f6d369448d407..01850fc596a85974287ff6750427186d21acac41 100644 --- a/src/main/java/net/minecraft/world/effect/MobEffect.java +++ b/src/main/java/net/minecraft/world/effect/MobEffect.java @@ -59,16 +59,16 @@ public class MobEffect { public void applyEffectTick(LivingEntity entity, int amplifier) { if (this == MobEffects.REGENERATION) { if (entity.getHealth() < entity.getMaxHealth()) { - entity.heal(1.0F, RegainReason.MAGIC_REGEN); // CraftBukkit + entity.heal(entity.level().purpurConfig.entityHealthRegenAmount, RegainReason.MAGIC_REGEN); // CraftBukkit // Purpur } } else if (this == MobEffects.POISON) { - if (entity.getHealth() > 1.0F) { - entity.hurt(entity.damageSources().poison, 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON + if (entity.getHealth() > entity.level().purpurConfig.entityMinimalHealthPoison) { // Purpur + entity.hurt(entity.damageSources().poison, entity.level().purpurConfig.entityPoisonDegenerationAmount); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON // Purpur } } else if (this == MobEffects.WITHER) { - entity.hurt(entity.damageSources().wither(), 1.0F); + entity.hurt(entity.damageSources().wither(), entity.level().purpurConfig.entityWitherDegenerationAmount); // Purpur } else if (this == MobEffects.HUNGER && entity instanceof Player) { - ((Player) entity).causeFoodExhaustion(0.005F * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent + ((Player) entity).causeFoodExhaustion(entity.level().purpurConfig.humanHungerExhaustionAmount * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent // Purpur } else if (this == MobEffects.SATURATION && entity instanceof Player) { if (!entity.level().isClientSide) { // CraftBukkit start @@ -76,7 +76,7 @@ public class MobEffect { 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); + 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/MobEffectInstance.java b/src/main/java/net/minecraft/world/effect/MobEffectInstance.java index 14fab63346d56c72cd7534a04760efd10eef4295..745e792482f61c571e2efbd4200dd1bdaef6e474 100644 --- a/src/main/java/net/minecraft/world/effect/MobEffectInstance.java +++ b/src/main/java/net/minecraft/world/effect/MobEffectInstance.java @@ -14,6 +14,7 @@ import net.minecraft.util.ExtraCodecs; import net.minecraft.util.Mth; import net.minecraft.world.entity.LivingEntity; import org.slf4j.Logger; +import org.bukkit.NamespacedKey; public class MobEffectInstance implements Comparable { private static final Logger LOGGER = LogUtils.getLogger(); @@ -25,6 +26,7 @@ public class MobEffectInstance implements Comparable { private boolean visible; private boolean showIcon; @Nullable + private NamespacedKey key; // Purpur - add key private MobEffectInstance hiddenEffect; private final Optional factorData; @@ -44,17 +46,36 @@ public class MobEffectInstance implements Comparable { this(type, duration, amplifier, ambient, visible, visible); } + // Purpur start + public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean visible, @Nullable NamespacedKey key) { + this(type, duration, amplifier, ambient, visible, visible, key); + } + // Purpur end + public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon) { - this(type, duration, amplifier, ambient, showParticles, showIcon, (MobEffectInstance)null, type.createFactorData()); + // Purpur start + this(type, duration, amplifier, ambient, showParticles, showIcon, (MobEffectInstance)null, type.createFactorData(), (NamespacedKey)null); + } + + public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon, @Nullable NamespacedKey key) { + this(type, duration, amplifier, ambient, showParticles, showIcon, (MobEffectInstance)null, type.createFactorData(), key); + // Purpur end } public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon, @Nullable MobEffectInstance hiddenEffect, Optional factorCalculationData) { + // Purpur start + this(type, duration, amplifier, ambient, showParticles, showIcon, hiddenEffect, factorCalculationData, (NamespacedKey) null); + } + + public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon, @Nullable MobEffectInstance hiddenEffect, Optional factorCalculationData, @Nullable NamespacedKey key) { + // Purpur end this.effect = type; this.duration = duration; this.amplifier = amplifier; this.ambient = ambient; this.visible = showParticles; this.showIcon = showIcon; + this.key = key; // Purpur - add key this.hiddenEffect = hiddenEffect; this.factorData = factorCalculationData; } @@ -75,6 +96,7 @@ public class MobEffectInstance implements Comparable { this.ambient = that.ambient; this.visible = that.visible; this.showIcon = that.showIcon; + this.key = that.key; // Purpur - add key } public boolean update(MobEffectInstance that) { @@ -120,6 +142,13 @@ public class MobEffectInstance implements Comparable { bl = true; } + // Purpur start + if (that.key != this.key) { + this.key = that.key; + bl = true; + } + // Purpur end + return bl; } @@ -163,6 +192,17 @@ public class MobEffectInstance implements Comparable { return this.showIcon; } + // Purpur start + public boolean hasKey() { + return this.key != null; + } + + @Nullable + public NamespacedKey getKey() { + return this.key; + } + // Purpur end + public boolean tick(LivingEntity entity, Runnable overwriteCallback) { if (this.hasRemainingDuration()) { int i = this.isInfiniteDuration() ? entity.tickCount : this.duration; @@ -226,6 +266,12 @@ public class MobEffectInstance implements Comparable { string = string + ", Show Icon: false"; } + // Purpur start + if (this.hasKey()) { + string = string + ", Key: " + this.key; + } + // Purpur end + return string; } @@ -241,7 +287,7 @@ public class MobEffectInstance implements Comparable { return false; } else { MobEffectInstance mobEffectInstance = (MobEffectInstance)object; - return this.duration == mobEffectInstance.duration && this.amplifier == mobEffectInstance.amplifier && this.ambient == mobEffectInstance.ambient && this.effect.equals(mobEffectInstance.effect); + return this.duration == mobEffectInstance.duration && this.amplifier == mobEffectInstance.amplifier && this.ambient == mobEffectInstance.ambient && this.effect.equals(mobEffectInstance.effect) && this.key == mobEffectInstance.key; // Purpur - add key } } @@ -265,6 +311,11 @@ public class MobEffectInstance implements Comparable { nbt.putBoolean("Ambient", this.isAmbient()); nbt.putBoolean("ShowParticles", this.isVisible()); nbt.putBoolean("ShowIcon", this.showIcon()); + // Purpur start + if (this.key != null) { + nbt.putString("Key", this.key.toString()); + } + // Purpur end if (this.hiddenEffect != null) { CompoundTag compoundTag = new CompoundTag(); this.hiddenEffect.save(compoundTag); @@ -299,6 +350,13 @@ public class MobEffectInstance implements Comparable { bl3 = nbt.getBoolean("ShowIcon"); } + // Purpur start + NamespacedKey key = null; + if (nbt.contains("Key")) { + key = NamespacedKey.fromString(nbt.getString("Key")); + } + // Purpur end + MobEffectInstance mobEffectInstance = null; if (nbt.contains("HiddenEffect", 10)) { mobEffectInstance = loadSpecifiedEffect(type, nbt.getCompound("HiddenEffect")); @@ -311,7 +369,7 @@ public class MobEffectInstance implements Comparable { optional = Optional.empty(); } - return new MobEffectInstance(type, j, Math.max(i, 0), bl, bl2, bl3, mobEffectInstance, optional); + return new MobEffectInstance(type, j, Math.max(i, 0), bl, bl2, bl3, mobEffectInstance, optional, key); // Purpur - add key } @Override diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index 36795968f4c296f680f79cc5a795391ae13c64e4..cf0d8da4c4b5f4aa4e4ef15897ca252a2b52ec8d 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -156,7 +156,7 @@ import org.bukkit.plugin.PluginManager; // CraftBukkit end public abstract class Entity implements Nameable, EntityAccess, CommandSource { - + public static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur // CraftBukkit start private static final int CURRENT_LEVEL = 2; public boolean preserveMotion = true; // Paper - keep initial motion on first setPositionRotation @@ -334,7 +334,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { public double xOld; public double yOld; public double zOld; - private float maxUpStep; + public float maxUpStep; // Purpur - private -> public public boolean noPhysics; protected final RandomSource random; public int tickCount; @@ -376,7 +376,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { private final Set tags; private final double[] pistonDeltas; private long pistonDeltasGameTime; - private EntityDimensions dimensions; + protected EntityDimensions dimensions; // Purpur - private -> protected private float eyeHeight; public boolean isInPowderSnow; public boolean wasInPowderSnow; @@ -418,6 +418,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { public boolean freezeLocked = false; // Paper - Freeze Tick Lock API public boolean collidingWithWorldBorder; // Paper public boolean fixedPose = false; // Paper + public @Nullable Boolean immuneToFire = null; // Purpur - Fire immune API public void setOrigin(@javax.annotation.Nonnull Location location) { this.origin = location.toVector(); @@ -439,7 +440,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { public int activatedPriority = gg.pufferfish.pufferfish.PufferfishConfig.maximumActivationPrio; // golf score public final BlockPos.MutableBlockPos cachedBlockPos = new BlockPos.MutableBlockPos(); // used where needed // Pufferfish end - + public float getBukkitYaw() { return this.yRot; } @@ -493,6 +494,25 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { return false; } + public boolean canSaveToDisk() { + return true; + } + + // Purpur start - copied from Mob + 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; + } + public final boolean hardCollides() { return this.hardCollides; } @@ -597,7 +617,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.bb = Entity.INITIAL_AABB; this.stuckSpeedMultiplier = Vec3.ZERO; this.nextStep = 1.0F; - this.random = SHARED_RANDOM; // Paper + this.random = world == null || world.purpurConfig.entitySharedRandom ? SHARED_RANDOM : RandomSource.create(); // Paper // Purpur this.remainingFireTicks = -this.getFireImmuneTicks(); this.fluidHeight = new Object2DoubleArrayMap(2); this.fluidOnEyes = new HashSet(); @@ -845,7 +865,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { // CraftBukkit end public void baseTick() { - this.level().getProfiler().push("entityBaseTick"); + //this.level().getProfiler().push("entityBaseTick"); // Purpur if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Update last hurt when ticking this.feetBlockState = null; if (this.isPassenger() && this.getVehicle().isRemoved()) { @@ -906,7 +926,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } this.firstTick = false; - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur } public void setSharedFlagOnFire(boolean onFire) { @@ -915,10 +935,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { public void checkBelowWorld() { // Paper start - Configurable nether ceiling damage - if (this.getY() < (double) (this.level.getMinBuildHeight() - 64) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER + if (this.getY() < (double) (this.level.getMinBuildHeight() + level().purpurConfig.voidDamageHeight) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER // Purpur && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v) && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) { // Paper end + if (this.level().purpurConfig.teleportOnNetherCeilingDamage && this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER && this instanceof ServerPlayer player) player.teleport(io.papermc.paper.util.MCUtil.toLocation(this.level, this.level.getSharedSpawnPos())); else // Purpur this.onBelowWorld(); } @@ -1125,7 +1146,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } } - this.level().getProfiler().push("move"); + //this.level().getProfiler().push("move"); // Purpur if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7D) { movement = movement.multiply(this.stuckSpeedMultiplier); this.stuckSpeedMultiplier = Vec3.ZERO; @@ -1134,7 +1155,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { // Paper start - ignore movement changes while inactive. if (isTemporarilyActive && !(this instanceof ItemEntity || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) && movement == getDeltaMovement() && movementType == MoverType.SELF) { setDeltaMovement(Vec3.ZERO); - this.level.getProfiler().pop(); + //this.level.getProfiler().pop(); // Purpur return; } // Paper end @@ -1155,8 +1176,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.setPos(this.getX() + vec3d1.x, this.getY() + vec3d1.y, this.getZ() + vec3d1.z); } - this.level().getProfiler().pop(); - this.level().getProfiler().push("rest"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("rest"); // Purpur boolean flag = !Mth.equal(movement.x, vec3d1.x); boolean flag1 = !Mth.equal(movement.z, vec3d1.z); @@ -1175,7 +1196,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.checkFallDamage(vec3d1.y, this.onGround(), iblockdata, blockposition); if (this.isRemoved()) { - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur } else { if (this.horizontalCollision) { Vec3 vec3d2 = this.getDeltaMovement(); @@ -1313,7 +1334,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.setRemainingFireTicks(-this.getFireImmuneTicks()); } - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur } } // Paper start - detailed watchdog information @@ -1803,7 +1824,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } public boolean fireImmune() { - return this.getType().fireImmune(); + return this.immuneToFire != null ? immuneToFire : this.getType().fireImmune(); // Purpur - add fire immune API } public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { @@ -1872,7 +1893,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { return this.isInWater() || flag; } - void updateInWaterStateAndDoWaterCurrentPushing() { + public void updateInWaterStateAndDoWaterCurrentPushing() { // Purpur - package-private -> public Entity entity = this.getVehicle(); if (entity instanceof Boat) { @@ -2473,6 +2494,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { nbt.putBoolean("Paper.FreezeLock", true); } // Paper end + // Purpur start + if (immuneToFire != null) { + nbt.putBoolean("Purpur.FireImmune", immuneToFire); + } + // Purpur end return nbt; } catch (Throwable throwable) { CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); @@ -2641,6 +2667,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { freezeLocked = nbt.getBoolean("Paper.FreezeLock"); } // Paper end + // Purpur start + if (nbt.contains("Purpur.FireImmune")) { + immuneToFire = nbt.getBoolean("Purpur.FireImmune"); + } + // Purpur end } catch (Throwable throwable) { CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT"); @@ -2942,6 +2973,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.passengers = ImmutableList.copyOf(list); } + // Purpur start + if (isRidable() && this.passengers.get(0) == passenger && passenger instanceof Player player) { + onMount(player); + this.rider = player; + } + // Purpur end + this.gameEvent(GameEvent.ENTITY_MOUNT, passenger); } } @@ -2982,6 +3020,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { return false; } // Spigot end + + // Purpur start + if (this.rider != null && this.passengers.get(0) == this.rider) { + onDismount(this.rider); + this.rider = null; + } + // Purpur end + if (this.passengers.size() == 1 && this.passengers.get(0) == entity) { this.passengers = ImmutableList.of(); } else { @@ -3041,12 +3087,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { return Vec3.directionFromRotation(this.getRotationVector()); } + public BlockPos portalPos = BlockPos.ZERO; // Purpur public void handleInsidePortal(BlockPos pos) { if (this.isOnPortalCooldown()) { + if (!(level().purpurConfig.playerFixStuckPortal && this instanceof Player && !pos.equals(portalPos))) // Purpur this.setPortalCooldown(); - } else { + } else if (level().purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer) { // Purpur if (!this.level().isClientSide && !pos.equals(this.portalEntrancePos)) { this.portalEntrancePos = pos.immutable(); + portalPos = BlockPos.ZERO; // Purpur } this.isInsidePortal = true; @@ -3064,7 +3113,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { ServerLevel worldserver1 = minecraftserver.getLevel(resourcekey); if (true && !this.isPassenger() && this.portalTime++ >= i) { // CraftBukkit - this.level().getProfiler().push("portal"); + //this.level().getProfiler().push("portal"); // Purpur this.portalTime = i; // Paper start io.papermc.paper.event.entity.EntityPortalReadyEvent event = new io.papermc.paper.event.entity.EntityPortalReadyEvent(this.getBukkitEntity(), worldserver1 == null ? null : worldserver1.getWorld(), org.bukkit.PortalType.NETHER); @@ -3082,7 +3131,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } } // Paper // CraftBukkit end - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur } this.isInsidePortal = false; @@ -3097,7 +3146,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } this.processPortalCooldown(); - this.tickEndPortal(); // Paper - make end portalling safe + if (this.level().purpurConfig.endPortalSafeTeleporting) this.tickEndPortal(); // Paper - make end portalling safe // Purpur } } @@ -3283,7 +3332,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } public int getMaxAirSupply() { - return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() + return this.level == null? this.maxAirTicks : this.level().purpurConfig.drowningAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() // Purpur } public int getAirSupply() { @@ -3553,14 +3602,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } // Paper end if (this.level() instanceof ServerLevel && !this.isRemoved()) { - this.level().getProfiler().push("changeDimension"); + //this.level().getProfiler().push("changeDimension"); // Purpur // CraftBukkit start // this.unRide(); if (worldserver == null) { return null; } // CraftBukkit end - this.level().getProfiler().push("reposition"); + //this.level().getProfiler().push("reposition"); // Purpur PortalInfo shapedetectorshape = (location == null) ? this.findDimensionEntryPoint(worldserver) : new PortalInfo(new Vec3(location.x(), location.y(), location.z()), Vec3.ZERO, this.yRot, this.xRot, worldserver, null); // CraftBukkit if (shapedetectorshape == null) { @@ -3599,7 +3648,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.unRide(); // CraftBukkit end - this.level().getProfiler().popPush("reloading"); + //this.level().getProfiler().popPush("reloading"); // Purpur // Paper start - Change lead drop timing to prevent dupe if (this instanceof Mob) { ((Mob) this).dropLeash(true, true); // Paper drop lead @@ -3622,10 +3671,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } this.removeAfterChangingDimensions(); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur ((ServerLevel) this.level()).resetEmptyTime(); worldserver.resetEmptyTime(); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur return entity; } } else { @@ -3745,7 +3794,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { } public boolean canChangeDimensions() { - return !this.isPassenger() && !this.isVehicle() && isAlive() && valid; // Paper + return !this.isPassenger() && !this.isVehicle() && isAlive() && valid && (level().purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer); // Paper // Purpur } public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) { @@ -4047,6 +4096,20 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { return SlotAccess.NULL; } + // Purpur Start + public void sendMiniMessage(@Nullable String message) { + if (message != null && !message.isEmpty()) { + this.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message)); + } + } + + public void sendMessage(@Nullable net.kyori.adventure.text.Component message) { + if (message != null) { + this.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message)); + } + } + // Purpur end + @Override public void sendSystemMessage(Component message) {} @@ -4316,6 +4379,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { this.yRotO = this.getYRot(); } + // Purpur start + public AABB getAxisForFluidCheck() { + return this.getBoundingBox().deflate(0.001D); + } + // Purpur end + public boolean updateFluidHeightAndDoFluidPushing(TagKey tag, double speed) { if (false && this.touchingUnloadedChunk()) { // Pufferfish - cost of a lookup here is the same cost as below, so skip return false; @@ -4869,4 +4938,45 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { return ((net.minecraft.server.level.ServerChunkCache) level.getChunkSource()).isPositionTicking(this); } // Paper end + + // Purpur start + @Nullable + private Player rider = null; + + @Nullable + public Player getRider() { + return rider; + } + + public boolean isRidable() { + return false; + } + + public boolean isControllable() { + return true; + } + + public void onMount(Player rider) { + if (this instanceof Mob) { + ((Mob) this).setTarget(null, null, false); + ((Mob) this).getNavigation().stop(); + } + rider.setJumping(false); // fixes jump on mount + } + + public void onDismount(Player player) { + } + + public boolean onSpacebar() { + return false; + } + + public boolean onClick(InteractionHand hand) { + return false; + } + + public boolean processClick(InteractionHand hand) { + return false; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java index 3ff999734d14e2b6e7828e117f5ee32a60c26bc1..cfa9607241c3e69777ffc317206996c2f783437a 100644 --- a/src/main/java/net/minecraft/world/entity/EntitySelector.java +++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java @@ -39,6 +39,7 @@ public final class EntitySelector { return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks; }; // Paper end + public static Predicate notAfk = (player) -> !player.isAfk(); // Purpur private EntitySelector() {} // Paper start diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java index aa5cec6d56d7a8e80861aa4c9b4a74ca3e64be8c..a8ffa403ac48041e17aa4ce057be0539e5468fd4 100644 --- a/src/main/java/net/minecraft/world/entity/EntityType.java +++ b/src/main/java/net/minecraft/world/entity/EntityType.java @@ -308,13 +308,24 @@ public class EntityType implements FeatureElement, EntityTypeT private Component description; @Nullable private ResourceLocation lootTable; - private final EntityDimensions dimensions; + private EntityDimensions dimensions; // Purpur - remove final + public void setDimensions(EntityDimensions dimensions) { this.dimensions = dimensions; } // Purpur private final FeatureFlagSet requiredFeatures; private static EntityType register(String id, EntityType.Builder type) { // CraftBukkit - decompile error return (EntityType) Registry.register(BuiltInRegistries.ENTITY_TYPE, id, (EntityType) type.build(id)); // CraftBukkit - decompile error } + // Purpur start + public static EntityType getFromBukkitType(org.bukkit.entity.EntityType bukkitType) { + return getFromKey(new ResourceLocation(bukkitType.getKey().toString())); + } + + public static EntityType getFromKey(ResourceLocation location) { + return BuiltInRegistries.ENTITY_TYPE.get(location); + } + // Purpur end + public static ResourceLocation getKey(EntityType type) { return BuiltInRegistries.ENTITY_TYPE.getKey(type); } @@ -530,6 +541,16 @@ public class EntityType implements FeatureElement, EntityTypeT return this.category; } + // Purpur start + public String getName() { + return BuiltInRegistries.ENTITY_TYPE.getKey(this).getPath(); + } + + public String getTranslatedName() { + return getDescription().getString(); + } + // Purpur end + public String getDescriptionId() { if (this.descriptionId == null) { this.descriptionId = Util.makeDescriptionId("entity", BuiltInRegistries.ENTITY_TYPE.getKey(this)); @@ -591,6 +612,12 @@ public class EntityType implements FeatureElement, EntityTypeT entity.load(nbt); }, () -> { EntityType.LOGGER.warn("Skipping Entity with id {}", nbt.getString("id")); + // Purpur start - log skipped entity's position + try { + ListTag pos = nbt.getList("Pos", 6); + EntityType.LOGGER.warn("Location: {} {},{},{}", world.getWorld().getName(), pos.getDouble(0), pos.getDouble(1), pos.getDouble(2)); + } catch (Throwable ignore) {} + // Purpur end }); } diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java index eca634792d2a7cc649675e3394e84dbaf1453905..724bf857bf1b89cb0947b8a82e0ce09a0bec0335 100644 --- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java @@ -313,7 +313,7 @@ public class ExperienceOrb extends Entity { public void playerTouch(Player player) { if (!this.level().isClientSide) { if (player.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - 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(player, this.value); @@ -331,7 +331,7 @@ public class ExperienceOrb extends Entity { } private int repairPlayerItems(Player player, int amount) { - Entry entry = EnchantmentHelper.getRandomItemWith(Enchantments.MENDING, player, ItemStack::isDamaged); + Entry entry = level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedEquipment(Enchantments.MENDING, player) : EnchantmentHelper.getRandomItemWith(Enchantments.MENDING, player, ItemStack::isDamaged); // Purpur if (entry != null) { ItemStack itemstack = (ItemStack) entry.getValue(); @@ -359,13 +359,15 @@ public class ExperienceOrb extends Entity { } } + // Purpur start public int durabilityToXp(int repairAmount) { - return repairAmount / 2; + return (int) (repairAmount / (2 * level().purpurConfig.mendingMultiplier)); } public int xpToDurability(int experienceAmount) { - return experienceAmount * 2; + return (int) ((experienceAmount * 2) * level().purpurConfig.mendingMultiplier); } + // Purpur end public int getValue() { return this.value; diff --git a/src/main/java/net/minecraft/world/entity/GlowSquid.java b/src/main/java/net/minecraft/world/entity/GlowSquid.java index 759713f7c646aaf1a918c87a2834a1d405385dad..43cdda0cb26c5d5cc9025199eb71673d71c2abea 100644 --- a/src/main/java/net/minecraft/world/entity/GlowSquid.java +++ b/src/main/java/net/minecraft/world/entity/GlowSquid.java @@ -23,6 +23,39 @@ public class GlowSquid extends Squid { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.glowSquidRidable; + } + + + @Override + public boolean isControllable() { + return level().purpurConfig.glowSquidControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.glowSquidMaxHealth); + } + + @Override + public boolean canFly() { + return this.level().purpurConfig.glowSquidsCanFly; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.glowSquidTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.glowSquidAlwaysDropExp; + } + @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 d265a32bd273beed0e65e224e1c5f9c0bca7b3fb..49906b4dc8392eee592215d0b4cba85267a875fc 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -218,9 +218,9 @@ public abstract class LivingEntity extends Entity implements Attackable { protected int deathScore; public float lastHurt; public boolean jumping; - public float xxa; - public float yya; - public float zza; + public float xxa; public float getStrafeMot() { return xxa; } public void setStrafeMot(float strafe) { xxa = strafe; } // Purpur - OBFHELPER + public float yya; public float getVerticalMot() { return yya; } public void setVerticalMot(float vertical) { yya = vertical; } // Purpur - OBFHELPER + public float zza; public float getForwardMot() { return zza; } public void setForwardMot(float forward) { zza = forward; } // Purpur - OBFHELPER protected int lerpSteps; protected double lerpX; protected double lerpY; @@ -253,6 +253,7 @@ public abstract class LivingEntity extends Entity implements Attackable { private boolean skipDropExperience; // CraftBukkit start public int expToDrop; + public float safeFallDistance = 3.0F; // Purpur public boolean forceDrops; public ArrayList drops = new ArrayList(); public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes; @@ -262,6 +263,7 @@ public abstract class LivingEntity extends Entity implements Attackable { public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper + protected boolean shouldBurnInDay = false; public boolean shouldBurnInDay() { return this.shouldBurnInDay; } public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Purpur @Override public float getBukkitYaw() { @@ -286,7 +288,8 @@ public abstract class LivingEntity extends Entity implements Attackable { this.effectsDirty = true; this.useItem = ItemStack.EMPTY; this.lastClimbablePos = Optional.empty(); - this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type)); + this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type), this); // Purpur + this.initAttributes(); // Purpur this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor this.entityData.set(LivingEntity.DATA_HEALTH_ID, (float) this.getAttribute(Attributes.MAX_HEALTH).getValue()); @@ -302,6 +305,8 @@ public abstract class LivingEntity extends Entity implements Attackable { this.brain = this.makeBrain(new Dynamic(dynamicopsnbt, (Tag) dynamicopsnbt.createMap((Map) ImmutableMap.of(dynamicopsnbt.createString("memories"), (Tag) dynamicopsnbt.emptyMap())))); } + protected void initAttributes() {}// Purpur + public Brain getBrain() { return this.brain; } @@ -337,6 +342,7 @@ public abstract class LivingEntity extends Entity implements Attackable { public static AttributeSupplier.Builder createLivingAttributes() { return AttributeSupplier.builder().add(Attributes.MAX_HEALTH).add(Attributes.KNOCKBACK_RESISTANCE).add(Attributes.MOVEMENT_SPEED).add(Attributes.ARMOR).add(Attributes.ARMOR_TOUGHNESS); } + public boolean shouldSendAttribute(Attribute attribute) { return true; } // Purpur @Override protected void checkFallDamage(double heightDifference, boolean onGround, BlockState state, BlockPos landedPosition) { @@ -349,7 +355,7 @@ public abstract class LivingEntity extends Entity implements Attackable { this.tryAddSoulSpeed(); } - if (!this.level().isClientSide && this.fallDistance > 3.0F && onGround && !state.isAir()) { + if (!this.level().isClientSide && this.fallDistance > this.safeFallDistance && onGround && !state.isAir()) { // Purpur double d1 = this.getX(); double d2 = this.getY(); double d3 = this.getZ(); @@ -364,7 +370,7 @@ public abstract class LivingEntity extends Entity implements Attackable { d3 = (double) landedPosition.getZ() + 0.5D + d5 / d6 * 0.5D; } - float f = (float) Mth.ceil(this.fallDistance - 3.0F); + float f = (float) Mth.ceil(this.fallDistance - this.safeFallDistance); // Purpur double d7 = Math.min((double) (0.2F + f / 15.0F), 2.5D); int i = (int) (150.0D * d7); @@ -404,7 +410,7 @@ public abstract class LivingEntity extends Entity implements Attackable { } super.baseTick(); - this.level().getProfiler().push("livingEntityBaseTick"); + //this.level().getProfiler().push("livingEntityBaseTick"); // Purpur if (this.fireImmune() || this.level().isClientSide) { this.clearFire(); } @@ -422,6 +428,7 @@ public abstract class LivingEntity extends Entity implements Attackable { double d1 = this.level().getWorldBorder().getDamagePerBlock(); if (d1 > 0.0D) { + if (level().purpurConfig.teleportIfOutsideBorder && this instanceof ServerPlayer serverPlayer) { serverPlayer.teleport(io.papermc.paper.util.MCUtil.toLocation(level(), ((ServerLevel) level()).getSharedSpawnPos())); return; } // Purpur this.hurt(this.damageSources().outOfBorder(), (float) Math.max(1, Mth.floor(-d0 * d1))); } } @@ -433,7 +440,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(); @@ -445,7 +452,7 @@ public abstract class LivingEntity extends Entity implements Attackable { this.level().addParticle(ParticleTypes.BUBBLE, this.getX() + d2, this.getY() + d3, this.getZ() + d4, vec3d.x, vec3d.y, vec3d.z); } - this.hurt(this.damageSources().drown(), 2.0F); + this.hurt(this.damageSources().drown(), (float) this.level().purpurConfig.damageFromDrowning); // Purpur } } @@ -506,7 +513,7 @@ public abstract class LivingEntity extends Entity implements Attackable { this.yHeadRotO = this.yHeadRot; this.yRotO = this.getYRot(); this.xRotO = this.getXRot(); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur } public boolean canSpawnSoulSpeedParticle() { @@ -798,6 +805,7 @@ public abstract class LivingEntity extends Entity implements Attackable { dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> { nbt.put("Brain", nbtbase); }); + nbt.putBoolean("Purpur.ShouldBurnInDay", shouldBurnInDay); // Purpur } @Override @@ -882,6 +890,11 @@ public abstract class LivingEntity extends Entity implements Attackable { this.brain = this.makeBrain(new Dynamic(NbtOps.INSTANCE, nbt.get("Brain"))); } + // Purpur start + if (nbt.contains("Purpur.ShouldBurnInDay")) { + shouldBurnInDay = nbt.getBoolean("Purpur.ShouldBurnInDay"); + } + // Purpur end } // CraftBukkit start @@ -1027,9 +1040,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; @@ -1089,6 +1124,7 @@ public abstract class LivingEntity extends Entity implements Attackable { for (flag = false; iterator.hasNext(); flag = true) { // CraftBukkit start MobEffectInstance effect = (MobEffectInstance) iterator.next(); + if (cause == EntityPotionEffectEvent.Cause.MILK && !this.level().purpurConfig.milkClearsBeneficialEffects && effect.getEffect().isBeneficial()) continue; // Purpur EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED); if (event.isCancelled()) { continue; @@ -1471,13 +1507,13 @@ public abstract class LivingEntity extends Entity implements Attackable { if (entity1 instanceof net.minecraft.world.entity.player.Player) { net.minecraft.world.entity.player.Player entityhuman = (net.minecraft.world.entity.player.Player) entity1; - this.lastHurtByPlayerTime = 100; + this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur this.lastHurtByPlayer = entityhuman; } else if (entity1 instanceof Wolf) { Wolf entitywolf = (Wolf) entity1; if (entitywolf.isTame()) { - this.lastHurtByPlayerTime = 100; + this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur LivingEntity entityliving2 = entitywolf.getOwner(); if (entityliving2 instanceof net.minecraft.world.entity.player.Player) { @@ -1588,6 +1624,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); @@ -1754,7 +1802,7 @@ public abstract class LivingEntity extends Entity implements Attackable { boolean flag = false; if (this.dead && adversary instanceof WitherBoss) { // Paper - if (this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (this.level().purpurConfig.witherBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur BlockPos blockposition = this.blockPosition(); BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState(); @@ -1800,6 +1848,7 @@ public abstract class LivingEntity extends Entity implements Attackable { this.dropEquipment(); // CraftBukkit - from below if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { + if (!(source.is(net.minecraft.world.damagesource.DamageTypes.CRAMMING) && level().purpurConfig.disableDropsOnCrammingDeath)) { // Purpur this.dropFromLootTable(source, flag); // Paper start final boolean prev = this.clearEquipmentSlots; @@ -1808,6 +1857,7 @@ public abstract class LivingEntity extends Entity implements Attackable { // Paper end this.dropCustomDeathLoot(source, i, flag); this.clearEquipmentSlots = prev; // Paper + } // Purpur } // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops, () -> { @@ -2054,7 +2104,7 @@ public abstract class LivingEntity extends Entity implements Attackable { MobEffectInstance mobeffect = this.getEffect(MobEffects.JUMP); float f2 = mobeffect == null ? 0.0F : (float) (mobeffect.getAmplifier() + 1); - return Mth.ceil((fallDistance - 3.0F - f2) * damageMultiplier); + return Mth.ceil((fallDistance - this.safeFallDistance - f2) * damageMultiplier); // Purpur } } @@ -2277,6 +2327,20 @@ public abstract class LivingEntity extends Entity implements Attackable { } } + // Purpur start + if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player player && damagesource.getEntity().level().purpurConfig.creativeOnePunch) { + if (player.isCreative()) { + double attackDamage = 0; + for (AttributeModifier modifier : player.getMainHandItem().getAttributeModifiers(EquipmentSlot.MAINHAND).get(Attributes.ATTACK_DAMAGE)) { + attackDamage += modifier.getAmount(); + } + if (attackDamage == 0) { + this.setHealth(0); + } + } + } + // Purpur end + if (f > 0 || !human) { if (human) { // PAIL: Be sure to drag all this code from the EntityHuman subclass each update. @@ -2493,7 +2557,7 @@ public abstract class LivingEntity extends Entity implements Attackable { @Override protected void onBelowWorld() { - this.hurt(this.damageSources().fellOutOfWorld(), 4.0F); + this.hurt(this.damageSources().fellOutOfWorld(), (float) level().purpurConfig.voidDamageDealt); // Purpur } protected void updateSwingTime() { @@ -2690,7 +2754,7 @@ public abstract class LivingEntity extends Entity implements Attackable { } protected long lastJumpTime = 0L; // Paper - protected void jumpFromGround() { + public void jumpFromGround() { // Purpur - protected -> public Vec3 vec3d = this.getDeltaMovement(); // Paper start long time = System.nanoTime(); @@ -2842,6 +2906,7 @@ public abstract class LivingEntity extends Entity implements Attackable { if (f3 > 0.0F) { this.playSound(this.getFallDamageSound((int) f3), 1.0F, 1.0F); + if (level().purpurConfig.elytraKineticDamage) // Purpur this.hurt(this.damageSources().flyIntoWall(), f3); } } @@ -3063,10 +3128,10 @@ public abstract class LivingEntity extends Entity implements Attackable { } this.run += (f3 - this.run) * 0.3F; - this.level().getProfiler().push("headTurn"); + //this.level().getProfiler().push("headTurn"); // Purpur f2 = this.tickHeadTurn(f1, f2); - this.level().getProfiler().pop(); - this.level().getProfiler().push("rangeChecks"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("rangeChecks"); // Purpur // Paper start - stop large pitch and yaw changes from crashing the server this.yRotO += Math.round((this.getYRot() - this.yRotO) / 360.0F) * 360.0F; @@ -3078,7 +3143,7 @@ public abstract class LivingEntity extends Entity implements Attackable { this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F; // Paper end - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur this.animStep += f2; if (this.isFallFlying()) { ++this.fallFlyTicks; @@ -3367,19 +3432,19 @@ public abstract class LivingEntity extends Entity implements Attackable { } this.setDeltaMovement(d4, d5, d6); - this.level().getProfiler().push("ai"); + //this.level().getProfiler().push("ai"); // Purpur if (this.isImmobile()) { this.jumping = false; this.xxa = 0.0F; this.zza = 0.0F; } else if (this.isEffectiveAi()) { - this.level().getProfiler().push("newAi"); + //this.level().getProfiler().push("newAi"); // Purpur this.serverAiStep(); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur } - this.level().getProfiler().pop(); - this.level().getProfiler().push("jump"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("jump"); // Purpur if (this.jumping && this.isAffectedByFluids()) { double d7; @@ -3406,8 +3471,8 @@ public abstract class LivingEntity extends Entity implements Attackable { this.noJumpDelay = 0; } - this.level().getProfiler().pop(); - this.level().getProfiler().push("travel"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("travel"); // Purpur this.xxa *= 0.98F; this.zza *= 0.98F; this.updateFallFlying(); @@ -3434,8 +3499,8 @@ public abstract class LivingEntity extends Entity implements Attackable { this.travel(vec3d1); } - this.level().getProfiler().pop(); - this.level().getProfiler().push("freezing"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("freezing"); // Purpur if (!this.level().isClientSide && !this.isDeadOrDying() && !freezeLocked) { // Paper - Freeze Tick Lock API int i = this.getTicksFrozen(); @@ -3452,18 +3517,20 @@ public abstract class LivingEntity extends Entity implements Attackable { this.hurt(this.damageSources().freeze(), 1.0F); } - this.level().getProfiler().pop(); - this.level().getProfiler().push("push"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("push"); // Purpur if (this.autoSpinAttackTicks > 0) { --this.autoSpinAttackTicks; this.checkAutoSpinAttack(axisalignedbb, this.getBoundingBox()); } this.pushEntities(); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur // Paper start - 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 + if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { + if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { + // Purpur end Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); Location to = new Location (this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone()); @@ -3473,12 +3540,48 @@ 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 + if (getRider() != null) { + getRider().resetLastActionTime(); + if (((ServerLevel) level()).hasRidableMoveEvent && this instanceof Mob) { + Location from = new Location(level().getWorld(), xo, yo, zo, this.yRotO, this.xRotO); + Location to = new Location(level().getWorld(), getX(), getY(), getZ(), this.getYRot(), this.getXRot()); + org.purpurmc.purpur.event.entity.RidableMoveEvent event = new org.purpurmc.purpur.event.entity.RidableMoveEvent((org.bukkit.entity.Mob) getBukkitLivingEntity(), (Player) getRider().getBukkitEntity(), from, to.clone()); + if (!event.callEvent()) { + absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch()); + } else if (!to.equals(event.getTo())) { + absMoveTo(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch()); + } + } + } + // Purpur end } // Paper end if (!this.level().isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { this.hurt(this.damageSources().drown(), 1.0F); } + // Purpur start - copied from Zombie + if (this.isAlive()) { + boolean flag = this.shouldBurnInDay() && this.isSunBurnTick(); + if (flag) { + ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); + if (!itemstack.isEmpty()) { + if (itemstack.isDamageableItem()) { + itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); + if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { + this.broadcastBreakEvent(EquipmentSlot.HEAD); + this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); + } + } + flag = false; + } + if (flag) { + this.setSecondsOnFire(8); + } + } + } + // Purpur end } public boolean isSensitiveToWater() { @@ -3499,7 +3602,16 @@ public abstract class LivingEntity extends Entity implements Attackable { int j = i / 10; if (j % 2 == 0) { - itemstack.hurtAndBreak(1, this, (entityliving) -> { + // Purpur start + int damage = level().purpurConfig.elytraDamagePerSecond; + if (level().purpurConfig.elytraDamageMultiplyBySpeed > 0) { + double speed = getDeltaMovement().lengthSqr(); + if (speed > level().purpurConfig.elytraDamageMultiplyBySpeed) { + damage *= (int) speed; + } + } + itemstack.hurtAndBreak(damage, this, (entityliving) -> { + // Purpur end entityliving.broadcastBreakEvent(EquipmentSlot.CHEST); }); } diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java index 7e453b4c49975bb06cb080f3626545286b947ddc..5dd0c925af627f7159289dc1e3e79bc593c421f2 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java @@ -65,6 +65,7 @@ import net.minecraft.world.item.ProjectileWeaponItem; import net.minecraft.world.item.SpawnEggItem; import net.minecraft.world.item.SwordItem; import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.Level; @@ -132,6 +133,7 @@ public abstract class Mob extends LivingEntity implements Targeting { private BlockPos restrictCenter; private float restrictRadius; + public int ticksSinceLastInteraction; // Purpur public boolean aware = true; // CraftBukkit protected Mob(EntityType type, Level world) { @@ -145,8 +147,8 @@ public abstract class Mob extends LivingEntity implements Targeting { this.restrictRadius = -1.0F; this.goalSelector = new GoalSelector(world.getProfilerSupplier()); this.targetSelector = new GoalSelector(world.getProfilerSupplier()); - this.lookControl = new LookControl(this); - this.moveControl = new MoveControl(this); + this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this); // Purpur + this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this); // Purpur this.jumpControl = new JumpControl(this); this.bodyRotationControl = this.createBodyControl(); this.navigation = this.createNavigation(world); @@ -322,6 +324,7 @@ public abstract class Mob extends LivingEntity implements Targeting { entityliving = null; } } + if (entityliving instanceof ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur this.target = entityliving; return true; // CraftBukkit end @@ -362,15 +365,35 @@ public abstract class Mob extends LivingEntity implements Targeting { @Override public void baseTick() { super.baseTick(); - this.level().getProfiler().push("mobBaseTick"); + //this.level().getProfiler().push("mobBaseTick"); // Purpur if (this.isAlive() && this.random.nextInt(1000) < this.ambientSoundTime++) { this.resetAmbientSoundTime(); this.playAmbientSound(); } - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur + incrementTicksSinceLastInteraction(); // Purpur } + // Purpur start + private void incrementTicksSinceLastInteraction() { + ++this.ticksSinceLastInteraction; + if (getRider() != null) { + this.ticksSinceLastInteraction = 0; + return; + } + if (this.level().purpurConfig.entityLifeSpan <= 0) { + return; // feature disabled + } + if (!this.removeWhenFarAway(0) || isPersistenceRequired() || requiresCustomPersistence() || hasCustomName()) { + return; // mob persistent + } + if (this.ticksSinceLastInteraction > this.level().purpurConfig.entityLifeSpan) { + this.discard(); + } + } + // Purpur end + @Override protected void playHurtSound(DamageSource source) { this.resetAmbientSoundTime(); @@ -560,6 +583,7 @@ public abstract class Mob extends LivingEntity implements Targeting { } nbt.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit + nbt.putInt("Purpur.ticksSinceLastInteraction", this.ticksSinceLastInteraction); // Purpur } @Override @@ -630,6 +654,11 @@ public abstract class Mob extends LivingEntity implements Targeting { this.aware = nbt.getBoolean("Bukkit.Aware"); } // CraftBukkit end + // Purpur start + if (nbt.contains("Purpur.ticksSinceLastInteraction")) { + this.ticksSinceLastInteraction = nbt.getInt("Purpur.ticksSinceLastInteraction"); + } + // Purpur end } @Override @@ -673,8 +702,8 @@ public abstract class Mob extends LivingEntity implements Targeting { @Override public void aiStep() { super.aiStep(); - this.level().getProfiler().push("looting"); - if (!this.level().isClientSide && this.canPickUpLoot() && this.isAlive() && !this.dead && this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + //this.level().getProfiler().push("looting"); // Purpur + if (!this.level().isClientSide && this.canPickUpLoot() && this.isAlive() && !this.dead && (this.level().purpurConfig.entitiesPickUpLootBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { Vec3i baseblockposition = this.getPickupReach(); List list = this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ())); Iterator iterator = list.iterator(); @@ -693,7 +722,7 @@ public abstract class Mob extends LivingEntity implements Targeting { } } - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur } protected Vec3i getPickupReach() { @@ -905,46 +934,46 @@ public abstract class Mob extends LivingEntity implements Targeting { return; } // Paper end - this.level().getProfiler().push("sensing"); + //this.level().getProfiler().push("sensing"); // Purpur this.sensing.tick(); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur int i = this.level().getServer().getTickCount() + this.getId(); if (i % 2 != 0 && this.tickCount > 1) { - this.level().getProfiler().push("targetSelector"); + //this.level().getProfiler().push("targetSelector"); // Purpur if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking this.targetSelector.tickRunningGoals(false); - this.level().getProfiler().pop(); - this.level().getProfiler().push("goalSelector"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("goalSelector"); // Purpur if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking this.goalSelector.tickRunningGoals(false); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur } else { - this.level().getProfiler().push("targetSelector"); + //this.level().getProfiler().push("targetSelector"); if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking this.targetSelector.tick(); - this.level().getProfiler().pop(); - this.level().getProfiler().push("goalSelector"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("goalSelector"); // Purpur if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking this.goalSelector.tick(); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur } - this.level().getProfiler().push("navigation"); + //this.level().getProfiler().push("navigation"); // Purpur this.navigation.tick(); - this.level().getProfiler().pop(); - this.level().getProfiler().push("mob tick"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("mob tick"); // Purpur this.customServerAiStep(); - this.level().getProfiler().pop(); - this.level().getProfiler().push("controls"); - this.level().getProfiler().push("move"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("controls"); // Purpur + //this.level().getProfiler().push("move"); // Purpur this.moveControl.tick(); - this.level().getProfiler().popPush("look"); + //this.level().getProfiler().popPush("look"); // Purpur this.lookControl.tick(); - this.level().getProfiler().popPush("jump"); + //this.level().getProfiler().popPush("jump"); // Purpur this.jumpControl.tick(); - this.level().getProfiler().pop(); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().pop(); // Purpur this.sendDebugPackets(); } @@ -1173,6 +1202,12 @@ public abstract class Mob extends LivingEntity implements Targeting { } + // Purpur start + public static @Nullable EquipmentSlot getSlotForDispenser(ItemStack itemstack) { + return EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BINDING_CURSE, itemstack) > 0 ? null : getEquipmentSlotForItem(itemstack); + } + // Purpur end + @Nullable public static Item getEquipmentForSlot(EquipmentSlot equipmentSlot, int equipmentLevel) { switch (equipmentSlot) { @@ -1267,7 +1302,7 @@ public abstract class Mob extends LivingEntity implements Targeting { RandomSource randomsource = world.getRandom(); this.getAttribute(Attributes.FOLLOW_RANGE).addPermanentModifier(new AttributeModifier("Random spawn bonus", randomsource.triangle(0.0D, 0.11485000000000001D), AttributeModifier.Operation.MULTIPLY_BASE)); - if (randomsource.nextFloat() < 0.05F) { + if (randomsource.nextFloat() < world.getLevel().purpurConfig.entityLeftHandedChance) { // Purpur this.setLeftHanded(true); } else { this.setLeftHanded(false); @@ -1315,6 +1350,7 @@ public abstract class Mob extends LivingEntity implements Targeting { if (!this.isAlive()) { return InteractionResult.PASS; } else if (this.getLeashHolder() == player) { + if (hand == InteractionHand.OFF_HAND && (level().purpurConfig.villagerCanBeLeashed || level().purpurConfig.wanderingTraderCanBeLeashed) && this instanceof net.minecraft.world.entity.npc.AbstractVillager) return InteractionResult.CONSUME; // Purpur // CraftBukkit start - fire PlayerUnleashEntityEvent // Paper start - drop leash variable org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.getAbilities().instabuild); @@ -1388,7 +1424,7 @@ public abstract class Mob extends LivingEntity implements Targeting { protected void onOffspringSpawnedFromEgg(Player player, Mob child) {} protected InteractionResult mobInteract(Player player, InteractionHand hand) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } public boolean isWithinRestriction() { @@ -1693,6 +1729,7 @@ public abstract class Mob extends LivingEntity implements Targeting { this.setLastHurtMob(target); } + if (target instanceof ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur return flag; } @@ -1709,17 +1746,7 @@ public abstract class Mob extends LivingEntity implements Targeting { } 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; + return super.isSunBurnTick(); } @Override @@ -1766,4 +1793,56 @@ public abstract class Mob extends LivingEntity implements Targeting { return itemmonsteregg == null ? null : new ItemStack(itemmonsteregg); } + + // Purpur start + public double getMaxY() { + return level().getHeight(); + } + + public InteractionResult tryRide(Player player, InteractionHand hand) { + return tryRide(player, hand, InteractionResult.PASS); + } + + public InteractionResult tryRide(Player player, InteractionHand hand, InteractionResult result) { + if (!isRidable()) { + return result; + } + if (hand != InteractionHand.MAIN_HAND) { + return InteractionResult.PASS; + } + if (player.isShiftKeyDown()) { + return InteractionResult.PASS; + } + if (!player.getItemInHand(hand).isEmpty()) { + return InteractionResult.PASS; + } + if (!passengers.isEmpty() || player.isPassenger()) { + return InteractionResult.PASS; + } + if (this instanceof TamableAnimal tamable) { + if (tamable.isTame() && !tamable.isOwnedBy(player)) { + return InteractionResult.PASS; + } + if (!tamable.isTame() && !level().purpurConfig.untamedTamablesAreRidable) { + return InteractionResult.PASS; + } + } + if (this instanceof AgeableMob ageable) { + if (ageable.isBaby() && !level().purpurConfig.babiesAreRidable) { + return InteractionResult.PASS; + } + } + if (!player.getBukkitEntity().hasPermission("allow.ride." + net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getKey(getType()).getPath())) { + player.sendMiniMessage(org.purpurmc.purpur.PurpurConfig.cannotRideMob); + return InteractionResult.PASS; + } + player.setYRot(this.getYRot()); + player.setXRot(this.getXRot()); + if (player.startRiding(this)) { + return InteractionResult.SUCCESS; + } else { + return InteractionResult.PASS; + } + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/Shearable.java b/src/main/java/net/minecraft/world/entity/Shearable.java index 5e8cc5cfac8888628c6d513148f41be09ca65a2c..a089fc61ec09be6b7490375489178dc6ba5a644b 100644 --- a/src/main/java/net/minecraft/world/entity/Shearable.java +++ b/src/main/java/net/minecraft/world/entity/Shearable.java @@ -3,7 +3,13 @@ package net.minecraft.world.entity; import net.minecraft.sounds.SoundSource; public interface Shearable { - void shear(SoundSource shearedSoundCategory); + // Purpur start + default void shear(SoundSource shearedSoundCategory) { + shear(shearedSoundCategory, 0); + } + + void shear(SoundSource shearedSoundCategory, int looting); + // Purpur end boolean readyForShearing(); } diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java index e283eb57c25f7de222f9d09dca851169f5f6e488..210a0bee1227e4671909dd553ab22027cfc868fb 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 @@ -24,14 +24,21 @@ public class AttributeMap { private final Set dirtyAttributes = Sets.newHashSet(); private final AttributeSupplier supplier; private final java.util.function.Function createInstance; // Pufferfish + private final net.minecraft.world.entity.LivingEntity entity; // Purpur public AttributeMap(AttributeSupplier defaultAttributes) { + // Purpur start + this(defaultAttributes, null); + } + public AttributeMap(AttributeSupplier defaultAttributes, net.minecraft.world.entity.LivingEntity entity) { + this.entity = entity; + // Purpur end this.supplier = defaultAttributes; this.createInstance = attribute -> this.supplier.createInstance(this::onAttributeModified, attribute); // Pufferfish } private void onAttributeModified(AttributeInstance instance) { - if (instance.getAttribute().isClientSyncable()) { + if (instance.getAttribute().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute()))) { // Purpur this.dirtyAttributes.add(instance); } @@ -43,7 +50,7 @@ public class AttributeMap { public Collection getSyncableAttributes() { return this.attributes.values().stream().filter((attribute) -> { - return attribute.getAttribute().isClientSyncable(); + return attribute.getAttribute().isClientSyncable() && (entity == null || entity.shouldSendAttribute(attribute.getAttribute())); // Purpur }).collect(Collectors.toList()); } diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java index 8a720f9ae81d7ea856e28cb27a66adcf04bcb0eb..e0b70d9732a2b7d96999b7e4a497ffa1d8cf86a7 100644 --- a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java +++ b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java @@ -80,7 +80,88 @@ import org.slf4j.Logger; public class DefaultAttributes { private static final Logger LOGGER = LogUtils.getLogger(); - private static final Map, AttributeSupplier> SUPPLIERS = ImmutableMap., AttributeSupplier>builder().put(EntityType.ALLAY, Allay.createAttributes().build()).put(EntityType.ARMOR_STAND, LivingEntity.createLivingAttributes().build()).put(EntityType.AXOLOTL, Axolotl.createAttributes().build()).put(EntityType.BAT, Bat.createAttributes().build()).put(EntityType.BEE, Bee.createAttributes().build()).put(EntityType.BLAZE, Blaze.createAttributes().build()).put(EntityType.CAT, Cat.createAttributes().build()).put(EntityType.CAMEL, Camel.createAttributes().build()).put(EntityType.CAVE_SPIDER, CaveSpider.createCaveSpider().build()).put(EntityType.CHICKEN, Chicken.createAttributes().build()).put(EntityType.COD, AbstractFish.createAttributes().build()).put(EntityType.COW, Cow.createAttributes().build()).put(EntityType.CREEPER, Creeper.createAttributes().build()).put(EntityType.DOLPHIN, Dolphin.createAttributes().build()).put(EntityType.DONKEY, AbstractChestedHorse.createBaseChestedHorseAttributes().build()).put(EntityType.DROWNED, Zombie.createAttributes().build()).put(EntityType.ELDER_GUARDIAN, ElderGuardian.createAttributes().build()).put(EntityType.ENDERMAN, EnderMan.createAttributes().build()).put(EntityType.ENDERMITE, Endermite.createAttributes().build()).put(EntityType.ENDER_DRAGON, EnderDragon.createAttributes().build()).put(EntityType.EVOKER, Evoker.createAttributes().build()).put(EntityType.FOX, Fox.createAttributes().build()).put(EntityType.FROG, Frog.createAttributes().build()).put(EntityType.GHAST, Ghast.createAttributes().build()).put(EntityType.GIANT, Giant.createAttributes().build()).put(EntityType.GLOW_SQUID, GlowSquid.createAttributes().build()).put(EntityType.GOAT, Goat.createAttributes().build()).put(EntityType.GUARDIAN, Guardian.createAttributes().build()).put(EntityType.HOGLIN, Hoglin.createAttributes().build()).put(EntityType.HORSE, AbstractHorse.createBaseHorseAttributes().build()).put(EntityType.HUSK, Zombie.createAttributes().build()).put(EntityType.ILLUSIONER, Illusioner.createAttributes().build()).put(EntityType.IRON_GOLEM, IronGolem.createAttributes().build()).put(EntityType.LLAMA, Llama.createAttributes().build()).put(EntityType.MAGMA_CUBE, MagmaCube.createAttributes().build()).put(EntityType.MOOSHROOM, Cow.createAttributes().build()).put(EntityType.MULE, AbstractChestedHorse.createBaseChestedHorseAttributes().build()).put(EntityType.OCELOT, Ocelot.createAttributes().build()).put(EntityType.PANDA, Panda.createAttributes().build()).put(EntityType.PARROT, Parrot.createAttributes().build()).put(EntityType.PHANTOM, Monster.createMonsterAttributes().build()).put(EntityType.PIG, Pig.createAttributes().build()).put(EntityType.PIGLIN, Piglin.createAttributes().build()).put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build()).put(EntityType.PILLAGER, Pillager.createAttributes().build()).put(EntityType.PLAYER, Player.createAttributes().build()).put(EntityType.POLAR_BEAR, PolarBear.createAttributes().build()).put(EntityType.PUFFERFISH, AbstractFish.createAttributes().build()).put(EntityType.RABBIT, Rabbit.createAttributes().build()).put(EntityType.RAVAGER, Ravager.createAttributes().build()).put(EntityType.SALMON, AbstractFish.createAttributes().build()).put(EntityType.SHEEP, Sheep.createAttributes().build()).put(EntityType.SHULKER, Shulker.createAttributes().build()).put(EntityType.SILVERFISH, Silverfish.createAttributes().build()).put(EntityType.SKELETON, AbstractSkeleton.createAttributes().build()).put(EntityType.SKELETON_HORSE, SkeletonHorse.createAttributes().build()).put(EntityType.SLIME, Monster.createMonsterAttributes().build()).put(EntityType.SNIFFER, Sniffer.createAttributes().build()).put(EntityType.SNOW_GOLEM, SnowGolem.createAttributes().build()).put(EntityType.SPIDER, Spider.createAttributes().build()).put(EntityType.SQUID, Squid.createAttributes().build()).put(EntityType.STRAY, AbstractSkeleton.createAttributes().build()).put(EntityType.STRIDER, Strider.createAttributes().build()).put(EntityType.TADPOLE, Tadpole.createAttributes().build()).put(EntityType.TRADER_LLAMA, Llama.createAttributes().build()).put(EntityType.TROPICAL_FISH, AbstractFish.createAttributes().build()).put(EntityType.TURTLE, Turtle.createAttributes().build()).put(EntityType.VEX, Vex.createAttributes().build()).put(EntityType.VILLAGER, Villager.createAttributes().build()).put(EntityType.VINDICATOR, Vindicator.createAttributes().build()).put(EntityType.WARDEN, Warden.createAttributes().build()).put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build()).put(EntityType.WITCH, Witch.createAttributes().build()).put(EntityType.WITHER, WitherBoss.createAttributes().build()).put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build()).put(EntityType.WOLF, Wolf.createAttributes().build()).put(EntityType.ZOGLIN, Zoglin.createAttributes().build()).put(EntityType.ZOMBIE, Zombie.createAttributes().build()).put(EntityType.ZOMBIE_HORSE, ZombieHorse.createAttributes().build()).put(EntityType.ZOMBIE_VILLAGER, Zombie.createAttributes().build()).put(EntityType.ZOMBIFIED_PIGLIN, ZombifiedPiglin.createAttributes().build()).build(); + private static final Map, AttributeSupplier> SUPPLIERS = ImmutableMap., AttributeSupplier>builder() + .put(EntityType.ALLAY, Allay.createAttributes().build()) + .put(EntityType.ARMOR_STAND, LivingEntity.createLivingAttributes().build()) + .put(EntityType.AXOLOTL, Axolotl.createAttributes().build()) + .put(EntityType.BAT, Bat.createAttributes().build()) + .put(EntityType.BEE, Bee.createAttributes().build()) + .put(EntityType.BLAZE, Blaze.createAttributes().build()) + .put(EntityType.CAT, Cat.createAttributes().build()) + .put(EntityType.CAMEL, Camel.createAttributes().build()) + .put(EntityType.CAVE_SPIDER, CaveSpider.createCaveSpider().build()) + .put(EntityType.CHICKEN, Chicken.createAttributes().build()) + .put(EntityType.COD, AbstractFish.createAttributes().build()) + .put(EntityType.COW, Cow.createAttributes().build()) + .put(EntityType.CREEPER, Creeper.createAttributes().build()) + .put(EntityType.DOLPHIN, Dolphin.createAttributes().build()) + .put(EntityType.DONKEY, AbstractChestedHorse.createBaseChestedHorseAttributes().build()) + .put(EntityType.DROWNED, Zombie.createAttributes().build()) + .put(EntityType.ELDER_GUARDIAN, ElderGuardian.createAttributes().build()) + .put(EntityType.ENDERMAN, EnderMan.createAttributes().build()) + .put(EntityType.ENDERMITE, Endermite.createAttributes().build()) + .put(EntityType.ENDER_DRAGON, EnderDragon.createAttributes().build()) + .put(EntityType.EVOKER, Evoker.createAttributes().build()) + .put(EntityType.FOX, Fox.createAttributes().build()) + .put(EntityType.FROG, Frog.createAttributes().build()) + .put(EntityType.GHAST, Ghast.createAttributes().build()) + .put(EntityType.GIANT, Giant.createAttributes().build()) + .put(EntityType.GLOW_SQUID, GlowSquid.createAttributes().build()) + .put(EntityType.GOAT, Goat.createAttributes().build()) + .put(EntityType.GUARDIAN, Guardian.createAttributes().build()) + .put(EntityType.HOGLIN, Hoglin.createAttributes().build()) + .put(EntityType.HORSE, AbstractHorse.createBaseHorseAttributes().build()) + .put(EntityType.HUSK, Zombie.createAttributes().build()) + .put(EntityType.ILLUSIONER, Illusioner.createAttributes().build()) + .put(EntityType.IRON_GOLEM, IronGolem.createAttributes().build()) + .put(EntityType.LLAMA, Llama.createAttributes().build()) + .put(EntityType.MAGMA_CUBE, MagmaCube.createAttributes().build()) + .put(EntityType.MOOSHROOM, Cow.createAttributes().build()) + .put(EntityType.MULE, AbstractChestedHorse.createBaseChestedHorseAttributes().build()) + .put(EntityType.OCELOT, Ocelot.createAttributes().build()) + .put(EntityType.PANDA, Panda.createAttributes().build()) + .put(EntityType.PARROT, Parrot.createAttributes().build()) + .put(EntityType.PHANTOM, net.minecraft.world.entity.monster.Phantom.createAttributes().build()) // Purpur + .put(EntityType.PIG, Pig.createAttributes().build()) + .put(EntityType.PIGLIN, Piglin.createAttributes().build()) + .put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build()) + .put(EntityType.PILLAGER, Pillager.createAttributes().build()) + .put(EntityType.PLAYER, Player.createAttributes().build()) + .put(EntityType.POLAR_BEAR, PolarBear.createAttributes().build()) + .put(EntityType.PUFFERFISH, AbstractFish.createAttributes().build()) + .put(EntityType.RABBIT, Rabbit.createAttributes().build()) + .put(EntityType.RAVAGER, Ravager.createAttributes().build()) + .put(EntityType.SALMON, AbstractFish.createAttributes().build()) + .put(EntityType.SHEEP, Sheep.createAttributes().build()) + .put(EntityType.SHULKER, Shulker.createAttributes().build()) + .put(EntityType.SILVERFISH, Silverfish.createAttributes().build()) + .put(EntityType.SKELETON, AbstractSkeleton.createAttributes().build()) + .put(EntityType.SKELETON_HORSE, SkeletonHorse.createAttributes().build()) + .put(EntityType.SLIME, Monster.createMonsterAttributes().build()) + .put(EntityType.SNIFFER, Sniffer.createAttributes().build()) + .put(EntityType.SNOW_GOLEM, SnowGolem.createAttributes().build()) + .put(EntityType.SPIDER, Spider.createAttributes().build()) + .put(EntityType.SQUID, Squid.createAttributes().build()) + .put(EntityType.STRAY, AbstractSkeleton.createAttributes().build()) + .put(EntityType.STRIDER, Strider.createAttributes().build()) + .put(EntityType.TADPOLE, Tadpole.createAttributes().build()) + .put(EntityType.TRADER_LLAMA, Llama.createAttributes().build()) + .put(EntityType.TROPICAL_FISH, AbstractFish.createAttributes().build()) + .put(EntityType.TURTLE, Turtle.createAttributes().build()) + .put(EntityType.VEX, Vex.createAttributes().build()) + .put(EntityType.VILLAGER, Villager.createAttributes().build()) + .put(EntityType.VINDICATOR, Vindicator.createAttributes().build()) + .put(EntityType.WARDEN, Warden.createAttributes().build()) + .put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build()) + .put(EntityType.WITCH, Witch.createAttributes().build()) + .put(EntityType.WITHER, WitherBoss.createAttributes().build()) + .put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build()) + .put(EntityType.WOLF, Wolf.createAttributes().build()) + .put(EntityType.ZOGLIN, Zoglin.createAttributes().build()) + .put(EntityType.ZOMBIE, Zombie.createAttributes().build()) + .put(EntityType.ZOMBIE_HORSE, ZombieHorse.createAttributes().build()) + .put(EntityType.ZOMBIE_VILLAGER, Zombie.createAttributes().build()) + .put(EntityType.ZOMBIFIED_PIGLIN, ZombifiedPiglin.createAttributes().build()).build(); public static AttributeSupplier getSupplier(EntityType type) { return SUPPLIERS.get(type); diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java b/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java index f0703302e7dbbda88de8c648d20d87c55ed9b1e0..a913ebabaa5f443afa987b972355a8f8d1723c78 100644 --- a/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java +++ b/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java @@ -29,6 +29,7 @@ public class RangedAttribute extends Attribute { @Override public double sanitizeValue(double value) { + if (!org.purpurmc.purpur.PurpurConfig.clampAttributes) return Double.isNaN(value) ? this.minValue : value; // Purpur return Double.isNaN(value) ? this.minValue : Mth.clamp(value, this.minValue, this.maxValue); } } diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java index 57ef7fbba3028c28231abf7b7ae78aa019323536..651c156dc8a5aad04d461add02e22147af657d07 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java @@ -58,9 +58,9 @@ public abstract class Behavior implements BehaviorContro this.status = Behavior.Status.RUNNING; int i = this.minDuration + world.getRandom().nextInt(this.maxDuration + 1 - this.minDuration); this.endTimestamp = time + (long)i; - this.timing.startTiming(); // Paper - behavior timings + //this.timing.startTiming(); // Paper - behavior timings // Purpur this.start(world, entity, time); - this.timing.stopTiming(); // Paper - behavior timings + //this.timing.stopTiming(); // Paper - behavior timings // Purpur return true; } else { return false; @@ -72,13 +72,13 @@ public abstract class Behavior implements BehaviorContro @Override public final void tickOrStop(ServerLevel world, E entity, long time) { - this.timing.startTiming(); // Paper - behavior timings + //this.timing.startTiming(); // Paper - behavior timings // Purpur if (!this.timedOut(time) && this.canStillUse(world, entity, time)) { this.tick(world, entity, time); } else { this.doStop(world, entity, time); } - this.timing.stopTiming(); // Paper - behavior timings + //this.timing.stopTiming(); // Paper - behavior timings // Purpur } 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 d3a2a6dee2d83b3df0ddc521c080f7d72b709461..a1acc479c9d6a838e3180da3ac8a3a02d22db5c1 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 @@ -40,17 +40,19 @@ public class HarvestFarmland extends Behavior { private long nextOkStartTime; private int timeWorkedSoFar; private final List validFarmlandAroundVillager = Lists.newArrayList(); + private boolean clericWartFarmer = false; // Purpur public HarvestFarmland() { super(ImmutableMap.of(MemoryModuleType.LOOK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.SECONDARY_JOB_SITE, MemoryStatus.VALUE_PRESENT)); } protected boolean checkExtraStartConditions(ServerLevel world, Villager entity) { - if (!world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (!world.purpurConfig.villagerBypassMobGriefing && !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur return false; - } else if (entity.getVillagerData().getProfession() != VillagerProfession.FARMER) { + } else if (entity.getVillagerData().getProfession() != VillagerProfession.FARMER && !(world.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC)) { // Purpur return false; } else { + if (!this.clericWartFarmer && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC) this.clericWartFarmer = true; // Purpur BlockPos.MutableBlockPos blockposition_mutableblockposition = entity.blockPosition().mutable(); this.validFarmlandAroundVillager.clear(); @@ -81,6 +83,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; } @@ -106,20 +109,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) { @@ -135,7 +138,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 42ae4d293a420f0b8eb476df6389b2e7a693895f..97c20c5b89e6d7e4ed844eff39ee55dfa8988d37 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java @@ -57,7 +57,7 @@ public class InteractWithDoor { if (iblockdata.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> { return blockbase_blockdata.getBlock() instanceof DoorBlock; - })) { + }) && !DoorBlock.requiresRedstone(entityliving.level(), iblockdata, blockposition)) { // Purpur DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock(); if (!blockdoor.isOpen(iblockdata)) { @@ -79,7 +79,7 @@ public class InteractWithDoor { if (iblockdata1.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> { return blockbase_blockdata.getBlock() instanceof DoorBlock; - })) { + }) && !DoorBlock.requiresRedstone(entityliving.level(), iblockdata, blockposition1)) { // Purpur DoorBlock blockdoor1 = (DoorBlock) iblockdata1.getBlock(); if (!blockdoor1.isOpen(iblockdata1)) { @@ -122,7 +122,7 @@ public class InteractWithDoor { if (!iblockdata.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> { return blockbase_blockdata.getBlock() instanceof DoorBlock; - })) { + }) || DoorBlock.requiresRedstone(entity.level(), iblockdata, blockposition)) { // Purpur iterator.remove(); } else { DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock(); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java index 050be72c815010bf3f4b72427e2052b00420e8ee..8a213205f57c8dcd2226d7194d74b1b13dec5a78 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java @@ -42,6 +42,7 @@ public class ShowTradesToPlayer extends Behavior { @Override public boolean canStillUse(ServerLevel world, Villager entity, long time) { + if (!entity.level().purpurConfig.villagerDisplayTradeItem) return false; // Purpur return this.checkExtraStartConditions(world, entity) && this.lookTime > 0 && entity.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).isPresent(); } diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java b/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java index 3af715e2f3f3949af614a8fcebbc4a835d48ca49..ade1e411ea1f3b4c9a417265e77b0d6861b222f9 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 @@ -56,6 +56,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.getMaxStackSize() / 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 cd7a90ec1073b2b452ca70decefe6a594445003b..47672e48c1cae73cffe532d622b296343fc12ef0 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java @@ -30,8 +30,13 @@ public class VillagerGoalPackages { } public static ImmutableList>> getWorkPackage(VillagerProfession profession, float speed) { + // Purpur start + return getWorkPackage(profession, speed, false); + } + public static ImmutableList>> getWorkPackage(VillagerProfession profession, float speed, boolean clericsFarmWarts) { + // Purpur end WorkAtPoi workAtPoi; - if (profession == VillagerProfession.FARMER) { + if (profession == VillagerProfession.FARMER || (clericsFarmWarts && profession == VillagerProfession.CLERIC)) { // Purpur workAtPoi = new WorkAtComposter(); } else { workAtPoi = new WorkAtPoi(); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java index 645d204fe2c727637a6a547aad7d4db1279628ae..8be2f612c9e8a64b0aad6bb54450cdfb12b3c19e 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 @@ -127,8 +127,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/control/MoveControl.java b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java index 03d77c6d4697d4934ca65e3329982f0efe089820..ce7e3e90993b76225dc820a21e04267e488f5f7b 100644 --- a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java +++ b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java @@ -29,6 +29,20 @@ public class MoveControl implements Control { this.mob = entity; } + // Purpur start + public void setSpeedModifier(double speed) { + this.speedModifier = speed; + } + + public void setForward(float forward) { + this.strafeForwards = forward; + } + + public void setStrafe(float strafe) { + this.strafeRight = strafe; + } + // Purpur end + public boolean hasWanted() { return this.operation == MoveControl.Operation.MOVE_TO; } diff --git a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java index 7df56705a4a0de2dc4ff7ab133fc26612c219162..384bed4505b6cabb1ae151cd2c4eb5e56f24563f 100644 --- a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java +++ b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java @@ -3,7 +3,7 @@ package net.minecraft.world.entity.ai.control; import net.minecraft.util.Mth; import net.minecraft.world.entity.Mob; -public class SmoothSwimmingLookControl extends LookControl { +public class SmoothSwimmingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur private final int maxYRotFromCenter; private static final int HEAD_TILT_X = 10; private static final int HEAD_TILT_Y = 20; @@ -14,7 +14,7 @@ public class SmoothSwimmingLookControl extends LookControl { } @Override - public void tick() { + public void vanillaTick() { // Purpur if (this.lookAtCooldown > 0) { --this.lookAtCooldown; this.getYRotD().ifPresent((yaw) -> { 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 a85885ee51df585fa11ae9f8fcd67ff2a71c5a18..d81509e08e70ec5b2f837c9dc66b1254c86854e4 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java @@ -32,7 +32,7 @@ public class BreakDoorGoal extends DoorInteractGoal { @Override public boolean canUse() { - return !super.canUse() ? false : (!this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.isValidDifficulty(this.mob.level().getDifficulty()) && !this.isOpen()); + return !super.canUse() ? false : ((!this.mob.level().purpurConfig.zombieBypassMobGriefing && !this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) ? false : this.isValidDifficulty(this.mob.level().getDifficulty()) && !this.isOpen()); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java index 93bbda61f0eb2dd52573602b1f9cc7b031d1fc5a..63f9e5e2490e5b2fec6f2395077e21e601804ca5 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(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state + if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !this.level.purpurConfig.sheepBypassMobGriefing && !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state // Purpur 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(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state + if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !this.level.purpurConfig.sheepBypassMobGriefing && !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state // Purpur this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState())); this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2); } diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java index 1635818fc4b1788c0d397085239df6dd75b210ab..02978315bc2b828cc603ce7478408f3f82c249c2 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 @@ -105,8 +105,8 @@ public class GoalSelector { } public void tick() { - ProfilerFiller profilerFiller = this.profiler.get(); - profilerFiller.push("goalCleanup"); + //ProfilerFiller profilerFiller = this.profiler.get(); // Purpur + //profilerFiller.push("goalCleanup"); // Purpur for(WrappedGoal wrappedGoal : this.availableGoals) { if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) { @@ -123,8 +123,8 @@ public class GoalSelector { } } - profilerFiller.pop(); - profilerFiller.push("goalUpdate"); + //profilerFiller.pop(); // Purpur + //profilerFiller.push("goalUpdate"); // Purpur for(WrappedGoal wrappedGoal2 : this.availableGoals) { // Paper start @@ -144,13 +144,13 @@ public class GoalSelector { } } - profilerFiller.pop(); + //profilerFiller.pop(); // Purpur this.tickRunningGoals(true); } public void tickRunningGoals(boolean tickAll) { - ProfilerFiller profilerFiller = this.profiler.get(); - profilerFiller.push("goalTick"); + //ProfilerFiller profilerFiller = this.profiler.get(); // Purpur + //profilerFiller.push("goalTick"); // Purpur for(WrappedGoal wrappedGoal : this.availableGoals) { if (wrappedGoal.isRunning() && (tickAll || wrappedGoal.requiresUpdateEveryTick())) { @@ -158,7 +158,7 @@ public class GoalSelector { } } - profilerFiller.pop(); + //profilerFiller.pop(); // Purpur } public Set getAvailableGoals() { 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 21725aee29e9120d1c7e1e19f91c21a73a28844f..3fc9528201fb96d6a0f905afe0b6a82ec88a7235 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java @@ -22,6 +22,7 @@ public class LlamaFollowCaravanGoal extends Goal { @Override public boolean canUse() { + if (!this.llama.level().purpurConfig.llamaJoinCaravans || !this.llama.shouldJoinCaravan) return false; // Purpur if (!this.llama.isLeashed() && !this.llama.inCaravan()) { List list = this.llama.level().getEntities(this.llama, this.llama.getBoundingBox().inflate(9.0D, 4.0D, 9.0D), (entity) -> { EntityType entityType = entity.getType(); @@ -71,6 +72,7 @@ public class LlamaFollowCaravanGoal extends Goal { @Override public boolean canContinueToUse() { + if (!this.llama.shouldJoinCaravan) return false; // Purpur if (this.llama.inCaravan() && this.llama.getCaravanHead().isAlive() && this.firstIsLeashed(this.llama, 0)) { double d = this.llama.distanceToSqr(this.llama.getCaravanHead()); if (d > 676.0D) { diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java index 87fb10096fc9dade33c663234b1cecc34d3d77bb..874c7b29a261b1b5ad6e86ca219ff935870aecb0 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 @@ -119,9 +119,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 509317a26c79f453335df1c19dc4c9ec570046af..8e4d673e4f2d7f50ea5ed13794da08b1d20e6665 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java @@ -40,7 +40,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal { @Override public boolean canUse() { - if (!this.removerMob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (!this.removerMob.level().purpurConfig.zombieBypassMobGriefing && !this.removerMob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur return false; } else if (this.nextStartTick > 0) { --this.nextStartTick; diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java index 7fc5b5d624b61a3de9c71e975b51d20c5800be00..8fb070b1ae11d34b2fa420ded58097d421263cab 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 @@ -66,7 +66,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 && ((Player) entity).getAbilities().instabuild) || (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((Player) entity); return; } diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java index e241ae250f4f04a17ef2c583d00b065a4ca56a4c..7b99c3446b50939241d3e220d93e05649f72a6df 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 0d9b194781d152e842c9a4b8d6f23d307b2e4452..00cf59524477ec79d4354cc403fc3e75a63b81a0 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java @@ -62,7 +62,7 @@ public class TemptGoal extends Goal { } private boolean shouldFollow(LivingEntity entity) { - return this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem()); + return (this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem())) && (!(this.mob instanceof net.minecraft.world.entity.npc.Villager villager) || !villager.isSleeping()); // Purpur Fix #512 } @Override diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java index b376670d11088e524ce246f667e580e90cd119a3..fdb08ecc19bc4a1bc93cf6b18adcea2bc5573965 100644 --- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java +++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java @@ -172,12 +172,12 @@ public abstract class PathNavigation { } } // Paper end - this.level.getProfiler().push("pathfind"); + //this.level.getProfiler().push("pathfind"); // Purpur BlockPos blockPos = useHeadPos ? this.mob.blockPosition().above() : this.mob.blockPosition(); int i = (int)(followRange + (float)range); PathNavigationRegion pathNavigationRegion = new PathNavigationRegion(this.level, blockPos.offset(-i, -i, -i), blockPos.offset(i, i, i)); Path path = this.pathFinder.findPath(pathNavigationRegion, this.mob, positions, followRange, distance, this.maxVisitedNodesMultiplier); - this.level.getProfiler().pop(); + //this.level.getProfiler().pop(); // Purpur if (path != null && path.getTarget() != null) { this.targetPos = path.getTarget(); this.reachRange = distance; 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 cb1d91f9fe98f21c2afbe3894dfd9bca3bdd3ba6..d2703432af207c74ea8d298a784329c3219d2f13 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/sensing/Sensing.java b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensing.java index 51772f03a3469b11e7166ec6f3a1b9c64a606221..02f2f46ccc48bb4d9bd08555818b0489f60d9f13 100644 --- a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensing.java +++ b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensing.java @@ -26,9 +26,9 @@ public class Sensing { } else if (this.unseen.contains(i)) { return false; } else { - this.mob.level().getProfiler().push("hasLineOfSight"); + //this.mob.level().getProfiler().push("hasLineOfSight"); // Purpur boolean bl = this.mob.hasLineOfSight(entity); - this.mob.level().getProfiler().pop(); + //this.mob.level().getProfiler().pop(); // Purpur if (bl) { this.seen.add(i); } else { diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java index fcdb9bde8e1605e30dde3e580491522d4b62cdc0..7094701d213c73ba47ace806962244c10fdf4dda 100644 --- a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java +++ b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java @@ -46,10 +46,10 @@ public abstract class Sensor { if (--this.timeToTick <= 0L) { // Paper start - configurable sensor tick rate and timings this.timeToTick = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.sensor.get(entity.getType(), this.configKey), this.scanRate); - this.timing.startTiming(); + //this.timing.startTiming(); // Purpur // Paper end this.doTick(world, entity); - this.timing.stopTiming(); // Paper - sensor timings + //this.timing.stopTiming(); // Paper - sensor timings // Purpur } } 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 d25307ae8bbdf10ae067ec70fc2cb957b852a0eb..54bdb81785b617e13e67530752395f2a0c6d703a 100644 --- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java +++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java @@ -64,6 +64,10 @@ public class TargetingConditions { return false; } else if (this.selector != null && !this.selector.test(targetEntity)) { return false; + // Purpur start + } else if (!targetEntity.level().purpurConfig.idleTimeoutTargetPlayer && targetEntity instanceof net.minecraft.server.level.ServerPlayer player && player.isAfk()) { + return false; + // Purpur end } else { if (baseEntity == null) { if (this.isCombat && (!targetEntity.canBeSeenAsEnemy() || targetEntity.level().getDifficulty() == Difficulty.PEACEFUL)) { diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java index 2c91fe46355c9a201507de5577f693ed4f5fb974..1eab1393a2636c4a247f25dae317cea60cc7021c 100644 --- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java +++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java @@ -18,6 +18,7 @@ import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.entity.MoverType; import net.minecraft.world.entity.Pose; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; @@ -43,12 +44,59 @@ public class Bat extends AmbientCreature { public Bat(EntityType type, Level world) { super(type, world); + this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.075F); // Purpur if (!world.isClientSide) { this.setResting(true); } } + // Purpur start + @Override + public boolean shouldSendAttribute(net.minecraft.world.entity.ai.attributes.Attribute attribute) { return attribute != Attributes.FLYING_SPEED; } // Fixes log spam on clients + + @Override + public boolean isRidable() { + return level().purpurConfig.batRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.batRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.batControllable; + } + + @Override + public double getMaxY() { + return level().purpurConfig.batMaxY; + } + + @Override + public void onMount(Player rider) { + super.onMount(rider); + if (isResting()) { + setResting(false); + level().levelEvent(null, 1025, new BlockPos(this).above(), 0); + } + } + + @Override + public void travel(Vec3 vec3) { + super.travel(vec3); + if (getRider() != null && this.isControllable() && !onGround) { + float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2; + setSpeed(speed); + Vec3 mot = getDeltaMovement(); + move(MoverType.SELF, mot.multiply(speed, 0.25, speed)); + setDeltaMovement(mot.scale(0.9D)); + } + } + // Purpur end + @Override public boolean isFlapping() { return !this.isResting() && this.tickCount % Bat.TICKS_PER_FLAP == 0; @@ -98,7 +146,7 @@ public class Bat extends AmbientCreature { protected void pushEntities() {} public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D); + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur } public boolean isResting() { @@ -130,6 +178,14 @@ public class Bat extends AmbientCreature { @Override protected void customServerAiStep() { + // Purpur start + if (getRider() != null && this.isControllable()) { + Vec3 mot = getDeltaMovement(); + setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z()); + return; + } + // Purpur end + super.customServerAiStep(); BlockPos blockposition = this.blockPosition(); BlockPos blockposition1 = blockposition.above(); @@ -208,6 +264,28 @@ public class Bat extends AmbientCreature { } } + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.batMaxHealth); + this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(this.level().purpurConfig.batFollowRange); + this.getAttribute(Attributes.KNOCKBACK_RESISTANCE).setBaseValue(this.level().purpurConfig.batKnockbackResistance); + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.batMovementSpeed); + this.getAttribute(Attributes.FLYING_SPEED).setBaseValue(this.level().purpurConfig.batFlyingSpeed); + this.getAttribute(Attributes.ARMOR).setBaseValue(this.level().purpurConfig.batArmor); + this.getAttribute(Attributes.ARMOR_TOUGHNESS).setBaseValue(this.level().purpurConfig.batArmorToughness); + this.getAttribute(Attributes.ATTACK_KNOCKBACK).setBaseValue(this.level().purpurConfig.batAttackKnockback); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.batTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.batAlwaysDropExp; + } + @Override public void readAdditionalSaveData(CompoundTag nbt) { super.readAdditionalSaveData(nbt); @@ -227,7 +305,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; @@ -241,6 +319,7 @@ public class Bat extends AmbientCreature { 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(); 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 2249fc6dd98afb8d52623b5864955fdd3b3fc042..2ccfaab0a02cf5ff9779e250fb79a75a9852e10d 100644 --- a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java +++ b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java @@ -94,7 +94,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { @Override protected void registerGoals() { super.registerGoals(); - this.goalSelector.addGoal(0, new PanicGoal(this, 1.25D)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 1.6D, 1.4D, EntitySelector.NO_SPECTATORS::test)); this.goalSelector.addGoal(4, new AbstractFish.FishSwimGoal(this)); } @@ -107,7 +107,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { @Override public void travel(Vec3 movementInput) { if (this.isEffectiveAi() && this.isInWater()) { - this.moveRelative(0.01F, movementInput); + this.moveRelative(getRider() != null ? getSpeed() : 0.01F, movementInput); // Purpur this.move(MoverType.SELF, this.getDeltaMovement()); this.setDeltaMovement(this.getDeltaMovement().scale(0.9D)); if (this.getTarget() == null) { @@ -166,7 +166,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { protected void playStepSound(BlockPos pos, BlockState state) { } - static class FishMoveControl extends MoveControl { + static class FishMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur private final AbstractFish fish; FishMoveControl(AbstractFish owner) { @@ -174,14 +174,22 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { this.fish = owner; } + // Purpur start @Override - public void tick() { + public void purpurTick(Player rider) { + super.purpurTick(rider); + fish.setDeltaMovement(fish.getDeltaMovement().add(0.0D, 0.005D, 0.0D)); + } + // Purpur end + + @Override + public void vanillaTick() { // Purpur if (this.fish.isEyeInFluid(FluidTags.WATER)) { this.fish.setDeltaMovement(this.fish.getDeltaMovement().add(0.0D, 0.005D, 0.0D)); } if (this.operation == MoveControl.Operation.MOVE_TO && !this.fish.getNavigation().isDone()) { - float f = (float)(this.speedModifier * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED)); + float f = (float)(this.getSpeedModifier() * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur this.fish.setSpeed(Mth.lerp(0.125F, this.fish.getSpeed(), f)); double d = this.wantedX - this.fish.getX(); double e = this.wantedY - this.fish.getY(); diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java index c2f61ed153260692c96af4f20bc5b7d55cbbc380..8773b1072016a3bbf025959e9ab827704ec17fc6 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Animal.java +++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java @@ -42,6 +42,7 @@ public abstract class Animal extends AgeableMob { @Nullable public UUID loveCause; public ItemStack breedItem; // CraftBukkit - Add breedItem variable + public abstract int getPurpurBreedTime(); // Purpur protected Animal(EntityType type, Level world) { super(type, world); @@ -154,7 +155,7 @@ public abstract class Animal extends AgeableMob { if (this.isFood(itemstack)) { int i = this.getAge(); - if (!this.level().isClientSide && i == 0 && this.canFallInLove()) { + if (!this.level().isClientSide && i == 0 && this.canFallInLove() && (this.level().purpurConfig.animalBreedingCooldownSeconds <= 0 || !this.level().hasBreedingCooldown(player.getUUID(), this.getClass()))) { // Purpur this.usePlayerItem(player, hand, itemstack); this.setInLove(player); return InteractionResult.SUCCESS; @@ -236,12 +237,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()) { @@ -269,8 +278,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 + this.setAge(this.getPurpurBreedTime()); + entityanimal.setAge(entityanimal.getPurpurBreedTime()); + // Purpur end 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 55026e1731e41b4e3e4c6a8fef5d96a32051a556..6c04c8e7776b2830ac368229da834532e8ce163e 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Bee.java +++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java @@ -43,6 +43,7 @@ import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.MobType; +import net.minecraft.world.entity.MoverType; import net.minecraft.world.entity.NeutralMob; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.entity.Pose; @@ -147,6 +148,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { public Bee(EntityType type, Level world) { super(type, world); this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60); + final org.purpurmc.purpur.controller.FlyingMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.25F, 1.0F, false); // Purpur // Paper start - apply gravity to bees when they get stuck in the void, fixes MC-167279 class BeeFlyingMoveControl extends FlyingMoveControl { public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) { @@ -155,22 +157,69 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { @Override public void tick() { + // Purpur start + if (mob.getRider() != null && mob.isControllable()) { + flyingController.purpurTick(mob.getRider()); + return; + } + // Purpur end if (this.mob.getY() <= Bee.this.level().getMinBuildHeight()) { this.mob.setNoGravity(false); } super.tick(); } + + // Purpur start + @Override + public boolean hasWanted() { + return mob.getRider() != null || !mob.isControllable() || super.hasWanted(); + } + // Purpur end } this.moveControl = new BeeFlyingMoveControl(this, 20, true); // Paper end this.lookControl = new Bee.BeeLookControl(this); this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F); - this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); + if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur this.setPathfindingMalus(BlockPathTypes.WATER_BORDER, 16.0F); this.setPathfindingMalus(BlockPathTypes.COCOA, -1.0F); this.setPathfindingMalus(BlockPathTypes.FENCE, -1.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.beeRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.beeRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.beeControllable; + } + + @Override + public double getMaxY() { + return level().purpurConfig.beeMaxY; + } + + @Override + public void travel(Vec3 vec3) { + super.travel(vec3); + if (getRider() != null && this.isControllable() && !onGround) { + float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2; + setSpeed(speed); + Vec3 mot = getDeltaMovement(); + move(MoverType.SELF, mot.multiply(speed, speed, speed)); + setDeltaMovement(mot.scale(0.9D)); + } + } + // Purpur end + @Override protected void defineSynchedData() { super.defineSynchedData(); @@ -185,6 +234,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(0, new Bee.BeeAttackGoal(this, 1.399999976158142D, true)); this.goalSelector.addGoal(1, new Bee.BeeEnterHiveGoal()); this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); @@ -200,6 +250,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { this.goalSelector.addGoal(7, new Bee.BeeGrowCropGoal()); this.goalSelector.addGoal(8, new Bee.BeeWanderGoal()); this.goalSelector.addGoal(9, new FloatGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new Bee.BeeHurtByOtherGoal(this)).setAlertOthers(new Class[0])); this.targetSelector.addGoal(2, new Bee.BeeBecomeAngryTargetGoal(this)); this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true)); @@ -348,7 +399,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { boolean wantsToEnterHive() { if (this.stayOutOfHiveCountdown <= 0 && !this.beePollinateGoal.isPollinating() && !this.hasStung() && this.getTarget() == null) { - boolean flag = this.isTiredOfLookingForNectar() || this.level().isRaining() || this.level().isNight() || this.hasNectar(); + boolean flag = this.isTiredOfLookingForNectar() || (this.level().isRaining() && !this.level().purpurConfig.beeCanWorkInRain) || (this.level().isNight() && !this.level().purpurConfig.beeCanWorkAtNight) || this.hasNectar(); // Purpur return flag && !this.isHiveNearFire(); } else { @@ -388,6 +439,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { this.hurt(this.damageSources().drown(), 1.0F); } + if (flag && !this.level().purpurConfig.beeDiesAfterSting) setHasStung(false); else // Purpur if (flag) { ++this.timeSinceSting; if (this.timeSinceSting % 5 == 0 && this.random.nextInt(Mth.clamp(1200 - this.timeSinceSting, 1, 1200)) == 0) { @@ -420,6 +472,26 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { } } + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.beeMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.beeBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.beeTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.beeAlwaysDropExp; + } + @Override public int getRemainingPersistentAngerTime() { return (Integer) this.entityData.get(Bee.DATA_REMAINING_ANGER_TIME); @@ -735,6 +807,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); @@ -791,6 +864,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 @@ -837,6 +911,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; @@ -881,16 +956,16 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { } } - private class BeeLookControl extends LookControl { + private class BeeLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur BeeLookControl(Mob entity) { super(entity); } @Override - public void tick() { + public void vanillaTick() { // Purpur if (!Bee.this.isAngry()) { - super.tick(); + super.vanillaTick(); // Purpur } } diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java index 90ce201bc7c47cef9bc59d7b535a7453854bac75..3347b39fa1bc3308aa3b70b4169523885b91a047 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Cat.java +++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java @@ -97,6 +97,51 @@ public class Cat extends TamableAnimal implements VariantHolder { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.catRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.catRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.catControllable; + } + + @Override + public void onMount(Player rider) { + super.onMount(rider); + setInSittingPose(false); + setLying(false); + setRelaxStateOne(false); + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.catMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.catBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.catTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.catAlwaysDropExp; + } + public ResourceLocation getResourceLocation() { return this.getVariant().texture(); } @@ -105,6 +150,7 @@ public class Cat extends TamableAnimal implements VariantHolder { protected void registerGoals() { this.temptGoal = new Cat.CatTemptGoal(this, 0.6D, Cat.TEMPT_INGREDIENT, true); this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new PanicGoal(this, 1.5D)); this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this)); this.goalSelector.addGoal(3, new Cat.CatRelaxOnOwnerGoal(this)); @@ -117,6 +163,7 @@ public class Cat extends TamableAnimal implements VariantHolder { this.goalSelector.addGoal(10, new BreedGoal(this, 0.8D)); this.goalSelector.addGoal(11, new WaterAvoidingRandomStrollGoal(this, 0.8D, 1.0000001E-5F)); this.goalSelector.addGoal(12, new LookAtPlayerGoal(this, Player.class, 10.0F)); + this.targetSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Rabbit.class, false, (Predicate) null)); this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR)); } @@ -308,6 +355,14 @@ public class Cat extends TamableAnimal implements VariantHolder { return Mth.lerp(tickDelta, this.relaxStateOneAmountO, this.relaxStateOneAmount); } + // Purpur start + @Override + public void tame(Player player) { + setCollarColor(level().purpurConfig.catDefaultCollarColor); + super.tame(player); + } + // Purpur end + @Nullable @Override public Cat getBreedOffspring(ServerLevel world, AgeableMob entity) { @@ -373,6 +428,7 @@ public class Cat extends TamableAnimal implements VariantHolder { @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { + if (getRider() != null) return InteractionResult.PASS; // Purpur ItemStack itemstack = player.getItemInHand(hand); Item item = itemstack.getItem(); @@ -419,7 +475,7 @@ public class Cat extends TamableAnimal implements VariantHolder { } } else if (this.isFood(itemstack)) { this.usePlayerItem(player, hand, itemstack); - if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit + if ((this.level().purpurConfig.alwaysTameInCreative && player.getAbilities().instabuild) || (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled())) { // CraftBukkit // Purpur this.tame(player); this.setOrderedToSit(true); this.level().broadcastEntityEvent(this, (byte) 7); diff --git a/src/main/java/net/minecraft/world/entity/animal/Chicken.java b/src/main/java/net/minecraft/world/entity/animal/Chicken.java index 84cefbc339ca368ac306d521012ce2f826a660b5..00bdb2b75221349cb40da2b5fc1563393fd17ef6 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Chicken.java +++ b/src/main/java/net/minecraft/world/entity/animal/Chicken.java @@ -54,16 +54,65 @@ public class Chicken extends Animal { this.setPathfindingMalus(BlockPathTypes.WATER, 0.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.chickenRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.chickenRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.chickenControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.chickenMaxHealth); + if (level().purpurConfig.chickenRetaliate) { + this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(2.0D); + } + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.chickenBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.chickenTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.chickenAlwaysDropExp; + } + @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); - this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + // this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D)); // Purpur - moved down this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); this.goalSelector.addGoal(3, new TemptGoal(this, 1.0D, Chicken.FOOD_ITEMS, false)); this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.1D)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); + // Purpur start + if (level().purpurConfig.chickenRetaliate) { + this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.0D, false)); + this.targetSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal(this)); + } else { + this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D)); + } + // Purpur end } @Override @@ -72,7 +121,7 @@ public class Chicken extends Animal { } public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 4.0D).add(Attributes.MOVEMENT_SPEED, 0.25D); + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 4.0D).add(Attributes.MOVEMENT_SPEED, 0.25D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/entity/animal/Cod.java b/src/main/java/net/minecraft/world/entity/animal/Cod.java index 824e5e4fe7619ae46061c3c978c9a044db8c84ab..f0b6118a9995bb41836685bbf94d2e7fb15761eb 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Cod.java +++ b/src/main/java/net/minecraft/world/entity/animal/Cod.java @@ -13,6 +13,33 @@ public class Cod extends AbstractSchoolingFish { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.codRidable; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.codControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.codMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.codTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.codAlwaysDropExp; + } + @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 0263acf81330ada1bb8ae7d52a3b04e08d0c4dcb..3cee7d5656b2df2997ceaee3489c02ce881e6875 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Cow.java +++ b/src/main/java/net/minecraft/world/entity/animal/Cow.java @@ -2,6 +2,7 @@ package net.minecraft.world.entity.animal; import javax.annotation.Nullable; import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.ParticleTypes; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; @@ -29,6 +30,7 @@ import net.minecraft.world.item.ItemUtils; import net.minecraft.world.item.Items; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; // CraftBukkit start import org.bukkit.craftbukkit.event.CraftEventFactory; @@ -37,25 +39,74 @@ import org.bukkit.event.player.PlayerBucketFillEvent; // CraftBukkit end public class Cow extends Animal { + private boolean isNaturallyAggressiveToPlayers; // Purpur public Cow(EntityType type, Level world) { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.cowRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.cowRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.cowControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.cowMaxHealth); + this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.cowNaturallyAggressiveToPlayersDamage); // Purpur + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.cowBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.cowTakeDamageFromWater; + } + + @Override + public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.MobSpawnType spawnReason, net.minecraft.world.entity.SpawnGroupData entityData, net.minecraft.nbt.CompoundTag entityNbt) { + this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance; + return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt); + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.cowAlwaysDropExp; + } + @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new PanicGoal(this, 2.0D)); + this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); + if (level().purpurConfig.cowFeedMushrooms > 0) this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, Ingredient.of(Items.WHEAT, Blocks.RED_MUSHROOM.asItem(), Blocks.BROWN_MUSHROOM.asItem()), false)); else // Purpur this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, Ingredient.of(Items.WHEAT), false)); this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25D)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, target -> isNaturallyAggressiveToPlayers)); // Purpur } public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D); + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur } @Override @@ -85,6 +136,7 @@ public class Cow extends Animal { @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { + if (getRider() != null) return InteractionResult.PASS; // Purpur ItemStack itemstack = player.getItemInHand(hand); if (itemstack.is(Items.BUCKET) && !this.isBaby()) { @@ -92,7 +144,7 @@ public class Cow extends Animal { PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand); if (event.isCancelled()) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } // CraftBukkit end @@ -101,6 +153,10 @@ public class Cow extends Animal { player.setItemInHand(hand, itemstack1); return InteractionResult.sidedSuccess(this.level().isClientSide); + // Purpur start - feed mushroom to change to mooshroom + } else if (level().purpurConfig.cowFeedMushrooms > 0 && this.getType() != EntityType.MOOSHROOM && isMushroom(itemstack)) { + return this.feedMushroom(player, itemstack); + // Purpur end } else { return super.mobInteract(player, hand); } @@ -116,4 +172,69 @@ public class Cow extends Animal { protected float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) { return this.isBaby() ? dimensions.height * 0.95F : 1.3F; } + + // Purpur start - feed mushroom to change to mooshroom + private int redMushroomsFed = 0; + private int brownMushroomsFed = 0; + + private boolean isMushroom(ItemStack stack) { + return stack.getItem() == Blocks.RED_MUSHROOM.asItem() || stack.getItem() == Blocks.BROWN_MUSHROOM.asItem(); + } + + private int incrementFeedCount(ItemStack stack) { + if (stack.getItem() == Blocks.RED_MUSHROOM.asItem()) { + return ++redMushroomsFed; + } else { + return ++brownMushroomsFed; + } + } + + private InteractionResult feedMushroom(Player player, ItemStack stack) { + level().broadcastEntityEvent(this, (byte) 18); // hearts + playSound(SoundEvents.COW_MILK, 1.0F, 1.0F); + if (incrementFeedCount(stack) < level().purpurConfig.cowFeedMushrooms) { + if (!player.getAbilities().instabuild) { + stack.shrink(1); + } + return InteractionResult.CONSUME; // require 5 mushrooms to transform (prevents mushroom duping) + } + MushroomCow mooshroom = EntityType.MOOSHROOM.create(level()); + if (mooshroom == null) { + return InteractionResult.PASS; + } + if (stack.getItem() == Blocks.BROWN_MUSHROOM.asItem()) { + mooshroom.setVariant(MushroomCow.MushroomType.BROWN); + } else { + mooshroom.setVariant(MushroomCow.MushroomType.RED); + } + mooshroom.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); + mooshroom.setHealth(this.getHealth()); + mooshroom.setAge(getAge()); + mooshroom.copyPosition(this); + mooshroom.setYBodyRot(this.yBodyRot); + mooshroom.setYHeadRot(this.getYHeadRot()); + mooshroom.yRotO = this.yRotO; + mooshroom.xRotO = this.xRotO; + if (this.hasCustomName()) { + mooshroom.setCustomName(this.getCustomName()); + } + if (CraftEventFactory.callEntityTransformEvent(this, mooshroom, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) { + return InteractionResult.PASS; + } + if (!new com.destroystokyo.paper.event.entity.EntityTransformedEvent(this.getBukkitEntity(), mooshroom.getBukkitEntity(), com.destroystokyo.paper.event.entity.EntityTransformedEvent.TransformedReason.INFECTED).callEvent()) { + return InteractionResult.PASS; + } + this.level().addFreshEntity(mooshroom); + this.remove(RemovalReason.DISCARDED); + if (!player.getAbilities().instabuild) { + stack.shrink(1); + } + for (int i = 0; i < 15; ++i) { + ((ServerLevel) level()).sendParticles(((ServerLevel) level()).players(), null, ParticleTypes.HAPPY_VILLAGER, + getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1, + random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true); + } + return InteractionResult.SUCCESS; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java index 8448c5d778998390cf2b683f36e4e18ca7ffdc34..4c9c07d470357c35bd5ba3c21437a2c44c52811e 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java @@ -82,19 +82,104 @@ public class Dolphin extends WaterAnimal { public static final Predicate ALLOWED_ITEMS = (entityitem) -> { return !entityitem.hasPickUpDelay() && entityitem.isAlive() && entityitem.isInWater(); }; + private boolean isNaturallyAggressiveToPlayers; // Purpur + private int spitCooldown; // Purpur public Dolphin(EntityType type, Level world) { super(type, world); - this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); + // Purpur start + class DolphinMoveControl extends SmoothSwimmingMoveControl { + private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterMoveControllerWASD; + private final Dolphin dolphin; + + public DolphinMoveControl(Dolphin dolphin, int pitchChange, int yawChange, float speedInWater, float speedInAir, boolean buoyant) { + super(dolphin, pitchChange, yawChange, speedInWater, speedInAir, buoyant); + this.dolphin = dolphin; + this.waterMoveControllerWASD = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(dolphin); + } + + @Override + public void tick() { + if (dolphin.getRider() != null && dolphin.isControllable()) { + purpurTick(dolphin.getRider()); + } else { + super.tick(); + } + } + + public void purpurTick(Player rider) { + if (dolphin.getAirSupply() < 150) { + // if drowning override player WASD controls to find air + super.tick(); + } else { + waterMoveControllerWASD.purpurTick(rider); + dolphin.setDeltaMovement(dolphin.getDeltaMovement().add(0.0D, 0.005D, 0.0D)); + } + } + }; + this.moveControl = new DolphinMoveControl(this, 85, 10, 0.02F, 0.1F, true); + // Purpur end this.lookControl = new SmoothSwimmingLookControl(this, 10); this.setCanPickUpLoot(true); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.dolphinRidable; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.dolphinControllable; + } + + @Override + public boolean onSpacebar() { + if (spitCooldown == 0 && getRider() != null) { + spitCooldown = level().purpurConfig.dolphinSpitCooldown; + + org.bukkit.craftbukkit.entity.CraftPlayer player = (org.bukkit.craftbukkit.entity.CraftPlayer) getRider().getBukkitEntity(); + if (!player.hasPermission("allow.special.dolphin")) { + return false; + } + + org.bukkit.Location loc = player.getEyeLocation(); + loc.setPitch(loc.getPitch() - 10); + org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(10).add(loc.toVector()); + + org.purpurmc.purpur.entity.DolphinSpit spit = new org.purpurmc.purpur.entity.DolphinSpit(level(), this); + spit.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), level().purpurConfig.dolphinSpitSpeed, 5.0F); + + level().addFreshEntity(spit); + playSound(SoundEvents.DOLPHIN_ATTACK, 1.0F, 1.0F + (random.nextFloat() - random.nextFloat()) * 0.2F); + return true; + } + return false; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.dolphinMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.dolphinTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.dolphinAlwaysDropExp; + } + @Nullable @Override public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) { this.setAirSupply(this.getMaxAirSupply()); this.setXRot(0.0F); + this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance; // Purpur return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt); } @@ -164,17 +249,21 @@ public class Dolphin extends WaterAnimal { protected void registerGoals() { this.goalSelector.addGoal(0, new BreathAirGoal(this)); this.goalSelector.addGoal(0, new TryFindWaterGoal(this)); + this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new Dolphin.DolphinSwimToTreasureGoal(this)); this.goalSelector.addGoal(2, new Dolphin.DolphinSwimWithPlayerGoal(this, 4.0D)); this.goalSelector.addGoal(4, new RandomSwimmingGoal(this, 1.0D, 10)); this.goalSelector.addGoal(4, new RandomLookAroundGoal(this)); this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(5, new DolphinJumpGoal(this, 10)); - this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2000000476837158D, true)); + //this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur - moved up this.goalSelector.addGoal(8, new Dolphin.PlayWithItemsGoal()); this.goalSelector.addGoal(8, new FollowBoatGoal(this)); this.goalSelector.addGoal(9, new AvoidEntityGoal<>(this, Guardian.class, 8.0F, 1.0D, 1.0D)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Guardian.class})).setAlertOthers()); + this.targetSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, target -> isNaturallyAggressiveToPlayers)); // Purpur } public static AttributeSupplier.Builder createAttributes() { @@ -225,7 +314,7 @@ public class Dolphin extends WaterAnimal { @Override protected boolean canRide(Entity entity) { - return true; + return boardingCooldown <= 0; // Purpur - make dolphin honor ride cooldown like all other non-boss mobs; } @Override @@ -260,6 +349,11 @@ public class Dolphin extends WaterAnimal { @Override public void tick() { super.tick(); + // Purpur start + if (spitCooldown > 0) { + spitCooldown--; + } + // Purpur end if (this.isNoAi()) { this.setAirSupply(this.getMaxAirSupply()); } else { @@ -405,6 +499,7 @@ public class Dolphin extends WaterAnimal { @Override public boolean canUse() { + if (this.dolphin.level().purpurConfig.dolphinDisableTreasureSearching) return false; // Purpur return this.dolphin.gotFish() && this.dolphin.getAirSupply() >= 100; } 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 62604ed2e82afd3603ccac5d9c6e285c431542b1..ca3096abbd54745af591101c7b306ed87f212954 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Fox.java +++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java @@ -35,6 +35,7 @@ import net.minecraft.util.RandomSource; import net.minecraft.util.StringRepresentable; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.AgeableMob; import net.minecraft.world.entity.Entity; @@ -88,6 +89,7 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.CaveVines; import net.minecraft.world.level.block.SweetBerryBushBlock; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.pathfinder.BlockPathTypes; import net.minecraft.world.phys.Vec3; @@ -141,6 +143,64 @@ public class Fox extends Animal implements VariantHolder { this.setCanPickUpLoot(true); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.foxRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.foxRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.foxControllable; + } + + @Override + public float getJumpPower() { + return getRider() != null && this.isControllable() ? 0.5F : super.getJumpPower(); + } + + @Override + public void onMount(Player rider) { + super.onMount(rider); + setCanPickUpLoot(false); + clearStates(); + setIsPouncing(false); + spitOutItem(getItemBySlot(EquipmentSlot.MAINHAND)); + setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); + } + + @Override + public void onDismount(Player rider) { + super.onDismount(rider); + setCanPickUpLoot(true); + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.foxMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.foxBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.foxTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.foxAlwaysDropExp; + } + @Override protected void defineSynchedData() { super.defineSynchedData(); @@ -160,6 +220,7 @@ public class Fox extends Animal implements VariantHolder { return entityliving instanceof AbstractSchoolingFish; }); this.goalSelector.addGoal(0, new Fox.FoxFloatGoal()); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(0, new ClimbOnTopOfPowderSnowGoal(this, this.level())); this.goalSelector.addGoal(1, new Fox.FaceplantGoal()); this.goalSelector.addGoal(2, new Fox.FoxPanicGoal(2.2D)); @@ -186,6 +247,7 @@ public class Fox extends Animal implements VariantHolder { this.goalSelector.addGoal(11, new Fox.FoxSearchForItemsGoal()); this.goalSelector.addGoal(12, new Fox.FoxLookAtPlayerGoal(this, Player.class, 24.0F)); this.goalSelector.addGoal(13, new Fox.PerchAndSearchGoal()); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(3, new Fox.DefendTrustedTargetGoal(LivingEntity.class, false, false, (entityliving) -> { return Fox.TRUSTED_TARGET_SELECTOR.test(entityliving) && !this.trusts(entityliving.getUUID()); })); @@ -342,6 +404,11 @@ public class Fox extends Animal implements VariantHolder { } private void setTargetGoals() { + // Purpur start - do not add duplicate goals + this.targetSelector.removeGoal(this.landTargetGoal); + this.targetSelector.removeGoal(this.turtleEggTargetGoal); + this.targetSelector.removeGoal(this.fishTargetGoal); + // Purpur end if (this.getVariant() == Fox.Type.RED) { this.targetSelector.addGoal(4, this.landTargetGoal); this.targetSelector.addGoal(4, this.turtleEggTargetGoal); @@ -375,6 +442,7 @@ public class Fox extends Animal implements VariantHolder { public void setVariant(Fox.Type variant) { this.entityData.set(Fox.DATA_TYPE_ID, variant.getId()); + this.setTargetGoals(); // Purpur - fix API bug not updating pathfinders on type change } List getTrustedUUIDs() { @@ -711,6 +779,29 @@ public class Fox extends Animal implements VariantHolder { return this.getTrustedUUIDs().contains(uuid); } + // Purpur start + @Override + public InteractionResult mobInteract(Player player, InteractionHand hand) { + if (level().purpurConfig.foxTypeChangesWithTulips) { + ItemStack itemstack = player.getItemInHand(hand); + if (getVariant() == Type.RED && itemstack.getItem() == Items.WHITE_TULIP) { + setVariant(Type.SNOW); + if (!player.getAbilities().instabuild) { + itemstack.shrink(1); + } + return InteractionResult.SUCCESS; + } else if (getVariant() == Type.SNOW && itemstack.getItem() == Items.ORANGE_TULIP) { + setVariant(Type.RED); + if (!player.getAbilities().instabuild) { + itemstack.shrink(1); + } + return InteractionResult.SUCCESS; + } + } + return super.mobInteract(player, hand); + } + // Purpur end + @Override // Paper start - Cancellable death event protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource source) { @@ -758,16 +849,16 @@ public class Fox extends Animal implements VariantHolder { return new Vec3(0.0D, (double) (0.55F * this.getEyeHeight()), (double) (this.getBbWidth() * 0.4F)); } - public class FoxLookControl extends LookControl { + public class FoxLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur public FoxLookControl() { super(Fox.this); } @Override - public void tick() { + public void vanillaTick() { // Purpur if (!Fox.this.isSleeping()) { - super.tick(); + super.vanillaTick(); // Purpur } } @@ -778,16 +869,16 @@ public class Fox extends Animal implements VariantHolder { } } - private class FoxMoveControl extends MoveControl { + private class FoxMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur public FoxMoveControl() { super(Fox.this); } @Override - public void tick() { + public void vanillaTick() { // Purpur if (Fox.this.canMove()) { - super.tick(); + super.vanillaTick(); // Purpur } } @@ -905,8 +996,10 @@ public class Fox extends Animal implements VariantHolder { CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer2, this.animal, this.partner, entityfox); } - this.animal.setAge(6000); - this.partner.setAge(6000); + // Purpur start + this.animal.setAge(this.animal.getPurpurBreedTime()); + this.partner.setAge(this.partner.getPurpurBreedTime()); + // Purpur end this.animal.resetLove(); this.partner.resetLove(); worldserver.addFreshEntityWithPassengers(entityfox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason @@ -1294,7 +1387,7 @@ public class Fox extends Animal implements VariantHolder { } protected void onReachedTarget() { - if (Fox.this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (Fox.this.level().purpurConfig.foxBypassMobGriefing || Fox.this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur BlockState iblockdata = Fox.this.level().getBlockState(this.blockPos); if (iblockdata.is(Blocks.SWEET_BERRY_BUSH)) { diff --git a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java index f383928fc5b331ddf128bdcb6a23010d8fe088d3..64aba511e615983988cdb6a0fd45b7d9d4f2f16d 100644 --- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java +++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java @@ -60,14 +60,59 @@ public class IronGolem extends AbstractGolem implements NeutralMob { private int remainingPersistentAngerTime; @Nullable private UUID persistentAngerTarget; + @Nullable private UUID summoner; // Purpur public IronGolem(EntityType type, Level world) { super(type, world); this.setMaxUpStep(1.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.ironGolemRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ironGolemRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.ironGolemControllable; + } + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ironGolemMaxHealth); + } + // Purpur end + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.ironGolemTakeDamageFromWater; + } + + @Nullable + public UUID getSummoner() { + return summoner; + } + + public void setSummoner(@Nullable UUID summoner) { + this.summoner = summoner; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.ironGolemAlwaysDropExp; + } + @Override protected void registerGoals() { + if (level().purpurConfig.ironGolemCanSwim) this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur + if (this.level().purpurConfig.ironGolemPoppyCalm) this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.ReceiveFlower(this)); // Purpur + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0D, true)); this.goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9D, 32.0F)); this.goalSelector.addGoal(2, new MoveBackToVillageGoal(this, 0.6D, false)); @@ -75,6 +120,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { this.goalSelector.addGoal(5, new OfferFlowerGoal(this)); this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new DefendVillageTargetGoal(this)); this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); @@ -139,6 +185,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { public void addAdditionalSaveData(CompoundTag nbt) { super.addAdditionalSaveData(nbt); nbt.putBoolean("PlayerCreated", this.isPlayerCreated()); + if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur this.addPersistentAngerSaveData(nbt); } @@ -146,6 +193,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { public void readAdditionalSaveData(CompoundTag nbt) { super.readAdditionalSaveData(nbt); this.setPlayerCreated(nbt.getBoolean("PlayerCreated")); + if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur this.readPersistentAngerSaveData(this.level(), nbt); } @@ -270,13 +318,13 @@ public class IronGolem extends AbstractGolem implements NeutralMob { ItemStack itemstack = player.getItemInHand(hand); if (!itemstack.is(Items.IRON_INGOT)) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } else { float f = this.getHealth(); this.heal(25.0F); if (this.getHealth() == f) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } else { float f1 = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F; @@ -285,6 +333,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { itemstack.shrink(1); } + if (this.level().purpurConfig.ironGolemHealCalm && isAngry() && getHealth() == getMaxHealth()) stopBeingAngry(); // Purpur return InteractionResult.sidedSuccess(this.level().isClientSide); } } diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java index a04374f91f2fbb31219d86b6ae63bcf8fdf7318c..e462b81aa1276a8f1222b74459359f6c6d57f739 100644 --- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java +++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java @@ -63,6 +63,43 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder { @@ -145,7 +182,7 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder> optional = this.getEffectFromItemStack(itemstack); if (!optional.isPresent()) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } Pair pair = (Pair) optional.get(); @@ -170,7 +207,7 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder(this, Chicken.class, false)); this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, false, false, Turtle.BABY_ON_LAND_SELECTOR)); } @@ -254,7 +293,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 f43c4fed59c4c75540008284ddb197d9a6b5487b..bd7c5f6768a54a3d8ffd585d91414e65936991da 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Panda.java +++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java @@ -111,6 +111,53 @@ public class Panda extends Animal { } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.pandaRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.pandaRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.pandaControllable; + } + + @Override + public void onMount(Player rider) { + super.onMount(rider); + setForwardMot(0.0F); + sit(false); + eat(false); + setOnBack(false); + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pandaMaxHealth); + setAttributes(); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.pandaBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.pandaTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.pandaAlwaysDropExp; + } + @Override public boolean canTakeItem(ItemStack stack) { EquipmentSlot enumitemslot = Mob.getEquipmentSlotForItem(stack); @@ -272,6 +319,7 @@ public class Panda extends Animal { @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(2, new Panda.PandaPanicGoal(this, 2.0D)); this.goalSelector.addGoal(2, new Panda.PandaBreedGoal(this, 1.0D)); this.goalSelector.addGoal(3, new Panda.PandaAttackGoal(this, 1.2000000476837158D, true)); @@ -287,6 +335,7 @@ public class Panda extends Animal { this.goalSelector.addGoal(12, new Panda.PandaRollGoal(this)); this.goalSelector.addGoal(13, new FollowParentGoal(this, 1.25D)); this.goalSelector.addGoal(14, new WaterAvoidingRandomStrollGoal(this, 1.0D)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new Panda.PandaHurtByTargetGoal(this, new Class[0])).setAlertOthers(new Class[0])); } @@ -610,7 +659,10 @@ public class Panda extends Animal { public void setAttributes() { if (this.isWeak()) { - this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(10.0D); + // Purpur start + net.minecraft.world.entity.ai.attributes.AttributeInstance maxHealth = this.getAttribute(Attributes.MAX_HEALTH); + maxHealth.setBaseValue(maxHealth.getValue() / 2); + // Purpur end } if (this.isLazy()) { @@ -633,7 +685,7 @@ public class Panda extends Animal { ItemStack itemstack = player.getItemInHand(hand); if (this.isScared()) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } else if (this.isOnBack()) { this.setOnBack(false); return InteractionResult.sidedSuccess(this.level().isClientSide); @@ -650,7 +702,7 @@ public class Panda extends Animal { this.setInLove(player); } else { if (this.level().isClientSide || this.isSitting() || this.isInWater()) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } this.tryToSit(); @@ -669,7 +721,7 @@ public class Panda extends Animal { return InteractionResult.SUCCESS; } else { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } } @@ -709,7 +761,7 @@ public class Panda extends Animal { return !this.isOnBack() && !this.isScared() && !this.isEating() && !this.isRolling() && !this.isSitting(); } - private static class PandaMoveControl extends MoveControl { + private static class PandaMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur private final Panda panda; @@ -719,9 +771,9 @@ public class Panda extends Animal { } @Override - public void tick() { + public void vanillaTick() { // Purpur if (this.panda.canPerformAction()) { - super.tick(); + super.vanillaTick(); // Purpur } } } diff --git a/src/main/java/net/minecraft/world/entity/animal/Parrot.java b/src/main/java/net/minecraft/world/entity/animal/Parrot.java index e1874aaa0065a6e8e6ae043713c22f08b58b5e21..0e20d9ed5b25f5aaab68dc3a4fa4e63ced280969 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java +++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java @@ -129,12 +129,88 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, Level world) { super(type, world); - this.moveControl = new FlyingMoveControl(this, 10, false); + // Purpur start + final org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); + class ParrotMoveControl extends FlyingMoveControl { + public ParrotMoveControl(Mob entity, int maxPitchChange, boolean noGravity) { + super(entity, maxPitchChange, noGravity); + } + + @Override + public void tick() { + if (mob.getRider() != null && mob.isControllable()) { + flyingController.purpurTick(mob.getRider()); + } else { + super.tick(); + } + } + + @Override + public boolean hasWanted() { + return mob.getRider() != null && mob.isControllable() ? getForwardMot() != 0 || getStrafeMot() != 0 : super.hasWanted(); + } + } + this.moveControl = new ParrotMoveControl(this, 10, false); + // Purpur end this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F); this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, -1.0F); this.setPathfindingMalus(BlockPathTypes.COCOA, -1.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.parrotRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.parrotRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.parrotControllable; + } + + @Override + public double getMaxY() { + return level().purpurConfig.parrotMaxY; + } + + @Override + public void travel(Vec3 vec3) { + super.travel(vec3); + if (getRider() != null && this.isControllable() && !onGround) { + float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2; + setSpeed(speed); + Vec3 mot = getDeltaMovement(); + move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.25, speed)); + setDeltaMovement(mot.scale(0.9D)); + } + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.parrotMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return 6000; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.parrotTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.parrotAlwaysDropExp; + } + @Nullable @Override public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) { @@ -153,8 +229,11 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { @@ -306,13 +386,13 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, Level world) { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.polarBearRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.polarBearRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.polarBearControllable; + } + + @Override + public boolean onSpacebar() { + if (!isStanding()) { + if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0) { + setStanding(true); + playSound(SoundEvents.POLAR_BEAR_WARNING, 1.0F, 1.0F); + } + } + return false; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.polarBearMaxHealth); + } + + public boolean canMate(Animal other) { + if (other == this) { + return false; + } else if (this.isStanding()) { + return false; + } else if (this.getTarget() != null) { + return false; + } else if (!(other instanceof PolarBear)) { + return false; + } else { + PolarBear bear = (PolarBear) other; + if (bear.isStanding()) { + return false; + } + if (bear.getTarget() != null) { + return false; + } + return this.isInLove() && bear.isInLove(); + } + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.polarBearBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.polarBearTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.polarBearAlwaysDropExp; + } + @Nullable @Override public AgeableMob getBreedOffspring(ServerLevel world, AgeableMob entity) { @@ -73,19 +143,27 @@ public class PolarBear extends Animal implements NeutralMob { @Override public boolean isFood(ItemStack stack) { - return false; + return level().purpurConfig.polarBearBreedableItem != null && stack.getItem() == level().purpurConfig.polarBearBreedableItem; // Purpur } @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new PolarBear.PolarBearMeleeAttackGoal()); this.goalSelector.addGoal(1, new PolarBear.PolarBearPanicGoal()); + // Purpur start + if (level().purpurConfig.polarBearBreedableItem != null) { + this.goalSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.BreedGoal(this, 1.0D)); + this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, net.minecraft.world.item.crafting.Ingredient.of(level().purpurConfig.polarBearBreedableItem), false)); + } + // Purpur end this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25D)); this.goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new PolarBear.PolarBearHurtByTargetGoal()); this.targetSelector.addGoal(2, new PolarBear.PolarBearAttackPlayersGoal()); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); @@ -202,6 +280,11 @@ public class PolarBear extends Animal implements NeutralMob { this.updatePersistentAnger((ServerLevel)this.level(), true); } + // Purpur start + if (isStanding() && --standTimer <= 0) { + setStanding(false); + } + // Purpur end } @Override @@ -231,6 +314,7 @@ public class PolarBear extends Animal implements NeutralMob { public void setStanding(boolean warning) { this.entityData.set(DATA_STANDING_ID, warning); + standTimer = warning ? 20 : -1; // Purpur } public float getStandingAnimationScale(float tickDelta) { diff --git a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java index a197337f2e09f53cf382022569c8836745d78769..54f5206b686c3cf4d2e5b470c07047a518f5dd00 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java +++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java @@ -45,6 +45,33 @@ public class Pufferfish extends AbstractFish { this.refreshDimensions(); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.pufferfishRidable; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.pufferfishControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pufferfishMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.pufferfishTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.pufferfishAlwaysDropExp; + } + @Override protected void defineSynchedData() { super.defineSynchedData(); diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java index 082351bd5f98d8738334b9164375f63fdc890455..81a33954aedffd18fcfa96babeaf6388715ab3f3 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java +++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java @@ -86,6 +86,7 @@ public class Rabbit extends Animal implements VariantHolder { private boolean wasOnGround; private int jumpDelayTicks; public int moreCarrotTicks; + private boolean actualJump; // Purpur public Rabbit(EntityType type, Level world) { super(type, world); @@ -93,9 +94,75 @@ public class Rabbit extends Animal implements VariantHolder { this.moveControl = new Rabbit.RabbitMoveControl(this); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.rabbitRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.rabbitRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.rabbitControllable; + } + + @Override + public boolean onSpacebar() { + if (onGround) { + actualJump = true; + jumpFromGround(); + actualJump = false; + } + return true; + } + + private void handleJumping() { + if (onGround) { + RabbitJumpControl jumpController = (RabbitJumpControl) jumpControl; + if (!wasOnGround) { + setJumping(false); + jumpController.setCanJump(false); + } + if (!jumpController.wantJump()) { + if (moveControl.hasWanted()) { + startJumping(); + } + } else if (!jumpController.canJump()) { + jumpController.setCanJump(true); + } + } + wasOnGround = onGround; + } + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.rabbitMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.rabbitBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.rabbitTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.rabbitAlwaysDropExp; + } + // Purpur end + @Override public void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level())); this.goalSelector.addGoal(1, new Rabbit.RabbitPanicGoal(this, 2.2D)); this.goalSelector.addGoal(2, new BreedGoal(this, 0.8D)); @@ -110,6 +177,14 @@ public class Rabbit extends Animal implements VariantHolder { @Override protected float getJumpPower() { + // Purpur start + if (getRider() != null && this.isControllable()) { + if (getForwardMot() < 0) { + setSpeed(getForwardMot() * 2F); + } + return actualJump ? 0.5F : 0.3F; + } + // Purpur end float f = 0.3F; if (this.horizontalCollision || this.moveControl.hasWanted() && this.moveControl.getWantedY() > this.getY() + 0.5D) { @@ -134,7 +209,7 @@ public class Rabbit extends Animal implements VariantHolder { } @Override - protected void jumpFromGround() { + public void jumpFromGround() { // Purpur - protected -> public super.jumpFromGround(); double d0 = this.moveControl.getSpeedModifier(); @@ -184,6 +259,13 @@ public class Rabbit extends Animal implements VariantHolder { @Override public void customServerAiStep() { + // Purpur start + if (getRider() != null && this.isControllable()) { + handleJumping(); + return; + } + // Purpur end + if (this.jumpDelayTicks > 0) { --this.jumpDelayTicks; } @@ -401,10 +483,23 @@ public class Rabbit extends Animal implements VariantHolder { } this.setVariant(entityrabbit_variant); + + // Purpur start + if (entityrabbit_variant != Variant.EVIL && world.getLevel().purpurConfig.rabbitNaturalToast > 0D && random.nextDouble() <= world.getLevel().purpurConfig.rabbitNaturalToast) { + setCustomName(Component.translatable("Toast")); + } + // Purpur end + return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData, entityNbt); } private static Rabbit.Variant getRandomRabbitVariant(LevelAccessor world, BlockPos pos) { + // Purpur start + Level level = world.getMinecraftWorld(); + if (level.purpurConfig.rabbitNaturalKiller > 0D && world.getRandom().nextDouble() <= level.purpurConfig.rabbitNaturalKiller) { + return Rabbit.Variant.EVIL; + } + // Purpur end Holder holder = world.getBiome(pos); int i = world.getRandom().nextInt(100); @@ -468,7 +563,7 @@ public class Rabbit extends Animal implements VariantHolder { } } - private static class RabbitMoveControl extends MoveControl { + private static class RabbitMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur private final Rabbit rabbit; private double nextJumpSpeed; @@ -479,14 +574,14 @@ public class Rabbit extends Animal implements VariantHolder { } @Override - public void tick() { + public void vanillaTick() { // Purpur if (this.rabbit.onGround() && !this.rabbit.jumping && !((Rabbit.RabbitJumpControl) this.rabbit.jumpControl).wantJump()) { this.rabbit.setSpeedModifier(0.0D); } else if (this.hasWanted()) { this.rabbit.setSpeedModifier(this.nextJumpSpeed); } - super.tick(); + super.vanillaTick(); // Purpur } @Override @@ -548,7 +643,7 @@ public class Rabbit extends Animal implements VariantHolder { @Override public boolean canUse() { if (this.nextStartTick <= 0) { - if (!this.rabbit.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (!this.rabbit.level().purpurConfig.rabbitBypassMobGriefing && !this.rabbit.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur return false; } diff --git a/src/main/java/net/minecraft/world/entity/animal/Salmon.java b/src/main/java/net/minecraft/world/entity/animal/Salmon.java index 0af79daa357f53a8871e293b57e16c099e5d3f64..382e47f26ee94506cb76463a677351b9bdcf8040 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Salmon.java +++ b/src/main/java/net/minecraft/world/entity/animal/Salmon.java @@ -13,6 +13,33 @@ public class Salmon extends AbstractSchoolingFish { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.salmonRidable; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.salmonControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.salmonMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.salmonTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.salmonAlwaysDropExp; + } + @Override public int getMaxSchoolSize() { return 5; diff --git a/src/main/java/net/minecraft/world/entity/animal/Sheep.java b/src/main/java/net/minecraft/world/entity/animal/Sheep.java index c0e89262c596fbdd0bb3c3f76baccb17a1bb5fcd..54de1f782e45cb3e3b1442aeb56b6e8547b065c0 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Sheep.java +++ b/src/main/java/net/minecraft/world/entity/animal/Sheep.java @@ -117,10 +117,48 @@ public class Sheep extends Animal implements Shearable { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.sheepRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.sheepRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.sheepControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.sheepMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.sheepBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.sheepTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.sheepAlwaysDropExp; + } + @Override protected void registerGoals() { this.eatBlockGoal = new EatBlockGoal(this); this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new PanicGoal(this, 1.25D)); this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); this.goalSelector.addGoal(3, new TemptGoal(this, 1.1D, Ingredient.of(Items.WHEAT), false)); @@ -255,7 +293,7 @@ public class Sheep extends Animal implements Shearable { return InteractionResult.PASS; } // CraftBukkit end - this.shear(SoundSource.PLAYERS); + this.shear(SoundSource.PLAYERS, net.minecraft.world.item.enchantment.EnchantmentHelper.getMobLooting(player)); // Purpur this.gameEvent(GameEvent.SHEAR, player); itemstack.hurtAndBreak(1, player, (entityhuman1) -> { entityhuman1.broadcastBreakEvent(hand); @@ -270,14 +308,15 @@ public class Sheep extends Animal implements Shearable { } @Override - public void shear(SoundSource shearedSoundCategory) { + public void shear(SoundSource shearedSoundCategory, int looting) { // Purpur this.level().playSound((Player) null, (Entity) this, SoundEvents.SHEEP_SHEAR, shearedSoundCategory, 1.0F, 1.0F); this.setSheared(true); int i = 1 + this.random.nextInt(3); + if (org.purpurmc.purpur.PurpurConfig.allowShearsLooting) i += looting; // Purpur for (int j = 0; j < i; ++j) { this.forceDrops = true; // CraftBukkit - ItemEntity entityitem = this.spawnAtLocation((ItemLike) Sheep.ITEM_BY_DYE.get(this.getColor()), 1); + ItemEntity entityitem = this.spawnAtLocation((ItemLike) Sheep.ITEM_BY_DYE.get(this.level().purpurConfig.sheepShearJebRandomColor && hasCustomName() && getCustomName().getString().equals("jeb_") ? DyeColor.random(this.level().random) : this.getColor()), 1); // Purpur this.forceDrops = false; // CraftBukkit if (entityitem != null) { diff --git a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java index 8adcfc8f6772a32b5915e4a07100e8eb735f907a..8b364fe9f3a3d47ae6daa331b8f16941ca17432a 100644 --- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java +++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java @@ -49,17 +49,56 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM private static final EntityDataAccessor DATA_PUMPKIN_ID = SynchedEntityData.defineId(SnowGolem.class, EntityDataSerializers.BYTE); private static final byte PUMPKIN_FLAG = 16; private static final float EYE_HEIGHT = 1.7F; + @Nullable private java.util.UUID summoner; // Purpur public SnowGolem(EntityType type, Level world) { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.snowGolemRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.snowGolemRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.snowGolemControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.snowGolemMaxHealth); + } + + @Nullable + public java.util.UUID getSummoner() { + return summoner; + } + + public void setSummoner(@Nullable java.util.UUID summoner) { + this.summoner = summoner; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.snowGolemAlwaysDropExp; + } + @Override protected void registerGoals() { - this.goalSelector.addGoal(1, new RangedAttackGoal(this, 1.25D, 20, 10.0F)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + this.goalSelector.addGoal(1, new RangedAttackGoal(this, level().purpurConfig.snowGolemAttackDistance, level().purpurConfig.snowGolemSnowBallMin, level().purpurConfig.snowGolemSnowBallMax, level().purpurConfig.snowGolemSnowBallModifier)); // Purpur this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D, 1.0000001E-5F)); this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(4, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Mob.class, 10, true, false, (entityliving) -> { return entityliving instanceof Enemy; })); @@ -79,6 +118,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM public void addAdditionalSaveData(CompoundTag nbt) { super.addAdditionalSaveData(nbt); nbt.putBoolean("Pumpkin", this.hasPumpkin()); + if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur } @Override @@ -87,12 +127,13 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM if (nbt.contains("Pumpkin")) { this.setPumpkin(nbt.getBoolean("Pumpkin")); } + if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur } @Override public boolean isSensitiveToWater() { - return true; + return this.level().purpurConfig.snowGolemTakeDamageFromWater; // Purpur } @Override @@ -103,10 +144,11 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM this.hurt(this.damageSources().melting, 1.0F); // CraftBukkit - DamageSource.BURN -> CraftEventFactory.MELTING } - if (!this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (!this.level().purpurConfig.snowGolemBypassMobGriefing && !this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur return; } + if (getRider() != null && this.isControllable() && !level().purpurConfig.snowGolemLeaveTrailWhenRidden) return; // Purpur - don't leave snow trail when being ridden BlockState iblockdata = Blocks.SNOW.defaultBlockState(); for (int i = 0; i < 4; ++i) { @@ -154,10 +196,10 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM if (itemstack.is(Items.SHEARS) && this.readyForShearing()) { // CraftBukkit start if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } // CraftBukkit end - this.shear(SoundSource.PLAYERS); + this.shear(SoundSource.PLAYERS, net.minecraft.world.item.enchantment.EnchantmentHelper.getMobLooting(player)); // Purpur this.gameEvent(GameEvent.SHEAR, player); if (!this.level().isClientSide) { itemstack.hurtAndBreak(1, player, (entityhuman1) -> { @@ -166,17 +208,27 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM } return InteractionResult.sidedSuccess(this.level().isClientSide); + // Purpur start + } else if (level().purpurConfig.snowGolemPutPumpkinBack && !hasPumpkin() && itemstack.getItem() == Blocks.CARVED_PUMPKIN.asItem()) { + setPumpkin(true); + if (!player.getAbilities().instabuild) { + itemstack.shrink(1); + } + return InteractionResult.SUCCESS; + // Purpur end } else { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } } @Override - public void shear(SoundSource shearedSoundCategory) { + public void shear(SoundSource shearedSoundCategory, int looting) { // Purpur this.level().playSound((Player) null, (Entity) this, SoundEvents.SNOW_GOLEM_SHEAR, shearedSoundCategory, 1.0F, 1.0F); if (!this.level().isClientSide()) { this.setPumpkin(false); this.forceDrops = true; // CraftBukkit + if (level().purpurConfig.snowGolemDropsPumpkin) // Purpur + for (int i = 0; i < 1 + (org.purpurmc.purpur.PurpurConfig.allowShearsLooting ? looting : 0); i++) // Purpur this.spawnAtLocation(new ItemStack(Items.CARVED_PUMPKIN), 1.7F); this.forceDrops = false; // CraftBukkit } diff --git a/src/main/java/net/minecraft/world/entity/animal/Squid.java b/src/main/java/net/minecraft/world/entity/animal/Squid.java index f60c4cd0543fd5d50fa7e2c1a9e8381227adb540..dd7f2beabf0edad4143ac2365ac04a22edf1f75e 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Squid.java +++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java @@ -44,13 +44,66 @@ public class Squid extends WaterAnimal { public Squid(EntityType type, Level world) { super(type, world); - //this.random.setSeed((long)this.getId()); // Paper - we set the random to shared, do not clobber the seed + if (!world.purpurConfig.entitySharedRandom) this.random.setSeed((long) this.getId()); // Paper - we set the random to shared, do not clobber the seed // Purpur this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.squidRidable; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.squidControllable; + } + + protected void rotateVectorAroundY(org.bukkit.util.Vector vector, double degrees) { + double rad = Math.toRadians(degrees); + double cos = Math.cos(rad); + double sine = Math.sin(rad); + double x = vector.getX(); + double z = vector.getZ(); + vector.setX(cos * x - sine * z); + vector.setZ(sine * x + cos * z); + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.squidMaxHealth); + } + + @Override + public net.minecraft.world.phys.AABB getAxisForFluidCheck() { + // Stops squids from floating just over the water + return super.getAxisForFluidCheck().offsetY(level().purpurConfig.squidOffsetWaterCheck); + } + + public boolean canFly() { + return this.level().purpurConfig.squidsCanFly; + } + + @Override + public boolean isInWater() { + return this.wasTouchingWater || canFly(); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.squidTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.squidAlwaysDropExp; + } + @Override protected void registerGoals() { this.goalSelector.addGoal(0, new Squid.SquidRandomMovementGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new Squid.SquidFleeGoal()); } @@ -119,6 +172,7 @@ public class Squid extends WaterAnimal { } 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; @@ -298,10 +352,41 @@ public class Squid extends WaterAnimal { @Override public void tick() { + // Purpur start + Player rider = squid.getRider(); + if (rider != null && squid.isControllable()) { + if (rider.jumping) { + squid.onSpacebar(); + } + float forward = rider.getForwardMot(); + float strafe = rider.getStrafeMot(); + float speed = (float) squid.getAttributeValue(Attributes.MOVEMENT_SPEED) * 10F; + if (forward < 0.0F) { + speed *= -0.5; + } + org.bukkit.util.Vector dir = rider.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(speed / 20.0F); + if (strafe != 0.0F) { + if (forward == 0.0F) { + dir.setY(0); + rotateVectorAroundY(dir, strafe > 0.0F ? -90 : 90); + } else if (forward < 0.0F) { + rotateVectorAroundY(dir, strafe > 0.0F ? 45 : -45); + } else { + rotateVectorAroundY(dir, strafe > 0.0F ? -45 : 45); + } + } + if (forward != 0.0F || strafe != 0.0F) { + squid.setMovementVector((float) dir.getX(), (float) dir.getY(), (float) dir.getZ()); + } else { + squid.setMovementVector(0.0F, 0.0F, 0.0F); + } + return; + } + // Purpur end int i = this.squid.getNoActionTime(); if (i > 100) { this.squid.setMovementVector(0.0F, 0.0F, 0.0F); - } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.wasTouchingWater || !this.squid.hasMovementVector()) { + } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.isInWater() || !this.squid.hasMovementVector()) { // Purpur float f = this.squid.getRandom().nextFloat() * ((float)Math.PI * 2F); float g = Mth.cos(f) * 0.2F; float h = -0.1F + this.squid.getRandom().nextFloat() * 0.2F; diff --git a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java index b05b560b7570e97bc234b75f26233909fcf575b3..87b6f6b10ba6e3d9c6a42298a2019a526a183d90 100644 --- a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java +++ b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java @@ -42,6 +42,33 @@ public class TropicalFish extends AbstractSchoolingFish implements VariantHolder super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.tropicalFishRidable; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.tropicalFishControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.tropicalFishMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.tropicalFishTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.tropicalFishAlwaysDropExp; + } + 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 d3c15d029d5f003cba3c89f7ea1f3ed4f943f2bd..1502f13acd0a104efe470e605826213ea92af191 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java +++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java @@ -83,6 +83,43 @@ public class Turtle extends Animal { this.setMaxUpStep(1.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.turtleRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.turtleRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.turtleControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.turtleMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.turtleBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.turtleTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.turtleAlwaysDropExp; + } + public void setHomePos(BlockPos pos) { this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos... } @@ -185,6 +222,7 @@ public class Turtle extends Animal { @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(0, new Turtle.TurtlePanicGoal(this, 1.2D)); this.goalSelector.addGoal(1, new Turtle.TurtleBreedGoal(this, 1.0D)); this.goalSelector.addGoal(1, new Turtle.TurtleLayEggGoal(this, 1.0D)); @@ -342,13 +380,15 @@ public class Turtle extends Animal { org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = null; // CraftBukkit } - private static class TurtleMoveControl extends MoveControl { + private static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur private final Turtle turtle; + private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur TurtleMoveControl(Turtle turtle) { super(turtle); this.turtle = turtle; + waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(turtle, 0.25D); // Purpur } private void updateSpeed() { @@ -367,8 +407,18 @@ public class Turtle extends Animal { } + // Purpur start + public void purpurTick(Player rider) { + if (turtle.isInWater()) { + waterController.purpurTick(rider); + } else { + super.purpurTick(rider); + } + } + // Purpur end + @Override - public void tick() { + public void vanillaTick() { // Purpur this.updateSpeed(); if (this.operation == MoveControl.Operation.MOVE_TO && !this.turtle.getNavigation().isDone()) { double d0 = this.wantedX - this.turtle.getX(); @@ -384,7 +434,7 @@ public class Turtle extends Animal { this.turtle.setYRot(this.rotlerp(this.turtle.getYRot(), f, 90.0F)); this.turtle.yBodyRot = this.turtle.getYRot(); - float f1 = (float) (this.speedModifier * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); + float f1 = (float) (this.getSpeedModifier() * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); this.turtle.setSpeed(Mth.lerp(0.125F, this.turtle.getSpeed(), f1)); this.turtle.setDeltaMovement(this.turtle.getDeltaMovement().add(0.0D, (double) this.turtle.getSpeed() * d1 * 0.1D, 0.0D)); diff --git a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java index cd2ce5bcb8c30e4657cd0e340d80544c7e805905..c8c6fed3f93903bb5c6145930538d415f6f59738 100644 --- a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java +++ b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java @@ -82,6 +82,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 - return pos.getY() >= j && pos.getY() <= i && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER); + return ((reason == MobSpawnType.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 57ceec70bb150afaa66962090b142048d5b50c2f..64bceae4d06b35fcbecb0daca2496ba30e39d995 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Wolf.java +++ b/src/main/java/net/minecraft/world/entity/animal/Wolf.java @@ -10,6 +10,7 @@ import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; import net.minecraft.tags.BlockTags; @@ -17,9 +18,12 @@ import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.util.TimeUtil; import net.minecraft.util.valueproviders.UniformInt; +import net.minecraft.world.DifficultyInstance; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.AgeableMob; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; @@ -29,6 +33,7 @@ import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.MobSpawnType; import net.minecraft.world.entity.NeutralMob; import net.minecraft.world.entity.Pose; +import net.minecraft.world.entity.SpawnGroupData; import net.minecraft.world.entity.TamableAnimal; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; @@ -37,6 +42,7 @@ import net.minecraft.world.entity.ai.goal.BegGoal; import net.minecraft.world.entity.ai.goal.BreedGoal; import net.minecraft.world.entity.ai.goal.FloatGoal; import net.minecraft.world.entity.ai.goal.FollowOwnerGoal; +import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.LeapAtTargetGoal; import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; import net.minecraft.world.entity.ai.goal.MeleeAttackGoal; @@ -64,6 +70,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.pathfinder.BlockPathTypes; @@ -84,6 +91,37 @@ public class Wolf extends TamableAnimal implements NeutralMob { return entitytypes == EntityType.SHEEP || entitytypes == EntityType.RABBIT || entitytypes == EntityType.FOX; }; + // Purpur start - rabid wolf spawn chance + private boolean isRabid = false; + private static final Predicate RABID_PREDICATE = entity -> entity instanceof ServerPlayer || entity instanceof Mob; + private final Goal PATHFINDER_VANILLA = new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR); + private final Goal PATHFINDER_RABID = new NonTameRandomTargetGoal<>(this, LivingEntity.class, false, RABID_PREDICATE); + private static final class AvoidRabidWolfGoal extends AvoidEntityGoal { + private final Wolf wolf; + + public AvoidRabidWolfGoal(Wolf wolf, float distance, double minSpeed, double maxSpeed) { + super(wolf, Wolf.class, distance, minSpeed, maxSpeed); + this.wolf = wolf; + } + + @Override + public boolean canUse() { + return super.canUse() && !this.wolf.isRabid() && this.toAvoid != null && this.toAvoid.isRabid(); // wolves which are not rabid run away from rabid wolves + } + + @Override + public void start() { + this.wolf.setTarget(null); + super.start(); + } + + @Override + public void tick() { + this.wolf.setTarget(null); + super.tick(); + } + } + // Purpur end private static final float START_HEALTH = 8.0F; private static final float TAME_HEALTH = 20.0F; private float interestedAngle; @@ -103,12 +141,93 @@ public class Wolf extends TamableAnimal implements NeutralMob { this.setPathfindingMalus(BlockPathTypes.DANGER_POWDER_SNOW, -1.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.wolfRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wolfRidableInWater; + } + + public void onMount(Player rider) { + super.onMount(rider); + setInSittingPose(false); + } + + @Override + public boolean isControllable() { + return level().purpurConfig.wolfControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.wolfMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.wolfBreedingTicks; + } + + public boolean isRabid() { + return this.isRabid; + } + + public void setRabid(boolean isRabid) { + this.isRabid = isRabid; + updatePathfinders(true); + } + + public void updatePathfinders(boolean modifyEffects) { + this.targetSelector.removeGoal(PATHFINDER_VANILLA); + this.targetSelector.removeGoal(PATHFINDER_RABID); + if (this.isRabid) { + setTame(false); + setOwnerUUID(null); + this.targetSelector.addGoal(5, PATHFINDER_RABID); + if (modifyEffects) this.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 1200)); + } else { + this.targetSelector.addGoal(5, PATHFINDER_VANILLA); + this.stopBeingAngry(); + if (modifyEffects) this.removeEffect(MobEffects.CONFUSION); + } + } + + @Override + public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType type, @Nullable SpawnGroupData data, @Nullable CompoundTag nbt) { + this.isRabid = world.getLevel().purpurConfig.wolfNaturalRabid > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.wolfNaturalRabid; + this.updatePathfinders(false); + return super.finalizeSpawn(world, difficulty, type, data, nbt); + } + + @Override + public void tame(Player player) { + setCollarColor(level().purpurConfig.wolfDefaultCollarColor); + super.tame(player); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.wolfTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.wolfAlwaysDropExp; + } + @Override protected void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new Wolf.WolfPanicGoal(1.5D)); this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this)); this.goalSelector.addGoal(3, new Wolf.WolfAvoidEntityGoal<>(this, Llama.class, 24.0F, 1.5D, 1.5D)); + this.goalSelector.addGoal(3, new AvoidRabidWolfGoal(this, 24.0F, 1.5D, 1.5D)); // Purpur this.goalSelector.addGoal(4, new LeapAtTargetGoal(this, 0.4F)); this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0D, true)); this.goalSelector.addGoal(6, new FollowOwnerGoal(this, 1.0D, 10.0F, 2.0F, false)); @@ -117,11 +236,12 @@ public class Wolf extends TamableAnimal implements NeutralMob { this.goalSelector.addGoal(9, new BegGoal(this, 8.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(10, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new OwnerHurtByTargetGoal(this)); this.targetSelector.addGoal(2, new OwnerHurtTargetGoal(this)); this.targetSelector.addGoal(3, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); - this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, Wolf.PREY_SELECTOR)); + // this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, Wolf.PREY_SELECTOR)); // Purpur - moved to updatePathfinders() this.targetSelector.addGoal(6, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR)); this.targetSelector.addGoal(7, new NearestAttackableTargetGoal<>(this, AbstractSkeleton.class, false)); this.targetSelector.addGoal(8, new ResetUniversalAngerTargetGoal<>(this, true)); @@ -148,6 +268,7 @@ public class Wolf extends TamableAnimal implements NeutralMob { public void addAdditionalSaveData(CompoundTag nbt) { super.addAdditionalSaveData(nbt); nbt.putByte("CollarColor", (byte) this.getCollarColor().getId()); + nbt.putBoolean("Purpur.IsRabid", this.isRabid); // Purpur this.addPersistentAngerSaveData(nbt); } @@ -157,6 +278,10 @@ public class Wolf extends TamableAnimal implements NeutralMob { if (nbt.contains("CollarColor", 99)) { this.setCollarColor(DyeColor.byId(nbt.getInt("CollarColor"))); } + // Purpur start + this.isRabid = nbt.getBoolean("Purpur.IsRabid"); + this.updatePathfinders(false); + // Purpur end this.readPersistentAngerSaveData(this.level(), nbt); } @@ -201,6 +326,11 @@ public class Wolf extends TamableAnimal implements NeutralMob { public void tick() { super.tick(); if (this.isAlive()) { + // Purpur start + if (this.age % 300 == 0 && this.isRabid()) { + this.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 400)); + } + // Purpur end this.interestedAngleO = this.interestedAngle; if (this.isInterested()) { this.interestedAngle += (1.0F - this.interestedAngle) * 0.4F; @@ -405,7 +535,7 @@ public class Wolf extends TamableAnimal implements NeutralMob { } // CraftBukkit - added event call and isCancelled check. - if (this.random.nextInt(3) == 0 && !CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { + if ((this.level().purpurConfig.alwaysTameInCreative && player.getAbilities().instabuild) || (this.random.nextInt(3) == 0 && !CraftEventFactory.callEntityTameEvent(this, player).isCancelled())) { // Purpur this.tame(player); this.navigation.stop(); this.setTarget((LivingEntity) null); @@ -416,6 +546,19 @@ public class Wolf extends TamableAnimal implements NeutralMob { } return InteractionResult.SUCCESS; + // Purpur start + } else if (this.level().purpurConfig.wolfMilkCuresRabies && itemstack.getItem() == Items.MILK_BUCKET && this.isRabid()) { + if (!player.isCreative()) { + player.setItemInHand(hand, new ItemStack(Items.BUCKET)); + } + this.setRabid(false); + for (int i = 0; i < 10; ++i) { + ((ServerLevel) level()).sendParticles(((ServerLevel) level()).players(), null, ParticleTypes.HAPPY_VILLAGER, + getX() + random.nextFloat(), getY() + (random.nextFloat() * 1.5), getZ() + random.nextFloat(), 1, + random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true); + } + return InteractionResult.SUCCESS; + // Purpur end } else { return super.mobInteract(player, hand); } diff --git a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java index 1a0eee3b766a5ce5623c32ed9c023a0f80db1d1a..7166f4a39fd615e10d7b1f53c57363832a41f365 100644 --- a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java +++ b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java @@ -101,10 +101,23 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS private float spinningAnimationTicks; private float spinningAnimationTicks0; public boolean forceDancing = false; // CraftBukkit + private org.purpurmc.purpur.controller.FlyingMoveControllerWASD purpurController; // Purpur public Allay(EntityType type, Level world) { super(type, world); - this.moveControl = new FlyingMoveControl(this, 20, true); + // Purpur start + this.purpurController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.1F, 0.5F); + this.moveControl = new FlyingMoveControl(this, 20, true) { + @Override + public void tick() { + if (mob.getRider() != null && mob.isControllable()) { + purpurController.purpurTick(mob.getRider()); + } else { + super.tick(); + } + } + }; + // Purpur end this.setCanPickUpLoot(this.canPickUpLoot()); this.vibrationUser = new Allay.VibrationUser(); this.vibrationData = new VibrationSystem.Data(); @@ -118,6 +131,28 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS } // CraftBukkit end + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.allayRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.allayRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.allayControllable; + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + } + // Purpur end + @Override protected Brain.Provider brainProvider() { return Brain.provider(Allay.MEMORY_TYPES, Allay.SENSOR_TYPES); @@ -225,13 +260,13 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep() { - this.level().getProfiler().push("allayBrain"); - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + //this.level().getProfiler().push("allayBrain"); // Purpur + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel) this.level(), this); - this.level().getProfiler().pop(); - this.level().getProfiler().push("allayActivityUpdate"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("allayActivityUpdate"); // Purpur AllayAi.updateActivity(this); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur super.customServerAiStep(); } @@ -372,9 +407,31 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS @Override public boolean wantsToPickUp(ItemStack stack) { - ItemStack itemstack1 = this.getItemInHand(InteractionHand.MAIN_HAND); - - return !itemstack1.isEmpty() && this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.inventory.canAddItem(stack) && this.allayConsidersItemEqual(itemstack1, stack); + // Purpur start + if (!this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + return false; + } + ItemStack itemStack = this.getItemInHand(InteractionHand.MAIN_HAND); + if (itemStack.isEmpty()) { + return false; + } + if (!allayConsidersItemEqual(itemStack, stack)) { + return false; + } + if (!this.inventory.canAddItem(stack)) { + return false; + } + for (String tag : this.level().purpurConfig.allayRespectNBT) { + if (stack.hasTag() && itemStack.hasTag()) { + Tag tag1 = stack.getTag().get(tag); + Tag tag2 = itemStack.getTag().get(tag); + if (!Objects.equals(tag1, tag2)) { + return false; + } + } + } + return true; + // Purpur end } private boolean allayConsidersItemEqual(ItemStack stack, ItemStack stack2) { diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java index 42e22a4b9cb6841de04862cc81454da3232aa65a..07abf72b7aed98652ac6639b26ade358fcd9135a 100644 --- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java +++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java @@ -98,6 +98,43 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder getModelRotationValues() { return this.modelRotationValues; @@ -288,13 +325,13 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder optional = this.getBrain().getMemory(MemoryModuleType.PLAY_DEAD_TICKS); @@ -521,14 +558,22 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder brain = (Brain) this.getBrain(); // Paper - decompile fix brain.tick((ServerLevel)this.level(), this); - this.level().getProfiler().pop(); - this.level().getProfiler().push("camelActivityUpdate"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("camelActivityUpdate"); // Purpur CamelAi.updateActivity(this); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur super.customServerAiStep(); } @@ -319,6 +330,23 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Rider return this.dashCooldown; } + // Purpur start + @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 + @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 80ddb3059d6484c2b90c55ef601043798f1a4b50..dc1e8bcd8049d79c0e383ccd6a5697f79a3a2ebd 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 @@ -77,16 +77,69 @@ 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 + private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurWaterController; // Purpur public Frog(EntityType type, Level world) { super(type, world); this.lookControl = new Frog.FrogLookControl(this); this.setPathfindingMalus(BlockPathTypes.WATER, 4.0F); this.setPathfindingMalus(BlockPathTypes.TRAPDOOR, -1.0F); - this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); + // Purpur start + this.purpurLandController = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.2F); + this.purpurWaterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F); + this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) { + @Override + public void tick() { + net.minecraft.world.entity.player.Player rider = mob.getRider(); + if (rider != null && mob.isControllable()) { + if (mob.isInWater()) { + purpurWaterController.purpurTick(rider); + mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, -0.005D, 0.0D)); + } else { + purpurLandController.purpurTick(rider); + } + } else { + super.tick(); + } + } + }; + // Purpur end this.setMaxUpStep(1.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.frogRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.frogRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.frogControllable; + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + } + + @Override + public float getJumpPower() { + return (getRider() != null && isControllable()) ? level().purpurConfig.frogRidableJumpHeight * this.getBlockJumpFactor() : super.getJumpPower(); + } + // Purpur end + + public int getPurpurBreedTime() { + return this.level().purpurConfig.frogBreedingTicks; + } + @Override protected Brain.Provider brainProvider() { return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); @@ -165,13 +218,13 @@ public class Frog extends Animal implements VariantHolder { private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep() { - this.level().getProfiler().push("frogBrain"); - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + //this.level().getProfiler().push("frogBrain"); // Purpur + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel)this.level(), this); - this.level().getProfiler().pop(); - this.level().getProfiler().push("frogActivityUpdate"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("frogActivityUpdate"); // Purpur FrogAi.updateActivity(this); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur super.customServerAiStep(); } @@ -347,7 +400,7 @@ public class Frog extends Animal implements VariantHolder { return world.getBlockState(pos.below()).is(BlockTags.FROGS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos); } - class FrogLookControl extends LookControl { + class FrogLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur FrogLookControl(Mob entity) { super(entity); } diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java index 6ed4ac06c76b8d0d6e8db778cade15dbd1e3e5f5..6b012bea26e8ef0c04571f43da67f6e108188830 100644 --- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java +++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java @@ -45,13 +45,50 @@ public class Tadpole extends AbstractFish { protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS); protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING); public boolean ageLocked; // Paper + private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurController; // Purpur public Tadpole(EntityType type, Level world) { super(type, world); - this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); + // Purpur start + this.purpurController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F); + this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) { + @Override + public void tick() { + Player rider = mob.getRider(); + if (rider != null && mob.isControllable()) { + purpurController.purpurTick(rider); + mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, 0.002D, 0.0D)); + } else { + super.tick(); + } + } + }; + // Purpur end this.lookControl = new SmoothSwimmingLookControl(this, 10); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.tadpoleRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.tadpoleRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.tadpoleControllable; + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + } + // Purpur end + @Override protected PathNavigation createNavigation(Level world) { return new WaterBoundPathNavigation(this, world); @@ -80,13 +117,13 @@ public class Tadpole extends AbstractFish { private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep() { - this.level().getProfiler().push("tadpoleBrain"); - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + //this.level().getProfiler().push("tadpoleBrain"); // Purpur + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel) this.level(), this); - this.level().getProfiler().pop(); - this.level().getProfiler().push("tadpoleActivityUpdate"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("tadpoleActivityUpdate"); // Purpur TadpoleAi.updateActivity(this); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur super.customServerAiStep(); } diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java index 7f21d1d400c8a5615ed1a787dcb0680338f8c28d..b04af8b72357b455597d80592c8b8b0c9da744cd 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 @@ -90,6 +90,38 @@ public class Goat extends Animal { return InstrumentItem.create(Items.GOAT_HORN, (Holder) holderset.getRandomElement(randomsource).get()); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.goatRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.goatRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.goatControllable; + } + // Purpur end + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.goatBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.goatTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.goatAlwaysDropExp; + } + @Override protected Brain.Provider brainProvider() { return Brain.provider(Goat.MEMORY_TYPES, Goat.SENSOR_TYPES); @@ -192,13 +224,14 @@ public class Goat extends Animal { private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep() { - this.level().getProfiler().push("goatBrain"); - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + //this.level().getProfiler().push("goatBrain"); // Purpur + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider // Purpur - TODO: Uncomment when pufferfish patch + //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider // Purpur - TODO: Uncomment when pufferfish patch this.getBrain().tick((ServerLevel) this.level(), this); - this.level().getProfiler().pop(); - this.level().getProfiler().push("goatActivityUpdate"); + //this.level().getProfiler().pop(); // Purpur + //this.level().getProfiler().push("goatActivityUpdate"); // Purpur GoatAi.updateActivity(this); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur super.customServerAiStep(); } @@ -390,6 +423,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 49d7109b6ca63c8073db777549a65b2fcb70967e..fdf41f4990923aa4309febb9c0ca8009de8e16f1 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 @@ -147,12 +147,60 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, protected AbstractHorse(EntityType type, Level world) { super(type, world); + this.moveControl = new net.minecraft.world.entity.ai.control.MoveControl(this); // Purpur - use vanilla controller + this.lookControl = new net.minecraft.world.entity.ai.control.LookControl(this); // Purpur - use vanilla controller this.setMaxUpStep(1.0F); this.createInventory(); } + // Purpur start + @Override + public boolean isRidable() { + return false; // vanilla handles + } + // Purpur end + + @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; + } + @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur this.goalSelector.addGoal(1, new PanicGoal(this, 1.2D)); this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2D)); this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D, AbstractHorse.class)); @@ -163,6 +211,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, if (this.canPerformRearing()) { this.goalSelector.addGoal(9, new RandomStandGoal(this)); } + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur this.addBehaviourGoals(); } @@ -335,7 +384,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, @Override protected int calculateFallDamage(float fallDistance, float damageMultiplier) { - return Mth.ceil((fallDistance * 0.5F - 3.0F) * damageMultiplier); + return Mth.ceil((fallDistance * 0.5F - this.safeFallDistance) * damageMultiplier); } protected int getInventorySize() { @@ -1247,7 +1296,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, entityData = new AgeableMob.AgeableMobGroupData(0.2F); } - this.randomizeAttributes(world.getRandom()); + // this.randomizeAttributes(world.getRandom()); // Purpur - replaced by initAttributes() return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData, entityNbt); } diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java index e0dfee0e0ce091d5ae0ec740e939c2c50915c104..72ad12175325091397459e06743875cce6df8d94 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java @@ -15,6 +15,43 @@ public class Donkey extends AbstractChestedHorse { super(type, world); } + // Purpur start + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.donkeyRidableInWater; + } + // Purpur end + + @Override + public float generateMaxHealth(net.minecraft.util.RandomSource random) { + return (float) generateMaxHealth(this.level().purpurConfig.donkeyMaxHealthMin, this.level().purpurConfig.donkeyMaxHealthMax); + } + + @Override + public double generateJumpStrength(net.minecraft.util.RandomSource random) { + return generateJumpStrength(this.level().purpurConfig.donkeyJumpStrengthMin, this.level().purpurConfig.donkeyJumpStrengthMax); + } + + @Override + public double generateSpeed(net.minecraft.util.RandomSource random) { + return generateSpeed(this.level().purpurConfig.donkeyMovementSpeedMin, this.level().purpurConfig.donkeyMovementSpeedMax); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.donkeyBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.donkeyTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.donkeyAlwaysDropExp; + } + @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 5f5dc651d570989ec1294c31a14dcfede466b80a..3b1faa63e46a48e83ea672cf6da444a1d7e13270 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java @@ -40,6 +40,43 @@ public class Horse extends AbstractHorse implements VariantHolder { super(type, world); } + // Purpur start + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.horseRidableInWater; + } + // Purpur end + + @Override + public float generateMaxHealth(RandomSource random) { + return (float) generateMaxHealth(this.level().purpurConfig.horseMaxHealthMin, this.level().purpurConfig.horseMaxHealthMax); + } + + @Override + public double generateJumpStrength(RandomSource random) { + return generateJumpStrength(this.level().purpurConfig.horseJumpStrengthMin, this.level().purpurConfig.horseJumpStrengthMax); + } + + @Override + public double generateSpeed(RandomSource random) { + return generateSpeed(this.level().purpurConfig.horseMovementSpeedMin, this.level().purpurConfig.horseMovementSpeedMax); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.horseBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.horseTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.horseAlwaysDropExp; + } + @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 d28089c37707f323f73e60cb0ebed4e3cdf8c0fd..a9f36af3e07f74b3d11697d5fad9f0a469844779 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java @@ -73,9 +73,84 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) { super(type, world); + // Purpur start + this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this) { + @Override + public void tick() { + if (entity.getRider() != null && entity.isControllable() && isSaddled()) { + purpurTick(entity.getRider()); + } else { + vanillaTick(); + } + } + }; + this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) { + @Override + public void tick() { + if (entity.getRider() != null && entity.isControllable() && isSaddled()) { + purpurTick(entity.getRider()); + } else { + vanillaTick(); + } + } + }; + // Purpur end + } + + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.llamaRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.llamaRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.llamaControllable; + } + + @Override + public boolean isSaddled() { + return super.isSaddled() || (isTamed() && getSwag() != null); + } + // Purpur end + + @Override + public float generateMaxHealth(RandomSource random) { + return (float) generateMaxHealth(this.level().purpurConfig.llamaMaxHealthMin, this.level().purpurConfig.llamaMaxHealthMax); + } + + @Override + public double generateJumpStrength(RandomSource random) { + return generateJumpStrength(this.level().purpurConfig.llamaJumpStrengthMin, this.level().purpurConfig.llamaJumpStrengthMax); + } + + @Override + public double generateSpeed(RandomSource random) { + return generateSpeed(this.level().purpurConfig.llamaMovementSpeedMin, this.level().purpurConfig.llamaMovementSpeedMax); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.llamaBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.llamaTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.llamaAlwaysDropExp; } public boolean isTraderLlama() { @@ -109,7 +184,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder public super.jumpFromGround(); double d0 = this.moveControl.getSpeedModifier(); @@ -493,11 +520,11 @@ public class Sniffer extends Animal { @Override protected void customServerAiStep() { - this.level().getProfiler().push("snifferBrain"); + //this.level().getProfiler().push("snifferBrain"); // Purpur this.getBrain().tick((ServerLevel) this.level(), this); - this.level().getProfiler().popPush("snifferActivityUpdate"); + //this.level().getProfiler().popPush("snifferActivityUpdate"); // Purpur SnifferAi.updateActivity(this); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur super.customServerAiStep(); } diff --git a/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java b/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java index a6f3fba3b02b0b4d2a4e9e5205301c6f52d0188a..4eebfc27ded55e4d764d04f35d3e9c9e0791c89f 100644 --- a/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java +++ b/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java @@ -24,6 +24,13 @@ public class EnderDragonPart extends Entity { this.name = name; } + // Purpur start + @Override + public net.minecraft.world.InteractionResult interact(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) { + return parentMob.isAlive() ? parentMob.tryRide(player, hand) : net.minecraft.world.InteractionResult.PASS; + } + // Purpur end + @Override protected void defineSynchedData() { } diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java index ffc5b68c4246a7111845230a75552bb15875a209..440add62fcfa62d483409e1aecfc91591ba476c3 100644 --- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java +++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java @@ -30,6 +30,12 @@ public class EndCrystal extends Entity { private static final EntityDataAccessor DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); public int time; public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals + // Purpur start + private net.minecraft.world.entity.monster.Phantom targetPhantom; + private int phantomBeamTicks = 0; + private int phantomDamageCooldown = 0; + private int idleCooldown = 0; + // Purpur end public EndCrystal(EntityType type, Level world) { super(type, world); @@ -42,6 +48,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; @@ -77,9 +99,53 @@ public class EndCrystal extends Entity { } } // Paper end + if (this.level().purpurConfig.endCrystalCramming > 0 && this.level().getEntitiesOfClass(EndCrystal.class, getBoundingBox()).size() > this.level().purpurConfig.endCrystalCramming) this.hurt(this.damageSources().cramming(), 6.0F); // Purpur } + // Purpur start + if (level().purpurConfig.phantomAttackedByCrystalRadius <= 0 || --idleCooldown > 0) { + return; // on cooldown + } + + if (targetPhantom == null) { + for (net.minecraft.world.entity.monster.Phantom phantom : level().getEntitiesOfClass(net.minecraft.world.entity.monster.Phantom.class, getBoundingBox().inflate(level().purpurConfig.phantomAttackedByCrystalRadius))) { + if (phantom.hasLineOfSight(this)) { + attackPhantom(phantom); + break; + } + } + } else { + setBeamTarget(new BlockPos(targetPhantom).offset(0, -2, 0)); + if (--phantomBeamTicks > 0 && targetPhantom.isAlive()) { + phantomDamageCooldown--; + if (targetPhantom.hasLineOfSight(this)) { + if (phantomDamageCooldown <= 0) { + phantomDamageCooldown = 20; + targetPhantom.hurt(targetPhantom.damageSources().indirectMagic(this, this), level().purpurConfig.phantomAttackedByCrystalDamage); + } + } else { + forgetPhantom(); // no longer in sight + } + } else { + forgetPhantom(); // attacked long enough + } + } + } + + private void attackPhantom(net.minecraft.world.entity.monster.Phantom phantom) { + phantomDamageCooldown = 0; + phantomBeamTicks = 60; + targetPhantom = phantom; + } + + private void forgetPhantom() { + targetPhantom = null; + setBeamTarget(null); + phantomBeamTicks = 0; + phantomDamageCooldown = 0; + idleCooldown = 60; } + // Purpur end @Override protected void addAdditionalSaveData(CompoundTag nbt) { @@ -124,16 +190,18 @@ public class EndCrystal extends Entity { // CraftBukkit end this.remove(Entity.RemovalReason.KILLED); if (!source.is(DamageTypeTags.IS_EXPLOSION)) { + if (shouldExplode()) {// Purpur DamageSource damagesource1 = source.getEntity() != null ? this.damageSources().explosion(this, source.getEntity()) : null; // CraftBukkit start - ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false); + ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, getExplosionPower(), hasExplosionFire()); // Purpur if (event.isCancelled()) { this.unsetRemoved(); return false; } - this.level().explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK); + this.level().explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), getExplosionEffect()); // Purpur // CraftBukkit end + } else this.unsetRemoved(); // Purpur } this.onDestroyedBy(source); diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java index a24ae93efcdb2da5782d342c7697a1bb253400c7..ec595b74a6376adb65840035cdaa7502c9a3fe50 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 @@ -105,9 +105,11 @@ public class EnderDragon extends Mob implements Enemy { @Nullable private BlockPos podium; // Paper end + private boolean hadRider; // Purpur public EnderDragon(EntityType entitytypes, Level world) { super(EntityType.ENDER_DRAGON, world); + this.explosionSource = new Explosion(world, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.BlockInteraction.DESTROY); // Purpur - moved instantiation from field this.fightOrigin = BlockPos.ZERO; this.growlTime = 100; this.nodes = new Node[24]; @@ -126,7 +128,37 @@ public class EnderDragon extends Mob implements Enemy { this.noPhysics = true; this.noCulling = true; this.phaseManager = new EnderDragonPhaseManager(this); - this.explosionSource = new Explosion(world, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit + + // Purpur start + this.moveControl = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this) { + @Override + public void vanillaTick() { + // dragon doesn't use the controller. do nothing + } + }; + this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) { + @Override + public void vanillaTick() { + // dragon doesn't use the controller. do nothing + } + + @Override + public void purpurTick(Player rider) { + setYawPitch(rider.getYRot() - 180F, rider.xRotO * 0.5F); + } + }; + // Purpur end + } + + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.enderDragonRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.enderDragonRidableInWater; } public void setDragonFight(EndDragonFight fight) { @@ -141,6 +173,27 @@ 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 + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.enderDragonMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.enderDragonTakeDamageFromWater; + } + public static AttributeSupplier.Builder createAttributes() { return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D); } @@ -202,6 +255,37 @@ public class EnderDragon extends Mob implements Enemy { @Override public void aiStep() { + // Purpur start + boolean hasRider = getRider() != null && this.isControllable(); + if (hasRider) { + if (!hadRider) { + hadRider = true; + noPhysics = false; + this.dimensions = net.minecraft.world.entity.EntityDimensions.scalable(4.0F, 2.0F); + } + + // dragon doesn't use controllers, so must tick manually + moveControl.tick(); + lookControl.tick(); + + moveRelative((float) getAttributeValue(Attributes.MOVEMENT_SPEED) * 0.1F, new Vec3(-getStrafeMot(), getVerticalMot(), -getForwardMot())); + Vec3 mot = getDeltaMovement(); + setDeltaMovement(mot); + move(MoverType.PLAYER, mot); + + mot = mot.multiply(0.9F, 0.9F, 0.9F); + setDeltaMovement(mot); + + // control wing flap speed on client + phaseManager.setPhase(mot.x() * mot.x() + mot.z() * mot.z() < 0.005F ? EnderDragonPhase.HOVERING : EnderDragonPhase.HOLDING_PATTERN); + } else if (hadRider) { + hadRider = false; + noPhysics = true; + this.dimensions = net.minecraft.world.entity.EntityDimensions.scalable(16.0F, 8.0F); + phaseManager.setPhase(EnderDragonPhase.HOLDING_PATTERN); // HoldingPattern + } + // Purpur end + this.processFlappingMovement(); if (this.level().isClientSide) { this.setHealth(this.getHealth()); @@ -228,6 +312,8 @@ public class EnderDragon extends Mob implements Enemy { float f; if (this.isDeadOrDying()) { + if (hasRider) ejectPassengers(); // Purpur + float f1 = (this.random.nextFloat() - 0.5F) * 8.0F; f = (this.random.nextFloat() - 0.5F) * 4.0F; @@ -240,9 +326,9 @@ public class EnderDragon extends Mob implements Enemy { f = 0.2F / ((float) vec3d.horizontalDistance() * 10.0F + 1.0F); f *= (float) Math.pow(2.0D, vec3d.y); - if (this.phaseManager.getCurrentPhase().isSitting()) { + if (!hasRider && this.phaseManager.getCurrentPhase().isSitting()) { // Purpur this.flapTime += 0.1F; - } else if (this.inWall) { + } else if (!hasRider && this.inWall) { // Purpur this.flapTime += f * 0.5F; } else { this.flapTime += f; @@ -287,7 +373,7 @@ public class EnderDragon extends Mob implements Enemy { } this.phaseManager.getCurrentPhase().doClientTick(); - } else { + } else if (!hasRider) { // Purpur DragonPhaseInstance idragoncontroller = this.phaseManager.getCurrentPhase(); idragoncontroller.doServerTick(); @@ -356,7 +442,7 @@ public class EnderDragon extends Mob implements Enemy { this.tickPart(this.body, (double) (f11 * 0.5F), 0.0D, (double) (-f12 * 0.5F)); this.tickPart(this.wing1, (double) (f12 * 4.5F), 2.0D, (double) (f11 * 4.5F)); this.tickPart(this.wing2, (double) (f12 * -4.5F), 2.0D, (double) (f11 * -4.5F)); - if (!this.level().isClientSide && this.hurtTime == 0) { + if (!hasRider && !this.level().isClientSide && this.hurtTime == 0) { // Purpur this.knockBack(this.level().getEntities((Entity) this, this.wing1.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); this.knockBack(this.level().getEntities((Entity) this, this.wing2.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); this.hurt(this.level().getEntities((Entity) this, this.head.getBoundingBox().inflate(1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); @@ -400,7 +486,7 @@ public class EnderDragon extends Mob implements Enemy { } if (!this.level().isClientSide) { - this.inWall = this.checkWalls(this.head.getBoundingBox()) | this.checkWalls(this.neck.getBoundingBox()) | this.checkWalls(this.body.getBoundingBox()); + this.inWall = !hasRider && this.checkWalls(this.head.getBoundingBox()) | this.checkWalls(this.neck.getBoundingBox()) | this.checkWalls(this.body.getBoundingBox()); // Purpur if (this.dragonFight != null) { this.dragonFight.updateDragon(this); } @@ -532,7 +618,7 @@ public class EnderDragon extends Mob implements Enemy { BlockState iblockdata = this.level().getBlockState(blockposition); if (!iblockdata.isAir() && !iblockdata.is(BlockTags.DRAGON_TRANSPARENT)) { - if (this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) { + if ((this.level().purpurConfig.enderDragonBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) { // Purpur // CraftBukkit start - Add blocks to list rather than destroying them // flag1 = this.level().removeBlock(blockposition, false) || flag1; flag1 = true; @@ -676,7 +762,7 @@ public class EnderDragon extends Mob implements Enemy { boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT); short short0 = 500; - if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) { + if (this.dragonFight != null && (level().purpurConfig.enderDragonAlwaysDropsFullExp || !this.dragonFight.hasPreviouslyKilledDragon())) { short0 = 12000; } @@ -1111,6 +1197,7 @@ public class EnderDragon extends Mob implements Enemy { @Override protected boolean canRide(Entity entity) { + if (this.level().purpurConfig.enderDragonCanRideVehicles) return this.boardingCooldown <= 0; // Purpur return false; } diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java index c65ab566c6241dd6a44bd11a449ef0c4b2f6dc65..c94a1b75593ad01e7f79fdc84818ea8ec269e124 100644 --- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java @@ -84,20 +84,59 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob return entityliving.getMobType() != MobType.UNDEAD && entityliving.attackable(); }; private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR); + @Nullable private java.util.UUID summoner; // Purpur + private int shootCooldown = 0; // Purpur // Paper start private boolean canPortal = false; public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; } // Paper end + private org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD purpurController; // Purpur public WitherBoss(EntityType type, Level world) { super(type, world); this.bossEvent = (ServerBossEvent) (new ServerBossEvent(this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS)).setDarkenScreen(true); - this.moveControl = new FlyingMoveControl(this, 10, false); + // Purpur start + this.purpurController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.1F); + this.moveControl = new FlyingMoveControl(this, 10, false) { + @Override + public void tick() { + if (mob.getRider() != null && mob.isControllable()) { + purpurController.purpurTick(mob.getRider()); + } else { + super.tick(); + } + } + }; + // Purpur end this.setHealth(this.getMaxHealth()); this.xpReward = 50; } + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.witherTakeDamageFromWater; + } + + @Nullable + public java.util.UUID getSummoner() { + return summoner; + } + + public void setSummoner(@Nullable java.util.UUID summoner) { + this.summoner = summoner; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.witherAlwaysDropExp; + } + @Override protected PathNavigation createNavigation(Level world) { FlyingPathNavigation navigationflying = new FlyingPathNavigation(this, world); @@ -108,13 +147,113 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob return navigationflying; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.witherRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witherRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.witherControllable; + } + + @Override + public double getMaxY() { + return level().purpurConfig.witherMaxY; + } + + @Override + public void travel(Vec3 vec3) { + super.travel(vec3); + if (getRider() != null && this.isControllable() && !onGround) { + float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 5F; + setSpeed(speed); + Vec3 mot = getDeltaMovement(); + move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.5, speed)); + setDeltaMovement(mot.scale(0.9D)); + } + } + + @Override + public void onMount(Player rider) { + super.onMount(rider); + this.entityData.set(DATA_TARGETS.get(0), 0); + this.entityData.set(DATA_TARGETS.get(1), 0); + this.entityData.set(DATA_TARGETS.get(2), 0); + getNavigation().stop(); + shootCooldown = 20; + } + + @Override + public boolean onClick(net.minecraft.world.InteractionHand hand) { + return shoot(getRider(), hand == net.minecraft.world.InteractionHand.MAIN_HAND ? new int[]{1} : new int[]{2}); + } + + public boolean shoot(@Nullable Player rider, int[] heads) { + if (shootCooldown > 0) { + return false; + } + + shootCooldown = 20; + if (rider == null) { + return false; + } + + org.bukkit.craftbukkit.entity.CraftHumanEntity player = rider.getBukkitEntity(); + if (!player.hasPermission("allow.special.wither")) { + return false; + } + + net.minecraft.world.phys.HitResult rayTrace = getRayTrace(120, net.minecraft.world.level.ClipContext.Fluid.NONE); + if (rayTrace == null) { + return false; + } + + Vec3 loc; + if (rayTrace.getType() == net.minecraft.world.phys.HitResult.Type.BLOCK) { + BlockPos pos = ((net.minecraft.world.phys.BlockHitResult) rayTrace).getBlockPos(); + loc = new Vec3(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D); + } else if (rayTrace.getType() == net.minecraft.world.phys.HitResult.Type.ENTITY) { + Entity target = ((net.minecraft.world.phys.EntityHitResult) rayTrace).getEntity(); + loc = new Vec3(target.getX(), target.getY() + (target.getEyeHeight() / 2), target.getZ()); + } else { + org.bukkit.block.Block block = player.getTargetBlock(null, 120); + loc = new Vec3(block.getX() + 0.5D, block.getY() + 0.5D, block.getZ() + 0.5D); + } + + for (int head : heads) { + shoot(head, loc.x(), loc.y(), loc.z(), rider); + } + + return true; // handled + } + + public void shoot(int head, double x, double y, double z, Player rider) { + level().levelEvent(null, 1024, blockPosition(), 0); + double headX = getHeadX(head); + double headY = getHeadY(head); + double headZ = getHeadZ(head); + WitherSkull skull = new WitherSkull(level(), this, x - headX, y - headY, z - headZ); + skull.setPosRaw(headX, headY, headZ); + level().addFreshEntity(skull); + } + // Purpur end + @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(0, new WitherBoss.WitherDoNothingGoal()); this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 40, 20.0F)); this.goalSelector.addGoal(5, new WaterAvoidingRandomFlyingGoal(this, 1.0D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 0, false, false, WitherBoss.LIVING_ENTITY_SELECTOR)); } @@ -132,6 +271,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob public void addAdditionalSaveData(CompoundTag nbt) { super.addAdditionalSaveData(nbt); nbt.putInt("Invul", this.getInvulnerableTicks()); + if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur } @Override @@ -141,6 +281,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob if (this.hasCustomName()) { this.bossEvent.setName(this.getDisplayName()); } + if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur } @@ -263,6 +404,16 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob @Override protected void customServerAiStep() { + // Purpur start + if (getRider() != null && this.isControllable()) { + Vec3 mot = getDeltaMovement(); + setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z()); + } + if (shootCooldown > 0) { + shootCooldown--; + } + // Purpur end + int i; if (this.getInvulnerableTicks() > 0) { @@ -279,7 +430,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob } // CraftBukkit end - if (!this.isSilent()) { + if (!this.isSilent() && level().purpurConfig.witherPlaySpawnSound) { // CraftBukkit start - Use relative location for far away sounds // this.level().globalLevelEvent(1023, new BlockPosition(this), 0); int viewDistance = ((ServerLevel) this.level()).getCraftServer().getViewDistance() * 16; @@ -303,7 +454,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob this.setInvulnerableTicks(i); if (this.tickCount % 10 == 0) { - this.heal(10.0F, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit + this.heal(this.getMaxHealth() / 33, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit // Purpur } } else { @@ -363,7 +514,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob if (this.destroyBlocksTick > 0) { --this.destroyBlocksTick; - if (this.destroyBlocksTick == 0 && this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (this.destroyBlocksTick == 0 && (this.level().purpurConfig.witherBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur i = Mth.floor(this.getY()); j = Mth.floor(this.getX()); int i1 = Mth.floor(this.getZ()); @@ -396,8 +547,10 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob } } - if (this.tickCount % 20 == 0) { - this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit + // Purpur start - customizable heal rate and amount + if (this.tickCount % level().purpurConfig.witherHealthRegenDelay == 0) { + this.heal(level().purpurConfig.witherHealthRegenAmount, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit + // Purpur end } this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth()); @@ -583,11 +736,11 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob } public int getAlternativeTarget(int headIndex) { - return (Integer) this.entityData.get((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex)); + return getRider() != null && this.isControllable() ? 0 : this.entityData.get(WitherBoss.DATA_TARGETS.get(headIndex)); // Purpur } public void setAlternativeTarget(int headIndex, int id) { - this.entityData.set((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex), id); + if (getRider() == null || !this.isControllable()) this.entityData.set(WitherBoss.DATA_TARGETS.get(headIndex), id); // Purpur } @Override @@ -602,6 +755,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob @Override protected boolean canRide(Entity entity) { + if (this.level().purpurConfig.witherCanRideVehicles) return this.boardingCooldown <= 0; // Purpur return false; } diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java index 5c6e060a54ffb13c37ff5711992e964bdd59643d..4c5f20bafe589fbd4a034408edb721ac79bbc666 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java @@ -99,10 +99,12 @@ public class ArmorStand extends LivingEntity { private boolean noTickPoseDirty = false; private boolean noTickEquipmentDirty = false; // Paper end + public boolean canMovementTick = true; // Purpur public ArmorStand(EntityType type, Level world) { super(type, world); if (world != null) this.canTick = world.paperConfig().entities.armorStands.tick; // Paper - armour stand ticking + if (world != null) this.canMovementTick = world.purpurConfig.armorstandMovement; // Purpur this.handItems = NonNullList.withSize(2, ItemStack.EMPTY); this.armorItems = NonNullList.withSize(4, ItemStack.EMPTY); this.headPose = ArmorStand.DEFAULT_HEAD_POSE; @@ -112,6 +114,7 @@ public class ArmorStand extends LivingEntity { this.leftLegPose = ArmorStand.DEFAULT_LEFT_LEG_POSE; this.rightLegPose = ArmorStand.DEFAULT_RIGHT_LEG_POSE; this.setMaxUpStep(0.0F); + this.setShowArms(world != null && world.purpurConfig.armorstandPlaceWithArms); // Purpur } public ArmorStand(Level world, double x, double y, double z) { @@ -606,7 +609,7 @@ public class ArmorStand extends LivingEntity { private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(DamageSource damageSource) { // Paper ItemStack itemstack = new ItemStack(Items.ARMOR_STAND); - if (this.hasCustomName()) { + if (this.level().purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { // Purpur itemstack.setHoverName(this.getCustomName()); } @@ -682,6 +685,7 @@ public class ArmorStand extends LivingEntity { @Override public void tick() { + maxUpStep = level().purpurConfig.armorstandStepHeight; // Paper start if (!this.canTick) { if (this.noTickPoseDirty) { @@ -1016,4 +1020,18 @@ public class ArmorStand extends LivingEntity { } // Paper end // Paper end + + // Purpur start + @Override + public void updateInWaterStateAndDoWaterCurrentPushing() { + if (this.level().purpurConfig.armorstandWaterMovement && + (this.level().purpurConfig.armorstandWaterFence || !(level().getBlockState(blockPosition().below()).getBlock() instanceof net.minecraft.world.level.block.FenceBlock))) + super.updateInWaterStateAndDoWaterCurrentPushing(); + } + + @Override + public void aiStep() { + if (this.canMovementTick && this.canMove) super.aiStep(); + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java index 759ecd79534a7706f7d4a63eb9dacbefcfe54674..182faba889dc15a3500c5919cad8a5483a53033a 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java @@ -273,7 +273,13 @@ public class ItemFrame extends HangingEntity { } if (alwaysDrop) { - this.spawnAtLocation(this.getFrameItemStack()); + // Purpur start + final ItemStack itemFrame = this.getFrameItemStack(); + if (this.level().purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { + itemFrame.setHoverName(this.getCustomName()); + } + this.spawnAtLocation(itemFrame); + // Purpur end } if (!itemstack.isEmpty()) { diff --git a/src/main/java/net/minecraft/world/entity/decoration/Painting.java b/src/main/java/net/minecraft/world/entity/decoration/Painting.java index 0f4ef103afcbabc04880c8fc3547b861341c15fc..4b3a8e4a084585d56dd10a08405463b1a677368b 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/Painting.java +++ b/src/main/java/net/minecraft/world/entity/decoration/Painting.java @@ -121,7 +121,7 @@ public class Painting extends HangingEntity implements VariantHolder holder = loadVariant(nbt).orElseGet(Painting::getDefaultVariant); + Holder holder = loadVariant(nbt).orElseGet(() -> (Holder.Reference) getDefaultVariant()); // Purpur - decompile error TODO: still needed? this.setVariant(holder); this.direction = Direction.from2DDataValue(nbt.getByte("facing")); super.readAdditionalSaveData(nbt); @@ -159,7 +159,13 @@ public class Painting extends HangingEntity implements VariantHolder type, Level world) { super(type, world); @@ -346,7 +352,16 @@ public class ItemEntity extends Entity implements TraceableEntity { @Override public boolean hurt(DamageSource source, float amount) { - if (this.isInvulnerableTo(source)) { + // Purpur start + if ( + (immuneToCactus && source.is(net.minecraft.world.damagesource.DamageTypes.CACTUS)) || + (immuneToFire && (source.is(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(DamageTypeTags.IS_EXPLOSION)) + ) { + return false; + } else if (this.isInvulnerableTo(source)) { + // Purpur end return false; } else if (!this.getItem().isEmpty() && this.getItem().is(Items.NETHER_STAR) && source.is(DamageTypeTags.IS_EXPLOSION)) { return false; @@ -549,6 +564,12 @@ public class ItemEntity extends Entity implements TraceableEntity { // com.google.common.base.Preconditions.checkArgument(!stack.isEmpty(), "Cannot drop air"); // CraftBukkit // Paper - Remove check 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 + // Purpur start + if (level().purpurConfig.itemImmuneToCactus.contains(stack.getItem())) immuneToCactus = true; + if (level().purpurConfig.itemImmuneToExplosion.contains(stack.getItem())) immuneToExplosion = true; + if (level().purpurConfig.itemImmuneToFire.contains(stack.getItem())) immuneToFire = true; + if (level().purpurConfig.itemImmuneToLightning.contains(stack.getItem())) immuneToLightning = true; + // level end } @Override diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java index 4ce3e69970dd9eb251d0538a2d233ca30e9e5e47..afc65b8bb7e7f7f70a25f2d869412ed325b658da 100644 --- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java @@ -175,4 +175,29 @@ public class PrimedTnt extends Entity implements TraceableEntity { return !level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid(); } // Paper end + // 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) { + if (!level().isClientSide && 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") || + level().random.nextFloat() > level().purpurConfig.shearsCanDefuseTntChance) return net.minecraft.world.InteractionResult.PASS; + + net.minecraft.world.entity.item.ItemEntity tntItem = new net.minecraft.world.entity.item.ItemEntity(level(), getX(), getY(), getZ(), + new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.TNT)); + tntItem.setPickUpDelay(10); + + inHand.hurtAndBreak(1, player, entity -> entity.broadcastBreakEvent(hand)); + level().addFreshEntity(tntItem, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CUSTOM); + + this.playSound(net.minecraft.sounds.SoundEvents.SHEEP_SHEAR); + + this.kill(); + 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 26c7b2f18f21b04529f989b345c385693d98c903..917648c2cc52790f40123b27b1cafa3fc3ba4b3f 100644 --- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java @@ -66,16 +66,19 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo protected AbstractSkeleton(EntityType type, Level world) { super(type, world); this.reassessWeaponGoal(); + this.setShouldBurnInDay(true); // Purpur } @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(2, new RestrictSunGoal(this)); this.goalSelector.addGoal(3, new FleeSunGoal(this, 1.0D)); this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Wolf.class, 6.0F, 1.0D, 1.2D)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); @@ -99,35 +102,14 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo } // Paper start - private boolean shouldBurnInDay = true; + // private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity - keep methods for ABI compatibility public boolean shouldBurnInDay() { return shouldBurnInDay; } public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Paper end @Override public void aiStep() { - boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - Configurable Burning - - if (flag) { - ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); - - if (!itemstack.isEmpty()) { - if (itemstack.isDamageableItem()) { - itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); - if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { - this.broadcastBreakEvent(EquipmentSlot.HEAD); - this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); - } - } - - flag = false; - } - - if (flag) { - this.setSecondsOnFire(8); - } - } - + // Purpur start - implemented in LivingEntity super.aiStep(); } @@ -161,11 +143,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 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; } @@ -192,7 +170,6 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo } else { this.goalSelector.addGoal(4, this.meleeGoal); } - } } @@ -205,7 +182,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo double d2 = target.getZ() - this.getZ(); double d3 = Math.sqrt(d0 * d0 + d2 * d2); - entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.level().getDifficulty().getId() * 4)); + entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, this.level().purpurConfig.skeletonBowAccuracyMap.getOrDefault(this.level().getDifficulty().getId(), (float) (14 - this.level().getDifficulty().getId() * 4))); // Purpur // CraftBukkit start org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getPickupItem(), entityarrow, net.minecraft.world.InteractionHand.MAIN_HAND, 0.8F, true); // Paper if (event.isCancelled()) { @@ -236,7 +213,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo this.reassessWeaponGoal(); // Paper start if (nbt.contains("Paper.ShouldBurnInDay")) { - this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); + // this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); // Purpur - implemented in LivingEntity } // Paper end } @@ -245,7 +222,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo @Override public void addAdditionalSaveData(CompoundTag nbt) { super.addAdditionalSaveData(nbt); - nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); + // nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - implemented in LivingEntity } // Paper end diff --git a/src/main/java/net/minecraft/world/entity/monster/Blaze.java b/src/main/java/net/minecraft/world/entity/monster/Blaze.java index 17aa7676ab624440651850bbe5689f8a6c9dbeed..a8b58469fd8a1ed4ec0ce443cf05557903527bd7 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Blaze.java +++ b/src/main/java/net/minecraft/world/entity/monster/Blaze.java @@ -32,26 +32,73 @@ public class Blaze extends Monster { public Blaze(EntityType type, Level world) { super(type, world); - this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); + this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); // Purpur + if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F); this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F); this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F); this.xpReward = 10; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.blazeRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.blazeRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.blazeControllable; + } + + @Override + public double getMaxY() { + return level().purpurConfig.blazeMaxY; + } + + @Override + public void travel(Vec3 vec3) { + super.travel(vec3); + if (getRider() != null && this.isControllable() && !onGround) { + float speed = (float) getAttributeValue(Attributes.FLYING_SPEED); + setSpeed(speed); + Vec3 mot = getDeltaMovement(); + move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed)); + setDeltaMovement(mot.scale(0.9D)); + } + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.blazeMaxHealth); + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.blazeAlwaysDropExp; + } + @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(4, new Blaze.BlazeAttackGoal(this)); this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0D)); this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D, 0.0F)); this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new HurtByTargetGoal(this)).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); } public static AttributeSupplier.Builder createAttributes() { - return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0D).add(Attributes.MOVEMENT_SPEED, (double)0.23F).add(Attributes.FOLLOW_RANGE, 48.0D); + return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0D).add(Attributes.MOVEMENT_SPEED, (double)0.23F).add(Attributes.FOLLOW_RANGE, 48.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur } @Override @@ -101,11 +148,19 @@ public class Blaze extends Monster { @Override public boolean isSensitiveToWater() { - return true; + return this.level().purpurConfig.blazeTakeDamageFromWater; // Purpur } @Override protected void customServerAiStep() { + // Purpur start + if (getRider() != null && this.isControllable()) { + Vec3 mot = getDeltaMovement(); + setDeltaMovement(mot.x(), getVerticalMot() > 0 ? 0.07D : -0.07D, mot.z()); + return; + } + // Purpur end + --this.nextHeightOffsetChangeTick; if (this.nextHeightOffsetChangeTick <= 0) { this.nextHeightOffsetChangeTick = 100; diff --git a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java index da48bd8e15463be8170262ae90a8e95575959bf2..bf218586a79ea5f4288a64ccf6c245ee8491ce9a 100644 --- a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java +++ b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java @@ -28,6 +28,38 @@ public class CaveSpider extends Spider { return Spider.createAttributes().add(Attributes.MAX_HEALTH, 12.0D); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.caveSpiderRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.caveSpiderRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.caveSpiderControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.caveSpiderMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.caveSpiderTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.caveSpiderAlwaysDropExp; + } + @Override public boolean doHurtTarget(Entity target) { if (super.doHurtTarget(target)) { diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java index fd1b5a1beea7594fa65decfdcccfa15781fc005b..304ea7fdcd410a7c88ec61143364e14de8db0b0c 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java +++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java @@ -59,21 +59,99 @@ public class Creeper extends Monster implements PowerableMob { public int maxSwell = 30; public int explosionRadius = 3; private int droppedSkulls; + // Purpur start + private int spacebarCharge = 0; + private int prevSpacebarCharge = 0; + private int powerToggleDelay = 0; + private boolean exploding = false; + // Purpur end public Creeper(EntityType type, Level world) { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.creeperRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.creeperRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.creeperControllable; + } + + @Override + protected void customServerAiStep() { + if (powerToggleDelay > 0) { + powerToggleDelay--; + } + if (getRider() != null && this.isControllable()) { + if (getRider().getForwardMot() != 0 || getRider().getStrafeMot() != 0) { + spacebarCharge = 0; + setIgnited(false); + setSwellDir(-1); + } + if (spacebarCharge == prevSpacebarCharge) { + spacebarCharge = 0; + } + prevSpacebarCharge = spacebarCharge; + } + super.customServerAiStep(); + } + + @Override + public void onMount(Player rider) { + super.onMount(rider); + setIgnited(false); + setSwellDir(-1); + } + + @Override + public boolean onSpacebar() { + if (powerToggleDelay > 0) { + return true; // just toggled power, do not jump or ignite + } + spacebarCharge++; + if (spacebarCharge > maxSwell - 2) { + spacebarCharge = 0; + if (getRider() != null && getRider().getBukkitEntity().hasPermission("allow.powered.creeper")) { + powerToggleDelay = 20; + setPowered(!isPowered()); + setIgnited(false); + setSwellDir(-1); + return true; + } + } + if (!isIgnited()) { + if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0 && + getRider().getBukkitEntity().hasPermission("allow.special.creeper")) { + setIgnited(true); + setSwellDir(1); + return true; + } + } + return getForwardMot() == 0 && getStrafeMot() == 0; // do not jump if standing still + } + // Purpur end + @Override protected void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); this.goalSelector.addGoal(2, new SwellGoal(this)); + this.goalSelector.addGoal(3, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Ocelot.class, 6.0F, 1.0D, 1.2D)); this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Cat.class, 6.0F, 1.0D, 1.2D)); this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, false)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); } @@ -173,6 +251,37 @@ public class Creeper extends Monster implements PowerableMob { } } + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.creeperMaxHealth); + } + + public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.MobSpawnType spawnReason, @Nullable net.minecraft.world.entity.SpawnGroupData entityData, @Nullable CompoundTag entityNbt) { + double chance = world.getLevel().purpurConfig.creeperChargedChance; + if (chance > 0D && random.nextDouble() <= chance) { + setPowered(true); + } + return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.creeperTakeDamageFromWater; + } + + @Override + protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource damagesource) { + if (!exploding && this.level().purpurConfig.creeperExplodeWhenKilled && damagesource.getEntity() instanceof net.minecraft.server.level.ServerPlayer) { + this.explodeCreeper(); + } + return super.dropAllDeathLoot(damagesource); + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.creeperAlwaysDropExp; + } + @Override protected SoundEvent getHurtSound(DamageSource source) { return SoundEvents.CREEPER_HURT; @@ -264,15 +373,17 @@ public class Creeper extends Monster implements PowerableMob { } public void explodeCreeper() { + this.exploding = true; // Purpur if (!this.level().isClientSide) { float f = this.isPowered() ? 2.0F : 1.0F; + float multiplier = this.level().purpurConfig.creeperHealthRadius ? this.getHealth() / this.getMaxHealth() : 1; // Purpur // CraftBukkit start - ExplosionPrimeEvent event = 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; - this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit + this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), this.level().getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) && level().purpurConfig.creeperAllowGriefing ? Level.ExplosionInteraction.MOB : Level.ExplosionInteraction.NONE); // CraftBukkit // Purpur this.discard(); this.spawnLingeringCloud(); // CraftBukkit start @@ -282,7 +393,7 @@ public class Creeper extends Monster implements PowerableMob { } // CraftBukkit end } - + this.exploding = false; // Purpur } private void spawnLingeringCloud() { @@ -324,6 +435,7 @@ public class Creeper extends Monster implements PowerableMob { com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited); if (event.callEvent()) { this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited()); + if (!event.isIgnited()) setSwellDir(-1); // Purpur } } // Paper end diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java index 991d728db2a3b64316fc2102cf3aee470327a62e..63a1cf5604c14025171d7be7434e2d6b64c98107 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java +++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java @@ -29,6 +29,7 @@ import net.minecraft.world.entity.ai.goal.MoveToBlockGoal; import net.minecraft.world.entity.ai.goal.RandomStrollGoal; import net.minecraft.world.entity.ai.goal.RangedAttackGoal; import net.minecraft.world.entity.ai.goal.ZombieAttackGoal; +import net.minecraft.world.entity.ai.goal.MoveThroughVillageGoal; import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; import net.minecraft.world.entity.ai.navigation.GroundPathNavigation; @@ -68,6 +69,58 @@ public class Drowned extends Zombie implements RangedAttackMob { this.groundNavigation = new GroundPathNavigation(this, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.drownedRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.drownedRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.drownedControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.drownedMaxHealth); + } + + @Override + protected void randomizeReinforcementsChance() { + this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.drownedSpawnReinforcements); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.drownedTakeDamageFromWater; + } + + @Override + public boolean jockeyOnlyBaby() { + return level().purpurConfig.drownedJockeyOnlyBaby; + } + + @Override + public double jockeyChance() { + return level().purpurConfig.drownedJockeyChance; + } + + @Override + public boolean jockeyTryExistingChickens() { + return level().purpurConfig.drownedJockeyTryExistingChickens; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.drownedAlwaysDropExp; + } + @Override protected void addBehaviourGoals() { this.goalSelector.addGoal(1, new Drowned.DrownedGoToWaterGoal(this, 1.0D)); @@ -75,10 +128,23 @@ public class Drowned extends Zombie implements RangedAttackMob { this.goalSelector.addGoal(2, new Drowned.DrownedAttackGoal(this, 1.0D, false)); this.goalSelector.addGoal(5, new Drowned.DrownedGoToBeachGoal(this, 1.0D)); this.goalSelector.addGoal(6, new Drowned.DrownedSwimUpGoal(this, 1.0D, this.level().getSeaLevel())); + if (level().purpurConfig.drownedBreakDoors) this.goalSelector.addGoal(6, new MoveThroughVillageGoal(this, 1.0D, true, 4, this::canBreakDoors)); this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0D)); this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Drowned.class})).setAlertOthers(ZombifiedPiglin.class)); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::okTarget)); - if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper + // Purpur start + if (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(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false)); this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); @@ -112,7 +178,7 @@ public class Drowned extends Zombie implements RangedAttackMob { @Override public boolean supportsBreakDoorGoal() { - return false; + return level().purpurConfig.drownedBreakDoors ? true : false; } @Override @@ -259,8 +325,7 @@ public class Drowned extends Zombie implements RangedAttackMob { this.searchingForLand = targetingUnderwater; } - private static class DrownedMoveControl extends MoveControl { - + private static class DrownedMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur private final Drowned drowned; public DrownedMoveControl(Drowned drowned) { @@ -269,7 +334,7 @@ public class Drowned extends Zombie implements RangedAttackMob { } @Override - public void tick() { + public void vanillaTick() { // Purpur LivingEntity entityliving = this.drowned.getTarget(); if (this.drowned.wantsToSwim() && this.drowned.isInWater()) { @@ -292,7 +357,7 @@ public class Drowned extends Zombie implements RangedAttackMob { this.drowned.setYRot(this.rotlerp(this.drowned.getYRot(), f, 90.0F)); this.drowned.yBodyRot = this.drowned.getYRot(); - float f1 = (float) (this.speedModifier * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); + float f1 = (float) (this.getSpeedModifier() * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur float f2 = Mth.lerp(0.125F, this.drowned.getSpeed(), f1); this.drowned.setSpeed(f2); @@ -302,7 +367,7 @@ public class Drowned extends Zombie implements RangedAttackMob { this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(0.0D, -0.008D, 0.0D)); } - super.tick(); + super.vanillaTick(); // Purpur } } diff --git a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java index 8f481e11815d7162dd62a2b850b3d2af6d904519..b30f13d2a7198f568bc36c0d974fd6dc6be292c2 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java +++ b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java @@ -33,6 +33,33 @@ public class ElderGuardian extends Guardian { } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.elderGuardianRidable; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.elderGuardianControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.elderGuardianMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.elderGuardianTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.elderGuardianAlwaysDropExp; + } + 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 410f10ad93935d1c078447a4596023f367a8e9b7..44331be61edffd5afadf3e637dd181bff1133d4f 100644 --- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java @@ -93,12 +93,40 @@ public class EnderMan extends Monster implements NeutralMob { public EnderMan(EntityType type, Level world) { super(type, world); this.setMaxUpStep(1.0F); - this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); + if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur + } + + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.endermanRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.endermanRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.endermanControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.endermanMaxHealth); + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.endermanAlwaysDropExp; } @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new EnderMan.EndermanFreezeWhenLookedAt(this)); this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false)); this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D, 0.0F)); @@ -106,9 +134,10 @@ public class EnderMan extends Monster implements NeutralMob { this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); this.goalSelector.addGoal(10, new EnderMan.EndermanLeaveBlockGoal(this)); this.goalSelector.addGoal(11, new EnderMan.EndermanTakeBlockGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new EnderMan.EndermanLookForPlayerGoal(this, this::isAngryAt)); this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, true, false)); + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, 10, true, false, (entityliving) -> entityliving.level().purpurConfig.endermanAggroEndermites && entityliving instanceof Endermite endermite && (!entityliving.level().purpurConfig.endermanAggroEndermitesOnlyIfPlayerSpawned || endermite.isPlayerSpawned()))); // Purpur this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal<>(this, false)); } @@ -245,7 +274,7 @@ public class EnderMan extends Monster implements NeutralMob { // Paper end ItemStack itemstack = (ItemStack) player.getInventory().armor.get(3); - if (itemstack.is(Blocks.CARVED_PUMPKIN.asItem())) { + if (this.level().purpurConfig.endermanDisableStareAggro || itemstack.is(Blocks.CARVED_PUMPKIN.asItem()) || (this.level().purpurConfig.endermanIgnorePlayerDragonHead && itemstack.is(net.minecraft.world.item.Items.DRAGON_HEAD))) { // Purpur return false; } else { Vec3 vec3d = player.getViewVector(1.0F).normalize(); @@ -282,12 +311,12 @@ public class EnderMan extends Monster implements NeutralMob { @Override public boolean isSensitiveToWater() { - return true; + return this.level().purpurConfig.endermanTakeDamageFromWater; // Purpur } @Override protected void customServerAiStep() { - if (this.level().isDay() && this.tickCount >= this.targetChangeTime + 600) { + if ((getRider() == null || !this.isControllable()) && this.level().isDay() && this.tickCount >= this.targetChangeTime + 600) { // Purpur - no random teleporting float f = this.getLightLevelDependentMagicValue(); if (f > 0.5F && this.level().canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper @@ -408,6 +437,8 @@ public class EnderMan extends Monster implements NeutralMob { public boolean hurt(DamageSource source, float amount) { if (this.isInvulnerableTo(source)) { return false; + } else if (getRider() != null && this.isControllable()) { return super.hurt(source, amount); // Purpur - no teleporting on damage + } else if (org.purpurmc.purpur.PurpurConfig.endermanShortHeight && source.is(net.minecraft.world.damagesource.DamageTypes.IN_WALL)) { return false; // Purpur - no suffocation damage if short height } else { boolean flag = source.getDirectEntity() instanceof ThrownPotion; boolean flag1; @@ -422,6 +453,7 @@ public class EnderMan extends Monster implements NeutralMob { } else { flag1 = flag && this.hurtWithCleanWater(source, (ThrownPotion) source.getDirectEntity(), amount); + if (!flag1 && this.level().purpurConfig.endermanIgnoreProjectiles) return super.hurt(source, amount); // Purpur if (this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.INDIRECT)) { // Paper start for (int i = 0; i < 64; ++i) { if (this.teleport()) { @@ -468,7 +500,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 { @@ -515,7 +547,16 @@ public class EnderMan extends Monster implements NeutralMob { @Override public boolean canUse() { - return this.enderman.getCarriedBlock() == null ? false : (!this.enderman.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0); + if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur + // Purpur start + if (this.enderman.getCarriedBlock() == null) { + return false; + } + if (!this.enderman.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !this.enderman.level().purpurConfig.endermanBypassMobGriefing) { + return false; + } + return this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0; + // Purpur end } @Override @@ -560,7 +601,16 @@ public class EnderMan extends Monster implements NeutralMob { @Override public boolean canUse() { - return this.enderman.getCarriedBlock() != null ? false : (!this.enderman.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0); + if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur + // Purpur start + if (this.enderman.getCarriedBlock() != null) { + return false; + } + if (!this.enderman.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !this.enderman.level().purpurConfig.endermanBypassMobGriefing) { + return false; + } + return this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0; + // Purpur end } @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Endermite.java b/src/main/java/net/minecraft/world/entity/monster/Endermite.java index 29b8771ec85f07fcd9d9ba041c9a25a212009b16..e131949bd6aab4c8ff5cd4ea57280ad945ceef67 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Endermite.java +++ b/src/main/java/net/minecraft/world/entity/monster/Endermite.java @@ -31,20 +31,63 @@ import net.minecraft.world.level.block.state.BlockState; public class Endermite extends Monster { private static final int MAX_LIFE = 2400; public int life; + private boolean isPlayerSpawned; // Purpur public Endermite(EntityType type, Level world) { super(type, world); this.xpReward = 3; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.endermiteRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.endermiteRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.endermiteControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.endermiteMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.endermiteTakeDamageFromWater; + } + + public boolean isPlayerSpawned() { + return this.isPlayerSpawned; + } + + public void setPlayerSpawned(boolean playerSpawned) { + this.isPlayerSpawned = playerSpawned; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.endermiteAlwaysDropExp; + } + @Override protected void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level())); this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false)); this.goalSelector.addGoal(3, new WaterAvoidingRandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new HurtByTargetGoal(this)).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); } @@ -87,12 +130,14 @@ public class Endermite extends Monster { public void readAdditionalSaveData(CompoundTag nbt) { super.readAdditionalSaveData(nbt); this.life = nbt.getInt("Lifetime"); + this.isPlayerSpawned = nbt.getBoolean("PlayerSpawned"); // Purpur } @Override public void addAdditionalSaveData(CompoundTag nbt) { super.addAdditionalSaveData(nbt); nbt.putInt("Lifetime", this.life); + nbt.putBoolean("PlayerSpawned", this.isPlayerSpawned); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Evoker.java b/src/main/java/net/minecraft/world/entity/monster/Evoker.java index a6dde4e31790eaecff27135a4036627192c85702..4a87b76cc92220f6569b976871a86c8eab3c218d 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Evoker.java +++ b/src/main/java/net/minecraft/world/entity/monster/Evoker.java @@ -48,10 +48,43 @@ public class Evoker extends SpellcasterIllager { this.xpReward = 10; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.evokerRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.evokerRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.evokerControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.evokerMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.evokerTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.evokerAlwaysDropExp; + } + @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new Evoker.EvokerCastingSpellGoal()); this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 0.6D, 1.0D)); this.goalSelector.addGoal(4, new Evoker.EvokerSummonSpellGoal()); @@ -60,6 +93,7 @@ public class Evoker extends SpellcasterIllager { this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); this.targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(this, Player.class, true)).setUnseenMemoryTicks(300)); this.targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)).setUnseenMemoryTicks(300)); @@ -317,7 +351,7 @@ public class Evoker extends SpellcasterIllager { return false; } else if (Evoker.this.tickCount < this.nextAttackTickCount) { return false; - } else if (!Evoker.this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + } else if (!Evoker.this.level().purpurConfig.evokerBypassMobGriefing && !Evoker.this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur return false; } else { List list = Evoker.this.level().getNearbyEntities(Sheep.class, this.wololoTargeting, Evoker.this, Evoker.this.getBoundingBox().inflate(16.0D, 4.0D, 16.0D)); diff --git a/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java index 592b0dae251800552a0771ec46b4b8532b63075d..b0aad5778a12d3f0f2ef806f856064c4da842fb3 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java +++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java @@ -44,11 +44,47 @@ public class Ghast extends FlyingMob implements Enemy { this.moveControl = new Ghast.GhastMoveControl(this); } + // Purpur start + @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 + @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(5, new Ghast.RandomFloatAroundGoal(this)); this.goalSelector.addGoal(7, new Ghast.GhastLookGoal(this)); this.goalSelector.addGoal(7, new Ghast.GhastShootFireballGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving) -> { return Math.abs(entityliving.getY() - this.getY()) <= 4.0D; })); @@ -96,6 +132,21 @@ public class Ghast extends FlyingMob implements Enemy { } } + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ghastMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.ghastTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.ghastAlwaysDropExp; + } + @Override protected void defineSynchedData() { super.defineSynchedData(); @@ -103,7 +154,7 @@ public class Ghast extends FlyingMob implements Enemy { } public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D); + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur } @Override @@ -160,7 +211,7 @@ public class Ghast extends FlyingMob implements Enemy { return 2.6F; } - private static class GhastMoveControl extends MoveControl { + private static class GhastMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur private final Ghast ghast; private int floatDuration; @@ -171,7 +222,7 @@ public class Ghast extends FlyingMob implements Enemy { } @Override - public void tick() { + public void vanillaTick() { // Purpur if (this.operation == MoveControl.Operation.MOVE_TO) { if (this.floatDuration-- <= 0) { this.floatDuration += this.ghast.getRandom().nextInt(5) + 2; diff --git a/src/main/java/net/minecraft/world/entity/monster/Giant.java b/src/main/java/net/minecraft/world/entity/monster/Giant.java index 41004c28edb748e12c4f868aa07b4672891197c1..88b0dffe2c2b4da1406f218186d28f018ee879a3 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Giant.java +++ b/src/main/java/net/minecraft/world/entity/monster/Giant.java @@ -1,17 +1,122 @@ package net.minecraft.world.entity.monster; import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.Difficulty; +import net.minecraft.world.DifficultyInstance; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.MobSpawnType; import net.minecraft.world.entity.Pose; +import net.minecraft.world.entity.SpawnGroupData; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.ai.goal.FloatGoal; +import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; +import net.minecraft.world.entity.ai.goal.MeleeAttackGoal; +import net.minecraft.world.entity.ai.goal.MoveTowardsRestrictionGoal; +import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; +import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; +import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; +import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; +import net.minecraft.world.entity.animal.IronGolem; +import net.minecraft.world.entity.animal.Turtle; +import net.minecraft.world.entity.npc.Villager; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.ServerLevelAccessor; + +import javax.annotation.Nullable; public class Giant extends Monster { public Giant(EntityType type, Level world) { super(type, world); + this.safeFallDistance = 10.0F; // Purpur + } + + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.giantRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.giantRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.giantControllable; + } + + @Override + protected void registerGoals() { + if (level().purpurConfig.giantHaveAI) { + this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); + this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); + this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 16.0F)); + this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0D)); + if (level().purpurConfig.giantHaveHostileAI) { + this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); + this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class)); + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Villager.class, false)); + this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); + this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, true)); + } + } + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.giantTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.giantAlwaysDropExp; + } + // Purpur end + + @Override + protected void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.giantMaxHealth); + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.giantMovementSpeed); + this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.giantAttackDamage); + } + + @Override + public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) { + SpawnGroupData groupData = super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt); + if (groupData == null) { + populateDefaultEquipmentSlots(this.random, difficulty); + populateDefaultEquipmentEnchantments(this.random, difficulty); + } + return groupData; + } + + @Override + protected void populateDefaultEquipmentSlots(net.minecraft.util.RandomSource random, DifficultyInstance difficulty) { + super.populateDefaultEquipmentSlots(this.random, difficulty); + // TODO make configurable + if (random.nextFloat() < (level().getDifficulty() == Difficulty.HARD ? 0.1F : 0.05F)) { + this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.IRON_SWORD)); + } + } + + @Override + public float getJumpPower() { + // make giants jump as high as everything else relative to their size + // 1.0 makes bottom of feet about as high as their waist when they jump + return level().purpurConfig.giantJumpHeight; } @Override @@ -25,6 +130,6 @@ public class Giant extends Monster { @Override public float getWalkTargetValue(BlockPos pos, LevelReader world) { - return world.getPathfindingCostFromLightLevels(pos); + return super.getWalkTargetValue(pos, world); // Purpur - fix light requirements for natural spawns } } diff --git a/src/main/java/net/minecraft/world/entity/monster/Guardian.java b/src/main/java/net/minecraft/world/entity/monster/Guardian.java index 5e9b9e9f124ec6103b2e832ae94bc5b334a2c533..96338ed7952683f56d9a406d0956ce230581f146 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Guardian.java +++ b/src/main/java/net/minecraft/world/entity/monster/Guardian.java @@ -69,15 +69,51 @@ public class Guardian extends Monster { this.xpReward = 10; this.setPathfindingMalus(BlockPathTypes.WATER, 0.0F); this.moveControl = new Guardian.GuardianMoveControl(this); + // Purpur start + this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) { + @Override + public void setYawPitch(float yaw, float pitch) { + super.setYawPitch(yaw, pitch * 0.35F); + } + }; + // Purpur end this.clientSideTailAnimation = this.random.nextFloat(); this.clientSideTailAnimationO = this.clientSideTailAnimation; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.guardianRidable; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.guardianControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.guardianMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.guardianTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.guardianAlwaysDropExp; + } + @Override protected void registerGoals() { MoveTowardsRestrictionGoal pathfindergoalmovetowardsrestriction = new MoveTowardsRestrictionGoal(this, 1.0D); this.randomStrollGoal = new RandomStrollGoal(this, 1.0D, 80); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(4, this.guardianAttackGoal = new Guardian.GuardianAttackGoal(this)); // CraftBukkit - assign field this.goalSelector.addGoal(5, pathfindergoalmovetowardsrestriction); this.goalSelector.addGoal(7, this.randomStrollGoal); @@ -86,6 +122,7 @@ public class Guardian extends Monster { this.goalSelector.addGoal(9, new RandomLookAroundGoal(this)); this.randomStrollGoal.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK)); pathfindergoalmovetowardsrestriction.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 10, true, false, new Guardian.GuardianAttackSelector(this))); } @@ -351,7 +388,7 @@ public class Guardian extends Monster { @Override public void travel(Vec3 movementInput) { if (this.isControlledByLocalInstance() && this.isInWater()) { - this.moveRelative(0.1F, movementInput); + this.moveRelative(getRider() != null && this.isControllable() ? getSpeed() : 0.1F, movementInput); // Purpur this.move(MoverType.SELF, this.getDeltaMovement()); this.setDeltaMovement(this.getDeltaMovement().scale(0.9D)); if (!this.isMoving() && this.getTarget() == null) { @@ -363,7 +400,7 @@ public class Guardian extends Monster { } - private static class GuardianMoveControl extends MoveControl { + private static class GuardianMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur private final Guardian guardian; @@ -372,8 +409,17 @@ public class Guardian extends Monster { this.guardian = guardian; } + // Purpur start @Override - public void tick() { + public void purpurTick(Player rider) { + super.purpurTick(rider); + guardian.setDeltaMovement(guardian.getDeltaMovement().add(0.0D, 0.005D, 0.0D)); + guardian.setMoving(guardian.getForwardMot() > 0.0F); // control tail speed + } + // Purpur end + + @Override + public void vanillaTick() { // Purpur if (this.operation == MoveControl.Operation.MOVE_TO && !this.guardian.getNavigation().isDone()) { Vec3 vec3d = new Vec3(this.wantedX - this.guardian.getX(), this.wantedY - this.guardian.getY(), this.wantedZ - this.guardian.getZ()); double d0 = vec3d.length(); @@ -384,7 +430,7 @@ public class Guardian extends Monster { this.guardian.setYRot(this.rotlerp(this.guardian.getYRot(), f, 90.0F)); this.guardian.yBodyRot = this.guardian.getYRot(); - float f1 = (float) (this.speedModifier * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); + float f1 = (float) (this.getSpeedModifier() * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur float f2 = Mth.lerp(0.125F, this.guardian.getSpeed(), f1); this.guardian.setSpeed(f2); diff --git a/src/main/java/net/minecraft/world/entity/monster/Husk.java b/src/main/java/net/minecraft/world/entity/monster/Husk.java index efe8e6b895da1a81e5de42697fda58443cf15a59..cd78fbca21112a2354bc40747531e5202bbd5f20 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Husk.java +++ b/src/main/java/net/minecraft/world/entity/monster/Husk.java @@ -20,6 +20,59 @@ public class Husk extends Zombie { public Husk(EntityType type, Level world) { super(type, world); + this.setShouldBurnInDay(false); // Purpur + } + + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.huskRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.huskRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.huskControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.huskMaxHealth); + } + + @Override + protected void randomizeReinforcementsChance() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.huskSpawnReinforcements); + } + + @Override + public boolean jockeyOnlyBaby() { + return level().purpurConfig.huskJockeyOnlyBaby; + } + + @Override + public double jockeyChance() { + return level().purpurConfig.huskJockeyChance; + } + + @Override + public boolean jockeyTryExistingChickens() { + return level().purpurConfig.huskJockeyTryExistingChickens; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.huskTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.huskAlwaysDropExp; } public static boolean checkHuskSpawnRules(EntityType type, ServerLevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { @@ -28,7 +81,7 @@ public class Husk extends Zombie { @Override public boolean isSunSensitive() { - return false; + return this.shouldBurnInDay; // Purpur - moved to LivingEntity - keep methods for ABI compatibility } @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java index 63fce7e3d9f59f36e29bc827a46396d73143bb8b..f9cddf46d1dbcabc738842ba039daa76bb6f3eb5 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java +++ b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java @@ -59,10 +59,45 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.illusionerRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.illusionerRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.illusionerControllable; + } + // Purpur end + + @Override + protected void initAttributes() { + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.illusionerMovementSpeed); + this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(this.level().purpurConfig.illusionerFollowRange); + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.illusionerMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.illusionerTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.illusionerAlwaysDropExp; + } + @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new SpellcasterIllager.SpellcasterCastingSpellGoal()); this.goalSelector.addGoal(4, new Illusioner.IllusionerMirrorSpellGoal()); this.goalSelector.addGoal(5, new Illusioner.IllusionerBlindnessSpellGoal()); @@ -70,6 +105,7 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); this.targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(this, Player.class, true)).setUnseenMemoryTicks(300)); this.targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)).setUnseenMemoryTicks(300)); diff --git a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java index 2858ea5562d06c11e5c7337a2b123f9be7a3f62e..1ad97267394d3717b1871336193cdc91f3ffb276 100644 --- a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java +++ b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java @@ -25,6 +25,58 @@ public class MagmaCube extends Slime { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.magmaCubeRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.magmaCubeRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.magmaCubeControllable; + } + + @Override + public float getJumpPower() { + return 0.42F * this.getBlockJumpFactor(); // from EntityLiving + } + // Purpur end + + @Override + protected String getMaxHealthEquation() { + return level().purpurConfig.magmaCubeMaxHealth; + } + + @Override + protected String getAttackDamageEquation() { + return level().purpurConfig.magmaCubeAttackDamage; + } + + @Override + protected java.util.Map getMaxHealthCache() { + return level().purpurConfig.magmaCubeMaxHealthCache; + } + + @Override + protected java.util.Map getAttackDamageCache() { + return level().purpurConfig.magmaCubeAttackDamageCache; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.magmaCubeTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.magmaCubeAlwaysDropExp; + } + public static AttributeSupplier.Builder createAttributes() { return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, (double)0.2F); } @@ -70,11 +122,12 @@ public class MagmaCube extends Slime { } @Override - protected void jumpFromGround() { + public void jumpFromGround() { // Purpur - protected -> public Vec3 vec3 = this.getDeltaMovement(); float f = (float)this.getSize() * 0.1F; this.setDeltaMovement(vec3.x, (double)(this.getJumpPower() + f), vec3.z); this.hasImpulse = true; + this.actualJump = false; // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java index 55c245d0dfa369dc6de2197ae37335fba4fae4ae..c9b40515f4c2ff1eedfc9510930c3baebc078ebd 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Monster.java +++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java @@ -89,6 +89,14 @@ public abstract class Monster extends PathfinderMob implements Enemy { } public static boolean isDarkEnoughToSpawn(ServerLevelAccessor world, BlockPos pos, RandomSource random) { + // Purpur start + if (!world.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce || !world.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce) { + net.minecraft.world.level.block.state.BlockState spawnBlock = world.getBlockState(pos.below()); + if ((!world.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.PACKED_ICE)) || (!world.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.BLUE_ICE))) { + return false; + } + } + // Purpur end if (world.getBrightness(LightLayer.SKY, pos) > random.nextInt(32)) { return false; } else { diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java index 4a132c3eff6978e927bcd4df56b9ce0306af6d19..17638b9d3340c86528a8ae597712c7590b98dba6 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; + 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 + Vec3 crystalPosition; // Purpur public Phantom(EntityType type, Level world) { super(type, world); @@ -58,6 +60,92 @@ public class Phantom extends FlyingMob implements Enemy { this.xpReward = 5; this.moveControl = new Phantom.PhantomMoveControl(this); this.lookControl = new Phantom.PhantomLookControl(this); + this.setShouldBurnInDay(true); // Purpur + } + + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.phantomRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.phantomRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.phantomControllable; + } + + @Override + public double getMaxY() { + return level().purpurConfig.phantomMaxY; + } + + @Override + public void travel(Vec3 vec3) { + super.travel(vec3); + if (getRider() != null && this.isControllable() && !onGround) { + float speed = (float) getAttributeValue(Attributes.FLYING_SPEED); + setSpeed(speed); + Vec3 mot = getDeltaMovement(); + move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, speed, speed)); + setDeltaMovement(mot.scale(0.9D)); + } + } + + public static net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder createAttributes() { + return Monster.createMonsterAttributes().add(Attributes.FLYING_SPEED, 3.0D); + } + + @Override + public boolean onSpacebar() { + if (getRider() != null && getRider().getBukkitEntity().hasPermission("allow.special.phantom")) { + shoot(); + } + return false; + } + + public boolean shoot() { + org.bukkit.Location loc = ((org.bukkit.entity.LivingEntity) getBukkitEntity()).getEyeLocation(); + loc.setPitch(-loc.getPitch()); + org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(100).add(loc.toVector()); + + org.purpurmc.purpur.entity.PhantomFlames flames = new org.purpurmc.purpur.entity.PhantomFlames(level(), this); + flames.canGrief = level().purpurConfig.phantomAllowGriefing; + flames.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), 1.0F, 5.0F); + level().addFreshEntity(flames); + return true; + } + + @Override + protected void dropFromLootTable(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(new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.PHANTOM_MEMBRANE)) != null; + } + } + if (!dropped) { + super.dropFromLootTable(damageSource, causedByPlayer); + } + } + + public boolean isCirclingCrystal() { + return crystalPosition != null; + } + // Purpur end + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.phantomTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.phantomAlwaysDropExp; } @Override @@ -72,9 +160,17 @@ public class Phantom extends FlyingMob implements Enemy { @Override protected void registerGoals() { - this.goalSelector.addGoal(1, new Phantom.PhantomAttackStrategyGoal()); - this.goalSelector.addGoal(2, new Phantom.PhantomSweepAttackGoal()); - this.goalSelector.addGoal(3, new Phantom.PhantomCircleAroundAnchorGoal()); + // Purpur start + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); + if (level().purpurConfig.phantomOrbitCrystalRadius > 0) { + this.goalSelector.addGoal(1, new FindCrystalGoal(this)); + this.goalSelector.addGoal(2, new OrbitCrystalGoal(this)); + } + this.goalSelector.addGoal(3, new Phantom.PhantomAttackStrategyGoal()); + this.goalSelector.addGoal(4, new Phantom.PhantomSweepAttackGoal()); + this.goalSelector.addGoal(5, new Phantom.PhantomCircleAroundAnchorGoal()); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); + // Purpur end this.targetSelector.addGoal(1, new Phantom.PhantomAttackPlayerTargetGoal()); } @@ -90,7 +186,10 @@ public class Phantom extends FlyingMob implements Enemy { private void updatePhantomSizeInfo() { this.refreshDimensions(); - this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) (6 + this.getPhantomSize())); + // Purpur start + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomMaxHealth, () -> this.level().purpurConfig.phantomMaxHealthCache, () -> 20.0D)); + this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomAttackDamage, () -> this.level().purpurConfig.phantomAttackDamageCache, () -> (double) 6 + this.getPhantomSize())); + // Purpur end } public int getPhantomSize() { @@ -120,6 +219,21 @@ public class Phantom extends FlyingMob implements Enemy { return true; } + private double getFromCache(java.util.function.Supplier equation, java.util.function.Supplier> cache, java.util.function.Supplier defaultValue) { + int size = getPhantomSize(); + Double value = cache.get().get(size); + if (value == null) { + try { + value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue(); + } catch (javax.script.ScriptException e) { + e.printStackTrace(); + value = defaultValue.get(); + } + cache.get().put(size, value); + } + return value; + } + @Override public void tick() { super.tick(); @@ -140,14 +254,12 @@ public class Phantom extends FlyingMob implements Enemy { this.level().addParticle(ParticleTypes.MYCELIUM, this.getX() - (double) f2, this.getY() + (double) f4, this.getZ() - (double) f3, 0.0D, 0.0D, 0.0D); } + if (level().purpurConfig.phantomFlamesOnSwoop && attackPhase == AttackPhase.SWOOP) shoot(); // Purpur } @Override public void aiStep() { - if (this.isAlive() && shouldBurnInDay && this.isSunBurnTick()) { // Paper - Configurable Burning - this.setSecondsOnFire(8); - } - + // Purpur - moved down to shouldBurnInDay() super.aiStep(); } @@ -159,7 +271,11 @@ public class Phantom extends FlyingMob implements Enemy { @Override public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) { this.anchorPoint = this.blockPosition().above(5); - this.setPhantomSize(0); + // Purpur start + int min = world.getLevel().purpurConfig.phantomMinSize; + int max = world.getLevel().purpurConfig.phantomMaxSize; + this.setPhantomSize(min == max ? min : world.getRandom().nextInt(max + 1 - min) + min); + // Purpur end return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt); } @@ -175,7 +291,7 @@ public class Phantom extends FlyingMob implements Enemy { if (nbt.hasUUID("Paper.SpawningEntity")) { this.spawningEntity = nbt.getUUID("Paper.SpawningEntity"); } - if (nbt.contains("Paper.ShouldBurnInDay")) { + if (false && nbt.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); } // Paper end @@ -192,7 +308,7 @@ public class Phantom extends FlyingMob implements Enemy { if (this.spawningEntity != null) { nbt.putUUID("Paper.SpawningEntity", this.spawningEntity); } - nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); + // nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); // Purpur - implemented in LivingEntity // Paper end } @@ -258,8 +374,14 @@ public class Phantom extends FlyingMob implements Enemy { } public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } - private boolean shouldBurnInDay = true; - public boolean shouldBurnInDay() { return shouldBurnInDay; } + // private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity - keep methods for ABI compatibility + // Purpur start + public boolean shouldBurnInDay() { + boolean burnFromDaylight = this.shouldBurnInDay && this.level().purpurConfig.phantomBurnInDaylight; + boolean burnFromLightSource = this.level().purpurConfig.phantomBurnInLight > 0 && this.level().getMaxLocalRawBrightness(blockPosition()) >= this.level().purpurConfig.phantomBurnInLight; + return burnFromDaylight || burnFromLightSource; + } + // Purpur End public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Paper end private static enum AttackPhase { @@ -269,7 +391,125 @@ public class Phantom extends FlyingMob implements Enemy { private AttackPhase() {} } - private class PhantomMoveControl extends MoveControl { + // Purpur start + class FindCrystalGoal extends Goal { + private final Phantom phantom; + private net.minecraft.world.entity.boss.enderdragon.EndCrystal crystal; + private Comparator comparator; + + FindCrystalGoal(Phantom phantom) { + this.phantom = phantom; + this.comparator = Comparator.comparingDouble(phantom::distanceToSqr); + this.setFlags(EnumSet.of(Flag.LOOK)); + } + + @Override + public boolean canUse() { + double range = maxTargetRange(); + List crystals = level().getEntitiesOfClass(net.minecraft.world.entity.boss.enderdragon.EndCrystal.class, phantom.getBoundingBox().inflate(range)); + if (crystals.isEmpty()) { + return false; + } + crystals.sort(comparator); + crystal = crystals.get(0); + if (phantom.distanceToSqr(crystal) > range * range) { + crystal = null; + return false; + } + return true; + } + + @Override + public boolean canContinueToUse() { + if (crystal == null || !crystal.isAlive()) { + return false; + } + double range = maxTargetRange(); + return phantom.distanceToSqr(crystal) <= (range * range) * 2; + } + + @Override + public void start() { + phantom.crystalPosition = new Vec3(crystal.getX(), crystal.getY() + (phantom.random.nextInt(10) + 10), crystal.getZ()); + } + + @Override + public void stop() { + crystal = null; + phantom.crystalPosition = null; + super.stop(); + } + + private double maxTargetRange() { + return phantom.level().purpurConfig.phantomOrbitCrystalRadius; + } + } + + class OrbitCrystalGoal extends Goal { + private final Phantom phantom; + private float offset; + private float radius; + private float verticalChange; + private float direction; + + OrbitCrystalGoal(Phantom phantom) { + this.phantom = phantom; + this.setFlags(EnumSet.of(Flag.MOVE)); + } + + @Override + public boolean canUse() { + return phantom.isCirclingCrystal(); + } + + @Override + public void start() { + this.radius = 5.0F + phantom.random.nextFloat() * 10.0F; + this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F; + this.direction = phantom.random.nextBoolean() ? 1.0F : -1.0F; + updateOffset(); + } + + @Override + public void tick() { + if (phantom.random.nextInt(350) == 0) { + this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F; + } + if (phantom.random.nextInt(250) == 0) { + ++this.radius; + if (this.radius > 15.0F) { + this.radius = 5.0F; + this.direction = -this.direction; + } + } + if (phantom.random.nextInt(450) == 0) { + this.offset = phantom.random.nextFloat() * 2.0F * 3.1415927F; + updateOffset(); + } + if (phantom.moveTargetPoint.distanceToSqr(phantom.getX(), phantom.getY(), phantom.getZ()) < 4.0D) { + updateOffset(); + } + if (phantom.moveTargetPoint.y < phantom.getY() && !phantom.level().isEmptyBlock(new BlockPos(phantom).below(1))) { + this.verticalChange = Math.max(1.0F, this.verticalChange); + updateOffset(); + } + if (phantom.moveTargetPoint.y > phantom.getY() && !phantom.level().isEmptyBlock(new BlockPos(phantom).above(1))) { + this.verticalChange = Math.min(-1.0F, this.verticalChange); + updateOffset(); + } + } + + private void updateOffset() { + this.offset += this.direction * 15.0F * 0.017453292F; + phantom.moveTargetPoint = phantom.crystalPosition.add( + this.radius * Mth.cos(this.offset), + -4.0F + this.verticalChange, + this.radius * Mth.sin(this.offset)); + } + } + // Purpur end + + private class PhantomMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur private float speed = 0.1F; @@ -277,8 +517,19 @@ public class Phantom extends FlyingMob implements Enemy { super(entity); } + // Purpur start + public void purpurTick(Player rider) { + if (!Phantom.this.onGround) { + // phantom is always in motion when flying + // TODO - FIX THIS + // rider.setForward(1.0F); + } + super.purpurTick(rider); + } + // Purpur end + @Override - public void tick() { + public void vanillaTick() { // Purpur if (Phantom.this.horizontalCollision) { Phantom.this.setYRot(Phantom.this.getYRot() + 180.0F); this.speed = 0.1F; @@ -324,14 +575,20 @@ public class Phantom extends FlyingMob implements Enemy { } } - private class PhantomLookControl extends LookControl { + private class PhantomLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur public PhantomLookControl(Mob entity) { super(entity); } + // Purpur start + public void purpurTick(Player rider) { + setYawPitch(rider.getYRot(), -rider.xRotO * 0.75F); + } + // Purpur end + @Override - public void tick() {} + public void vanillaTick() {} // Purpur } private class PhantomBodyRotationControl extends BodyRotationControl { @@ -418,6 +675,12 @@ public class Phantom extends FlyingMob implements Enemy { return false; } else if (!entityliving.isAlive()) { return false; + // Purpur start + } else if (level().purpurConfig.phantomBurnInLight > 0 && level().getLightEmission(new BlockPos(Phantom.this)) >= level().purpurConfig.phantomBurnInLight) { + return false; + } else if (level().purpurConfig.phantomIgnorePlayersWithTorch && (TORCH.test(entityliving.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(entityliving.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND)))) { + return false; + // Purpur end } else { if (entityliving instanceof Player) { Player entityhuman = (Player) entityliving; @@ -563,6 +826,7 @@ public class Phantom extends FlyingMob implements Enemy { this.nextScanTick = reducedTickDelay(60); List list = Phantom.this.level().getNearbyPlayers(this.attackTargeting, Phantom.this, Phantom.this.getBoundingBox().inflate(16.0D, 64.0D, 16.0D)); + if (level().purpurConfig.phantomIgnorePlayersWithTorch) list.removeIf(human -> TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND)));// Purpur if (!list.isEmpty()) { list.sort(Comparator.comparing((Entity e) -> { return e.getY(); }).reversed()); // CraftBukkit - decompile error Iterator iterator = list.iterator(); diff --git a/src/main/java/net/minecraft/world/entity/monster/Pillager.java b/src/main/java/net/minecraft/world/entity/monster/Pillager.java index cec545c3baa6599d47b9cf1a4b97de8771062a22..06d52d8b61abc4dbbdc953bfed2e688be377b3cc 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Pillager.java +++ b/src/main/java/net/minecraft/world/entity/monster/Pillager.java @@ -62,15 +62,49 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.pillagerRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.pillagerRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.pillagerControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pillagerMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.pillagerTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.pillagerAlwaysDropExp; + } + @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, 10.0F)); this.goalSelector.addGoal(3, new RangedCrossbowAttackGoal<>(this, 1.0D, 8.0F)); this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 15.0F, 1.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 15.0F)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, Raider.class)).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java index e87c6c8ab585d6b0b38bbb8e42c0082e38f03250..0211b50ce4b48a26698f1ece8b593747d170178e 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java +++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java @@ -68,14 +68,54 @@ public class Ravager extends Raider { this.setPathfindingMalus(BlockPathTypes.LEAVES, 0.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.ravagerRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ravagerRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.ravagerControllable; + } + + @Override + public void onMount(Player rider) { + super.onMount(rider); + getNavigation().stop(); + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ravagerMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.ravagerTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.ravagerAlwaysDropExp; + } + @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(4, new Ravager.RavagerMeleeAttackGoal()); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.4D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(2, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true, (entityliving) -> { @@ -153,7 +193,7 @@ public class Ravager extends Raider { @Override public void aiStep() { super.aiStep(); - if (this.isAlive()) { + if (this.isAlive() && (getRider() == null || !this.isControllable())) { // Purpur if (this.isImmobile()) { this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.0D); } else { @@ -163,7 +203,7 @@ public class Ravager extends Raider { this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(Mth.lerp(0.1D, d1, d0)); } - if (this.horizontalCollision && this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (this.horizontalCollision && (this.level().purpurConfig.ravagerBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur boolean flag = false; AABB axisalignedbb = this.getBoundingBox().inflate(0.2D); Iterator iterator = BlockPos.betweenClosed(Mth.floor(axisalignedbb.minX), Mth.floor(axisalignedbb.minY), Mth.floor(axisalignedbb.minZ), Mth.floor(axisalignedbb.maxX), Mth.floor(axisalignedbb.maxY), Mth.floor(axisalignedbb.maxZ)).iterator(); @@ -173,7 +213,7 @@ public class Ravager extends Raider { BlockState iblockdata = this.level().getBlockState(blockposition); Block block = iblockdata.getBlock(); - if (block instanceof LeavesBlock) { + if (this.level().purpurConfig.ravagerGriefableBlocks.contains(block)) { // Purpur // 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 d2073de07bdad71b20e05046577b16649123967b..3e2c60a301d4c66c85cd8ff812331005a667dc4e 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java +++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java @@ -22,6 +22,8 @@ import net.minecraft.tags.DamageTypeTags; import net.minecraft.util.Mth; import net.minecraft.world.Difficulty; import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; @@ -50,6 +52,8 @@ import net.minecraft.world.entity.projectile.AbstractArrow; import net.minecraft.world.entity.projectile.ShulkerBullet; import net.minecraft.world.entity.vehicle.Boat; import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.DyeItem; +import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerLevelAccessor; import net.minecraft.world.level.block.Blocks; @@ -98,12 +102,59 @@ public class Shulker extends AbstractGolem implements VariantHolder= f) { + if ((!this.level().purpurConfig.shulkerSpawnFromBulletRequireOpenLid || !this.isClosed()) && this.teleportSomewhere()) { + // Purpur start + float chance = this.level().purpurConfig.shulkerSpawnFromBulletBaseChance; + if (!this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation.isBlank()) { + int nearby = this.level().getEntities((EntityTypeTest) EntityType.SHULKER, axisalignedbb.inflate(this.level().purpurConfig.shulkerSpawnFromBulletNearbyRange), Entity::isAlive).size(); + try { + chance -= ((Number) scriptEngine.eval("let nearby = " + nearby + "; " + this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation)).floatValue(); + } catch (javax.script.ScriptException e) { + e.printStackTrace(); + chance -= (nearby - 1) / 5.0F; + } + } + if (this.level().random.nextFloat() <= chance) { Shulker entityshulker = (Shulker) EntityType.SHULKER.create(this.level()); + // Purpur end if (entityshulker != null) { entityshulker.setVariant(this.getVariant()); @@ -599,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 @@ -609,7 +669,7 @@ public class Shulker extends AbstractGolem implements VariantHolder(this, Player.class, true)); } @@ -184,7 +218,7 @@ public class Silverfish extends Monster { continue; } // CraftBukkit end - if (world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (world.purpurConfig.silverfishBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur world.destroyBlock(blockposition1, true, this.silverfish); } else { world.setBlock(blockposition1, ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)), 3); @@ -222,7 +256,7 @@ public class Silverfish extends Monster { } else { RandomSource randomsource = this.mob.getRandom(); - if (this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && randomsource.nextInt(reducedTickDelay(10)) == 0) { + if ((this.mob.level().purpurConfig.silverfishBypassMobGriefing || this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && randomsource.nextInt(reducedTickDelay(10)) == 0) { // Purpur this.selectedDirection = Direction.getRandom(randomsource); BlockPos blockposition = BlockPos.containing(this.mob.getX(), this.mob.getY() + 0.5D, this.mob.getZ()).relative(this.selectedDirection); BlockState iblockdata = this.mob.level().getBlockState(blockposition); diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java index 8b818a7cb835512c4bd2ea9641d4bfd904150332..f4a47ac2c86cc95178922cce7320ba1ef5121b57 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java +++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java @@ -14,6 +14,16 @@ import net.minecraft.world.item.Items; import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.Level; +// Purpur start +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Blocks; +import org.bukkit.craftbukkit.event.CraftEventFactory; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.core.particles.ParticleTypes; +// Purpur end + public class Skeleton extends AbstractSkeleton { private static final int TOTAL_CONVERSION_TIME = 300; @@ -26,6 +36,38 @@ public class Skeleton extends AbstractSkeleton { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.skeletonRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.skeletonRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.skeletonControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.skeletonMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.skeletonTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.skeletonAlwaysDropExp; + } + @Override protected void defineSynchedData() { super.defineSynchedData(); @@ -142,4 +184,67 @@ public class Skeleton extends AbstractSkeleton { } } + + // Purpur start + private int witherRosesFed = 0; + + @Override + public InteractionResult mobInteract(Player player, InteractionHand hand) { + ItemStack stack = player.getItemInHand(hand); + + if (level().purpurConfig.skeletonFeedWitherRoses > 0 && this.getType() != EntityType.WITHER_SKELETON && stack.getItem() == Blocks.WITHER_ROSE.asItem()) { + return this.feedWitherRose(player, stack); + } + + return super.mobInteract(player, hand); + } + + private InteractionResult feedWitherRose(Player player, ItemStack stack) { + if (++witherRosesFed < level().purpurConfig.skeletonFeedWitherRoses) { + if (!player.getAbilities().instabuild) { + stack.shrink(1); + } + return InteractionResult.CONSUME; + } + + WitherSkeleton skeleton = EntityType.WITHER_SKELETON.create(level()); + if (skeleton == null) { + return InteractionResult.PASS; + } + + skeleton.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); + skeleton.setHealth(this.getHealth()); + skeleton.setAggressive(this.isAggressive()); + skeleton.copyPosition(this); + skeleton.setYBodyRot(this.yBodyRot); + skeleton.setYHeadRot(this.getYHeadRot()); + skeleton.yRotO = this.yRotO; + skeleton.xRotO = this.xRotO; + + if (this.hasCustomName()) { + skeleton.setCustomName(this.getCustomName()); + } + + if (CraftEventFactory.callEntityTransformEvent(this, skeleton, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) { + return InteractionResult.PASS; + } + + if (!new com.destroystokyo.paper.event.entity.EntityTransformedEvent(this.getBukkitEntity(), skeleton.getBukkitEntity(), com.destroystokyo.paper.event.entity.EntityTransformedEvent.TransformedReason.INFECTED).callEvent()) { + return InteractionResult.PASS; + } + + this.level().addFreshEntity(skeleton); + this.remove(RemovalReason.DISCARDED); + if (!player.getAbilities().instabuild) { + stack.shrink(1); + } + + for (int i = 0; i < 15; ++i) { + ((ServerLevel) level()).sendParticles(((ServerLevel) level()).players(), null, ParticleTypes.HAPPY_VILLAGER, + getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1, + random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true); + } + return InteractionResult.SUCCESS; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java index 343433158507451152e5b2fc5e5fd0b0e6b229db..467337542551dedc05d922bb3a37b811aeef4d7b 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Slime.java +++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java @@ -64,6 +64,7 @@ public class Slime extends Mob implements Enemy { public float squish; public float oSquish; private boolean wasOnGround; + protected boolean actualJump; // Purpur public Slime(EntityType type, Level world) { super(type, world); @@ -71,12 +72,89 @@ public class Slime extends Mob implements Enemy { this.moveControl = new Slime.SlimeMoveControl(this); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.slimeRidable; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.slimeTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.slimeAlwaysDropExp; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.slimeRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.slimeControllable; + } + + @Override + public float getJumpPower() { + float height = super.getJumpPower(); + return getRider() != null && this.isControllable() && actualJump ? height * 1.5F : height; + } + + @Override + public boolean onSpacebar() { + if (onGround && getRider() != null && this.isControllable()) { + actualJump = true; + if (getRider().getForwardMot() == 0 || getRider().getStrafeMot() == 0) { + jumpFromGround(); // jump() here if not moving + } + } + return true; // do not jump() in wasd controller, let vanilla controller handle + } + + protected String getMaxHealthEquation() { + return level().purpurConfig.slimeMaxHealth; + } + + protected String getAttackDamageEquation() { + return level().purpurConfig.slimeAttackDamage; + } + + protected java.util.Map getMaxHealthCache() { + return level().purpurConfig.slimeMaxHealthCache; + } + + protected java.util.Map getAttackDamageCache() { + return level().purpurConfig.slimeAttackDamageCache; + } + + protected double getFromCache(java.util.function.Supplier equation, java.util.function.Supplier> cache, java.util.function.Supplier defaultValue) { + int size = getSize(); + Double value = cache.get().get(size); + if (value == null) { + try { + value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue(); + } catch (javax.script.ScriptException e) { + e.printStackTrace(); + value = defaultValue.get(); + } + cache.get().put(size, value); + } + return value; + } + // Purpur end + @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new Slime.SlimeFloatGoal(this)); this.goalSelector.addGoal(2, new Slime.SlimeAttackGoal(this)); this.goalSelector.addGoal(3, new Slime.SlimeRandomDirectionGoal(this)); this.goalSelector.addGoal(5, new Slime.SlimeKeepOnJumpingGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving) -> { return Math.abs(entityliving.getY() - this.getY()) <= 4.0D; })); @@ -96,9 +174,9 @@ public class Slime extends Mob implements Enemy { this.entityData.set(Slime.ID_SIZE, j); this.reapplyPosition(); this.refreshDimensions(); - this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double) (j * j)); + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(this::getMaxHealthEquation, this::getMaxHealthCache, () -> (double) size * size)); // Purpur this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue((double) (0.2F + 0.1F * (float) j)); - this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) j); + this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(this::getAttackDamageEquation, this::getAttackDamageCache, () -> (double) j)); // Purpur if (heal) { this.setHealth(this.getMaxHealth()); } @@ -368,11 +446,12 @@ public class Slime extends Mob implements Enemy { } @Override - protected void jumpFromGround() { + public void jumpFromGround() { // Purpur - protected -> public Vec3 vec3d = this.getDeltaMovement(); this.setDeltaMovement(vec3d.x, (double) this.getJumpPower(), vec3d.z); this.hasImpulse = true; + this.actualJump = false; // Purpur } @Nullable @@ -406,7 +485,7 @@ public class Slime extends Mob implements Enemy { return super.getDimensions(pose).scale(0.255F * (float) this.getSize()); } - private static class SlimeMoveControl extends MoveControl { + private static class SlimeMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur private float yRot; private int jumpDelay; @@ -425,21 +504,33 @@ public class Slime extends Mob implements Enemy { } public void setWantedMovement(double speed) { - this.speedModifier = speed; + this.setSpeedModifier(speed); // Purpur this.operation = MoveControl.Operation.MOVE_TO; } @Override public void tick() { + // Purpur start + if (slime.getRider() != null && slime.isControllable()) { + purpurTick(slime.getRider()); + if (slime.getForwardMot() != 0 || slime.getStrafeMot() != 0) { + if (jumpDelay > 10) { + jumpDelay = 6; + } + } else { + jumpDelay = 20; + } + } else { + // Purpur end this.mob.setYRot(this.rotlerp(this.mob.getYRot(), this.yRot, 90.0F)); this.mob.yHeadRot = this.mob.getYRot(); this.mob.yBodyRot = this.mob.getYRot(); - if (this.operation != MoveControl.Operation.MOVE_TO) { + } if ((slime.getRider() == null || !slime.isControllable()) && this.operation != MoveControl.Operation.MOVE_TO) { // Purpur this.mob.setZza(0.0F); } else { this.operation = MoveControl.Operation.WAIT; if (this.mob.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 if (this.jumpDelay-- <= 0) { this.jumpDelay = this.slime.getJumpDelay(); if (this.isAggressive) { @@ -456,7 +547,7 @@ public class Slime extends Mob implements Enemy { this.mob.setSpeed(0.0F); } } else { - this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED))); + this.mob.setSpeed((float) (this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur } } diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java index 05432184077752b1d0cb764a5e39ed875748b2d6..b3ab0f8cdbd678970e39e89c2062772374466b67 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Spider.java +++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java @@ -51,14 +51,48 @@ public class Spider extends Monster { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.spiderRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.spiderRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.spiderControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.spiderMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.spiderTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.spiderAlwaysDropExp; + } + @Override protected void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(3, new LeapAtTargetGoal(this, 0.4F)); this.goalSelector.addGoal(4, new Spider.SpiderAttackGoal(this)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); this.targetSelector.addGoal(2, new Spider.SpiderTargetGoal<>(this, Player.class)); this.targetSelector.addGoal(3, new Spider.SpiderTargetGoal<>(this, IronGolem.class)); diff --git a/src/main/java/net/minecraft/world/entity/monster/Stray.java b/src/main/java/net/minecraft/world/entity/monster/Stray.java index 118b636a44e4b062e812e433f603b039276337da..a95e983032b3d3d125a39a46700b7db9dc69f307 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Stray.java +++ b/src/main/java/net/minecraft/world/entity/monster/Stray.java @@ -21,6 +21,38 @@ public class Stray extends AbstractSkeleton { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.strayRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.strayRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.strayControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.strayMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.strayTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.strayAlwaysDropExp; + } + public static boolean checkStraySpawnRules(EntityType type, ServerLevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { BlockPos blockPos = pos; diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java index ebf54d6e36fdee2833250816fae14195ac45eb67..cfa603c7ccaaf1940aa89fa7cd8fafba29529075 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Strider.java +++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java @@ -94,12 +94,44 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { super(type, world); this.steering = new ItemBasedSteering(this.entityData, Strider.DATA_BOOST_TIME, Strider.DATA_SADDLE_ID); this.blocksBuilding = true; - this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); + if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur this.setPathfindingMalus(BlockPathTypes.LAVA, 0.0F); this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F); this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.striderRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.striderRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.striderControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.striderMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.striderBreedingTicks; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.striderAlwaysDropExp; + } + public static boolean checkStriderSpawnRules(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); @@ -161,6 +193,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { @Override protected void registerGoals() { this.panicGoal = new PanicGoal(this, 1.65D); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, this.panicGoal); this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); this.temptGoal = new TemptGoal(this, 1.4D, Strider.TEMPT_ITEMS, false); @@ -416,7 +449,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { @Override public boolean isSensitiveToWater() { - return true; + return this.level().purpurConfig.striderTakeDamageFromWater; // Purpur } @Override @@ -458,6 +491,19 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { public InteractionResult mobInteract(Player player, InteractionHand hand) { boolean flag = this.isFood(player.getItemInHand(hand)); + // Purpur start + if (level().purpurConfig.striderGiveSaddleBack && player.isSecondaryUseActive() && !flag && isSaddled() && !isVehicle()) { + this.steering.setSaddle(false); + if (!player.getAbilities().instabuild) { + ItemStack saddle = new ItemStack(Items.SADDLE); + if (!player.getInventory().add(saddle)) { + player.drop(saddle, false); + } + } + return InteractionResult.SUCCESS; + } + // Purpur end + if (!flag && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) { if (!this.level().isClientSide) { player.startRiding(this); @@ -470,7 +516,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { if (!enuminteractionresult.consumesAction()) { ItemStack itemstack = player.getItemInHand(hand); - return itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : InteractionResult.PASS; + return itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : tryRide(player, hand); // Purpur } else { if (flag && !this.isSilent()) { this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.STRIDER_EAT, this.getSoundSource(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F); diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java index 65cb385ab294e362d666a6d03c4496cdc3b64890..5db70031c8c9ba901a360c758d51222efe911fcc 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Vex.java +++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java @@ -63,6 +63,65 @@ public class Vex extends Monster implements TraceableEntity { this.xpReward = 3; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.vexRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.vexRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.vexControllable; + } + + @Override + public double getMaxY() { + return level().purpurConfig.vexMaxY; + } + + @Override + public void travel(Vec3 vec3) { + super.travel(vec3); + if (getRider() != null && this.isControllable()) { + float speed; + if (onGround) { + speed = (float) getAttributeValue(Attributes.MOVEMENT_SPEED) * 0.1F; + } else { + speed = (float) getAttributeValue(Attributes.FLYING_SPEED); + } + setSpeed(speed); + Vec3 mot = getDeltaMovement(); + move(MoverType.SELF, mot.multiply(speed, 1.0, speed)); + setDeltaMovement(mot.scale(0.9D)); + } + } + + @Override + public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { + return false; // no fall damage please + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vexMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.vexTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.vexAlwaysDropExp; + } + @Override protected float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) { return dimensions.height - 0.28125F; @@ -81,7 +140,7 @@ public class Vex extends Monster implements TraceableEntity { @Override public void tick() { - this.noPhysics = true; + this.noPhysics = getRider() == null || !this.isControllable(); // Purpur super.tick(); this.noPhysics = false; this.setNoGravity(true); @@ -96,17 +155,19 @@ public class Vex extends Monster implements TraceableEntity { protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(4, new Vex.VexChargeAttackGoal()); this.goalSelector.addGoal(8, new Vex.VexRandomMoveGoal()); this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); this.targetSelector.addGoal(2, new Vex.VexCopyOwnerTargetGoal(this)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true)); } public static AttributeSupplier.Builder createAttributes() { - return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D); + return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur; } @Override @@ -235,14 +296,14 @@ public class Vex extends Monster implements TraceableEntity { return 0.4D; } - private class VexMoveControl extends MoveControl { + private class VexMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur public VexMoveControl(Vex entityvex) { super(entityvex); } @Override - public void tick() { + public void vanillaTick() { // Purpur if (this.operation == MoveControl.Operation.MOVE_TO) { Vec3 vec3d = new Vec3(this.wantedX - Vex.this.getX(), this.wantedY - Vex.this.getY(), this.wantedZ - Vex.this.getZ()); double d0 = vec3d.length(); @@ -251,7 +312,7 @@ public class Vex extends Monster implements TraceableEntity { this.operation = MoveControl.Operation.WAIT; Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().scale(0.5D)); } else { - Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.speedModifier * 0.05D / d0))); + Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.getSpeedModifier() * 0.05D / d0))); // Purpur if (Vex.this.getTarget() == null) { Vec3 vec3d1 = Vex.this.getDeltaMovement(); diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java index 2acc531bd9e948251cac77d979f973678f576394..f1e410b602bf0d5520f8e9340f7b8b9e25ca4d39 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java +++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java @@ -58,14 +58,48 @@ public class Vindicator extends AbstractIllager { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.vindicatorRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.vindicatorRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.vindicatorControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vindicatorMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.vindicatorTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.vindicatorAlwaysDropExp; + } + @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new Vindicator.VindicatorBreakDoorGoal(this)); this.goalSelector.addGoal(2, new AbstractIllager.RaiderOpenDoorGoal(this)); this.goalSelector.addGoal(3, new Raider.HoldGroundAttackGoal(this, 10.0F)); this.goalSelector.addGoal(4, new Vindicator.VindicatorMeleeAttackGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, Raider.class)).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true)); @@ -130,6 +164,12 @@ public class Vindicator extends AbstractIllager { RandomSource randomSource = world.getRandom(); this.populateDefaultEquipmentSlots(randomSource, difficulty); this.populateDefaultEquipmentEnchantments(randomSource, difficulty); + // Purpur start + Level level = world.getMinecraftWorld(); + if (level().purpurConfig.vindicatorJohnnySpawnChance > 0D && random.nextDouble() <= level().purpurConfig.vindicatorJohnnySpawnChance) { + setCustomName(Component.translatable("Johnny")); + } + // Purpur end return spawnGroupData; } diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java index 701f6bf1d558cf0ec4bc1abb9e1f66d96ea5a6c9..80e90202eae1e12d5261d0bb77ecfadcc9ac4599 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Witch.java +++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java @@ -57,6 +57,38 @@ public class Witch extends Raider implements RangedAttackMob { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.witchRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witchRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.witchControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witchMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.witchTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.witchAlwaysDropExp; + } + @Override protected void registerGoals() { super.registerGoals(); @@ -65,10 +97,12 @@ public class Witch extends Raider implements RangedAttackMob { }); this.attackPlayersGoal = new NearestAttackableWitchTargetGoal<>(this, Player.class, 10, true, false, (Predicate) null); this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 60, 10.0F)); this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(3, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[]{Raider.class})); this.targetSelector.addGoal(2, this.healRaidersGoal); this.targetSelector.addGoal(3, this.attackPlayersGoal); diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java index 9a81cb0c3a5ac40ff50dc7c81f6196a447cd03c6..2b3decb90aeb99bef42b36f3b8df06cb1172b413 100644 --- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java +++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java @@ -35,6 +35,38 @@ public class WitherSkeleton extends AbstractSkeleton { this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.witherSkeletonRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witherSkeletonRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.witherSkeletonControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherSkeletonMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.witherSkeletonTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.witherSkeletonAlwaysDropExp; + } + @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 c1abeb62f63d2f8fb891efec8f76c6736b8f7f75..399e9baedaf37597c539aa5432a513365df83b5e 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java @@ -67,6 +67,38 @@ public class Zoglin extends Monster implements Enemy, HoglinBase { this.xpReward = 5; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.zoglinRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zoglinRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.zoglinControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zoglinMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.zoglinTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.zoglinAlwaysDropExp; + } + @Override protected Brain.Provider brainProvider() { return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); @@ -182,7 +214,7 @@ public class Zoglin extends Monster implements Enemy, HoglinBase { @Override public Brain getBrain() { - return super.getBrain(); + return (Brain) super.getBrain(); // Purpur - decompile error } protected void updateActivity() { @@ -198,9 +230,10 @@ public class Zoglin extends Monster implements Enemy, HoglinBase { @Override protected void customServerAiStep() { - this.level().getProfiler().push("zoglinBrain"); + //this.level().getProfiler().push("zoglinBrain"); // Purpur + if (getRider() == null || !this.isControllable()) // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel)this.level(), this); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur 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 3f8c1d1d3c408fc4f15c4b5680bc22c86f104a9d..7c5efb5582f43d9f333f926ad7dba1ed9920333a 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java @@ -95,22 +95,69 @@ public class Zombie extends Monster { private int inWaterTime; public int conversionTime; private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field - private boolean shouldBurnInDay = true; // Paper + // private boolean shouldBurnInDay = true; // Paper // Purpur - implemented in LivingEntity public Zombie(EntityType type, Level world) { super(type, world); this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(type, world.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper + this.setShouldBurnInDay(true); // Purpur } public Zombie(Level world) { this(EntityType.ZOMBIE, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.zombieRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombieRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.zombieControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieMaxHealth); + } + + public boolean jockeyOnlyBaby() { + return level().purpurConfig.zombieJockeyOnlyBaby; + } + + public double jockeyChance() { + return level().purpurConfig.zombieJockeyChance; + } + + public boolean jockeyTryExistingChickens() { + return level().purpurConfig.zombieJockeyTryExistingChickens; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.zombieTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.zombieAlwaysDropExp; + } + @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.addBehaviourGoals(); } @@ -120,7 +167,19 @@ public class Zombie extends Monster { this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers(ZombifiedPiglin.class)); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); - if ( 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)); } @@ -242,30 +301,7 @@ public class Zombie extends Monster { @Override public void aiStep() { - if (this.isAlive()) { - boolean flag = this.isSunSensitive() && this.isSunBurnTick(); - - if (flag) { - ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); - - if (!itemstack.isEmpty()) { - if (itemstack.isDamageableItem()) { - itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); - if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { - this.broadcastBreakEvent(EquipmentSlot.HEAD); - this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); - } - } - - flag = false; - } - - if (flag) { - this.setSecondsOnFire(8); - } - } - } - + // Purpur - implemented in LivingEntity super.aiStep(); } @@ -303,6 +339,7 @@ public class Zombie extends Monster { } + public boolean shouldBurnInDay() { return isSunSensitive(); } // Purpur - for ABI compatibility public boolean isSunSensitive() { return this.shouldBurnInDay; // Paper - use api value instead } @@ -432,7 +469,7 @@ public class Zombie extends Monster { nbt.putBoolean("CanBreakDoors", this.canBreakDoors()); nbt.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1); nbt.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1); - nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper + // nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper // Purpur - implemented in LivingEntity } @Override @@ -446,7 +483,7 @@ public class Zombie extends Monster { } // Paper start if (nbt.contains("Paper.ShouldBurnInDay")) { - this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); + // this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); // Purpur - implemented in LivingEntity } // Paper end @@ -527,19 +564,20 @@ public class Zombie extends Monster { if (object instanceof Zombie.ZombieGroupData) { Zombie.ZombieGroupData entityzombie_groupdatazombie = (Zombie.ZombieGroupData) object; - if (entityzombie_groupdatazombie.isBaby) { - this.setBaby(true); + // Purpur start + if (!jockeyOnlyBaby() || entityzombie_groupdatazombie.isBaby) { + this.setBaby(entityzombie_groupdatazombie.isBaby); if (entityzombie_groupdatazombie.canSpawnJockey) { - if ((double) randomsource.nextFloat() < 0.05D) { - List list = world.getEntitiesOfClass(Chicken.class, this.getBoundingBox().inflate(5.0D, 3.0D, 5.0D), EntitySelector.ENTITY_NOT_BEING_RIDDEN); + if ((double) randomsource.nextFloat() < jockeyChance()) { + List list = jockeyTryExistingChickens() ? world.getEntitiesOfClass(Chicken.class, this.getBoundingBox().inflate(5.0D, 3.0D, 5.0D), EntitySelector.ENTITY_NOT_BEING_RIDDEN) : java.util.Collections.emptyList(); + // Purpur end if (!list.isEmpty()) { Chicken entitychicken = (Chicken) list.get(0); entitychicken.setChickenJockey(true); this.startRiding(entitychicken); - } - } else if ((double) randomsource.nextFloat() < 0.05D) { + } else { // Purpur Chicken entitychicken1 = (Chicken) EntityType.CHICKEN.create(this.level()); if (entitychicken1 != null) { @@ -549,6 +587,7 @@ public class Zombie extends Monster { this.startRiding(entitychicken1); world.addFreshEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit } + } // Purpur } } } @@ -559,11 +598,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; } @@ -595,7 +630,7 @@ public class Zombie extends Monster { } protected void randomizeReinforcementsChance() { - this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * 0.10000000149011612D); + this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieSpawnReinforcements); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java index 25ed5571b24e590bc95056020d84496492b53298..be2a35f703310f699ee31bd2ec1c938af281a577 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java @@ -79,6 +79,58 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { }); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.zombieVillagerRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombieVillagerRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.zombieVillagerControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieVillagerMaxHealth); + } + + @Override + protected void randomizeReinforcementsChance() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieVillagerSpawnReinforcements); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.zombieVillagerTakeDamageFromWater; + } + + @Override + public boolean jockeyOnlyBaby() { + return level().purpurConfig.zombieVillagerJockeyOnlyBaby; + } + + @Override + public double jockeyChance() { + return level().purpurConfig.zombieVillagerJockeyChance; + } + + @Override + public boolean jockeyTryExistingChickens() { + return level().purpurConfig.zombieVillagerJockeyTryExistingChickens; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.zombieVillagerAlwaysDropExp; + } + @Override protected void defineSynchedData() { super.defineSynchedData(); @@ -165,13 +217,13 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { ItemStack itemstack = player.getItemInHand(hand); if (itemstack.is(Items.GOLDEN_APPLE)) { - if (this.hasEffect(MobEffects.WEAKNESS)) { + if (this.hasEffect(MobEffects.WEAKNESS) && level().purpurConfig.zombieVillagerCureEnabled) { // Purpur if (!player.getAbilities().instabuild) { itemstack.shrink(1); } if (!this.level().isClientSide) { - this.startConverting(player.getUUID(), this.random.nextInt(2401) + 3600); + this.startConverting(player.getUUID(), this.random.nextInt(level().purpurConfig.zombieVillagerCuringTimeMax - level().purpurConfig.zombieVillagerCuringTimeMin + 1) + level().purpurConfig.zombieVillagerCuringTimeMin); // Purpur } return InteractionResult.SUCCESS; diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java index aff140cfb2bbdce8b512080cf394c84c5c4ff807..7bb99d7fd8e05805e0cac7bec0b2771990057f58 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java +++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java @@ -63,6 +63,53 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.zombifiedPiglinRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombifiedPiglinRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.zombifiedPiglinControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombifiedPiglinMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.zombifiedPiglinTakeDamageFromWater; + } + + @Override + public boolean jockeyOnlyBaby() { + return level().purpurConfig.zombifiedPiglinJockeyOnlyBaby; + } + + @Override + public double jockeyChance() { + return level().purpurConfig.zombifiedPiglinJockeyChance; + } + + @Override + public boolean jockeyTryExistingChickens() { + return level().purpurConfig.zombifiedPiglinJockeyTryExistingChickens; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.zombifiedPiglinAlwaysDropExp; + } + @Override public void setPersistentAngerTarget(@Nullable UUID angryAt) { this.persistentAngerTarget = angryAt; @@ -115,7 +162,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { this.maybeAlertOthers(); } - if (this.isAngry()) { + if (this.isAngry() && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur this.lastHurtByPlayerTime = this.tickCount; } @@ -170,7 +217,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { this.ticksUntilNextAlert = ZombifiedPiglin.ALERT_INTERVAL.sample(this.random); } - if (entityliving instanceof Player) { + if (entityliving instanceof Player && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur this.setLastHurtByPlayer((Player) entityliving); } @@ -250,7 +297,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { @Override protected void randomizeReinforcementsChance() { - this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(0.0D); + this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombifiedPiglinSpawnReinforcements); // Purpur } @Nullable diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java index d6d61b91096d28eea1e5af69ea1c07890820ee7f..ecc445bb3cda2d79d64c580b72544567dd304f53 100644 --- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java +++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java @@ -67,6 +67,43 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { this.xpReward = 5; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.hoglinRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.hoglinRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.hoglinControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.hoglinMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.hoglinBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.hoglinTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.hoglinAlwaysDropExp; + } + @Override public boolean canBeLeashed(Player player) { return !this.isLeashed(); @@ -129,10 +166,10 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { private int behaviorTick; // Pufferfish @Override protected void customServerAiStep() { - this.level().getProfiler().push("hoglinBrain"); - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + //this.level().getProfiler().push("hoglinBrain"); // Purpur + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel)this.level(), this); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur HoglinAi.updateActivity(this); if (this.isConverting()) { ++this.timeInOverworld; diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java index e235cc9d1b3ce59ab662ef3cf3ce0267ca78536d..79f969ea76fc2073e10c050c2aa64b5b5e473181 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 @@ -96,6 +96,38 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento this.xpReward = 5; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.piglinRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.piglinRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.piglinControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.piglinMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.piglinTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.piglinAlwaysDropExp; + } + @Override public void addAdditionalSaveData(CompoundTag nbt) { super.addAdditionalSaveData(nbt); @@ -308,10 +340,10 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento private int behaviorTick; // Pufferfish @Override protected void customServerAiStep() { - this.level().getProfiler().push("piglinBrain"); - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + //this.level().getProfiler().push("piglinBrain"); // Purpur + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel) this.level(), this); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur PiglinAi.updateActivity(this); super.customServerAiStep(); } @@ -407,7 +439,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento @Override public boolean wantsToPickUp(ItemStack stack) { - return this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); + return (this.level().purpurConfig.piglinBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); } protected boolean canReplaceCurrentItem(ItemStack stack) { diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java index d98c526676202741e628d5e317b8cdd3f4d3be0a..edf9208ed49a179dc15308dd72ee47367c0a9ded 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 @@ -597,20 +597,33 @@ public class PiglinAi { Iterator iterator = iterable.iterator(); Item item; + ItemStack itemstack; // Purpur do { if (!iterator.hasNext()) { return false; } - ItemStack itemstack = (ItemStack) iterator.next(); + itemstack = (ItemStack) iterator.next(); // Purpur item = itemstack.getItem(); - } while (!(item instanceof ArmorItem) || ((ArmorItem) item).getMaterial() != ArmorMaterials.GOLD); + } while (!(item instanceof ArmorItem) || ((ArmorItem) item).getMaterial() != ArmorMaterials.GOLD && (!entity.level().purpurConfig.piglinIgnoresArmorWithGoldTrim || !isWearingGoldTrim(entity, itemstack))); // Purpur return true; } + // Purpur start + private static boolean isWearingGoldTrim(LivingEntity entity, ItemStack itemstack) { + Optional optionalArmorTrim = net.minecraft.world.item.armortrim.ArmorTrim.getTrim(entity.level().registryAccess(), itemstack); + + if (optionalArmorTrim.isEmpty()) return false; + + net.minecraft.world.item.armortrim.ArmorTrim armorTrim = optionalArmorTrim.get(); + + return armorTrim.material().is(net.minecraft.world.item.armortrim.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 d02ee11066fc4f07ccb110b09b86d895ff90d4f2..856e6e02c9424a6c06e310262cb4f5bdd34da516 100644 --- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java +++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java @@ -41,6 +41,38 @@ public class PiglinBrute extends AbstractPiglin { this.xpReward = 20; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.piglinBruteRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.piglinBruteRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.piglinBruteControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.piglinBruteMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.piglinBruteTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.piglinBruteAlwaysDropExp; + } + public static AttributeSupplier.Builder createAttributes() { return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 50.0D).add(Attributes.MOVEMENT_SPEED, (double)0.35F).add(Attributes.ATTACK_DAMAGE, 7.0D); } @@ -70,7 +102,7 @@ public class PiglinBrute extends AbstractPiglin { @Override public Brain getBrain() { - return super.getBrain(); + return (Brain) super.getBrain(); // Purpur - decompile error } @Override @@ -85,9 +117,10 @@ public class PiglinBrute extends AbstractPiglin { @Override protected void customServerAiStep() { - this.level().getProfiler().push("piglinBruteBrain"); + //this.level().getProfiler().push("piglinBruteBrain"); // Purpur + if (getRider() == null || this.isControllable()) // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel)this.level(), this); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur PiglinBruteAi.updateActivity(this); PiglinBruteAi.maybePlayActivitySound(this); super.customServerAiStep(); diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java index 71db8bd6885377d55cfc571fccc21df6d8a57b54..3fa46affc4d77d01909cfeaeaba6e06ba9fd5592 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 @@ -121,8 +121,32 @@ public class Warden extends Monster implements VibrationSystem { this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F); this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F); this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F); + this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.5F); // Purpur } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.wardenRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wardenRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.wardenControllable; + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + } + // Purpur end + @Override public Packet getAddEntityPacket() { return new ClientboundAddEntityPacket(this, this.hasPose(Pose.EMERGING) ? 1 : 0); @@ -276,10 +300,10 @@ public class Warden extends Monster implements VibrationSystem { protected void customServerAiStep() { ServerLevel worldserver = (ServerLevel) this.level(); - worldserver.getProfiler().push("wardenBrain"); + //worldserver.getProfiler().push("wardenBrain"); // Purpur if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish this.getBrain().tick(worldserver, this); - this.level().getProfiler().pop(); + //this.level().getProfiler().pop(); // Purpur super.customServerAiStep(); if ((this.tickCount + this.getId()) % 120 == 0) { Warden.applyDarknessAround(worldserver, this.position(), this, 20); @@ -396,19 +420,16 @@ 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 if (entity instanceof LivingEntity) { LivingEntity entityliving = (LivingEntity) entity; if (this.level() == entity.level() && EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(entity) && !this.isAlliedTo(entity) && entityliving.getType() != EntityType.ARMOR_STAND && entityliving.getType() != EntityType.WARDEN && !entityliving.isInvulnerable() && !entityliving.isDeadOrDying() && this.level().getWorldBorder().isWithinBounds(entityliving.getBoundingBox())) { - flag = true; - return flag; + return true; // Purpur - wtf } } - flag = false; - return flag; + return false; // Purpur - wtf } public static void applyDarknessAround(ServerLevel world, Vec3 pos, @Nullable Entity entity, int range) { diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java index 564908ce0a560c2190fb624e77d227d3b7031024..f2a4e214744227f1df32e3782e71f8a9d5cea261 100644 --- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java @@ -43,6 +43,7 @@ import org.bukkit.event.entity.VillagerAcquireTradeEvent; // CraftBukkit end public abstract class AbstractVillager extends AgeableMob implements InventoryCarrier, Npc, Merchant { + static final net.minecraft.world.item.crafting.Ingredient TEMPT_ITEMS = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.level.block.Blocks.EMERALD_BLOCK.asItem()); // Purpur // CraftBukkit start private CraftMerchant craftMerchant; diff --git a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java index 5f407535298a31a34cfe114dd863fd6a9b977707..29c7e33fe961020e5a0007287fe9b6631689f1b8 100644 --- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java +++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java @@ -30,7 +30,7 @@ public class CatSpawner implements CustomSpawner { if (this.nextTick > 0) { return 0; } else { - this.nextTick = 1200; + this.nextTick = world.purpurConfig.catSpawnDelay; // Purpur Player player = world.getRandomPlayer(); if (player == null) { return 0; @@ -63,11 +63,15 @@ public class CatSpawner implements CustomSpawner { } private int spawnInVillage(ServerLevel world, BlockPos pos) { - int i = 48; + // Purpur start + int range = world.purpurConfig.catSpawnVillageScanRange; + if (range <= 0) return 0; + if (world.getPoiManager().getCountInRange((entry) -> { return entry.is(PoiTypes.HOME); - }, pos, 48, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { - List list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(48.0D, 8.0D, 48.0D)); + }, pos, range, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { + List list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(range, 8.0D, range)); + // Purpur end if (list.size() < 5) { return this.spawnCat(pos, world); } @@ -77,8 +81,11 @@ public class CatSpawner implements CustomSpawner { } private int spawnInHut(ServerLevel world, BlockPos pos) { - int i = 16; - List list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(16.0D, 8.0D, 16.0D)); + // Purpur start + int range = world.purpurConfig.catSpawnSwampHutScanRange; + if (range <= 0) return 0; + List list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(range, 8.0D, range)); + // Purpur end return list.size() < 1 ? this.spawnCat(pos, world) : 0; } diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java index c4ddf2661bca728d504918171295e10e307b18b4..2ea19f72dd8e86d5821f734c13ac2dc6f53e9c30 100644 --- a/src/main/java/net/minecraft/world/entity/npc/Villager.java +++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java @@ -140,6 +140,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler }, MemoryModuleType.MEETING_POINT, (entityvillager, holder) -> { return holder.is(PoiTypes.MEETING); }); + private boolean isLobotomized = false; public boolean isLobotomized() { return this.isLobotomized; } // Purpur + private int notLobotomizedCount = 0; // Purpur public long nextGolemPanic = -1; // Pufferfish @@ -154,6 +156,90 @@ public class Villager extends AbstractVillager implements ReputationEventHandler this.getNavigation().setCanFloat(true); this.setCanPickUpLoot(true); this.setVillagerData(this.getVillagerData().setType(type).setProfession(VillagerProfession.NONE)); + if (level().purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); + } + + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.villagerRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.villagerRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.villagerControllable; + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.villagerMaxHealth); + } + + @Override + public boolean canBeLeashed(Player player) { + return level().purpurConfig.villagerCanBeLeashed && !this.isLeashed(); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.villagerTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.villagerAlwaysDropExp; + } + + private boolean checkLobotomized() { + int interval = this.level().purpurConfig.villagerLobotomizeCheckInterval; + if (this.notLobotomizedCount > 3) { + // check half as often if not lobotomized for the last 3+ consecutive checks + interval *= 2; + } + if (this.level().getGameTime() % interval == 0) { + // offset Y for short blocks like dirt_path/farmland + this.isLobotomized = !canTravelFrom(BlockPos.containing(this.position().x, this.getBoundingBox().minY + 0.0625D, this.position().z)); + + if (this.isLobotomized) { + this.notLobotomizedCount = 0; + } else { + this.notLobotomizedCount++; + } + } + return this.isLobotomized; + } + + private boolean canTravelFrom(BlockPos pos) { + return canTravelTo(pos.east()) || canTravelTo(pos.west()) || canTravelTo(pos.north()) || canTravelTo(pos.south()); + } + + private boolean canTravelTo(BlockPos pos) { + net.minecraft.world.level.block.state.BlockState state = this.level().getBlockStateIfLoaded(pos); + if (state == null) { + // chunk not loaded + return false; + } + net.minecraft.world.level.block.Block bottom = state.getBlock(); + if (bottom instanceof net.minecraft.world.level.block.FenceBlock || + bottom instanceof net.minecraft.world.level.block.FenceGateBlock || + bottom instanceof net.minecraft.world.level.block.WallBlock) { + // bottom block is too tall to get over + return false; + } + net.minecraft.world.level.block.Block top = level().getBlockState(pos.above()).getBlock(); + // only if both blocks have no collision + return !bottom.hasCollision && !top.hasCollision; } @Override @@ -190,7 +276,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)); @@ -253,15 +339,22 @@ public class Villager extends AbstractVillager implements ReputationEventHandler // Paper start this.customServerAiStep(false); } - protected void customServerAiStep(final boolean inactive) { + protected void customServerAiStep(boolean inactive) { // Purpur - not final // Paper end - this.level().getProfiler().push("villagerBrain"); - // Pufferfish start - if (!inactive && this.behaviorTick++ % this.activatedPriority == 0) { - this.getBrain().tick((ServerLevel) this.level(), this); // Paper + //this.level().getProfiler().push("villagerBrain"); // Purpur + // Purpur start + if (this.level().purpurConfig.villagerLobotomizeEnabled) { + // treat as inactive if lobotomized + inactive = inactive || checkLobotomized(); + } else { + // clean up state for API + this.isLobotomized = false; } - // Pufferfish end - this.level().getProfiler().pop(); + if (!inactive && (getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Purpur - only use brain if no rider + this.getBrain().tick((ServerLevel) this.level(), this); // Paper + else if (this.isLobotomized && shouldRestock()) restock(); + // Purpur end + //this.level().getProfiler().pop(); // Purpur if (this.assignProfessionWhenSpawned) { this.assignProfessionWhenSpawned = false; } @@ -317,7 +410,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler if (!itemstack.is(Items.VILLAGER_SPAWN_EGG) && this.isAlive() && !this.isTrading() && !this.isSleeping()) { if (this.isBaby()) { this.setUnhappy(); - return InteractionResult.sidedSuccess(this.level().isClientSide); + return tryRide(player, hand, InteractionResult.sidedSuccess(this.level().isClientSide)); // Purpur } else { boolean flag = this.getOffers().isEmpty(); @@ -330,9 +423,10 @@ public class Villager extends AbstractVillager implements ReputationEventHandler } if (flag) { - return InteractionResult.sidedSuccess(this.level().isClientSide); + return tryRide(player, hand, InteractionResult.sidedSuccess(this.level().isClientSide)); // Purpur } else { - if (!this.level().isClientSide && !this.offers.isEmpty()) { + if (level().purpurConfig.villagerRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur + if (this.level().purpurConfig.villagerAllowTrading && !this.offers.isEmpty()) { this.startTrading(player); } @@ -501,7 +595,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 } } @@ -751,7 +845,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() { @@ -944,6 +1038,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); }); } @@ -993,6 +1092,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); @@ -1066,6 +1166,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 ac70c2c03241e73943bd517a8c69dd05e0873634..0318663a824d2a9515f867a075d148c3fcb1a907 100644 --- a/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java +++ b/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java @@ -26,7 +26,7 @@ public record VillagerProfession(String name, Predicate> heldJob public static final VillagerProfession ARMORER = register("armorer", PoiTypes.ARMORER, SoundEvents.VILLAGER_WORK_ARMORER); public static final VillagerProfession BUTCHER = register("butcher", PoiTypes.BUTCHER, SoundEvents.VILLAGER_WORK_BUTCHER); public static final VillagerProfession CARTOGRAPHER = register("cartographer", PoiTypes.CARTOGRAPHER, SoundEvents.VILLAGER_WORK_CARTOGRAPHER); - public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, SoundEvents.VILLAGER_WORK_CLERIC); + public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, ImmutableSet.of(Items.NETHER_WART), ImmutableSet.of(Blocks.SOUL_SAND), SoundEvents.VILLAGER_WORK_CLERIC); // Purpur public static final VillagerProfession FARMER = register("farmer", PoiTypes.FARMER, ImmutableSet.of(Items.WHEAT, Items.WHEAT_SEEDS, Items.BEETROOT_SEEDS, Items.BONE_MEAL), ImmutableSet.of(Blocks.FARMLAND), SoundEvents.VILLAGER_WORK_FARMER); public static final VillagerProfession FISHERMAN = register("fisherman", PoiTypes.FISHERMAN, SoundEvents.VILLAGER_WORK_FISHERMAN); public static final VillagerProfession FLETCHER = register("fletcher", PoiTypes.FLETCHER, SoundEvents.VILLAGER_WORK_FLETCHER); diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java index 0002f3a2f2e096d4b2015baf707707f6cc1a9582..0e5fae26bf89749e446050c92dc7c23d788f5edb 100644 --- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java @@ -66,6 +66,43 @@ 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 + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.wanderingTraderMaxHealth); + } + + @Override + public boolean canBeLeashed(Player player) { + return level().purpurConfig.wanderingTraderCanBeLeashed && !this.isLeashed(); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.wanderingTraderTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.wanderingTraderAlwaysDropExp; + } + @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); @@ -73,7 +110,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)); @@ -86,6 +123,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill this.goalSelector.addGoal(1, new PanicGoal(this, 0.5D)); this.goalSelector.addGoal(1, new LookAtTradingPlayerGoal(this)); this.goalSelector.addGoal(2, new WanderingTrader.WanderToPositionGoal(this, 2.0D, 0.35D)); + if (level().purpurConfig.wanderingTraderFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); // Purpur this.goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, 0.35D)); this.goalSelector.addGoal(8, new WaterAvoidingRandomStrollGoal(this, 0.35D)); this.goalSelector.addGoal(9, new InteractGoal(this, Player.class, 3.0F, 1.0F)); @@ -113,9 +151,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill } if (this.getOffers().isEmpty()) { - return InteractionResult.sidedSuccess(this.level().isClientSide); + return tryRide(player, hand, InteractionResult.sidedSuccess(this.level().isClientSide)); // Purpur } else { - if (!this.level().isClientSide) { + if (level().purpurConfig.wanderingTraderRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur + if (this.level().purpurConfig.wanderingTraderAllowTrading) { this.setTradingPlayer(player); this.openTradingScreen(player, this.getDisplayName(), 1); } diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java index 8385eb1d60f377da94e3178ab506feefb43563fd..a5443f92786427c42092aec8350e7ab37704db7a 100644 --- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java @@ -159,7 +159,17 @@ public class WanderingTraderSpawner implements CustomSpawner { int k = pos.getX() + this.random.nextInt(range * 2) - range; int l = pos.getZ() + this.random.nextInt(range * 2) - range; int i1 = world.getHeight(Heightmap.Types.WORLD_SURFACE, k, l); - BlockPos blockposition2 = new BlockPos(k, i1, l); + // Purpur start - allow traders to spawn below nether roof + BlockPos.MutableBlockPos blockposition2 = new BlockPos.MutableBlockPos(k, i1, l); + if (world.dimensionType().hasCeiling()) { + do { + blockposition2.relative(net.minecraft.core.Direction.DOWN); + } while (!world.getBlockState(blockposition2).isAir()); + do { + blockposition2.relative(net.minecraft.core.Direction.DOWN); + } while (world.getBlockState(blockposition2).isAir() && blockposition2.getY() > 0); + } + // Purpur end if (NaturalSpawner.isSpawnPositionOk(SpawnPlacements.Type.ON_GROUND, world, blockposition2, EntityType.WANDERING_TRADER)) { blockposition1 = blockposition2; diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java index 58152160d609d0e9d105153aeb166a56a7955603..d00035e31cf4773a418d1cc6a6018d08e6b558f0 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -187,17 +187,40 @@ public abstract class Player extends LivingEntity { public boolean affectsSpawning = true; public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper end + public int sixRowEnderchestSlotCount = -1; // Purpur + public int burpDelay = 0; // Purpur + public boolean canPortalInstant = false; // Purpur // CraftBukkit start public boolean fauxSleeping; public int oldLevel = -1; + public void setAfk(boolean afk) { + } + + public boolean isAfk() { + return false; + } + @Override public CraftHumanEntity getBukkitEntity() { return (CraftHumanEntity) super.getBukkitEntity(); } // CraftBukkit end + // Purpur start + 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 + public Player(Level world, BlockPos pos, float yaw, GameProfile gameProfile) { super(EntityType.PLAYER, world); this.lastItemInMainHand = ItemStack.EMPTY; @@ -242,6 +265,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.setOnGround(false); @@ -345,6 +374,16 @@ public abstract class Player extends LivingEntity { this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TURTLE_HELMET); // CraftBukkit } + // Purpur start + if (this.level().purpurConfig.playerNetheriteFireResistanceDuration > 0 && this.level().getGameTime() % 20 == 0) { + if (itemstack.is(Items.NETHERITE_HELMET) + && this.getItemBySlot(EquipmentSlot.CHEST).is(Items.NETHERITE_CHESTPLATE) + && this.getItemBySlot(EquipmentSlot.LEGS).is(Items.NETHERITE_LEGGINGS) + && this.getItemBySlot(EquipmentSlot.FEET).is(Items.NETHERITE_BOOTS)) { + this.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, this.level().purpurConfig.playerNetheriteFireResistanceDuration, this.level().purpurConfig.playerNetheriteFireResistanceAmplifier, this.level().purpurConfig.playerNetheriteFireResistanceAmbient, this.level().purpurConfig.playerNetheriteFireResistanceShowParticles, this.level().purpurConfig.playerNetheriteFireResistanceShowIcon), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.NETHERITE_ARMOR); + } + } + // Purpur end } protected ItemCooldowns createItemCooldowns() { @@ -431,7 +470,7 @@ public abstract class Player extends LivingEntity { @Override public int getPortalWaitTime() { - return this.abilities.invulnerable ? 1 : 80; + return canPortalInstant ? 1 : this.abilities.invulnerable ? this.level().purpurConfig.playerCreativePortalWaitTime : this.level().purpurConfig.playerPortalWaitTime; // Purpur } @Override @@ -591,7 +630,7 @@ public abstract class Player extends LivingEntity { for (int i = 0; i < list.size(); ++i) { Entity entity = (Entity) list.get(i); - if (entity.getType() == EntityType.EXPERIENCE_ORB) { + if (entity.getType() == EntityType.EXPERIENCE_ORB && entity.level().purpurConfig.playerExpPickupDelay >= 0) { // Purpur list1.add(entity); } else if (!entity.isRemoved()) { this.touch(entity); @@ -1282,7 +1321,7 @@ public abstract class Player extends LivingEntity { flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper flag2 = flag2 && !this.isSprinting(); if (flag2) { - f *= 1.5F; + f *= this.level().purpurConfig.playerCriticalDamageMultiplier; // Purpur } f += f1; @@ -1977,9 +2016,19 @@ public abstract class Player extends LivingEntity { @Override public int getExperienceReward() { if (!this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator()) { - int i = this.experienceLevel * 7; - - return i > 100 ? 100 : i; + // Purpur start + int toDrop; + try { + toDrop = Math.round(((Number) scriptEngine.eval("let expLevel = " + experienceLevel + "; " + + "let expTotal = " + totalExperience + "; " + + "let exp = " + experienceProgress + "; " + + level().purpurConfig.playerDeathExpDropEquation)).floatValue()); + } catch (javax.script.ScriptException e) { + e.printStackTrace(); + toDrop = experienceLevel * 7; + } + return Math.min(toDrop, level().purpurConfig.playerDeathExpDropMax); + // Purpur end } else { return 0; } @@ -2055,6 +2104,11 @@ public abstract class Player extends LivingEntity { return this.inventory.armor; } + @Override + public boolean dismountsUnderwater() { + return !level().purpurConfig.playerRidableInWater; + } + public boolean setEntityOnShoulder(CompoundTag entityNbt) { if (!this.isPassenger() && this.onGround() && !this.isInWater() && !this.isInPowderSnow) { if (this.getShoulderEntityLeft().isEmpty()) { @@ -2339,7 +2393,7 @@ public abstract class Player extends LivingEntity { public ItemStack eat(Level world, ItemStack stack) { this.getFoodData().eat(stack.getItem(), stack); this.awardStat(Stats.ITEM_USED.get(stack.getItem())); - world.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); + // world.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); // Purpur - moved to tick() if (this instanceof ServerPlayer) { CriteriaTriggers.CONSUME_ITEM.trigger((ServerPlayer) this, stack); } diff --git a/src/main/java/net/minecraft/world/entity/player/StackedContents.java b/src/main/java/net/minecraft/world/entity/player/StackedContents.java index 68d272e98f9e54c9b150c75c27a9ae545be842f6..63fc6074082b579c3f88316008cbeb2a2d7f5841 100644 --- a/src/main/java/net/minecraft/world/entity/player/StackedContents.java +++ b/src/main/java/net/minecraft/world/entity/player/StackedContents.java @@ -40,8 +40,62 @@ public class StackedContents { int j = Math.min(maxCount, stack.getCount()); if (this.extrasMap != null && stack.hasTag() && this.extrasMap.accountStack(stack, j)) return; // Paper - if an exact ingredient, don't include it this.put(i, j); + // PaperPR start + if (stack.hasTag()) { + this.put(getExactStackingIndex(stack), j); + } + } + + } + private static final net.minecraft.core.IdMap EXACT_MATCHES_ID_MAP = new net.minecraft.core.IdMap<>() { + private final java.util.concurrent.atomic.AtomicInteger idCounter = new java.util.concurrent.atomic.AtomicInteger(BuiltInRegistries.ITEM.size()); + private final it.unimi.dsi.fastutil.objects.Object2IntMap itemstackToId = new it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap<>(new it.unimi.dsi.fastutil.Hash.Strategy<>() { + @Override + public int hashCode(ItemStack o) { + return java.util.Objects.hash(o.getItem(), o.getTag()); + } + + @Override + public boolean equals(@Nullable ItemStack a, @Nullable ItemStack b) { + if (a == null || b == null) { + return false; + } + return ItemStack.matches(a, b); + } + }); + private final it.unimi.dsi.fastutil.ints.Int2ObjectMap idToItemstack = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(); + + @Override + public int getId(ItemStack value) { + if (!this.itemstackToId.containsKey(value)) { + final int id = this.idCounter.incrementAndGet(); + final ItemStack copy = value.copy(); + this.itemstackToId.put(copy, id); + this.idToItemstack.put(id, copy); + return id; + } + return this.itemstackToId.getInt(value); + } + + @Override + public @Nullable ItemStack byId(int index) { + return this.idToItemstack.get(index); + } + + @Override + public int size() { + return this.itemstackToId.size(); + } + + @Override + public java.util.Iterator iterator() { + return this.idToItemstack.values().iterator(); } + }; + public static int getExactStackingIndex(ItemStack stack) { + return EXACT_MATCHES_ID_MAP.getId(stack); + // PaperPR end } public static int getStackingIndex(ItemStack stack) { @@ -83,6 +137,12 @@ public class StackedContents { } public static ItemStack fromStackingIndex(int itemId) { + // PaperPR start + if (itemId > BuiltInRegistries.ITEM.size()) { + final ItemStack stack = EXACT_MATCHES_ID_MAP.byId(itemId); + return stack == null ? ItemStack.EMPTY : stack.copy(); + } + // PaperPR end return itemId == 0 ? ItemStack.EMPTY : new ItemStack(Item.byId(itemId)); } diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java index fa885337085348308604e50049ecc5bb52023884..6b070ad6aa1b62a66fb85d54042af9d64cdcea7f 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java @@ -72,6 +72,7 @@ public abstract class AbstractArrow extends Projectile { private IntOpenHashSet piercingIgnoreEntityIds; @Nullable private List piercedAndKilledEntities; + public int lootingLevel; // Purpur // Spigot Start @Override @@ -312,7 +313,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 } @Override @@ -612,6 +613,12 @@ public abstract class AbstractArrow extends Projectile { this.knockback = punch; } + // Purpur start + public void setLootingLevel(int looting) { + this.lootingLevel = looting; + } + // Purpur end + public int getKnockback() { return this.knockback; } diff --git a/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java b/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java index 251e6bc87eb02ec4372062ef22b21249ac5164ff..6b60597a0e837054b0d1891c1e6e5e7cd3af9539 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java +++ b/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java @@ -16,20 +16,20 @@ public class LargeFireball extends Fireball { public LargeFireball(EntityType type, Level world) { super(type, world); - isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit + isIncendiary = this.level().purpurConfig.fireballsBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit // Purpur } public LargeFireball(Level world, LivingEntity owner, double velocityX, double velocityY, double velocityZ, int explosionPower) { super(EntityType.FIREBALL, owner, velocityX, velocityY, velocityZ, world); this.explosionPower = explosionPower; - isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit + isIncendiary = this.level().purpurConfig.fireballsBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit // Purpur } @Override protected void onHit(HitResult hitResult) { super.onHit(hitResult); if (!this.level().isClientSide) { - boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + boolean flag = this.level().purpurConfig.fireballsBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur // CraftBukkit start - fire ExplosionPrimeEvent ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity()); diff --git a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java index 0bbe853f7df93f9dcd2b21d762939f8b6be069aa..7db9844083703944f59e112c6dc5e1a5e062d31c 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java +++ b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java @@ -26,6 +26,12 @@ public class LlamaSpit extends Projectile { this.setPos(owner.getX() - (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(owner.yBodyRot * 0.017453292F), owner.getEyeY() - 0.10000000149011612D, owner.getZ() + (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(owner.yBodyRot * 0.017453292F)); } + // Purpur start + public void super_tick() { + super.tick(); + } + // Purpur end + @Override public void tick() { super.tick(); diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java index cd0629581bae5f805842157af36c2d838e01bee3..b0a559dfefac693bda8692a30fbaa5ac8062ef27 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java @@ -326,6 +326,6 @@ public abstract class Projectile extends Entity implements TraceableEntity { public boolean mayInteract(Level world, BlockPos pos) { Entity entity = this.getOwner(); - return entity instanceof Player ? entity.mayInteract(world, pos) : entity == null || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + return entity instanceof Player ? entity.mayInteract(world, pos) : entity == null || world.purpurConfig.projectilesBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); } } diff --git a/src/main/java/net/minecraft/world/entity/projectile/ProjectileUtil.java b/src/main/java/net/minecraft/world/entity/projectile/ProjectileUtil.java index cc0a3d9794d05b6bc6ab05f4f2ab8d83134b181d..e1f918d0bd2a70db1aba8bda8717149f58766825 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/ProjectileUtil.java +++ b/src/main/java/net/minecraft/world/entity/projectile/ProjectileUtil.java @@ -33,7 +33,7 @@ public final class ProjectileUtil { return getHitResult(vec32, entity, predicate, vec3, level); } - private static HitResult getHitResult(Vec3 pos, Entity entity, Predicate predicate, Vec3 velocity, Level world) { + public static HitResult getHitResult(Vec3 pos, Entity entity, Predicate predicate, Vec3 velocity, Level world) { // Purpur - private -> public Vec3 vec3 = pos.add(velocity); HitResult hitResult = world.clip(new ClipContext(pos, vec3, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, entity)); if (hitResult.getType() != HitResult.Type.MISS) { 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 04c2ea1ff44af72ae48e2d6b7b912b1c14285038..7839d856b3f924f0c87b298d9a1e3b15ab0693d6 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java +++ b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java @@ -24,7 +24,7 @@ public class SmallFireball extends Fireball { super(EntityType.SMALL_FIREBALL, owner, velocityX, velocityY, velocityZ, world); // CraftBukkit start if (this.getOwner() != null && this.getOwner() instanceof Mob) { - isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + isIncendiary = this.level().purpurConfig.fireballsBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur } // CraftBukkit end } diff --git a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java index 718e120c9768cf716b32d3d652f53f1dda925168..440d3d72d8b2dac14f83a83caa5ae9dbf3e979b6 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java @@ -53,10 +53,40 @@ public class Snowball extends ThrowableItemProjectile { protected void onHitEntity(EntityHitResult entityHitResult) { super.onHitEntity(entityHitResult); Entity entity = entityHitResult.getEntity(); - int i = entity instanceof Blaze ? 3 : 0; + int i = entity.level().purpurConfig.snowballDamage >= 0 ? entity.level().purpurConfig.snowballDamage : entity instanceof Blaze ? 3 : 0; // Purpur entity.hurt(this.damageSources().thrown(this, this.getOwner()), (float)i); } + // Purpur start - borrowed and modified code from ThrownPotion#onHitBlock and ThrownPotion#dowseFire + @Override + protected void onHitBlock(net.minecraft.world.phys.BlockHitResult blockHitResult) { + super.onHitBlock(blockHitResult); + + if (!this.level().isClientSide) { + net.minecraft.core.BlockPos blockposition = blockHitResult.getBlockPos(); + net.minecraft.core.BlockPos blockposition1 = blockposition.relative(blockHitResult.getDirection()); + + net.minecraft.world.level.block.state.BlockState iblockdata = this.level().getBlockState(blockposition); + + if (this.level().purpurConfig.snowballExtinguishesFire && this.level().getBlockState(blockposition1).is(net.minecraft.world.level.block.Blocks.FIRE)) { + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition1, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { + 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 e8114d89a3129e56c0329410a49ded63cc77cb4c..d7359c675707eade00f9b737fd67ef8d066e813f 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java @@ -68,10 +68,11 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { Bukkit.getPluginManager().callEvent(teleEvent); if (!teleEvent.isCancelled() && entityplayer.connection.isAcceptingMessages()) { - if (this.random.nextFloat() < 0.05F && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { + if (this.random.nextFloat() < this.level().purpurConfig.enderPearlEndermiteChance && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { // Purpur Endermite entityendermite = (Endermite) EntityType.ENDERMITE.create(this.level()); if (entityendermite != null) { + entityendermite.setPlayerSpawned(true); // Purpur entityendermite.moveTo(entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot()); this.level().addFreshEntity(entityendermite, CreatureSpawnEvent.SpawnReason.ENDER_PEARL); } @@ -84,7 +85,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { entityplayer.connection.teleport(teleEvent.getTo()); entity.resetFallDistance(); CraftEventFactory.entityDamage = this; - entity.hurt(this.damageSources().fall(), 5.0F); + entity.hurt(this.damageSources().fall(), this.level().purpurConfig.enderPearlDamage); // Purpur CraftEventFactory.entityDamage = null; } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java index a6bc277b6589dd7104566542733327822d6299a4..dde841cc09ba4a3575a462b03537887551d47ba5 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java +++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java @@ -62,7 +62,7 @@ public class ThrownTrident extends AbstractArrow { Entity entity = this.getOwner(); byte b0 = (Byte) this.entityData.get(ThrownTrident.ID_LOYALTY); - if (b0 > 0 && (this.dealtDamage || this.isNoPhysics()) && entity != null) { + if (b0 > 0 && (this.dealtDamage || this.isNoPhysics() || (level().purpurConfig.tridentLoyaltyVoidReturnHeight < 0.0D && getY() < level().purpurConfig.tridentLoyaltyVoidReturnHeight)) && entity != null) { // Purpur if (!this.isAcceptibleReturnOwner()) { if (!this.level().isClientSide && this.pickup == AbstractArrow.Pickup.ALLOWED) { this.spawnAtLocation(this.getPickupItem(), 0.1F); diff --git a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java index 06d1bdb9bd124b201c36d284c50d22bf50d3735a..b4687453256ead43cf5288994316c7bf946b86df 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java +++ b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java @@ -97,7 +97,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()) { @@ -109,6 +109,17 @@ public class WitherSkull extends AbstractHurtingProjectile { } + @Override + public boolean canHitEntity(Entity target) { + // do not hit rider + return target != this.getRider() && super.canHitEntity(target); + } + + @Override + public boolean canSaveToDisk() { + return false; + } + @Override public boolean isPickable() { return 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 57fdcdaf54fd1c92a6e51a3a81789029096e5abe..822dd4265ce02252afadbc652064450b7dfd7e0d 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raider.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java @@ -319,7 +319,7 @@ public abstract class Raider extends PatrollingMonster { @Override public boolean canUse() { - if (!this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items + if ((!this.mob.level().purpurConfig.pillagerBypassMobGriefing && !this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items // Purpur Raid raid = this.mob.getCurrentRaid(); if (this.mob.hasActiveRaid() && !this.mob.getCurrentRaid().isOver() && this.mob.canBeLeader() && !ItemStack.matches(this.mob.getItemBySlot(EquipmentSlot.HEAD), Raid.getLeaderBannerInstance())) { 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 41457c9f27b18fa2734a6cca297ec5186470e82f..94356e0541f8f4da68211fa533347cc97d4f3518 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raids.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raids.java @@ -28,6 +28,7 @@ import net.minecraft.world.phys.Vec3; public class Raids extends SavedData { private static final String RAID_FILE_ID = "raids"; + public final Map playerCooldowns = Maps.newHashMap(); public final Map raidMap = Maps.newHashMap(); private final ServerLevel level; private int nextAvailableID; @@ -45,6 +46,17 @@ public class Raids extends SavedData { public void tick() { ++this.tick; + // Purpur start + if (level.purpurConfig.raidCooldownSeconds != 0 && this.tick % 20 == 0) { + com.google.common.collect.ImmutableMap.copyOf(playerCooldowns).forEach((uuid, i) -> { + if (i < 1) { + playerCooldowns.remove(uuid); + } else { + playerCooldowns.put(uuid, i - 1); + } + }); + } + // Purpur end Iterator iterator = this.raidMap.values().iterator(); while (iterator.hasNext()) { @@ -129,11 +141,13 @@ public class Raids extends SavedData { } if (flag) { + if (level.purpurConfig.raidCooldownSeconds != 0 && playerCooldowns.containsKey(player.getUUID())) return null; // Purpur // CraftBukkit start if (!org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(raid, player)) { player.removeEffect(MobEffects.BAD_OMEN); return null; } + if (level.purpurConfig.raidCooldownSeconds != 0) playerCooldowns.put(player.getUUID(), level.purpurConfig.raidCooldownSeconds); // Purpur if (!this.raidMap.containsKey(raid.getId())) { this.raidMap.put(raid.getId(), raid); diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java index 9948a28dae4edba877c13ef0156be5ff58df3fa2..b99b94c6ec4767aba16d82eaca8b2761d779b226 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java @@ -108,11 +108,13 @@ public abstract class AbstractMinecart extends Entity { private double flyingY = 0.949999988079071D; // Paper - restore vanilla precision private double flyingZ = 0.949999988079071D; // Paper - restore vanilla precision public double maxSpeed = 0.4D; + public double storedMaxSpeed; // Purpur // CraftBukkit end protected AbstractMinecart(EntityType type, Level world) { super(type, world); this.blocksBuilding = true; + if (world != null) maxSpeed = storedMaxSpeed = world.purpurConfig.minecartMaxSpeed; // Purpur } protected AbstractMinecart(EntityType type, Level world, double x, double y, double z) { @@ -335,6 +337,12 @@ public abstract class AbstractMinecart extends Entity { @Override public void tick() { + // Purpur start + if (storedMaxSpeed != level().purpurConfig.minecartMaxSpeed) { + maxSpeed = storedMaxSpeed = level().purpurConfig.minecartMaxSpeed; + } + // Purpur end + // CraftBukkit start double prevX = this.getX(); double prevY = this.getY(); @@ -499,16 +507,62 @@ public abstract class AbstractMinecart extends Entity { public void activateMinecart(int x, int y, int z, boolean powered) {} + // Purpur start + private Double lastSpeed; + + public double getControllableSpeed() { + 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 + protected void comeOffTrack() { double d0 = this.getMaxSpeed(); Vec3 vec3d = this.getDeltaMovement(); this.setDeltaMovement(Mth.clamp(vec3d.x, -d0, d0), vec3d.y, Mth.clamp(vec3d.z, -d0, d0)); + + // Purpur start + if (level().purpurConfig.minecartControllable && !isInWater() && !isInLava() && !passengers.isEmpty()) { + Entity passenger = passengers.get(0); + if (passenger instanceof Player) { + Player player = (Player) passenger; + if (player.jumping && this.onGround) { + setDeltaMovement(new Vec3(getDeltaMovement().x, level().purpurConfig.minecartControllableHopBoost, getDeltaMovement().z)); + } + if (player.zza != 0.0F) { + Vector velocity = player.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(getControllableSpeed()); + if (player.zza < 0.0) { + velocity.multiply(-0.5); + } + setDeltaMovement(new Vec3(velocity.getX(), getDeltaMovement().y, velocity.getZ())); + } + this.setYRot(passenger.getYRot() - 90); + maxUpStep = level().purpurConfig.minecartControllableStepHeight; + } else { + maxUpStep = 0.0F; + } + } else { + maxUpStep = 0.0F; + } + // Purpur end + if (this.onGround()) { // CraftBukkit start - replace magic numbers with our variables this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.derailedX, this.getDeltaMovement().y * this.derailedY, this.getDeltaMovement().z * this.derailedZ)); // CraftBukkit end } + else if (level().purpurConfig.minecartControllable) setDeltaMovement(new Vec3(getDeltaMovement().x * derailedX, getDeltaMovement().y, getDeltaMovement().z * derailedZ)); // Purpur this.move(MoverType.SELF, this.getDeltaMovement()); if (!this.onGround()) { @@ -670,7 +724,7 @@ public abstract class AbstractMinecart extends Entity { if (d18 > 0.01D) { double d20 = 0.06D; - this.setDeltaMovement(vec3d4.add(vec3d4.x / d18 * 0.06D, 0.0D, vec3d4.z / d18 * 0.06D)); + this.setDeltaMovement(vec3d4.add(vec3d4.x / d18 * this.level().purpurConfig.poweredRailBoostModifier, 0.0D, vec3d4.z / d18 * this.level().purpurConfig.poweredRailBoostModifier)); // Purpur } else { Vec3 vec3d5 = this.getDeltaMovement(); double d21 = vec3d5.x; diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java index 2c5658df753ebc08f8531d4bdf22ff8f6ca77e94..a7ab9e2284d4a1ae6f5601adc3dfac534bce26fc 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java @@ -223,7 +223,13 @@ public class Boat extends Entity implements VariantHolder { } protected void destroy(DamageSource source) { - this.spawnAtLocation((ItemLike) this.getDropItem()); + // Purpur start + final ItemStack boat = new ItemStack(this.getDropItem()); + if (this.level().purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { + boat.setHoverName(this.getCustomName()); + } + this.spawnAtLocation(boat); + // Purpur end } @Override @@ -541,6 +547,7 @@ public class Boat extends Entity implements VariantHolder { if (f > 0.0F) { this.landFriction = f; + if (level().purpurConfig.boatEjectPlayersOnLand) ejectPassengers(); // Purpur return Boat.Status.ON_LAND; } else { return Boat.Status.IN_AIR; diff --git a/src/main/java/net/minecraft/world/food/FoodData.java b/src/main/java/net/minecraft/world/food/FoodData.java index 2038df72f8d7d33d4105de8129628daf21de6f0f..e54af9ff2a786e919b8261aa27509be942e70261 100644 --- a/src/main/java/net/minecraft/world/food/FoodData.java +++ b/src/main/java/net/minecraft/world/food/FoodData.java @@ -33,8 +33,10 @@ public class FoodData { // CraftBukkit end public void eat(int food, float saturationModifier) { + int oldValue = this.foodLevel; // Purpur this.foodLevel = Math.min(food + this.foodLevel, 20); this.saturationLevel = Math.min(this.saturationLevel + (float) food * saturationModifier * 2.0F, (float) this.foodLevel); + if (this.entityhuman.level().purpurConfig.playerBurpWhenFull && this.foodLevel == 20 && oldValue < 20) this.entityhuman.burpDelay = this.entityhuman.level().purpurConfig.playerBurpDelay; // Purpur } public void eat(Item item, ItemStack stack) { @@ -100,7 +102,7 @@ public class FoodData { ++this.tickTimer; if (this.tickTimer >= this.starvationRate) { // CraftBukkit - add regen rate manipulation if (player.getHealth() > 10.0F || enumdifficulty == Difficulty.HARD || player.getHealth() > 1.0F && enumdifficulty == Difficulty.NORMAL) { - player.hurt(player.damageSources().starve(), 1.0F); + player.hurt(player.damageSources().starve(), player.level().purpurConfig.hungerStarvationDamage); // Purpur } this.tickTimer = 0; diff --git a/src/main/java/net/minecraft/world/food/FoodProperties.java b/src/main/java/net/minecraft/world/food/FoodProperties.java index 9967ba762567631f2bdb1e4f8fe16a13ea927b46..6c945ae8fe8b1517e312c688f829fab41f12d9f4 100644 --- a/src/main/java/net/minecraft/world/food/FoodProperties.java +++ b/src/main/java/net/minecraft/world/food/FoodProperties.java @@ -2,15 +2,22 @@ package net.minecraft.world.food; import com.google.common.collect.Lists; import com.mojang.datafixers.util.Pair; + +import java.util.ArrayList; import java.util.List; import net.minecraft.world.effect.MobEffectInstance; public class FoodProperties { - private final int nutrition; - private final float saturationModifier; - private final boolean isMeat; - private final boolean canAlwaysEat; - private final boolean fastFood; + // Purpur start + private int nutrition; public void setNutrition(int nutrition) { this.nutrition = nutrition; } + private float saturationModifier; public void setSaturationModifier(float saturation) { this.saturationModifier = saturation; } + private boolean isMeat; public void setIsMeat(boolean isMeat) { this.isMeat = isMeat; } + private boolean canAlwaysEat; public void setCanAlwaysEat(boolean canAlwaysEat) { this.canAlwaysEat = canAlwaysEat; } + private boolean fastFood; public void setFastFood(boolean isFastFood) { this.fastFood = isFastFood; } + public FoodProperties copy() { + return new FoodProperties(this.nutrition, this.saturationModifier, this.isMeat, this.canAlwaysEat, this.fastFood, new ArrayList<>(this.effects)); + } + // Purpur end private final List> effects; FoodProperties(int hunger, float saturationModifier, boolean meat, boolean alwaysEdible, boolean snack, List> statusEffects) { diff --git a/src/main/java/net/minecraft/world/food/Foods.java b/src/main/java/net/minecraft/world/food/Foods.java index b16d9e2eaa589f19c563ee70b1a56d67dbcdecb0..71beab673f04cd051c46ea37f8c847316885d38d 100644 --- a/src/main/java/net/minecraft/world/food/Foods.java +++ b/src/main/java/net/minecraft/world/food/Foods.java @@ -4,6 +4,9 @@ import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; public class Foods { + public static final java.util.Map ALL_PROPERTIES = new java.util.HashMap<>(); // Purpur + public static final java.util.Map DEFAULT_PROPERTIES = new java.util.HashMap<>(); // Purpur + public static final FoodProperties APPLE = (new FoodProperties.Builder()).nutrition(4).saturationMod(0.3F).build(); public static final FoodProperties BAKED_POTATO = (new FoodProperties.Builder()).nutrition(5).saturationMod(0.6F).build(); public static final FoodProperties BEEF = (new FoodProperties.Builder()).nutrition(3).saturationMod(0.3F).meat().build(); diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java index 7bba845462813615224f48322c51c7b480adcaa7..38d47fbcd49cc291040ea00470abeb982fe2714c 100644 --- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java @@ -76,6 +76,7 @@ public abstract class AbstractContainerMenu { @Nullable private ContainerSynchronizer synchronizer; private boolean suppressRemoteUpdates; + @javax.annotation.Nullable protected ItemStack activeQuickItem = null; // Purpur // CraftBukkit start public boolean checkReachable = true; diff --git a/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java index 35b41bceeba72c3896c91c2605bac3b0bf9c54e9..5c028f957661089ff502109c996692856b12ee27 100644 --- a/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java @@ -145,7 +145,13 @@ public abstract class AbstractFurnaceMenu extends RecipeBookMenu { } else if (slot != 1 && slot != 0) { if (this.canSmelt(itemstack1)) { if (!this.moveItemStackTo(itemstack1, 0, 1, false)) { - return ItemStack.EMPTY; + // Purpur start - fix #625 + if (this.isFuel(itemstack1)) { + if (!this.moveItemStackTo(itemstack1, 1, 2, false)) { + return ItemStack.EMPTY; + } + } + // Purpur end } } else if (this.isFuel(itemstack1)) { if (!this.moveItemStackTo(itemstack1, 1, 2, false)) { diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java index e0c3a4ba27e21c3692e601acd0af60873bcbb84c..531b911c1bcdd3735529ee18f2bb0ccdf4ad6089 100644 --- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java @@ -23,6 +23,13 @@ import org.slf4j.Logger; import org.bukkit.craftbukkit.inventory.CraftInventoryView; // CraftBukkit end +// Purpur start +import net.minecraft.nbt.IntTag; +import net.minecraft.network.protocol.game.ClientboundContainerSetDataPacket; +import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; +import net.minecraft.server.level.ServerPlayer; +// Purpur end + public class AnvilMenu extends ItemCombinerMenu { public static final int INPUT_SLOT = 0; @@ -51,6 +58,8 @@ public class AnvilMenu extends ItemCombinerMenu { public int maximumRepairCost = 40; private CraftInventoryView bukkitEntity; // CraftBukkit end + public boolean bypassCost = false; // Purpur + public boolean canDoUnsafeEnchants = false; // Purpur public AnvilMenu(int syncId, Inventory inventory) { this(syncId, inventory, ContainerLevelAccess.NULL); @@ -78,12 +87,15 @@ public class AnvilMenu extends ItemCombinerMenu { @Override protected boolean mayPickup(Player player, boolean present) { - return (player.getAbilities().instabuild || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && present; // CraftBukkit - allow cost 0 like a free item + return (player.getAbilities().instabuild || player.experienceLevel >= this.cost.get()) && (bypassCost || this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST) && present; // CraftBukkit - allow cost 0 like a free item // Purpur } @Override protected void onTake(Player player, ItemStack stack) { + ItemStack itemstack = activeQuickItem == null ? stack : activeQuickItem; // Purpur + if (org.purpurmc.purpur.event.inventory.AnvilTakeResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)).callEvent(); // Purpur if (!player.getAbilities().instabuild) { + if (bypassCost) ((ServerPlayer) player).lastSentExp = -1; else // Purpur player.giveExperienceLevels(-this.cost.get()); } @@ -134,6 +146,12 @@ public class AnvilMenu extends ItemCombinerMenu { @Override public void createResult() { + // Purpur start + bypassCost = false; + canDoUnsafeEnchants = false; + if (org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent(getBukkitView()).callEvent(); + // Purpur end + ItemStack itemstack = this.inputSlots.getItem(0); this.cost.set(1); @@ -210,7 +228,8 @@ public class AnvilMenu extends ItemCombinerMenu { int i2 = (Integer) map1.get(enchantment); i2 = l1 == i2 ? i2 + 1 : Math.max(i2, l1); - boolean flag3 = enchantment.canEnchant(itemstack); + boolean flag3 = canDoUnsafeEnchants || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants && org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants) || enchantment.canEnchant(itemstack); // Purpur + boolean flag4 = true; // Purpur if (this.player.getAbilities().instabuild || itemstack.is(Items.ENCHANTED_BOOK)) { flag3 = true; @@ -222,16 +241,20 @@ public class AnvilMenu extends ItemCombinerMenu { Enchantment enchantment1 = (Enchantment) iterator1.next(); if (enchantment1 != enchantment && !enchantment.isCompatibleWith(enchantment1)) { - flag3 = false; + flag4 = canDoUnsafeEnchants || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants && org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants); // Purpur flag3 -> flag4 + if (!flag4 && org.purpurmc.purpur.PurpurConfig.replaceIncompatibleEnchants) { + iterator1.remove(); + flag4 = true; + } ++i; } } - if (!flag3) { + if (!flag3 || !flag4) { // Purpur flag2 = true; } else { flag1 = true; - if (i2 > enchantment.getMaxLevel()) { + if ((!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants || !org.purpurmc.purpur.PurpurConfig.allowHigherEnchantsLevels) && i2 > enchantment.getMaxLevel()) { // Purpur i2 = enchantment.getMaxLevel(); } @@ -276,6 +299,54 @@ public class AnvilMenu extends ItemCombinerMenu { if (!this.itemName.equals(itemstack.getHoverName().getString())) { b1 = 1; i += b1; + // Purpur start + if (this.player != null) { + org.bukkit.craftbukkit.entity.CraftHumanEntity player = this.player.getBukkitEntity(); + String name = this.itemName; + boolean removeItalics = false; + if (player.hasPermission("purpur.anvil.remove_italics")) { + if (name.startsWith("&r")) { + name = name.substring(2); + removeItalics = true; + } else if (name.startsWith("")) { + name = name.substring(3); + removeItalics = true; + } else if (name.startsWith("")) { + name = name.substring(7); + removeItalics = true; + } + } + if (this.player.level().purpurConfig.anvilAllowColors) { + if (player.hasPermission("purpur.anvil.color")) { + java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?i)&([0-9a-fr])").matcher(name); + while (matcher.find()) { + String match = matcher.group(1); + name = name.replace("&" + match, "\u00a7" + match.toLowerCase(java.util.Locale.ROOT)); + } + //name = name.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1"); + } + if (player.hasPermission("purpur.anvil.format")) { + java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?i)&([k-or])").matcher(name); + while (matcher.find()) { + String match = matcher.group(1); + name = name.replace("&" + match, "\u00a7" + match.toLowerCase(java.util.Locale.ROOT)); + } + //name = name.replaceAll("(?i)&([l-or])", "\u00a7$1"); + } + } + net.kyori.adventure.text.Component component; + if (this.player.level().purpurConfig.anvilColorsUseMiniMessage && player.hasPermission("purpur.anvil.minimessage")) { + component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.bukkit.ChatColor.stripColor(name)); + } else { + component = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(name); + } + if (removeItalics) { + component = component.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); + } + itemstack1.setHoverName(io.papermc.paper.adventure.PaperAdventure.asVanilla(component)); + } + else + // Purpur end itemstack1.setHoverName(Component.literal(this.itemName)); } } else if (itemstack.hasCustomHoverName()) { @@ -293,6 +364,13 @@ public class AnvilMenu extends ItemCombinerMenu { this.cost.set(this.maximumRepairCost - 1); // CraftBukkit } + // Purpur start + if (bypassCost && cost.get() >= maximumRepairCost) { + itemstack.addTagElement("Purpur.realCost", IntTag.valueOf(cost.get())); + cost.set(maximumRepairCost - 1); + } + // Purpur end + if (this.cost.get() >= this.maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit itemstack1 = ItemStack.EMPTY; } @@ -315,11 +393,17 @@ public class AnvilMenu extends ItemCombinerMenu { org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemstack1); // CraftBukkit sendAllDataToRemote(); // CraftBukkit - SPIGOT-6686: Always send completed inventory to stay in sync with client this.broadcastChanges(); + // Purpur start + if ((canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants) && itemstack1 != ItemStack.EMPTY) { + ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(containerId, incrementStateId(), 2, itemstack1)); + ((ServerPlayer) player).connection.send(new ClientboundContainerSetDataPacket(containerId, 0, cost.get())); + } + // Purpur end } } public static int calculateIncreasedRepairCost(int cost) { - return cost * 2 + 1; + return org.purpurmc.purpur.PurpurConfig.anvilCumulativeCost ? cost * 2 + 1 : 0; } public boolean setItemName(String newItemName) { diff --git a/src/main/java/net/minecraft/world/inventory/ChestMenu.java b/src/main/java/net/minecraft/world/inventory/ChestMenu.java index 0dbfd23bbfc6ad203f048142f8c90ef741849fe1..9a80427d2bb470b6b1638e59aba57216676dcbd2 100644 --- a/src/main/java/net/minecraft/world/inventory/ChestMenu.java +++ b/src/main/java/net/minecraft/world/inventory/ChestMenu.java @@ -67,10 +67,30 @@ public class ChestMenu extends AbstractContainerMenu { return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, 6); } + // Purpur start + public static ChestMenu oneRow(int syncId, Inventory playerInventory, Container inventory) { + return new ChestMenu(MenuType.GENERIC_9x1, syncId, playerInventory, inventory, 1); + } + + public static ChestMenu twoRows(int syncId, Inventory playerInventory, Container inventory) { + return new ChestMenu(MenuType.GENERIC_9x2, syncId, playerInventory, inventory, 2); + } + // Purpur end + public static ChestMenu threeRows(int syncId, Inventory playerInventory, Container inventory) { return new ChestMenu(MenuType.GENERIC_9x3, syncId, playerInventory, inventory, 3); } + // Purpur start + public static ChestMenu fourRows(int syncId, Inventory playerInventory, Container inventory) { + return new ChestMenu(MenuType.GENERIC_9x4, syncId, playerInventory, inventory, 4); + } + + public static ChestMenu fiveRows(int syncId, Inventory playerInventory, Container inventory) { + return new ChestMenu(MenuType.GENERIC_9x5, syncId, playerInventory, inventory, 5); + } + // Purpur end + public static ChestMenu sixRows(int syncId, Inventory playerInventory, Container inventory) { return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, inventory, 6); } diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java index 496c3fcf49043bba4d249e1c05aac7d7b4897ea8..f0a84889103cb605d3b1496f03d855848a5f5067 100644 --- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java @@ -38,6 +38,12 @@ import org.bukkit.event.enchantment.PrepareItemEnchantEvent; import org.bukkit.entity.Player; // CraftBukkit end +// Purpur start +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.EnchantmentTableBlockEntity; +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +// Purpur end + public class EnchantmentMenu extends AbstractContainerMenu { private final Container enchantSlots; @@ -71,6 +77,22 @@ public class EnchantmentMenu extends AbstractContainerMenu { return context.getLocation(); } // CraftBukkit end + + // Purpur start + @Override + public void onClose(CraftHumanEntity who) { + super.onClose(who); + + if (who.getHandle().level().purpurConfig.enchantmentTableLapisPersists) { + access.execute((level, pos) -> { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof EnchantmentTableBlockEntity enchantmentTable) { + enchantmentTable.setLapis(this.getItem(1).getCount()); + } + }); + } + } + // Purpur end }; this.random = RandomSource.create(); this.enchantmentSeed = DataSlot.standalone(); @@ -96,6 +118,17 @@ public class EnchantmentMenu extends AbstractContainerMenu { } }); + // Purpur start + access.execute((level, pos) -> { + if (level.purpurConfig.enchantmentTableLapisPersists) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof EnchantmentTableBlockEntity enchantmentTable) { + this.getSlot(1).set(new ItemStack(Items.LAPIS_LAZULI, enchantmentTable.getLapis())); + } + } + }); + // Purpur end + int j; for (j = 0; j < 3; ++j) { @@ -340,6 +373,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 a21eadcdfbdc4be803c5793bc97996db3e706071..3b721092a7a73472756064e0eb91d3220fdc4bc8 100644 --- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java @@ -95,9 +95,11 @@ public class GrindstoneMenu extends AbstractContainerMenu { @Override public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) { + ItemStack itemstack = activeQuickItem == null ? stack : activeQuickItem; // Purpur context.execute((world, blockposition) -> { + org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent grindstoneTakeResultEvent = new org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), this.getExperienceAmount(world)); grindstoneTakeResultEvent.callEvent(); // Purpur if (world instanceof ServerLevel) { - ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Paper + ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), grindstoneTakeResultEvent.getExperienceAmount(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Paper // Purpur } world.levelEvent(1042, blockposition, 0); @@ -130,7 +132,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { Enchantment enchantment = (Enchantment) entry.getKey(); Integer integer = (Integer) entry.getValue(); - if (!enchantment.isCurse()) { + if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(enchantment)) { // Purpur j += enchantment.getMinCost(integer); } } @@ -230,7 +232,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { Entry entry = (Entry) iterator.next(); Enchantment enchantment = (Enchantment) entry.getKey(); - if (!enchantment.isCurse() || EnchantmentHelper.getItemEnchantmentLevel(enchantment, itemstack2) == 0) { + if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(enchantment) || EnchantmentHelper.getItemEnchantmentLevel(enchantment, itemstack2) == 0) { // Purpur itemstack2.enchant(enchantment, (Integer) entry.getValue()); } } @@ -250,7 +252,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { } Map map = (Map) EnchantmentHelper.getEnchantments(item).entrySet().stream().filter((entry) -> { - return ((Enchantment) entry.getKey()).isCurse(); + return org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains((Enchantment) entry.getKey()); // Purpur }).collect(Collectors.toMap(Entry::getKey, Entry::getValue)); EnchantmentHelper.setEnchantments(map, itemstack1); @@ -266,6 +268,20 @@ public class GrindstoneMenu extends AbstractContainerMenu { itemstack1.setRepairCost(AnvilMenu.calculateIncreasedRepairCost(itemstack1.getBaseRepairCost())); } + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveAttributes && itemstack1.getTag() != null) { + for (String key : itemstack1.getTag().getAllKeys()) { + if (!key.equals("display")) { + itemstack1.getTag().remove(key); + } + } + } + + if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveDisplay && itemstack1.getTag() != null) { + itemstack1.getTag().remove("display"); + } + // Purpur end + return itemstack1; } @@ -327,7 +343,9 @@ public class GrindstoneMenu extends AbstractContainerMenu { return ItemStack.EMPTY; } + this.activeQuickItem = itemstack; // Purpur slot1.onTake(player, itemstack1); + this.activeQuickItem = null; // Purpur } return itemstack; diff --git a/src/main/java/net/minecraft/world/inventory/InventoryMenu.java b/src/main/java/net/minecraft/world/inventory/InventoryMenu.java index c549618421c5d077c3d977d8d2064eca2acc438a..5972fa434847d24fa98b7895fd8386d20a636885 100644 --- a/src/main/java/net/minecraft/world/inventory/InventoryMenu.java +++ b/src/main/java/net/minecraft/world/inventory/InventoryMenu.java @@ -4,6 +4,7 @@ import com.mojang.datafixers.util.Pair; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.Container; +import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.player.Inventory; @@ -95,7 +96,7 @@ public class InventoryMenu extends RecipeBookMenu { public boolean mayPickup(Player playerEntity) { ItemStack itemstack = this.getItem(); - return !itemstack.isEmpty() && !playerEntity.isCreative() && EnchantmentHelper.hasBindingCurse(itemstack) ? false : super.mayPickup(playerEntity); + return !itemstack.isEmpty() && !playerEntity.isCreative() && EnchantmentHelper.hasBindingCurse(itemstack) ? playerEntity.level().purpurConfig.playerRemoveBindingWithWeakness && playerEntity.hasEffect(MobEffects.WEAKNESS) : super.mayPickup(playerEntity); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java index c2cf5a8e788637c6264cf43d712a5be223ff1cc5..a578882b64c581ef4f47fa483a9f2988cfa4c8cf 100644 --- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java @@ -178,7 +178,9 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { return ItemStack.EMPTY; } + this.activeQuickItem = itemstack; // Purpur slot1.onTake(player, itemstack1); + this.activeQuickItem = null; // Purpur } return itemstack; diff --git a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java index 4703f23316f82a1a942907b46d2d6dcb7d70ec37..162798f57a05b78121fa6c4fadf5adee80fbe221 100644 --- a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java +++ b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java @@ -30,11 +30,18 @@ public class PlayerEnderChestContainer extends SimpleContainer { } public PlayerEnderChestContainer(Player owner) { - super(27); + super(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? 54 : 27); // Purpur this.owner = owner; // CraftBukkit end } + // Purpur start + @Override + public int getContainerSize() { + return owner.sixRowEnderchestSlotCount < 0 ? super.getContainerSize() : owner.sixRowEnderchestSlotCount; + } + // Purpur end + public void setActiveChest(EnderChestBlockEntity blockEntity) { this.activeChest = blockEntity; } diff --git a/src/main/java/net/minecraft/world/item/ArmorItem.java b/src/main/java/net/minecraft/world/item/ArmorItem.java index d7a0cbde8f8c99276307502674c71463fbe7e89c..3500c56cb85d8c76b2acd77976d374eaf487b3b3 100644 --- a/src/main/java/net/minecraft/world/item/ArmorItem.java +++ b/src/main/java/net/minecraft/world/item/ArmorItem.java @@ -60,7 +60,7 @@ public class ArmorItem extends Item implements Equipable { return false; } else { LivingEntity entityliving = (LivingEntity) list.get(0); - EquipmentSlot enumitemslot = Mob.getEquipmentSlotForItem(armor); + EquipmentSlot enumitemslot = pointer.getLevel().purpurConfig.dispenserApplyCursedArmor ? Mob.getEquipmentSlotForItem(armor) : Mob.getSlotForDispenser(armor); if (enumitemslot == null) return false; // Purpur ItemStack itemstack1 = armor.copyWithCount(1); // Paper - shrink below and single item in event // CraftBukkit start Level world = pointer.getLevel(); diff --git a/src/main/java/net/minecraft/world/item/ArmorStandItem.java b/src/main/java/net/minecraft/world/item/ArmorStandItem.java index 7cffc64573008502bdd14ae4906fe51166b12fb3..1feafdbb48cf760cb6ebf95d5be2c32bdb1ad44f 100644 --- a/src/main/java/net/minecraft/world/item/ArmorStandItem.java +++ b/src/main/java/net/minecraft/world/item/ArmorStandItem.java @@ -58,6 +58,14 @@ public class ArmorStandItem extends Item { return InteractionResult.FAIL; } // CraftBukkit end + // Purpur start + if (world.purpurConfig.persistentDroppableEntityDisplayNames && itemstack.hasCustomHoverName()) { + entityarmorstand.setCustomName(itemstack.getHoverName()); + if (world.purpurConfig.armorstandSetNameVisible) { + entityarmorstand.setCustomNameVisible(true); + } + } + // Purpur end worldserver.addFreshEntityWithPassengers(entityarmorstand); world.playSound((Player) null, entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F); entityarmorstand.gameEvent(GameEvent.ENTITY_PLACE, context.getPlayer()); diff --git a/src/main/java/net/minecraft/world/item/AxeItem.java b/src/main/java/net/minecraft/world/item/AxeItem.java index 18898e16ec42f6b694b06e09d9174b60d62450d7..20f33b77b4a9494be227456bc742a029eb0af59b 100644 --- a/src/main/java/net/minecraft/world/item/AxeItem.java +++ b/src/main/java/net/minecraft/world/item/AxeItem.java @@ -33,29 +33,32 @@ public class AxeItem extends DiggerItem { BlockPos blockPos = context.getClickedPos(); Player player = context.getPlayer(); BlockState blockState = level.getBlockState(blockPos); - Optional optional = this.getStripped(blockState); - Optional optional2 = WeatheringCopper.getPrevious(blockState); - Optional optional3 = Optional.ofNullable(HoneycombItem.WAX_OFF_BY_BLOCK.get().get(blockState.getBlock())).map((block) -> { - return block.withPropertiesOf(blockState); - }); + // Purpur start + Block clickedBlock = level.getBlockState(blockPos).getBlock(); + Optional optional = Optional.ofNullable(level.purpurConfig.axeStrippables.get(blockState.getBlock())); + Optional optional2 = Optional.ofNullable(level.purpurConfig.axeWeatherables.get(blockState.getBlock())); + Optional optional3 = Optional.ofNullable(level.purpurConfig.axeWaxables.get(blockState.getBlock())); + // Purpur end ItemStack itemStack = context.getItemInHand(); - Optional optional4 = Optional.empty(); + Optional optional4 = Optional.empty(); // Purpur if (optional.isPresent()) { - level.playSound(player, blockPos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); + if (!STRIPPABLES.containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound optional4 = optional; } else if (optional2.isPresent()) { - level.playSound(player, blockPos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); + if (!HoneycombItem.WAXABLES.get().containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound level.levelEvent(player, 3005, blockPos, 0); optional4 = optional2; } else if (optional3.isPresent()) { - level.playSound(player, blockPos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); + if (!HoneycombItem.WAX_OFF_BY_BLOCK.get().containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound level.levelEvent(player, 3004, blockPos, 0); optional4 = optional3; } if (optional4.isPresent()) { + org.purpurmc.purpur.tool.Actionable actionable = optional4.get(); // Purpur + BlockState state = actionable.into().withPropertiesOf(blockState); // Purpur // Paper start - EntityChangeBlockEvent - if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional4.get())) { + if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state)) { // Purpur return InteractionResult.PASS; } // Paper end @@ -63,15 +66,22 @@ public class AxeItem extends DiggerItem { CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); } - level.setBlock(blockPos, optional4.get(), 11); - level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, optional4.get())); + // Purpur start + level.setBlock(blockPos, state, 11); + actionable.drops().forEach((drop, chance) -> { + if (level.random.nextDouble() < chance) { + Block.popResourceFromFace(level, blockPos, context.getClickedFace(), new ItemStack(drop)); + } + }); + level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, state)); + // Purpur end if (player != null) { itemStack.hurtAndBreak(1, player, (p) -> { p.broadcastBreakEvent(context.getHand()); }); } - return InteractionResult.sidedSuccess(level.isClientSide); + return InteractionResult.SUCCESS; // Purpur - force arm swing } else { return InteractionResult.PASS; } diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java index e483186a5292b3b53bfb1af4d56f55fcc1a6106c..41728bcbd5ca3585da64f7c3289b26977f90c229 100644 --- a/src/main/java/net/minecraft/world/item/BlockItem.java +++ b/src/main/java/net/minecraft/world/item/BlockItem.java @@ -153,7 +153,24 @@ public class BlockItem extends Item { } protected boolean updateCustomBlockEntityTag(BlockPos pos, Level world, @Nullable Player player, ItemStack stack, BlockState state) { - return BlockItem.updateCustomBlockEntityTag(world, player, pos, stack); + // Purpur start + boolean handled = updateCustomBlockEntityTag(world, player, pos, stack); + if (world.purpurConfig.persistentTileEntityDisplayNames && stack.hasTag()) { + CompoundTag display = stack.getTagElement("display"); + if (display != null) { + BlockEntity blockEntity = world.getBlockEntity(pos); + if (blockEntity != null) { + if (display.contains("Name", 8)) { + blockEntity.setPersistentDisplayName(display.getString("Name")); + } + if (display.contains("Lore", 9)) { + blockEntity.setPersistentLore(display.getList("Lore", 8)); + } + } + } + } + return handled; + // Purpur end } @Nullable @@ -288,7 +305,7 @@ public class BlockItem extends Item { @Override public void onDestroyed(ItemEntity entity) { - if (this.block instanceof ShulkerBoxBlock) { + if (this.block instanceof ShulkerBoxBlock && entity.level().purpurConfig.shulkerBoxItemDropContentsWhenDestroyed) { ItemStack itemstack = entity.getItem(); CompoundTag nbttagcompound = BlockItem.getBlockEntityData(itemstack); diff --git a/src/main/java/net/minecraft/world/item/BoatItem.java b/src/main/java/net/minecraft/world/item/BoatItem.java index aec7ac31a35b1cc81f40b3fbeb5cf95c0f2c8a6c..cbcd35e60a2c344c83978abf0b94c2120ff53dee 100644 --- a/src/main/java/net/minecraft/world/item/BoatItem.java +++ b/src/main/java/net/minecraft/world/item/BoatItem.java @@ -69,6 +69,11 @@ public class BoatItem extends Item { entityboat.setVariant(this.type); entityboat.setYRot(user.getYRot()); + // Purpur start + if (world.purpurConfig.persistentDroppableEntityDisplayNames && itemstack.hasCustomHoverName()) { + entityboat.setCustomName(itemstack.getHoverName()); + } + // Purpur end if (!world.noCollision(entityboat, entityboat.getBoundingBox())) { return InteractionResultHolder.fail(itemstack); } else { diff --git a/src/main/java/net/minecraft/world/item/BowItem.java b/src/main/java/net/minecraft/world/item/BowItem.java index 08d597db1a5345a343777a01427655e6bf2c926b..d45a2f49c82d00801578c34e5f5277fc5e82be87 100644 --- a/src/main/java/net/minecraft/world/item/BowItem.java +++ b/src/main/java/net/minecraft/world/item/BowItem.java @@ -38,13 +38,13 @@ public class BowItem extends ProjectileWeaponItem implements Vanishable { float f = BowItem.getPowerForTime(j); if ((double) f >= 0.1D) { - boolean flag1 = flag && itemstack1.is(Items.ARROW); + boolean flag1 = flag && ((itemstack1.is(Items.ARROW) && world.purpurConfig.infinityWorksWithNormalArrows) || (itemstack1.is(Items.TIPPED_ARROW) && world.purpurConfig.infinityWorksWithTippedArrows) || (itemstack1.is(Items.SPECTRAL_ARROW) && world.purpurConfig.infinityWorksWithSpectralArrows)); // Purpur if (!world.isClientSide) { if (!world.isClientSide) { ArrowItem itemarrow = (ArrowItem) (itemstack1.getItem() instanceof ArrowItem ? itemstack1.getItem() : Items.ARROW); AbstractArrow entityarrow = itemarrow.createArrow(world, itemstack1, entityhuman); - entityarrow.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, f * 3.0F, 1.0F); + entityarrow.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, f * 3.0F, (float) world.purpurConfig.bowProjectileOffset); // Purpur if (f == 1.0F) { entityarrow.setCritArrow(true); } @@ -64,6 +64,13 @@ public class BowItem extends ProjectileWeaponItem implements Vanishable { if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.FLAMING_ARROWS, stack) > 0) { entityarrow.setSecondsOnFire(100); } + // Purpur start + int lootingLevel = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MOB_LOOTING, stack); + + if (lootingLevel > 0) { + entityarrow.setLootingLevel(lootingLevel); + } + // Purpur end // CraftBukkit start org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(entityhuman, stack, itemstack1, entityarrow, entityhuman.getUsedItemHand(), f, !flag1); if (event.isCancelled()) { @@ -132,7 +139,7 @@ public class BowItem extends ProjectileWeaponItem implements Vanishable { ItemStack itemstack = user.getItemInHand(hand); boolean flag = !user.getProjectile(itemstack).isEmpty(); - if (!user.getAbilities().instabuild && !flag) { + if (!(world.purpurConfig.infinityWorksWithoutArrows && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY_ARROWS, itemstack) > 0) && !user.getAbilities().instabuild && !flag) { // Purpur return InteractionResultHolder.fail(itemstack); } else { user.startUsingItem(hand); diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java index 578c3db52dda4c169b5ea615a4ce4a79f15a4cad..0bd98b802f246a3f6061f716d470a4797b28d59d 100644 --- a/src/main/java/net/minecraft/world/item/BucketItem.java +++ b/src/main/java/net/minecraft/world/item/BucketItem.java @@ -164,7 +164,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { // CraftBukkit end if (!flag1) { return movingobjectpositionblock != null && this.emptyContents(entityhuman, world, movingobjectpositionblock.getBlockPos().relative(movingobjectpositionblock.getDirection()), (BlockHitResult) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit - } else if (world.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) { + } else if ((world.dimensionType().ultraWarm() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) && this.content.is(FluidTags.WATER)) { // Purpur int i = blockposition.getX(); int j = blockposition.getY(); int k = blockposition.getZ(); @@ -172,7 +172,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { world.playSound(entityhuman, blockposition, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F); for (int l = 0; l < 8; ++l) { - world.addParticle(ParticleTypes.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 0.0D, 0.0D, 0.0D); + ((ServerLevel) world).sendParticles(null, ParticleTypes.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 1, 0.0D, 0.0D, 0.0D, 0.0D, true); // Purpur } return true; diff --git a/src/main/java/net/minecraft/world/item/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java index eede02c3f125d230af537bb67bebed9b88f7d1b4..2c51a73ebfd05af21b0f5d731fc9f1df77fed1a1 100644 --- a/src/main/java/net/minecraft/world/item/CrossbowItem.java +++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java @@ -64,7 +64,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { ItemStack itemstack = user.getItemInHand(hand); if (CrossbowItem.isCharged(itemstack)) { - CrossbowItem.performShooting(world, user, hand, itemstack, CrossbowItem.getShootingPower(itemstack), 1.0F); + CrossbowItem.performShooting(world, user, hand, itemstack, CrossbowItem.getShootingPower(itemstack), (float) world.purpurConfig.crossbowProjectileOffset); // Purpur CrossbowItem.setCharged(itemstack, false); return InteractionResultHolder.consume(itemstack); } else if (!user.getProjectile(itemstack).isEmpty()) { @@ -114,7 +114,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { // Paper end int i = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MULTISHOT, crossbow); int j = i == 0 ? 1 : 3; - boolean flag = !consume || shooter instanceof Player && ((Player) shooter).getAbilities().instabuild; // Paper - add consume + boolean flag = !consume || shooter instanceof Player && ((Player) shooter).getAbilities().instabuild || (org.purpurmc.purpur.PurpurConfig.allowCrossbowInfinity && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY_ARROWS, crossbow) > 0); // Paper - add consume // Purpur ItemStack itemstack1 = shooter.getProjectile(crossbow); ItemStack itemstack2 = itemstack1.copy(); @@ -291,6 +291,14 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { entityarrow.setPierceLevel((byte) i); } + // Purpur start + int lootingLevel = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MOB_LOOTING, crossbow); + + if (lootingLevel > 0) { + entityarrow.setLootingLevel(lootingLevel); + } + // Purpur end + return entityarrow; } @@ -300,7 +308,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { for (int i = 0; i < list.size(); ++i) { ItemStack itemstack1 = (ItemStack) list.get(i); - boolean flag = entity instanceof Player && ((Player) entity).getAbilities().instabuild; + boolean flag = entity instanceof Player && ((Player) entity).getAbilities().instabuild || (org.purpurmc.purpur.PurpurConfig.allowCrossbowInfinity && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY_ARROWS, stack) > 0); // Purpur if (!itemstack1.isEmpty()) { if (i == 0) { diff --git a/src/main/java/net/minecraft/world/item/DyeColor.java b/src/main/java/net/minecraft/world/item/DyeColor.java index 88e1c2431d51d8cdc3d555b711e506648225d289..ac8735cc9d127fc1f867b40d4000c033ef73bb83 100644 --- a/src/main/java/net/minecraft/world/item/DyeColor.java +++ b/src/main/java/net/minecraft/world/item/DyeColor.java @@ -103,4 +103,10 @@ public enum DyeColor implements StringRepresentable { public String getSerializedName() { return this.name; } + + // Purpur start + public static DyeColor random(net.minecraft.util.RandomSource random) { + return values()[random.nextInt(values().length)]; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/item/EggItem.java b/src/main/java/net/minecraft/world/item/EggItem.java index 58cb992c5defec2f092755cbde661ff10f38bf9d..52f48681407d23f0925f4c9c072d5f0a2a6b1778 100644 --- a/src/main/java/net/minecraft/world/item/EggItem.java +++ b/src/main/java/net/minecraft/world/item/EggItem.java @@ -24,7 +24,7 @@ public class EggItem extends Item { ThrownEgg entityegg = new ThrownEgg(world, user); entityegg.setItem(itemstack); - entityegg.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F); + entityegg.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, (float) world.purpurConfig.eggProjectileOffset); // Purpur // Paper start com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entityegg.getBukkitEntity()); if (event.callEvent() && world.addFreshEntity(entityegg)) { diff --git a/src/main/java/net/minecraft/world/item/EndCrystalItem.java b/src/main/java/net/minecraft/world/item/EndCrystalItem.java index 3688e9f8c6c6d1239095e3a87060ccca90386d0c..34254eec36d34ae343733fa1abbaaba60be41a3b 100644 --- a/src/main/java/net/minecraft/world/item/EndCrystalItem.java +++ b/src/main/java/net/minecraft/world/item/EndCrystalItem.java @@ -26,7 +26,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 diff --git a/src/main/java/net/minecraft/world/item/EnderpearlItem.java b/src/main/java/net/minecraft/world/item/EnderpearlItem.java index 749ab72edc0d2e9c6f1161415ab8d59d3d6ca976..6b27d98d06b163243bb0e1bb979aad03f48d7770 100644 --- a/src/main/java/net/minecraft/world/item/EnderpearlItem.java +++ b/src/main/java/net/minecraft/world/item/EnderpearlItem.java @@ -24,7 +24,7 @@ public class EnderpearlItem extends Item { ThrownEnderpearl entityenderpearl = new ThrownEnderpearl(world, user); entityenderpearl.setItem(itemstack); - entityenderpearl.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F); + entityenderpearl.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, (float) world.purpurConfig.enderPearlProjectileOffset); // Purpur // Paper start com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entityenderpearl.getBukkitEntity()); if (event.callEvent() && world.addFreshEntity(entityenderpearl)) { @@ -36,7 +36,7 @@ public class EnderpearlItem extends Item { world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (net.minecraft.world.entity.Entity.SHARED_RANDOM.nextFloat() * 0.4F + 0.8F)); user.awardStat(Stats.ITEM_USED.get(this)); - user.getCooldowns().addCooldown(this, 20); + user.getCooldowns().addCooldown(this, user.getAbilities().instabuild ? world.purpurConfig.enderPearlCooldownCreative : world.purpurConfig.enderPearlCooldown); // Purpur } else { // Paper end if (user instanceof net.minecraft.server.level.ServerPlayer) { diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java index 82b0bda3e35ec2157a477e1a17b2b46baadc97d9..0fc45b1048a1c4e0dc2bd1ae0437eecbe113cf96 100644 --- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java @@ -15,6 +15,7 @@ import net.minecraft.util.ByIdMap; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.FireworkRocketEntity; import net.minecraft.world.item.context.UseOnContext; @@ -69,6 +70,14 @@ public class FireworkRocketItem extends Item { com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) fireworkRocketEntity.getBukkitEntity()); if (event.callEvent() && world.addFreshEntity(fireworkRocketEntity)) { user.awardStat(Stats.ITEM_USED.get(this)); + // Purpur start + if (world.purpurConfig.elytraDamagePerFireworkBoost > 0) { + ItemStack chestItem = user.getItemBySlot(EquipmentSlot.CHEST); + if (chestItem.getItem() == Items.ELYTRA) { + chestItem.hurtAndBreak(world.purpurConfig.elytraDamagePerFireworkBoost, user, (entityliving) -> entityliving.broadcastBreakEvent(EquipmentSlot.CHEST)); + } + } + // Purpur end if (event.shouldConsume() && !user.getAbilities().instabuild) { itemStack.shrink(1); } else ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); diff --git a/src/main/java/net/minecraft/world/item/HangingEntityItem.java b/src/main/java/net/minecraft/world/item/HangingEntityItem.java index b2ad6d230de2c29f371178bccde1111c7532ee70..6667926519a0f1c151e53f59cce36e7417dfc1cd 100644 --- a/src/main/java/net/minecraft/world/item/HangingEntityItem.java +++ b/src/main/java/net/minecraft/world/item/HangingEntityItem.java @@ -48,7 +48,7 @@ public class HangingEntityItem extends Item { return InteractionResult.FAIL; } else { Level world = context.getLevel(); - Object object; + Entity object; // Purpur if (this.type == EntityType.PAINTING) { Optional optional = Painting.create(world, blockposition1, enumdirection); @@ -72,6 +72,11 @@ public class HangingEntityItem extends Item { if (nbttagcompound != null) { EntityType.updateCustomEntityTag(world, entityhuman, (Entity) object, nbttagcompound); + // Purpur start + if (world.purpurConfig.persistentDroppableEntityDisplayNames && itemstack.hasCustomHoverName()) { + object.setCustomName(itemstack.getHoverName()); + } + // Purpur end } if (((HangingEntity) object).survives()) { diff --git a/src/main/java/net/minecraft/world/item/HoeItem.java b/src/main/java/net/minecraft/world/item/HoeItem.java index 180aec596110309aade13d2080f8824d152b07cb..552c31c0f3746dd35388395036e70a925bf00bd4 100644 --- a/src/main/java/net/minecraft/world/item/HoeItem.java +++ b/src/main/java/net/minecraft/world/item/HoeItem.java @@ -34,15 +34,23 @@ public class HoeItem extends DiggerItem { public InteractionResult useOn(UseOnContext context) { Level level = context.getLevel(); BlockPos blockPos = context.getClickedPos(); - Pair, Consumer> pair = TILLABLES.get(level.getBlockState(blockPos).getBlock()); - if (pair == null) { - return InteractionResult.PASS; - } else { - Predicate predicate = pair.getFirst(); - Consumer consumer = pair.getSecond(); + // Purpur start + Block clickedBlock = level.getBlockState(blockPos).getBlock(); + var tillable = level.purpurConfig.hoeTillables.get(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) { @@ -52,7 +60,7 @@ public class HoeItem extends DiggerItem { } } - return InteractionResult.sidedSuccess(level.isClientSide); + return InteractionResult.SUCCESS; // Purpur - force arm swing } else { return InteractionResult.PASS; } diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java index d138660e459b2d09ba9dd7ae2bf3da499d9ae25c..50ea2bc3f785dabc686162c895590b7d7f15a93d 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -113,6 +113,7 @@ import org.bukkit.event.world.StructureGrowEvent; public final class ItemStack { + public boolean isExactRecipeIngredient = false; // PaperPR public static final Codec CODEC = RecordCodecBuilder.create((instance) -> { return instance.group(BuiltInRegistries.ITEM.byNameCodec().fieldOf("id").forGetter(ItemStack::getItem), Codec.INT.fieldOf("Count").forGetter(ItemStack::getCount), CompoundTag.CODEC.optionalFieldOf("tag").forGetter((itemstack) -> { return Optional.ofNullable(itemstack.getTag()); @@ -427,6 +428,7 @@ public final class ItemStack { world.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710 for (BlockState blockstate : blocks) { blockstate.update(true, false); + ((CraftBlock) blockstate.getBlock()).getNMS().getBlock().forgetPlacer(); // Purpur } world.preventPoiUpdated = false; @@ -456,6 +458,7 @@ public final class ItemStack { if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, context); // Paper - pass context } + block.getBlock().forgetPlacer(); // Purpur world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point } @@ -584,6 +587,16 @@ public final class ItemStack { return this.isDamageableItem() && this.getDamageValue() > 0; } + // Purpur start + public float getDamagePercent() { + if (isDamaged()) { + return (float) getDamageValue() / (float) getItem().getMaxDamage(); + } else { + return 0F; + } + } + // Purpur end + public int getDamageValue() { return this.tag == null ? 0 : this.tag.getInt("Damage"); } @@ -603,7 +616,7 @@ public final class ItemStack { int j; if (amount > 0) { - j = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.UNBREAKING, this); + j = (getItem() == Items.ELYTRA && player != null && player.level().purpurConfig.elytraIgnoreUnbreaking) ? 0 : EnchantmentHelper.getItemEnchantmentLevel(Enchantments.UNBREAKING, this); int k = 0; for (int l = 0; j > 0 && l < amount; ++l) { @@ -658,6 +671,12 @@ public final class ItemStack { if (this.hurt(amount, entity.getRandom(), entity /*instanceof ServerPlayer ? (ServerPlayer) entity : null*/)) { // Paper - pass LivingEntity for EntityItemDamageEvent breakCallback.accept(entity); Item item = this.getItem(); + // Purpur start + if (item == Items.ELYTRA) { + setDamageValue(item.getMaxDamage() - 1); + return; + } + // Purpur end // CraftBukkit start - Check for item breaking if (this.count == 1 && entity instanceof net.minecraft.world.entity.player.Player) { org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent((net.minecraft.world.entity.player.Player) entity, this); @@ -1184,7 +1203,7 @@ public final class ItemStack { ListTag nbttaglist = this.tag.getList("Enchantments", 10); - nbttaglist.add(EnchantmentHelper.storeEnchantment(EnchantmentHelper.getEnchantmentId(enchantment), (byte) level)); + nbttaglist.add(EnchantmentHelper.storeEnchantment(EnchantmentHelper.getEnchantmentId(enchantment), (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels) ? (byte) level : (short) level)); // Purpur processEnchantOrder(this.tag); // Paper } @@ -1192,6 +1211,12 @@ public final class ItemStack { return this.tag != null && this.tag.contains("Enchantments", 9) ? !this.tag.getList("Enchantments", 10).isEmpty() : false; } + // Purpur start + public boolean hasEnchantment(Enchantment enchantment) { + return isEnchanted() && EnchantmentHelper.deserializeEnchantments(getEnchantmentTags()).containsKey(enchantment); + } + // Purpur end + public void addTagElement(String key, Tag element) { this.getOrCreateTag().put(key, element); } diff --git a/src/main/java/net/minecraft/world/item/Items.java b/src/main/java/net/minecraft/world/item/Items.java index 5f20e075c532f0f1d413242949d1738c0c152bf7..5fbb13ebef0ca66419f3e5006d19e4a5918a038a 100644 --- a/src/main/java/net/minecraft/world/item/Items.java +++ b/src/main/java/net/minecraft/world/item/Items.java @@ -294,7 +294,7 @@ public class Items { public static final Item PURPUR_BLOCK = registerBlock(Blocks.PURPUR_BLOCK); public static final Item PURPUR_PILLAR = registerBlock(Blocks.PURPUR_PILLAR); public static final Item PURPUR_STAIRS = registerBlock(Blocks.PURPUR_STAIRS); - public static final Item SPAWNER = registerBlock(Blocks.SPAWNER); + public static final Item SPAWNER = registerBlock(new org.purpurmc.purpur.item.SpawnerItem(Blocks.SPAWNER, new Item.Properties().rarity(Rarity.EPIC))); // Purpur public static final Item CHEST = registerBlock(Blocks.CHEST); public static final Item CRAFTING_TABLE = registerBlock(Blocks.CRAFTING_TABLE); public static final Item FARMLAND = registerBlock(Blocks.FARMLAND); @@ -1184,7 +1184,7 @@ public class Items { public static final Item LANTERN = registerBlock(Blocks.LANTERN); public static final Item SOUL_LANTERN = registerBlock(Blocks.SOUL_LANTERN); public static final Item SWEET_BERRIES = registerItem("sweet_berries", new ItemNameBlockItem(Blocks.SWEET_BERRY_BUSH, (new Item.Properties()).food(Foods.SWEET_BERRIES))); - public static final Item GLOW_BERRIES = registerItem("glow_berries", new ItemNameBlockItem(Blocks.CAVE_VINES, (new Item.Properties()).food(Foods.GLOW_BERRIES))); + public static final Item GLOW_BERRIES = registerItem("glow_berries", new org.purpurmc.purpur.item.GlowBerryItem(Blocks.CAVE_VINES, (new Item.Properties()).food(Foods.GLOW_BERRIES))); // Purpur public static final Item CAMPFIRE = registerBlock(Blocks.CAMPFIRE); public static final Item SOUL_CAMPFIRE = registerBlock(Blocks.SOUL_CAMPFIRE); public static final Item SHROOMLIGHT = registerBlock(Blocks.SHROOMLIGHT); @@ -1309,6 +1309,13 @@ public class Items { ((BlockItem)item).registerBlocks(Item.BY_BLOCK, item); } + // Purpur start + if (item.getFoodProperties() != null) { + Foods.ALL_PROPERTIES.put(key.location().getPath(), item.getFoodProperties()); + Foods.DEFAULT_PROPERTIES.put(key.location().getPath(), item.getFoodProperties().copy()); + } + // Purpur end + return Registry.register(BuiltInRegistries.ITEM, key, item); } } diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java index d3c29e6bf8b3c2dd628809177dac50220a7de415..735486b46581bb3b91c85f57490b560ff490787e 100644 --- a/src/main/java/net/minecraft/world/item/MapItem.java +++ b/src/main/java/net/minecraft/world/item/MapItem.java @@ -243,6 +243,7 @@ public class MapItem extends ComplexItem { MapItemSavedData worldmap = MapItem.getSavedData(map, world); if (worldmap != null) { + worldmap.isExplorerMap = true; // Purpur if (world.dimension() == worldmap.dimension) { int i = 1 << worldmap.scale; int j = worldmap.centerX; diff --git a/src/main/java/net/minecraft/world/item/MilkBucketItem.java b/src/main/java/net/minecraft/world/item/MilkBucketItem.java index f33977d95b6db473be4f95075ba99caf90ad0220..56dc04d8875971ee9a5d077a695509af74fe2473 100644 --- a/src/main/java/net/minecraft/world/item/MilkBucketItem.java +++ b/src/main/java/net/minecraft/world/item/MilkBucketItem.java @@ -5,6 +5,8 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.stats.Stats; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; @@ -31,7 +33,9 @@ public class MilkBucketItem extends Item { } if (!world.isClientSide) { + MobEffectInstance badOmen = user.getEffect(MobEffects.BAD_OMEN); user.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.MILK); // CraftBukkit + if (!world.purpurConfig.milkCuresBadOmen && badOmen != null) user.addEffect(badOmen); // Purpur } return stack.isEmpty() ? new ItemStack(Items.BUCKET) : stack; diff --git a/src/main/java/net/minecraft/world/item/MinecartItem.java b/src/main/java/net/minecraft/world/item/MinecartItem.java index c6d2f764efa9b8bec730bbe757d480e365b25ccc..cef98413d25dcc2def82775bbae71f92b096d905 100644 --- a/src/main/java/net/minecraft/world/item/MinecartItem.java +++ b/src/main/java/net/minecraft/world/item/MinecartItem.java @@ -120,8 +120,9 @@ public class MinecartItem extends Item { BlockState iblockdata = world.getBlockState(blockposition); if (!iblockdata.is(BlockTags.RAILS)) { - return InteractionResult.FAIL; - } else { + if (!world.purpurConfig.minecartPlaceAnywhere) return InteractionResult.FAIL; + if (iblockdata.isSolid()) blockposition = blockposition.relative(context.getClickedFace()); + } // else { // Purpur - place minecarts anywhere ItemStack itemstack = context.getItemInHand(); if (!world.isClientSide) { @@ -149,6 +150,6 @@ public class MinecartItem extends Item { itemstack.shrink(1); return InteractionResult.sidedSuccess(world.isClientSide); - } + // } // Purpur - place minecarts anywhere } } diff --git a/src/main/java/net/minecraft/world/item/NameTagItem.java b/src/main/java/net/minecraft/world/item/NameTagItem.java index 2941c16ef486345b57ab2dfcd26f0272285d3b5a..7cc6812bf6f2ba015f65fd1fc1eaac02dd0f53e2 100644 --- a/src/main/java/net/minecraft/world/item/NameTagItem.java +++ b/src/main/java/net/minecraft/world/item/NameTagItem.java @@ -20,6 +20,7 @@ public class NameTagItem extends Item { if (!event.callEvent()) return InteractionResult.PASS; LivingEntity newEntityLiving = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); newEntityLiving.setCustomName(event.getName() != null ? 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() && newEntityLiving instanceof Mob) { ((Mob) newEntityLiving).setPersistenceRequired(); // Paper end diff --git a/src/main/java/net/minecraft/world/item/ShovelItem.java b/src/main/java/net/minecraft/world/item/ShovelItem.java index 21212462e6b415e96536a27b2c009d1562f18946..193bcb12152347f5f02ce18b01ba918e5e838f30 100644 --- a/src/main/java/net/minecraft/world/item/ShovelItem.java +++ b/src/main/java/net/minecraft/world/item/ShovelItem.java @@ -37,9 +37,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()) { @@ -68,7 +71,7 @@ public class ShovelItem extends DiggerItem { } } - return InteractionResult.sidedSuccess(level.isClientSide); + return InteractionResult.SUCCESS; // Purpur - force arm swing } else { return InteractionResult.PASS; } diff --git a/src/main/java/net/minecraft/world/item/SnowballItem.java b/src/main/java/net/minecraft/world/item/SnowballItem.java index ef3f90a5bcdd7b9815a4053cff166f9d2552f55d..e7e5e1cc92f56e3daba8fa09c59188febec5e8f2 100644 --- a/src/main/java/net/minecraft/world/item/SnowballItem.java +++ b/src/main/java/net/minecraft/world/item/SnowballItem.java @@ -25,7 +25,7 @@ public class SnowballItem extends Item { Snowball entitysnowball = new Snowball(world, user); entitysnowball.setItem(itemstack); - entitysnowball.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F); + entitysnowball.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, (float) world.purpurConfig.snowballProjectileOffset); // Purpur // Paper start com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entitysnowball.getBukkitEntity()); if (event.callEvent() && world.addFreshEntity(entitysnowball)) { diff --git a/src/main/java/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java index 741719301e6fc91a598e74342810c4185e6fde26..6fbff9c02fbabf03c9c649a9ea6128021081f9cd 100644 --- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java +++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java @@ -68,6 +68,15 @@ public class SpawnEggItem extends Item { SpawnerBlockEntity tileentitymobspawner = (SpawnerBlockEntity) tileentity; EntityType entitytypes = this.getType(itemstack.getTag()); + // Purpur start + org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); + org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent event = new org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), bukkitBlock, (org.bukkit.block.CreatureSpawner) bukkitBlock.getState(), org.bukkit.entity.EntityType.fromName(entitytypes.getName())); + if (!event.callEvent()) { + return InteractionResult.FAIL; + } + entitytypes = EntityType.getFromBukkitType(event.getEntityType()); + // Purpur end + tileentitymobspawner.setEntityId(entitytypes, world.getRandom()); tileentity.setChanged(); world.sendBlockUpdated(blockposition, iblockdata, iblockdata, 3); diff --git a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java index de5bdceb4c8578fb972a2fd5ee0dfdae509e46dc..bcf63ccb6e679cb97d658780b2663aafa3568bcb 100644 --- a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java +++ b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java @@ -18,7 +18,7 @@ public class ThrowablePotionItem extends PotionItem { if (!world.isClientSide) { ThrownPotion thrownPotion = new ThrownPotion(world, user); thrownPotion.setItem(itemStack); - thrownPotion.shootFromRotation(user, user.getXRot(), user.getYRot(), -20.0F, 0.5F, 1.0F); + thrownPotion.shootFromRotation(user, user.getXRot(), user.getYRot(), -20.0F, 0.5F, (float) world.purpurConfig.throwablePotionProjectileOffset); // Purpur // Paper start com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownPotion.getBukkitEntity()); if (event.callEvent() && world.addFreshEntity(thrownPotion)) { diff --git a/src/main/java/net/minecraft/world/item/TridentItem.java b/src/main/java/net/minecraft/world/item/TridentItem.java index 8078f127ff4b6e0aafb5804b9c02e237f79445b5..c32cbe6065ecb6810f352b8a3598c21e42e60e1d 100644 --- a/src/main/java/net/minecraft/world/item/TridentItem.java +++ b/src/main/java/net/minecraft/world/item/TridentItem.java @@ -77,11 +77,19 @@ public class TridentItem extends Item implements Vanishable { if (k == 0) { ThrownTrident entitythrowntrident = new ThrownTrident(world, entityhuman, stack); - entitythrowntrident.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, 2.5F + (float) k * 0.5F, 1.0F); + entitythrowntrident.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, 2.5F + (float) k * 0.5F, (float) world.purpurConfig.tridentProjectileOffset); // Purpur if (entityhuman.getAbilities().instabuild) { entitythrowntrident.pickup = AbstractArrow.Pickup.CREATIVE_ONLY; } + // Purpur start + int lootingLevel = EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MOB_LOOTING, stack); + + if (lootingLevel > 0) { + entitythrowntrident.setLootingLevel(lootingLevel); + } + // Purpur end + // CraftBukkit start // Paper start com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) entitythrowntrident.getBukkitEntity()); @@ -130,6 +138,14 @@ public class TridentItem extends Item implements Vanishable { f2 *= f6 / f5; f3 *= f6 / f5; f4 *= f6 / f5; + + // Purpur start + ItemStack chestItem = entityhuman.getItemBySlot(EquipmentSlot.CHEST); + if (chestItem.getItem() == Items.ELYTRA && world.purpurConfig.elytraDamagePerTridentBoost > 0) { + chestItem.hurtAndBreak(world.purpurConfig.elytraDamagePerTridentBoost, entityhuman, (entity) -> entity.broadcastBreakEvent(EquipmentSlot.CHEST)); + } + // Purpur end + entityhuman.push((double) f2, (double) f3, (double) f4); entityhuman.startAutoSpinAttack(20); if (entityhuman.onGround()) { 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 cd7ea0c16f9ddcb84b5d7e8a2533e6e84f3879c7..f47eab4c31925f51de4a6bc8be730281cb3388fc 100644 --- a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java +++ b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java @@ -39,6 +39,7 @@ public final class Ingredient implements Predicate { @Nullable private IntList stackingIds; public boolean exact; // CraftBukkit + public Predicate predicate; public Ingredient(Stream entries) { this.values = (Ingredient.Value[]) entries.toArray((i) -> { @@ -50,7 +51,11 @@ public final class Ingredient implements Predicate { if (this.itemStacks == null) { this.itemStacks = (ItemStack[]) Arrays.stream(this.values).flatMap((recipeitemstack_provider) -> { return recipeitemstack_provider.getItems().stream(); - }).distinct().toArray((i) -> { + // PaperPR start + }).distinct().peek(stack -> { + stack.isExactRecipeIngredient = this.exact; + }).toArray((i) -> { + // PaperPR end return new ItemStack[i]; }); } @@ -64,6 +69,12 @@ public final class Ingredient implements Predicate { } else if (this.isEmpty()) { return itemstack.isEmpty(); } else { + // Purpur start + if (predicate != null) { + return predicate.test(itemstack.asBukkitCopy()); + } + // Purpur end + ItemStack[] aitemstack = this.getItems(); int i = aitemstack.length; @@ -99,7 +110,13 @@ public final class Ingredient implements Predicate { for (int j = 0; j < i; ++j) { ItemStack itemstack = aitemstack1[j]; + // PaperPR start + if (itemstack.isExactRecipeIngredient) { + this.stackingIds.add(StackedContents.getExactStackingIndex(itemstack)); + } else { + // PaperPR end this.stackingIds.add(StackedContents.getStackingIndex(itemstack)); + } // PaperPR } this.stackingIds.sort(IntComparators.NATURAL_COMPARATOR); diff --git a/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java b/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java index 518d85a13c37a2f7d32ca0718323181048559986..27512787b37381a5236b1b473e9ce3f06df8e2d0 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java +++ b/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java @@ -7,6 +7,14 @@ public class ArrowInfiniteEnchantment extends Enchantment { super(weight, EnchantmentCategory.BOW, slotTypes); } + // Purpur start + @Override + public boolean canEnchant(net.minecraft.world.item.ItemStack stack) { + // we have to cheat the system because this class is loaded before purpur's config is loaded + return (org.purpurmc.purpur.PurpurConfig.allowCrossbowInfinity ? EnchantmentCategory.BOW_AND_CROSSBOW : EnchantmentCategory.BOW).canEnchant(stack.getItem()); + } + // Purpur end + @Override public int getMinCost(int level) { return 20; @@ -19,6 +27,6 @@ public class ArrowInfiniteEnchantment extends Enchantment { @Override public boolean checkCompatibility(Enchantment other) { - return other instanceof MendingEnchantment ? false : super.checkCompatibility(other); + return other instanceof MendingEnchantment ? org.purpurmc.purpur.PurpurConfig.allowInfinityMending : super.checkCompatibility(other); } } diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java index 246516e67db0b8b197b287c067d5a0163d8bde22..fc2c35f57436371cb0111aedfd289ac95d506d07 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java +++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java @@ -121,6 +121,20 @@ public enum EnchantmentCategory { public boolean canEnchant(Item item) { return item instanceof Vanishable || Block.byItem(item) instanceof Vanishable || BREAKABLE.canEnchant(item); } + // Purpur start + }, + BOW_AND_CROSSBOW { + @Override + public boolean canEnchant(Item item) { + return item instanceof BowItem || item instanceof CrossbowItem; + } + }, + WEAPON_AND_SHEARS { + @Override + public boolean canEnchant(Item item) { + return WEAPON.canEnchant(item) || item instanceof net.minecraft.world.item.ShearsItem; + } + // Purpur end }; public abstract boolean canEnchant(Item item); diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java index ecf640b00007a386290f8dfe9935a8aa610079fd..1eec84e217f6dc929091fa7451cd235ef3623822 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java +++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java @@ -46,7 +46,7 @@ public class EnchantmentHelper { } public static int getEnchantmentLevel(CompoundTag nbt) { - return Mth.clamp(nbt.getInt("lvl"), 0, 255); + return Mth.clamp(nbt.getInt("lvl"), 0, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels) ? 255 : 32767); // Purpur } @Nullable @@ -278,6 +278,29 @@ public class EnchantmentHelper { return getItemEnchantmentLevel(Enchantments.CHANNELING, stack) > 0; } + // Purpur start + @Nullable + public static Map.Entry getMostDamagedEquipment(Enchantment enchantment, LivingEntity entity) { + Map map = enchantment.getSlotItems(entity); + if (map.isEmpty()) { + return null; + } + Map.Entry item = null; + float maxPercent = 0F; + for (Map.Entry entry : map.entrySet()) { + ItemStack itemstack = entry.getValue(); + if (!itemstack.isEmpty() && itemstack.isDamaged() && getItemEnchantmentLevel(enchantment, itemstack) > 0) { + float percent = itemstack.getDamagePercent(); + if (item == null || percent > maxPercent) { + item = entry; + maxPercent = percent; + } + } + } + return item; + } + // Purpur end + @Nullable public static Map.Entry getRandomItemWith(Enchantment enchantment, LivingEntity entity) { return getRandomItemWith(enchantment, entity, (stack) -> { diff --git a/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java b/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java index 4007c16550683e23b396dfdff29530a82523fe05..8fe09c13643d99639fb242da4367c42ef31b38b4 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java +++ b/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java @@ -7,6 +7,14 @@ public class LootBonusEnchantment extends Enchantment { super(weight, target, slotTypes); } + // Purpur start + @Override + public boolean canEnchant(net.minecraft.world.item.ItemStack stack) { + // we have to cheat the system because this class is loaded before purpur's config is loaded + return (org.purpurmc.purpur.PurpurConfig.allowShearsLooting && this.category == EnchantmentCategory.WEAPON ? EnchantmentCategory.WEAPON_AND_SHEARS : this.category).canEnchant(stack.getItem()); + } + // Purpur end + @Override public int getMinCost(int level) { return 15 + (level - 1) * 9; diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java index 28bdcb14cb5b458d3c990fcf343ef97f08e4f3c6..48167334162443365bb8a6d082a51b2c626ab3d8 100644 --- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java @@ -134,7 +134,12 @@ public class MerchantOffer { } public void updateDemand() { - this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper + // Purpur start + this.updateDemand(0); + } + public void updateDemand(int minimumDemand) { + this.demand = Math.max(minimumDemand, this.demand + this.uses - (this.maxUses - this.uses)); + // Purpur end } public ItemStack assemble() { diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java index 633500aefd515df5dadda3802b94079f75a03fa0..64d911bee1607880514061c75116d8672df8bb8f 100644 --- a/src/main/java/net/minecraft/world/level/BaseSpawner.java +++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java @@ -55,6 +55,7 @@ public abstract class BaseSpawner { } public boolean isNearPlayer(Level world, BlockPos pos) { + if (world.purpurConfig.spawnerDeactivateByRedstone && world.hasNeighborSignal(pos)) return false; // Purpur return world.hasNearbyAlivePlayerThatAffectsSpawning((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper - Affects Spawning API } diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java index 3b959f42d958bf0f426853aee56753d6c455fcdb..d17abb283ea818244df0379d6b57fc634071e0b9 100644 --- a/src/main/java/net/minecraft/world/level/EntityGetter.java +++ b/src/main/java/net/minecraft/world/level/EntityGetter.java @@ -154,7 +154,7 @@ public interface EntityGetter { default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) { for(Player player : this.players()) { - if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) { + if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player) && EntitySelector.notAfk.test(player)) { double d = player.distanceToSqr(x, y, z); if (range < 0.0D || d < range * range) { return true; diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java index 8f97c9df726ac20cfce7bdddd5dd4f8c5aa76c35..4932374ab9a3d8582fb0ef024d817ad896dd23c4 100644 --- a/src/main/java/net/minecraft/world/level/Explosion.java +++ b/src/main/java/net/minecraft/world/level/Explosion.java @@ -86,7 +86,7 @@ public class Explosion { this.hitPlayers = Maps.newHashMap(); this.level = world; this.source = entity; - this.radius = (float) Math.max(power, 0.0); // CraftBukkit - clamp bad values + this.radius = (float) (world == null || world.purpurConfig.explosionClampRadius ? Math.max(power, 0.0) : power); // CraftBukkit - clamp bad values // Purpur this.x = x; this.y = y; this.z = z; @@ -137,10 +137,27 @@ public class Explosion { public void explode() { // CraftBukkit start - if (this.radius < 0.1F) { + if ((this.level == null || this.level.purpurConfig.explosionClampRadius) && this.radius < 0.1F) { // Purpur return; } // CraftBukkit end + + // Purpur start - add PreExplodeEvents + if(this.source != null){ + Location location = new Location(this.level.getWorld(), this.x, this.y, this.z); + if(!new org.purpurmc.purpur.event.entity.PreEntityExplodeEvent(this.source.getBukkitEntity(), location, this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F).callEvent()) { + this.wasCanceled = true; + return; + } + }else { + Location location = new Location(this.level.getWorld(), this.x, this.y, this.z); + if(!new org.purpurmc.purpur.event.PreBlockExplodeEvent(location.getBlock(), this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F, this.damageSource.explodedBlockState).callEvent()) { + this.wasCanceled = true; + return; + } + } + //Purpur end + this.level.gameEvent(this.source, GameEvent.EXPLODE, new Vec3(this.x, this.y, this.z)); Set set = Sets.newHashSet(); boolean flag = true; @@ -382,7 +399,7 @@ public class Explosion { if (!iblockdata.isAir() && iblockdata.isDestroyable()) { // Paper BlockPos blockposition1 = blockposition.immutable(); - this.level.getProfiler().push("explosion_blocks"); + //this.level.getProfiler().push("explosion_blocks"); // Purpur if (block.dropFromExplosion(this)) { Level world = this.level; @@ -404,7 +421,7 @@ public class Explosion { this.level.setBlock(blockposition, Blocks.AIR.defaultBlockState(), 3); block.wasExploded(this.level, blockposition, this); - this.level.getProfiler().pop(); + //this.level.getProfiler().pop(); // Purpur } } diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java index 21c11ff8d5ae9bf97dfcf3118cabe678deb4c064..713a091b8083e46b395311889c2a1482279c27ec 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -176,6 +176,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { // Paper end public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur public final co.aikar.timings.WorldTimingsHandler timings; // Paper public static BlockPos lastPhysicsProblem; // Spigot private org.spigotmc.TickLimiter entityLimiter; @@ -193,6 +194,49 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } // Paper end - fix and optimise world upgrading + // Purpur start + private com.google.common.cache.Cache playerBreedingCooldowns; + + private com.google.common.cache.Cache getNewBreedingCooldownCache() { + return com.google.common.cache.CacheBuilder.newBuilder().expireAfterWrite(this.purpurConfig.animalBreedingCooldownSeconds, java.util.concurrent.TimeUnit.SECONDS).build(); + } + + public void resetBreedingCooldowns() { + this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); + } + + public boolean hasBreedingCooldown(java.util.UUID player, Class animalType) { // Purpur + return this.playerBreedingCooldowns.getIfPresent(new BreedingCooldownPair(player, animalType)) != null; + } + + public void addBreedingCooldown(java.util.UUID player, Class animalType) { + this.playerBreedingCooldowns.put(new BreedingCooldownPair(player, animalType), new Object()); + } + + private static final class BreedingCooldownPair { + private final java.util.UUID playerUUID; + private final Class animalType; + + public BreedingCooldownPair(java.util.UUID playerUUID, Class animalType) { + this.playerUUID = playerUUID; + this.animalType = animalType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BreedingCooldownPair that = (BreedingCooldownPair) o; + return playerUUID.equals(that.playerUUID) && animalType.equals(that.animalType); + } + + @Override + public int hashCode() { + return java.util.Objects.hash(playerUUID, animalType); + } + } + // Purpur end + public CraftWorld getWorld() { return this.world; } @@ -273,7 +317,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public abstract ResourceKey getTypeKey(); - protected final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(java.util.concurrent.ThreadLocalRandom.current().nextLong()); public net.minecraft.util.RandomSource getThreadUnsafeRandom() { return this.randomTickRandom; } // Pufferfish - move thread unsafe random initialization // Pufferfish - getter + //protected final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(java.util.concurrent.ThreadLocalRandom.current().nextLong()); public net.minecraft.util.RandomSource getThreadUnsafeRandom() { return this.randomTickRandom; } // Pufferfish - move thread unsafe random initialization // Pufferfish - getter // Purpur - dont break ABI // Pufferfish start - ensure these get inlined private final int minBuildHeight, minSection, height, maxBuildHeight, maxSection; @@ -287,6 +331,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper + this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur + this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur this.generator = gen; this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); @@ -966,18 +1012,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } protected void tickBlockEntities() { - ProfilerFiller gameprofilerfiller = this.getProfiler(); + //ProfilerFiller gameprofilerfiller = this.getProfiler(); // Purpur - gameprofilerfiller.push("blockEntities"); - timings.tileEntityPending.startTiming(); // Spigot + //gameprofilerfiller.push("blockEntities"); // Purpur + //timings.tileEntityPending.startTiming(); // Spigot // Purpur this.tickingBlockEntities = true; if (!this.pendingBlockEntityTickers.isEmpty()) { this.blockEntityTickers.addAll(this.pendingBlockEntityTickers); this.pendingBlockEntityTickers.clear(); } - timings.tileEntityPending.stopTiming(); // Spigot + //timings.tileEntityPending.stopTiming(); // Spigot // Purpur - timings.tileEntityTick.startTiming(); // Spigot + //timings.tileEntityTick.startTiming(); // Spigot // Purpur // Spigot start // Iterator iterator = this.blockEntityTickers.iterator(); int tilesThisCycle = 0; @@ -1010,10 +1056,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } this.blockEntityTickers.removeAll(toRemove); - timings.tileEntityTick.stopTiming(); // Spigot + //timings.tileEntityTick.stopTiming(); // Spigot // Purpur this.tickingBlockEntities = false; co.aikar.timings.TimingHistory.tileEntityTicks += this.blockEntityTickers.size(); // Paper - gameprofilerfiller.pop(); + //gameprofilerfiller.pop(); // Purpur spigotConfig.currentPrimedTnt = 0; // Spigot } @@ -1206,7 +1252,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Override public List getEntities(@Nullable Entity except, AABB box, Predicate predicate) { - this.getProfiler().incrementCounter("getEntities"); + //this.getProfiler().incrementCounter("getEntities"); // Purpur List list = Lists.newArrayList(); ((ServerLevel)this).getEntityLookup().getEntities(except, box, list, predicate); // Paper - optimise this call return list; @@ -1225,7 +1271,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } public void getEntities(EntityTypeTest filter, AABB box, Predicate predicate, List result, int limit) { - this.getProfiler().incrementCounter("getEntities"); + //this.getProfiler().incrementCounter("getEntities"); // Purpur // Paper start - optimise this call //TODO use limit if (filter instanceof net.minecraft.world.entity.EntityType entityTypeTest) { @@ -1482,7 +1528,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } public ProfilerFiller getProfiler() { - if (gg.pufferfish.pufferfish.PufferfishConfig.disableMethodProfiler) return net.minecraft.util.profiling.InactiveProfiler.INSTANCE; // Pufferfish + if (true || gg.pufferfish.pufferfish.PufferfishConfig.disableMethodProfiler) return net.minecraft.util.profiling.InactiveProfiler.INSTANCE; // Pufferfish // Purpur return (ProfilerFiller) this.profiler.get(); } @@ -1584,4 +1630,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } } // Paper end + + // Purpur start + public boolean isNether() { + return getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER; + } + + public boolean isTheEnd() { + return getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java index 49de1fca183b2c6a0a5399025abfc0e47f314315..1f9994f4b0b736f64a8676d9431469527c6484df 100644 --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java +++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java @@ -132,8 +132,8 @@ public final class NaturalSpawner { } public static void spawnForChunk(ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnState info, boolean spawnAnimals, boolean spawnMonsters, boolean rareSpawn) { - world.getProfiler().push("spawner"); - world.timings.mobSpawn.startTiming(); // Spigot + //world.getProfiler().push("spawner"); // Purpur + //world.timings.mobSpawn.startTiming(); // Spigot // Purpur MobCategory[] aenumcreaturetype = NaturalSpawner.SPAWNING_CATEGORIES; int i = aenumcreaturetype.length; @@ -188,8 +188,8 @@ public final class NaturalSpawner { } } - world.timings.mobSpawn.stopTiming(); // Spigot - world.getProfiler().pop(); + //world.timings.mobSpawn.stopTiming(); // Spigot // Purpur + //world.getProfiler().pop(); // Purpur } // Paper start @@ -260,7 +260,7 @@ public final class NaturalSpawner { blockposition_mutableblockposition.set(l, i, i1); double d0 = (double) l + 0.5D; double d1 = (double) i1 + 0.5D; - Player entityhuman = (chunk instanceof LevelChunk) ? ((LevelChunk)chunk).findNearestPlayer(d0, i, d1, 576.0D, net.minecraft.world.entity.EntitySelector.NO_SPECTATORS) : world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); // Paper - use chunk's player cache to optimize search in range + Player entityhuman = (chunk instanceof LevelChunk) ? ((LevelChunk)chunk).findNearestPlayer(d0, i, d1, 576.0D, world.purpurConfig.mobSpawningIgnoreCreativePlayers ? net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR : net.minecraft.world.entity.EntitySelector.NO_SPECTATORS) : world.getNearestPlayer(d0, (double) i, d1, -1.0D, world.purpurConfig.mobSpawningIgnoreCreativePlayers); // Paper - use chunk's player cache to optimize search in range // Purpur if (entityhuman != null) { double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); diff --git a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java index 5c5a3b169795bf8a527b316c666cbc2105c66622..020afeca950d2c7fb6c7b179d424548fd90f8b0d 100644 --- a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java +++ b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java @@ -55,6 +55,54 @@ public class AnvilBlock extends FallingBlock { @Override public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + // Purpur start - repairable/damageable anvils + if (world.purpurConfig.anvilRepairIngotsAmount > 0) { + net.minecraft.world.item.ItemStack itemstack = player.getItemInHand(hand); + if (itemstack.is(net.minecraft.world.item.Items.IRON_INGOT)) { + if (itemstack.getCount() < world.purpurConfig.anvilRepairIngotsAmount) { + // not enough iron ingots, play "error" sound and consume + world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); + return InteractionResult.CONSUME; + } + if (state.is(Blocks.DAMAGED_ANVIL)) { + world.setBlock(pos, Blocks.CHIPPED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3); + } else if (state.is(Blocks.CHIPPED_ANVIL)) { + world.setBlock(pos, Blocks.ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3); + } else if (state.is(Blocks.ANVIL)) { + // anvil is already fully repaired, play "error" sound and consume + world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); + return InteractionResult.CONSUME; + } + if (!player.getAbilities().instabuild) { + itemstack.shrink(world.purpurConfig.anvilRepairIngotsAmount); + } + world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_PLACE, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); + return InteractionResult.CONSUME; + } + } + if (world.purpurConfig.anvilDamageObsidianAmount > 0) { + net.minecraft.world.item.ItemStack itemstack = player.getItemInHand(hand); + if (itemstack.is(net.minecraft.world.item.Items.OBSIDIAN)) { + if (itemstack.getCount() < world.purpurConfig.anvilDamageObsidianAmount) { + // not enough obsidian, play "error" sound and consume + world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); + return InteractionResult.CONSUME; + } + if (state.is(Blocks.DAMAGED_ANVIL)) { + world.destroyBlock(pos, false); + } else if (state.is(Blocks.CHIPPED_ANVIL)) { + world.setBlock(pos, Blocks.DAMAGED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3); + } else if (state.is(Blocks.ANVIL)) { + world.setBlock(pos, Blocks.CHIPPED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3); + } + if (!player.getAbilities().instabuild) { + itemstack.shrink(world.purpurConfig.anvilDamageObsidianAmount); + } + world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_LAND, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); + return InteractionResult.CONSUME; + } + } + // Purpur end if (world.isClientSide) { return InteractionResult.SUCCESS; } else { diff --git a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java index 95b53450a807fccfa55b59852da52785b8cf3e3d..c69f1d23979a0759472d22760a18d986b2d979b6 100644 --- a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java +++ b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java @@ -43,6 +43,20 @@ public class AzaleaBlock extends BushBlock implements BonemealableBlock { @Override public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + // Purpur start + growTree(world, random, pos, state); + } + + @Override + public void randomTick(net.minecraft.world.level.block.state.BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + double chance = state.getBlock() == Blocks.FLOWERING_AZALEA ? world.purpurConfig.floweringAzaleaGrowthChance : world.purpurConfig.azaleaGrowthChance; + if (chance > 0.0D && world.getMaxLocalRawBrightness(pos.above()) > 9 && random.nextDouble() < chance) { + growTree(world, random, pos, state); + } + } + + private void growTree(ServerLevel world, RandomSource random, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) { + // Purpur end TREE_GROWER.growTree(world, world.getChunkSource().getGenerator(), pos, state, random); } diff --git a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java index 3d2b34c5a7c9b00c1164b4f89c2cbff81fc460eb..b5505e926e5cdb447de68e8eb8e46c97eb988e27 100644 --- a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java @@ -35,6 +35,7 @@ public class BaseCoralPlantTypeBlock extends Block implements SimpleWaterloggedB } protected static boolean scanForWater(BlockState state, BlockGetter world, BlockPos pos) { + if (!((net.minecraft.world.level.LevelAccessor) world).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur if (state.getValue(WATERLOGGED)) { return true; } else { diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java index d40500f9a807cab0b2fb6fa9032f33f4fb74c895..2b66ddafaaca17f64d1e7502dfa4d7576e3e032f 100644 --- a/src/main/java/net/minecraft/world/level/block/BedBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java @@ -96,7 +96,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock Vec3 vec3d = pos.getCenter(); - world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); + if (world.purpurConfig.bedExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // Purpur return InteractionResult.SUCCESS; } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) { if (!BedBlock.canSetSpawn(world)) return this.explodeBed(state, world, pos); // Paper - check explode first @@ -149,7 +149,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock Vec3 vec3d = blockposition.getCenter(); - world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); + if (world.purpurConfig.bedExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // Purpur return InteractionResult.SUCCESS; } } @@ -173,7 +173,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 5921f7ebb56e1d5d3004ae327271873093cff357..c7bb655983d2ee2c6461d23d6fa921def1a9a74c 100644 --- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java @@ -236,7 +236,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone BigDripleafBlock.playTiltSound(world, blockposition, soundeffect); } - int i = BigDripleafBlock.DELAY_UNTIL_NEXT_TILT_STATE.getInt(tilt); + int i = world.purpurConfig.bigDripleafTiltDelay.getOrDefault(tilt, -1); // Purpur if (i != -1) { world.scheduleTick(blockposition, (Block) this, i); diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java index fdd9c61b7248e92dbcbec91cd6fe4c6310bba237..dc718efe054aaf36961514287c9cc48e2d28bc4c 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java @@ -62,6 +62,13 @@ import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import org.slf4j.Logger; +// Purpur start +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.world.Nameable; +// Purpur end + public class Block extends BlockBehaviour implements ItemLike { private static final Logger LOGGER = LogUtils.getLogger(); @@ -87,6 +94,10 @@ public class Block extends BlockBehaviour implements ItemLike { public static final int UPDATE_LIMIT = 512; protected final StateDefinition stateDefinition; private BlockState defaultBlockState; + // Purpur start + public float fallDamageMultiplier = 1.0F; + public float fallDistanceMultiplier = 1.0F; + // Purpur end // Paper start public final boolean isDestroyable() { return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || @@ -313,7 +324,7 @@ public class Block extends BlockBehaviour implements ItemLike { public static void dropResources(BlockState state, LevelAccessor world, BlockPos pos, @Nullable BlockEntity blockEntity) { if (world instanceof ServerLevel) { Block.getDrops(state, (ServerLevel) world, pos, blockEntity).forEach((itemstack) -> { - Block.popResource((ServerLevel) world, pos, itemstack); + Block.popResource((ServerLevel) world, pos, applyDisplayNameAndLoreFromTile(itemstack, blockEntity)); // Purpur }); state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true); } @@ -329,7 +340,7 @@ public class Block extends BlockBehaviour implements ItemLike { io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.block.CraftBlock.at(world, source), items); event.callEvent(); for (var drop : event.getDrops()) { - popResource(world.getMinecraftWorld(), pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); + popResource(world.getMinecraftWorld(), pos, applyDisplayNameAndLoreFromTile(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop), blockEntity)); // Purpur } state.spawnAfterBreak(world.getMinecraftWorld(), pos, ItemStack.EMPTY, true); } @@ -340,13 +351,53 @@ public class Block extends BlockBehaviour implements ItemLike { public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) { if (world instanceof ServerLevel) { Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> { - Block.popResource(world, pos, itemstack1); + Block.popResource(world, pos, applyDisplayNameAndLoreFromTile(itemstack1, blockEntity)); // Purpur }); state.spawnAfterBreak((ServerLevel) world, pos, tool, true); } } + // Purpur start + private static ItemStack applyDisplayNameAndLoreFromTile(ItemStack stack, BlockEntity blockEntity) { + if (stack.getItem() instanceof BlockItem) { + if (blockEntity != null && blockEntity.getLevel() instanceof ServerLevel && blockEntity.getLevel().purpurConfig.persistentTileEntityDisplayNames) { + String name = blockEntity.getPersistentDisplayName(); + ListTag lore = blockEntity.getPersistentLore(); + if (blockEntity instanceof Nameable) { + Nameable namedTile = (Nameable) blockEntity; + if (namedTile.hasCustomName()) { + name = Component.Serializer.toJson(namedTile.getCustomName()); + } + } + + if (name != null || lore != null) { + CompoundTag display = stack.getTagElement("display"); + if (display == null) { + display = new CompoundTag(); + } + + if (name != null) { + display.put("Name", StringTag.valueOf(name)); + } + if (lore != null) { + display.put("Lore", lore); + } + + CompoundTag tag = stack.getTag(); + if (tag == null) { + tag = new CompoundTag(); + } + tag.put("display", display); + + stack.setTag(tag); + } + } + } + return stack; + } + // Purpur end + public static void popResource(Level world, BlockPos pos, ItemStack stack) { double d0 = (double) EntityType.ITEM.getHeight() / 2.0D; double d1 = (double) pos.getX() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D); @@ -430,7 +481,17 @@ public class Block extends BlockBehaviour implements ItemLike { } // Paper } - public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {} + // Purpur start + @Nullable protected LivingEntity placer = null; + + public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) { + this.placer = placer; + } + + public void forgetPlacer() { + this.placer = null; + } + // Purpur end public boolean isPossibleToRespawnInThis(BlockState state) { return !state.isSolid() && !state.liquid(); @@ -449,7 +510,7 @@ public class Block extends BlockBehaviour implements ItemLike { } public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { - entity.causeFallDamage(fallDistance, 1.0F, entity.damageSources().fall()); + entity.causeFallDamage(fallDistance * fallDistanceMultiplier, fallDamageMultiplier, entity.damageSources().fall()); // Purpur } public void updateEntityAfterFallOn(BlockGetter world, Entity entity) { diff --git a/src/main/java/net/minecraft/world/level/block/Blocks.java b/src/main/java/net/minecraft/world/level/block/Blocks.java index 877035b6c6593a28f475b9c5bcd7727e3fcdb802..ed35878fdb9dffcd46c27d26ee8379401207cef5 100644 --- a/src/main/java/net/minecraft/world/level/block/Blocks.java +++ b/src/main/java/net/minecraft/world/level/block/Blocks.java @@ -1093,8 +1093,8 @@ public class Blocks { public static final Block CAVE_VINES = register("cave_vines", new CaveVinesBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).randomTicks().noCollission().lightLevel(CaveVines.emission(14)).instabreak().sound(SoundType.CAVE_VINES).pushReaction(PushReaction.DESTROY))); public static final Block CAVE_VINES_PLANT = register("cave_vines_plant", new CaveVinesPlantBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().lightLevel(CaveVines.emission(14)).instabreak().sound(SoundType.CAVE_VINES).pushReaction(PushReaction.DESTROY))); public static final Block SPORE_BLOSSOM = register("spore_blossom", new SporeBlossomBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instabreak().noCollission().sound(SoundType.SPORE_BLOSSOM).pushReaction(PushReaction.DESTROY))); - public static final Block AZALEA = register("azalea", new AzaleaBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).forceSolidOff().instabreak().sound(SoundType.AZALEA).noOcclusion().pushReaction(PushReaction.DESTROY))); - public static final Block FLOWERING_AZALEA = register("flowering_azalea", new AzaleaBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).forceSolidOff().instabreak().sound(SoundType.FLOWERING_AZALEA).noOcclusion().pushReaction(PushReaction.DESTROY))); + public static final Block AZALEA = register("azalea", new AzaleaBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).forceSolidOff().randomTicks().instabreak().sound(SoundType.AZALEA).noOcclusion().pushReaction(PushReaction.DESTROY))); // Purpur + public static final Block FLOWERING_AZALEA = register("flowering_azalea", new AzaleaBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).forceSolidOff().randomTicks().instabreak().sound(SoundType.FLOWERING_AZALEA).noOcclusion().pushReaction(PushReaction.DESTROY))); // Purpur public static final Block MOSS_CARPET = register("moss_carpet", new CarpetBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_GREEN).strength(0.1F).sound(SoundType.MOSS_CARPET).pushReaction(PushReaction.DESTROY))); public static final Block PINK_PETALS = register("pink_petals", new PinkPetalsBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.PINK_PETALS).pushReaction(PushReaction.DESTROY))); public static final Block MOSS_BLOCK = register("moss_block", new MossBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_GREEN).strength(0.1F).sound(SoundType.MOSS).pushReaction(PushReaction.DESTROY))); @@ -1159,7 +1159,7 @@ public class Blocks { } private static Boolean ocelotOrParrot(BlockState state, BlockGetter world, BlockPos pos, EntityType type) { - return (boolean)type == EntityType.OCELOT || type == EntityType.PARROT; + return type == EntityType.OCELOT || type == EntityType.PARROT; // Purpur - decompile error } private static BedBlock bed(DyeColor color) { diff --git a/src/main/java/net/minecraft/world/level/block/BushBlock.java b/src/main/java/net/minecraft/world/level/block/BushBlock.java index 03fde6e47c4a347c62fe9b4a3351769aedf874f6..ca906b0250e5332f7ececf1419ca6d2c1d385adc 100644 --- a/src/main/java/net/minecraft/world/level/block/BushBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BushBlock.java @@ -48,4 +48,24 @@ public class BushBlock extends Block { public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { return type == PathComputationType.AIR && !this.hasCollision ? true : super.isPathfindable(state, world, pos, type); } + + // Purpur start + public void playerDestroyAndReplant(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, net.minecraft.world.item.ItemStack itemInHand, net.minecraft.world.level.ItemLike itemToReplant) { + player.awardStat(net.minecraft.stats.Stats.BLOCK_MINED.get(this)); + player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); + java.util.List dropList = Block.getDrops(state, (net.minecraft.server.level.ServerLevel) world, pos, blockEntity, player, itemInHand); + + boolean planted = false; + for (net.minecraft.world.item.ItemStack itemToDrop : dropList) { + if (!planted && itemToDrop.getItem() == itemToReplant) { + world.setBlock(pos, defaultBlockState(), 3); + itemToDrop.setCount(itemToDrop.getCount() - 1); + planted = true; + } + Block.popResource(world, pos, itemToDrop); + } + + state.spawnAfterBreak((net.minecraft.server.level.ServerLevel) world, pos, itemInHand, true); + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java index 0003fb51ae3a6575575e10b4c86719f3061e2577..0d5f87d24231f6d2b8639825bcd62dd2f8791c8e 100644 --- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java @@ -22,7 +22,7 @@ import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit -public class CactusBlock extends Block { +public class CactusBlock extends Block implements BonemealableBlock { // Purpur public static final IntegerProperty AGE = BlockStateProperties.AGE_15; public static final int MAX_AGE = 15; @@ -107,7 +107,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; } @@ -129,4 +129,34 @@ public class CactusBlock extends Block { public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { return false; } + + // Purpur start + @Override + public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, BlockState state, boolean isClient) { + if (!((Level) world).purpurConfig.cactusAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false; + + int cactusHeight = 0; + while (world.getBlockState(pos.below(cactusHeight)).is(this)) { + cactusHeight++; + } + + return cactusHeight < ((Level) world).paperConfig().maxGrowthHeight.cactus; + } + + @Override + public boolean isBonemealSuccess(Level world, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + int cactusHeight = 0; + while (world.getBlockState(pos.below(cactusHeight)).is(this)) { + cactusHeight++; + } + for (int i = 0; i <= world.paperConfig().maxGrowthHeight.cactus - cactusHeight; i++) { + world.setBlockAndUpdate(pos.above(i), state.setValue(CactusBlock.AGE, 0)); + } + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java index 219c87dcf065e86512f330fbeec59e55f4675083..f8fd3b320494d1c1e8ee3d170f2feebd152230fa 100644 --- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java @@ -122,7 +122,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB BlockPos blockposition = ctx.getClickedPos(); boolean flag = world.getFluidState(blockposition).getType() == Fluids.WATER; - return (BlockState) ((BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(CampfireBlock.WATERLOGGED, flag)).setValue(CampfireBlock.SIGNAL_FIRE, this.isSmokeSource(world.getBlockState(blockposition.below())))).setValue(CampfireBlock.LIT, !flag)).setValue(CampfireBlock.FACING, ctx.getHorizontalDirection()); + return (BlockState) ((BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(CampfireBlock.WATERLOGGED, flag)).setValue(CampfireBlock.SIGNAL_FIRE, this.isSmokeSource(world.getBlockState(blockposition.below())))).setValue(CampfireBlock.LIT, world.purpurConfig.campFireLitWhenPlaced ? !flag : world.purpurConfig.campFireLitWhenPlaced)).setValue(CampfireBlock.FACING, ctx.getHorizontalDirection()); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java b/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java index 23c487e295b3b736d8800f0c884324c9b18a5373..ebeb7caf7fd4f45714bab0856a48b847a544cce7 100644 --- a/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java @@ -64,7 +64,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { SnowGolem entitysnowman = (SnowGolem) EntityType.SNOW_GOLEM.create(world); if (entitysnowman != null) { - CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection, entitysnowman, shapedetector_shapedetectorcollection.getBlock(0, 2, 0).getPos()); + CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection, entitysnowman, shapedetector_shapedetectorcollection.getBlock(0, 2, 0).getPos(), this.placer); // Purpur } } else { BlockPattern.BlockPatternMatch shapedetector_shapedetectorcollection1 = this.getOrCreateIronGolemFull().find(world, pos); @@ -74,7 +74,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 } } } @@ -82,6 +82,16 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { } private static void spawnGolemInWorld(Level world, BlockPattern.BlockPatternMatch patternResult, Entity entity, BlockPos pos) { + // Purpur start + spawnGolemInWorld(world, patternResult, entity, pos, null); + } + private static void spawnGolemInWorld(Level world, BlockPattern.BlockPatternMatch patternResult, Entity entity, BlockPos pos, net.minecraft.world.entity.LivingEntity placer) { + if (entity instanceof SnowGolem snowGolem) { + snowGolem.setSummoner(placer == null ? null : placer.getUUID()); + } else if (entity instanceof IronGolem ironGolem) { + ironGolem.setSummoner(placer == null ? null : placer.getUUID()); + } + // Purpur end // clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - moved down entity.moveTo((double) pos.getX() + 0.5D, (double) pos.getY() + 0.05D, (double) pos.getZ() + 0.5D, 0.0F, 0.0F); // CraftBukkit start diff --git a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java index 2f85b893dd0abc39fcedec65acc89e1567faf6f0..3ee012a9ef8cada0b2203e53b2f731f60f697cb1 100644 --- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java @@ -29,7 +29,7 @@ public class CauldronBlock extends AbstractCauldronBlock { } protected static boolean shouldHandlePrecipitation(Level world, Biome.Precipitation precipitation) { - return precipitation == Biome.Precipitation.RAIN ? world.getRandom().nextFloat() < 0.05F : (precipitation == Biome.Precipitation.SNOW ? world.getRandom().nextFloat() < 0.1F : false); + return precipitation == Biome.Precipitation.RAIN ? world.getRandom().nextFloat() < world.purpurConfig.cauldronRainChance : (precipitation == Biome.Precipitation.SNOW ? world.getRandom().nextFloat() < world.purpurConfig.cauldronPowderSnowChance : false); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java index 18b5bce1138d50be32e5da013221be69dc47e21f..58b4a0d97af37f7164db86ef821f04102c6c5ddd 100644 --- a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java @@ -88,4 +88,11 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements Bonemealabl public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { world.setBlock(pos, state.setValue(BERRIES, Boolean.valueOf(true)), 2); } + + // Purpur start + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.caveVinesMaxGrowthAge; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/ChestBlock.java b/src/main/java/net/minecraft/world/level/block/ChestBlock.java index 5e22d175b1048a58802cdf64ac70a8b56329e915..d81946b400f208c39941128ce823ff7709741c10 100644 --- a/src/main/java/net/minecraft/world/level/block/ChestBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ChestBlock.java @@ -355,6 +355,7 @@ public class ChestBlock extends AbstractChestBlock implements } private static boolean isBlockedChestByBlock(BlockGetter world, BlockPos pos) { + if (world instanceof Level && ((Level) world).purpurConfig.chestOpenWithBlockOnTop) return false; // Purpur BlockPos blockposition1 = pos.above(); return world.getBlockState(blockposition1).isRedstoneConductor(world, blockposition1); diff --git a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java index 10d3912ef043eefdf89105332e29b0d2bf4a5539..596b77306f690a2298835f0f0fea1abee2a7c85d 100644 --- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java @@ -229,20 +229,28 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { ItemStack itemstack = player.getItemInHand(hand); if (i < 8 && ComposterBlock.COMPOSTABLES.containsKey(itemstack.getItem())) { - if (i < 7 && !world.isClientSide) { - BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, itemstack); - // Paper start - handle cancelled events - if (iblockdata1 == null) { - return InteractionResult.PASS; - } - // Paper end + // Purpur start + BlockState newState = process(i, state, world, itemstack, pos, player); + if (newState == null) { + return InteractionResult.PASS; + } - world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); - player.awardStat(Stats.ITEM_USED.get(itemstack.getItem())); - if (!player.getAbilities().instabuild) { - itemstack.shrink(1); - } + if (world.purpurConfig.composterBulkProcess && player.isShiftKeyDown() && newState != state) { + BlockState oldState; + int oldCount, newCount, oldLevel, newLevel; + do { + oldState = newState; + oldCount = itemstack.getCount(); + oldLevel = oldState.getValue(ComposterBlock.LEVEL); + newState = process(oldLevel, oldState, world, itemstack, pos, player); + if (newState == null) { + return InteractionResult.PASS; + } + newCount = itemstack.getCount(); + newLevel = newState.getValue(ComposterBlock.LEVEL); + } while (newCount > 0 && (newCount != oldCount || newLevel != oldLevel || newState != oldState)); } + // Purpur end return InteractionResult.sidedSuccess(world.isClientSide); } else if (i == 8) { @@ -253,6 +261,26 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { } } + private static BlockState process(int level, BlockState state, Level world, ItemStack itemstack, BlockPos pos, Player player) { + if (level < 7 && !world.isClientSide) { + BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, itemstack); + // Paper start - handle cancelled events + if (iblockdata1 == null) { + return iblockdata1; + } + // Paper end + + world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); + player.awardStat(Stats.ITEM_USED.get(itemstack.getItem())); + if (!player.getAbilities().instabuild) { + itemstack.shrink(1); + } + return iblockdata1; + } + return state; + } + // Purpur end + public static BlockState insertItem(Entity user, BlockState state, ServerLevel world, ItemStack stack, BlockPos pos) { int i = (Integer) state.getValue(ComposterBlock.LEVEL); diff --git a/src/main/java/net/minecraft/world/level/block/CoralBlock.java b/src/main/java/net/minecraft/world/level/block/CoralBlock.java index 88faea00be60a519f56f975a5311df5e1eb3e6b8..cbb726ac367be81e27d3a86643baf7c4f0746edf 100644 --- a/src/main/java/net/minecraft/world/level/block/CoralBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CoralBlock.java @@ -45,6 +45,7 @@ public class CoralBlock extends Block { } protected boolean scanForWater(BlockGetter world, BlockPos pos) { + if (!((net.minecraft.world.level.LevelAccessor) world).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur Direction[] aenumdirection = Direction.values(); int i = aenumdirection.length; diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java index decb8caa67bc5f4525e4d92fedf465a17171fceb..26b3f162f25bed3e9d8001e3d9c2f8d7d4b3e08a 100644 --- a/src/main/java/net/minecraft/world/level/block/CropBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java @@ -168,7 +168,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { @Override public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit + if (entity instanceof Ravager && world.purpurConfig.ravagerGriefableBlocks.contains(world.getBlockState(pos).getBlock()) && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), (!world.purpurConfig.ravagerBypassMobGriefing && !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)))) { // CraftBukkit // Purpur world.destroyBlock(pos, true, entity); } @@ -203,4 +203,15 @@ public class CropBlock extends BushBlock implements BonemealableBlock { protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(CropBlock.AGE); } + + // Purpur start + @Override + public void playerDestroy(Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops) { + 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); + } + } + // 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 c028a7158e41a0754abb8e24dcd647633fbf3fe8..cd65d32f4af016d4937e598c71386a3072f4c490 100644 --- a/src/main/java/net/minecraft/world/level/block/DoorBlock.java +++ b/src/main/java/net/minecraft/world/level/block/DoorBlock.java @@ -167,6 +167,7 @@ public class DoorBlock extends Block { public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { if (!this.type.canOpenByHand()) { return InteractionResult.PASS; + } else if (requiresRedstone(world, state, pos)) { return InteractionResult.CONSUME; // Purpur } else { state = (BlockState) state.cycle(DoorBlock.OPEN); world.setBlock(pos, state, 10); @@ -270,4 +271,18 @@ public class DoorBlock extends Block { flag = false; return flag; } + + // Purpur start + public static boolean requiresRedstone(Level level, BlockState state, BlockPos pos) { + if (level.purpurConfig.doorRequiresRedstone.contains(state.getBlock())) { + // force update client + BlockPos otherPos = pos.relative(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN); + BlockState otherState = level.getBlockState(otherPos); + level.sendBlockUpdated(pos, state, state, 3); + level.sendBlockUpdated(otherPos, otherState, otherState, 3); + return true; + } + return false; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java b/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java index 7e1edcc7b9f170b7c649437c2f0dd78c0bab9be4..5f8ac1fdac2c334951261f2b9702f5e711743c88 100644 --- a/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java +++ b/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java @@ -42,8 +42,8 @@ public class DragonEggBlock extends FallingBlock { } private void teleport(BlockState state, Level world, BlockPos pos) { + if (!world.purpurConfig.dragonEggTeleport) return; // Purpur WorldBorder worldborder = world.getWorldBorder(); - for (int i = 0; i < 1000; ++i) { BlockPos blockposition1 = pos.offset(world.random.nextInt(16) - world.random.nextInt(16), world.random.nextInt(8) - world.random.nextInt(8), world.random.nextInt(16) - world.random.nextInt(16)); diff --git a/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java b/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java index 839b7bc9392906dca384003468746963631fe095..286f34eef22a85be3fe9747dc3c3f9a7d51f437c 100644 --- a/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java +++ b/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java @@ -29,6 +29,8 @@ import net.minecraft.world.level.pathfinder.PathComputationType; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraft.world.Containers; // Purpur +import net.minecraft.world.item.Items; // Purpur public class EnchantmentTableBlock extends BaseEntityBlock { protected static final VoxelShape SHAPE = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 12.0D, 16.0D); @@ -121,4 +123,18 @@ public class EnchantmentTableBlock extends BaseEntityBlock { public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { return false; } + + // Purpur start + @Override + public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean moved) { + BlockEntity blockEntity = level.getBlockEntity(pos); + + if (level.purpurConfig.enchantmentTableLapisPersists && blockEntity instanceof EnchantmentTableBlockEntity enchantmentTable) { + Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), new ItemStack(Items.LAPIS_LAZULI, enchantmentTable.getLapis())); + level.updateNeighbourForOutputSignal(pos, this); + } + + super.onRemove(state, level, pos, newState, moved); + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java index 41d7cff39fc37955877668337689b4b26cd8c7cf..2deddc746e43896584bd65ba8e7971a80acb4a4d 100644 --- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java @@ -46,6 +46,14 @@ public class EndPortalBlock extends BaseEntityBlock { public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper if (world instanceof ServerLevel && entity.canChangeDimensions() && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-pos.getX()), (double) (-pos.getY()), (double) (-pos.getZ()))), state.getShape(world, pos), BooleanOp.AND)) { + // Purpur start + if (entity.isPassenger() || entity.isVehicle()) { + if (new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, PlayerTeleportEvent.TeleportCause.END_PORTAL).callEvent()) { + this.entityInside(state, world, pos, entity); + } + return; + } + // Purpur end ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends ServerLevel worldserver = ((ServerLevel) world).getServer().getLevel(resourcekey); @@ -53,6 +61,22 @@ public class EndPortalBlock extends BaseEntityBlock { // return; // CraftBukkit - always fire event in case plugins wish to change it } + // Purpur start + if (!world.purpurConfig.endPortalSafeTeleporting) { + // CraftBukkit start - Entity in portal + EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); + world.getCraftServer().getPluginManager().callEvent(event); + + if (entity instanceof ServerPlayer) { + ((ServerPlayer) entity).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); + return; + } + // CraftBukkit end + entity.changeDimension(worldserver); + return; + } + // Purpur end + // Paper start - move all of this logic into portal tick entity.portalWorld = ((ServerLevel)world); entity.portalBlock = pos.immutable(); diff --git a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java index 7385e91f32f070e86a4e0fd3d214f55d832c7979..7b73de87236a60ce7343c29ec147e1866b448ba3 100644 --- a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java +++ b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java @@ -85,6 +85,34 @@ public class EnderChestBlock extends AbstractChestBlock i EnderChestBlockEntity enderChestBlockEntity = (EnderChestBlockEntity)blockEntity; playerEnderChestContainer.setActiveChest(enderChestBlockEntity); player.openMenu(new SimpleMenuProvider((syncId, inventory, playerx) -> { + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.enderChestSixRows) { + if (org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) { + org.bukkit.craftbukkit.entity.CraftHumanEntity bukkitPlayer = player.getBukkitEntity(); + if (bukkitPlayer.hasPermission("purpur.enderchest.rows.six")) { + 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 return ChestMenu.threeRows(syncId, inventory, playerEnderChestContainer); }, CONTAINER_TITLE)); player.awardStat(Stats.OPEN_ENDERCHEST); diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java index 5946f06f63b5694034bd027984a4925b0831d439..d566f67f8f6f1748023430de4f191881b79e44a1 100644 --- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java +++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java @@ -101,7 +101,7 @@ public class FarmBlock extends Block { @Override public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { super.fallOn(world, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage. - if (!world.isClientSide && world.random.nextFloat() < fallDistance - 0.5F && entity instanceof LivingEntity && (entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { + if (!world.isClientSide && (world.purpurConfig.farmlandTrampleHeight >= 0D ? fallDistance >= world.purpurConfig.farmlandTrampleHeight : world.random.nextFloat() < fallDistance - 0.5F) && entity instanceof LivingEntity && (entity instanceof Player || world.purpurConfig.farmlandBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { // Purpur // CraftBukkit start - Interact soil org.bukkit.event.Cancellable cancellable; if (entity instanceof Player) { @@ -115,6 +115,22 @@ public class FarmBlock extends Block { return; } + // Purpur start + if (world.purpurConfig.farmlandTramplingDisabled) return; + if (world.purpurConfig.farmlandTramplingOnlyPlayers && !(entity instanceof Player)) return; + if (world.purpurConfig.farmlandAlpha) { + Block block = world.getBlockState(pos.below()).getBlock(); + if (block instanceof FenceBlock || block instanceof WallBlock) { + return; + } + } + if (world.purpurConfig.farmlandTramplingFeatherFalling) { + Iterator armor = entity.getArmorSlots().iterator(); + if (armor.hasNext() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FALL_PROTECTION, armor.next()) >= (int) entity.fallDistance) { + return; + } + } + // Purpur end if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) { return; } @@ -162,7 +178,7 @@ public class FarmBlock extends Block { } } - return false; + return ((ServerLevel) world).purpurConfig.farmlandGetsMoistFromBelow && world.getFluidState(pos.relative(Direction.DOWN)).is(FluidTags.WATER); // Purpur; } @Override diff --git a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java index 3a1aa4e2405090ccebefb7f5944f36462929e221..f3cf9f06de40054720d1847c1869a9d82592134d 100644 --- a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +++ b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java @@ -30,12 +30,12 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements @Override public BlockState getStateForPlacement(LevelAccessor world) { - return (BlockState) this.defaultBlockState().setValue(GrowingPlantHeadBlock.AGE, world.getRandom().nextInt(25)); + return (BlockState) this.defaultBlockState().setValue(GrowingPlantHeadBlock.AGE, world.getRandom().nextInt(getMaxGrowthAge())); // Purpur } @Override public boolean isRandomlyTicking(BlockState state) { - return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25; + return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) < getMaxGrowthAge(); // Purpur } @Override @@ -51,7 +51,7 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements } else { modifier = world.spigotConfig.caveVinesModifier; } - if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution + if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < getMaxGrowthAge() && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution // Purpur // Spigot end BlockPos blockposition1 = pos.relative(this.growthDirection); @@ -73,11 +73,11 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements } public BlockState getMaxAgeState(BlockState state) { - return (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, 25); + return (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, getMaxGrowthAge()); // Purpur } public boolean isMaxAge(BlockState state) { - return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) == 25; + return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) >= getMaxGrowthAge(); // Purpur } protected BlockState updateBodyAfterConvertedFromHead(BlockState from, BlockState to) { @@ -119,13 +119,13 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements @Override public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { BlockPos blockposition1 = pos.relative(this.growthDirection); - int i = Math.min((Integer) state.getValue(GrowingPlantHeadBlock.AGE) + 1, 25); + int i = Math.min((Integer) state.getValue(GrowingPlantHeadBlock.AGE) + 1, getMaxGrowthAge()); // Purpur int j = this.getBlocksToGrowWhenBonemealed(random); for (int k = 0; k < j && this.canGrowInto(world.getBlockState(blockposition1)); ++k) { world.setBlockAndUpdate(blockposition1, (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, i)); blockposition1 = blockposition1.relative(this.growthDirection); - i = Math.min(i + 1, 25); + i = Math.min(i + 1, getMaxGrowthAge()); // Purpur } } @@ -138,4 +138,6 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements protected GrowingPlantHeadBlock getHeadBlock() { return this; } + + public abstract int getMaxGrowthAge(); // Purpur } diff --git a/src/main/java/net/minecraft/world/level/block/HayBlock.java b/src/main/java/net/minecraft/world/level/block/HayBlock.java index cfbe1dae76db76cf54a4f5d72aca72d5e893859e..74cb10230d459ac9f300a9d59af504d233ac663e 100644 --- a/src/main/java/net/minecraft/world/level/block/HayBlock.java +++ b/src/main/java/net/minecraft/world/level/block/HayBlock.java @@ -15,6 +15,6 @@ public class HayBlock extends RotatedPillarBlock { @Override public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { - entity.causeFallDamage(fallDistance, 0.2F, world.damageSources().fall()); + super.fallOn(world, state, pos, entity, fallDistance); // Purpur } } diff --git a/src/main/java/net/minecraft/world/level/block/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java index 04089e6f7d6e3d532b00585870283922b6be5246..61e6d14abd54ecd5e43a5459f8daa7d86adedf44 100644 --- a/src/main/java/net/minecraft/world/level/block/IceBlock.java +++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java @@ -33,7 +33,7 @@ public class IceBlock extends HalfTransparentBlock { public void afterDestroy(Level world, BlockPos pos, ItemStack tool) { // Paper end if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) == 0) { - if (world.dimensionType().ultraWarm()) { + if (world.isNether() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur world.removeBlock(pos, false); return; } @@ -61,7 +61,7 @@ public class IceBlock extends HalfTransparentBlock { return; } // CraftBukkit end - if (world.dimensionType().ultraWarm()) { + if (world.isNether() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur world.removeBlock(pos, false); } else { world.setBlockAndUpdate(pos, 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 bc66fa91ec3e13431d5d9b6e17935cab73066be7..0f16b5ed2e249f3d8f583dc941e32066d354cf95 100644 --- a/src/main/java/net/minecraft/world/level/block/KelpBlock.java +++ b/src/main/java/net/minecraft/world/level/block/KelpBlock.java @@ -64,4 +64,11 @@ public class KelpBlock extends GrowingPlantHeadBlock implements LiquidBlockConta public FluidState getFluidState(BlockState state) { return Fluids.WATER.getSource(false); } + + // Purpur start + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.kelpMaxGrowthAge; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java index bbabe4ad8afcc3a2069f6e9d4a9adcb643266894..55419bd653f7f5391fa13cd15a0b00fbff5e9c39 100644 --- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java +++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java @@ -105,7 +105,7 @@ public class LiquidBlock extends Block implements BucketPickup { @Override public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { - if (this.shouldSpreadLiquid(world, pos, state)) { + if (world.purpurConfig.tickFluids && this.shouldSpreadLiquid(world, pos, state)) { // Purpur world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper } @@ -133,7 +133,7 @@ public class LiquidBlock extends Block implements BucketPickup { @Override public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { + if (world.getMinecraftWorld().purpurConfig.tickFluids && state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { // Purpur world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world)); } @@ -142,7 +142,7 @@ public class LiquidBlock extends Block implements BucketPickup { @Override public void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { - if (this.shouldSpreadLiquid(world, pos, state)) { + if (world.purpurConfig.tickFluids && this.shouldSpreadLiquid(world, pos, state)) { // Purpur world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper } diff --git a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java index 1b766045687e4dcded5cbcc50b746c55b9a34e22..be365914856593bb3c4e1945cc990786072f2953 100644 --- a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java +++ b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java @@ -22,7 +22,7 @@ public class MagmaBlock extends Block { @Override public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) { - if (!entity.isSteppingCarefully() && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) { + if ((!entity.isSteppingCarefully() || world.purpurConfig.magmaBlockDamageWhenSneaking) && entity instanceof LivingEntity && (world.purpurConfig.magmaBlockDamageWithFrostWalker || !EnchantmentHelper.hasFrostWalker((LivingEntity) entity))) { // Purpur org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit entity.hurt(world.damageSources().hotFloor(), 1.0F); org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; // CraftBukkit diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java index a6ab0d0defc05e56a91084c49897059670a1324b..589b437e7c97c846410f293e2f014bdcd7cb333e 100644 --- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java @@ -52,7 +52,7 @@ public class NetherPortalBlock extends Block { @Override public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { - if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(2000) < world.getDifficulty().getId()) { // Spigot + if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(world.purpurConfig.piglinPortalSpawnModifier) < world.getDifficulty().getId()) { // Spigot // Purpur while (world.getBlockState(pos).is((Block) this)) { pos = pos.below(); } @@ -84,6 +84,14 @@ public class NetherPortalBlock extends Block { public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper if (entity.canChangeDimensions()) { + // Purpur start + if (entity.isPassenger() || entity.isVehicle()) { + if (new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.NETHER_PORTAL).callEvent()) { + this.entityInside(state, world, pos, entity); + } + return; + } + // Purpur end // CraftBukkit start - Entity in portal EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); world.getCraftServer().getPluginManager().callEvent(event); diff --git a/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java b/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java index e55720c4d2fbdf6aae526910e87a67c29cf906fd..4310d62c45c776eea81987809309da08c242c7b0 100644 --- a/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java +++ b/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java @@ -14,7 +14,7 @@ import net.minecraft.world.level.block.state.properties.IntegerProperty; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; -public class NetherWartBlock extends BushBlock { +public class NetherWartBlock extends BushBlock implements BonemealableBlock { // Purpur public static final int MAX_AGE = 3; public static final IntegerProperty AGE = BlockStateProperties.AGE_3; @@ -60,4 +60,32 @@ public class NetherWartBlock extends BushBlock { protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(NetherWartBlock.AGE); } + + // Purpur start + @Override + public void playerDestroy(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops) { + 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); + } + } + + @Override + public boolean isValidBonemealTarget(net.minecraft.world.level.LevelReader world, BlockPos pos, BlockState state, boolean isClient) { + return ((net.minecraft.world.level.Level) world).purpurConfig.netherWartAffectedByBonemeal && state.getValue(NetherWartBlock.AGE) < 3; + } + + @Override + public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + int i = Math.min(3, state.getValue(NetherWartBlock.AGE) + 1); + state = state.setValue(NetherWartBlock.AGE, i); + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state, 2); // CraftBukkit + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java index e46d84750bdd7c940f400efda226e12a3fdc3848..6343cd0c33cafb30225cfae17ea1cf15859073b1 100644 --- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java +++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java @@ -87,7 +87,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 7b45d6b9a005036ca5051d089a7be792eb87012f..8806c97ecc6bdd8a64c2d82bb2f58f46ac37c468 100644 --- a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java @@ -64,6 +64,7 @@ public class ObserverBlock extends DirectionalBlock { @Override public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { if (state.getValue(ObserverBlock.FACING) == direction && !(Boolean) state.getValue(ObserverBlock.POWERED)) { + if (!world.getMinecraftWorld().purpurConfig.disableObserverClocks || !(neighborState.getBlock() instanceof ObserverBlock) || neighborState.getValue(ObserverBlock.FACING).getOpposite() != direction) // Purpur this.startSignal(world, pos); } diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java index cd943997f11f5ea5c600fdc6db96043fb0fa713c..4adeda49a2e422e11f885bffb311653d99159bf4 100644 --- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java @@ -186,7 +186,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); @@ -195,13 +195,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 7e04ecba2a14be0f0d47c917368abd2a2bd64a05..5c944e39e611201c6ae4dad14511a54c451204f6 100644 --- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java +++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java @@ -72,7 +72,7 @@ public class PowderSnowBlock extends Block implements BucketPickup { if (!world.isClientSide) { // CraftBukkit start if (entity.isOnFire() && entity.mayInteract(world, pos)) { - if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) { + if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !((world.purpurConfig.powderSnowBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) || entity instanceof Player))) { return; } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java index 7fddb6fa8fd30ef88346a59f7867aae792f13772..40893e71fe8447b695350273bef9623bd5accdcd 100644 --- a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java +++ b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java @@ -23,7 +23,7 @@ public class PoweredRailBlock extends BaseRailBlock { } protected boolean findPoweredRailSignal(Level world, BlockPos pos, BlockState state, boolean flag, int distance) { - if (distance >= 8) { + if (distance >= world.purpurConfig.railActivationRange) { // Purpur return false; } else { int j = pos.getX(); diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java index 2ed78cf83c0ae66a6ddba1ff307da89a24b0d0a8..ae17d6a54fad0bd2d71d306f418b5ced2f11b863 100644 --- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java +++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java @@ -141,7 +141,7 @@ public class RespawnAnchorBlock extends Block { }; Vec3 vec3d = explodedPos.getCenter(); - world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // Paper + if (world.purpurConfig.respawnAnchorExplode)world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), explosiondamagecalculator, vec3d, (float) world.purpurConfig.respawnAnchorExplosionPower, world.purpurConfig.respawnAnchorExplosionFire, world.purpurConfig.respawnAnchorExplosionEffect); // Paper // Purpur } public static boolean canSetSpawn(Level world) { diff --git a/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java b/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java index 02d01eabb9606ae8c3b76ad9fa4bb9a525e247b1..ce51fec4a874f9466f9966684c535315dbf40b9e 100644 --- a/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java @@ -130,7 +130,7 @@ public class SculkShriekerBlock extends BaseEntityBlock implements SimpleWaterlo @Nullable @Override public BlockState getStateForPlacement(BlockPlaceContext ctx) { - return (BlockState) this.defaultBlockState().setValue(SculkShriekerBlock.WATERLOGGED, ctx.getLevel().getFluidState(ctx.getClickedPos()).getType() == Fluids.WATER); + return (BlockState) this.defaultBlockState().setValue(SculkShriekerBlock.WATERLOGGED, ctx.getLevel().getFluidState(ctx.getClickedPos()).getType() == Fluids.WATER).setValue(SculkShriekerBlock.CAN_SUMMON, ctx.getLevel().purpurConfig.sculkShriekerCanSummonDefault); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java b/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java index b51155ad12515b2d0dd0f202580b9f455c114d9a..dd6c82a418ee299d7a5614cb0260949c198b4149 100644 --- a/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java @@ -137,7 +137,7 @@ public class ShulkerBoxBlock extends BaseEntityBlock { public void playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) { BlockEntity blockEntity = world.getBlockEntity(pos); if (blockEntity instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity) { - if (!world.isClientSide && player.isCreative() && !shulkerBoxBlockEntity.isEmpty()) { + if (world.purpurConfig.shulkerBoxAllowOversizedStacks || (!world.isClientSide && player.isCreative() && !shulkerBoxBlockEntity.isEmpty())) { // Purpur ItemStack itemStack = getColoredItemStack(this.getColor()); blockEntity.saveToItem(itemStack); if (shulkerBoxBlockEntity.hasCustomName()) { diff --git a/src/main/java/net/minecraft/world/level/block/SlabBlock.java b/src/main/java/net/minecraft/world/level/block/SlabBlock.java index 18b603d646081926343dea108b55d641df1c2c34..370772b1297b78bcc7419684015830a87c4d9a17 100644 --- a/src/main/java/net/minecraft/world/level/block/SlabBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SlabBlock.java @@ -130,4 +130,25 @@ public class SlabBlock extends Block implements SimpleWaterloggedBlock { return false; } } + + // Purpur start + public boolean halfBreak(BlockState state, BlockPos pos, net.minecraft.server.level.ServerPlayer player) { + if (state.getValue(SlabBlock.TYPE) != SlabType.DOUBLE) { + return false; + } + net.minecraft.world.phys.HitResult result = player.getRayTrace(16, net.minecraft.world.level.ClipContext.Fluid.NONE); + if (result.getType() != net.minecraft.world.phys.HitResult.Type.BLOCK) { + return false; + } + double hitY = result.getLocation().y(); + int blockY = org.bukkit.util.NumberConversions.floor(hitY); + player.level().setBlock(pos, state.setValue(SlabBlock.TYPE, (hitY - blockY > 0.5 || blockY - pos.getY() == 1) ? SlabType.BOTTOM : SlabType.TOP), 3); + if (!player.getAbilities().instabuild) { + net.minecraft.world.entity.item.ItemEntity item = new net.minecraft.world.entity.item.ItemEntity(player.level(), pos.getX(), pos.getY(), pos.getZ(), new ItemStack(asItem())); + item.setDefaultPickUpDelay(); + player.level().addFreshEntity(item); + } + return true; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java index 14e00c7feb1c051d56a3d27cd00dcef072dd771a..4952fb1aaaafb55baa0fddb389f966a120a4786c 100644 --- a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java @@ -81,6 +81,12 @@ public class SnowLayerBlock extends Block { public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { BlockState iblockdata1 = world.getBlockState(pos.below()); + // Purpur start + if (iblockdata1.is(Blocks.BLUE_ICE) && !world.getWorldBorder().world.purpurConfig.snowOnBlueIce) { + return false; + } + // Purpur end + return iblockdata1.is(BlockTags.SNOW_LAYER_CANNOT_SURVIVE_ON) ? false : (iblockdata1.is(BlockTags.SNOW_LAYER_CAN_SURVIVE_ON) ? true : Block.isFaceFull(iblockdata1.getCollisionShape(world, pos.below()), Direction.UP) || iblockdata1.is((Block) this) && (Integer) iblockdata1.getValue(SnowLayerBlock.LAYERS) == 8); } diff --git a/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java b/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java index 936d844a5a246138c9f9ae4ae6e318242b8f1420..93f5f226cf6fd6110e4daa02b3f5d9ad253814a0 100644 --- a/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java @@ -40,6 +40,58 @@ public class SpawnerBlock extends BaseEntityBlock { return createTickerHelper(type, BlockEntityType.MOB_SPAWNER, world.isClientSide ? SpawnerBlockEntity::clientTick : SpawnerBlockEntity::serverTick); } + // Purpur start + @Override + public void playerDestroy(Level level, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, BlockEntity blockEntity, ItemStack stack, boolean includeDrops) { + if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.drop.spawners") && isSilkTouch(level, stack)) { + Optional> type = net.minecraft.world.entity.EntityType.by(((SpawnerBlockEntity) blockEntity).getSpawner().nextSpawnData.getEntityToSpawn()); + + net.minecraft.world.entity.EntityType entityType = type.orElse(null); + final net.kyori.adventure.text.Component mobName = io.papermc.paper.adventure.PaperAdventure.asAdventure(entityType == null ? Component.empty() : entityType.getDescription()); + CompoundTag display = new CompoundTag(); + CompoundTag tag = new CompoundTag(); + + String name = level.purpurConfig.silkTouchSpawnerName; + if (name != null && !name.isEmpty() && !name.equals("Monster Spawner")) { + net.kyori.adventure.text.Component displayName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName)); + if (name.startsWith("")) { + displayName = displayName.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); + } + display.put("Name", net.minecraft.nbt.StringTag.valueOf(io.papermc.paper.adventure.PaperAdventure.asJsonString(displayName, java.util.Locale.ROOT))); + tag.put("display", display); + } + + List lore = level.purpurConfig.silkTouchSpawnerLore; + if (lore != null && !lore.isEmpty()) { + net.minecraft.nbt.ListTag list = new net.minecraft.nbt.ListTag(); + for (String line : lore) { + net.kyori.adventure.text.Component lineComponent = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(line, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName)); + if (line.startsWith("")) { + lineComponent = lineComponent.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); + } + list.add(net.minecraft.nbt.StringTag.valueOf(io.papermc.paper.adventure.PaperAdventure.asJsonString(lineComponent, java.util.Locale.ROOT))); + } + display.put("Lore", list); + tag.put("display", display); + } + + ItemStack item = new ItemStack(Blocks.SPAWNER.asItem()); + if (entityType != null) { + tag.putString("Purpur.mob_type", entityType.getName()); + tag.putDouble("HideFlags", 32); // hides the "Interact with Spawn Egg" tooltip + item.setTag(tag); + } + + popResource(level, pos, item); + } + super.playerDestroy(level, player, pos, state, blockEntity, stack, includeDrops); + } + + private boolean isSilkTouch(Level level, ItemStack stack) { + return stack != null && level.purpurConfig.silkTouchTools.contains(stack.getItem()) && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.SILK_TOUCH, stack) >= level.purpurConfig.minimumSilkTouchSpawnerRequire; + } + // Purpur end + @Override public void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { super.spawnAfterBreak(state, world, pos, tool, dropExperience); @@ -48,6 +100,7 @@ public class SpawnerBlock extends BaseEntityBlock { @Override public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) { + if (isSilkTouch(worldserver, itemstack)) return 0; // Purpur if (flag) { int i = 15 + worldserver.random.nextInt(15) + worldserver.random.nextInt(15); diff --git a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java index 4bce895268542531598a01a1bccd8ac1ed703b7d..2276fed1feb4fea59b5bd49b5e4586d49478b3cc 100644 --- a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java @@ -48,7 +48,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 Direction[] aenumdirection = SpongeBlock.ALL_DIRECTIONS; int i = aenumdirection.length; @@ -67,7 +67,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 false; } else { Block block = iblockdata.getBlock(); @@ -82,6 +82,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 false; diff --git a/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java b/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java index 0a95842c53a9d0286c57bcb42db97e468e30fb7d..0882e67c5cf876e0fc58a4ca4accb4be40418983 100644 --- a/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java +++ b/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java @@ -92,4 +92,16 @@ public class StonecutterBlock extends Block { public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { return false; } + + // Purpur start + @Override + public void stepOn(Level level, BlockPos pos, BlockState state, net.minecraft.world.entity.Entity entity) { + if (level.purpurConfig.stonecutterDamage > 0.0F && entity instanceof net.minecraft.world.entity.LivingEntity) { + org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); + entity.hurt(entity.damageSources().stonecutter(), level.purpurConfig.stonecutterDamage); + org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; + } + super.stepOn(level, pos, state, entity); + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java index c3f500580d257e1397f2eb7c47b063a6fe6bb405..0d5c6bdfd4aeda472804b493315bf21ac3067e9d 100644 --- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java @@ -19,7 +19,7 @@ import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; -public class SugarCaneBlock extends Block { +public class SugarCaneBlock extends Block implements BonemealableBlock { // Purpur public static final IntegerProperty AGE = BlockStateProperties.AGE_15; protected static final float AABB_OFFSET = 6.0F; @@ -106,4 +106,34 @@ public class SugarCaneBlock extends Block { protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(SugarCaneBlock.AGE); } + + // Purpur start + @Override + public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, BlockState state, boolean isClient) { + if (!((net.minecraft.world.level.Level) world).purpurConfig.sugarCanAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false; + + int reedHeight = 0; + while (world.getBlockState(pos.below(reedHeight)).is(this)) { + reedHeight++; + } + + return reedHeight < ((net.minecraft.world.level.Level) world).paperConfig().maxGrowthHeight.reeds; + } + + @Override + public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + int reedHeight = 0; + while (world.getBlockState(pos.below(reedHeight)).is(this)) { + reedHeight++; + } + for (int i = 0; i <= world.paperConfig().maxGrowthHeight.reeds - reedHeight; i++) { + world.setBlockAndUpdate(pos.above(i), state.setValue(SugarCaneBlock.AGE, 0)); + } + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java index 181e99e3f29c70568b6f28f8a5d3bb692f7abad8..58facc048840d3ba0a58c92673340e6e0aa4e305 100644 --- a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java +++ b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java @@ -160,7 +160,7 @@ public class TurtleEggBlock extends Block { private boolean shouldUpdateHatchLevel(Level world) { float f = world.getTimeOfDay(1.0F); - return (double) f < 0.69D && (double) f > 0.65D ? true : world.random.nextInt(500) == 0; + return (double) f < 0.69D && (double) f > 0.65D ? true : world.random.nextInt(world.purpurConfig.turtleEggsRandomTickCrackChance) == 0; } @Override @@ -193,6 +193,31 @@ public class TurtleEggBlock extends Block { } private boolean canDestroyEgg(Level world, Entity entity) { - return !(entity instanceof Turtle) && !(entity instanceof Bat) ? (!(entity instanceof LivingEntity) ? false : entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) : false; + // Purpur start + if (entity instanceof Turtle || entity instanceof Bat) { + return false; + } + if (world.purpurConfig.turtleEggsBreakFromExpOrbs && entity instanceof net.minecraft.world.entity.ExperienceOrb) { + return true; + } + if (world.purpurConfig.turtleEggsBreakFromItems && entity instanceof net.minecraft.world.entity.item.ItemEntity) { + return true; + } + if (world.purpurConfig.turtleEggsBreakFromMinecarts && entity instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) { + return true; + } + if (!(entity instanceof LivingEntity)) { + return false; + } + if (world.purpurConfig.turtleEggsTramplingFeatherFalling) { + java.util.Iterator armor = entity.getArmorSlots().iterator(); + return !armor.hasNext() || net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FALL_PROTECTION, armor.next()) < (int) entity.fallDistance; + } + if (entity instanceof Player) { + return true; + } + + return world.purpurConfig.turtleEggsBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + // Purpur end } } diff --git a/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java b/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java index 6866605c7ef5361b21130a19a59c3fa3660dfb19..dee5d76d29da13f8639ab5d392cd0143201e71ba 100644 --- a/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java +++ b/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java @@ -27,4 +27,11 @@ public class TwistingVinesBlock extends GrowingPlantHeadBlock { protected boolean canGrowInto(BlockState state) { return NetherVines.isValidGrowthState(state); } + + // Purpur start + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.twistingVinesMaxGrowthAge; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java index e5c135ec059746b75fe58516809584221285cdbe..713c7e6e31a3e1097b612c77a4fce147c9252e0b 100644 --- a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java +++ b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java @@ -27,4 +27,11 @@ public class WeepingVinesBlock extends GrowingPlantHeadBlock { protected boolean canGrowInto(BlockState state) { return NetherVines.isValidGrowthState(state); } + + // Purpur start + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.weepingVinesMaxGrowthAge; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java index 1aa0e921890d600c9274deb923da04e72b12bcc6..44bd7bee2665a05878fd2df935a700f02cd13a75 100644 --- a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java +++ b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java @@ -69,6 +69,7 @@ public class WitherSkullBlock extends SkullBlock { entitywither.moveTo((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.55D, (double) blockposition1.getZ() + 0.5D, shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F, 0.0F); entitywither.yBodyRot = shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F; entitywither.makeInvulnerable(); + entitywither.setSummoner(iblockdata.getBlock().placer == null ? null : iblockdata.getBlock().placer.getUUID()); // Purpur // CraftBukkit start if (!world.addFreshEntity(entitywither, SpawnReason.BUILD_WITHER)) { return; diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java index 448fa4f4f200430d6ce3051763c7ceb697696146..997d0fab71eacc6466ffe3bc8f6349e5813d6d49 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 @@ -44,6 +44,7 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.AbstractFurnaceBlock; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.Vec3; // CraftBukkit start import org.bukkit.craftbukkit.block.CraftBlock; @@ -207,6 +208,22 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit // Paper end } + // Purpur start + public static void addFuel(ItemStack itemStack, Integer burnTime) { + Map map = Maps.newLinkedHashMap(); + map.putAll(getFuel()); + map.put(itemStack.getItem(), burnTime); + cachedBurnDurations = com.google.common.collect.ImmutableMap.copyOf(map); + } + + public static void removeFuel(ItemStack itemStack) { + Map map = Maps.newLinkedHashMap(); + map.putAll(getFuel()); + map.remove(itemStack.getItem()); + cachedBurnDurations = com.google.common.collect.ImmutableMap.copyOf(map); + } + // Purpur End + // CraftBukkit start - add fields and methods private int maxStack = MAX_STACK; public List transaction = new java.util.ArrayList(); @@ -324,6 +341,21 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit } ItemStack itemstack = (ItemStack) blockEntity.items.get(1); + // Purpur start + boolean usedLavaFromUnderneath = false; + if (world.purpurConfig.furnaceUseLavaFromUnderneath && !blockEntity.isLit() && itemstack.isEmpty() && !blockEntity.items.get(0).isEmpty() && world.getGameTime() % 20 == 0) { + BlockPos below = blockEntity.getBlockPos().below(); + BlockState belowState = world.getBlockStateIfLoaded(below); + if (belowState != null && belowState.is(Blocks.LAVA)) { + FluidState fluidState = belowState.getFluidState(); + if (fluidState != null && fluidState.isSource()) { + world.setBlock(below, Blocks.AIR.defaultBlockState(), 3); + itemstack = Items.LAVA_BUCKET.getDefaultInstance(); + usedLavaFromUnderneath = true; + } + } + } + // Purpur end boolean flag2 = !((ItemStack) blockEntity.items.get(0)).isEmpty(); boolean flag3 = !itemstack.isEmpty(); @@ -409,6 +441,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit setChanged(world, pos, state); } + if (usedLavaFromUnderneath) blockEntity.items.set(1, ItemStack.EMPTY); // Purpur } private static boolean canBurn(RegistryAccess registryManager, @Nullable Recipe recipe, NonNullList slots, int count) { diff --git a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java index 416aa989ebb18a8741cc9d605a1180ab830f6643..e38a0adf5463c48311ad08b8d2e5b5c2d989a3b5 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java @@ -67,7 +67,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { public BarrelBlockEntity(BlockPos pos, BlockState state) { super(BlockEntityType.BARREL, pos, state); - this.items = NonNullList.withSize(27, ItemStack.EMPTY); + // Purpur start + this.items = NonNullList.withSize(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { + case 6 -> 54; + case 5 -> 45; + case 4 -> 36; + case 2 -> 18; + case 1 -> 9; + default -> 27; + }, ItemStack.EMPTY); + // Purpur end this.openersCounter = new ContainerOpenersCounter() { @Override protected void onOpen(Level world, BlockPos pos, BlockState state) { @@ -118,7 +127,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { @Override public int getContainerSize() { - return 27; + // Purpur start + return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { + case 6 -> 54; + case 5 -> 45; + case 4 -> 36; + case 2 -> 18; + case 1 -> 9; + default -> 27; + }; + // Purpur end } @Override @@ -138,7 +156,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { @Override protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { - return ChestMenu.threeRows(syncId, playerInventory, this); + // Purpur start + return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { + case 6 -> ChestMenu.sixRows(syncId, playerInventory, this); + case 5 -> ChestMenu.fiveRows(syncId, playerInventory, this); + case 4 -> ChestMenu.fourRows(syncId, playerInventory, this); + case 2 -> ChestMenu.twoRows(syncId, playerInventory, this); + case 1 -> ChestMenu.oneRow(syncId, playerInventory, this); + default -> ChestMenu.threeRows(syncId, playerInventory, this); + }; + // Purpur end } @Override diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java index 5c951ef93fe4cf4f085df86b0cefc02bc2610ab9..2979c1eceaac79a82da19eba5d8fb1db6537ff45 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java @@ -84,6 +84,16 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name public double getEffectRange() { if (this.effectRange < 0) { + // Purpur Start + if (this.level != null) { + switch (this.levels) { + case 1: return this.level.purpurConfig.beaconLevelOne; + case 2: return this.level.purpurConfig.beaconLevelTwo; + case 3: return this.level.purpurConfig.beaconLevelThree; + case 4: return this.level.purpurConfig.beaconLevelFour; + } + } + // Purpur End return this.levels * 10 + 10; } else { return effectRange; @@ -155,6 +165,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name int j = pos.getY(); int k = pos.getZ(); BlockPos blockposition1; + boolean isTintedGlass = false; if (blockEntity.lastCheckY < j) { blockposition1 = pos; @@ -188,6 +199,9 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name } } } else { + if (world.purpurConfig.beaconAllowEffectsWithTintedGlass && block.equals(Blocks.TINTED_GLASS)) { + isTintedGlass = true; + } if (tileentitybeacon_beaconcolortracker == null || iblockdata1.getLightBlock(world, blockposition1) >= 15 && !iblockdata1.is(Blocks.BEDROCK)) { blockEntity.checkingBeamSections.clear(); blockEntity.lastCheckY = l; @@ -207,7 +221,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name blockEntity.levels = BeaconBlockEntity.updateBase(world, i, j, k); } - if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) { + if (blockEntity.levels > 0 && (!blockEntity.beamSections.isEmpty() || (world.purpurConfig.beaconAllowEffectsWithTintedGlass && isTintedGlass))) { BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); } diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java index 41c9f074203915c31c1ae7a160ce509c13383f84..5344393f62af19c3591f54a6ebc40b2e4c3b6226 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java @@ -43,7 +43,7 @@ public class BeehiveBlockEntity extends BlockEntity { private final List stored = Lists.newArrayList(); @Nullable public BlockPos savedFlowerPos; - public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold + public int maxBees = org.purpurmc.purpur.PurpurConfig.beeInsideBeeHive; // CraftBukkit - allow setting max amount of bees a hive can hold // Purpur public BeehiveBlockEntity(BlockPos pos, BlockState state) { super(BlockEntityType.BEEHIVE, pos, state); @@ -130,6 +130,36 @@ public class BeehiveBlockEntity extends BlockEntity { return list; } + // Purpur start + public List releaseBees(BlockState iblockdata, Level level, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, boolean force) { + List list = Lists.newArrayList(); + + this.stored.removeIf((tileentitybeehive_hivebee) -> { + return BeehiveBlockEntity.releaseBee(level, this.worldPosition, iblockdata, tileentitybeehive_hivebee, list, tileentitybeehive_releasestatus, this.savedFlowerPos, force); + }); + + if (!list.isEmpty()) { + super.setChanged(); + } + + return list; + } + + public List releaseBee(BlockState iblockdata, Level level, BeeData data, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, boolean force) { + List list = Lists.newArrayList(); + + BeehiveBlockEntity.releaseBee(level, this.worldPosition, iblockdata, data, list, tileentitybeehive_releasestatus, this.savedFlowerPos, force); + + if (!list.isEmpty()) { + stored.remove(data); + + super.setChanged(); + } + + return list; + } + // Purpur end + public void addOccupant(Entity entity, boolean hasNectar) { this.addOccupantWithPresetTicks(entity, hasNectar, 0); } @@ -139,6 +169,12 @@ public class BeehiveBlockEntity extends BlockEntity { return this.stored.size(); } + // Purpur start + public List getStored() { + return stored; + } + // Purpur end + // Paper start - Add EntityBlockStorage clearEntities public void clearBees() { this.stored.clear(); @@ -203,7 +239,7 @@ public class BeehiveBlockEntity extends BlockEntity { } private static boolean releaseBee(Level world, BlockPos blockposition, BlockState iblockdata, BeehiveBlockEntity.BeeData tileentitybeehive_hivebee, @Nullable List list, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, @Nullable BlockPos blockposition1, boolean force) { - if (!force && (world.isNight() || world.isRaining()) && tileentitybeehive_releasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) { + if (!force && ((world.isNight() && !world.purpurConfig.beeCanWorkAtNight) || (world.isRaining() && !world.purpurConfig.beeCanWorkInRain)) && tileentitybeehive_releasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) { // Purpur // CraftBukkit end return false; } else { @@ -425,9 +461,9 @@ public class BeehiveBlockEntity extends BlockEntity { private BeeReleaseStatus() {} } - private static class BeeData { + public static class BeeData { // Purpur - change from private to public - final CompoundTag entityData; + public final CompoundTag entityData; // Purpur - make public int ticksInHive; int exitTickCounter; // Paper - separate counter for checking if bee should exit to reduce exit attempts final int minOccupationTicks; 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 370a25d2deb54f10a35ee24d9e7e92fbfde60edf..3431f1a00ae2918b91a6b7a449e613e6e12ff6d4 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java @@ -6,6 +6,8 @@ import net.minecraft.CrashReportCategory; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.resources.ResourceLocation; @@ -73,10 +75,27 @@ public abstract class BlockEntity { if (persistentDataTag instanceof CompoundTag) { this.persistentDataContainer.putAll((CompoundTag) persistentDataTag); } + // Purpur start + if (nbt.contains("Purpur.persistentDisplayName")) { + this.persistentDisplayName = nbt.getString("Purpur.persistentDisplayName"); + } + if (nbt.contains("Purpur.persistentLore")) { + this.persistentLore = nbt.getList("Purpur.persistentLore", 8); + } + // Purpur end } // CraftBukkit end - protected void saveAdditional(CompoundTag nbt) {} + protected void saveAdditional(CompoundTag nbt) { + // Purpur start + if (this.persistentDisplayName != null) { + nbt.put("Purpur.persistentDisplayName", StringTag.valueOf(this.persistentDisplayName)); + } + if (this.persistentLore != null) { + nbt.put("Purpur.persistentLore", this.persistentLore); + } + // Purpur end + } public final CompoundTag saveWithFullMetadata() { CompoundTag nbttagcompound = this.saveWithoutMetadata(); @@ -186,10 +205,24 @@ public abstract class BlockEntity { @Nullable public Packet getUpdatePacket() { + // Purpur start + if (this instanceof net.minecraft.world.Nameable nameable && nameable.hasCustomName()) { + CompoundTag nbt = this.saveWithoutMetadata(); + nbt.remove("Items"); + return net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket.create(this, $ -> nbt); + } + // Purpur end return null; } public CompoundTag getUpdateTag() { + // Purpur start + if (this instanceof net.minecraft.world.Nameable nameable && nameable.hasCustomName()) { + CompoundTag nbt = this.saveWithoutMetadata(); + nbt.remove("Items"); + return nbt; + } + // Purpur end return new CompoundTag(); } @@ -263,4 +296,24 @@ public abstract class BlockEntity { } // Paper end + // Purpur start + private String persistentDisplayName = null; + private ListTag persistentLore = null; + + public void setPersistentDisplayName(String json) { + this.persistentDisplayName = json; + } + + public void setPersistentLore(ListTag lore) { + this.persistentLore = lore; + } + + public String getPersistentDisplayName() { + return this.persistentDisplayName; + } + + public ListTag getPersistentLore() { + return this.persistentLore; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java index 963a596154091b79ca139af6274aa323518ad1ad..4dcac3899a500d8586580bcfd5b4516e1dcdcd4a 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java @@ -171,7 +171,7 @@ public class ConduitBlockEntity extends BlockEntity { if ((l > 1 || i1 > 1 || j1 > 1) && (i == 0 && (i1 == 2 || j1 == 2) || j == 0 && (l == 2 || j1 == 2) || k == 0 && (l == 2 || i1 == 2))) { BlockPos blockposition2 = pos.offset(i, j, k); BlockState iblockdata = world.getBlockState(blockposition2); - Block[] ablock = ConduitBlockEntity.VALID_BLOCKS; + Block[] ablock = world.purpurConfig.conduitBlocks; // Purpur int k1 = ablock.length; for (int l1 = 0; l1 < k1; ++l1) { @@ -191,7 +191,7 @@ public class ConduitBlockEntity extends BlockEntity { private static void applyEffects(Level world, BlockPos pos, List activatingBlocks) { int i = activatingBlocks.size(); - int j = i / 7 * 16; + int j = i / 7 * world.purpurConfig.conduitDistance; // Purpur int k = pos.getX(); int l = pos.getY(); int i1 = pos.getZ(); @@ -222,21 +222,21 @@ public class ConduitBlockEntity extends BlockEntity { blockEntity.destroyTarget = ConduitBlockEntity.findDestroyTarget(world, pos, blockEntity.destroyTargetUUID); blockEntity.destroyTargetUUID = null; } else if (blockEntity.destroyTarget == null) { - List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos), (entityliving1) -> { + List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos, world), (entityliving1) -> { // Purpur return entityliving1 instanceof Enemy && entityliving1.isInWaterOrRain(); }); if (!list1.isEmpty()) { blockEntity.destroyTarget = (LivingEntity) list1.get(world.random.nextInt(list1.size())); } - } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), 8.0D)) { + } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), world.purpurConfig.conduitDamageDistance)) { // Purpur blockEntity.destroyTarget = null; } if (blockEntity.destroyTarget != null) { // CraftBukkit start CraftEventFactory.blockDamage = CraftBlock.at(world, pos); - if (blockEntity.destroyTarget.hurt(world.damageSources().magic(), 4.0F)) { + if (blockEntity.destroyTarget.hurt(world.damageSources().magic(), world.purpurConfig.conduitDamageAmount)) { // Purpur world.playSound((Player) null, blockEntity.destroyTarget.getX(), blockEntity.destroyTarget.getY(), blockEntity.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F); } CraftEventFactory.blockDamage = null; @@ -262,16 +262,22 @@ public class ConduitBlockEntity extends BlockEntity { } private static AABB getDestroyRangeAABB(BlockPos pos) { + // Purpur start + return getDestroyRangeAABB(pos, null); + } + + private static AABB getDestroyRangeAABB(BlockPos pos, Level level) { + // Purpur end int i = pos.getX(); int j = pos.getY(); int k = pos.getZ(); - return (new AABB((double) i, (double) j, (double) k, (double) (i + 1), (double) (j + 1), (double) (k + 1))).inflate(8.0D); + return (new AABB((double) i, (double) j, (double) k, (double) (i + 1), (double) (j + 1), (double) (k + 1))).inflate(level == null ? 8.0D : level.purpurConfig.conduitDamageDistance); // Purpur } @Nullable private static LivingEntity findDestroyTarget(Level world, BlockPos pos, UUID uuid) { - List list = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos), (entityliving) -> { + List list = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos, world), (entityliving) -> { // Purpur return entityliving.getUUID().equals(uuid); }); diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java index 65e1381bb2d10bd212463feb602c60f8fdb9ade1..b7370e64fd0d50e8725d7d5afc30af2e8bc8455d 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java @@ -24,6 +24,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable public float tRot; private static final RandomSource RANDOM = RandomSource.create(); private Component name; + private int lapis = 0; // Purpur public EnchantmentTableBlockEntity(BlockPos pos, BlockState state) { super(BlockEntityType.ENCHANTING_TABLE, pos, state); @@ -35,6 +36,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable if (this.hasCustomName()) { nbt.putString("CustomName", Component.Serializer.toJson(this.name)); } + nbt.putInt("Purpur.Lapis", this.lapis); // Purpur } @@ -44,6 +46,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable if (nbt.contains("CustomName", 8)) { this.name = io.papermc.paper.util.MCUtil.getBaseComponentFromNbt("CustomName", nbt); // Paper - Catch ParseException } + this.lapis = nbt.getInt("Purpur.Lapis"); // Purpur } @@ -117,4 +120,14 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable public Component getCustomName() { return this.name; } + + // Purpur start + public int getLapis() { + return this.lapis; + } + + public void setLapis(int lapis) { + this.lapis = lapis; + } + // Purpur } diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java index 38cde466714e5663cd416b6afd5d2558e139ec09..2d625f18f2ba42ee5a1ebeea78ca395ad6f88b37 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 @@ -202,16 +202,31 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C 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.SharedConstants.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only + signtext = signtext.setMessage(i, translateColors(player, net.minecraft.SharedConstants.filterText(filteredtext.filteredOrEmpty()), chatmodifier)); // Paper - filter sign text to chat only // Purpur } else { - signtext = signtext.setMessage(i, Component.literal(net.minecraft.SharedConstants.filterText(filteredtext.raw())).setStyle(chatmodifier), Component.literal(net.minecraft.SharedConstants.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only + signtext = signtext.setMessage(i, translateColors(player, net.minecraft.SharedConstants.filterText(filteredtext.raw()), chatmodifier), translateColors(player, net.minecraft.SharedConstants.filterText(filteredtext.filteredOrEmpty()), chatmodifier)); // Paper - filter sign text to chat only // Purpur } } @@ -351,6 +366,28 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C return ClientboundBlockEntityDataPacket.create(this); } + // Purpur start + public ClientboundBlockEntityDataPacket getTranslatedUpdatePacket(boolean filtered, boolean front) { + final CompoundTag nbt = new CompoundTag(); + this.saveAdditional(nbt); + 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, entity -> nbt); + } + // Purpur end + @Override public CompoundTag getUpdateTag() { return this.saveWithoutMetadata(); diff --git a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java index 1ec80f9c901dff1c9f29befa5a8e3c3f6f37aaf7..7291e4056b8e46ab59b71818388ac55fbb12993f 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java @@ -177,6 +177,15 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { public static void teleportEntity(Level world, BlockPos pos, BlockState state, Entity entity, TheEndGatewayBlockEntity blockEntity) { if (world instanceof ServerLevel && !blockEntity.isCoolingDown()) { + if (!entity.canChangeDimensions()) return; // Purpur + // 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()) { + teleportEntity(world, pos, state, entity, blockEntity); + } + return; + } + // Purpur end ServerLevel worldserver = (ServerLevel) world; blockEntity.teleportCooldown = 100; diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java b/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java index 744d91546d1a810f60a43c15ed74b4158f341a4a..354538daefa603f6df5a139b6bff87dbb4cef178 100644 --- a/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java +++ b/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java @@ -86,7 +86,7 @@ public class PistonStructureResolver { return true; } else { int i = 1; - if (i + this.toPush.size() > 12) { + if (i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur return false; } else { while(isSticky(blockState)) { @@ -98,7 +98,7 @@ public class PistonStructureResolver { } ++i; - if (i + this.toPush.size() > 12) { + if (i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur return false; } } @@ -142,7 +142,7 @@ public class PistonStructureResolver { return true; } - if (this.toPush.size() >= 12) { + if (this.toPush.size() >= this.level.purpurConfig.pistonBlockPushLimit) { // Purpur return false; } diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java index de4c1e4701236e7d5ec77339c51ad6a9d8288bb6..942ce713afe27ec75d849877a88721ef6334fafa 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 @@ -81,7 +81,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; @@ -89,7 +89,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 @Nullable protected ResourceLocation drops; 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 3eeb1f0eac76efe9b7c24f6d5787018c7842d07a..c17b04880b4032984b43dd4fbe1c08f96112abac 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -125,7 +125,7 @@ public class LevelChunk extends ChunkAccess { this.blockTicks = blockTickScheduler; this.fluidTicks = fluidTickScheduler; - this.lightningTick = this.level.getThreadUnsafeRandom().nextInt(100000) << 1; // Pufferfish - initialize lightning tick + this.lightningTick = java.util.concurrent.ThreadLocalRandom.current().nextInt(100000) << 1; // Pufferfish - initialize lightning tick // Purpur - any random will do } // CraftBukkit start @@ -552,11 +552,11 @@ public class LevelChunk extends ChunkAccess { if (LightEngine.hasDifferentLightProperties(this, blockposition, iblockdata1, iblockdata)) { ProfilerFiller gameprofilerfiller = this.level.getProfiler(); - gameprofilerfiller.push("updateSkyLightSources"); + //gameprofilerfiller.push("updateSkyLightSources"); // Purpur this.skyLightSources.update(this, j, i, l); - gameprofilerfiller.popPush("queueCheckLight"); + //gameprofilerfiller.popPush("queueCheckLight"); // Purpur this.level.getChunkSource().getLightEngine().checkBlock(blockposition); - gameprofilerfiller.pop(); + //gameprofilerfiller.pop(); // Purpur } boolean flag3 = iblockdata1.hasBlockEntity(); @@ -895,7 +895,7 @@ public class LevelChunk extends ChunkAccess { this.chunkHolder.getEntityChunk().callEntitiesLoadEvent(); // Paper - rewrite chunk system if (this.needsDecoration) { - try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper + //try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper // Purpur this.needsDecoration = false; java.util.Random random = new java.util.Random(); random.setSeed(this.level.getSeed()); @@ -915,7 +915,7 @@ public class LevelChunk extends ChunkAccess { } } server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk)); - } // Paper + //} // Paper // Purpur } } } @@ -1270,10 +1270,10 @@ public class LevelChunk extends ChunkAccess { if (LevelChunk.this.isTicking(blockposition)) { try { - ProfilerFiller gameprofilerfiller = LevelChunk.this.level.getProfiler(); + //ProfilerFiller gameprofilerfiller = LevelChunk.this.level.getProfiler(); // Purpur - gameprofilerfiller.push(this::getType); - this.blockEntity.tickTimer.startTiming(); // Spigot + //gameprofilerfiller.push(this::getType); // Purpur + //this.blockEntity.tickTimer.startTiming(); // Spigot // Purpur BlockState iblockdata = LevelChunk.this.getBlockState(blockposition); if (this.blockEntity.getType().isValid(iblockdata)) { @@ -1284,7 +1284,7 @@ public class LevelChunk extends ChunkAccess { LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata}); } - gameprofilerfiller.pop(); + //gameprofilerfiller.pop(); // Purpur } catch (Throwable throwable) { if (throwable instanceof ThreadDeath) throw throwable; // Paper // Paper start - Prevent tile entity and entity crashes @@ -1295,7 +1295,7 @@ public class LevelChunk extends ChunkAccess { // Paper end // Spigot start } finally { - this.blockEntity.tickTimer.stopTiming(); + //this.blockEntity.tickTimer.stopTiming(); // Purpur // Spigot end } } diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java index 060e064625969610539dbf969ce773b877a7c579..32cd9df202704cdfb8fa06aaf0e738d483054feb 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java @@ -112,6 +112,7 @@ public class EntityStorage implements EntityPersistentStorage { ListTag listTag = new ListTag(); final java.util.Map, Integer> savedEntityCounts = new java.util.HashMap<>(); // Paper entities.forEach((entity) -> { // diff here: use entities parameter + if (!entity.canSaveToDisk()) return; // Purpur // Paper start final EntityType entityType = entity.getType(); final int saveLimit = level.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1); diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java index dfeb3e336e06ef01f5401a362755030db942bb07..f74c5eda91a3d521763ec7bc33f23e0c62458cc2 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java @@ -49,7 +49,7 @@ public class PhantomSpawner implements CustomSpawner { int spawnAttemptMaxSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds; this.nextTick += (spawnAttemptMinSeconds + randomsource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; // Paper end - if (world.getSkyDarken() < 5 && world.dimensionType().hasSkyLight()) { + if (world.getSkyDarken() < world.purpurConfig.phantomSpawnMinSkyDarkness && world.dimensionType().hasSkyLight()) { // Purpur return 0; } else { int i = 0; @@ -61,10 +61,10 @@ public class PhantomSpawner implements CustomSpawner { if (!entityplayer.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityplayer.isCreative())) { // Paper 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; @@ -76,7 +76,7 @@ public class PhantomSpawner implements CustomSpawner { if (NaturalSpawner.isValidEmptySpawnBlock(world, blockposition1, iblockdata, fluid, EntityType.PHANTOM)) { SpawnGroupData groupdataentity = null; - int k = 1 + randomsource.nextInt(difficultydamagescaler.getDifficulty().getId() + 1); + int k = world.purpurConfig.phantomSpawnMinPerAttempt + world.random.nextInt((world.purpurConfig.phantomSpawnMaxPerAttempt < 0 ? difficultydamagescaler.getDifficulty().getId() : world.purpurConfig.phantomSpawnMaxPerAttempt - world.purpurConfig.phantomSpawnMinPerAttempt) + 1); // Purpur for (int l = 0; l < k; ++l) { // Paper start diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java index 7d56693102ee558fe784e3a9b9fdcff4b7ad57b9..485bbec4b52f126393332d5cad2d97c2a220f91a 100644 --- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java @@ -227,7 +227,7 @@ public abstract class FlowingFluid extends Fluid { } } - if (this.canConvertToSource(world) && j >= 2) { + if (this.canConvertToSource(world) && j >= getRequiredSources(world)) { BlockState iblockdata2 = world.getBlockState(pos.below()); FluidState fluid1 = iblockdata2.getFluidState(); @@ -325,6 +325,12 @@ public abstract class FlowingFluid extends Fluid { protected abstract boolean canConvertToSource(Level world); + // Purpur start + protected int getRequiredSources(Level level) { + return 2; + } + // Purpur end + protected void spreadTo(LevelAccessor world, BlockPos pos, BlockState state, Direction direction, FluidState fluidState) { if (state.getBlock() instanceof LiquidBlockContainer) { ((LiquidBlockContainer) state.getBlock()).placeLiquid(world, pos, state, fluidState); diff --git a/src/main/java/net/minecraft/world/level/material/LavaFluid.java b/src/main/java/net/minecraft/world/level/material/LavaFluid.java index c3f8e1e2dd89c168b8b4a15b589109db486bc8d7..70ddb3b130ee59a6e200ea5af3ac89f3c3fa9e9b 100644 --- a/src/main/java/net/minecraft/world/level/material/LavaFluid.java +++ b/src/main/java/net/minecraft/world/level/material/LavaFluid.java @@ -180,7 +180,7 @@ public abstract class LavaFluid extends FlowingFluid { @Override public int getTickDelay(LevelReader world) { - return world.dimensionType().ultraWarm() ? 10 : 30; + return world.dimensionType().ultraWarm() ? world.getWorldBorder().world.purpurConfig.lavaSpeedNether : world.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur } @Override @@ -198,6 +198,13 @@ public abstract class LavaFluid extends FlowingFluid { world.levelEvent(1501, pos, 0); } + // Purpur start + @Override + protected int getRequiredSources(Level level) { + return level.purpurConfig.lavaInfiniteRequiredSources; + } + // Purpur end + @Override protected boolean canConvertToSource(Level world) { return world.getGameRules().getBoolean(GameRules.RULE_LAVA_SOURCE_CONVERSION); 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 d280c98aed5262c4ce39526c917de884f25a8584..e7d9f6802520620a1dcf0938256ffe80fc72a6f0 100644 --- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java +++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java @@ -64,6 +64,13 @@ public abstract class WaterFluid extends FlowingFluid { return world.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION); } + // Purpur start + @Override + protected int getRequiredSources(Level level) { + return level.purpurConfig.waterInfiniteRequiredSources; + } + // Purpur end + // Paper start @Override protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java index d23481453717f715124156b5d83f6448f720d049..a8af51a25b0f99c3a64d9150fdfcd6b818aa7581 100644 --- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java +++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java @@ -53,8 +53,8 @@ public class PathFinder { @Nullable // Paper start - optimize collection private Path findPath(ProfilerFiller profiler, Node startNode, List> positions, float followRange, int distance, float rangeMultiplier) { - profiler.push("find_path"); - profiler.markForCharting(MetricCategory.PATH_FINDING); + //profiler.push("find_path"); // Purpur + //profiler.markForCharting(MetricCategory.PATH_FINDING); // Purpur // Set set = positions.keySet(); startNode.g = 0.0F; startNode.h = this.getBestH(startNode, positions); // Paper - optimize collection 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 3583fcf5284bc5883308876dbd9886664b391e28..ba57accc272958da4714896baeadb52c99383561 100644 --- a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +++ b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java @@ -241,7 +241,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { } if (blockPathTypes != BlockPathTypes.WALKABLE && (!this.isAmphibious() || blockPathTypes != BlockPathTypes.WATER)) { - if ((node == null || node.costMalus < 0.0F) && maxYStep > 0 && (blockPathTypes != BlockPathTypes.FENCE || this.canWalkOverFences()) && blockPathTypes != BlockPathTypes.UNPASSABLE_RAIL && blockPathTypes != BlockPathTypes.TRAPDOOR && blockPathTypes != BlockPathTypes.POWDER_SNOW) { + if ((node == null || node.costMalus < 0.0F) && maxYStep > 0 && (blockPathTypes != BlockPathTypes.FENCE || this.canWalkOverFences()) && (this.mob.level().purpurConfig.mobsIgnoreRails || blockPathTypes != BlockPathTypes.UNPASSABLE_RAIL) && blockPathTypes != BlockPathTypes.TRAPDOOR && blockPathTypes != BlockPathTypes.POWDER_SNOW) { // Purpur node = this.findAcceptedNode(x, y + 1, z, maxYStep - 1, prevFeetY, direction, nodeType); if (node != null && (node.type == BlockPathTypes.OPEN || node.type == BlockPathTypes.WALKABLE) && this.mob.getBbWidth() < 1.0F) { double g = (double)(x - direction.getStepX()) + 0.5D; @@ -465,7 +465,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { return BlockPathTypes.BLOCKED; } else { // Paper end - if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH)) { + if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH) || blockState.is(Blocks.STONECUTTER)) { // Purpur return BlockPathTypes.DANGER_OTHER; } @@ -498,7 +498,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { } else if (!blockState.is(BlockTags.TRAPDOORS) && !blockState.is(Blocks.LILY_PAD) && !blockState.is(Blocks.BIG_DRIPLEAF)) { if (blockState.is(Blocks.POWDER_SNOW)) { return BlockPathTypes.POWDER_SNOW; - } else if (!blockState.is(Blocks.CACTUS) && !blockState.is(Blocks.SWEET_BERRY_BUSH)) { + } else if (!blockState.is(Blocks.CACTUS) && !blockState.is(Blocks.SWEET_BERRY_BUSH) && !blockState.is(Blocks.STONECUTTER)) { // Purpur if (blockState.is(Blocks.HONEY_BLOCK)) { return BlockPathTypes.STICKY_HONEY; } else if (blockState.is(Blocks.COCOA)) { diff --git a/src/main/java/net/minecraft/world/level/portal/PortalShape.java b/src/main/java/net/minecraft/world/level/portal/PortalShape.java index c461e0d04047db9c0c5ecc04063cebd38bf96ec2..e7554ec800f321e4e34c926c53f2375a8c3aa979 100644 --- a/src/main/java/net/minecraft/world/level/portal/PortalShape.java +++ b/src/main/java/net/minecraft/world/level/portal/PortalShape.java @@ -34,7 +34,7 @@ public class PortalShape { private static final int MIN_HEIGHT = 3; public static final int MAX_HEIGHT = 21; private static final BlockBehaviour.StatePredicate FRAME = (iblockdata, iblockaccess, blockposition) -> { - return iblockdata.is(Blocks.OBSIDIAN); + return iblockdata.is(Blocks.OBSIDIAN) || (org.purpurmc.purpur.PurpurConfig.cryingObsidianValidForPortalFrame && iblockdata.is(Blocks.CRYING_OBSIDIAN)); // Purpur }; private static final float SAFE_TRAVEL_MAX_ENTITY_XY = 4.0F; private static final double SAFE_TRAVEL_MAX_VERTICAL_DELTA = 1.0D; diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java index 50713f03c783c63f93710d986d94af544be0615a..e64921f454c88e2ca9e1396fcb1ca7c179ff71a7 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 @@ -66,6 +66,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/LootingEnchantFunction.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java index 31918fa2eb38e42a5ea5366e559f25ea9d7d59ae..15d8e9261a89da30529ac347462c520920ca4e7d 100644 --- a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java +++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java @@ -49,6 +49,13 @@ public class LootingEnchantFunction extends LootItemConditionalFunction { if (entity instanceof LivingEntity) { int i = EnchantmentHelper.getMobLooting((LivingEntity) entity); + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.fixProjectileLootingTransfer && + context.getParamOrNull(LootContextParams.DIRECT_KILLER_ENTITY) + instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) { + i = arrow.lootingLevel; + } + // Purpur end // CraftBukkit start - use lootingModifier if set by plugin if (context.hasParam(LootContextParams.LOOTING_MOD)) { i = context.getParamOrNull(LootContextParams.LOOTING_MOD); diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java index ffc76354ead6937daf366c3d87bcb51d3e4c47f5..5b98d42b5d6bc07265fbb017e51a6281c148436a 100644 --- a/src/main/java/net/minecraft/world/phys/AABB.java +++ b/src/main/java/net/minecraft/world/phys/AABB.java @@ -374,4 +374,10 @@ public class AABB { public static AABB ofSize(Vec3 center, double dx, double dy, double dz) { return new AABB(center.x - dx / 2.0D, center.y - dy / 2.0D, center.z - dz / 2.0D, center.x + dx / 2.0D, center.y + dy / 2.0D, center.z + dz / 2.0D); } + + // Purpur - tuinity added method + public final AABB offsetY(double dy) { + return new AABB(this.minX, this.minY + dy, this.minZ, this.maxX, this.maxY + dy, this.maxZ); + } + // Purpur } diff --git a/src/main/java/net/minecraft/world/ticks/LevelTicks.java b/src/main/java/net/minecraft/world/ticks/LevelTicks.java index 1d7c663fa0e550bd0cfb9a4b83ccd7e2968666f0..0043c0087896a6df6910b0500da37d84b287c901 100644 --- a/src/main/java/net/minecraft/world/ticks/LevelTicks.java +++ b/src/main/java/net/minecraft/world/ticks/LevelTicks.java @@ -86,20 +86,20 @@ public class LevelTicks implements LevelTickAccess { } public void tick(long time, int maxTicks, BiConsumer ticker) { - ProfilerFiller profilerFiller = this.profiler.get(); - profilerFiller.push("collect"); - this.collectTicks(time, maxTicks, profilerFiller); - profilerFiller.popPush("run"); - profilerFiller.incrementCounter("ticksToRun", this.toRunThisTick.size()); + //ProfilerFiller profilerFiller = this.profiler.get(); // Purpur + //profilerFiller.push("collect"); // Purpur + this.collectTicks(time, maxTicks, null); // Purpur + //profilerFiller.popPush("run"); // Purpur + //profilerFiller.incrementCounter("ticksToRun", this.toRunThisTick.size()); // Purpur this.runCollectedTicks(ticker); - profilerFiller.popPush("cleanup"); + //profilerFiller.popPush("cleanup"); // Purpur this.cleanupAfterTick(); - profilerFiller.pop(); + //profilerFiller.pop(); // Purpur } private void collectTicks(long time, int maxTicks, ProfilerFiller profiler) { this.sortContainersToTick(time); - profiler.incrementCounter("containersToTick", this.containersToTick.size()); + //profiler.incrementCounter("containersToTick", this.containersToTick.size()); // Purpur this.drainContainers(time, maxTicks); this.rescheduleLeftoverContainers(); } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java index c1b874cd6e0498fce3cd53fdbaca30d290e004d7..23087d511b609693f0bb06cbaac8b6cf8d0defe2 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java @@ -567,4 +567,213 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa manager.save(); } } + + // Purpur start - OfflinePlayer API + @Override + public boolean getAllowFlight() { + if (this.isOnline()) { + return this.getPlayer().getAllowFlight(); + } else { + CompoundTag data = this.getData(); + if (data == null) return false; + if (!data.contains("abilities")) return false; + CompoundTag abilities = data.getCompound("abilities"); + return abilities.getByte("mayfly") == (byte) 1; + } + } + + @Override + public void setAllowFlight(boolean flight) { + if (this.isOnline()) { + this.getPlayer().setAllowFlight(flight); + } else { + CompoundTag data = this.getData(); + if (data == null) return; + if (!data.contains("abilities")) return; + CompoundTag abilities = data.getCompound("abilities"); + abilities.putByte("mayfly", (byte) (flight ? 1 : 0)); + data.put("abilities", abilities); + save(data); + } + } + + @Override + public boolean isFlying() { + if (this.isOnline()) { + return this.isFlying(); + } else { + CompoundTag data = this.getData(); + if (data == null) return false; + if (!data.contains("abilities")) return false; + CompoundTag abilities = data.getCompound("abilities"); + return abilities.getByte("flying") == (byte) 1; + } + } + + @Override + public void setFlying(boolean value) { + if (this.isOnline()) { + this.getPlayer().setFlying(value); + } else { + CompoundTag data = this.getData(); + if (data == null) return; + if (!data.contains("abilities")) return; + CompoundTag abilities = data.getCompound("abilities"); + abilities.putByte("mayfly", (byte) (value ? 1 : 0)); + data.put("abilities", abilities); + save(data); + } + } + + @Override + public void setFlySpeed(float value) throws IllegalArgumentException { + if (value < -1f || value > 1f) throw new IllegalArgumentException("FlySpeed needs to be between -1 and 1"); + if (this.isOnline()) { + this.getPlayer().setFlySpeed(value); + } else { + CompoundTag data = this.getData(); + if (data == null) return; + if (!data.contains("abilities")) return; + CompoundTag abilities = data.getCompound("abilities"); + abilities.putFloat("flySpeed", value); + data.put("abilities", abilities); + save(data); + } + } + + @Override + public float getFlySpeed() { + if (this.isOnline()) { + return this.getPlayer().getFlySpeed(); + } else { + CompoundTag data = this.getData(); + if (data == null) return 0; + if (!data.contains("abilities")) return 0; + CompoundTag abilities = data.getCompound("abilities"); + return abilities.getFloat("flySpeed"); + } + } + + @Override + public void setWalkSpeed(float value) throws IllegalArgumentException { + if (value < -1f || value > 1f) throw new IllegalArgumentException("WalkSpeed needs to be between -1 and 1"); + if (this.isOnline()) { + this.getPlayer().setWalkSpeed(value); + } else { + CompoundTag data = this.getData(); + if (data == null) return; + if (!data.contains("abilities")) return; + CompoundTag abilities = data.getCompound("abilities"); + abilities.putFloat("walkSpeed", value); + data.put("abilities", abilities); + save(data); + } + } + + @Override + public float getWalkSpeed() { + if (this.isOnline()) { + return this.getPlayer().getWalkSpeed(); + } else { + CompoundTag data = this.getData(); + if (data == null) return 0; + if (!data.contains("abilities")) return 0; + CompoundTag abilities = data.getCompound("abilities"); + return abilities.getFloat("walkSpeed"); + } + } + + @Override + public Location getLocation() { + if (this.isOnline()) { + return this.getPlayer().getLocation(); + } else { + CompoundTag data = this.getData(); + if (data == null) return null; + long worldUUIDMost = data.getLong("WorldUUIDMost"); + long worldUUIDLeast = data.getLong("WorldUUIDLeast"); + net.minecraft.nbt.ListTag position = data.getList("Pos", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_DOUBLE); + net.minecraft.nbt.ListTag rotation = data.getList("Rotation", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_FLOAT); + UUID worldUuid = new UUID(worldUUIDMost, worldUUIDLeast); + org.bukkit.World world = server.getWorld(worldUuid); + double x = position.getDouble(0); + double y = position.getDouble(1); + double z = position.getDouble(2); + float yaw = rotation.getFloat(0); + float pitch = rotation.getFloat(1); + return new Location(world, x, y, z, yaw, pitch); + } + } + + @Override + public boolean teleportOffline(Location destination) { + if (this.isOnline()) { + return this.getPlayer().teleport(destination); + } else { + return setLocation(destination); + } + } + + @Override + public boolean teleportOffline(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause){ + if (this.isOnline()) { + return this.getPlayer().teleport(destination, cause); + } else { + return setLocation(destination); + } + } + + @Override + public java.util.concurrent.CompletableFuture teleportOfflineAsync(Location destination) { + if (this.isOnline()) { + return this.getPlayer().teleportAsync(destination); + } else { + return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination)); + } + } + + @Override + public java.util.concurrent.CompletableFuture teleportOfflineAsync(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { + if (this.isOnline()) { + return this.getPlayer().teleportAsync(destination, cause); + } else { + return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination)); + } + } + + private boolean setLocation(Location location) { + CompoundTag data = this.getData(); + if (data == null) return false; + data.putLong("WorldUUIDMost", location.getWorld().getUID().getMostSignificantBits()); + data.putLong("WorldUUIDLeast", location.getWorld().getUID().getLeastSignificantBits()); + net.minecraft.nbt.ListTag position = new net.minecraft.nbt.ListTag(); + position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getX())); + position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getY())); + position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getZ())); + data.put("Pos", position); + net.minecraft.nbt.ListTag rotation = new net.minecraft.nbt.ListTag(); + rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getYaw())); + rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getPitch())); + data.put("Rotation", rotation); + save(data); + return true; + } + + /** + * Safely replaces player's .dat file with provided CompoundTag + * @param compoundTag + */ + private void save(CompoundTag compoundTag) { + File playerDir = server.console.playerDataStorage.getPlayerDir(); + try { + File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir); + net.minecraft.nbt.NbtIo.writeCompressed(compoundTag, tempFile); + File playerDataFile = new File(playerDir, this.getUniqueId()+".dat"); + File playerDataFileOld = new File(playerDir, this.getUniqueId()+".dat_old"); + net.minecraft.Util.safeReplaceFile(playerDataFile, tempFile, playerDataFileOld); + } catch (java.io.IOException e) { + e.printStackTrace(); + } + } + // Purpur end - OfflinePlayer API } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 0f89716d1131178bd7ac927ef68337357139e2b7..b21bc96232d0fd52e5dae7fa1c413714f8ddd7ee 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -265,7 +265,7 @@ import javax.annotation.Nullable; // Paper import javax.annotation.Nonnull; // Paper public final class CraftServer implements Server { - private final String serverName = "Pufferfish"; // Paper // Pufferfish + private final String serverName = "Purpur"; // Paper // Pufferfish // Purpur private final String serverVersion; private final String bukkitVersion = Versioning.getBukkitVersion(); private final Logger logger = Logger.getLogger("Minecraft"); @@ -397,6 +397,20 @@ public final class CraftServer implements Server { this.dataPackManager = new CraftDataPackManager(this.getServer().getPackRepository()); Bukkit.setServer(this); + // Purpur start + org.purpurmc.purpur.language.Language.setLanguage(new org.purpurmc.purpur.language.Language() { + private net.minecraft.locale.Language language = net.minecraft.locale.Language.getInstance(); + @Override + public boolean has(@org.jetbrains.annotations.NotNull String key) { + return language.has(key); + } + + @Override + public @org.jetbrains.annotations.NotNull String getOrDefault(@org.jetbrains.annotations.NotNull String key) { + return language.getOrDefault(key); + } + }); + // Purpur end CraftRegistry.setMinecraftRegistry(console.registryAccess()); @@ -1037,6 +1051,7 @@ public final class CraftServer implements Server { org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot this.console.paperConfigurations.reloadConfigs(this.console); + org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur for (ServerLevel world : this.console.getAllLevels()) { // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters, config.spawnAnimals); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) @@ -1052,6 +1067,7 @@ public final class CraftServer implements Server { } } world.spigotConfig.init(); // Spigot + world.purpurConfig.init(); // Purpur } Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper @@ -1067,6 +1083,7 @@ public final class CraftServer implements Server { this.reloadData(); org.spigotmc.SpigotConfig.registerCommands(); // Spigot io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper + org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); @@ -1532,6 +1549,55 @@ public final class CraftServer implements Server { return true; } + // Purpur Start + @Override + public void addFuel(org.bukkit.Material material, int burnTime) { + Preconditions.checkArgument(burnTime > 0, "BurnTime must be greater than 0"); + net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity.addFuel(net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)), burnTime); + } + + @Override + public void removeFuel(org.bukkit.Material material) { + net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity.removeFuel(net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material))); + } + + @Override + public void sendBlockHighlight(Location location, int duration) { + sendBlockHighlight(location, duration, "", 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, int argb) { + sendBlockHighlight(location, duration, "", argb); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text) { + sendBlockHighlight(location, duration, text, 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, int argb) { + this.worlds.forEach((name, world) -> world.sendBlockHighlight(location, duration, text, argb)); + } + + @Override + public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { + sendBlockHighlight(location, duration, "", color, transparency); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { + if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); + sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); + } + + @Override + public void clearBlockHighlights() { + this.worlds.forEach((name, world) -> clearBlockHighlights()); + } + // Purpur End + @Override public List getRecipesFor(ItemStack result) { Preconditions.checkArgument(result != null, "ItemStack cannot be null"); @@ -2871,6 +2937,7 @@ public final class CraftServer implements Server { @Override public double[] getTPS() { return new double[] { + net.minecraft.server.MinecraftServer.getServer().tps5s.getAverage(), // Purpur net.minecraft.server.MinecraftServer.getServer().tps1.getAverage(), net.minecraft.server.MinecraftServer.getServer().tps5.getAverage(), net.minecraft.server.MinecraftServer.getServer().tps15.getAverage() @@ -2917,6 +2984,18 @@ public final class CraftServer implements Server { return CraftServer.this.console.paperConfigurations.createLegacyObject(CraftServer.this.console); } + // Purpur start + @Override + public YamlConfiguration getPurpurConfig() { + return org.purpurmc.purpur.PurpurConfig.config; + } + + @Override + public java.util.Properties getServerProperties() { + return getProperties().properties; + } + // Purpur end + @Override public void restart() { org.spigotmc.RestartCommand.restart(); @@ -3130,4 +3209,16 @@ public final class CraftServer implements Server { } // Paper end + + // Purpur start + @Override + public String getServerName() { + return this.getProperties().serverName; + } + + @Override + public boolean isLagging() { + return getServer().lagging; + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index f857f490ffba2f25f7c06c5fb1a1905f0b51fbe2..1392e483c363e25d1f16465d876cb7d7c70afa68 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -2290,6 +2290,48 @@ public class CraftWorld extends CraftRegionAccessor implements World { return (this.getHandle().getDragonFight() == null) ? null : new CraftDragonBattle(this.getHandle().getDragonFight()); } + // Purpur start + public float getLocalDifficultyAt(Location location) { + return getHandle().getCurrentDifficultyAt(io.papermc.paper.util.MCUtil.toBlockPosition(location)).getEffectiveDifficulty(); + } + + @Override + public void sendBlockHighlight(Location location, int duration) { + sendBlockHighlight(location, duration, "", 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, int argb) { + sendBlockHighlight(location, duration, "", argb); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text) { + sendBlockHighlight(location, duration, text, 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, int argb) { + net.minecraft.network.protocol.game.DebugPackets.sendGameTestAddMarker(getHandle(), io.papermc.paper.util.MCUtil.toBlockPosition(location), text, argb, duration); + } + + @Override + public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { + sendBlockHighlight(location, duration, "", color, transparency); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { + if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); + sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); + } + + @Override + public void clearBlockHighlights() { + net.minecraft.network.protocol.game.DebugPackets.sendGameTestClearPacket(getHandle()); + } + // Purpur end + @Override public PersistentDataContainer getPersistentDataContainer() { return this.persistentDataContainer; diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java index 7c29d92362a82330440dff151bcf421f15c68f71..4bc0a370c7aec06d30b4ebf7fa7d73263d0543bc 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -173,6 +173,20 @@ public class Main { .describedAs("Jar file"); // Paper end + // Purpur Start + acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File("purpur.yml")) + .describedAs("Yml file"); + + acceptsAll(asList("pufferfish", "pufferfish-settings"), "File for pufferfish settings") + .withRequiredArg() + .ofType(File.class) + .defaultsTo(new File("pufferfish.yml")) + .describedAs("Yml file"); + // Purpur end + // Paper start acceptsAll(asList("server-name"), "Name of the server") .withRequiredArg() @@ -292,7 +306,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 380897c010521f368848a3e6986d307cf47ff319..3c5eae31dc229b8c5f15215be9462410bcb41f07 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 + public CraftBeehive(World world, BeehiveBlockEntity tileEntity) { super(world, tileEntity); + // Purpur start - load bees to be able to modify them individually + for(BeehiveBlockEntity.BeeData data : getSnapshot().getStored()) { + storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(data, this)); + } + // Purpur end } @Override @@ -66,25 +73,67 @@ public class CraftBeehive extends CraftBlockEntityState impl List bees = new ArrayList<>(); if (isPlaced()) { - BeehiveBlockEntity beehive = ((BeehiveBlockEntity) this.getTileEntityFromWorld()); - for (Entity bee : beehive.releaseBees(this.getHandle(), BeeReleaseStatus.BEE_RELEASED, true)) { + // Purpur start - change which releaseBees method is called, and use beehive snapshot + BeehiveBlockEntity beehive = ((BeehiveBlockEntity) this.getSnapshot()); + for (Entity bee : beehive.releaseBees(this.getHandle(), getWorldHandle().getMinecraftWorld(), BeeReleaseStatus.BEE_RELEASED, true)) { + // Purpur end bees.add((Bee) bee.getBukkitEntity()); } } - + storage.clear(); // Purpur return bees; } + // Purpur start + @Override + public Bee releaseEntity(org.purpurmc.purpur.entity.StoredEntity entity) { + ensureNoWorldGeneration(); + + if(!getEntities().contains(entity)) { + return null; + } + + if(isPlaced()) { + BeehiveBlockEntity beehive = this.getSnapshot(); + BeehiveBlockEntity.BeeData data = ((org.purpurmc.purpur.entity.PurpurStoredBee) entity).getHandle(); + + List list = beehive.releaseBee(getHandle(), getWorldHandle().getMinecraftWorld(), 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 + @Override public void addEntity(Bee entity) { Preconditions.checkArgument(entity != null, "Entity must not be null"); + int length = getSnapshot().getStored().size(); // Purpur getSnapshot().addOccupant(((CraftBee) entity).getHandle(), false); + + // Purpur start - check if new bee was added, and if yes, add to stored bees + List s = getSnapshot().getStored(); + if(length < s.size()) { + storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(s.get(s.size() - 1), this)); + } + // Purpur end } // Paper start - Add EntityBlockStorage clearEntities @Override public void clearEntities() { getSnapshot().clearBees(); + storage.clear(); // Purpur } // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java index 4e56018b64d11f76c8da43fd8f85c6de72204e36..9607675e6c5bff2183c4420d11fc63eeb5747fb6 100644 --- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java @@ -21,7 +21,12 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co @Override public void sendMessage(String message) { - this.sendRawMessage(message); + // Purpur start + String[] parts = message.split("\n"); + for (String part : parts) { + this.sendRawMessage(part); + } + // Purpur end } @Override @@ -91,7 +96,7 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co // Paper start @Override public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) { - this.sendRawMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); + this.sendMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); // Purpur } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java index 3d0ce0803e1da8a2681a3cb41096ac942ece54a1..bcd075a771c7f43c6d1549aeec2ccb20ee168b57 100644 --- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java @@ -59,6 +59,7 @@ public class CraftEnchantment extends Enchantment { return EnchantmentTarget.CROSSBOW; case VANISHABLE: return EnchantmentTarget.VANISHABLE; + case BOW_AND_CROSSBOW: return EnchantmentTarget.BOW_AND_CROSSBOW; // Purpur default: return null; } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java index d657fd2c507a5b215aeab0a5f3e9c2ee892a27c8..985e9ec21c60a1f47973bd5fc53b96a6f9b7d04a 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 } @Override public void setPlayerSpawned(boolean playerSpawned) { - // Nop + getHandle().setPlayerSpawned(playerSpawned); // Purpur } // Paper start @Override diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java index 2dbe8b870fd39b4d22e9725912f443757ae70761..8973c8a3bad120e55269bf1b7b810284ad0fe14c 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -224,6 +224,21 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { this.entityType = (type != null) ? type : EntityType.UNKNOWN; } + @Override + public boolean isImmuneToFire() { + return getHandle().fireImmune(); + } + + @Override + public void setImmuneToFire(Boolean fireImmune) { + getHandle().immuneToFire = fireImmune; + } + + @Override + public boolean isInDaylight() { + return getHandle().isSunBurnTick(); + } + public static CraftEntity getEntity(CraftServer server, Entity entity) { /* * Order is *EXTREMELY* important -- keep it right! =D @@ -601,6 +616,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { // Paper end if ((!ignorePassengers && this.entity.isVehicle()) || this.entity.isRemoved()) { // Paper - Teleport passenger API + // Purpur start + if (!entity.isRemoved() && new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) + return teleport(location, cause); + // Purpur end return false; } @@ -1507,4 +1526,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { return this.getHandle().getScoreboardName(); } // Paper end - entity scoreboard name + + // Purpur start + @Override + public org.bukkit.entity.Player getRider() { + net.minecraft.world.entity.player.Player rider = getHandle().getRider(); + return rider != null ? (org.bukkit.entity.Player) rider.getBukkitEntity() : null; + } + + @Override + public boolean hasRider() { + return getHandle().getRider() != null; + } + + @Override + public boolean isRidable() { + return getHandle().isRidable(); + } + + @Override + public boolean isRidableInWater() { + return !getHandle().dismountsUnderwater(); + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java index 7db63d9ef93902872937b69f431137336e4abc3a..05ab5d0ae25f154d16c7bf107e93ed8e31e1f992 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java @@ -266,6 +266,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { @Override public void recalculatePermissions() { this.perm.recalculatePermissions(); + getHandle().canPortalInstant = hasPermission("purpur.portal.instant"); // Purpur } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java index 75d10b5322eb0a62bce2855c04a5151eb857d7de..208018981a2a5666c455eb34614b03f617354165 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 + @Override + @org.jetbrains.annotations.Nullable + public java.util.UUID getSummoner() { + return getHandle().getSummoner(); + } + + @Override + public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { + getHandle().setSummoner(summoner); + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java index 5e83fabb20bc2b0668cbf48530053ca1bb9092f3..4ffb4046b63cbc140c76721f51c9a7a09e81844d 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java @@ -154,4 +154,51 @@ public class CraftItem extends CraftEntity implements Item { public String toString() { return "CraftItem"; } + + // Purpur start + @Override + public void setImmuneToCactus(boolean immuneToCactus) { + item.immuneToCactus = immuneToCactus; + } + + @Override + public boolean isImmuneToCactus() { + return item.immuneToCactus; + } + + @Override + public void setImmuneToExplosion(boolean immuneToExplosion) { + item.immuneToExplosion = immuneToExplosion; + } + + @Override + public boolean isImmuneToExplosion() { + return item.immuneToExplosion; + } + + @Override + public void setImmuneToFire(@org.jetbrains.annotations.Nullable Boolean immuneToFire) { + item.immuneToFire = (immuneToFire != null && immuneToFire); + } + + @Override + public void setImmuneToFire(boolean immuneToFire) { + this.setImmuneToFire((Boolean) immuneToFire); + } + + @Override + public boolean isImmuneToFire() { + return item.immuneToFire; + } + + @Override + public void setImmuneToLightning(boolean immuneToLightning) { + item.immuneToLightning = immuneToLightning; + } + + @Override + public boolean isImmuneToLightning() { + return item.immuneToLightning; + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java index b25f5931f836fd4d8695120f0bcb7c52deff8583..b473f89c59c0fc080fadd0d4db320895e917fd41 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -453,7 +453,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 @@ -465,7 +465,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { @Override public boolean addPotionEffect(PotionEffect effect, boolean force) { org.spigotmc.AsyncCatcher.catchOp("effect add"); // Paper - this.getHandle().addEffect(new MobEffectInstance(MobEffect.byId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon()), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon + this.getHandle().addEffect(new MobEffectInstance(MobEffect.byId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon(), effect.getKey()), EntityPotionEffectEvent.Cause.PLUGIN); // Purpur - add key // Paper - Don't ignore icon return true; } @@ -486,7 +486,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { @Override public PotionEffect getPotionEffect(PotionEffectType type) { MobEffectInstance handle = this.getHandle().getEffect(MobEffect.byId(type.getId())); - return (handle == null) ? null : new PotionEffect(PotionEffectType.getById(MobEffect.getId(handle.getEffect())), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible()); + return (handle == null) ? null : new PotionEffect(PotionEffectType.getById(MobEffect.getId(handle.getEffect())), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible(), handle.getKey()); // Purpur - add key } @Override @@ -498,7 +498,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { public Collection getActivePotionEffects() { List effects = new ArrayList(); for (MobEffectInstance handle : this.getHandle().activeEffects.values()) { - effects.add(new PotionEffect(PotionEffectType.getById(MobEffect.getId(handle.getEffect())), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible())); + effects.add(new PotionEffect(PotionEffectType.getById(MobEffect.getId(handle.getEffect())), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible(), handle.getKey())); // Purpur - add key } return effects; } @@ -912,7 +912,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { return EntityCategory.WATER; } - throw new UnsupportedOperationException("Unsupported monster type: " + type + ". This is a bug, report this to Spigot."); + throw new UnsupportedOperationException("Unsupported monster type: " + type + ". This is a bug, report this to Purpur."); // Purpur } @Override @@ -1099,4 +1099,32 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { getHandle().knockback(strength, directionX, directionZ); }; // Paper end + + // Purpur start + @Override + public float getSafeFallDistance() { + return getHandle().safeFallDistance; + } + + @Override + public void setSafeFallDistance(float safeFallDistance) { + getHandle().safeFallDistance = safeFallDistance; + } + + @Override + public void broadcastItemBreak(org.bukkit.inventory.EquipmentSlot slot) { + if (slot == null) return; + getHandle().broadcastBreakEvent(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); + } + + @Override + public boolean shouldBurnInDay() { + return getHandle().shouldBurnInDay(); + } + + @Override + public void setShouldBurnInDay(boolean shouldBurnInDay) { + getHandle().setShouldBurnInDay(shouldBurnInDay); + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java index 0ad16ee7b33582d214dab41eeee378d52c8e38ed..16bd1294c219f15ada653ef810bc2d748222d0da 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 3d5876cbadb5c8c02de751c3e15fc7f251ea7d35..720a81da72a8002ff02cc39e5fca42354519c742 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -517,10 +517,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public void setPlayerListName(String name) { + // Purpur start + setPlayerListName(name, false); + } + public void setPlayerListName(String name, boolean useMM) { + // Purpur end if (name == null) { name = getName(); } - this.getHandle().listName = name.equals(getName()) ? null : CraftChatMessage.fromStringOrNull(name); + this.getHandle().listName = name.equals(getName()) ? null : useMM ? io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name)) : CraftChatMessage.fromStringOrNull(name); // Purpur for (ServerPlayer player : (List) server.getHandle().players) { if (player.getBukkitEntity().canSee(this)) { player.connection.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, this.getHandle())); @@ -1350,6 +1355,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API + // Purpur start + if (new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) + return teleport(location, cause); + // Purpur end return false; } @@ -2496,6 +2505,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); } @@ -3267,4 +3298,90 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return this.spigot; } // Spigot end + + // Purpur start + @Override + public boolean usesPurpurClient() { + return getHandle().purpurClient; + } + + @Override + public boolean isAfk() { + return getHandle().isAfk(); + } + + @Override + public void setAfk(boolean setAfk) { + getHandle().setAfk(setAfk); + } + + @Override + public void resetIdleTimer() { + getHandle().resetLastActionTime(); + } + + @Override + public boolean isSpawnInvulnerable() { + return getHandle().isSpawnInvulnerable(); + } + + @Override + public int getSpawnInvulnerableTicks() { + return getHandle().spawnInvulnerableTime; + } + + @Override + public void setSpawnInvulnerableTicks(int spawnInvulnerableTime) { + getHandle().spawnInvulnerableTime = spawnInvulnerableTime; + } + + @Override + public void sendBlockHighlight(Location location, int duration) { + sendBlockHighlight(location, duration, "", 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, int argb) { + sendBlockHighlight(location, duration, "", argb); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text) { + sendBlockHighlight(location, duration, text, 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, int argb) { + if (this.getHandle().connection == null) return; + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeBlockPos(io.papermc.paper.util.MCUtil.toBlockPosition(location)); + buf.writeInt(argb); + buf.writeUtf(text); + buf.writeInt(duration); + this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.DEBUG_GAME_TEST_ADD_MARKER, buf)); + } + + @Override + public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { + sendBlockHighlight(location, duration, "", color, transparency); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { + if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); + sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); + } + + @Override + public void clearBlockHighlights() { + if (this.getHandle().connection == null) return; + this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.DEBUG_GAME_TEST_CLEAR, new FriendlyByteBuf(io.netty.buffer.Unpooled.buffer()))); + } + + @Override + public void sendDeathScreen(net.kyori.adventure.text.Component message) { + 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 } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java index e4a14cdfeb91a3d32e622d27d612605b1bca08e2..898d934aafd6066df45f02fe3406fa83f79b745c 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 + @Override + @org.jetbrains.annotations.Nullable + public java.util.UUID getSummoner() { + return getHandle().getSummoner(); + } + + @Override + public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { + getHandle().setSummoner(summoner); + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java index f29e221e5b850516c169c03bfbd2b0885d1a841b..9e343d6e6393db17748fd13d76354464e128001f 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java @@ -216,4 +216,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { getHandle().getGossips().gossips.clear(); } // Paper end + + // Purpur start + @Override + public boolean isLobotomized() { + return getHandle().isLobotomized(); + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java index 5a97c92f9b044d8ab7bd3346ceb464455a09046e..e30d8b80734f04b1fa89e8a3cef666116fd7366c 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 + @Override + @org.jetbrains.annotations.Nullable + public java.util.UUID getSummoner() { + return getHandle().getSummoner(); + } + + @Override + public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { + getHandle().setSummoner(summoner); + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java index e16459c9cfcac790edd6d912750d32c68387cbbc..890938ad866e2c588f3f819230ba121b0b436401 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java @@ -57,4 +57,16 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf { public void setInterested(boolean flag) { this.getHandle().setIsInterested(flag); } + + // Purpur start + @Override + public boolean isRabid() { + return getHandle().isRabid(); + } + + @Override + public void setRabid(boolean isRabid) { + getHandle().setRabid(isRabid); + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index 090b1ee57ddef58ca71469ad860960f66da7d5a2..5641df31686d6203a523b04406c2c85eab2240d2 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -590,6 +590,15 @@ public class CraftEventFactory { // Paper end craftServer.getPluginManager().callEvent(event); + // Purpur start + if (who != null) { + switch (action) { + case LEFT_CLICK_BLOCK, LEFT_CLICK_AIR -> who.processClick(InteractionHand.MAIN_HAND); + case RIGHT_CLICK_BLOCK, RIGHT_CLICK_AIR -> who.processClick(InteractionHand.OFF_HAND); + } + } + // Purpur end + return event; } @@ -1031,6 +1040,7 @@ public class CraftEventFactory { damageCause = DamageCause.ENTITY_EXPLOSION; } event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions, source.isCritical()); // Paper - add critical damage API + damager.processClick(InteractionHand.MAIN_HAND); // Purpur } event.setCancelled(cancelled); @@ -1145,6 +1155,7 @@ public class CraftEventFactory { } else { entity.lastDamageCancelled = true; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled } + damager.getHandle().processClick(InteractionHand.MAIN_HAND); // Purpur return event; } @@ -1208,6 +1219,7 @@ public class CraftEventFactory { EntityDamageEvent event; if (damager != null) { event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions, critical); // Paper - add critical damage API + damager.processClick(InteractionHand.MAIN_HAND); // Purpur } else { event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java index 633e6f4922ccaf59979a22885162f42c65bf628a..15001e7c8eae3a89f9d8669be532d56b70d538ef 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java @@ -181,8 +181,19 @@ public class CraftContainer extends AbstractContainerMenu { case PLAYER: case CHEST: case ENDER_CHEST: + // Purpur start + this.delegate = new ChestMenu(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? MenuType.GENERIC_9x6 : MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9); + break; case BARREL: - this.delegate = new ChestMenu(MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9); + this.delegate = new ChestMenu(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { + case 6 -> MenuType.GENERIC_9x6; + case 5 -> MenuType.GENERIC_9x5; + case 4 -> MenuType.GENERIC_9x4; + case 2 -> MenuType.GENERIC_9x2; + case 1 -> MenuType.GENERIC_9x1; + default -> MenuType.GENERIC_9x3; + }, windowId, bottom, top, top.getContainerSize() / 9); + // Purpur end break; case DISPENSER: case DROPPER: diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java index 471ae4458e7ea7c29d7551b32cec98180fbccd4e..23db63c78e9fcf86cd498b3ed36ca50253c2fe97 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java @@ -83,7 +83,7 @@ public class CraftInventory implements Inventory { @Override public void setContents(ItemStack[] items) { - 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 88d3ca586ff6905f18a8ab9f0e229f440ed44088..27dd4eb4781a3c75772860c11db886e1038cecd2 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java @@ -9,7 +9,7 @@ import org.bukkit.inventory.AnvilInventory; public class CraftInventoryAnvil extends CraftResultInventory implements AnvilInventory { private final Location location; - private final AnvilMenu container; + public final AnvilMenu container; // Purpur - private -> public public CraftInventoryAnvil(Location location, Container inventory, Container resultInventory, AnvilMenu container) { super(inventory, resultInventory); @@ -57,4 +57,26 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn Preconditions.checkArgument(levels >= 0, "Maximum repair cost must be positive (or 0)"); container.maximumRepairCost = levels; } + + // Purpur start + @Override + public boolean canBypassCost() { + return container.bypassCost; + } + + @Override + public void setBypassCost(boolean bypassCost) { + container.bypassCost = bypassCost; + } + + @Override + public boolean canDoUnsafeEnchants() { + return container.canDoUnsafeEnchants; + } + + @Override + public void setDoUnsafeEnchants(boolean canDoUnsafeEnchants) { + container.canDoUnsafeEnchants = canDoUnsafeEnchants; + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java index d94ab2607d1ab657a6b37924ce5ebcbbc3984011..f594596029513426f974ba820ed4702703b667a4 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java @@ -13,6 +13,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import org.bukkit.Color; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.configuration.serialization.DelegateDeserialization; import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; import org.bukkit.craftbukkit.potion.CraftPotionUtil; @@ -42,6 +43,7 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { static final ItemMetaKey POTION_COLOR = new ItemMetaKey("CustomPotionColor", "custom-color"); static final ItemMetaKey ID = new ItemMetaKey("Id", "potion-id"); static final ItemMetaKey DEFAULT_POTION = new ItemMetaKey("Potion", "potion-type"); + static final ItemMetaKey KEY = new ItemMetaKey("Key", "namespacedkey"); // Purpur - add key // Having an initial "state" in ItemMeta seems bit dirty but the UNCRAFTABLE potion type // is treated as the empty form of the meta because it represents an empty potion with no effect @@ -91,7 +93,13 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { boolean ambient = effect.getBoolean(AMBIENT.NBT); boolean particles = effect.contains(SHOW_PARTICLES.NBT, CraftMagicNumbers.NBT.TAG_BYTE) ? effect.getBoolean(SHOW_PARTICLES.NBT) : true; boolean icon = effect.contains(SHOW_ICON.NBT, CraftMagicNumbers.NBT.TAG_BYTE) ? effect.getBoolean(SHOW_ICON.NBT) : particles; - this.customEffects.add(new PotionEffect(type, duration, amp, ambient, particles, icon)); + // Purpur start + NamespacedKey key = null; + if (tag.contains(KEY.NBT)) { + key = NamespacedKey.fromString(effect.getString(KEY.NBT)); + } + this.customEffects.add(new PotionEffect(type, duration, amp, ambient, particles, icon, key)); + // Purpur end } } } @@ -138,6 +146,11 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { effectData.putBoolean(AMBIENT.NBT, effect.isAmbient()); effectData.putBoolean(SHOW_PARTICLES.NBT, effect.hasParticles()); effectData.putBoolean(SHOW_ICON.NBT, effect.hasIcon()); + // Purpur start + if (effect.hasKey()) { + effectData.putString(KEY.NBT, effect.getKey().toString()); + } + // Purpur end effectList.add(effectData); } } @@ -199,7 +212,7 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { if (index != -1) { if (overwrite) { PotionEffect old = this.customEffects.get(index); - if (old.getAmplifier() == effect.getAmplifier() && old.getDuration() == effect.getDuration() && old.isAmbient() == effect.isAmbient()) { + if (old.getAmplifier() == effect.getAmplifier() && old.getDuration() == effect.getDuration() && old.isAmbient() == effect.isAmbient() && old.getKey() == effect.getKey()) { // Purpur - add key return false; } this.customEffects.set(index, effect); diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java index 13d25d118eb4d3ef35a4cdfb9bbde9ed83f6c04b..553ecc9b5631ffc0848a798bb3295f16d1722c1f 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java @@ -30,6 +30,7 @@ public interface CraftRecipe extends Recipe { } else if (bukkit instanceof RecipeChoice.ExactChoice) { stack = new Ingredient(((RecipeChoice.ExactChoice) bukkit).getChoices().stream().map((mat) -> new net.minecraft.world.item.crafting.Ingredient.ItemValue(CraftItemStack.asNMSCopy(mat)))); stack.exact = true; + stack.predicate = ((RecipeChoice.ExactChoice) bukkit).getPredicate(); // Purpur } else { throw new IllegalArgumentException("Unknown recipe stack instance " + bukkit); } diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java index 2677e21d8239bf0361a3bc5c9a50c328e54d70f6..544a79d5da661aff19e2019f7b83a3a49350bb68 100644 --- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java +++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java @@ -256,6 +256,7 @@ public final class CraftLegacy { } static { + if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressInitLegacyMaterialError) // Purpur LOGGER.warn("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); // Paper - doesn't need to be an error if (MinecraftServer.getServer() != null && MinecraftServer.getServer().isDebugging()) { new Exception().printStackTrace(); diff --git a/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java index 9683d7d103af66fffd68c11abc38fb4fd2f99482..18a4de76021ff8b500801ef0121003f8dd62a961 100644 --- a/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java +++ b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java @@ -46,4 +46,10 @@ public class CraftMapRenderer extends MapRenderer { } } + // Purpur - start + @Override + public boolean isExplorerMap() { + return this.worldMap.isExplorerMap; + } + // Purpur - end } diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java index acb69821a99aa69bce6d127e10976089c85be223..c5abd73981c5f4b41605eba0d44e6573dfd2a77a 100644 --- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java +++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java @@ -101,7 +101,7 @@ public class CraftPotionUtil { public static MobEffectInstance fromBukkit(PotionEffect effect) { MobEffect type = MobEffect.byId(effect.getType().getId()); - return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()); + return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.getKey()); // Purpur - add key } public static PotionEffect toBukkit(MobEffectInstance effect) { @@ -110,7 +110,7 @@ public class CraftPotionUtil { int duration = effect.getDuration(); boolean ambient = effect.isAmbient(); boolean particles = effect.isVisible(); - return new PotionEffect(type, duration, amp, ambient, particles); + return new PotionEffect(type, duration, amp, ambient, particles, effect.getKey()); // Purpur - add key } public static boolean equals(MobEffect mobEffect, PotionEffectType type) { diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java index d7ce4971d9271dbeff4adb9d852e4e7bdf60bf03..a4567188e2fe3f922bb6aeb71a2845d1a1be536f 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java @@ -501,7 +501,7 @@ public class CraftScheduler implements BukkitScheduler { this.parsePending(); } else { // this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(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 Paper"); // Paper + task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Purpur"); // Paper // Purpur // 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) } @@ -513,10 +513,10 @@ public class CraftScheduler implements BukkitScheduler { this.runners.remove(task.getTaskId()); } } - MinecraftTimings.bukkitSchedulerFinishTimer.startTiming(); // Paper + //MinecraftTimings.bukkitSchedulerFinishTimer.startTiming(); // Paper // Purpur this.pending.addAll(temp); temp.clear(); - MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming(); // Paper + //MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming(); // Paper // Purpur //this.debugHead = this.debugHead.getNextHead(currentTick); // Paper } @@ -559,7 +559,7 @@ public class CraftScheduler implements BukkitScheduler { } void parsePending() { // Paper - if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); // Paper + //if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); // Paper // Purpur CraftTask head = this.head; CraftTask task = head.getNext(); CraftTask lastTask = head; @@ -578,7 +578,7 @@ public class CraftScheduler implements BukkitScheduler { task.setNext(null); } this.head = lastTask; - if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); // Paper + //if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); // Paper // Purpur } private boolean isReady(final int currentTick) { diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java index 3f45bab0e9f7b3697e6d9d1092a1e6e579f7066f..4f1cf281c4bf68c37982d390da8779dea78dab18 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java @@ -96,13 +96,13 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot @Override public void run() { - try (Timing ignored = timings.startTiming()) { // Paper + //try (Timing ignored = timings.startTiming()) { // Paper // Purpur if (this.rTask != null) { this.rTask.run(); } else { this.cTask.accept(this); } - } // Paper + //} // Paper // Purpur } long getCreatedAt() { diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java index 500f2eb0df5a07637cd278c263e95592b0037eb6..895431584c476067790f1f131699d49eac3f4b8f 100644 --- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java +++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java @@ -115,7 +115,7 @@ public final class CraftScoreboardManager implements ScoreboardManager { public void getScoreboardScores(ObjectiveCriteria criteria, String name, Consumer consumer) { // Paper start - add timings for scoreboard search // plugins leaking scoreboards will make this very expensive, let server owners debug it easily - co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.startTimingIfSync(); + //co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.startTimingIfSync(); // Purpur try { // Paper end - add timings for scoreboard search for (CraftScoreboard scoreboard : this.scoreboards) { @@ -123,7 +123,7 @@ public final class CraftScoreboardManager implements ScoreboardManager { board.forAllObjectives(criteria, name, (score) -> consumer.accept(score)); } } finally { // Paper start - add timings for scoreboard search - co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.stopTimingIfSync(); + //co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.stopTimingIfSync(); // Purpur } // Paper end - add timings for scoreboard search } diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java index 2b4581f92543c11f31bcc1417e90d7f90b2aea20..ffca5970a6259b024c9aa935e22cf72ed8cd8e9f 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -470,7 +470,7 @@ public final class CraftMagicNumbers implements UnsafeValues { @Override public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { - return new gg.pufferfish.pufferfish.PufferfishVersionFetcher(); // Pufferfish + return new com.destroystokyo.paper.PaperVersionFetcher(); // Pufferfish // Purpur } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java index 80553face9c70c2a3d897681e7761df85b22d464..99597258e8e88cd9e2c901c4ac3ff7faeeabee2b 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java @@ -11,7 +11,7 @@ public final class Versioning { public static String getBukkitVersion() { String result = "Unknown-Version"; - InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/gg.pufferfish.pufferfish/pufferfish-api/pom.properties"); // Pufferfish + InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.purpurmc.purpur/purpur-api/pom.properties"); // Pufferfish // Purpur Properties properties = new Properties(); if (stream != null) { diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java index 2959f713ce75a1df9c6c7cf5e021690cfcb6e1e7..3fa9539cfb2c35beeba6d44fa05cee971444b7d0 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java +++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java @@ -23,7 +23,15 @@ public final class CommandPermissions { DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "kick", "Allows the user to kick players", PermissionDefault.OP, commands); DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "stop", "Allows the user to stop the server", PermissionDefault.OP, commands); DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "list", "Allows the user to list all online players", PermissionDefault.OP, commands); - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "gamemode", "Allows the user to change the gamemode of another player", PermissionDefault.OP, commands); + // Purpur start + Permission gamemodeVanilla = DefaultPermissions.registerPermission(PREFIX + "gamemode", "Allows the user to change the gamemode", PermissionDefault.OP, commands); + for (net.minecraft.world.level.GameType gametype : net.minecraft.world.level.GameType.values()) { + Permission gamemodeSelf = DefaultPermissions.registerPermission(PREFIX + "gamemode." + gametype.getName(), "Allows the user to set " + gametype.getName() + " gamemode for self", PermissionDefault.OP); + Permission gamemodeOther = DefaultPermissions.registerPermission(PREFIX + "gamemode." + gametype.getName() + ".other", "Allows the user to set " + gametype.getName() + " gamemode for other players", PermissionDefault.OP); + gamemodeSelf.addParent(gamemodeOther, true); + gamemodeVanilla.addParent(gamemodeSelf, true); + } + // Purpur end DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "experience", "Allows the user to give themselves or others arbitrary values of experience", PermissionDefault.OP, commands); // Paper - wrong permission; redirects are de-redirected and the root literal name is used, so xp -> 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..3633574e112f217b412217dd243a631dc4e9c40c --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java @@ -0,0 +1,655 @@ +package org.purpurmc.purpur; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import com.mojang.datafixers.util.Pair; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.food.FoodProperties; +import net.minecraft.world.food.Foods; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.purpurmc.purpur.command.PurpurCommand; +import org.purpurmc.purpur.task.TPSBarTask; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +@SuppressWarnings("unused") +public class PurpurConfig { + private static final String HEADER = "This is the main configuration file for Purpur.\n" + + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n" + + "with caution, and make sure you know what each option does before configuring.\n" + + "\n" + + "If you need help with the configuration or have any questions related to Purpur,\n" + + "join us in our Discord guild.\n" + + "\n" + + "Website: https://purpurmc.org \n" + + "Docs: https://purpurmc.org/docs \n"; + private static File CONFIG_FILE; + public static YamlConfiguration config; + + private static Map commands; + + public static int version; + static boolean verbose; + + public static void init(File configFile) { + CONFIG_FILE = configFile; + config = new YamlConfiguration(); + try { + config.load(CONFIG_FILE); + } catch (IOException ignore) { + } catch (InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Could not load purpur.yml, please correct your syntax errors", ex); + throw Throwables.propagate(ex); + } + config.options().header(HEADER); + config.options().copyDefaults(true); + verbose = getBoolean("verbose", false); + + commands = new HashMap<>(); + commands.put("purpur", new PurpurCommand("purpur")); + + version = getInt("config-version", 33); + set("config-version", 33); + + readConfig(PurpurConfig.class, null); + + Blocks.rebuildCache(); + } + + protected static void log(String s) { + if (verbose) { + log(Level.INFO, s); + } + } + + protected static void log(Level level, String s) { + Bukkit.getLogger().log(level, s); + } + + public static void registerCommands() { + for (Map.Entry entry : commands.entrySet()) { + MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Purpur", entry.getValue()); + } + } + + static void readConfig(Class clazz, Object instance) { + for (Method method : clazz.getDeclaredMethods()) { + if (Modifier.isPrivate(method.getModifiers())) { + if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) { + try { + method.setAccessible(true); + method.invoke(instance); + } catch (InvocationTargetException ex) { + throw Throwables.propagate(ex.getCause()); + } catch (Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex); + } + } + } + } + + try { + config.save(CONFIG_FILE); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex); + } + } + + private static void set(String path, Object val) { + config.addDefault(path, val); + config.set(path, val); + } + + private static String getString(String path, String def) { + config.addDefault(path, def); + return config.getString(path, config.getString(path)); + } + + private static boolean getBoolean(String path, boolean def) { + config.addDefault(path, def); + return config.getBoolean(path, config.getBoolean(path)); + } + + private static double getDouble(String path, double def) { + config.addDefault(path, def); + return config.getDouble(path, config.getDouble(path)); + } + + private static int getInt(String path, int def) { + config.addDefault(path, def); + return config.getInt(path, config.getInt(path)); + } + + private static List getList(String path, T def) { + config.addDefault(path, def); + return config.getList(path, config.getList(path)); + } + + static Map getMap(String path, Map def) { + if (def != null && config.getConfigurationSection(path) == null) { + config.addDefault(path, def); + return def; + } + return toMap(config.getConfigurationSection(path)); + } + + private static Map toMap(ConfigurationSection section) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + if (section != null) { + for (String key : section.getKeys(false)) { + Object obj = section.get(key); + if (obj != null) { + builder.put(key, obj instanceof ConfigurationSection val ? toMap(val) : obj); + } + } + } + return builder.build(); + } + + public static String cannotRideMob = "You cannot mount that mob"; + public static String afkBroadcastAway = "%s is now AFK"; + public static String afkBroadcastBack = "%s is no longer AFK"; + public static 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 = "Purpur"; + private static void serverModName() { + serverModName = getString("settings.server-mod-name", serverModName); + } + + public static double laggingThreshold = 19.0D; + private static void tickLoopSettings() { + laggingThreshold = getDouble("settings.lagging-threshold", laggingThreshold); + } + + public static boolean useAlternateKeepAlive = false; + private static void useAlternateKeepAlive() { + useAlternateKeepAlive = getBoolean("settings.use-alternate-keepalive", useAlternateKeepAlive); + } + + public static boolean disableGiveCommandDrops = false; + private static void disableGiveCommandDrops() { + disableGiveCommandDrops = getBoolean("settings.disable-give-dropping", disableGiveCommandDrops); + } + + public static String commandRamBarTitle = "Ram: / ()"; + public static BossBar.Overlay commandRamBarProgressOverlay = BossBar.Overlay.NOTCHED_20; + public static BossBar.Color commandRamBarProgressColorGood = BossBar.Color.GREEN; + public static BossBar.Color commandRamBarProgressColorMedium = BossBar.Color.YELLOW; + public static BossBar.Color commandRamBarProgressColorLow = BossBar.Color.RED; + public static String commandRamBarTextColorGood = ""; + public static String commandRamBarTextColorMedium = ""; + public static String commandRamBarTextColorLow = ""; + public static int commandRamBarTickInterval = 20; + public static String commandTPSBarTitle = "TPS: MSPT: Ping: ms"; + public static BossBar.Overlay commandTPSBarProgressOverlay = BossBar.Overlay.NOTCHED_20; + public static TPSBarTask.FillMode commandTPSBarProgressFillMode = TPSBarTask.FillMode.MSPT; + public static BossBar.Color commandTPSBarProgressColorGood = BossBar.Color.GREEN; + public static BossBar.Color commandTPSBarProgressColorMedium = BossBar.Color.YELLOW; + public static BossBar.Color commandTPSBarProgressColorLow = BossBar.Color.RED; + public static String commandTPSBarTextColorGood = ""; + public static String commandTPSBarTextColorMedium = ""; + public static String commandTPSBarTextColorLow = ""; + public static int commandTPSBarTickInterval = 20; + public static String commandCompassBarTitle = "S \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 W \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 N \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 E \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 S \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 W \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 N \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 E \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 "; + public static BossBar.Overlay commandCompassBarProgressOverlay = BossBar.Overlay.PROGRESS; + public static BossBar.Color commandCompassBarProgressColor = BossBar.Color.BLUE; + public static float commandCompassBarProgressPercent = 1.0F; + public static int commandCompassBarTickInterval = 5; + public static boolean commandGamemodeRequiresPermission = false; + public static boolean hideHiddenPlayersFromEntitySelector = false; + public static String uptimeFormat = ""; + public static String uptimeDay = "%02d day, "; + public static String uptimeDays = "%02d days, "; + public static String uptimeHour = "%02d hour, "; + public static String uptimeHours = "%02d hours, "; + public static String uptimeMinute = "%02d minute, and "; + public static String uptimeMinutes = "%02d minutes, and "; + public static String uptimeSecond = "%02d second"; + public static String uptimeSeconds = "%02d seconds"; + private static void commandSettings() { + commandRamBarTitle = getString("settings.command.rambar.title", commandRamBarTitle); + commandRamBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.rambar.overlay", commandRamBarProgressOverlay.name())); + commandRamBarProgressColorGood = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.good", commandRamBarProgressColorGood.name())); + commandRamBarProgressColorMedium = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.medium", commandRamBarProgressColorMedium.name())); + commandRamBarProgressColorLow = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.low", commandRamBarProgressColorLow.name())); + commandRamBarTextColorGood = getString("settings.command.rambar.text-color.good", commandRamBarTextColorGood); + commandRamBarTextColorMedium = getString("settings.command.rambar.text-color.medium", commandRamBarTextColorMedium); + commandRamBarTextColorLow = getString("settings.command.rambar.text-color.low", commandRamBarTextColorLow); + commandRamBarTickInterval = getInt("settings.command.rambar.tick-interval", commandRamBarTickInterval); + + commandTPSBarTitle = getString("settings.command.tpsbar.title", commandTPSBarTitle); + commandTPSBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.tpsbar.overlay", commandTPSBarProgressOverlay.name())); + commandTPSBarProgressFillMode = TPSBarTask.FillMode.valueOf(getString("settings.command.tpsbar.fill-mode", commandTPSBarProgressFillMode.name())); + commandTPSBarProgressColorGood = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.good", commandTPSBarProgressColorGood.name())); + commandTPSBarProgressColorMedium = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.medium", commandTPSBarProgressColorMedium.name())); + commandTPSBarProgressColorLow = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.low", commandTPSBarProgressColorLow.name())); + commandTPSBarTextColorGood = getString("settings.command.tpsbar.text-color.good", commandTPSBarTextColorGood); + commandTPSBarTextColorMedium = getString("settings.command.tpsbar.text-color.medium", commandTPSBarTextColorMedium); + commandTPSBarTextColorLow = getString("settings.command.tpsbar.text-color.low", commandTPSBarTextColorLow); + commandTPSBarTickInterval = getInt("settings.command.tpsbar.tick-interval", commandTPSBarTickInterval); + + commandCompassBarTitle = getString("settings.command.compass.title", commandCompassBarTitle); + commandCompassBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.compass.overlay", commandCompassBarProgressOverlay.name())); + commandCompassBarProgressColor = BossBar.Color.valueOf(getString("settings.command.compass.progress-color", commandCompassBarProgressColor.name())); + commandCompassBarProgressPercent = (float) getDouble("settings.command.compass.percent", commandCompassBarProgressPercent); + commandCompassBarTickInterval = getInt("settings.command.compass.tick-interval", commandCompassBarTickInterval); + + commandGamemodeRequiresPermission = getBoolean("settings.command.gamemode.requires-specific-permission", commandGamemodeRequiresPermission); + hideHiddenPlayersFromEntitySelector = getBoolean("settings.command.hide-hidden-players-from-entity-selector", hideHiddenPlayersFromEntitySelector); + uptimeFormat = getString("settings.command.uptime.format", uptimeFormat); + uptimeDay = getString("settings.command.uptime.day", uptimeDay); + uptimeDays = getString("settings.command.uptime.days", uptimeDays); + uptimeHour = getString("settings.command.uptime.hour", uptimeHour); + uptimeHours = getString("settings.command.uptime.hours", uptimeHours); + uptimeMinute = getString("settings.command.uptime.minute", uptimeMinute); + uptimeMinutes = getString("settings.command.uptime.minutes", uptimeMinutes); + uptimeSecond = getString("settings.command.uptime.second", uptimeSecond); + uptimeSeconds = getString("settings.command.uptime.seconds", uptimeSeconds); + } + + public static int barrelRows = 3; + public static boolean enderChestSixRows = false; + public static boolean enderChestPermissionRows = false; + public static boolean cryingObsidianValidForPortalFrame = false; + public static int beeInsideBeeHive = 3; + public static boolean anvilCumulativeCost = true; + public static int lightningRodRange = 128; + public static Set grindstoneIgnoredEnchants = new HashSet<>(); + public static boolean grindstoneRemoveAttributes = false; + public static boolean grindstoneRemoveDisplay = false; + public static int caveVinesMaxGrowthAge = 25; + public static int kelpMaxGrowthAge = 25; + public static int twistingVinesMaxGrowthAge = 25; + public static int weepingVinesMaxGrowthAge = 25; + private static void blockSettings() { + if (version < 3) { + boolean oldValue = getBoolean("settings.barrel.packed-barrels", true); + set("settings.blocks.barrel.six-rows", oldValue); + set("settings.packed-barrels", null); + oldValue = getBoolean("settings.large-ender-chests", true); + set("settings.blocks.ender_chest.six-rows", oldValue); + set("settings.large-ender-chests", null); + } + if (version < 20) { + boolean oldValue = getBoolean("settings.blocks.barrel.six-rows", false); + set("settings.blocks.barrel.rows", oldValue ? 6 : 3); + set("settings.blocks.barrel.six-rows", null); + } + barrelRows = getInt("settings.blocks.barrel.rows", barrelRows); + if (barrelRows < 1 || barrelRows > 6) { + Bukkit.getLogger().severe("settings.blocks.barrel.rows must be 1-6, resetting to default"); + barrelRows = 3; + } + org.bukkit.event.inventory.InventoryType.BARREL.setDefaultSize(switch (barrelRows) { + case 6 -> 54; + case 5 -> 45; + case 4 -> 36; + case 2 -> 18; + case 1 -> 9; + default -> 27; + }); + enderChestSixRows = getBoolean("settings.blocks.ender_chest.six-rows", enderChestSixRows); + org.bukkit.event.inventory.InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27); + enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); + cryingObsidianValidForPortalFrame = getBoolean("settings.blocks.crying_obsidian.valid-for-portal-frame", cryingObsidianValidForPortalFrame); + beeInsideBeeHive = getInt("settings.blocks.beehive.max-bees-inside", beeInsideBeeHive); + anvilCumulativeCost = getBoolean("settings.blocks.anvil.cumulative-cost", anvilCumulativeCost); + lightningRodRange = getInt("settings.blocks.lightning_rod.range", lightningRodRange); + ArrayList defaultCurses = new ArrayList<>(){{ + add("minecraft:binding_curse"); + add("minecraft:vanishing_curse"); + }}; + if (version < 24 && !getBoolean("settings.blocks.grindstone.ignore-curses", true)) { + defaultCurses.clear(); + } + getList("settings.blocks.grindstone.ignored-enchants", defaultCurses).forEach(key -> { + Enchantment enchantment = BuiltInRegistries.ENCHANTMENT.get(new ResourceLocation(key.toString())); + grindstoneIgnoredEnchants.add(enchantment); + }); + grindstoneRemoveAttributes = getBoolean("settings.blocks.grindstone.remove-attributes", grindstoneRemoveAttributes); + grindstoneRemoveDisplay = getBoolean("settings.blocks.grindstone.remove-name-and-lore", grindstoneRemoveDisplay); + caveVinesMaxGrowthAge = getInt("settings.blocks.cave_vines.max-growth-age", caveVinesMaxGrowthAge); + if (caveVinesMaxGrowthAge > 25) { + caveVinesMaxGrowthAge = 25; + log(Level.WARNING, "blocks.cave_vines.max-growth-age is set to above maximum allowed value of 25"); + log(Level.WARNING, "Using value of 25 to prevent issues"); + } + kelpMaxGrowthAge = getInt("settings.blocks.kelp.max-growth-age", kelpMaxGrowthAge); + if (kelpMaxGrowthAge > 25) { + kelpMaxGrowthAge = 25; + log(Level.WARNING, "blocks.kelp.max-growth-age is set to above maximum allowed value of 25"); + log(Level.WARNING, "Using value of 25 to prevent issues"); + } + twistingVinesMaxGrowthAge = getInt("settings.blocks.twisting_vines.max-growth-age", twistingVinesMaxGrowthAge); + if (twistingVinesMaxGrowthAge > 25) { + twistingVinesMaxGrowthAge = 25; + log(Level.WARNING, "blocks.twisting_vines.max-growth-age is set to above maximum allowed value of 25"); + log(Level.WARNING, "Using value of 25 to prevent issues"); + } + weepingVinesMaxGrowthAge = getInt("settings.blocks.weeping_vines.max-growth-age", weepingVinesMaxGrowthAge); + if (weepingVinesMaxGrowthAge > 25) { + weepingVinesMaxGrowthAge = 25; + log(Level.WARNING, "blocks.weeping_vines.max-growth-age is set to above maximum allowed value of 25"); + log(Level.WARNING, "Using value of 25 to prevent issues"); + } + } + + public static boolean allowInfinityMending = false; + public static boolean allowCrossbowInfinity = false; + public static boolean allowShearsLooting = false; + public static boolean allowUnsafeEnchants = false; + public static boolean allowInapplicableEnchants = true; + public static boolean allowIncompatibleEnchants = true; + public static boolean allowHigherEnchantsLevels = true; + public static boolean allowUnsafeEnchantCommand = false; + public static boolean replaceIncompatibleEnchants = false; + public static boolean clampEnchantLevels = true; + private static void enchantmentSettings() { + if (version < 5) { + boolean oldValue = getBoolean("settings.enchantment.allow-infinite-and-mending-together", false); + set("settings.enchantment.allow-infinity-and-mending-together", oldValue); + set("settings.enchantment.allow-infinite-and-mending-together", null); + } + if (version < 30) { + boolean oldValue = getBoolean("settings.enchantment.allow-unsafe-enchants", false); + set("settings.enchantment.anvil.allow-unsafe-enchants", oldValue); + set("settings.enchantment.anvil.allow-inapplicable-enchants", true); + set("settings.enchantment.anvil.allow-incompatible-enchants", true); + set("settings.enchantment.anvil.allow-higher-enchants-levels", true); + set("settings.enchantment.allow-unsafe-enchants", null); + } + allowInfinityMending = getBoolean("settings.enchantment.allow-infinity-and-mending-together", allowInfinityMending); + allowCrossbowInfinity = getBoolean("settings.enchantment.allow-infinity-on-crossbow", allowCrossbowInfinity); + allowShearsLooting = getBoolean("settings.enchantment.allow-looting-on-shears", allowShearsLooting); + allowUnsafeEnchants = getBoolean("settings.enchantment.anvil.allow-unsafe-enchants", allowUnsafeEnchants); + allowInapplicableEnchants = getBoolean("settings.enchantment.anvil.allow-inapplicable-enchants", allowInapplicableEnchants); + allowIncompatibleEnchants = getBoolean("settings.enchantment.anvil.allow-incompatible-enchants", allowIncompatibleEnchants); + allowHigherEnchantsLevels = getBoolean("settings.enchantment.anvil.allow-higher-enchants-levels", allowHigherEnchantsLevels); + allowUnsafeEnchantCommand = getBoolean("settings.enchantment.allow-unsafe-enchant-command", allowUnsafeEnchants); // allowUnsafeEnchants as default for backwards compatability + 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 loggerSuppressInitLegacyMaterialError = false; + public static boolean loggerSuppressIgnoredAdvancementWarnings = false; + public static boolean loggerSuppressUnrecognizedRecipeErrors = false; + public static boolean loggerSuppressSetBlockFarChunk = false; + public static boolean loggerSuppressLibraryLoader = false; + private static void loggerSettings() { + loggerSuppressInitLegacyMaterialError = getBoolean("settings.logger.suppress-init-legacy-material-errors", loggerSuppressInitLegacyMaterialError); + loggerSuppressIgnoredAdvancementWarnings = getBoolean("settings.logger.suppress-ignored-advancement-warnings", loggerSuppressIgnoredAdvancementWarnings); + loggerSuppressUnrecognizedRecipeErrors = getBoolean("settings.logger.suppress-unrecognized-recipe-errors", loggerSuppressUnrecognizedRecipeErrors); + loggerSuppressSetBlockFarChunk = getBoolean("settings.logger.suppress-setblock-in-far-chunk-errors", loggerSuppressSetBlockFarChunk); + loggerSuppressLibraryLoader = getBoolean("settings.logger.suppress-library-loader", loggerSuppressLibraryLoader); + org.bukkit.plugin.java.JavaPluginLoader.SuppressLibraryLoaderLogger = loggerSuppressLibraryLoader; + } + + public static boolean tpsCatchup = true; + private static void tpsCatchup() { + tpsCatchup = getBoolean("settings.tps-catchup", tpsCatchup); + } + + public static boolean useUPnP = false; + public static boolean maxJoinsPerSecond = false; + public static boolean kickForOutOfOrderChat = true; + private static void networkSettings() { + useUPnP = getBoolean("settings.network.upnp-port-forwarding", useUPnP); + maxJoinsPerSecond = getBoolean("settings.network.max-joins-per-second", maxJoinsPerSecond); + kickForOutOfOrderChat = getBoolean("settings.network.kick-for-out-of-order-chat", kickForOutOfOrderChat); + } + + public static java.util.regex.Pattern usernameValidCharactersPattern; + private static void usernameValidationSettings() { + String defaultPattern = "^[a-zA-Z0-9_.]*$"; + String setPattern = getString("settings.username-valid-characters", defaultPattern); + usernameValidCharactersPattern = java.util.regex.Pattern.compile(setPattern == null || setPattern.isBlank() ? defaultPattern : setPattern); + } + + private static void foodSettings() { + ConfigurationSection properties = config.getConfigurationSection("settings.food-properties"); + if (properties == null) { + config.addDefault("settings.food-properties", new HashMap<>()); + return; + } + + Map> effectDefaults = new HashMap<>(); + Map EFFECT_DEFAULT = Map.of( + "chance", 0.0F, + "duration", 0, + "amplifier", 0, + "ambient", false, + "visible", true, + "show-icon", true + ); + + properties.getKeys(false).forEach(foodKey -> { + FoodProperties food = Foods.ALL_PROPERTIES.get(foodKey); + if (food == null) { + PurpurConfig.log(Level.SEVERE, "Invalid food property: " + foodKey); + return; + } + FoodProperties foodDefaults = Foods.DEFAULT_PROPERTIES.get(foodKey); + food.setNutrition(properties.getInt(foodKey + ".nutrition", foodDefaults.getNutrition())); + food.setSaturationModifier((float) properties.getDouble(foodKey + ".saturation-modifier", foodDefaults.getSaturationModifier())); + food.setIsMeat(properties.getBoolean(foodKey + ".is-meat", foodDefaults.isMeat())); + food.setCanAlwaysEat(properties.getBoolean(foodKey + ".can-always-eat", foodDefaults.canAlwaysEat())); + food.setFastFood(properties.getBoolean(foodKey + ".fast-food", foodDefaults.isFastFood())); + ConfigurationSection effects = properties.getConfigurationSection(foodKey + ".effects"); + if (effects != null) { + effectDefaults.clear(); + foodDefaults.getEffects().forEach(pair -> { + MobEffectInstance effect = pair.getFirst(); + effectDefaults.put(effect.getEffect(), Map.of( + "chance", pair.getSecond(), + "duration", effect.getDuration(), + "amplifier", effect.getAmplifier(), + "ambient", effect.isAmbient(), + "visible", effect.isVisible(), + "show-icon", effect.showIcon() + )); + }); + effects.getKeys(false).forEach(effectKey -> { + MobEffect effect = BuiltInRegistries.MOB_EFFECT.get(new ResourceLocation(effectKey)); + if (effect == null) { + PurpurConfig.log(Level.SEVERE, "Invalid food property effect for " + foodKey + ": " + effectKey); + return; + } + + Map effectDefault = effectDefaults.get(effect); + if (effectDefault == null) { + effectDefault = EFFECT_DEFAULT; + } + + food.getEffects().removeIf(pair -> pair.getFirst().getEffect() == effect); + float chance = (float) effects.getDouble(effectKey + ".chance", ((Float) effectDefault.get("chance")).doubleValue()); + int duration = effects.getInt(effectKey + ".duration", (int) effectDefault.get("duration")); + if (chance <= 0.0F || duration < 0) { + return; + } + int amplifier = effects.getInt(effectKey + ".amplifier", (int) effectDefault.get("amplifier")); + boolean ambient = effects.getBoolean(effectKey + ".ambient", (boolean) effectDefault.get("ambient")); + boolean visible = effects.getBoolean(effectKey + ".visible", (boolean) effectDefault.get("visible")); + boolean showIcon = effects.getBoolean(effectKey + ".show-icon", (boolean) effectDefault.get("show-icon")); + food.getEffects().add(Pair.of(new MobEffectInstance(effect, duration, amplifier, ambient, visible, showIcon), chance)); + }); + } + }); + } + + public static boolean fixNetworkSerializedItemsInCreative = false; + private static void fixNetworkSerializedCreativeItems() { + fixNetworkSerializedItemsInCreative = getBoolean("settings.fix-network-serialized-items-in-creative", fixNetworkSerializedItemsInCreative); + } + + public static boolean fixProjectileLootingTransfer = false; + private static void fixProjectileLootingTransfer() { + fixProjectileLootingTransfer = getBoolean("settings.fix-projectile-looting-transfer", fixProjectileLootingTransfer); + } + + public static boolean clampAttributes = true; + private static void clampAttributes() { + clampAttributes = getBoolean("settings.clamp-attributes", clampAttributes); + } + + public static boolean limitArmor = true; + private static void limitArmor() { + limitArmor = getBoolean("settings.limit-armor", limitArmor); + } + + private static void blastResistanceSettings() { + getMap("settings.blast-resistance-overrides", Collections.emptyMap()).forEach((blockId, value) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { + log(Level.SEVERE, "Invalid block for `settings.blast-resistance-overrides`: " + blockId); + return; + } + if (!(value instanceof Number blastResistance)) { + log(Level.SEVERE, "Invalid blast resistance for `settings.blast-resistance-overrides." + blockId + "`: " + value); + return; + } + block.explosionResistance = blastResistance.floatValue(); + }); + } + private static void blockFallMultiplierSettings() { + getMap("settings.block-fall-multipliers", Map.ofEntries( + Map.entry("minecraft:hay_block", Map.of("damage", 0.2F)), + Map.entry("minecraft:white_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:light_gray_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:gray_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:black_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:brown_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:pink_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:red_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:orange_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:yellow_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:green_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:lime_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:cyan_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:light_blue_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:blue_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:purple_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:magenta_bed", Map.of("distance", 0.5F)) + )).forEach((blockId, value) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { + log(Level.SEVERE, "Invalid block for `settings.block-fall-multipliers`: " + blockId); + return; + } + if (!(value instanceof Map map)) { + log(Level.SEVERE, "Invalid fall multiplier for `settings.block-fall-multipliers." + blockId + "`: " + value + + ", expected a map with keys `damage` and `distance` to floats."); + return; + } + Object rawFallDamageMultiplier = map.get("damage"); + if (rawFallDamageMultiplier == null) rawFallDamageMultiplier = 1F; + if (!(rawFallDamageMultiplier instanceof Number fallDamageMultiplier)) { + log(Level.SEVERE, "Invalid multiplier for `settings.block-fall-multipliers." + blockId + ".damage`: " + map.get("damage")); + return; + } + Object rawFallDistanceMultiplier = map.get("distance"); + if (rawFallDistanceMultiplier == null) rawFallDistanceMultiplier = 1F; + if (!(rawFallDistanceMultiplier instanceof Number fallDistanceMultiplier)) { + log(Level.SEVERE, "Invalid multiplier for `settings.block-fall-multipliers." + blockId + ".distance`: " + map.get("distance")); + return; + } + block.fallDamageMultiplier = fallDamageMultiplier.floatValue(); + block.fallDistanceMultiplier = fallDistanceMultiplier.floatValue(); + }); + } + + public static boolean playerDeathsAlwaysShowItem = false; + private static void playerDeathsAlwaysShowItem() { + playerDeathsAlwaysShowItem = getBoolean("settings.player-deaths-always-show-item", playerDeathsAlwaysShowItem); + } +} 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..078102e636803f38facc049952813ff2f8b63594 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java @@ -0,0 +1,3239 @@ +package org.purpurmc.purpur; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.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 = true; + public boolean armorstandFixNametags = false; + public boolean armorstandMovement = true; + public boolean armorstandWaterMovement = true; + public boolean armorstandWaterFence = true; + public boolean armorstandPlaceWithArms = false; + private void armorstandSettings() { + armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight); + armorstandSetNameVisible = getBoolean("gameplay-mechanics.armorstand.set-name-visible-when-placing-with-custom-name", armorstandSetNameVisible); + armorstandFixNametags = getBoolean("gameplay-mechanics.armorstand.fix-nametags", armorstandFixNametags); + armorstandMovement = getBoolean("gameplay-mechanics.armorstand.can-movement-tick", armorstandMovement); + armorstandWaterMovement = getBoolean("gameplay-mechanics.armorstand.can-move-in-water", armorstandWaterMovement); + armorstandWaterFence = getBoolean("gameplay-mechanics.armorstand.can-move-in-water-over-fence", armorstandWaterFence); + armorstandPlaceWithArms = getBoolean("gameplay-mechanics.armorstand.place-with-arms-visible", armorstandPlaceWithArms); + } + + public boolean arrowMovementResetsDespawnCounter = true; + private void arrowSettings() { + arrowMovementResetsDespawnCounter = getBoolean("gameplay-mechanics.arrow.movement-resets-despawn-counter", arrowMovementResetsDespawnCounter); + } + + public boolean useBetterMending = false; + public double mendingMultiplier = 1.0; + public boolean alwaysTameInCreative = false; + public boolean boatEjectPlayersOnLand = false; + public boolean boatsDoFallDamage = false; + public boolean disableDropsOnCrammingDeath = false; + public boolean entitiesCanUsePortals = true; + public boolean entitiesPickUpLootBypassMobGriefing = false; + public boolean fireballsBypassMobGriefing = false; + public boolean imposeTeleportRestrictionsOnGateways = false; + public boolean milkCuresBadOmen = true; + public boolean milkClearsBeneficialEffects = true; + public boolean noteBlockIgnoreAbove = false; + public boolean persistentDroppableEntityDisplayNames = true; + public boolean persistentTileEntityDisplayNames = false; + public boolean projectilesBypassMobGriefing = false; + public boolean tickFluids = true; + public double mobsBlindnessMultiplier = 1; + public double tridentLoyaltyVoidReturnHeight = 0.0D; + public double voidDamageHeight = -64.0D; + public double voidDamageDealt = 4.0D; + public int raidCooldownSeconds = 0; + public int animalBreedingCooldownSeconds = 0; + public boolean mobsIgnoreRails = false; + public boolean rainStopsAfterSleep = true; + public boolean thunderStopsAfterSleep = true; + public int mobLastHurtByPlayerTime = 100; + private void miscGameplayMechanicsSettings() { + useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending); + mendingMultiplier = getDouble("gameplay-mechanics.mending-multiplier", mendingMultiplier); + alwaysTameInCreative = getBoolean("gameplay-mechanics.always-tame-in-creative", alwaysTameInCreative); + boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); + boatsDoFallDamage = getBoolean("gameplay-mechanics.boat.do-fall-damage", boatsDoFallDamage); + disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); + entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); + entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); + fireballsBypassMobGriefing = getBoolean("gameplay-mechanics.fireballs-bypass-mob-griefing", fireballsBypassMobGriefing); + imposeTeleportRestrictionsOnGateways = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-gateways", imposeTeleportRestrictionsOnGateways); + milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); + milkClearsBeneficialEffects = getBoolean("gameplay-mechanics.milk-clears-beneficial-effects", milkClearsBeneficialEffects); + noteBlockIgnoreAbove = getBoolean("gameplay-mechanics.note-block-ignore-above", noteBlockIgnoreAbove); + persistentTileEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityDisplayNames); + persistentDroppableEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-droppable-entity-display-names", persistentDroppableEntityDisplayNames); + projectilesBypassMobGriefing = getBoolean("gameplay-mechanics.projectiles-bypass-mob-griefing", projectilesBypassMobGriefing); + tickFluids = getBoolean("gameplay-mechanics.tick-fluids", tickFluids); + mobsBlindnessMultiplier = getDouble("gameplay-mechanics.entity-blindness-multiplier", mobsBlindnessMultiplier); + tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); + voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); + voidDamageDealt = getDouble("gameplay-mechanics.void-damage-dealt", voidDamageDealt); + raidCooldownSeconds = getInt("gameplay-mechanics.raid-cooldown-seconds", raidCooldownSeconds); + animalBreedingCooldownSeconds = getInt("gameplay-mechanics.animal-breeding-cooldown-seconds", animalBreedingCooldownSeconds); + mobsIgnoreRails = getBoolean("gameplay-mechanics.mobs-ignore-rails", mobsIgnoreRails); + rainStopsAfterSleep = getBoolean("gameplay-mechanics.rain-stops-after-sleep", rainStopsAfterSleep); + thunderStopsAfterSleep = getBoolean("gameplay-mechanics.thunder-stops-after-sleep", thunderStopsAfterSleep); + mobLastHurtByPlayerTime = getInt("gameplay-mechanics.mob-last-hurt-by-player-time", mobLastHurtByPlayerTime); + } + + public int daytimeTicks = 12000; + public int nighttimeTicks = 12000; + private void daytimeCycleSettings() { + daytimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.daytime", daytimeTicks); + nighttimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.nighttime", nighttimeTicks); + } + + public int drowningAirTicks = 300; + public int drowningDamageInterval = 20; + public double damageFromDrowning = 2.0F; + private void drowningSettings() { + drowningAirTicks = getInt("gameplay-mechanics.drowning.air-ticks", drowningAirTicks); + drowningDamageInterval = getInt("gameplay-mechanics.drowning.ticks-per-damage", drowningDamageInterval); + damageFromDrowning = getDouble("gameplay-mechanics.drowning.damage-from-drowning", damageFromDrowning); + } + + public int elytraDamagePerSecond = 1; + public double elytraDamageMultiplyBySpeed = 0; + public boolean elytraIgnoreUnbreaking = false; + public int elytraDamagePerFireworkBoost = 0; + public int elytraDamagePerTridentBoost = 0; + public boolean elytraKineticDamage = true; + private void elytraSettings() { + elytraDamagePerSecond = getInt("gameplay-mechanics.elytra.damage-per-second", elytraDamagePerSecond); + elytraDamageMultiplyBySpeed = getDouble("gameplay-mechanics.elytra.damage-multiplied-by-speed", elytraDamageMultiplyBySpeed); + elytraIgnoreUnbreaking = getBoolean("gameplay-mechanics.elytra.ignore-unbreaking", elytraIgnoreUnbreaking); + elytraDamagePerFireworkBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.firework", elytraDamagePerFireworkBoost); + elytraDamagePerTridentBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.trident", elytraDamagePerTridentBoost); + elytraKineticDamage = getBoolean("gameplay-mechanics.elytra.kinetic-damage", elytraKineticDamage); + } + + public int entityLifeSpan = 0; + public float entityLeftHandedChance = 0.05f; + public boolean entitySharedRandom = true; + private void entitySettings() { + entityLifeSpan = getInt("gameplay-mechanics.entity-lifespan", entityLifeSpan); + entityLeftHandedChance = (float) getDouble("gameplay-mechanics.entity-left-handed-chance", entityLeftHandedChance); + entitySharedRandom = getBoolean("settings.entity.shared-random", entitySharedRandom); + } + + public boolean explosionClampRadius = true; + private void explosionSettings() { + explosionClampRadius = getBoolean("gameplay-mechanics.clamp-explosion-radius", explosionClampRadius); + } + + public boolean infinityWorksWithoutArrows = false; + public boolean infinityWorksWithNormalArrows = true; + public boolean infinityWorksWithSpectralArrows = false; + public boolean infinityWorksWithTippedArrows = false; + private void infinityArrowsSettings() { + infinityWorksWithoutArrows = getBoolean("gameplay-mechanics.infinity-bow.works-without-arrows", infinityWorksWithoutArrows); + infinityWorksWithNormalArrows = getBoolean("gameplay-mechanics.infinity-bow.normal-arrows", infinityWorksWithNormalArrows); + infinityWorksWithSpectralArrows = getBoolean("gameplay-mechanics.infinity-bow.spectral-arrows", infinityWorksWithSpectralArrows); + infinityWorksWithTippedArrows = getBoolean("gameplay-mechanics.infinity-bow.tipped-arrows", infinityWorksWithTippedArrows); + } + + public List itemImmuneToCactus = new ArrayList<>(); + public List itemImmuneToExplosion = new ArrayList<>(); + public List itemImmuneToFire = new ArrayList<>(); + public List itemImmuneToLightning = new ArrayList<>(); + public boolean dontRunWithScissors = false; + public boolean ignoreScissorsInWater = false; + public boolean ignoreScissorsInLava = false; + public double scissorsRunningDamage = 1D; + public float enderPearlDamage = 5.0F; + public int enderPearlCooldown = 20; + public int enderPearlCooldownCreative = 20; + public float enderPearlEndermiteChance = 0.05F; + public int glowBerriesEatGlowDuration = 0; + public boolean shulkerBoxItemDropContentsWhenDestroyed = true; + public boolean compassItemShowsBossBar = false; + public boolean snowballExtinguishesFire = false; + public boolean snowballExtinguishesCandles = false; + public boolean snowballExtinguishesCampfires = false; + private void itemSettings() { + itemImmuneToCactus.clear(); + getList("gameplay-mechanics.item.immune.cactus", new ArrayList<>()).forEach(key -> { + if (key.toString().equals("*")) { + BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToCactus.add(item)); + return; + } + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); + if (item != Items.AIR) itemImmuneToCactus.add(item); + }); + itemImmuneToExplosion.clear(); + getList("gameplay-mechanics.item.immune.explosion", new ArrayList<>()).forEach(key -> { + if (key.toString().equals("*")) { + BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToExplosion.add(item)); + return; + } + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); + if (item != Items.AIR) itemImmuneToExplosion.add(item); + }); + itemImmuneToFire.clear(); + getList("gameplay-mechanics.item.immune.fire", new ArrayList<>()).forEach(key -> { + if (key.toString().equals("*")) { + BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToFire.add(item)); + return; + } + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); + if (item != Items.AIR) itemImmuneToFire.add(item); + }); + itemImmuneToLightning.clear(); + getList("gameplay-mechanics.item.immune.lightning", new ArrayList<>()).forEach(key -> { + if (key.toString().equals("*")) { + BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToLightning.add(item)); + return; + } + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); + if (item != Items.AIR) itemImmuneToLightning.add(item); + }); + dontRunWithScissors = getBoolean("gameplay-mechanics.item.shears.damage-if-sprinting", dontRunWithScissors); + ignoreScissorsInWater = getBoolean("gameplay-mechanics.item.shears.ignore-in-water", ignoreScissorsInWater); + ignoreScissorsInLava = getBoolean("gameplay-mechanics.item.shears.ignore-in-lava", ignoreScissorsInLava); + scissorsRunningDamage = getDouble("gameplay-mechanics.item.shears.sprinting-damage", scissorsRunningDamage); + enderPearlDamage = (float) getDouble("gameplay-mechanics.item.ender-pearl.damage", enderPearlDamage); + enderPearlCooldown = getInt("gameplay-mechanics.item.ender-pearl.cooldown", enderPearlCooldown); + enderPearlCooldownCreative = getInt("gameplay-mechanics.item.ender-pearl.creative-cooldown", enderPearlCooldownCreative); + enderPearlEndermiteChance = (float) getDouble("gameplay-mechanics.item.ender-pearl.endermite-spawn-chance", enderPearlEndermiteChance); + glowBerriesEatGlowDuration = getInt("gameplay-mechanics.item.glow_berries.eat-glow-duration", glowBerriesEatGlowDuration); + shulkerBoxItemDropContentsWhenDestroyed = getBoolean("gameplay-mechanics.item.shulker_box.drop-contents-when-destroyed", shulkerBoxItemDropContentsWhenDestroyed); + compassItemShowsBossBar = getBoolean("gameplay-mechanics.item.compass.holding-shows-bossbar", compassItemShowsBossBar); + snowballExtinguishesFire = getBoolean("gameplay-mechanics.item.snowball.extinguish.fire", snowballExtinguishesFire); + snowballExtinguishesCandles = getBoolean("gameplay-mechanics.item.snowball.extinguish.candles", snowballExtinguishesCandles); + snowballExtinguishesCampfires = getBoolean("gameplay-mechanics.item.snowball.extinguish.campfires", snowballExtinguishesCampfires); + } + + public double minecartMaxSpeed = 0.4D; + public boolean minecartPlaceAnywhere = false; + public boolean minecartControllable = false; + public float minecartControllableStepHeight = 1.0F; + public double minecartControllableHopBoost = 0.5D; + public boolean minecartControllableFallDamage = true; + public double minecartControllableBaseSpeed = 0.1D; + public Map minecartControllableBlockSpeeds = new HashMap<>(); + public double poweredRailBoostModifier = 0.06; + private void minecartSettings() { + if (PurpurConfig.version < 12) { + boolean oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.place-anywhere", minecartPlaceAnywhere); + set("gameplay-mechanics.controllable-minecarts.place-anywhere", null); + set("gameplay-mechanics.minecart.place-anywhere", oldBool); + oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.enabled", minecartControllable); + set("gameplay-mechanics.controllable-minecarts.enabled", null); + set("gameplay-mechanics.minecart.controllable.enabled", oldBool); + double oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.step-height", minecartControllableStepHeight); + set("gameplay-mechanics.controllable-minecarts.step-height", null); + set("gameplay-mechanics.minecart.controllable.step-height", oldDouble); + oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.hop-boost", minecartControllableHopBoost); + set("gameplay-mechanics.controllable-minecarts.hop-boost", null); + set("gameplay-mechanics.minecart.controllable.hop-boost", oldDouble); + oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.fall-damage", minecartControllableFallDamage); + set("gameplay-mechanics.controllable-minecarts.fall-damage", null); + set("gameplay-mechanics.minecart.controllable.fall-damage", oldBool); + oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.base-speed", minecartControllableBaseSpeed); + set("gameplay-mechanics.controllable-minecarts.base-speed", null); + set("gameplay-mechanics.minecart.controllable.base-speed", oldDouble); + ConfigurationSection section = getConfigurationSection("gameplay-mechanics.controllable-minecarts.block-speed"); + if (section != null) { + for (String key : section.getKeys(false)) { + if ("grass-block".equals(key)) key = "grass_block"; // oopsie + oldDouble = section.getDouble(key, minecartControllableBaseSpeed); + set("gameplay-mechanics.controllable-minecarts.block-speed." + key, null); + set("gameplay-mechanics.minecart.controllable.block-speed." + key, oldDouble); + } + set("gameplay-mechanics.controllable-minecarts.block-speed", null); + } + set("gameplay-mechanics.controllable-minecarts", null); + } + + minecartMaxSpeed = getDouble("gameplay-mechanics.minecart.max-speed", minecartMaxSpeed); + minecartPlaceAnywhere = getBoolean("gameplay-mechanics.minecart.place-anywhere", minecartPlaceAnywhere); + minecartControllable = getBoolean("gameplay-mechanics.minecart.controllable.enabled", minecartControllable); + minecartControllableStepHeight = (float) getDouble("gameplay-mechanics.minecart.controllable.step-height", minecartControllableStepHeight); + minecartControllableHopBoost = getDouble("gameplay-mechanics.minecart.controllable.hop-boost", minecartControllableHopBoost); + minecartControllableFallDamage = getBoolean("gameplay-mechanics.minecart.controllable.fall-damage", minecartControllableFallDamage); + minecartControllableBaseSpeed = getDouble("gameplay-mechanics.minecart.controllable.base-speed", minecartControllableBaseSpeed); + ConfigurationSection section = getConfigurationSection("gameplay-mechanics.minecart.controllable.block-speed"); + if (section != null) { + for (String key : section.getKeys(false)) { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key)); + if (block != Blocks.AIR) { + minecartControllableBlockSpeeds.put(block, section.getDouble(key, minecartControllableBaseSpeed)); + } + } + } else { + set("gameplay-mechanics.minecart.controllable.block-speed.grass_block", 0.3D); + set("gameplay-mechanics.minecart.controllable.block-speed.stone", 0.5D); + } + poweredRailBoostModifier = getDouble("gameplay-mechanics.minecart.powered-rail.boost-modifier", poweredRailBoostModifier); + } + + public float entityHealthRegenAmount = 1.0F; + public float entityMinimalHealthPoison = 1.0F; + public float entityPoisonDegenerationAmount = 1.0F; + public float entityWitherDegenerationAmount = 1.0F; + public float humanHungerExhaustionAmount = 0.005F; + public float humanSaturationRegenAmount = 1.0F; + private void mobEffectSettings() { + entityHealthRegenAmount = (float) getDouble("gameplay-mechanics.mob-effects.health-regen-amount", entityHealthRegenAmount); + entityMinimalHealthPoison = (float) getDouble("gameplay-mechanics.mob-effects.minimal-health-poison-amount", entityMinimalHealthPoison); + entityPoisonDegenerationAmount = (float) getDouble("gameplay-mechanics.mob-effects.poison-degeneration-amount", entityPoisonDegenerationAmount); + entityWitherDegenerationAmount = (float) getDouble("gameplay-mechanics.mob-effects.wither-degeneration-amount", entityWitherDegenerationAmount); + humanHungerExhaustionAmount = (float) getDouble("gameplay-mechanics.mob-effects.hunger-exhaustion-amount", humanHungerExhaustionAmount); + humanSaturationRegenAmount = (float) getDouble("gameplay-mechanics.mob-effects.saturation-regen-amount", humanSaturationRegenAmount); + } + + public boolean catSpawning; + public boolean patrolSpawning; + public boolean phantomSpawning; + public boolean villagerTraderSpawning; + public boolean villageSiegeSpawning; + public boolean mobSpawningIgnoreCreativePlayers = false; + private void mobSpawnerSettings() { + // values of "default" or null will default to true only if the world environment is normal (aka overworld) + Predicate predicate = (bool) -> (bool != null && bool) || (bool == null && environment == World.Environment.NORMAL); + catSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-cats", predicate); + patrolSpawning = getBoolean("gameplay-mechanics.mob-spawning.raid-patrols", predicate); + phantomSpawning = getBoolean("gameplay-mechanics.mob-spawning.phantoms", predicate); + villagerTraderSpawning = getBoolean("gameplay-mechanics.mob-spawning.wandering-traders", predicate); + villageSiegeSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-sieges", predicate); + mobSpawningIgnoreCreativePlayers = getBoolean("gameplay-mechanics.mob-spawning.ignore-creative-players", mobSpawningIgnoreCreativePlayers); + } + + public boolean disableObserverClocks = false; + private void observerSettings() { + disableObserverClocks = getBoolean("blocks.observer.disable-clock", disableObserverClocks); + } + + public int playerNetheriteFireResistanceDuration = 0; + public int playerNetheriteFireResistanceAmplifier = 0; + public boolean playerNetheriteFireResistanceAmbient = false; + public boolean playerNetheriteFireResistanceShowParticles = false; + public boolean playerNetheriteFireResistanceShowIcon = true; + private void playerNetheriteFireResistance() { + playerNetheriteFireResistanceDuration = getInt("gameplay-mechanics.player.netherite-fire-resistance.duration", playerNetheriteFireResistanceDuration); + playerNetheriteFireResistanceAmplifier = getInt("gameplay-mechanics.player.netherite-fire-resistance.amplifier", playerNetheriteFireResistanceAmplifier); + playerNetheriteFireResistanceAmbient = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.ambient", playerNetheriteFireResistanceAmbient); + playerNetheriteFireResistanceShowParticles = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-particles", playerNetheriteFireResistanceShowParticles); + playerNetheriteFireResistanceShowIcon = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-icon", playerNetheriteFireResistanceShowIcon); + } + + public boolean idleTimeoutKick = true; + public boolean idleTimeoutTickNearbyEntities = true; + public boolean idleTimeoutCountAsSleeping = false; + public boolean idleTimeoutUpdateTabList = false; + public boolean idleTimeoutTargetPlayer = true; + public int playerSpawnInvulnerableTicks = 60; + public boolean playerInvulnerableWhileAcceptingResourcePack = false; + public String playerDeathExpDropEquation = "expLevel * 7"; + public int playerDeathExpDropMax = 100; + public boolean teleportIfOutsideBorder = false; + public boolean teleportOnNetherCeilingDamage = false; + public boolean totemOfUndyingWorksInInventory = false; + public boolean playerFixStuckPortal = false; + public boolean creativeOnePunch = false; + public boolean playerSleepNearMonsters = false; + public boolean playersSkipNight = true; + public double playerCriticalDamageMultiplier = 1.5D; + public int playerBurpDelay = 10; + public boolean playerBurpWhenFull = false; + public int playerPortalWaitTime = 80; + public int playerCreativePortalWaitTime = 1; + public boolean playerRidableInWater = false; + public boolean playerRemoveBindingWithWeakness = false; + public int shiftRightClickRepairsMendingPoints = 0; + public int playerExpPickupDelay = 2; + public boolean playerVoidTrading = false; + private void playerSettings() { + if (PurpurConfig.version < 19) { + boolean oldVal = getBoolean("gameplay-mechanics.player.idle-timeout.mods-target", idleTimeoutTargetPlayer); + set("gameplay-mechanics.player.idle-timeout.mods-target", null); + set("gameplay-mechanics.player.idle-timeout.mobs-target", oldVal); + } + idleTimeoutKick = System.getenv("PURPUR_FORCE_IDLE_KICK") == null ? getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick) : Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")); + idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); + idleTimeoutCountAsSleeping = getBoolean("gameplay-mechanics.player.idle-timeout.count-as-sleeping", idleTimeoutCountAsSleeping); + idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList); + idleTimeoutTargetPlayer = getBoolean("gameplay-mechanics.player.idle-timeout.mobs-target", idleTimeoutTargetPlayer); + playerSpawnInvulnerableTicks = getInt("gameplay-mechanics.player.spawn-invulnerable-ticks", playerSpawnInvulnerableTicks); + playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack); + playerDeathExpDropEquation = getString("gameplay-mechanics.player.exp-dropped-on-death.equation", playerDeathExpDropEquation); + playerDeathExpDropMax = getInt("gameplay-mechanics.player.exp-dropped-on-death.maximum", playerDeathExpDropMax); + teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder); + teleportOnNetherCeilingDamage = getBoolean("gameplay-mechanics.player.teleport-on-nether-ceiling-damage", teleportOnNetherCeilingDamage); + totemOfUndyingWorksInInventory = getBoolean("gameplay-mechanics.player.totem-of-undying-works-in-inventory", totemOfUndyingWorksInInventory); + playerFixStuckPortal = getBoolean("gameplay-mechanics.player.fix-stuck-in-portal", playerFixStuckPortal); + creativeOnePunch = getBoolean("gameplay-mechanics.player.one-punch-in-creative", creativeOnePunch); + playerSleepNearMonsters = getBoolean("gameplay-mechanics.player.sleep-ignore-nearby-mobs", playerSleepNearMonsters); + playersSkipNight = getBoolean("gameplay-mechanics.player.can-skip-night", playersSkipNight); + playerCriticalDamageMultiplier = getDouble("gameplay-mechanics.player.critical-damage-multiplier", playerCriticalDamageMultiplier); + playerBurpDelay = getInt("gameplay-mechanics.player.burp-delay", playerBurpDelay); + playerBurpWhenFull = getBoolean("gameplay-mechanics.player.burp-when-full", playerBurpWhenFull); + playerPortalWaitTime = getInt("gameplay-mechanics.player.portal-wait-time", playerPortalWaitTime); + playerCreativePortalWaitTime = getInt("gameplay-mechanics.player.creative-portal-wait-time", playerCreativePortalWaitTime); + playerRidableInWater = getBoolean("gameplay-mechanics.player.ridable-in-water", playerRidableInWater); + playerRemoveBindingWithWeakness = getBoolean("gameplay-mechanics.player.curse-of-binding.remove-with-weakness", playerRemoveBindingWithWeakness); + shiftRightClickRepairsMendingPoints = getInt("gameplay-mechanics.player.shift-right-click-repairs-mending-points", shiftRightClickRepairsMendingPoints); + playerExpPickupDelay = getInt("gameplay-mechanics.player.exp-pickup-delay-ticks", playerExpPickupDelay); + playerVoidTrading = getBoolean("gameplay-mechanics.player.allow-void-trading", playerVoidTrading); + } + + private static boolean projectileDespawnRateSettingsMigrated = false; + private void projectileDespawnRateSettings() { + if (PurpurConfig.version < 28 && !projectileDespawnRateSettingsMigrated) { + migrateProjectileDespawnRateSettings(EntityType.DRAGON_FIREBALL); + migrateProjectileDespawnRateSettings(EntityType.EGG); + migrateProjectileDespawnRateSettings(EntityType.ENDER_PEARL); + migrateProjectileDespawnRateSettings(EntityType.EXPERIENCE_BOTTLE); + migrateProjectileDespawnRateSettings(EntityType.FIREWORK_ROCKET); + migrateProjectileDespawnRateSettings(EntityType.FISHING_BOBBER); + migrateProjectileDespawnRateSettings(EntityType.FIREBALL); + migrateProjectileDespawnRateSettings(EntityType.LLAMA_SPIT); + migrateProjectileDespawnRateSettings(EntityType.POTION); + migrateProjectileDespawnRateSettings(EntityType.SHULKER_BULLET); + migrateProjectileDespawnRateSettings(EntityType.SMALL_FIREBALL); + migrateProjectileDespawnRateSettings(EntityType.SNOWBALL); + migrateProjectileDespawnRateSettings(EntityType.WITHER_SKULL); + //PufferfishConfig.save(); + set("gameplay-mechanics.projectile-despawn-rates", null); + // pufferfish's entity_timeout is a global config + // we only want to migrate values from the + // default world (first world loaded) + projectileDespawnRateSettingsMigrated = true; + } + } + private void migrateProjectileDespawnRateSettings(EntityType type) { + //String pufferName = "entity_timeouts." + type.id.toUpperCase(Locale.ROOT); + //int value = getInt("gameplay-mechanics.projectile-despawn-rates." + type.id, -1); + //if (value != -1 && PufferfishConfig.getRawInt(pufferName, -1) == -1) { + // PufferfishConfig.setInt(pufferName, value); + // type.ttl = value; + //} + } + + public double bowProjectileOffset = 1.0D; + public double crossbowProjectileOffset = 1.0D; + public double eggProjectileOffset = 1.0D; + public double enderPearlProjectileOffset = 1.0D; + public double throwablePotionProjectileOffset = 1.0D; + public double tridentProjectileOffset = 1.0D; + public double snowballProjectileOffset = 1.0D; + private void projectileOffsetSettings() { + bowProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.bow", bowProjectileOffset); + crossbowProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.crossbow", crossbowProjectileOffset); + eggProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.egg", eggProjectileOffset); + enderPearlProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.ender-pearl", enderPearlProjectileOffset); + throwablePotionProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.throwable-potion", throwablePotionProjectileOffset); + tridentProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.trident", tridentProjectileOffset); + snowballProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.snowball", snowballProjectileOffset); + } + + public int snowballDamage = -1; + private void snowballSettings() { + snowballDamage = getInt("gameplay-mechanics.projectile-damage.snowball", snowballDamage); + } + + public boolean silkTouchEnabled = false; + public String silkTouchSpawnerName = "Monster Spawner"; + public List silkTouchSpawnerLore = new ArrayList<>(); + public List silkTouchTools = new ArrayList<>(); + public int minimumSilkTouchSpawnerRequire = 1; + private void silkTouchSettings() { + if (PurpurConfig.version < 21) { + String oldName = getString("gameplay-mechanics.silk-touch.spawner-name", silkTouchSpawnerName); + set("gameplay-mechanics.silk-touch.spawner-name", "" + ChatColor.toMM(oldName.replace("{mob}", ""))); + List list = new ArrayList<>(); + getList("gameplay-mechanics.silk-touch.spawner-lore", List.of("Spawns a ")) + .forEach(line -> list.add("" + ChatColor.toMM(line.toString().replace("{mob}", "")))); + set("gameplay-mechanics.silk-touch.spawner-lore", list); + } + silkTouchEnabled = getBoolean("gameplay-mechanics.silk-touch.enabled", silkTouchEnabled); + silkTouchSpawnerName = getString("gameplay-mechanics.silk-touch.spawner-name", silkTouchSpawnerName); + minimumSilkTouchSpawnerRequire = getInt("gameplay-mechanics.silk-touch.minimal-level", minimumSilkTouchSpawnerRequire); + silkTouchSpawnerLore.clear(); + getList("gameplay-mechanics.silk-touch.spawner-lore", List.of("Spawns a ")) + .forEach(line -> silkTouchSpawnerLore.add(line.toString())); + silkTouchTools.clear(); + getList("gameplay-mechanics.silk-touch.tools", List.of( + "minecraft:iron_pickaxe", + "minecraft:golden_pickaxe", + "minecraft:diamond_pickaxe", + "minecraft:netherite_pickaxe" + )).forEach(key -> { + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); + if (item != Items.AIR) silkTouchTools.add(item); + }); + } + + public Map axeStrippables = new HashMap<>(); + public Map axeWaxables = new HashMap<>(); + public Map axeWeatherables = new HashMap<>(); + public Map hoeTillables = new HashMap<>(); + public 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); + } + getMap("tools.axe.strippables", Map.ofEntries( + Map.entry("minecraft:oak_wood", Map.of("into", "minecraft:stripped_oak_wood", "drops", new HashMap())), + Map.entry("minecraft:oak_log", Map.of("into", "minecraft:stripped_oak_log", "drops", new HashMap())), + Map.entry("minecraft:dark_oak_wood", Map.of("into", "minecraft:stripped_dark_oak_wood", "drops", new HashMap())), + Map.entry("minecraft:dark_oak_log", Map.of("into", "minecraft:stripped_dark_oak_log", "drops", new HashMap())), + Map.entry("minecraft:acacia_wood", Map.of("into", "minecraft:stripped_acacia_wood", "drops", new HashMap())), + Map.entry("minecraft:acacia_log", Map.of("into", "minecraft:stripped_acacia_log", "drops", new HashMap())), + Map.entry("minecraft:birch_wood", Map.of("into", "minecraft:stripped_birch_wood", "drops", new HashMap())), + Map.entry("minecraft:birch_log", Map.of("into", "minecraft:stripped_birch_log", "drops", new HashMap())), + Map.entry("minecraft:jungle_wood", Map.of("into", "minecraft:stripped_jungle_wood", "drops", new HashMap())), + Map.entry("minecraft:jungle_log", Map.of("into", "minecraft:stripped_jungle_log", "drops", new HashMap())), + Map.entry("minecraft:spruce_wood", Map.of("into", "minecraft:stripped_spruce_wood", "drops", new HashMap())), + Map.entry("minecraft:spruce_log", Map.of("into", "minecraft:stripped_spruce_log", "drops", new HashMap())), + Map.entry("minecraft:mangrove_wood", Map.of("into", "minecraft:stripped_mangrove_wood", "drops", new HashMap())), + Map.entry("minecraft:mangrove_log", Map.of("into", "minecraft:stripped_mangrove_log", "drops", new HashMap())), + Map.entry("minecraft:cherry_log", Map.of("into", "minecraft:stripped_cherry_log", "drops", new HashMap())), + Map.entry("minecraft:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap())), + Map.entry("minecraft:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap())), + Map.entry("minecraft:warped_stem", Map.of("into", "minecraft:stripped_warped_stem", "drops", new HashMap())), + Map.entry("minecraft:warped_hyphae", Map.of("into", "minecraft:stripped_warped_hyphae", "drops", new HashMap())), + Map.entry("minecraft:crimson_stem", Map.of("into", "minecraft:stripped_crimson_stem", "drops", new HashMap())), + Map.entry("minecraft:crimson_hyphae", Map.of("into", "minecraft:stripped_crimson_hyphae", "drops", new HashMap()))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.strippables." + blockId + "`"); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.strippables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.strippables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + axeStrippables.put(block, new Strippable(into, drops)); + }); + getMap("tools.axe.waxables", Map.ofEntries( + Map.entry("minecraft:waxed_copper_block", Map.of("into", "minecraft:copper_block", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_copper", Map.of("into", "minecraft:exposed_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_copper", Map.of("into", "minecraft:weathered_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_copper", Map.of("into", "minecraft:oxidized_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_cut_copper", Map.of("into", "minecraft:cut_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_cut_copper", Map.of("into", "minecraft:exposed_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_cut_copper", Map.of("into", "minecraft:weathered_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_cut_copper", Map.of("into", "minecraft:oxidized_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_cut_copper_slab", Map.of("into", "minecraft:cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_cut_copper_slab", Map.of("into", "minecraft:exposed_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_cut_copper_slab", Map.of("into", "minecraft:weathered_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_cut_copper_slab", Map.of("into", "minecraft:oxidized_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:waxed_cut_copper_stairs", Map.of("into", "minecraft:cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_cut_copper_stairs", Map.of("into", "minecraft:exposed_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_cut_copper_stairs", Map.of("into", "minecraft:oxidized_cut_copper_stairs", "drops", new HashMap()))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.waxables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.waxables." + blockId + "`"); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.waxables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.waxables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.waxables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + axeWaxables.put(block, new Waxable(into, drops)); + }); + getMap("tools.axe.weatherables", Map.ofEntries( + Map.entry("minecraft:exposed_copper", Map.of("into", "minecraft:copper_block", "drops", new HashMap())), + Map.entry("minecraft:weathered_copper", Map.of("into", "minecraft:exposed_copper", "drops", new HashMap())), + Map.entry("minecraft:oxidized_copper", Map.of("into", "minecraft:weathered_copper", "drops", new HashMap())), + Map.entry("minecraft:exposed_cut_copper", Map.of("into", "minecraft:cut_copper", "drops", new HashMap())), + Map.entry("minecraft:weathered_cut_copper", Map.of("into", "minecraft:exposed_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:oxidized_cut_copper", Map.of("into", "minecraft:weathered_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:exposed_cut_copper_slab", Map.of("into", "minecraft:cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:weathered_cut_copper_slab", Map.of("into", "minecraft:exposed_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:oxidized_cut_copper_slab", Map.of("into", "minecraft:weathered_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:exposed_cut_copper_stairs", Map.of("into", "minecraft:cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:weathered_cut_copper_stairs", Map.of("into", "minecraft:exposed_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:oxidized_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap()))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.weatherables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.weatherables." + blockId + "`"); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.weatherables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.weatherables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.weatherables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + axeWeatherables.put(block, new Weatherable(into, drops)); + }); + getMap("tools.hoe.tillables", Map.ofEntries( + Map.entry("minecraft:grass_block", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap())), + Map.entry("minecraft:dirt_path", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap())), + Map.entry("minecraft:dirt", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap())), + Map.entry("minecraft:coarse_dirt", Map.of("condition", "air_above", "into", "minecraft:dirt", "drops", new HashMap())), + Map.entry("minecraft:rooted_dirt", Map.of("condition", "always", "into", "minecraft:dirt", "drops", Map.of("minecraft:hanging_roots", 1.0D)))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.hoe.tillables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.hoe.tillables." + blockId + "`"); return; } + String conditionId = (String) map.get("condition"); + Tillable.Condition condition = Tillable.Condition.get(conditionId); + if (condition == null) { PurpurConfig.log(Level.SEVERE, "Invalid condition for `tools.hoe.tillables." + blockId + ".condition`: " + conditionId); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.hoe.tillables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.hoe.tillables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.hoe.tillables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + hoeTillables.put(block, new Tillable(condition, into, drops)); + }); + 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.get(new ResourceLocation(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.get(new ResourceLocation(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.get(new ResourceLocation(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.get(new ResourceLocation(key.toString())); + if (!block.defaultBlockState().isAir()) { + doorRequiresRedstone.add(block); + } + }); + } + + public boolean dragonEggTeleport = true; + private void dragonEggSettings() { + dragonEggTeleport = getBoolean("blocks.dragon_egg.teleport", dragonEggTeleport); + } + + public boolean baselessEndCrystalExplode = true; + public double baselessEndCrystalExplosionPower = 6.0D; + public boolean baselessEndCrystalExplosionFire = false; + public net.minecraft.world.level.Level.ExplosionInteraction baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + public boolean basedEndCrystalExplode = true; + public double basedEndCrystalExplosionPower = 6.0D; + public boolean basedEndCrystalExplosionFire = false; + public net.minecraft.world.level.Level.ExplosionInteraction basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + public int endCrystalCramming = 0; + 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 endPortalSafeTeleporting = true; + private void endPortalSettings() { + endPortalSafeTeleporting = getBoolean("blocks.end_portal.safe-teleporting", endPortalSafeTeleporting); + } + + public boolean mobsSpawnOnPackedIce = true; + public boolean mobsSpawnOnBlueIce = true; + public boolean snowOnBlueIce = true; + private void iceSettings() { + mobsSpawnOnPackedIce = getBoolean("blocks.packed_ice.allow-mob-spawns", mobsSpawnOnPackedIce); + mobsSpawnOnBlueIce = getBoolean("blocks.blue_ice.allow-mob-spawns", mobsSpawnOnBlueIce); + snowOnBlueIce = getBoolean("blocks.blue_ice.allow-snow-formation", snowOnBlueIce); + } + + public int lavaInfiniteRequiredSources = 2; + public int lavaSpeedNether = 10; + public int lavaSpeedNotNether = 30; + private void lavaSettings() { + lavaInfiniteRequiredSources = getInt("blocks.lava.infinite-required-sources", lavaInfiniteRequiredSources); + lavaSpeedNether = getInt("blocks.lava.speed.nether", lavaSpeedNether); + lavaSpeedNotNether = getInt("blocks.lava.speed.not-nether", lavaSpeedNotNether); + } + + public int pistonBlockPushLimit = 12; + private void pistonSettings() { + pistonBlockPushLimit = getInt("blocks.piston.block-push-limit", pistonBlockPushLimit); + } + + public boolean magmaBlockDamageWhenSneaking = false; + public boolean magmaBlockDamageWithFrostWalker = false; + private void magmaBlockSettings() { + magmaBlockDamageWhenSneaking = getBoolean("blocks.magma-block.damage-when-sneaking", magmaBlockDamageWhenSneaking); + magmaBlockDamageWithFrostWalker = getBoolean("blocks.magma-block.damage-with-frost-walker", magmaBlockDamageWithFrostWalker); + } + + public boolean powderSnowBypassMobGriefing = false; + private void powderSnowSettings() { + powderSnowBypassMobGriefing = getBoolean("blocks.powder_snow.bypass-mob-griefing", powderSnowBypassMobGriefing); + } + + public int railActivationRange = 8; + private void railSettings() { + railActivationRange = getInt("blocks.powered-rail.activation-range", railActivationRange); + } + + public boolean respawnAnchorExplode = true; + public double respawnAnchorExplosionPower = 5.0D; + public boolean respawnAnchorExplosionFire = true; + public net.minecraft.world.level.Level.ExplosionInteraction respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + private void respawnAnchorSettings() { + if (PurpurConfig.version < 31) { + if ("DESTROY".equals(getString("blocks.respawn_anchor.explosion-effect", respawnAnchorExplosionEffect.name()))) { + set("blocks.respawn_anchor.explosion-effect", "BLOCK"); + } + } + respawnAnchorExplode = getBoolean("blocks.respawn_anchor.explode", respawnAnchorExplode); + respawnAnchorExplosionPower = getDouble("blocks.respawn_anchor.explosion-power", respawnAnchorExplosionPower); + respawnAnchorExplosionFire = getBoolean("blocks.respawn_anchor.explosion-fire", respawnAnchorExplosionFire); + try { + respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.respawn_anchor.explosion-effect", respawnAnchorExplosionEffect.name())); + } catch (IllegalArgumentException e) { + log(Level.SEVERE, "Unknown value for `blocks.respawn_anchor.explosion-effect`! Using default of `BLOCK`"); + respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + } + } + + public boolean fixSandDuping = true; + private void sandSettings() { + fixSandDuping = getBoolean("blocks.sand.fix-duping", fixSandDuping); + } + + public boolean sculkShriekerCanSummonDefault = false; + private void sculkShriekerSettings() { + sculkShriekerCanSummonDefault = getBoolean("blocks.sculk_shrieker.can-summon-default", sculkShriekerCanSummonDefault); + } + + public boolean shulkerBoxAllowOversizedStacks = false; + private void shulkerBoxSettings() { + shulkerBoxAllowOversizedStacks = getBoolean("blocks.shulker_box.allow-oversized-stacks", shulkerBoxAllowOversizedStacks); + } + + public boolean 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 = true; + public boolean turtleEggsBreakFromItems = true; + public boolean turtleEggsBreakFromMinecarts = true; + public boolean turtleEggsBypassMobGriefing = false; + public int turtleEggsRandomTickCrackChance = 500; + public boolean turtleEggsTramplingFeatherFalling = false; + private void turtleEggSettings() { + turtleEggsBreakFromExpOrbs = getBoolean("blocks.turtle_egg.break-from-exp-orbs", turtleEggsBreakFromExpOrbs); + turtleEggsBreakFromItems = getBoolean("blocks.turtle_egg.break-from-items", turtleEggsBreakFromItems); + turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); + turtleEggsBypassMobGriefing = getBoolean("blocks.turtle_egg.bypass-mob-griefing", turtleEggsBypassMobGriefing); + turtleEggsRandomTickCrackChance = getInt("blocks.turtle_egg.random-tick-crack-chance", turtleEggsRandomTickCrackChance); + turtleEggsTramplingFeatherFalling = getBoolean("blocks.turtle_egg.feather-fall-distance-affects-trampling", turtleEggsTramplingFeatherFalling); + } + + public int waterInfiniteRequiredSources = 2; + private void waterSources() { + waterInfiniteRequiredSources = getInt("blocks.water.infinite-required-sources", waterInfiniteRequiredSources); + } + + public boolean babiesAreRidable = true; + public boolean untamedTamablesAreRidable = true; + public boolean useNightVisionWhenRiding = false; + public boolean useDismountsUnderwaterTag = true; + private void ridableSettings() { + babiesAreRidable = getBoolean("ridable-settings.babies-are-ridable", babiesAreRidable); + untamedTamablesAreRidable = getBoolean("ridable-settings.untamed-tamables-are-ridable", untamedTamablesAreRidable); + useNightVisionWhenRiding = getBoolean("ridable-settings.use-night-vision", useNightVisionWhenRiding); + useDismountsUnderwaterTag = getBoolean("ridable-settings.use-dismounts-underwater-tag", useDismountsUnderwaterTag); + } + + public boolean allayRidable = false; + public boolean allayRidableInWater = true; + public boolean allayControllable = true; + public List allayRespectNBT = new ArrayList<>(); + private void allaySettings() { + allayRidable = getBoolean("mobs.allay.ridable", allayRidable); + allayRidableInWater = getBoolean("mobs.allay.ridable-in-water", allayRidableInWater); + allayControllable = getBoolean("mobs.allay.controllable", allayControllable); + allayRespectNBT.clear(); + getList("mobs.allay.respect-nbt", new ArrayList<>()).forEach(key -> allayRespectNBT.add(key.toString())); + } + + public boolean axolotlRidable = false; + public boolean axolotlControllable = true; + public double axolotlMaxHealth = 14.0D; + public int axolotlBreedingTicks = 6000; + public boolean axolotlTakeDamageFromWater = false; + public boolean axolotlAlwaysDropExp = false; + private void axolotlSettings() { + axolotlRidable = getBoolean("mobs.axolotl.ridable", axolotlRidable); + axolotlControllable = getBoolean("mobs.axolotl.controllable", axolotlControllable); + axolotlMaxHealth = getDouble("mobs.axolotl.attributes.max_health", axolotlMaxHealth); + axolotlBreedingTicks = getInt("mobs.axolotl.breeding-delay-ticks", axolotlBreedingTicks); + axolotlTakeDamageFromWater = getBoolean("mobs.axolotl.takes-damage-from-water", axolotlTakeDamageFromWater); + axolotlAlwaysDropExp = getBoolean("mobs.axolotl.always-drop-exp", axolotlAlwaysDropExp); + } + + public boolean batRidable = false; + public boolean batRidableInWater = true; + public boolean batControllable = true; + public double batMaxY = 320D; + public double batMaxHealth = 6.0D; + public double batFollowRange = 16.0D; + public double batKnockbackResistance = 0.0D; + public double batMovementSpeed = 0.6D; + public double batFlyingSpeed = 0.6D; + public double batArmor = 0.0D; + public double batArmorToughness = 0.0D; + public double batAttackKnockback = 0.0D; + public boolean batTakeDamageFromWater = false; + public boolean batAlwaysDropExp = false; + private void batSettings() { + batRidable = getBoolean("mobs.bat.ridable", batRidable); + batRidableInWater = getBoolean("mobs.bat.ridable-in-water", batRidableInWater); + batControllable = getBoolean("mobs.bat.controllable", batControllable); + batMaxY = getDouble("mobs.bat.ridable-max-y", batMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.bat.attributes.max-health", batMaxHealth); + set("mobs.bat.attributes.max-health", null); + set("mobs.bat.attributes.max_health", oldValue); + } + batMaxHealth = getDouble("mobs.bat.attributes.max_health", batMaxHealth); + 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 int beeBreedingTicks = 6000; + public boolean beeTakeDamageFromWater = false; + public boolean beeCanWorkAtNight = false; + public boolean beeCanWorkInRain = false; + public boolean beeAlwaysDropExp = false; + public boolean beeDiesAfterSting = true; + private void beeSettings() { + beeRidable = getBoolean("mobs.bee.ridable", beeRidable); + beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater); + beeControllable = getBoolean("mobs.bee.controllable", beeControllable); + beeMaxY = getDouble("mobs.bee.ridable-max-y", beeMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.bee.attributes.max-health", beeMaxHealth); + set("mobs.bee.attributes.max-health", null); + set("mobs.bee.attributes.max_health", oldValue); + } + beeMaxHealth = getDouble("mobs.bee.attributes.max_health", beeMaxHealth); + beeBreedingTicks = getInt("mobs.bee.breeding-delay-ticks", beeBreedingTicks); + beeTakeDamageFromWater = getBoolean("mobs.bee.takes-damage-from-water", beeTakeDamageFromWater); + beeCanWorkAtNight = getBoolean("mobs.bee.can-work-at-night", beeCanWorkAtNight); + beeCanWorkInRain = getBoolean("mobs.bee.can-work-in-rain", beeCanWorkInRain); + beeAlwaysDropExp = getBoolean("mobs.bee.always-drop-exp", beeAlwaysDropExp); + beeDiesAfterSting = getBoolean("mobs.bee.dies-after-sting", beeDiesAfterSting); + } + + public boolean blazeRidable = false; + public boolean blazeRidableInWater = true; + public boolean blazeControllable = true; + public double blazeMaxY = 320D; + public double blazeMaxHealth = 20.0D; + public boolean blazeTakeDamageFromWater = true; + public boolean blazeAlwaysDropExp = false; + private void blazeSettings() { + blazeRidable = getBoolean("mobs.blaze.ridable", blazeRidable); + blazeRidableInWater = getBoolean("mobs.blaze.ridable-in-water", blazeRidableInWater); + blazeControllable = getBoolean("mobs.blaze.controllable", blazeControllable); + blazeMaxY = getDouble("mobs.blaze.ridable-max-y", blazeMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.blaze.attributes.max-health", blazeMaxHealth); + set("mobs.blaze.attributes.max-health", null); + set("mobs.blaze.attributes.max_health", oldValue); + } + blazeMaxHealth = getDouble("mobs.blaze.attributes.max_health", blazeMaxHealth); + blazeTakeDamageFromWater = getBoolean("mobs.blaze.takes-damage-from-water", blazeTakeDamageFromWater); + blazeAlwaysDropExp = getBoolean("mobs.blaze.always-drop-exp", blazeAlwaysDropExp); + } + + public 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 int catSpawnDelay = 1200; + public int catSpawnSwampHutScanRange = 16; + public int catSpawnVillageScanRange = 48; + public int catBreedingTicks = 6000; + public DyeColor catDefaultCollarColor = DyeColor.RED; + public boolean catTakeDamageFromWater = false; + public boolean catAlwaysDropExp = false; + private void catSettings() { + catRidable = getBoolean("mobs.cat.ridable", catRidable); + catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater); + catControllable = getBoolean("mobs.cat.controllable", catControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.cat.attributes.max-health", catMaxHealth); + set("mobs.cat.attributes.max-health", null); + set("mobs.cat.attributes.max_health", oldValue); + } + catMaxHealth = getDouble("mobs.cat.attributes.max_health", catMaxHealth); + catSpawnDelay = getInt("mobs.cat.spawn-delay", catSpawnDelay); + catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange); + catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange); + catBreedingTicks = getInt("mobs.cat.breeding-delay-ticks", catBreedingTicks); + try { + catDefaultCollarColor = DyeColor.valueOf(getString("mobs.cat.default-collar-color", 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 boolean caveSpiderTakeDamageFromWater = false; + public boolean caveSpiderAlwaysDropExp = false; + private void caveSpiderSettings() { + caveSpiderRidable = getBoolean("mobs.cave_spider.ridable", caveSpiderRidable); + caveSpiderRidableInWater = getBoolean("mobs.cave_spider.ridable-in-water", caveSpiderRidableInWater); + caveSpiderControllable = getBoolean("mobs.cave_spider.controllable", caveSpiderControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.cave_spider.attributes.max-health", caveSpiderMaxHealth); + set("mobs.cave_spider.attributes.max-health", null); + set("mobs.cave_spider.attributes.max_health", oldValue); + } + caveSpiderMaxHealth = getDouble("mobs.cave_spider.attributes.max_health", caveSpiderMaxHealth); + caveSpiderTakeDamageFromWater = getBoolean("mobs.cave_spider.takes-damage-from-water", caveSpiderTakeDamageFromWater); + caveSpiderAlwaysDropExp = getBoolean("mobs.cave_spider.always-drop-exp", caveSpiderAlwaysDropExp); + } + + public boolean chickenRidable = false; + public boolean chickenRidableInWater = false; + public boolean chickenControllable = true; + public double chickenMaxHealth = 4.0D; + public boolean chickenRetaliate = false; + public int chickenBreedingTicks = 6000; + public boolean chickenTakeDamageFromWater = false; + public boolean chickenAlwaysDropExp = false; + private void chickenSettings() { + chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable); + chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater); + chickenControllable = getBoolean("mobs.chicken.controllable", chickenControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.chicken.attributes.max-health", chickenMaxHealth); + set("mobs.chicken.attributes.max-health", null); + set("mobs.chicken.attributes.max_health", oldValue); + } + chickenMaxHealth = getDouble("mobs.chicken.attributes.max_health", chickenMaxHealth); + chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate); + chickenBreedingTicks = getInt("mobs.chicken.breeding-delay-ticks", chickenBreedingTicks); + chickenTakeDamageFromWater = getBoolean("mobs.chicken.takes-damage-from-water", chickenTakeDamageFromWater); + chickenAlwaysDropExp = getBoolean("mobs.chicken.always-drop-exp", chickenAlwaysDropExp); + } + + public boolean codRidable = false; + public boolean codControllable = true; + public double codMaxHealth = 3.0D; + public boolean codTakeDamageFromWater = false; + public boolean codAlwaysDropExp = false; + private void codSettings() { + codRidable = getBoolean("mobs.cod.ridable", codRidable); + codControllable = getBoolean("mobs.cod.controllable", codControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.cod.attributes.max-health", codMaxHealth); + set("mobs.cod.attributes.max-health", null); + set("mobs.cod.attributes.max_health", oldValue); + } + codMaxHealth = getDouble("mobs.cod.attributes.max_health", codMaxHealth); + codTakeDamageFromWater = getBoolean("mobs.cod.takes-damage-from-water", codTakeDamageFromWater); + codAlwaysDropExp = getBoolean("mobs.cod.always-drop-exp", codAlwaysDropExp); + } + + public boolean cowRidable = false; + public boolean cowRidableInWater = true; + public boolean cowControllable = true; + public double cowMaxHealth = 10.0D; + public int cowFeedMushrooms = 0; + public int cowBreedingTicks = 6000; + public boolean cowTakeDamageFromWater = false; + public double cowNaturallyAggressiveToPlayersChance = 0.0D; + public double cowNaturallyAggressiveToPlayersDamage = 2.0D; + public boolean cowAlwaysDropExp = false; + private void cowSettings() { + if (PurpurConfig.version < 22) { + double oldValue = getDouble("mobs.cow.naturally-aggressive-to-players-chance", cowNaturallyAggressiveToPlayersChance); + set("mobs.cow.naturally-aggressive-to-players-chance", null); + set("mobs.cow.naturally-aggressive-to-players.chance", oldValue); + } + cowRidable = getBoolean("mobs.cow.ridable", cowRidable); + cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater); + cowControllable = getBoolean("mobs.cow.controllable", cowControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.cow.attributes.max-health", cowMaxHealth); + set("mobs.cow.attributes.max-health", null); + set("mobs.cow.attributes.max_health", oldValue); + } + cowMaxHealth = getDouble("mobs.cow.attributes.max_health", cowMaxHealth); + cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms); + cowBreedingTicks = getInt("mobs.cow.breeding-delay-ticks", cowBreedingTicks); + cowTakeDamageFromWater = getBoolean("mobs.cow.takes-damage-from-water", cowTakeDamageFromWater); + cowNaturallyAggressiveToPlayersChance = getDouble("mobs.cow.naturally-aggressive-to-players.chance", cowNaturallyAggressiveToPlayersChance); + cowNaturallyAggressiveToPlayersDamage = getDouble("mobs.cow.naturally-aggressive-to-players.damage", cowNaturallyAggressiveToPlayersDamage); + cowAlwaysDropExp = getBoolean("mobs.cow.always-drop-exp", cowAlwaysDropExp); + } + + public boolean creeperRidable = false; + public boolean creeperRidableInWater = true; + public boolean creeperControllable = true; + public double creeperMaxHealth = 20.0D; + public double creeperChargedChance = 0.0D; + public boolean creeperAllowGriefing = true; + public boolean creeperBypassMobGriefing = false; + public boolean creeperTakeDamageFromWater = false; + public boolean creeperExplodeWhenKilled = false; + public boolean creeperHealthRadius = false; + public boolean creeperAlwaysDropExp = false; + public double creeperHeadVisibilityPercent = 0.5D; + public boolean creeperEncircleTarget = false; + private void creeperSettings() { + creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable); + creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater); + creeperControllable = getBoolean("mobs.creeper.controllable", creeperControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.creeper.attributes.max-health", creeperMaxHealth); + set("mobs.creeper.attributes.max-health", null); + set("mobs.creeper.attributes.max_health", oldValue); + } + creeperMaxHealth = getDouble("mobs.creeper.attributes.max_health", creeperMaxHealth); + creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); + creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing); + creeperBypassMobGriefing = getBoolean("mobs.creeper.bypass-mob-griefing", creeperBypassMobGriefing); + creeperTakeDamageFromWater = getBoolean("mobs.creeper.takes-damage-from-water", creeperTakeDamageFromWater); + creeperExplodeWhenKilled = getBoolean("mobs.creeper.explode-when-killed", creeperExplodeWhenKilled); + creeperHealthRadius = getBoolean("mobs.creeper.health-impacts-explosion", creeperHealthRadius); + creeperAlwaysDropExp = getBoolean("mobs.creeper.always-drop-exp", creeperAlwaysDropExp); + creeperHeadVisibilityPercent = getDouble("mobs.creeper.head-visibility-percent", creeperHeadVisibilityPercent); + creeperEncircleTarget = getBoolean("mobs.creeper.encircle-target", creeperEncircleTarget); + } + + public boolean dolphinRidable = false; + public boolean dolphinControllable = true; + public int dolphinSpitCooldown = 20; + public float dolphinSpitSpeed = 1.0F; + public float dolphinSpitDamage = 2.0F; + public double dolphinMaxHealth = 10.0D; + public boolean dolphinDisableTreasureSearching = false; + public boolean dolphinTakeDamageFromWater = false; + public double dolphinNaturallyAggressiveToPlayersChance = 0.0D; + public boolean dolphinAlwaysDropExp = false; + private void dolphinSettings() { + dolphinRidable = getBoolean("mobs.dolphin.ridable", dolphinRidable); + dolphinControllable = getBoolean("mobs.dolphin.controllable", dolphinControllable); + dolphinSpitCooldown = getInt("mobs.dolphin.spit.cooldown", dolphinSpitCooldown); + dolphinSpitSpeed = (float) getDouble("mobs.dolphin.spit.speed", dolphinSpitSpeed); + dolphinSpitDamage = (float) getDouble("mobs.dolphin.spit.damage", dolphinSpitDamage); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.dolphin.attributes.max-health", dolphinMaxHealth); + set("mobs.dolphin.attributes.max-health", null); + set("mobs.dolphin.attributes.max_health", oldValue); + } + dolphinMaxHealth = getDouble("mobs.dolphin.attributes.max_health", dolphinMaxHealth); + dolphinDisableTreasureSearching = getBoolean("mobs.dolphin.disable-treasure-searching", dolphinDisableTreasureSearching); + dolphinTakeDamageFromWater = getBoolean("mobs.dolphin.takes-damage-from-water", dolphinTakeDamageFromWater); + dolphinNaturallyAggressiveToPlayersChance = getDouble("mobs.dolphin.naturally-aggressive-to-players-chance", dolphinNaturallyAggressiveToPlayersChance); + dolphinAlwaysDropExp = getBoolean("mobs.dolphin.always-drop-exp", dolphinAlwaysDropExp); + } + + public boolean donkeyRidableInWater = false; + public double donkeyMaxHealthMin = 15.0D; + public double donkeyMaxHealthMax = 30.0D; + public double donkeyJumpStrengthMin = 0.5D; + public double donkeyJumpStrengthMax = 0.5D; + public double donkeyMovementSpeedMin = 0.175D; + public double donkeyMovementSpeedMax = 0.175D; + public int donkeyBreedingTicks = 6000; + public boolean donkeyTakeDamageFromWater = false; + public boolean donkeyAlwaysDropExp = false; + private void donkeySettings() { + donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.donkey.attributes.max-health.min", donkeyMaxHealthMin); + double oldMax = getDouble("mobs.donkey.attributes.max-health.max", donkeyMaxHealthMax); + set("mobs.donkey.attributes.max-health", null); + set("mobs.donkey.attributes.max_health.min", oldMin); + set("mobs.donkey.attributes.max_health.max", oldMax); + } + donkeyMaxHealthMin = getDouble("mobs.donkey.attributes.max_health.min", donkeyMaxHealthMin); + donkeyMaxHealthMax = getDouble("mobs.donkey.attributes.max_health.max", donkeyMaxHealthMax); + donkeyJumpStrengthMin = getDouble("mobs.donkey.attributes.jump_strength.min", donkeyJumpStrengthMin); + donkeyJumpStrengthMax = getDouble("mobs.donkey.attributes.jump_strength.max", donkeyJumpStrengthMax); + donkeyMovementSpeedMin = getDouble("mobs.donkey.attributes.movement_speed.min", donkeyMovementSpeedMin); + donkeyMovementSpeedMax = getDouble("mobs.donkey.attributes.movement_speed.max", donkeyMovementSpeedMax); + donkeyBreedingTicks = getInt("mobs.donkey.breeding-delay-ticks", donkeyBreedingTicks); + donkeyTakeDamageFromWater = getBoolean("mobs.donkey.takes-damage-from-water", donkeyTakeDamageFromWater); + donkeyAlwaysDropExp = getBoolean("mobs.donkey.always-drop-exp", donkeyAlwaysDropExp); + } + + public boolean drownedRidable = false; + public boolean drownedRidableInWater = true; + public boolean drownedControllable = true; + public double drownedMaxHealth = 20.0D; + public double drownedSpawnReinforcements = 0.1D; + public boolean drownedJockeyOnlyBaby = true; + public double drownedJockeyChance = 0.05D; + public boolean drownedJockeyTryExistingChickens = true; + public boolean drownedTakeDamageFromWater = false; + public boolean drownedBreakDoors = false; + public boolean drownedAlwaysDropExp = false; + private void drownedSettings() { + drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable); + drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater); + drownedControllable = getBoolean("mobs.drowned.controllable", drownedControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.drowned.attributes.max-health", drownedMaxHealth); + set("mobs.drowned.attributes.max-health", null); + set("mobs.drowned.attributes.max_health", oldValue); + } + drownedMaxHealth = getDouble("mobs.drowned.attributes.max_health", drownedMaxHealth); + drownedSpawnReinforcements = getDouble("mobs.drowned.attributes.spawn_reinforcements", drownedSpawnReinforcements); + drownedJockeyOnlyBaby = getBoolean("mobs.drowned.jockey.only-babies", drownedJockeyOnlyBaby); + drownedJockeyChance = getDouble("mobs.drowned.jockey.chance", drownedJockeyChance); + drownedJockeyTryExistingChickens = getBoolean("mobs.drowned.jockey.try-existing-chickens", drownedJockeyTryExistingChickens); + drownedTakeDamageFromWater = getBoolean("mobs.drowned.takes-damage-from-water", drownedTakeDamageFromWater); + drownedBreakDoors = getBoolean("mobs.drowned.can-break-doors", drownedBreakDoors); + drownedAlwaysDropExp = getBoolean("mobs.drowned.always-drop-exp", drownedAlwaysDropExp); + } + + public boolean elderGuardianRidable = false; + public boolean elderGuardianControllable = true; + public double elderGuardianMaxHealth = 80.0D; + public boolean elderGuardianTakeDamageFromWater = false; + public boolean elderGuardianAlwaysDropExp = false; + private void elderGuardianSettings() { + elderGuardianRidable = getBoolean("mobs.elder_guardian.ridable", elderGuardianRidable); + elderGuardianControllable = getBoolean("mobs.elder_guardian.controllable", elderGuardianControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.elder_guardian.attributes.max-health", elderGuardianMaxHealth); + set("mobs.elder_guardian.attributes.max-health", null); + set("mobs.elder_guardian.attributes.max_health", oldValue); + } + elderGuardianMaxHealth = getDouble("mobs.elder_guardian.attributes.max_health", elderGuardianMaxHealth); + elderGuardianTakeDamageFromWater = getBoolean("mobs.elder_guardian.takes-damage-from-water", elderGuardianTakeDamageFromWater); + elderGuardianAlwaysDropExp = getBoolean("mobs.elder_guardian.always-drop-exp", elderGuardianAlwaysDropExp); + } + + public boolean enchantmentTableLapisPersists = false; + private void enchantmentTableSettings() { + enchantmentTableLapisPersists = getBoolean("blocks.enchantment-table.lapis-persists", enchantmentTableLapisPersists); + } + + public boolean enderDragonRidable = false; + public boolean enderDragonRidableInWater = true; + public boolean enderDragonControllable = true; + public double enderDragonMaxY = 320D; + public double enderDragonMaxHealth = 200.0D; + public boolean enderDragonAlwaysDropsFullExp = false; + public boolean enderDragonBypassMobGriefing = false; + public boolean enderDragonTakeDamageFromWater = false; + public boolean enderDragonCanRideVehicles = false; + private void enderDragonSettings() { + enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable); + enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater); + enderDragonControllable = getBoolean("mobs.ender_dragon.controllable", enderDragonControllable); + enderDragonMaxY = getDouble("mobs.ender_dragon.ridable-max-y", enderDragonMaxY); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.ender_dragon.max-health", enderDragonMaxHealth); + set("mobs.ender_dragon.max-health", null); + set("mobs.ender_dragon.attributes.max_health", oldValue); + } else if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.ender_dragon.attributes.max-health", enderDragonMaxHealth); + set("mobs.ender_dragon.attributes.max-health", null); + set("mobs.ender_dragon.attributes.max_health", oldValue); + } + enderDragonMaxHealth = getDouble("mobs.ender_dragon.attributes.max_health", enderDragonMaxHealth); + enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp); + enderDragonBypassMobGriefing = getBoolean("mobs.ender_dragon.bypass-mob-griefing", enderDragonBypassMobGriefing); + enderDragonTakeDamageFromWater = getBoolean("mobs.ender_dragon.takes-damage-from-water", enderDragonTakeDamageFromWater); + enderDragonCanRideVehicles = getBoolean("mobs.ender_dragon.can-ride-vehicles", enderDragonCanRideVehicles); + } + + public boolean endermanRidable = false; + public boolean endermanRidableInWater = true; + public boolean endermanControllable = true; + public double endermanMaxHealth = 40.0D; + public boolean endermanAllowGriefing = true; + public boolean endermanDespawnEvenWithBlock = false; + public boolean endermanBypassMobGriefing = false; + public boolean endermanTakeDamageFromWater = true; + public boolean endermanAggroEndermites = true; + public boolean endermanAggroEndermitesOnlyIfPlayerSpawned = false; + public boolean endermanIgnorePlayerDragonHead = false; + public boolean endermanDisableStareAggro = false; + public boolean endermanIgnoreProjectiles = false; + public boolean endermanAlwaysDropExp = false; + private void endermanSettings() { + endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); + endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); + endermanControllable = getBoolean("mobs.enderman.controllable", endermanControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.enderman.attributes.max-health", endermanMaxHealth); + set("mobs.enderman.attributes.max-health", null); + set("mobs.enderman.attributes.max_health", oldValue); + } + if (PurpurConfig.version < 15) { + // remove old option + set("mobs.enderman.aggressive-towards-spawned-endermites", null); + } + endermanMaxHealth = getDouble("mobs.enderman.attributes.max_health", endermanMaxHealth); + endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing); + endermanDespawnEvenWithBlock = getBoolean("mobs.enderman.can-despawn-with-held-block", endermanDespawnEvenWithBlock); + endermanBypassMobGriefing = getBoolean("mobs.enderman.bypass-mob-griefing", endermanBypassMobGriefing); + endermanTakeDamageFromWater = getBoolean("mobs.enderman.takes-damage-from-water", endermanTakeDamageFromWater); + endermanAggroEndermites = getBoolean("mobs.enderman.aggressive-towards-endermites", endermanAggroEndermites); + endermanAggroEndermitesOnlyIfPlayerSpawned = getBoolean("mobs.enderman.aggressive-towards-endermites-only-spawned-by-player-thrown-ender-pearls", endermanAggroEndermitesOnlyIfPlayerSpawned); + endermanIgnorePlayerDragonHead = getBoolean("mobs.enderman.ignore-players-wearing-dragon-head", endermanIgnorePlayerDragonHead); + endermanDisableStareAggro = getBoolean("mobs.enderman.disable-player-stare-aggression", endermanDisableStareAggro); + endermanIgnoreProjectiles = getBoolean("mobs.enderman.ignore-projectiles", endermanIgnoreProjectiles); + endermanAlwaysDropExp = getBoolean("mobs.enderman.always-drop-exp", endermanAlwaysDropExp); + } + + public boolean endermiteRidable = false; + public boolean endermiteRidableInWater = true; + public boolean endermiteControllable = true; + public double endermiteMaxHealth = 8.0D; + public boolean endermiteTakeDamageFromWater = false; + public boolean endermiteAlwaysDropExp = false; + private void endermiteSettings() { + endermiteRidable = getBoolean("mobs.endermite.ridable", endermiteRidable); + endermiteRidableInWater = getBoolean("mobs.endermite.ridable-in-water", endermiteRidableInWater); + endermiteControllable = getBoolean("mobs.endermite.controllable", endermiteControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.endermite.attributes.max-health", endermiteMaxHealth); + set("mobs.endermite.attributes.max-health", null); + set("mobs.endermite.attributes.max_health", oldValue); + } + endermiteMaxHealth = getDouble("mobs.endermite.attributes.max_health", endermiteMaxHealth); + endermiteTakeDamageFromWater = getBoolean("mobs.endermite.takes-damage-from-water", endermiteTakeDamageFromWater); + endermiteAlwaysDropExp = getBoolean("mobs.endermite.always-drop-exp", endermiteAlwaysDropExp); + } + + public boolean evokerRidable = false; + public boolean evokerRidableInWater = true; + public boolean evokerControllable = true; + public double evokerMaxHealth = 24.0D; + public boolean evokerBypassMobGriefing = false; + public boolean evokerTakeDamageFromWater = false; + public boolean evokerAlwaysDropExp = false; + private void evokerSettings() { + evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable); + evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater); + evokerControllable = getBoolean("mobs.evoker.controllable", evokerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.evoker.attributes.max-health", evokerMaxHealth); + set("mobs.evoker.attributes.max-health", null); + set("mobs.evoker.attributes.max_health", oldValue); + } + evokerMaxHealth = getDouble("mobs.evoker.attributes.max_health", evokerMaxHealth); + evokerBypassMobGriefing = getBoolean("mobs.evoker.bypass-mob-griefing", evokerBypassMobGriefing); + evokerTakeDamageFromWater = getBoolean("mobs.evoker.takes-damage-from-water", evokerTakeDamageFromWater); + evokerAlwaysDropExp = getBoolean("mobs.evoker.always-drop-exp", evokerAlwaysDropExp); + } + + public boolean foxRidable = false; + public boolean foxRidableInWater = true; + public boolean foxControllable = true; + public double foxMaxHealth = 10.0D; + public boolean foxTypeChangesWithTulips = false; + public int foxBreedingTicks = 6000; + public boolean foxBypassMobGriefing = false; + public boolean foxTakeDamageFromWater = false; + public boolean foxAlwaysDropExp = false; + private void foxSettings() { + foxRidable = getBoolean("mobs.fox.ridable", foxRidable); + foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater); + foxControllable = getBoolean("mobs.fox.controllable", foxControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.fox.attributes.max-health", foxMaxHealth); + set("mobs.fox.attributes.max-health", null); + set("mobs.fox.attributes.max_health", oldValue); + } + foxMaxHealth = getDouble("mobs.fox.attributes.max_health", foxMaxHealth); + foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); + foxBreedingTicks = getInt("mobs.fox.breeding-delay-ticks", foxBreedingTicks); + foxBypassMobGriefing = getBoolean("mobs.fox.bypass-mob-griefing", foxBypassMobGriefing); + foxTakeDamageFromWater = getBoolean("mobs.fox.takes-damage-from-water", foxTakeDamageFromWater); + foxAlwaysDropExp = getBoolean("mobs.fox.always-drop-exp", foxAlwaysDropExp); + } + + public boolean frogRidable = false; + public boolean frogRidableInWater = true; + public boolean frogControllable = true; + public float frogRidableJumpHeight = 0.65F; + public int frogBreedingTicks = 6000; + private void frogSettings() { + frogRidable = getBoolean("mobs.frog.ridable", frogRidable); + frogRidableInWater = getBoolean("mobs.frog.ridable-in-water", frogRidableInWater); + frogControllable = getBoolean("mobs.frog.controllable", frogControllable); + frogRidableJumpHeight = (float) getDouble("mobs.frog.ridable-jump-height", frogRidableJumpHeight); + frogBreedingTicks = getInt("mobs.frog.breeding-delay-ticks", frogBreedingTicks); + } + + public boolean ghastRidable = false; + public boolean ghastRidableInWater = true; + public boolean ghastControllable = true; + public double ghastMaxY = 320D; + public double ghastMaxHealth = 10.0D; + public boolean ghastTakeDamageFromWater = false; + public boolean ghastAlwaysDropExp = false; + private void ghastSettings() { + ghastRidable = getBoolean("mobs.ghast.ridable", ghastRidable); + ghastRidableInWater = getBoolean("mobs.ghast.ridable-in-water", ghastRidableInWater); + ghastControllable = getBoolean("mobs.ghast.controllable", ghastControllable); + ghastMaxY = getDouble("mobs.ghast.ridable-max-y", ghastMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.ghast.attributes.max-health", ghastMaxHealth); + set("mobs.ghast.attributes.max-health", null); + set("mobs.ghast.attributes.max_health", oldValue); + } + ghastMaxHealth = getDouble("mobs.ghast.attributes.max_health", ghastMaxHealth); + ghastTakeDamageFromWater = getBoolean("mobs.ghast.takes-damage-from-water", ghastTakeDamageFromWater); + ghastAlwaysDropExp = getBoolean("mobs.ghast.always-drop-exp", ghastAlwaysDropExp); + } + + public boolean giantRidable = false; + public boolean giantRidableInWater = true; + public boolean giantControllable = true; + public double giantMovementSpeed = 0.5D; + public double giantAttackDamage = 50.0D; + public double giantMaxHealth = 100.0D; + public float giantStepHeight = 2.0F; + public float giantJumpHeight = 1.0F; + public boolean giantHaveAI = false; + public boolean giantHaveHostileAI = false; + public boolean giantTakeDamageFromWater = false; + public boolean giantAlwaysDropExp = false; + private void giantSettings() { + giantRidable = getBoolean("mobs.giant.ridable", giantRidable); + giantRidableInWater = getBoolean("mobs.giant.ridable-in-water", giantRidableInWater); + giantControllable = getBoolean("mobs.giant.controllable", giantControllable); + giantMovementSpeed = getDouble("mobs.giant.movement-speed", giantMovementSpeed); + giantAttackDamage = getDouble("mobs.giant.attack-damage", giantAttackDamage); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.giant.max-health", giantMaxHealth); + set("mobs.giant.max-health", null); + set("mobs.giant.attributes.max_health", oldValue); + } else if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.giant.attributes.max-health", giantMaxHealth); + set("mobs.giant.attributes.max-health", null); + set("mobs.giant.attributes.max_health", oldValue); + } + giantMaxHealth = getDouble("mobs.giant.attributes.max_health", giantMaxHealth); + giantStepHeight = (float) getDouble("mobs.giant.step-height", giantStepHeight); + giantJumpHeight = (float) getDouble("mobs.giant.jump-height", giantJumpHeight); + giantHaveAI = getBoolean("mobs.giant.have-ai", giantHaveAI); + giantHaveHostileAI = getBoolean("mobs.giant.have-hostile-ai", giantHaveHostileAI); + giantTakeDamageFromWater = getBoolean("mobs.giant.takes-damage-from-water", giantTakeDamageFromWater); + giantAlwaysDropExp = getBoolean("mobs.giant.always-drop-exp", giantAlwaysDropExp); + } + + public boolean glowSquidRidable = false; + public boolean glowSquidControllable = true; + public double glowSquidMaxHealth = 10.0D; + public boolean glowSquidsCanFly = false; + public boolean glowSquidTakeDamageFromWater = false; + public boolean glowSquidAlwaysDropExp = false; + private void glowSquidSettings() { + glowSquidRidable = getBoolean("mobs.glow_squid.ridable", glowSquidRidable); + glowSquidControllable = getBoolean("mobs.glow_squid.controllable", glowSquidControllable); + glowSquidMaxHealth = getDouble("mobs.glow_squid.attributes.max_health", glowSquidMaxHealth); + glowSquidsCanFly = getBoolean("mobs.glow_squid.can-fly", glowSquidsCanFly); + glowSquidTakeDamageFromWater = getBoolean("mobs.glow_squid.takes-damage-from-water", glowSquidTakeDamageFromWater); + glowSquidAlwaysDropExp = getBoolean("mobs.glow_squid.always-drop-exp", glowSquidAlwaysDropExp); + } + + public boolean goatRidable = false; + public boolean goatRidableInWater = true; + public boolean goatControllable = true; + public double goatMaxHealth = 10.0D; + public int goatBreedingTicks = 6000; + public boolean goatTakeDamageFromWater = false; + public boolean goatAlwaysDropExp = false; + private void goatSettings() { + goatRidable = getBoolean("mobs.goat.ridable", goatRidable); + goatRidableInWater = getBoolean("mobs.goat.ridable-in-water", goatRidableInWater); + goatControllable = getBoolean("mobs.goat.controllable", goatControllable); + goatMaxHealth = getDouble("mobs.goat.attributes.max_health", goatMaxHealth); + goatBreedingTicks = getInt("mobs.goat.breeding-delay-ticks", goatBreedingTicks); + goatTakeDamageFromWater = getBoolean("mobs.goat.takes-damage-from-water", goatTakeDamageFromWater); + goatAlwaysDropExp = getBoolean("mobs.goat.always-drop-exp", goatAlwaysDropExp); + } + + public boolean guardianRidable = false; + public boolean guardianControllable = true; + public double guardianMaxHealth = 30.0D; + public boolean guardianTakeDamageFromWater = false; + public boolean guardianAlwaysDropExp = false; + private void guardianSettings() { + guardianRidable = getBoolean("mobs.guardian.ridable", guardianRidable); + guardianControllable = getBoolean("mobs.guardian.controllable", guardianControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.guardian.attributes.max-health", guardianMaxHealth); + set("mobs.guardian.attributes.max-health", null); + set("mobs.guardian.attributes.max_health", oldValue); + } + guardianMaxHealth = getDouble("mobs.guardian.attributes.max_health", guardianMaxHealth); + guardianTakeDamageFromWater = getBoolean("mobs.guardian.takes-damage-from-water", guardianTakeDamageFromWater); + guardianAlwaysDropExp = getBoolean("mobs.guardian.always-drop-exp", guardianAlwaysDropExp); + } + + public boolean forceHalloweenSeason = false; + public float chanceHeadHalloweenOnEntity = 0.25F; + private void halloweenSetting() { + forceHalloweenSeason = getBoolean("gameplay-mechanics.halloween.force", forceHalloweenSeason); + chanceHeadHalloweenOnEntity = (float) getDouble("gameplay-mechanics.halloween.head-chance", chanceHeadHalloweenOnEntity); + } + + public boolean hoglinRidable = false; + public boolean hoglinRidableInWater = true; + public boolean hoglinControllable = true; + public double hoglinMaxHealth = 40.0D; + public int hoglinBreedingTicks = 6000; + public boolean hoglinTakeDamageFromWater = false; + public boolean hoglinAlwaysDropExp = false; + private void hoglinSettings() { + hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable); + hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater); + hoglinControllable = getBoolean("mobs.hoglin.controllable", hoglinControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.hoglin.attributes.max-health", hoglinMaxHealth); + set("mobs.hoglin.attributes.max-health", null); + set("mobs.hoglin.attributes.max_health", oldValue); + } + hoglinMaxHealth = getDouble("mobs.hoglin.attributes.max_health", hoglinMaxHealth); + hoglinBreedingTicks = getInt("mobs.hoglin.breeding-delay-ticks", hoglinBreedingTicks); + hoglinTakeDamageFromWater = getBoolean("mobs.hoglin.takes-damage-from-water", hoglinTakeDamageFromWater); + hoglinAlwaysDropExp = getBoolean("mobs.hoglin.always-drop-exp", hoglinAlwaysDropExp); + } + + public boolean horseRidableInWater = false; + public double horseMaxHealthMin = 15.0D; + public double horseMaxHealthMax = 30.0D; + public double horseJumpStrengthMin = 0.4D; + public double horseJumpStrengthMax = 1.0D; + public double horseMovementSpeedMin = 0.1125D; + public double horseMovementSpeedMax = 0.3375D; + public int horseBreedingTicks = 6000; + public boolean horseTakeDamageFromWater = false; + public boolean horseAlwaysDropExp = false; + private void horseSettings() { + horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.horse.attributes.max-health.min", horseMaxHealthMin); + double oldMax = getDouble("mobs.horse.attributes.max-health.max", horseMaxHealthMax); + set("mobs.horse.attributes.max-health", null); + set("mobs.horse.attributes.max_health.min", oldMin); + set("mobs.horse.attributes.max_health.max", oldMax); + } + horseMaxHealthMin = getDouble("mobs.horse.attributes.max_health.min", horseMaxHealthMin); + horseMaxHealthMax = getDouble("mobs.horse.attributes.max_health.max", horseMaxHealthMax); + horseJumpStrengthMin = getDouble("mobs.horse.attributes.jump_strength.min", horseJumpStrengthMin); + horseJumpStrengthMax = getDouble("mobs.horse.attributes.jump_strength.max", horseJumpStrengthMax); + horseMovementSpeedMin = getDouble("mobs.horse.attributes.movement_speed.min", horseMovementSpeedMin); + horseMovementSpeedMax = getDouble("mobs.horse.attributes.movement_speed.max", horseMovementSpeedMax); + horseBreedingTicks = getInt("mobs.horse.breeding-delay-ticks", horseBreedingTicks); + horseTakeDamageFromWater = getBoolean("mobs.horse.takes-damage-from-water", horseTakeDamageFromWater); + horseAlwaysDropExp = getBoolean("mobs.horse.always-drop-exp", horseAlwaysDropExp); + } + + public boolean huskRidable = false; + public boolean huskRidableInWater = true; + public boolean huskControllable = true; + public double huskMaxHealth = 20.0D; + public double huskSpawnReinforcements = 0.1D; + public boolean huskJockeyOnlyBaby = true; + public double huskJockeyChance = 0.05D; + public boolean huskJockeyTryExistingChickens = true; + public boolean huskTakeDamageFromWater = false; + public boolean huskAlwaysDropExp = false; + private void huskSettings() { + huskRidable = getBoolean("mobs.husk.ridable", huskRidable); + huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater); + huskControllable = getBoolean("mobs.husk.controllable", huskControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.husk.attributes.max-health", huskMaxHealth); + set("mobs.husk.attributes.max-health", null); + set("mobs.husk.attributes.max_health", oldValue); + } + huskMaxHealth = getDouble("mobs.husk.attributes.max_health", huskMaxHealth); + huskSpawnReinforcements = getDouble("mobs.husk.attributes.spawn_reinforcements", huskSpawnReinforcements); + huskJockeyOnlyBaby = getBoolean("mobs.husk.jockey.only-babies", huskJockeyOnlyBaby); + huskJockeyChance = getDouble("mobs.husk.jockey.chance", huskJockeyChance); + huskJockeyTryExistingChickens = getBoolean("mobs.husk.jockey.try-existing-chickens", huskJockeyTryExistingChickens); + huskTakeDamageFromWater = getBoolean("mobs.husk.takes-damage-from-water", huskTakeDamageFromWater); + huskAlwaysDropExp = getBoolean("mobs.husk.always-drop-exp", huskAlwaysDropExp); + } + + public boolean illusionerRidable = false; + public boolean illusionerRidableInWater = true; + public boolean illusionerControllable = true; + public double illusionerMovementSpeed = 0.5D; + public double illusionerFollowRange = 18.0D; + public double illusionerMaxHealth = 32.0D; + public boolean illusionerTakeDamageFromWater = false; + public boolean illusionerAlwaysDropExp = false; + private void illusionerSettings() { + illusionerRidable = getBoolean("mobs.illusioner.ridable", illusionerRidable); + illusionerRidableInWater = getBoolean("mobs.illusioner.ridable-in-water", illusionerRidableInWater); + illusionerControllable = getBoolean("mobs.illusioner.controllable", illusionerControllable); + illusionerMovementSpeed = getDouble("mobs.illusioner.movement-speed", illusionerMovementSpeed); + illusionerFollowRange = getDouble("mobs.illusioner.follow-range", illusionerFollowRange); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.illusioner.max-health", illusionerMaxHealth); + set("mobs.illusioner.max-health", null); + set("mobs.illusioner.attributes.max_health", oldValue); + } else if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth); + set("mobs.illusioner.attributes.max-health", null); + set("mobs.illusioner.attributes.max_health", oldValue); + } + illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max_health", illusionerMaxHealth); + illusionerTakeDamageFromWater = getBoolean("mobs.illusioner.takes-damage-from-water", illusionerTakeDamageFromWater); + illusionerAlwaysDropExp = getBoolean("mobs.illusioner.always-drop-exp", illusionerAlwaysDropExp); + } + + public boolean ironGolemRidable = false; + public boolean ironGolemRidableInWater = true; + public boolean ironGolemControllable = true; + public boolean ironGolemCanSwim = false; + public double ironGolemMaxHealth = 100.0D; + public boolean ironGolemTakeDamageFromWater = false; + public boolean ironGolemPoppyCalm = false; + public boolean ironGolemHealCalm = false; + public boolean ironGolemAlwaysDropExp = false; + private void ironGolemSettings() { + ironGolemRidable = getBoolean("mobs.iron_golem.ridable", ironGolemRidable); + ironGolemRidableInWater = getBoolean("mobs.iron_golem.ridable-in-water", ironGolemRidableInWater); + ironGolemControllable = getBoolean("mobs.iron_golem.controllable", ironGolemControllable); + ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.iron_golem.attributes.max-health", ironGolemMaxHealth); + set("mobs.iron_golem.attributes.max-health", null); + set("mobs.iron_golem.attributes.max_health", oldValue); + } + ironGolemMaxHealth = getDouble("mobs.iron_golem.attributes.max_health", ironGolemMaxHealth); + ironGolemTakeDamageFromWater = getBoolean("mobs.iron_golem.takes-damage-from-water", ironGolemTakeDamageFromWater); + ironGolemPoppyCalm = getBoolean("mobs.iron_golem.poppy-calms-anger", ironGolemPoppyCalm); + ironGolemHealCalm = getBoolean("mobs.iron_golem.healing-calms-anger", ironGolemHealCalm); + ironGolemAlwaysDropExp = getBoolean("mobs.iron_golem.always-drop-exp", ironGolemAlwaysDropExp); + } + + public boolean llamaRidable = false; + public boolean llamaRidableInWater = false; + public boolean llamaControllable = true; + public double llamaMaxHealthMin = 15.0D; + public double llamaMaxHealthMax = 30.0D; + public double llamaJumpStrengthMin = 0.5D; + public double llamaJumpStrengthMax = 0.5D; + public double llamaMovementSpeedMin = 0.175D; + public double llamaMovementSpeedMax = 0.175D; + public int llamaBreedingTicks = 6000; + public boolean llamaTakeDamageFromWater = false; + public boolean llamaJoinCaravans = true; + public boolean llamaAlwaysDropExp = false; + private void llamaSettings() { + llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable); + llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater); + llamaControllable = getBoolean("mobs.llama.controllable", llamaControllable); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.llama.attributes.max-health.min", llamaMaxHealthMin); + double oldMax = getDouble("mobs.llama.attributes.max-health.max", llamaMaxHealthMax); + set("mobs.llama.attributes.max-health", null); + set("mobs.llama.attributes.max_health.min", oldMin); + set("mobs.llama.attributes.max_health.max", oldMax); + } + llamaMaxHealthMin = getDouble("mobs.llama.attributes.max_health.min", llamaMaxHealthMin); + llamaMaxHealthMax = getDouble("mobs.llama.attributes.max_health.max", llamaMaxHealthMax); + llamaJumpStrengthMin = getDouble("mobs.llama.attributes.jump_strength.min", llamaJumpStrengthMin); + llamaJumpStrengthMax = getDouble("mobs.llama.attributes.jump_strength.max", llamaJumpStrengthMax); + llamaMovementSpeedMin = getDouble("mobs.llama.attributes.movement_speed.min", llamaMovementSpeedMin); + llamaMovementSpeedMax = getDouble("mobs.llama.attributes.movement_speed.max", llamaMovementSpeedMax); + llamaBreedingTicks = getInt("mobs.llama.breeding-delay-ticks", llamaBreedingTicks); + llamaTakeDamageFromWater = getBoolean("mobs.llama.takes-damage-from-water", llamaTakeDamageFromWater); + llamaJoinCaravans = getBoolean("mobs.llama.join-caravans", llamaJoinCaravans); + llamaAlwaysDropExp = getBoolean("mobs.llama.always-drop-exp", llamaAlwaysDropExp); + } + + public boolean magmaCubeRidable = false; + public boolean magmaCubeRidableInWater = true; + public boolean magmaCubeControllable = true; + public String magmaCubeMaxHealth = "size * size"; + public String magmaCubeAttackDamage = "size"; + public Map magmaCubeMaxHealthCache = new HashMap<>(); + public Map magmaCubeAttackDamageCache = new HashMap<>(); + public boolean magmaCubeTakeDamageFromWater = false; + public boolean magmaCubeAlwaysDropExp = false; + private void magmaCubeSettings() { + magmaCubeRidable = getBoolean("mobs.magma_cube.ridable", magmaCubeRidable); + magmaCubeRidableInWater = getBoolean("mobs.magma_cube.ridable-in-water", magmaCubeRidableInWater); + magmaCubeControllable = getBoolean("mobs.magma_cube.controllable", magmaCubeControllable); + if (PurpurConfig.version < 10) { + String oldValue = getString("mobs.magma_cube.attributes.max-health", magmaCubeMaxHealth); + set("mobs.magma_cube.attributes.max-health", null); + set("mobs.magma_cube.attributes.max_health", oldValue); + } + magmaCubeMaxHealth = getString("mobs.magma_cube.attributes.max_health", magmaCubeMaxHealth); + magmaCubeAttackDamage = getString("mobs.magma_cube.attributes.attack_damage", magmaCubeAttackDamage); + magmaCubeMaxHealthCache.clear(); + magmaCubeAttackDamageCache.clear(); + magmaCubeTakeDamageFromWater = getBoolean("mobs.magma_cube.takes-damage-from-water", magmaCubeTakeDamageFromWater); + magmaCubeAlwaysDropExp = getBoolean("mobs.magma_cube.always-drop-exp", magmaCubeAlwaysDropExp); + } + + public boolean mooshroomRidable = false; + public boolean mooshroomRidableInWater = true; + public boolean mooshroomControllable = true; + public double mooshroomMaxHealth = 10.0D; + public int mooshroomBreedingTicks = 6000; + public boolean mooshroomTakeDamageFromWater = false; + public boolean mooshroomAlwaysDropExp = false; + private void mooshroomSettings() { + mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable); + mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater); + mooshroomControllable = getBoolean("mobs.mooshroom.controllable", mooshroomControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.mooshroom.attributes.max-health", mooshroomMaxHealth); + set("mobs.mooshroom.attributes.max-health", null); + set("mobs.mooshroom.attributes.max_health", oldValue); + } + mooshroomMaxHealth = getDouble("mobs.mooshroom.attributes.max_health", mooshroomMaxHealth); + mooshroomBreedingTicks = getInt("mobs.mooshroom.breeding-delay-ticks", mooshroomBreedingTicks); + mooshroomTakeDamageFromWater = getBoolean("mobs.mooshroom.takes-damage-from-water", mooshroomTakeDamageFromWater); + mooshroomAlwaysDropExp = getBoolean("mobs.mooshroom.always-drop-exp", mooshroomAlwaysDropExp); + } + + public boolean muleRidableInWater = false; + public double muleMaxHealthMin = 15.0D; + public double muleMaxHealthMax = 30.0D; + public double muleJumpStrengthMin = 0.5D; + public double muleJumpStrengthMax = 0.5D; + public double muleMovementSpeedMin = 0.175D; + public double muleMovementSpeedMax = 0.175D; + public int muleBreedingTicks = 6000; + public boolean muleTakeDamageFromWater = false; + public boolean muleAlwaysDropExp = false; + private void muleSettings() { + muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.mule.attributes.max-health.min", muleMaxHealthMin); + double oldMax = getDouble("mobs.mule.attributes.max-health.max", muleMaxHealthMax); + set("mobs.mule.attributes.max-health", null); + set("mobs.mule.attributes.max_health.min", oldMin); + set("mobs.mule.attributes.max_health.max", oldMax); + } + muleMaxHealthMin = getDouble("mobs.mule.attributes.max_health.min", muleMaxHealthMin); + muleMaxHealthMax = getDouble("mobs.mule.attributes.max_health.max", muleMaxHealthMax); + muleJumpStrengthMin = getDouble("mobs.mule.attributes.jump_strength.min", muleJumpStrengthMin); + muleJumpStrengthMax = getDouble("mobs.mule.attributes.jump_strength.max", muleJumpStrengthMax); + muleMovementSpeedMin = getDouble("mobs.mule.attributes.movement_speed.min", muleMovementSpeedMin); + muleMovementSpeedMax = getDouble("mobs.mule.attributes.movement_speed.max", muleMovementSpeedMax); + muleBreedingTicks = getInt("mobs.mule.breeding-delay-ticks", muleBreedingTicks); + muleTakeDamageFromWater = getBoolean("mobs.mule.takes-damage-from-water", muleTakeDamageFromWater); + muleAlwaysDropExp = getBoolean("mobs.mule.always-drop-exp", muleAlwaysDropExp); + } + + public boolean ocelotRidable = false; + public boolean ocelotRidableInWater = true; + public boolean ocelotControllable = true; + public double ocelotMaxHealth = 10.0D; + public int ocelotBreedingTicks = 6000; + public boolean ocelotTakeDamageFromWater = false; + public boolean ocelotAlwaysDropExp = false; + 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); + 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 int pandaBreedingTicks = 6000; + public boolean pandaTakeDamageFromWater = false; + public boolean pandaAlwaysDropExp = false; + private void pandaSettings() { + pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable); + pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater); + pandaControllable = getBoolean("mobs.panda.controllable", pandaControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.panda.attributes.max-health", pandaMaxHealth); + set("mobs.panda.attributes.max-health", null); + set("mobs.panda.attributes.max_health", oldValue); + } + pandaMaxHealth = getDouble("mobs.panda.attributes.max_health", pandaMaxHealth); + pandaBreedingTicks = getInt("mobs.panda.breeding-delay-ticks", pandaBreedingTicks); + pandaTakeDamageFromWater = getBoolean("mobs.panda.takes-damage-from-water", pandaTakeDamageFromWater); + pandaAlwaysDropExp = getBoolean("mobs.panda.always-drop-exp", pandaAlwaysDropExp); + } + + public boolean parrotRidable = false; + public boolean parrotRidableInWater = true; + public boolean parrotControllable = true; + public double parrotMaxY = 320D; + public double parrotMaxHealth = 6.0D; + public boolean parrotTakeDamageFromWater = false; + public boolean parrotBreedable = false; + public boolean parrotAlwaysDropExp = false; + private void parrotSettings() { + parrotRidable = getBoolean("mobs.parrot.ridable", parrotRidable); + parrotRidableInWater = getBoolean("mobs.parrot.ridable-in-water", parrotRidableInWater); + parrotControllable = getBoolean("mobs.parrot.controllable", parrotControllable); + parrotMaxY = getDouble("mobs.parrot.ridable-max-y", parrotMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.parrot.attributes.max-health", parrotMaxHealth); + set("mobs.parrot.attributes.max-health", null); + set("mobs.parrot.attributes.max_health", oldValue); + } + parrotMaxHealth = getDouble("mobs.parrot.attributes.max_health", parrotMaxHealth); + parrotTakeDamageFromWater = getBoolean("mobs.parrot.takes-damage-from-water", parrotTakeDamageFromWater); + parrotBreedable = getBoolean("mobs.parrot.can-breed", parrotBreedable); + parrotAlwaysDropExp = getBoolean("mobs.parrot.always-drop-exp", parrotAlwaysDropExp); + } + + public boolean phantomRidable = false; + public boolean phantomRidableInWater = true; + public boolean phantomControllable = true; + public double phantomMaxY = 320D; + public float phantomFlameDamage = 1.0F; + public int phantomFlameFireTime = 8; + public boolean phantomAllowGriefing = false; + public String phantomMaxHealth = "20.0"; + public String phantomAttackDamage = "6 + size"; + public Map phantomMaxHealthCache = new HashMap<>(); + public Map phantomAttackDamageCache = new HashMap<>(); + public double phantomAttackedByCrystalRadius = 0.0D; + public float phantomAttackedByCrystalDamage = 1.0F; + public double phantomOrbitCrystalRadius = 0.0D; + public int phantomSpawnMinSkyDarkness = 5; + public boolean phantomSpawnOnlyAboveSeaLevel = true; + public boolean phantomSpawnOnlyWithVisibleSky = true; + public double phantomSpawnLocalDifficultyChance = 3.0D; + public int phantomSpawnMinPerAttempt = 1; + public int phantomSpawnMaxPerAttempt = -1; + public int phantomBurnInLight = 0; + public boolean phantomIgnorePlayersWithTorch = false; + public boolean phantomBurnInDaylight = true; + public boolean phantomFlamesOnSwoop = false; + public boolean phantomTakeDamageFromWater = false; + public boolean phantomAlwaysDropExp = false; + public int phantomMinSize = 0; + public int phantomMaxSize = 0; + private void phantomSettings() { + phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable); + phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater); + phantomControllable = getBoolean("mobs.phantom.controllable", phantomControllable); + phantomMaxY = getDouble("mobs.phantom.ridable-max-y", phantomMaxY); + phantomFlameDamage = (float) getDouble("mobs.phantom.flames.damage", phantomFlameDamage); + phantomFlameFireTime = getInt("mobs.phantom.flames.fire-time", phantomFlameFireTime); + phantomAllowGriefing = getBoolean("mobs.phantom.allow-griefing", phantomAllowGriefing); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.phantom.attributes.max-health", Double.parseDouble(phantomMaxHealth)); + set("mobs.phantom.attributes.max-health", null); + set("mobs.phantom.attributes.max_health", String.valueOf(oldValue)); + } + if (PurpurConfig.version < 25) { + double oldValue = getDouble("mobs.phantom.attributes.max_health", Double.parseDouble(phantomMaxHealth)); + set("mobs.phantom.attributes.max_health", String.valueOf(oldValue)); + } + phantomMaxHealth = getString("mobs.phantom.attributes.max_health", phantomMaxHealth); + phantomAttackDamage = getString("mobs.phantom.attributes.attack_damage", phantomAttackDamage); + phantomMaxHealthCache.clear(); + phantomAttackDamageCache.clear(); + phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius); + phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage); + phantomOrbitCrystalRadius = getDouble("mobs.phantom.orbit-crystal-radius", phantomOrbitCrystalRadius); + phantomSpawnMinSkyDarkness = getInt("mobs.phantom.spawn.min-sky-darkness", phantomSpawnMinSkyDarkness); + phantomSpawnOnlyAboveSeaLevel = getBoolean("mobs.phantom.spawn.only-above-sea-level", phantomSpawnOnlyAboveSeaLevel); + phantomSpawnOnlyWithVisibleSky = getBoolean("mobs.phantom.spawn.only-with-visible-sky", phantomSpawnOnlyWithVisibleSky); + phantomSpawnLocalDifficultyChance = getDouble("mobs.phantom.spawn.local-difficulty-chance", phantomSpawnLocalDifficultyChance); + phantomSpawnMinPerAttempt = getInt("mobs.phantom.spawn.per-attempt.min", phantomSpawnMinPerAttempt); + phantomSpawnMaxPerAttempt = getInt("mobs.phantom.spawn.per-attempt.max", phantomSpawnMaxPerAttempt); + phantomBurnInLight = getInt("mobs.phantom.burn-in-light", phantomBurnInLight); + phantomBurnInDaylight = getBoolean("mobs.phantom.burn-in-daylight", phantomBurnInDaylight); + phantomIgnorePlayersWithTorch = getBoolean("mobs.phantom.ignore-players-with-torch", phantomIgnorePlayersWithTorch); + phantomFlamesOnSwoop = getBoolean("mobs.phantom.flames-on-swoop", phantomFlamesOnSwoop); + phantomTakeDamageFromWater = getBoolean("mobs.phantom.takes-damage-from-water", phantomTakeDamageFromWater); + phantomAlwaysDropExp = getBoolean("mobs.phantom.always-drop-exp", phantomAlwaysDropExp); + phantomMinSize = Mth.clamp(getInt("mobs.phantom.size.min", phantomMinSize), 0, 64); + phantomMaxSize = Mth.clamp(getInt("mobs.phantom.size.max", phantomMaxSize), 0, 64); + if (phantomMinSize > phantomMaxSize) { + phantomMinSize = phantomMinSize ^ phantomMaxSize; + phantomMaxSize = phantomMinSize ^ phantomMaxSize; + phantomMinSize = phantomMinSize ^ phantomMaxSize; + } + } + + public boolean pigRidable = false; + public boolean pigRidableInWater = false; + public boolean pigControllable = true; + public double pigMaxHealth = 10.0D; + public boolean pigGiveSaddleBack = false; + public int pigBreedingTicks = 6000; + public boolean pigTakeDamageFromWater = false; + public boolean pigAlwaysDropExp = false; + private void pigSettings() { + pigRidable = getBoolean("mobs.pig.ridable", pigRidable); + pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater); + pigControllable = getBoolean("mobs.pig.controllable", pigControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.pig.attributes.max-health", pigMaxHealth); + set("mobs.pig.attributes.max-health", null); + set("mobs.pig.attributes.max_health", oldValue); + } + pigMaxHealth = getDouble("mobs.pig.attributes.max_health", pigMaxHealth); + pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack); + pigBreedingTicks = getInt("mobs.pig.breeding-delay-ticks", pigBreedingTicks); + pigTakeDamageFromWater = getBoolean("mobs.pig.takes-damage-from-water", pigTakeDamageFromWater); + pigAlwaysDropExp = getBoolean("mobs.pig.always-drop-exp", pigAlwaysDropExp); + } + + public boolean piglinRidable = false; + public boolean piglinRidableInWater = true; + public boolean piglinControllable = true; + public double piglinMaxHealth = 16.0D; + public boolean piglinBypassMobGriefing = false; + public boolean piglinTakeDamageFromWater = false; + public int piglinPortalSpawnModifier = 2000; + public boolean piglinAlwaysDropExp = false; + public double piglinHeadVisibilityPercent = 0.5D; + 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); + 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 boolean piglinBruteTakeDamageFromWater = false; + public boolean piglinBruteAlwaysDropExp = false; + private void piglinBruteSettings() { + piglinBruteRidable = getBoolean("mobs.piglin_brute.ridable", piglinBruteRidable); + piglinBruteRidableInWater = getBoolean("mobs.piglin_brute.ridable-in-water", piglinBruteRidableInWater); + piglinBruteControllable = getBoolean("mobs.piglin_brute.controllable", piglinBruteControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.piglin_brute.attributes.max-health", piglinBruteMaxHealth); + set("mobs.piglin_brute.attributes.max-health", null); + set("mobs.piglin_brute.attributes.max_health", oldValue); + } + piglinBruteMaxHealth = getDouble("mobs.piglin_brute.attributes.max_health", piglinBruteMaxHealth); + piglinBruteTakeDamageFromWater = getBoolean("mobs.piglin_brute.takes-damage-from-water", piglinBruteTakeDamageFromWater); + piglinBruteAlwaysDropExp = getBoolean("mobs.piglin_brute.always-drop-exp", piglinBruteAlwaysDropExp); + } + + public boolean pillagerRidable = false; + public boolean pillagerRidableInWater = true; + public boolean pillagerControllable = true; + public double pillagerMaxHealth = 24.0D; + public boolean pillagerBypassMobGriefing = false; + public boolean pillagerTakeDamageFromWater = false; + public boolean pillagerAlwaysDropExp = false; + private void pillagerSettings() { + pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable); + pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater); + pillagerControllable = getBoolean("mobs.pillager.controllable", pillagerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.pillager.attributes.max-health", pillagerMaxHealth); + set("mobs.pillager.attributes.max-health", null); + set("mobs.pillager.attributes.max_health", oldValue); + } + pillagerMaxHealth = getDouble("mobs.pillager.attributes.max_health", pillagerMaxHealth); + pillagerBypassMobGriefing = getBoolean("mobs.pillager.bypass-mob-griefing", pillagerBypassMobGriefing); + pillagerTakeDamageFromWater = getBoolean("mobs.pillager.takes-damage-from-water", pillagerTakeDamageFromWater); + pillagerAlwaysDropExp = getBoolean("mobs.pillager.always-drop-exp", pillagerAlwaysDropExp); + } + + public boolean polarBearRidable = false; + public boolean polarBearRidableInWater = true; + public boolean polarBearControllable = true; + public double polarBearMaxHealth = 30.0D; + public String polarBearBreedableItemString = ""; + public Item polarBearBreedableItem = null; + public int polarBearBreedingTicks = 6000; + public boolean polarBearTakeDamageFromWater = false; + public boolean polarBearAlwaysDropExp = false; + private void polarBearSettings() { + polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable); + polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater); + polarBearControllable = getBoolean("mobs.polar_bear.controllable", polarBearControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.polar_bear.attributes.max-health", polarBearMaxHealth); + set("mobs.polar_bear.attributes.max-health", null); + set("mobs.polar_bear.attributes.max_health", oldValue); + } + polarBearMaxHealth = getDouble("mobs.polar_bear.attributes.max_health", polarBearMaxHealth); + polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString); + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(polarBearBreedableItemString)); + if (item != Items.AIR) polarBearBreedableItem = item; + polarBearBreedingTicks = getInt("mobs.polar_bear.breeding-delay-ticks", polarBearBreedingTicks); + polarBearTakeDamageFromWater = getBoolean("mobs.polar_bear.takes-damage-from-water", polarBearTakeDamageFromWater); + polarBearAlwaysDropExp = getBoolean("mobs.polar_bear.always-drop-exp", polarBearAlwaysDropExp); + } + + public boolean pufferfishRidable = false; + public boolean pufferfishControllable = true; + public double pufferfishMaxHealth = 3.0D; + public boolean pufferfishTakeDamageFromWater = false; + public boolean pufferfishAlwaysDropExp = false; + private void pufferfishSettings() { + pufferfishRidable = getBoolean("mobs.pufferfish.ridable", pufferfishRidable); + pufferfishControllable = getBoolean("mobs.pufferfish.controllable", pufferfishControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.pufferfish.attributes.max-health", pufferfishMaxHealth); + set("mobs.pufferfish.attributes.max-health", null); + set("mobs.pufferfish.attributes.max_health", oldValue); + } + pufferfishMaxHealth = getDouble("mobs.pufferfish.attributes.max_health", pufferfishMaxHealth); + pufferfishTakeDamageFromWater = getBoolean("mobs.pufferfish.takes-damage-from-water", pufferfishTakeDamageFromWater); + pufferfishAlwaysDropExp = getBoolean("mobs.pufferfish.always-drop-exp", pufferfishAlwaysDropExp); + } + + public boolean rabbitRidable = false; + public boolean rabbitRidableInWater = true; + public boolean rabbitControllable = true; + public double rabbitMaxHealth = 3.0D; + public double rabbitNaturalToast = 0.0D; + public double rabbitNaturalKiller = 0.0D; + public int rabbitBreedingTicks = 6000; + public boolean rabbitBypassMobGriefing = false; + public boolean rabbitTakeDamageFromWater = false; + public boolean rabbitAlwaysDropExp = false; + private void rabbitSettings() { + rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable); + rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater); + rabbitControllable = getBoolean("mobs.rabbit.controllable", rabbitControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.rabbit.attributes.max-health", rabbitMaxHealth); + set("mobs.rabbit.attributes.max-health", null); + set("mobs.rabbit.attributes.max_health", oldValue); + } + rabbitMaxHealth = getDouble("mobs.rabbit.attributes.max_health", rabbitMaxHealth); + rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast); + rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller); + rabbitBreedingTicks = getInt("mobs.rabbit.breeding-delay-ticks", rabbitBreedingTicks); + rabbitBypassMobGriefing = getBoolean("mobs.rabbit.bypass-mob-griefing", rabbitBypassMobGriefing); + rabbitTakeDamageFromWater = getBoolean("mobs.rabbit.takes-damage-from-water", rabbitTakeDamageFromWater); + rabbitAlwaysDropExp = getBoolean("mobs.rabbit.always-drop-exp", rabbitAlwaysDropExp); + } + + public boolean ravagerRidable = false; + public boolean ravagerRidableInWater = false; + public boolean ravagerControllable = true; + public double ravagerMaxHealth = 100.0D; + public boolean ravagerBypassMobGriefing = false; + public boolean ravagerTakeDamageFromWater = false; + public List ravagerGriefableBlocks = new ArrayList<>(); + public boolean ravagerAlwaysDropExp = false; + private void ravagerSettings() { + ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable); + ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater); + ravagerControllable = getBoolean("mobs.ravager.controllable", ravagerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.ravager.attributes.max-health", ravagerMaxHealth); + set("mobs.ravager.attributes.max-health", null); + set("mobs.ravager.attributes.max_health", oldValue); + } + ravagerMaxHealth = getDouble("mobs.ravager.attributes.max_health", ravagerMaxHealth); + ravagerBypassMobGriefing = getBoolean("mobs.ravager.bypass-mob-griefing", ravagerBypassMobGriefing); + ravagerTakeDamageFromWater = getBoolean("mobs.ravager.takes-damage-from-water", ravagerTakeDamageFromWater); + getList("mobs.ravager.griefable-blocks", new ArrayList(){{ + add("minecraft:oak_leaves"); + add("minecraft:spruce_leaves"); + add("minecraft:birch_leaves"); + add("minecraft:jungle_leaves"); + add("minecraft:acacia_leaves"); + add("minecraft:dark_oak_leaves"); + add("minecraft:beetroots"); + add("minecraft:carrots"); + add("minecraft:potatoes"); + add("minecraft:wheat"); + }}).forEach(key -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key.toString())); + if (!block.defaultBlockState().isAir()) { + ravagerGriefableBlocks.add(block); + } + }); + ravagerAlwaysDropExp = getBoolean("mobs.ravager.always-drop-exp", ravagerAlwaysDropExp); + } + + public boolean salmonRidable = false; + public boolean salmonControllable = true; + public double salmonMaxHealth = 3.0D; + public boolean salmonTakeDamageFromWater = false; + public boolean salmonAlwaysDropExp = false; + private void salmonSettings() { + salmonRidable = getBoolean("mobs.salmon.ridable", salmonRidable); + salmonControllable = getBoolean("mobs.salmon.controllable", salmonControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.salmon.attributes.max-health", salmonMaxHealth); + set("mobs.salmon.attributes.max-health", null); + set("mobs.salmon.attributes.max_health", oldValue); + } + salmonMaxHealth = getDouble("mobs.salmon.attributes.max_health", salmonMaxHealth); + salmonTakeDamageFromWater = getBoolean("mobs.salmon.takes-damage-from-water", salmonTakeDamageFromWater); + salmonAlwaysDropExp = getBoolean("mobs.salmon.always-drop-exp", salmonAlwaysDropExp); + } + + public boolean sheepRidable = false; + public boolean sheepRidableInWater = true; + public boolean sheepControllable = true; + public double sheepMaxHealth = 8.0D; + public int sheepBreedingTicks = 6000; + public boolean sheepBypassMobGriefing = false; + public boolean sheepTakeDamageFromWater = false; + public boolean sheepAlwaysDropExp = false; + public boolean sheepShearJebRandomColor = false; + private void sheepSettings() { + sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable); + sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater); + sheepControllable = getBoolean("mobs.sheep.controllable", sheepControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.sheep.attributes.max-health", sheepMaxHealth); + set("mobs.sheep.attributes.max-health", null); + set("mobs.sheep.attributes.max_health", oldValue); + } + sheepMaxHealth = getDouble("mobs.sheep.attributes.max_health", sheepMaxHealth); + sheepBreedingTicks = getInt("mobs.sheep.breeding-delay-ticks", sheepBreedingTicks); + sheepBypassMobGriefing = getBoolean("mobs.sheep.bypass-mob-griefing", sheepBypassMobGriefing); + sheepTakeDamageFromWater = getBoolean("mobs.sheep.takes-damage-from-water", sheepTakeDamageFromWater); + sheepAlwaysDropExp = getBoolean("mobs.sheep.always-drop-exp", sheepAlwaysDropExp); + sheepShearJebRandomColor = getBoolean("mobs.sheep.jeb-shear-random-color", sheepShearJebRandomColor); + } + + public boolean shulkerRidable = false; + public boolean shulkerRidableInWater = true; + public boolean shulkerControllable = true; + public double shulkerMaxHealth = 30.0D; + public boolean shulkerTakeDamageFromWater = false; + public float shulkerSpawnFromBulletBaseChance = 1.0F; + public boolean shulkerSpawnFromBulletRequireOpenLid = true; + public double shulkerSpawnFromBulletNearbyRange = 8.0D; + public String shulkerSpawnFromBulletNearbyEquation = "(nearby - 1) / 5.0"; + public boolean shulkerSpawnFromBulletRandomColor = false; + public boolean shulkerChangeColorWithDye = false; + public boolean shulkerAlwaysDropExp = false; + private void shulkerSettings() { + shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable); + shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater); + shulkerControllable = getBoolean("mobs.shulker.controllable", shulkerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.shulker.attributes.max-health", shulkerMaxHealth); + set("mobs.shulker.attributes.max-health", null); + set("mobs.shulker.attributes.max_health", oldValue); + } + shulkerMaxHealth = getDouble("mobs.shulker.attributes.max_health", shulkerMaxHealth); + shulkerTakeDamageFromWater = getBoolean("mobs.shulker.takes-damage-from-water", shulkerTakeDamageFromWater); + shulkerSpawnFromBulletBaseChance = (float) getDouble("mobs.shulker.spawn-from-bullet.base-chance", shulkerSpawnFromBulletBaseChance); + shulkerSpawnFromBulletRequireOpenLid = getBoolean("mobs.shulker.spawn-from-bullet.require-open-lid", shulkerSpawnFromBulletRequireOpenLid); + shulkerSpawnFromBulletNearbyRange = getDouble("mobs.shulker.spawn-from-bullet.nearby-range", shulkerSpawnFromBulletNearbyRange); + shulkerSpawnFromBulletNearbyEquation = getString("mobs.shulker.spawn-from-bullet.nearby-equation", shulkerSpawnFromBulletNearbyEquation); + shulkerSpawnFromBulletRandomColor = getBoolean("mobs.shulker.spawn-from-bullet.random-color", shulkerSpawnFromBulletRandomColor); + shulkerChangeColorWithDye = getBoolean("mobs.shulker.change-color-with-dye", shulkerChangeColorWithDye); + shulkerAlwaysDropExp = getBoolean("mobs.shulker.always-drop-exp", shulkerAlwaysDropExp); + } + + public boolean silverfishRidable = false; + public boolean silverfishRidableInWater = true; + public boolean silverfishControllable = true; + public double silverfishMaxHealth = 8.0D; + public boolean silverfishBypassMobGriefing = false; + public boolean silverfishTakeDamageFromWater = false; + public boolean silverfishAlwaysDropExp = false; + private void silverfishSettings() { + silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable); + silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater); + silverfishControllable = getBoolean("mobs.silverfish.controllable", silverfishControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.silverfish.attributes.max-health", silverfishMaxHealth); + set("mobs.silverfish.attributes.max-health", null); + set("mobs.silverfish.attributes.max_health", oldValue); + } + silverfishMaxHealth = getDouble("mobs.silverfish.attributes.max_health", silverfishMaxHealth); + silverfishBypassMobGriefing = getBoolean("mobs.silverfish.bypass-mob-griefing", silverfishBypassMobGriefing); + silverfishTakeDamageFromWater = getBoolean("mobs.silverfish.takes-damage-from-water", silverfishTakeDamageFromWater); + silverfishAlwaysDropExp = getBoolean("mobs.silverfish.always-drop-exp", silverfishAlwaysDropExp); + } + + public boolean skeletonRidable = false; + public boolean skeletonRidableInWater = true; + public boolean skeletonControllable = true; + public double skeletonMaxHealth = 20.0D; + public boolean skeletonTakeDamageFromWater = false; + public boolean skeletonAlwaysDropExp = false; + public double skeletonHeadVisibilityPercent = 0.5D; + public int skeletonFeedWitherRoses = 0; + public String skeletonBowAccuracy = "14 - difficulty * 4"; + public Map skeletonBowAccuracyMap = new HashMap<>(); + private void skeletonSettings() { + skeletonRidable = getBoolean("mobs.skeleton.ridable", skeletonRidable); + skeletonRidableInWater = getBoolean("mobs.skeleton.ridable-in-water", skeletonRidableInWater); + skeletonControllable = getBoolean("mobs.skeleton.controllable", skeletonControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.skeleton.attributes.max-health", skeletonMaxHealth); + set("mobs.skeleton.attributes.max-health", null); + set("mobs.skeleton.attributes.max_health", oldValue); + } + skeletonMaxHealth = getDouble("mobs.skeleton.attributes.max_health", skeletonMaxHealth); + skeletonTakeDamageFromWater = getBoolean("mobs.skeleton.takes-damage-from-water", skeletonTakeDamageFromWater); + skeletonAlwaysDropExp = getBoolean("mobs.skeleton.always-drop-exp", skeletonAlwaysDropExp); + skeletonHeadVisibilityPercent = getDouble("mobs.skeleton.head-visibility-percent", skeletonHeadVisibilityPercent); + skeletonFeedWitherRoses = getInt("mobs.skeleton.feed-wither-roses", skeletonFeedWitherRoses); + final String defaultSkeletonBowAccuracy = skeletonBowAccuracy; + skeletonBowAccuracy = getString("mobs.skeleton.bow-accuracy", skeletonBowAccuracy); + for (int i = 1; i < 4; i++) { + final float divergence; + try { + divergence = ((Number) Entity.scriptEngine.eval("let difficulty = " + i + "; " + skeletonBowAccuracy)).floatValue(); + } catch (javax.script.ScriptException e) { + e.printStackTrace(); + break; + } + skeletonBowAccuracyMap.put(i, divergence); + } + } + + public boolean skeletonHorseRidableInWater = true; + public boolean skeletonHorseCanSwim = false; + public double skeletonHorseMaxHealthMin = 15.0D; + public double skeletonHorseMaxHealthMax = 15.0D; + public double skeletonHorseJumpStrengthMin = 0.4D; + public double skeletonHorseJumpStrengthMax = 1.0D; + public double skeletonHorseMovementSpeedMin = 0.2D; + public double skeletonHorseMovementSpeedMax = 0.2D; + public boolean skeletonHorseTakeDamageFromWater = false; + public boolean skeletonHorseAlwaysDropExp = false; + private void skeletonHorseSettings() { + skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater); + skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.skeleton_horse.attributes.max-health", skeletonHorseMaxHealthMin); + set("mobs.skeleton_horse.attributes.max-health", null); + set("mobs.skeleton_horse.attributes.max_health.min", oldValue); + set("mobs.skeleton_horse.attributes.max_health.max", oldValue); + } + skeletonHorseMaxHealthMin = getDouble("mobs.skeleton_horse.attributes.max_health.min", skeletonHorseMaxHealthMin); + skeletonHorseMaxHealthMax = getDouble("mobs.skeleton_horse.attributes.max_health.max", skeletonHorseMaxHealthMax); + skeletonHorseJumpStrengthMin = getDouble("mobs.skeleton_horse.attributes.jump_strength.min", skeletonHorseJumpStrengthMin); + skeletonHorseJumpStrengthMax = getDouble("mobs.skeleton_horse.attributes.jump_strength.max", skeletonHorseJumpStrengthMax); + skeletonHorseMovementSpeedMin = getDouble("mobs.skeleton_horse.attributes.movement_speed.min", skeletonHorseMovementSpeedMin); + skeletonHorseMovementSpeedMax = getDouble("mobs.skeleton_horse.attributes.movement_speed.max", skeletonHorseMovementSpeedMax); + skeletonHorseTakeDamageFromWater = getBoolean("mobs.skeleton_horse.takes-damage-from-water", skeletonHorseTakeDamageFromWater); + skeletonHorseAlwaysDropExp = getBoolean("mobs.skeleton_horse.always-drop-exp", skeletonHorseAlwaysDropExp); + } + + public boolean slimeRidable = false; + public boolean slimeRidableInWater = true; + public boolean slimeControllable = true; + public String slimeMaxHealth = "size * size"; + public String slimeAttackDamage = "size"; + public Map slimeMaxHealthCache = new HashMap<>(); + public Map slimeAttackDamageCache = new HashMap<>(); + public boolean slimeTakeDamageFromWater = false; + public boolean slimeAlwaysDropExp = false; + private void slimeSettings() { + slimeRidable = getBoolean("mobs.slime.ridable", slimeRidable); + slimeRidableInWater = getBoolean("mobs.slime.ridable-in-water", slimeRidableInWater); + slimeControllable = getBoolean("mobs.slime.controllable", slimeControllable); + if (PurpurConfig.version < 10) { + String oldValue = getString("mobs.slime.attributes.max-health", slimeMaxHealth); + set("mobs.slime.attributes.max-health", null); + set("mobs.slime.attributes.max_health", oldValue); + } + slimeMaxHealth = getString("mobs.slime.attributes.max_health", slimeMaxHealth); + slimeAttackDamage = getString("mobs.slime.attributes.attack_damage", slimeAttackDamage); + slimeMaxHealthCache.clear(); + slimeAttackDamageCache.clear(); + slimeTakeDamageFromWater = getBoolean("mobs.slime.takes-damage-from-water", slimeTakeDamageFromWater); + slimeAlwaysDropExp = getBoolean("mobs.slime.always-drop-exp", slimeAlwaysDropExp); + } + + public boolean snowGolemRidable = false; + public boolean snowGolemRidableInWater = true; + public boolean snowGolemControllable = true; + public boolean snowGolemLeaveTrailWhenRidden = false; + public double snowGolemMaxHealth = 4.0D; + public boolean snowGolemDropsPumpkin = true; + public boolean snowGolemPutPumpkinBack = false; + public int snowGolemSnowBallMin = 20; + public int snowGolemSnowBallMax = 20; + public float snowGolemSnowBallModifier = 10.0F; + public double snowGolemAttackDistance = 1.25D; + public boolean snowGolemBypassMobGriefing = false; + public boolean snowGolemTakeDamageFromWater = true; + public boolean snowGolemAlwaysDropExp = false; + private void snowGolemSettings() { + snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable); + snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater); + snowGolemControllable = getBoolean("mobs.snow_golem.controllable", snowGolemControllable); + snowGolemLeaveTrailWhenRidden = getBoolean("mobs.snow_golem.leave-trail-when-ridden", snowGolemLeaveTrailWhenRidden); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.snow_golem.attributes.max-health", snowGolemMaxHealth); + set("mobs.snow_golem.attributes.max-health", null); + set("mobs.snow_golem.attributes.max_health", oldValue); + } + snowGolemMaxHealth = getDouble("mobs.snow_golem.attributes.max_health", snowGolemMaxHealth); + snowGolemDropsPumpkin = getBoolean("mobs.snow_golem.drop-pumpkin-when-sheared", snowGolemDropsPumpkin); + snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack); + snowGolemSnowBallMin = getInt("mobs.snow_golem.min-shoot-interval-ticks", snowGolemSnowBallMin); + snowGolemSnowBallMax = getInt("mobs.snow_golem.max-shoot-interval-ticks", snowGolemSnowBallMax); + snowGolemSnowBallModifier = (float) getDouble("mobs.snow_golem.snow-ball-modifier", snowGolemSnowBallModifier); + snowGolemAttackDistance = getDouble("mobs.snow_golem.attack-distance", snowGolemAttackDistance); + snowGolemBypassMobGriefing = getBoolean("mobs.snow_golem.bypass-mob-griefing", snowGolemBypassMobGriefing); + snowGolemTakeDamageFromWater = getBoolean("mobs.snow_golem.takes-damage-from-water", snowGolemTakeDamageFromWater); + snowGolemAlwaysDropExp = getBoolean("mobs.snow_golem.always-drop-exp", snowGolemAlwaysDropExp); + } + + public boolean snifferRidable = false; + public boolean snifferRidableInWater = true; + public boolean snifferControllable = true; + public double snifferMaxHealth = 14.0D; + public int snifferBreedingTicks = 6000; + private void snifferSettings() { + snifferRidable = getBoolean("mobs.sniffer.ridable", snifferRidable); + snifferRidableInWater = getBoolean("mobs.sniffer.ridable-in-water", snifferRidableInWater); + snifferControllable = getBoolean("mobs.sniffer.controllable", snifferControllable); + snifferMaxHealth = getDouble("mobs.sniffer.attributes.max_health", snifferMaxHealth); + snifferBreedingTicks = getInt("mobs.sniffer.breeding-delay-ticks", chickenBreedingTicks); + } + + public boolean squidRidable = false; + public boolean squidControllable = true; + public double squidMaxHealth = 10.0D; + public boolean squidImmuneToEAR = true; + public double squidOffsetWaterCheck = 0.0D; + public boolean squidsCanFly = false; + public boolean squidTakeDamageFromWater = false; + public boolean squidAlwaysDropExp = false; + private void squidSettings() { + squidRidable = getBoolean("mobs.squid.ridable", squidRidable); + squidControllable = getBoolean("mobs.squid.controllable", squidControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.squid.attributes.max-health", squidMaxHealth); + set("mobs.squid.attributes.max-health", null); + set("mobs.squid.attributes.max_health", oldValue); + } + squidMaxHealth = getDouble("mobs.squid.attributes.max_health", squidMaxHealth); + squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR); + squidOffsetWaterCheck = getDouble("mobs.squid.water-offset-check", squidOffsetWaterCheck); + squidsCanFly = getBoolean("mobs.squid.can-fly", squidsCanFly); + squidTakeDamageFromWater = getBoolean("mobs.squid.takes-damage-from-water", squidTakeDamageFromWater); + squidAlwaysDropExp = getBoolean("mobs.squid.always-drop-exp", squidAlwaysDropExp); + } + + public boolean spiderRidable = false; + public boolean spiderRidableInWater = false; + public boolean spiderControllable = true; + public double spiderMaxHealth = 16.0D; + public boolean spiderTakeDamageFromWater = false; + public boolean spiderAlwaysDropExp = false; + private void spiderSettings() { + spiderRidable = getBoolean("mobs.spider.ridable", spiderRidable); + spiderRidableInWater = getBoolean("mobs.spider.ridable-in-water", spiderRidableInWater); + spiderControllable = getBoolean("mobs.spider.controllable", spiderControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.spider.attributes.max-health", spiderMaxHealth); + set("mobs.spider.attributes.max-health", null); + set("mobs.spider.attributes.max_health", oldValue); + } + spiderMaxHealth = getDouble("mobs.spider.attributes.max_health", spiderMaxHealth); + spiderTakeDamageFromWater = getBoolean("mobs.spider.takes-damage-from-water", spiderTakeDamageFromWater); + spiderAlwaysDropExp = getBoolean("mobs.spider.always-drop-exp", spiderAlwaysDropExp); + } + + public boolean strayRidable = false; + public boolean strayRidableInWater = true; + public boolean strayControllable = true; + public double strayMaxHealth = 20.0D; + public boolean strayTakeDamageFromWater = false; + public boolean strayAlwaysDropExp = false; + private void straySettings() { + strayRidable = getBoolean("mobs.stray.ridable", strayRidable); + strayRidableInWater = getBoolean("mobs.stray.ridable-in-water", strayRidableInWater); + strayControllable = getBoolean("mobs.stray.controllable", strayControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.stray.attributes.max-health", strayMaxHealth); + set("mobs.stray.attributes.max-health", null); + set("mobs.stray.attributes.max_health", oldValue); + } + strayMaxHealth = getDouble("mobs.stray.attributes.max_health", strayMaxHealth); + strayTakeDamageFromWater = getBoolean("mobs.stray.takes-damage-from-water", strayTakeDamageFromWater); + strayAlwaysDropExp = getBoolean("mobs.stray.always-drop-exp", strayAlwaysDropExp); + } + + public boolean striderRidable = false; + public boolean striderRidableInWater = false; + public boolean striderControllable = true; + public double striderMaxHealth = 20.0D; + public int striderBreedingTicks = 6000; + public boolean striderGiveSaddleBack = false; + public boolean striderTakeDamageFromWater = true; + public boolean striderAlwaysDropExp = false; + private void striderSettings() { + striderRidable = getBoolean("mobs.strider.ridable", striderRidable); + striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater); + striderControllable = getBoolean("mobs.strider.controllable", striderControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.strider.attributes.max-health", striderMaxHealth); + set("mobs.strider.attributes.max-health", null); + set("mobs.strider.attributes.max_health", oldValue); + } + striderMaxHealth = getDouble("mobs.strider.attributes.max_health", striderMaxHealth); + striderBreedingTicks = getInt("mobs.strider.breeding-delay-ticks", striderBreedingTicks); + striderGiveSaddleBack = getBoolean("mobs.strider.give-saddle-back", striderGiveSaddleBack); + striderTakeDamageFromWater = getBoolean("mobs.strider.takes-damage-from-water", striderTakeDamageFromWater); + striderAlwaysDropExp = getBoolean("mobs.strider.always-drop-exp", striderAlwaysDropExp); + } + + public boolean tadpoleRidable = false; + public boolean tadpoleRidableInWater = true; + public boolean tadpoleControllable = true; + private void tadpoleSettings() { + tadpoleRidable = getBoolean("mobs.tadpole.ridable", tadpoleRidable); + tadpoleRidableInWater = getBoolean("mobs.tadpole.ridable-in-water", tadpoleRidableInWater); + tadpoleControllable = getBoolean("mobs.tadpole.controllable", tadpoleControllable); + } + + public boolean traderLlamaRidable = false; + public boolean traderLlamaRidableInWater = false; + public boolean traderLlamaControllable = true; + public double traderLlamaMaxHealthMin = 15.0D; + public double traderLlamaMaxHealthMax = 30.0D; + public double traderLlamaJumpStrengthMin = 0.5D; + public double traderLlamaJumpStrengthMax = 0.5D; + public double traderLlamaMovementSpeedMin = 0.175D; + public double traderLlamaMovementSpeedMax = 0.175D; + public int traderLlamaBreedingTicks = 6000; + public boolean traderLlamaTakeDamageFromWater = false; + public boolean traderLlamaAlwaysDropExp = false; + private void traderLlamaSettings() { + traderLlamaRidable = getBoolean("mobs.trader_llama.ridable", traderLlamaRidable); + traderLlamaRidableInWater = getBoolean("mobs.trader_llama.ridable-in-water", traderLlamaRidableInWater); + traderLlamaControllable = getBoolean("mobs.trader_llama.controllable", traderLlamaControllable); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.trader_llama.attributes.max-health.min", traderLlamaMaxHealthMin); + double oldMax = getDouble("mobs.trader_llama.attributes.max-health.max", traderLlamaMaxHealthMax); + set("mobs.trader_llama.attributes.max-health", null); + set("mobs.trader_llama.attributes.max_health.min", oldMin); + set("mobs.trader_llama.attributes.max_health.max", oldMax); + } + traderLlamaMaxHealthMin = getDouble("mobs.trader_llama.attributes.max_health.min", traderLlamaMaxHealthMin); + traderLlamaMaxHealthMax = getDouble("mobs.trader_llama.attributes.max_health.max", traderLlamaMaxHealthMax); + traderLlamaJumpStrengthMin = getDouble("mobs.trader_llama.attributes.jump_strength.min", traderLlamaJumpStrengthMin); + traderLlamaJumpStrengthMax = getDouble("mobs.trader_llama.attributes.jump_strength.max", traderLlamaJumpStrengthMax); + traderLlamaMovementSpeedMin = getDouble("mobs.trader_llama.attributes.movement_speed.min", traderLlamaMovementSpeedMin); + traderLlamaMovementSpeedMax = getDouble("mobs.trader_llama.attributes.movement_speed.max", traderLlamaMovementSpeedMax); + traderLlamaBreedingTicks = getInt("mobs.trader_llama.breeding-delay-ticks", traderLlamaBreedingTicks); + traderLlamaTakeDamageFromWater = getBoolean("mobs.trader_llama.takes-damage-from-water", traderLlamaTakeDamageFromWater); + traderLlamaAlwaysDropExp = getBoolean("mobs.trader_llama.always-drop-exp", traderLlamaAlwaysDropExp); + } + + public boolean tropicalFishRidable = false; + public boolean tropicalFishControllable = true; + public double tropicalFishMaxHealth = 3.0D; + public boolean tropicalFishTakeDamageFromWater = false; + public boolean tropicalFishAlwaysDropExp = false; + private void tropicalFishSettings() { + tropicalFishRidable = getBoolean("mobs.tropical_fish.ridable", tropicalFishRidable); + tropicalFishControllable = getBoolean("mobs.tropical_fish.controllable", tropicalFishControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.tropical_fish.attributes.max-health", tropicalFishMaxHealth); + set("mobs.tropical_fish.attributes.max-health", null); + set("mobs.tropical_fish.attributes.max_health", oldValue); + } + tropicalFishMaxHealth = getDouble("mobs.tropical_fish.attributes.max_health", tropicalFishMaxHealth); + tropicalFishTakeDamageFromWater = getBoolean("mobs.tropical_fish.takes-damage-from-water", tropicalFishTakeDamageFromWater); + tropicalFishAlwaysDropExp = getBoolean("mobs.tropical_fish.always-drop-exp", tropicalFishAlwaysDropExp); + } + + public boolean turtleRidable = false; + public boolean turtleRidableInWater = true; + public boolean turtleControllable = true; + public double turtleMaxHealth = 30.0D; + public int turtleBreedingTicks = 6000; + public boolean turtleTakeDamageFromWater = false; + public boolean turtleAlwaysDropExp = false; + private void turtleSettings() { + turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable); + turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater); + turtleControllable = getBoolean("mobs.turtle.controllable", turtleControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.turtle.attributes.max-health", turtleMaxHealth); + set("mobs.turtle.attributes.max-health", null); + set("mobs.turtle.attributes.max_health", oldValue); + } + turtleMaxHealth = getDouble("mobs.turtle.attributes.max_health", turtleMaxHealth); + turtleBreedingTicks = getInt("mobs.turtle.breeding-delay-ticks", turtleBreedingTicks); + turtleTakeDamageFromWater = getBoolean("mobs.turtle.takes-damage-from-water", turtleTakeDamageFromWater); + turtleAlwaysDropExp = getBoolean("mobs.turtle.always-drop-exp", turtleAlwaysDropExp); + } + + public boolean vexRidable = false; + public boolean vexRidableInWater = true; + public boolean vexControllable = true; + public double vexMaxY = 320D; + public double vexMaxHealth = 14.0D; + public boolean vexTakeDamageFromWater = false; + public boolean vexAlwaysDropExp = false; + private void vexSettings() { + vexRidable = getBoolean("mobs.vex.ridable", vexRidable); + vexRidableInWater = getBoolean("mobs.vex.ridable-in-water", vexRidableInWater); + vexControllable = getBoolean("mobs.vex.controllable", vexControllable); + vexMaxY = getDouble("mobs.vex.ridable-max-y", vexMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.vex.attributes.max-health", vexMaxHealth); + set("mobs.vex.attributes.max-health", null); + set("mobs.vex.attributes.max_health", oldValue); + } + vexMaxHealth = getDouble("mobs.vex.attributes.max_health", vexMaxHealth); + vexTakeDamageFromWater = getBoolean("mobs.vex.takes-damage-from-water", vexTakeDamageFromWater); + vexAlwaysDropExp = getBoolean("mobs.vex.always-drop-exp", vexAlwaysDropExp); + } + + public boolean villagerRidable = false; + public boolean villagerRidableInWater = true; + public boolean villagerControllable = true; + public double villagerMaxHealth = 20.0D; + public boolean villagerFollowEmeraldBlock = false; + public boolean villagerCanBeLeashed = false; + public boolean villagerCanBreed = true; + public int villagerBreedingTicks = 6000; + public boolean villagerClericsFarmWarts = false; + public boolean villagerClericFarmersThrowWarts = true; + public boolean villagerBypassMobGriefing = false; + public boolean villagerTakeDamageFromWater = false; + public boolean villagerAllowTrading = true; + public boolean villagerAlwaysDropExp = false; + public int villagerMinimumDemand = 0; + public boolean villagerLobotomizeEnabled = false; + public int villagerLobotomizeCheckInterval = 100; + public boolean villagerDisplayTradeItem = true; + public int villagerSpawnIronGolemRadius = 0; + public int villagerSpawnIronGolemLimit = 0; + private void villagerSettings() { + villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); + villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); + villagerControllable = getBoolean("mobs.villager.controllable", villagerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.villager.attributes.max-health", villagerMaxHealth); + set("mobs.villager.attributes.max-health", null); + set("mobs.villager.attributes.max_health", oldValue); + } + villagerMaxHealth = getDouble("mobs.villager.attributes.max_health", villagerMaxHealth); + villagerFollowEmeraldBlock = getBoolean("mobs.villager.follow-emerald-blocks", villagerFollowEmeraldBlock); + villagerCanBeLeashed = getBoolean("mobs.villager.can-be-leashed", villagerCanBeLeashed); + villagerCanBreed = getBoolean("mobs.villager.can-breed", villagerCanBreed); + villagerBreedingTicks = getInt("mobs.villager.breeding-delay-ticks", villagerBreedingTicks); + villagerClericsFarmWarts = getBoolean("mobs.villager.clerics-farm-warts", villagerClericsFarmWarts); + villagerClericFarmersThrowWarts = getBoolean("mobs.villager.cleric-wart-farmers-throw-warts-at-villagers", villagerClericFarmersThrowWarts); + villagerBypassMobGriefing = getBoolean("mobs.villager.bypass-mob-griefing", villagerBypassMobGriefing); + villagerTakeDamageFromWater = getBoolean("mobs.villager.takes-damage-from-water", villagerTakeDamageFromWater); + villagerAllowTrading = getBoolean("mobs.villager.allow-trading", villagerAllowTrading); + villagerAlwaysDropExp = getBoolean("mobs.villager.always-drop-exp", villagerAlwaysDropExp); + villagerMinimumDemand = getInt("mobs.villager.minimum-demand", villagerMinimumDemand); + if (PurpurConfig.version < 9) { + boolean oldValue = getBoolean("mobs.villager.lobotomize-1x1", villagerLobotomizeEnabled); + set("mobs.villager.lobotomize.enabled", oldValue); + set("mobs.villager.lobotomize-1x1", null); + } + if (PurpurConfig.version < 27) { + int oldValue = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheckInterval); + set("mobs.villager.lobotomize.check-interval", oldValue == 60 ? 100 : oldValue); + } + villagerLobotomizeEnabled = getBoolean("mobs.villager.lobotomize.enabled", villagerLobotomizeEnabled); + villagerLobotomizeCheckInterval = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheckInterval); + villagerDisplayTradeItem = getBoolean("mobs.villager.display-trade-item", villagerDisplayTradeItem); + villagerSpawnIronGolemRadius = getInt("mobs.villager.spawn-iron-golem.radius", villagerSpawnIronGolemRadius); + villagerSpawnIronGolemLimit = getInt("mobs.villager.spawn-iron-golem.limit", villagerSpawnIronGolemLimit); + } + + public boolean vindicatorRidable = false; + public boolean vindicatorRidableInWater = true; + public boolean vindicatorControllable = true; + public double vindicatorMaxHealth = 24.0D; + public double vindicatorJohnnySpawnChance = 0D; + public boolean vindicatorTakeDamageFromWater = false; + public boolean vindicatorAlwaysDropExp = false; + private void vindicatorSettings() { + vindicatorRidable = getBoolean("mobs.vindicator.ridable", vindicatorRidable); + vindicatorRidableInWater = getBoolean("mobs.vindicator.ridable-in-water", vindicatorRidableInWater); + vindicatorControllable = getBoolean("mobs.vindicator.controllable", vindicatorControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.vindicator.attributes.max-health", vindicatorMaxHealth); + set("mobs.vindicator.attributes.max-health", null); + set("mobs.vindicator.attributes.max_health", oldValue); + } + vindicatorMaxHealth = getDouble("mobs.vindicator.attributes.max_health", vindicatorMaxHealth); + vindicatorJohnnySpawnChance = getDouble("mobs.vindicator.johnny.spawn-chance", vindicatorJohnnySpawnChance); + vindicatorTakeDamageFromWater = getBoolean("mobs.vindicator.takes-damage-from-water", vindicatorTakeDamageFromWater); + vindicatorAlwaysDropExp = getBoolean("mobs.vindicator.always-drop-exp", vindicatorAlwaysDropExp); + } + + public boolean wanderingTraderRidable = false; + public boolean wanderingTraderRidableInWater = true; + public boolean wanderingTraderControllable = true; + public double wanderingTraderMaxHealth = 20.0D; + public boolean wanderingTraderFollowEmeraldBlock = false; + public boolean wanderingTraderCanBeLeashed = false; + public boolean wanderingTraderTakeDamageFromWater = false; + public boolean wanderingTraderAllowTrading = true; + public boolean wanderingTraderAlwaysDropExp = false; + private void wanderingTraderSettings() { + wanderingTraderRidable = getBoolean("mobs.wandering_trader.ridable", wanderingTraderRidable); + wanderingTraderRidableInWater = getBoolean("mobs.wandering_trader.ridable-in-water", wanderingTraderRidableInWater); + wanderingTraderControllable = getBoolean("mobs.wandering_trader.controllable", wanderingTraderControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.wandering_trader.attributes.max-health", wanderingTraderMaxHealth); + set("mobs.wandering_trader.attributes.max-health", null); + set("mobs.wandering_trader.attributes.max_health", oldValue); + } + wanderingTraderMaxHealth = getDouble("mobs.wandering_trader.attributes.max_health", wanderingTraderMaxHealth); + wanderingTraderFollowEmeraldBlock = getBoolean("mobs.wandering_trader.follow-emerald-blocks", wanderingTraderFollowEmeraldBlock); + wanderingTraderCanBeLeashed = getBoolean("mobs.wandering_trader.can-be-leashed", wanderingTraderCanBeLeashed); + wanderingTraderTakeDamageFromWater = getBoolean("mobs.wandering_trader.takes-damage-from-water", wanderingTraderTakeDamageFromWater); + wanderingTraderAllowTrading = getBoolean("mobs.wandering_trader.allow-trading", wanderingTraderAllowTrading); + wanderingTraderAlwaysDropExp = getBoolean("mobs.wandering_trader.always-drop-exp", wanderingTraderAlwaysDropExp); + } + + public boolean wardenRidable = false; + public boolean wardenRidableInWater = true; + public boolean wardenControllable = true; + private void wardenSettings() { + wardenRidable = getBoolean("mobs.warden.ridable", wardenRidable); + wardenRidableInWater = getBoolean("mobs.warden.ridable-in-water", wardenRidableInWater); + wardenControllable = getBoolean("mobs.warden.controllable", wardenControllable); + } + + public boolean witchRidable = false; + public boolean witchRidableInWater = true; + public boolean witchControllable = true; + public double witchMaxHealth = 26.0D; + public boolean witchTakeDamageFromWater = false; + public boolean witchAlwaysDropExp = false; + private void witchSettings() { + witchRidable = getBoolean("mobs.witch.ridable", witchRidable); + witchRidableInWater = getBoolean("mobs.witch.ridable-in-water", witchRidableInWater); + witchControllable = getBoolean("mobs.witch.controllable", witchControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.witch.attributes.max-health", witchMaxHealth); + set("mobs.witch.attributes.max-health", null); + set("mobs.witch.attributes.max_health", oldValue); + } + witchMaxHealth = getDouble("mobs.witch.attributes.max_health", witchMaxHealth); + witchTakeDamageFromWater = getBoolean("mobs.witch.takes-damage-from-water", witchTakeDamageFromWater); + witchAlwaysDropExp = getBoolean("mobs.witch.always-drop-exp", witchAlwaysDropExp); + } + + public boolean witherRidable = false; + public boolean witherRidableInWater = true; + public boolean witherControllable = true; + public double witherMaxY = 320D; + public double witherMaxHealth = 300.0D; + public float witherHealthRegenAmount = 1.0f; + public int witherHealthRegenDelay = 20; + public boolean witherBypassMobGriefing = false; + public boolean witherTakeDamageFromWater = false; + public boolean witherCanRideVehicles = false; + public float witherExplosionRadius = 1.0F; + public boolean witherPlaySpawnSound = true; + public boolean witherAlwaysDropExp = false; + private void witherSettings() { + witherRidable = getBoolean("mobs.wither.ridable", witherRidable); + witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater); + witherControllable = getBoolean("mobs.wither.controllable", witherControllable); + witherMaxY = getDouble("mobs.wither.ridable-max-y", witherMaxY); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.wither.max-health", witherMaxHealth); + set("mobs.wither.max_health", null); + set("mobs.wither.attributes.max-health", oldValue); + } else if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.wither.attributes.max-health", witherMaxHealth); + set("mobs.wither.attributes.max-health", null); + set("mobs.wither.attributes.max_health", oldValue); + } + witherMaxHealth = getDouble("mobs.wither.attributes.max_health", witherMaxHealth); + witherHealthRegenAmount = (float) getDouble("mobs.wither.health-regen-amount", witherHealthRegenAmount); + witherHealthRegenDelay = getInt("mobs.wither.health-regen-delay", witherHealthRegenDelay); + witherBypassMobGriefing = getBoolean("mobs.wither.bypass-mob-griefing", witherBypassMobGriefing); + witherTakeDamageFromWater = getBoolean("mobs.wither.takes-damage-from-water", witherTakeDamageFromWater); + witherCanRideVehicles = getBoolean("mobs.wither.can-ride-vehicles", witherCanRideVehicles); + witherExplosionRadius = (float) getDouble("mobs.wither.explosion-radius", witherExplosionRadius); + witherPlaySpawnSound = getBoolean("mobs.wither.play-spawn-sound", witherPlaySpawnSound); + witherAlwaysDropExp = getBoolean("mobs.wither.always-drop-exp", witherAlwaysDropExp); + } + + public boolean witherSkeletonRidable = false; + public boolean witherSkeletonRidableInWater = true; + public boolean witherSkeletonControllable = true; + public double witherSkeletonMaxHealth = 20.0D; + public boolean witherSkeletonTakeDamageFromWater = false; + public boolean witherSkeletonAlwaysDropExp = false; + private void witherSkeletonSettings() { + witherSkeletonRidable = getBoolean("mobs.wither_skeleton.ridable", witherSkeletonRidable); + witherSkeletonRidableInWater = getBoolean("mobs.wither_skeleton.ridable-in-water", witherSkeletonRidableInWater); + witherSkeletonControllable = getBoolean("mobs.wither_skeleton.controllable", witherSkeletonControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.wither_skeleton.attributes.max-health", witherSkeletonMaxHealth); + set("mobs.wither_skeleton.attributes.max-health", null); + set("mobs.wither_skeleton.attributes.max_health", oldValue); + } + witherSkeletonMaxHealth = getDouble("mobs.wither_skeleton.attributes.max_health", witherSkeletonMaxHealth); + witherSkeletonTakeDamageFromWater = getBoolean("mobs.wither_skeleton.takes-damage-from-water", witherSkeletonTakeDamageFromWater); + witherSkeletonAlwaysDropExp = getBoolean("mobs.wither_skeleton.always-drop-exp", witherSkeletonAlwaysDropExp); + } + + public boolean wolfRidable = false; + public boolean wolfRidableInWater = true; + public boolean wolfControllable = true; + public double wolfMaxHealth = 8.0D; + public DyeColor wolfDefaultCollarColor = DyeColor.RED; + public boolean wolfMilkCuresRabies = true; + public double wolfNaturalRabid = 0.0D; + public int wolfBreedingTicks = 6000; + public boolean wolfTakeDamageFromWater = false; + public boolean wolfAlwaysDropExp = false; + private void wolfSettings() { + wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable); + wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater); + wolfControllable = getBoolean("mobs.wolf.controllable", wolfControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.wolf.attributes.max-health", wolfMaxHealth); + set("mobs.wolf.attributes.max-health", null); + set("mobs.wolf.attributes.max_health", oldValue); + } + wolfMaxHealth = getDouble("mobs.wolf.attributes.max_health", wolfMaxHealth); + try { + wolfDefaultCollarColor = DyeColor.valueOf(getString("mobs.wolf.default-collar-color", wolfDefaultCollarColor.name())); + } catch (IllegalArgumentException ignore) { + wolfDefaultCollarColor = DyeColor.RED; + } + wolfMilkCuresRabies = getBoolean("mobs.wolf.milk-cures-rabid-wolves", wolfMilkCuresRabies); + wolfNaturalRabid = getDouble("mobs.wolf.spawn-rabid-chance", wolfNaturalRabid); + wolfBreedingTicks = getInt("mobs.wolf.breeding-delay-ticks", wolfBreedingTicks); + wolfTakeDamageFromWater = getBoolean("mobs.wolf.takes-damage-from-water", wolfTakeDamageFromWater); + wolfAlwaysDropExp = getBoolean("mobs.wolf.always-drop-exp", wolfAlwaysDropExp); + } + + public boolean zoglinRidable = false; + public boolean zoglinRidableInWater = true; + public boolean zoglinControllable = true; + public double zoglinMaxHealth = 40.0D; + public boolean zoglinTakeDamageFromWater = false; + public boolean zoglinAlwaysDropExp = false; + private void zoglinSettings() { + zoglinRidable = getBoolean("mobs.zoglin.ridable", zoglinRidable); + zoglinRidableInWater = getBoolean("mobs.zoglin.ridable-in-water", zoglinRidableInWater); + zoglinControllable = getBoolean("mobs.zoglin.controllable", zoglinControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zoglin.attributes.max-health", zoglinMaxHealth); + set("mobs.zoglin.attributes.max-health", null); + set("mobs.zoglin.attributes.max_health", oldValue); + } + zoglinMaxHealth = getDouble("mobs.zoglin.attributes.max_health", zoglinMaxHealth); + zoglinTakeDamageFromWater = getBoolean("mobs.zoglin.takes-damage-from-water", zoglinTakeDamageFromWater); + zoglinAlwaysDropExp = getBoolean("mobs.zoglin.always-drop-exp", zoglinAlwaysDropExp); + } + + public boolean zombieRidable = false; + public boolean zombieRidableInWater = true; + public boolean zombieControllable = true; + public double zombieMaxHealth = 20.0D; + public double zombieSpawnReinforcements = 0.1D; + public boolean zombieJockeyOnlyBaby = true; + public double zombieJockeyChance = 0.05D; + public boolean zombieJockeyTryExistingChickens = true; + public boolean zombieAggressiveTowardsVillagerWhenLagging = true; + public boolean zombieBypassMobGriefing = false; + public boolean zombieTakeDamageFromWater = false; + public boolean zombieAlwaysDropExp = false; + public double zombieHeadVisibilityPercent = 0.5D; + private void zombieSettings() { + zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); + zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); + zombieControllable = getBoolean("mobs.zombie.controllable", zombieControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zombie.attributes.max-health", zombieMaxHealth); + set("mobs.zombie.attributes.max-health", null); + set("mobs.zombie.attributes.max_health", oldValue); + } + zombieMaxHealth = getDouble("mobs.zombie.attributes.max_health", zombieMaxHealth); + zombieSpawnReinforcements = getDouble("mobs.zombie.attributes.spawn_reinforcements", zombieSpawnReinforcements); + zombieJockeyOnlyBaby = getBoolean("mobs.zombie.jockey.only-babies", zombieJockeyOnlyBaby); + zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance); + zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens); + zombieAggressiveTowardsVillagerWhenLagging = getBoolean("mobs.zombie.aggressive-towards-villager-when-lagging", zombieAggressiveTowardsVillagerWhenLagging); + zombieBypassMobGriefing = getBoolean("mobs.zombie.bypass-mob-griefing", zombieBypassMobGriefing); + zombieTakeDamageFromWater = getBoolean("mobs.zombie.takes-damage-from-water", zombieTakeDamageFromWater); + zombieAlwaysDropExp = getBoolean("mobs.zombie.always-drop-exp", zombieAlwaysDropExp); + zombieHeadVisibilityPercent = getDouble("mobs.zombie.head-visibility-percent", zombieHeadVisibilityPercent); + } + + public boolean zombieHorseRidableInWater = false; + public boolean zombieHorseCanSwim = false; + public double zombieHorseMaxHealthMin = 15.0D; + public double zombieHorseMaxHealthMax = 15.0D; + public double zombieHorseJumpStrengthMin = 0.4D; + public double zombieHorseJumpStrengthMax = 1.0D; + public double zombieHorseMovementSpeedMin = 0.2D; + public double zombieHorseMovementSpeedMax = 0.2D; + public double zombieHorseSpawnChance = 0.0D; + public boolean zombieHorseTakeDamageFromWater = false; + public boolean zombieHorseAlwaysDropExp = false; + private void zombieHorseSettings() { + zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater); + zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zombie_horse.attributes.max-health", zombieHorseMaxHealthMin); + set("mobs.zombie_horse.attributes.max-health", null); + set("mobs.zombie_horse.attributes.max_health.min", oldValue); + set("mobs.zombie_horse.attributes.max_health.max", oldValue); + } + zombieHorseMaxHealthMin = getDouble("mobs.zombie_horse.attributes.max_health.min", zombieHorseMaxHealthMin); + zombieHorseMaxHealthMax = getDouble("mobs.zombie_horse.attributes.max_health.max", zombieHorseMaxHealthMax); + zombieHorseJumpStrengthMin = getDouble("mobs.zombie_horse.attributes.jump_strength.min", zombieHorseJumpStrengthMin); + zombieHorseJumpStrengthMax = getDouble("mobs.zombie_horse.attributes.jump_strength.max", zombieHorseJumpStrengthMax); + zombieHorseMovementSpeedMin = getDouble("mobs.zombie_horse.attributes.movement_speed.min", zombieHorseMovementSpeedMin); + zombieHorseMovementSpeedMax = getDouble("mobs.zombie_horse.attributes.movement_speed.max", zombieHorseMovementSpeedMax); + zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance); + zombieHorseTakeDamageFromWater = getBoolean("mobs.zombie_horse.takes-damage-from-water", zombieHorseTakeDamageFromWater); + zombieHorseAlwaysDropExp = getBoolean("mobs.zombie_horse.always-drop-exp", zombieHorseAlwaysDropExp); + } + + public boolean zombieVillagerRidable = false; + public boolean zombieVillagerRidableInWater = true; + public boolean zombieVillagerControllable = true; + public double zombieVillagerMaxHealth = 20.0D; + public double zombieVillagerSpawnReinforcements = 0.1D; + public boolean zombieVillagerJockeyOnlyBaby = true; + public double zombieVillagerJockeyChance = 0.05D; + public boolean zombieVillagerJockeyTryExistingChickens = true; + public boolean zombieVillagerTakeDamageFromWater = false; + public int zombieVillagerCuringTimeMin = 3600; + public int zombieVillagerCuringTimeMax = 6000; + public boolean zombieVillagerCureEnabled = true; + public boolean zombieVillagerAlwaysDropExp = false; + private void zombieVillagerSettings() { + zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable); + zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater); + zombieVillagerControllable = getBoolean("mobs.zombie_villager.controllable", zombieVillagerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zombie_villager.attributes.max-health", zombieVillagerMaxHealth); + set("mobs.zombie_villager.attributes.max-health", null); + set("mobs.zombie_villager.attributes.max_health", oldValue); + } + zombieVillagerMaxHealth = getDouble("mobs.zombie_villager.attributes.max_health", zombieVillagerMaxHealth); + zombieVillagerSpawnReinforcements = getDouble("mobs.zombie_villager.attributes.spawn_reinforcements", zombieVillagerSpawnReinforcements); + zombieVillagerJockeyOnlyBaby = getBoolean("mobs.zombie_villager.jockey.only-babies", zombieVillagerJockeyOnlyBaby); + zombieVillagerJockeyChance = getDouble("mobs.zombie_villager.jockey.chance", zombieVillagerJockeyChance); + zombieVillagerJockeyTryExistingChickens = getBoolean("mobs.zombie_villager.jockey.try-existing-chickens", zombieVillagerJockeyTryExistingChickens); + zombieVillagerTakeDamageFromWater = getBoolean("mobs.zombie_villager.takes-damage-from-water", zombieVillagerTakeDamageFromWater); + zombieVillagerCuringTimeMin = getInt("mobs.zombie_villager.curing_time.min", zombieVillagerCuringTimeMin); + zombieVillagerCuringTimeMax = getInt("mobs.zombie_villager.curing_time.max", zombieVillagerCuringTimeMax); + zombieVillagerCureEnabled = getBoolean("mobs.zombie_villager.cure.enabled", zombieVillagerCureEnabled); + zombieVillagerAlwaysDropExp = getBoolean("mobs.zombie_villager.always-drop-exp", zombieVillagerAlwaysDropExp); + } + + public boolean zombifiedPiglinRidable = false; + public boolean zombifiedPiglinRidableInWater = true; + public boolean zombifiedPiglinControllable = true; + public double zombifiedPiglinMaxHealth = 20.0D; + public double zombifiedPiglinSpawnReinforcements = 0.0D; + public boolean zombifiedPiglinJockeyOnlyBaby = true; + public double zombifiedPiglinJockeyChance = 0.05D; + public boolean zombifiedPiglinJockeyTryExistingChickens = true; + public boolean zombifiedPiglinCountAsPlayerKillWhenAngry = true; + public boolean zombifiedPiglinTakeDamageFromWater = false; + public boolean zombifiedPiglinAlwaysDropExp = false; + private void zombifiedPiglinSettings() { + zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable); + zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater); + zombifiedPiglinControllable = getBoolean("mobs.zombified_piglin.controllable", zombifiedPiglinControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zombified_piglin.attributes.max-health", zombifiedPiglinMaxHealth); + set("mobs.zombified_piglin.attributes.max-health", null); + set("mobs.zombified_piglin.attributes.max_health", oldValue); + } + zombifiedPiglinMaxHealth = getDouble("mobs.zombified_piglin.attributes.max_health", zombifiedPiglinMaxHealth); + zombifiedPiglinSpawnReinforcements = getDouble("mobs.zombified_piglin.attributes.spawn_reinforcements", zombifiedPiglinSpawnReinforcements); + zombifiedPiglinJockeyOnlyBaby = getBoolean("mobs.zombified_piglin.jockey.only-babies", zombifiedPiglinJockeyOnlyBaby); + zombifiedPiglinJockeyChance = getDouble("mobs.zombified_piglin.jockey.chance", zombifiedPiglinJockeyChance); + zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens); + zombifiedPiglinCountAsPlayerKillWhenAngry = getBoolean("mobs.zombified_piglin.count-as-player-kill-when-angry", zombifiedPiglinCountAsPlayerKillWhenAngry); + zombifiedPiglinTakeDamageFromWater = getBoolean("mobs.zombified_piglin.takes-damage-from-water", zombifiedPiglinTakeDamageFromWater); + zombifiedPiglinAlwaysDropExp = getBoolean("mobs.zombified_piglin.always-drop-exp", zombifiedPiglinAlwaysDropExp); + } + + public float hungerStarvationDamage = 1.0F; + private void hungerSettings() { + hungerStarvationDamage = (float) getDouble("hunger.starvation-damage", hungerStarvationDamage); + } + + public int conduitDistance = 16; + public double conduitDamageDistance = 8; + public float conduitDamageAmount = 4; + public Block[] conduitBlocks; + private void conduitSettings() { + conduitDistance = getInt("blocks.conduit.effect-distance", conduitDistance); + conduitDamageDistance = getDouble("blocks.conduit.mob-damage.distance", conduitDamageDistance); + conduitDamageAmount = (float) getDouble("blocks.conduit.mob-damage.damage-amount", conduitDamageAmount); + List conduitBlockList = new ArrayList<>(); + getList("blocks.conduit.valid-ring-blocks", new ArrayList(){{ + add("minecraft:prismarine"); + add("minecraft:prismarine_bricks"); + add("minecraft:sea_lantern"); + add("minecraft:dark_prismarine"); + }}).forEach(key -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key.toString())); + if (!block.defaultBlockState().isAir()) { + conduitBlockList.add(block); + } + }); + conduitBlocks = conduitBlockList.toArray(Block[]::new); + } + + public float cauldronRainChance = 0.05F; + public float cauldronPowderSnowChance = 0.1F; + public float cauldronDripstoneWaterFillChance = 0.17578125F; + public float cauldronDripstoneLavaFillChance = 0.05859375F; + private void cauldronSettings() { + cauldronRainChance = (float) getDouble("blocks.cauldron.fill-chances.rain", cauldronRainChance); + cauldronPowderSnowChance = (float) getDouble("blocks.cauldron.fill-chances.powder-snow", cauldronPowderSnowChance); + cauldronDripstoneWaterFillChance = (float) getDouble("blocks.cauldron.fill-chances.dripstone-water", cauldronDripstoneWaterFillChance); + cauldronDripstoneLavaFillChance = (float) getDouble("blocks.cauldron.fill-chances.dripstone-lava", cauldronDripstoneLavaFillChance); + } + + 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..179727c6b3171c040d1aaf069525f61a9a2d54d9 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/PingCommand.java @@ -0,0 +1,33 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +import java.util.Collection; +import java.util.Collections; + +public class PingCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("ping") + .requires((listener) -> listener.hasPermission(2, "bukkit.command.ping")) + .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.ping.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + String output = String.format(PurpurConfig.pingCommandOutput, player.getGameProfile().getName(), player.latency); + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..2621e54879e9ab0029a875f1d09eee67878b90d5 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java @@ -0,0 +1,66 @@ +package org.purpurmc.purpur.command; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import org.purpurmc.purpur.PurpurConfig; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class PurpurCommand extends Command { + public PurpurCommand(String name) { + super(name); + this.description = "Purpur related commands"; + this.usageMessage = "/purpur [reload | version]"; + this.setPermission("bukkit.command.purpur"); + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { + if (args.length == 1) { + return Stream.of("reload", "version") + .filter(arg -> arg.startsWith(args[0].toLowerCase())) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if (!testPermission(sender)) return true; + + if (args.length != 1) { + sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); + return false; + } + + if (args[0].equalsIgnoreCase("reload")) { + Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues."); + Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server."); + + MinecraftServer console = MinecraftServer.getServer(); + PurpurConfig.init((File) console.options.valueOf("purpur-settings")); + for (ServerLevel level : console.getAllLevels()) { + level.purpurConfig.init(); + level.resetBreedingCooldowns(); + } + console.server.reloadCount++; + + Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Purpur config reload complete."); + } else if (args[0].equalsIgnoreCase("version")) { + Command verCmd = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version"); + if (verCmd != null) { + return verCmd.execute(sender, commandLabel, new String[0]); + } + } + + return true; + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java b/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..2852c07adb080c34905f5d1b19efed8ea47eecc6 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java @@ -0,0 +1,44 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; +import org.purpurmc.purpur.task.RamBarTask; + +import java.util.Collection; +import java.util.Collections; + +public class RamBarCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("rambar") + .requires(listener -> listener.hasPermission(2, "bukkit.command.rambar")) + .executes(context -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.rambar.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + boolean result = RamBarTask.instance().togglePlayer(player.getBukkitEntity()); + player.ramBar(result); + + Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.rambarCommandOutput, + Placeholder.component("onoff", Component.translatable(result ? "options.on" : "options.off") + .color(result ? NamedTextColor.GREEN : NamedTextColor.RED)), + Placeholder.parsed("target", player.getGameProfile().getName())); + + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/RamCommand.java b/src/main/java/org/purpurmc/purpur/command/RamCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..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/controller/FlyingMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java new file mode 100644 index 0000000000000000000000000000000000000000..ed494e0ad278813a0eb261101447b84cca3ad7aa --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java @@ -0,0 +1,71 @@ +package org.purpurmc.purpur.controller; + +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.player.Player; + +public class FlyingMoveControllerWASD extends MoveControllerWASD { + protected final float groundSpeedModifier; + protected final float flyingSpeedModifier; + protected int tooHighCooldown = 0; + protected boolean setNoGravityFlag; + + public FlyingMoveControllerWASD(Mob entity) { + this(entity, 1.0F); + } + + public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier) { + this(entity, groundSpeedModifier, 1.0F, true); + } + + public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, float flyingSpeedModifier) { + this(entity, groundSpeedModifier, flyingSpeedModifier, true); + } + + public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, float flyingSpeedModifier, boolean setNoGravityFlag) { + super(entity); + this.groundSpeedModifier = groundSpeedModifier; + this.flyingSpeedModifier = flyingSpeedModifier; + this.setNoGravityFlag = setNoGravityFlag; + } + + @Override + public void purpurTick(Player rider) { + float forward = Math.max(0.0F, rider.getForwardMot()); + float vertical = forward == 0.0F ? 0.0F : -(rider.xRotO / 45.0F); + float strafe = rider.getStrafeMot(); + + if (rider.jumping && spacebarEvent(entity)) { + entity.onSpacebar(); + } + + if (entity.getY() >= entity.getMaxY() || --tooHighCooldown > 0) { + if (tooHighCooldown <= 0) { + tooHighCooldown = 20; + } + entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.05D, 0.0D)); + vertical = 0.0F; + } + + setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED)); + float speed = (float) getSpeedModifier(); + + if (entity.onGround) { + speed *= groundSpeedModifier; // TODO = fix this! + } else { + speed *= flyingSpeedModifier; + } + + if (setNoGravityFlag) { + entity.setNoGravity(forward > 0); + } + + entity.setSpeed(speed); + entity.setVerticalMot(vertical); + entity.setStrafeMot(strafe); + entity.setForwardMot(forward); + + setForward(entity.getForwardMot()); + setStrafe(entity.getStrafeMot()); + } +} diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java new file mode 100644 index 0000000000000000000000000000000000000000..9383c07fa53141127106a1f289366a040960d52e --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java @@ -0,0 +1,63 @@ +package org.purpurmc.purpur.controller; + +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.Vec3; + +public class FlyingWithSpacebarMoveControllerWASD extends FlyingMoveControllerWASD { + public FlyingWithSpacebarMoveControllerWASD(Mob entity) { + super(entity); + } + + public FlyingWithSpacebarMoveControllerWASD(Mob entity, float groundSpeedModifier) { + super(entity, groundSpeedModifier); + } + + @Override + public void purpurTick(Player rider) { + float forward = rider.getForwardMot(); + float strafe = rider.getStrafeMot() * 0.5F; + float vertical = 0; + + if (forward < 0.0F) { + forward *= 0.5F; + strafe *= 0.5F; + } + + float speed = (float) entity.getAttributeValue(Attributes.MOVEMENT_SPEED); + + if (entity.onGround) { + speed *= groundSpeedModifier; + } + + if (rider.jumping && spacebarEvent(entity) && !entity.onSpacebar()) { + entity.setNoGravity(true); + vertical = 1.0F; + } else { + entity.setNoGravity(false); + } + + if (entity.getY() >= entity.getMaxY() || --tooHighCooldown > 0) { + if (tooHighCooldown <= 0) { + tooHighCooldown = 20; + } + entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.2D, 0.0D)); + vertical = 0.0F; + } + + setSpeedModifier(speed); + entity.setSpeed((float) getSpeedModifier()); + entity.setVerticalMot(vertical); + entity.setStrafeMot(strafe); + entity.setForwardMot(forward); + + setForward(entity.getForwardMot()); + setStrafe(entity.getStrafeMot()); + + Vec3 mot = entity.getDeltaMovement(); + if (mot.y > 0.2D) { + entity.setDeltaMovement(mot.x, 0.2D, mot.z); + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java new file mode 100644 index 0000000000000000000000000000000000000000..b8c25c96e95dd5ec3ad9fa4c41bd6c08e144832d --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java @@ -0,0 +1,76 @@ +package org.purpurmc.purpur.controller; + + +import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.control.LookControl; +import net.minecraft.world.entity.player.Player; + +public class LookControllerWASD extends LookControl { + protected final Mob entity; + private float yOffset = 0; + private float xOffset = 0; + + public LookControllerWASD(Mob entity) { + super(entity); + this.entity = entity; + } + + // tick + @Override + public void tick() { + if (entity.getRider() != null && entity.isControllable()) { + purpurTick(entity.getRider()); + } else { + vanillaTick(); + } + } + + protected void purpurTick(Player rider) { + setYawPitch(rider.getYRot(), rider.getXRot()); + } + + public void vanillaTick() { + super.tick(); + } + + public void setYawPitch(float yRot, float xRot) { + entity.setXRot(normalizePitch(xRot + xOffset)); + entity.setYRot(normalizeYaw(yRot + yOffset)); + entity.setYHeadRot(entity.getYRot()); + entity.xRotO = entity.getXRot(); + entity.yRotO = entity.getYRot(); + + entity.tracker.broadcast(new ClientboundMoveEntityPacket + .PosRot(entity.getId(), + (short) 0, (short) 0, (short) 0, + (byte) Mth.floor(entity.getYRot() * 256.0F / 360.0F), + (byte) Mth.floor(entity.getXRot() * 256.0F / 360.0F), + entity.onGround)); + } + + public void setOffsets(float yaw, float pitch) { + yOffset = yaw; + xOffset = pitch; + } + + public float normalizeYaw(float yaw) { + yaw %= 360.0f; + if (yaw >= 180.0f) { + yaw -= 360.0f; + } else if (yaw < -180.0f) { + yaw += 360.0f; + } + return yaw; + } + + public float normalizePitch(float pitch) { + if (pitch > 90.0f) { + pitch = 90.0f; + } else if (pitch < -90.0f) { + pitch = -90.0f; + } + return pitch; + } +} diff --git a/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java new file mode 100644 index 0000000000000000000000000000000000000000..21fd6ea2a482758a3016e3bc2cdebe2d89267481 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java @@ -0,0 +1,89 @@ +package org.purpurmc.purpur.controller; + +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.ai.control.MoveControl; +import net.minecraft.world.entity.player.Player; +import org.purpurmc.purpur.event.entity.RidableSpacebarEvent; + +public class MoveControllerWASD extends MoveControl { + protected final Mob entity; + private final double speedModifier; + + public MoveControllerWASD(Mob entity) { + this(entity, 1.0D); + } + + public MoveControllerWASD(Mob entity, double speedModifier) { + super(entity); + this.entity = entity; + this.speedModifier = speedModifier; + } + + @Override + public boolean hasWanted() { + return entity.getRider() != null ? strafeForwards != 0 || strafeRight != 0 : super.hasWanted(); + } + + @Override + public void tick() { + if (entity.getRider() != null && entity.isControllable()) { + purpurTick(entity.getRider()); + } else { + vanillaTick(); + } + } + + public void vanillaTick() { + super.tick(); + } + + public void purpurTick(Player rider) { + float forward = rider.getForwardMot() * 0.5F; + float strafe = rider.getStrafeMot() * 0.25F; + + if (forward <= 0.0F) { + forward *= 0.5F; + } + + float yawOffset = 0; + if (strafe != 0) { + if (forward == 0) { + yawOffset += strafe > 0 ? -90 : 90; + forward = Math.abs(strafe * 2); + } else { + yawOffset += strafe > 0 ? -30 : 30; + strafe /= 2; + if (forward < 0) { + yawOffset += strafe > 0 ? -110 : 110; + forward *= -1; + } + } + } else if (forward < 0) { + yawOffset -= 180; + forward *= -1; + } + + ((LookControllerWASD) entity.getLookControl()).setOffsets(yawOffset, 0); + + if (rider.jumping && spacebarEvent(entity) && !entity.onSpacebar() && entity.onGround) { + entity.jumpFromGround(); + } + + setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED) * speedModifier); + + entity.setSpeed((float) getSpeedModifier()); + entity.setForwardMot(forward); + + setForward(entity.getForwardMot()); + setStrafe(entity.getStrafeMot()); + } + + public static boolean spacebarEvent(Mob entity) { + if (RidableSpacebarEvent.getHandlerList().getRegisteredListeners().length > 0) { + return new RidableSpacebarEvent(entity.getBukkitEntity()).callEvent(); + } else { + return true; + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java new file mode 100644 index 0000000000000000000000000000000000000000..ba2a37dad43e238e54632975abea8ee6fafaa9e0 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java @@ -0,0 +1,50 @@ +package org.purpurmc.purpur.controller; + +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.player.Player; + +public class WaterMoveControllerWASD extends MoveControllerWASD { + private final double speedModifier; + + public WaterMoveControllerWASD(Mob entity) { + this(entity, 1.0D); + } + + public WaterMoveControllerWASD(Mob entity, double speedModifier) { + super(entity); + this.speedModifier = speedModifier; + } + + @Override + public void purpurTick(Player rider) { + float forward = rider.getForwardMot(); + float strafe = rider.getStrafeMot() * 0.5F; // strafe slower by default + float vertical = -(rider.xRotO / 90); + + if (forward == 0.0F) { + // strafe slower if not moving forward + strafe *= 0.5F; + // do not move vertically if not moving forward + vertical = 0.0F; + } else if (forward < 0.0F) { + // water animals can't swim backwards + forward = 0.0F; + vertical = 0.0F; + } + + if (rider.jumping && spacebarEvent(entity)) { + entity.onSpacebar(); + } + + setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED) * speedModifier); + entity.setSpeed((float) getSpeedModifier() * 0.1F); + + entity.setForwardMot(forward * (float) speedModifier); + entity.setStrafeMot(strafe * (float) speedModifier); + entity.setVerticalMot(vertical * (float) speedModifier); + + setForward(entity.getForwardMot()); + setStrafe(entity.getStrafeMot()); + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java new file mode 100644 index 0000000000000000000000000000000000000000..ce614ae6b1fa0b31c1ee8dacb69134bb20c949f4 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java @@ -0,0 +1,106 @@ +package org.purpurmc.purpur.entity; + +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.animal.Dolphin; +import net.minecraft.world.entity.projectile.LlamaSpit; +import net.minecraft.world.entity.projectile.ProjectileUtil; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; + +public class DolphinSpit extends LlamaSpit { + public LivingEntity dolphin; + public int ticksLived; + + public DolphinSpit(EntityType type, Level world) { + super(type, world); + } + + public DolphinSpit(Level world, Dolphin dolphin) { + this(EntityType.LLAMA_SPIT, world); + setOwner(dolphin.getRider() != null ? dolphin.getRider() : dolphin); + this.dolphin = dolphin; + this.setPos( + dolphin.getX() - (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(dolphin.yBodyRot * 0.017453292F), + dolphin.getEyeY() - 0.10000000149011612D, + dolphin.getZ() + (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(dolphin.yBodyRot * 0.017453292F)); + } + + // Purpur start + @Override + public boolean canSaveToDisk() { + return false; + } + // Purpur end + + public void tick() { + super_tick(); + + Vec3 mot = this.getDeltaMovement(); + HitResult hitResult = ProjectileUtil.getHitResult(this.position(), this, this::canHitEntity, mot, level()); + + this.preOnHit(hitResult); + + double x = this.getX() + mot.x; + double y = this.getY() + mot.y; + double z = this.getZ() + mot.z; + + this.updateRotation(); + + Vec3 motDouble = mot.scale(2.0); + for (int i = 0; i < 5; i++) { + ((ServerLevel) level()).sendParticles(null, ParticleTypes.BUBBLE, + getX() + random.nextFloat() / 2 - 0.25F, + getY() + random.nextFloat() / 2 - 0.25F, + getZ() + random.nextFloat() / 2 - 0.25F, + 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D, true); + } + + if (++ticksLived > 20) { + this.discard(); + } else { + this.setDeltaMovement(mot.scale(0.99D)); + if (!this.isNoGravity()) { + this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); + } + + this.setPos(x, y, z); + } + } + + @Override + public void shoot(double x, double y, double z, float speed, float inaccuracy) { + setDeltaMovement(new Vec3(x, y, z).normalize().add( + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) + .scale(speed)); + } + + @Override + protected void onHitEntity(EntityHitResult entityHitResult) { + Entity shooter = this.getOwner(); + if (shooter instanceof LivingEntity) { + entityHitResult.getEntity().hurt(entityHitResult.getEntity().damageSources().mobProjectile(this, (LivingEntity) shooter), level().purpurConfig.dolphinSpitDamage); + } + } + + @Override + protected void onHitBlock(BlockHitResult blockHitResult) { + if (this.hitCancelled) { + return; + } + BlockState state = this.level().getBlockState(blockHitResult.getBlockPos()); + state.onProjectileHit(this.level(), state, blockHitResult, this); + this.discard(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java new file mode 100644 index 0000000000000000000000000000000000000000..ea8b928b6d82689e71bbcc39ab497491072dfba6 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java @@ -0,0 +1,121 @@ +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 + @Override + public boolean canSaveToDisk() { + return false; + } + // Purpur end + + public void tick() { + super_tick(); + + Vec3 mot = this.getDeltaMovement(); + HitResult hitResult = ProjectileUtil.getHitResult(this.position(), this, this::canHitEntity, mot, level()); + + this.preOnHit(hitResult); + + double x = this.getX() + mot.x; + double y = this.getY() + mot.y; + double z = this.getZ() + mot.z; + + this.updateRotation(); + + Vec3 motDouble = mot.scale(2.0); + for (int i = 0; i < 5; i++) { + ((ServerLevel) level()).sendParticles(null, ParticleTypes.FLAME, + getX() + random.nextFloat() / 2 - 0.25F, + getY() + random.nextFloat() / 2 - 0.25F, + getZ() + random.nextFloat() / 2 - 0.25F, + 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D, true); + } + + if (++ticksLived > 20) { + this.discard(); + } else if (this.level().getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) { + this.discard(); + } else if (this.isInWaterOrBubble()) { + this.discard(); + } else { + this.setDeltaMovement(mot.scale(0.99D)); + if (!this.isNoGravity()) { + this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); + } + + this.setPos(x, y, z); + } + } + + @Override + public void shoot(double x, double y, double z, float speed, float inaccuracy) { + setDeltaMovement(new Vec3(x, y, z).normalize().add( + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) + .scale(speed)); + } + + @Override + protected void onHitEntity(EntityHitResult entityHitResult) { + Entity shooter = this.getOwner(); + if (shooter instanceof LivingEntity) { + Entity target = entityHitResult.getEntity(); + if (canGrief || (target instanceof LivingEntity && !(target instanceof ArmorStand))) { + boolean hurt = target.hurt(target.damageSources().mobProjectile(this, (LivingEntity) shooter), level().purpurConfig.phantomFlameDamage); + if (hurt && level().purpurConfig.phantomFlameFireTime > 0) { + target.setSecondsOnFire(level().purpurConfig.phantomFlameFireTime); + } + } + } + } + + @Override + protected void onHitBlock(BlockHitResult blockHitResult) { + if (this.hitCancelled) { + return; + } + if (this.canGrief) { + BlockState state = this.level().getBlockState(blockHitResult.getBlockPos()); + state.onProjectileHit(this.level(), state, blockHitResult, this); + } + this.discard(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java b/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java new file mode 100644 index 0000000000000000000000000000000000000000..8efca1d91188ac4db911a8eb0fa9ea2cc3c48e28 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java @@ -0,0 +1,102 @@ +package org.purpurmc.purpur.entity; + +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.Component; +import net.minecraft.nbt.Tag; +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; + + this.customName = handle.entityData.contains("CustomName", Tag.TAG_STRING) + ? PaperAdventure.asAdventure(net.minecraft.network.chat.Component.Serializer.fromJson(handle.entityData.getString("CustomName"))) + : null; + + if(handle.entityData.contains("BukkitValues", Tag.TAG_COMPOUND)) { + this.persistentDataContainer.putAll(handle.entityData.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.entityData.put("BukkitValues", this.persistentDataContainer.toTagCompound()); + if(customName == null) { + handle.entityData.remove("CustomName"); + } else { + handle.entityData.putString("CustomName", net.minecraft.network.chat.Component.Serializer.toJson(PaperAdventure.asVanilla(customName))); + } + } +} 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..0f2e7e0b81620c8581949bd5f0bdb567cd93c17e --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java @@ -0,0 +1,54 @@ +package org.purpurmc.purpur.gui; + +import net.md_5.bungee.api.ChatColor; + +import java.awt.Color; +import java.util.HashMap; +import java.util.Map; + +public enum GUIColor { + BLACK(ChatColor.BLACK, new Color(0x000000)), + DARK_BLUE(ChatColor.DARK_BLUE, new Color(0x0000AA)), + DARK_GREEN(ChatColor.DARK_GREEN, new Color(0x00AA00)), + DARK_AQUA(ChatColor.DARK_AQUA, new Color(0x009999)), + DARK_RED(ChatColor.DARK_RED, new Color(0xAA0000)), + DARK_PURPLE(ChatColor.DARK_PURPLE, new Color(0xAA00AA)), + GOLD(ChatColor.GOLD, new Color(0xBB8800)), + GRAY(ChatColor.GRAY, new Color(0x888888)), + DARK_GRAY(ChatColor.DARK_GRAY, new Color(0x444444)), + BLUE(ChatColor.BLUE, new Color(0x5555FF)), + GREEN(ChatColor.GREEN, new Color(0x55FF55)), + AQUA(ChatColor.AQUA, new Color(0x55DDDD)), + RED(ChatColor.RED, new Color(0xFF5555)), + LIGHT_PURPLE(ChatColor.LIGHT_PURPLE, new Color(0xFF55FF)), + YELLOW(ChatColor.YELLOW, new Color(0xFFBB00)), + WHITE(ChatColor.WHITE, new Color(0xBBBBBB)); + + private final ChatColor chat; + private final Color color; + + private static final Map BY_CHAT = new HashMap<>(); + + GUIColor(ChatColor chat, Color color) { + this.chat = chat; + this.color = color; + } + + public Color getColor() { + return color; + } + + public String getCode() { + return chat.toString(); + } + + public static GUIColor getColor(ChatColor chat) { + return BY_CHAT.get(chat); + } + + static { + for (GUIColor color : values()) { + BY_CHAT.put(color.chat, color); + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java new file mode 100644 index 0000000000000000000000000000000000000000..33e89b4c00fa8318506b36cbe49fe4e412e0a9a1 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java @@ -0,0 +1,78 @@ +package org.purpurmc.purpur.gui; + +import com.google.common.collect.Sets; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; + +import javax.swing.JTextPane; +import javax.swing.Timer; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyleContext; +import java.util.Set; + +public class JColorTextPane extends JTextPane { + private static final GUIColor DEFAULT_COLOR = GUIColor.BLACK; + + public void append(String msg) { + // TODO: update to use adventure instead + BaseComponent[] components = TextComponent.fromLegacyText(DEFAULT_COLOR.getCode() + msg, ChatColor.BLACK); + for (BaseComponent component : components) { + String text = component.toPlainText(); + if (text == null || text.isEmpty()) { + continue; + } + + GUIColor guiColor = GUIColor.getColor(component.getColor()); + + StyleContext context = StyleContext.getDefaultStyleContext(); + AttributeSet attr = context.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, guiColor.getColor()); + attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Bold, component.isBold() || guiColor != DEFAULT_COLOR); + attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Italic, component.isItalic()); + attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Underline, component.isUnderlined()); + attr = context.addAttribute(attr, StyleConstants.CharacterConstants.StrikeThrough, component.isStrikethrough()); + //attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Blink, component.isObfuscated()); // no such thing as Blink, sadly + + try { + int pos = getDocument().getLength(); + getDocument().insertString(pos, text, attr); + + if (component.isObfuscated()) { + // dirty hack to blink some text + Blink blink = new Blink(pos, text.length(), attr, context.addAttribute(attr, StyleConstants.Foreground, getBackground())); + BLINKS.add(blink); + } + } catch (BadLocationException ignore) { + } + } + } + + private static final Set BLINKS = Sets.newHashSet(); + private static boolean SYNC_BLINK; + + static { + new Timer(500, e -> { + SYNC_BLINK = !SYNC_BLINK; + BLINKS.forEach(Blink::blink); + }).start(); + } + + public class Blink { + private final int start, length; + private final AttributeSet attr1, attr2; + + private Blink(int start, int length, AttributeSet attr1, AttributeSet attr2) { + this.start = start; + this.length = length; + this.attr1 = attr1; + this.attr2 = attr2; + } + + private void blink() { + getStyledDocument().setCharacterAttributes(start, length, SYNC_BLINK ? attr1 : attr2, true); + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java new file mode 100644 index 0000000000000000000000000000000000000000..7f526883495b3222746de3d0442e9e4fb5107036 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java @@ -0,0 +1,26 @@ +package org.purpurmc.purpur.item; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemNameBlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import org.bukkit.event.entity.EntityPotionEffectEvent; + +public class GlowBerryItem extends ItemNameBlockItem { + public GlowBerryItem(Block block, Properties settings) { + super(block, settings); + } + + @Override + public ItemStack finishUsingItem(ItemStack stack, Level world, LivingEntity user) { + ItemStack result = super.finishUsingItem(stack, world, user); + if (world.purpurConfig.glowBerriesEatGlowDuration > 0 && user instanceof ServerPlayer player) { + player.addEffect(new MobEffectInstance(MobEffects.GLOWING, world.purpurConfig.glowBerriesEatGlowDuration), EntityPotionEffectEvent.Cause.FOOD); + } + return result; + } +} diff --git a/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java new file mode 100644 index 0000000000000000000000000000000000000000..c038fb2bbb0f0e78380bc24bbd6348b869669a90 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java @@ -0,0 +1,36 @@ +package org.purpurmc.purpur.item; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.SpawnerBlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class SpawnerItem extends BlockItem { + + public SpawnerItem(Block block, Properties settings) { + super(block, settings); + } + + @Override + protected boolean updateCustomBlockEntityTag(BlockPos pos, Level level, Player player, ItemStack stack, BlockState state) { + boolean handled = super.updateCustomBlockEntityTag(pos, level, player, stack, state); + if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.place.spawners")) { + BlockEntity spawner = level.getBlockEntity(pos); + if (spawner instanceof SpawnerBlockEntity && stack.hasTag()) { + CompoundTag tag = stack.getTag(); + if (tag.contains("Purpur.mob_type")) { + EntityType.byString(tag.getString("Purpur.mob_type")).ifPresent(type -> + ((SpawnerBlockEntity) spawner).getSpawner().setEntityId(type, level, level.random, pos)); + } + } + } + return handled; + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java b/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java new file mode 100644 index 0000000000000000000000000000000000000000..2ebbaf5faa92a88bfb4d61298951e5b74157d1e1 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java @@ -0,0 +1,81 @@ +package org.purpurmc.purpur.task; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import io.netty.buffer.Unpooled; +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.block.entity.BeehiveBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin; +import org.bukkit.entity.Player; +import org.bukkit.plugin.PluginBase; +import org.bukkit.plugin.messaging.PluginMessageListener; +import org.jetbrains.annotations.NotNull; + +public class BeehiveTask implements PluginMessageListener { + public static final ResourceLocation BEEHIVE_C2S = new ResourceLocation("purpur", "beehive_c2s"); + public static final ResourceLocation BEEHIVE_S2C = new ResourceLocation("purpur", "beehive_s2c"); + + private static BeehiveTask instance; + + public static BeehiveTask instance() { + if (instance == null) { + instance = new BeehiveTask(); + } + return instance; + } + + private final PluginBase plugin = new MinecraftInternalPlugin(); + + private BeehiveTask() { + } + + public void register() { + Bukkit.getMessenger().registerOutgoingPluginChannel(this.plugin, BEEHIVE_S2C.toString()); + Bukkit.getMessenger().registerIncomingPluginChannel(this.plugin, BEEHIVE_C2S.toString(), this); + } + + public void unregister() { + Bukkit.getMessenger().unregisterOutgoingPluginChannel(this.plugin, BEEHIVE_S2C.toString()); + Bukkit.getMessenger().unregisterIncomingPluginChannel(this.plugin, BEEHIVE_C2S.toString()); + } + + @Override + public void onPluginMessageReceived(@NotNull String channel, Player player, byte[] bytes) { + ByteArrayDataInput in = in(bytes); + long packedPos = in.readLong(); + BlockPos pos = BlockPos.of(packedPos); + + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + + BlockEntity blockEntity = serverPlayer.level().getBlockEntity(pos); + if (!(blockEntity instanceof BeehiveBlockEntity beehive)) { + return; + } + + ByteArrayDataOutput out = out(); + + out.writeInt(beehive.getOccupantCount()); + out.writeLong(packedPos); + + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(out.toByteArray())); + serverPlayer.connection.send(new ClientboundCustomPayloadPacket(BEEHIVE_S2C, buf)); + } + + @SuppressWarnings("UnstableApiUsage") + private static ByteArrayDataOutput out() { + return ByteStreams.newDataOutput(); + } + + @SuppressWarnings("UnstableApiUsage") + private static ByteArrayDataInput in(byte[] bytes) { + return ByteStreams.newDataInput(bytes); + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/BossBarTask.java b/src/main/java/org/purpurmc/purpur/task/BossBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..114f273dd7f8b8a3c02f0651f6944859b33a65d4 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/BossBarTask.java @@ -0,0 +1,121 @@ +package org.purpurmc.purpur.task; + +import net.kyori.adventure.bossbar.BossBar; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; + +public abstract class BossBarTask extends BukkitRunnable { + private final Map bossbars = new HashMap<>(); + private boolean started; + + abstract BossBar createBossBar(); + + abstract void updateBossBar(BossBar bossbar, Player player); + + @Override + public void run() { + Iterator> iter = bossbars.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + Player player = Bukkit.getPlayer(entry.getKey()); + if (player == null) { + iter.remove(); + continue; + } + updateBossBar(entry.getValue(), player); + } + } + + @Override + public void cancel() { + super.cancel(); + new HashSet<>(this.bossbars.keySet()).forEach(uuid -> { + Player player = Bukkit.getPlayer(uuid); + if (player != null) { + removePlayer(player); + } + }); + this.bossbars.clear(); + } + + public boolean removePlayer(Player player) { + BossBar bossbar = this.bossbars.remove(player.getUniqueId()); + if (bossbar != null) { + player.hideBossBar(bossbar); + return true; + } + return false; + } + + public void addPlayer(Player player) { + removePlayer(player); + BossBar bossbar = createBossBar(); + this.bossbars.put(player.getUniqueId(), bossbar); + this.updateBossBar(bossbar, player); + player.showBossBar(bossbar); + } + + public boolean hasPlayer(UUID uuid) { + return this.bossbars.containsKey(uuid); + } + + public boolean togglePlayer(Player player) { + if (removePlayer(player)) { + return false; + } + addPlayer(player); + return true; + } + + public void start() { + stop(); + this.runTaskTimerAsynchronously(new MinecraftInternalPlugin(), 1, 1); + started = true; + } + + public void stop() { + if (started) { + cancel(); + } + } + + public static void startAll() { + RamBarTask.instance().start(); + TPSBarTask.instance().start(); + CompassTask.instance().start(); + } + + public static void stopAll() { + RamBarTask.instance().stop(); + TPSBarTask.instance().stop(); + CompassTask.instance().stop(); + } + + public static void addToAll(ServerPlayer player) { + Player bukkit = player.getBukkitEntity(); + if (player.ramBar()) { + RamBarTask.instance().addPlayer(bukkit); + } + if (player.tpsBar()) { + TPSBarTask.instance().addPlayer(bukkit); + } + if (player.compassBar()) { + CompassTask.instance().addPlayer(bukkit); + } + } + + public static void removeFromAll(Player player) { + RamBarTask.instance().removePlayer(player); + TPSBarTask.instance().removePlayer(player); + CompassTask.instance().removePlayer(player); + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/CompassTask.java b/src/main/java/org/purpurmc/purpur/task/CompassTask.java new file mode 100644 index 0000000000000000000000000000000000000000..bece7eefc8ba8822b433835526251d2fb916c025 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/CompassTask.java @@ -0,0 +1,68 @@ +package org.purpurmc.purpur.task; + +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.item.Items; +import org.bukkit.entity.Player; +import org.purpurmc.purpur.PurpurConfig; + +public class CompassTask extends BossBarTask { + private static CompassTask instance; + + private int tick = 0; + + public static CompassTask instance() { + if (instance == null) { + instance = new CompassTask(); + } + return instance; + } + + @Override + public void run() { + if (++tick < PurpurConfig.commandCompassBarTickInterval) { + return; + } + tick = 0; + + MinecraftServer.getServer().getAllLevels().forEach((level) -> { + if (level.purpurConfig.compassItemShowsBossBar) { + level.players().forEach(player -> { + if (!player.compassBar()) { + if (player.getMainHandItem().getItem() != Items.COMPASS && player.getOffhandItem().getItem() != Items.COMPASS) { + removePlayer(player.getBukkitEntity()); + } else if (!hasPlayer(player.getUUID())) { + addPlayer(player.getBukkitEntity()); + } + } + }); + } + }); + + super.run(); + } + + @Override + BossBar createBossBar() { + return BossBar.bossBar(Component.text(""), PurpurConfig.commandCompassBarProgressPercent, PurpurConfig.commandCompassBarProgressColor, PurpurConfig.commandCompassBarProgressOverlay); + } + + @Override + void updateBossBar(BossBar bossbar, Player player) { + float yaw = player.getLocation().getYaw(); + int length = PurpurConfig.commandCompassBarTitle.length(); + int pos = (int) ((normalize(yaw) * (length / 720F)) + (length / 2F)); + bossbar.name(Component.text(PurpurConfig.commandCompassBarTitle.substring(pos - 25, pos + 25))); + } + + private float normalize(float yaw) { + while (yaw < -180.0F) { + yaw += 360.0F; + } + while (yaw > 180.0F) { + yaw -= 360.0F; + } + return yaw; + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/RamBarTask.java b/src/main/java/org/purpurmc/purpur/task/RamBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..8e98c0ae73e2c40002a72b5d0d246ffa0c3ab38f --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/RamBarTask.java @@ -0,0 +1,117 @@ +package org.purpurmc.purpur.task; + +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import org.bukkit.entity.Player; +import org.purpurmc.purpur.PurpurConfig; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryUsage; + +public class RamBarTask extends BossBarTask { + private static RamBarTask instance; + private long allocated = 0L; + private long used = 0L; + private long xmx = 0L; + private long xms = 0L; + private float percent = 0F; + private int tick = 0; + + public static RamBarTask instance() { + if (instance == null) { + instance = new RamBarTask(); + } + return instance; + } + + @Override + BossBar createBossBar() { + return BossBar.bossBar(Component.text(""), 0.0F, instance().getBossBarColor(), PurpurConfig.commandRamBarProgressOverlay); + } + + @Override + void updateBossBar(BossBar bossbar, Player player) { + bossbar.progress(getBossBarProgress()); + bossbar.color(getBossBarColor()); + bossbar.name(MiniMessage.miniMessage().deserialize(PurpurConfig.commandRamBarTitle, + Placeholder.component("allocated", format(this.allocated)), + Placeholder.component("used", format(this.used)), + Placeholder.component("xmx", format(this.xmx)), + Placeholder.component("xms", format(this.xms)), + Placeholder.unparsed("percent", ((int) (this.percent * 100)) + "%") + )); + } + + @Override + public void run() { + if (++this.tick < PurpurConfig.commandRamBarTickInterval) { + return; + } + this.tick = 0; + + MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); + + this.allocated = heap.getCommitted(); + this.used = heap.getUsed(); + this.xmx = heap.getMax(); + this.xms = heap.getInit(); + this.percent = Math.max(Math.min((float) this.used / this.xmx, 1.0F), 0.0F); + + super.run(); + } + + private float getBossBarProgress() { + return this.percent; + } + + private BossBar.Color getBossBarColor() { + if (this.percent < 0.5F) { + return PurpurConfig.commandRamBarProgressColorGood; + } else if (this.percent < 0.75F) { + return PurpurConfig.commandRamBarProgressColorMedium; + } else { + return PurpurConfig.commandRamBarProgressColorLow; + } + } + + public Component format(long v) { + String color; + if (this.percent < 0.60F) { + color = PurpurConfig.commandRamBarTextColorGood; + } else if (this.percent < 0.85F) { + color = PurpurConfig.commandRamBarTextColorMedium; + } else { + color = PurpurConfig.commandRamBarTextColorLow; + } + String value; + if (v < 1024) { + value = v + "B"; + } else { + int z = (63 - Long.numberOfLeadingZeros(v)) / 10; + value = String.format("%.1f%s", (double) v / (1L << (z * 10)), "BKMGTPE".charAt(z)); + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.unparsed("text", value)); + } + + public long getAllocated() { + return this.allocated; + } + + public long getUsed() { + return this.used; + } + + public long getXmx() { + return this.xmx; + } + + public long getXms() { + return this.xms; + } + + public float getPercent() { + return this.percent; + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java b/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..8769993e7ca59da309087051a3cd38fc562c15d1 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java @@ -0,0 +1,142 @@ +package org.purpurmc.purpur.task; + +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import org.purpurmc.purpur.PurpurConfig; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class TPSBarTask extends BossBarTask { + private static TPSBarTask instance; + private double tps = 20.0D; + private double mspt = 0.0D; + private int tick = 0; + + public static TPSBarTask instance() { + if (instance == null) { + instance = new TPSBarTask(); + } + return instance; + } + + @Override + BossBar createBossBar() { + return BossBar.bossBar(Component.text(""), 0.0F, instance().getBossBarColor(), PurpurConfig.commandTPSBarProgressOverlay); + } + + @Override + void updateBossBar(BossBar bossbar, Player player) { + bossbar.progress(getBossBarProgress()); + bossbar.color(getBossBarColor()); + bossbar.name(MiniMessage.miniMessage().deserialize(PurpurConfig.commandTPSBarTitle, + Placeholder.component("tps", getTPSColor()), + Placeholder.component("mspt", getMSPTColor()), + Placeholder.component("ping", getPingColor(player.getPing())) + )); + } + + @Override + public void run() { + if (++tick < PurpurConfig.commandTPSBarTickInterval) { + return; + } + tick = 0; + + this.tps = Math.max(Math.min(Bukkit.getTPS()[0], 20.0D), 0.0D); + this.mspt = Bukkit.getAverageTickTime(); + + super.run(); + } + + private float getBossBarProgress() { + if (PurpurConfig.commandTPSBarProgressFillMode == FillMode.MSPT) { + return Math.max(Math.min((float) mspt / 50.0F, 1.0F), 0.0F); + } else { + return Math.max(Math.min((float) tps / 20.0F, 1.0F), 0.0F); + } + } + + private BossBar.Color getBossBarColor() { + if (isGood(PurpurConfig.commandTPSBarProgressFillMode)) { + return PurpurConfig.commandTPSBarProgressColorGood; + } else if (isMedium(PurpurConfig.commandTPSBarProgressFillMode)) { + return PurpurConfig.commandTPSBarProgressColorMedium; + } else { + return PurpurConfig.commandTPSBarProgressColorLow; + } + } + + private boolean isGood(FillMode mode) { + return isGood(mode, 0); + } + + private boolean isGood(FillMode mode, int ping) { + if (mode == FillMode.MSPT) { + return mspt < 40; + } else if (mode == FillMode.TPS) { + return tps >= 19; + } else if (mode == FillMode.PING) { + return ping < 100; + } else { + return false; + } + } + + private boolean isMedium(FillMode mode) { + return isMedium(mode, 0); + } + + private boolean isMedium(FillMode mode, int ping) { + if (mode == FillMode.MSPT) { + return mspt < 50; + } else if (mode == FillMode.TPS) { + return tps >= 15; + } else if (mode == FillMode.PING) { + return ping < 200; + } else { + return false; + } + } + + private Component getTPSColor() { + String color; + if (isGood(FillMode.TPS)) { + color = PurpurConfig.commandTPSBarTextColorGood; + } else if (isMedium(FillMode.TPS)) { + color = PurpurConfig.commandTPSBarTextColorMedium; + } else { + color = PurpurConfig.commandTPSBarTextColorLow; + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%.2f", tps))); + } + + private Component getMSPTColor() { + String color; + if (isGood(FillMode.MSPT)) { + color = PurpurConfig.commandTPSBarTextColorGood; + } else if (isMedium(FillMode.MSPT)) { + color = PurpurConfig.commandTPSBarTextColorMedium; + } else { + color = PurpurConfig.commandTPSBarTextColorLow; + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%.2f", mspt))); + } + + private Component getPingColor(int ping) { + String color; + if (isGood(FillMode.PING, ping)) { + color = PurpurConfig.commandTPSBarTextColorGood; + } else if (isMedium(FillMode.PING, ping)) { + color = PurpurConfig.commandTPSBarTextColorMedium; + } else { + color = PurpurConfig.commandTPSBarTextColorLow; + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%s", ping))); + } + + public enum FillMode { + TPS, MSPT, PING + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Actionable.java b/src/main/java/org/purpurmc/purpur/tool/Actionable.java new file mode 100644 index 0000000000000000000000000000000000000000..e18c37f06730da9d3055d5215e813b1477c1e70e --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Actionable.java @@ -0,0 +1,24 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Map; + +public abstract class Actionable { + private final Block into; + private final Map drops; + + public Actionable(Block into, Map drops) { + this.into = into; + this.drops = drops; + } + + public Block into() { + return into; + } + + public Map drops() { + return drops; + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/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/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java index 68557964e27fa1e5ba218178f9bcc0b28e3a78d9..43d9e7287cf0e498ccbff9b865bb813e7fb567c0 100644 --- a/src/main/java/org/spigotmc/ActivationRange.java +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -15,6 +15,7 @@ import net.minecraft.world.entity.ambient.AmbientCreature; import net.minecraft.world.entity.animal.Animal; import net.minecraft.world.entity.animal.Bee; import net.minecraft.world.entity.animal.Sheep; +import net.minecraft.world.entity.animal.Squid; import net.minecraft.world.entity.animal.WaterAnimal; import net.minecraft.world.entity.animal.horse.Llama; import net.minecraft.world.entity.boss.EnderDragonPart; @@ -169,7 +170,7 @@ public class ActivationRange */ public static void activateEntities(Level world) { - MinecraftTimings.entityActivationCheckTimer.startTiming(); + //MinecraftTimings.entityActivationCheckTimer.startTiming(); // Purpur final int miscActivationRange = world.spigotConfig.miscActivationRange; final int raiderActivationRange = world.spigotConfig.raiderActivationRange; final int animalActivationRange = world.spigotConfig.animalActivationRange; @@ -203,6 +204,7 @@ public class ActivationRange continue; } + if (!player.level().purpurConfig.idleTimeoutTickNearbyEntities && player.isAfk()) continue; // Purpur // Paper start int worldHeight = world.getHeight(); ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, worldHeight, maxRange ); @@ -243,7 +245,7 @@ public class ActivationRange } // Paper end } - MinecraftTimings.entityActivationCheckTimer.stopTiming(); + //MinecraftTimings.entityActivationCheckTimer.stopTiming(); // Purpur } /** @@ -396,6 +398,7 @@ public class ActivationRange */ public static boolean checkIfActive(Entity entity) { + if (entity.level().purpurConfig.squidImmuneToEAR && entity instanceof Squid) return true; // Purpur // Never safe to skip fireworks or entities not yet added to chunk if ( entity instanceof FireworkRocketEntity ) { return true; diff --git a/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/src/main/java/org/spigotmc/TicksPerSecondCommand.java index bf970bf3356a914459c2d6db93537ce2d32c7e18..08221c7256f41ca511a4a61ffb2b979325aebee9 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)); 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 50c72e5db369a180f425eaaa0411cb8871bc3463..dbd502761ff6e6efb252bb41376a7ff028c73895 100644 --- a/src/main/java/org/spigotmc/WatchdogThread.java +++ b/src/main/java/org/spigotmc/WatchdogThread.java @@ -96,7 +96,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa private WatchdogThread(long timeoutTime, boolean restart) { - super( "Paper Watchdog Thread" ); + super( "Watchdog Thread" ); // Purpur - use a generic name this.timeoutTime = timeoutTime; this.restart = restart; earlyWarningEvery = Math.min(io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.earlyWarningEvery, timeoutTime); // Paper @@ -155,14 +155,14 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa 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 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 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 // if ( net.minecraft.world.level.Level.lastPhysicsProblem != null ) { @@ -185,12 +185,12 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa // 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 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 io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(isLongTimeout); // Paper // Paper - rewrite chunk system this.dumpTickingInfo(); // Paper - log detailed tick information WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); @@ -206,7 +206,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa 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 } log.log( Level.SEVERE, "------------------------------" ); diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 675cd61221e807aadf28322b46c3daa1370241b5..0769f5c4711a3b7f59489e611ed01ad8367e5db1 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 zcmWk!Wmptl7+qlLS~|W3b}1=IK|*5b5@|#_l0AUP zC6t~Ie$33hbMG7HJ?G9mbDv4n)lnlSVI~2AK;# z4OMQt9~6LaN1#s_Fh>FQQNXigV2~Fm&;xWcfNA!dm*#+B)G7nz8bF6QFw6s>Er4EOK&%?bF$4;AfiW&% zoCBC(2Ns!cigf^wAiz2NZqBnLz$Fx@R=#VuNdgNjK)EK+fB@F$fJ{?BsqoI$ED5Mo z1rqE4Uti#zCSVl@%tLSX$$)HQ;Jq4~ho`CY^a7vP8(5UvMwUftX}AK<&Iwsa{VUUgUh z2@c>vB_LZ0XlDV+Z-B?IZf;?2Q{C)P0>ZVx9Q3a7hXhz;0*3DaKX~ub_6P=EnF2ok zcR7!90-gbPNmi@e3BV~F2=oSCMBV|pg>WmCTgLr-01Q8169d>q-)SS&0%(`GBd$p2 zE<~Lo5bOy|!S8arrBXcyc!~mEJ_FLt@08Ob4a7eO9#jGH#Xy)lfU>@0405w;9)s13%z-g3FI09k4gZC@SEGh21MSdMw@%zE`WMpeH{Z3F$F%s4K+W4gOWCo zM2#hp$Mq4nbl;~?VJ4luX8W7#1E`a&x!wY&zb88l zN1p;u{w(({h5e>J1%Y6K8p;U6z`4mh7wrra#_v{h<9beb_Uu_ssLuklU5mIC>bM*t zcnDp3!57GGcIWN~=GwhPk`|qj+4X29PCWJ%!mm8dv^)i!1KFLpM5tu4Zu)l4x1`+4 zuool8`RpVod-L>kH&(y$T!#xcuSSC206r^ApC6SB-*ez^5f|E=>CVr`YArBlJ*aH= zv9&F3YdaKfe*MZ~i5LpP{mi>QT}i><&#tzbf_0y?pPchedeE97X-j8))~zv`+-R^c zXW+`~MJv5ye2+%Kvb|-$zve#6Fq3j>e(Z$inlS;)z#xljVJ?019I=wkGZKCb>mY`{ zC8KIq#mXl@3vR}_b~1^fB_&=iJ$$n&3S*05tirE<8!l{ZZCzis{#|hXxvFTM`o}iR zq}ASGAx!t@bKd6MjeH3iBEIB}%Yan>#e?6>$^PrcHJlA)z3Hwi9`j3t3g5m_#CTR2 zJWL`g(S4CEDe2|vYOlPr-diIV^oy)GsZk29-%}jHck!wdB5f==uk13XY9^5>Drj(Jal(oLc`8 z7D=J9cm#rVYK(uzdmUKaOiVbY*(WAOkmLNmxVRVy4%oluYjcE3ejCZtD>w+KsRMI; z87X<1i$@!2V<|7#NM2W6ZEQ5}eehW7L)rSeirL`4wOgYhpON3G_`a-$K8jR7b1;WL z!~?6_@enfHT1uN@893H{iZNi1|9834^3*)D`mGffWf%2a)wvAK&u>(jHANwD#zmPX zb+%6usxsHvd2-@g4UX8e(%gfc=yCIMYT;LE&!w^Hm;hjMmbIls&Ko=!h)JjeBiK7j zJg8+Wwf=llHxatzP37JYP2MYV!uiXB_G}g(g)9DJKdnb9^WJMK(TfEmW8+Fb{$ocI zd_IJNIpT-3}l*&5tlx{G{r8LcDpgZGm&_LCN*L6GgC7m* z9Ckq~9^@3|Q4>gQQNDOZ2HyVd9pW<)EoE)RyU2C+kH**{1?C-Xq|NDRXjCBSZu!dW zkWoaF%%=W+tXxD?vrJiW1>QdCcU?IwSAAVKXSF08Ri&so^yCRhU8OJJ-7;U}%kOXh z;uh`2naxh}@bu)!tqCu)Xf)+K3I8Q5)ZcBvHOp43NX8R{ud%yohLd|fFsJPE=-WnM zy9)c2e5+K_=dT7ym;4dn(y2MkPN*C+c_fH{ZfAWh`@@h_TYs$2*8y!HC~F*!SDduQi((Yakpx9^)x zJx6lr@C*IFi6C32_c%iv6B`G$;M6Q37L;6Uui3x9r{WSzT7DwjzcfM?D1LbD#A$qd zu4>LOEHC&2!$+8)#A2;xEg(p|Jdy(=RRcM>kUoYG*Qz;+&oyU511wbY(v-jRqxsns z%#|?EAim6T>_zCj2<-iPVp$G)<1~m`qSX7m3`(@)$3!@_Il;X3RXlkq+00DbtS7gY z>#I3K0|TFc5y9cN<3t=oP_kgwm69oEvtnYz&nRnvO7Njr5W~StAj9NIY?n)l*H$Iz z2YURl{gS{bBrJXar($8yfT4n#w=5%{AEV-@Grkiy0_J z=#2;?ULHsk`lZB!Pq?X9XYS_xO{hASO}zKeLGkV?N1%z z>{sH;{En>vxtZU%K&AU5%Gwj}B-+Pkl=AbytOz{;6ed;B5v9!jh)%X6>$P18-6BW0 z9}ExHska~pvMsOcE?mMPyWj`s*pbbrIhx_ij%6Esl?wROHn#vuvN1k)+M*(8X8`A{ zS4o(sjn)kE#;i6MtveA?5(&g98!Mcr!J5=}Qa@!L1e14aJEQmcLzXKl5nn2bAJ$tZtP$P8Z1 zZ>}d+7jWYR^n<^KOqhO^N==PYrD)O9EEFNs=im5ICPNsHVP!Zx`PfqbpT-5=sn650 zHSeafmr~){Zr!JcC1%$s2t+!}=}Ip+y3BYtETmoWiR}1P><_9ZZ0FT%gEZStrgdbp zI0aw6jiD;PvU!g!GH_nZQDTX%5h%9pk4-fdm}0u~yzd;=Cni~sWY3r;O}XL;q&8-M z47pHP%S*mY4oBx}PU^}#j%4iThs7lM7N=#@Fdn{XdiIW0L(=C2~Fu=G5&Vq%Yr zY&qWfrH35E^L#BnOgMwRc8B)xMX+GlbUbtr`nPrR=jS(Tb=kQ6o7eVqUZd`0fo9D@ z`vyo59#*A!saE#!$G7Cxfh7n>ZWX!0gkro``0W{b9=XxNGqu#u6^e-7Kk1lUvA@1| zbiNNGZAv0UHvN5m{KO8QNS~$+u-B>s_5L`L`jBdrke`Xe{0N zj*umKyN|_A>O3@@8y|M457h5tzNEqIDV$3|c%QOiW7;H(RNLM9W0najA-;HEwdD>u zkW^fo^J0f?_u(^NWw(nT>JS~~smIbnC8QxFcuQkHNQUJyj;eYMTmvVN^O|RPDvczq z>kslY@C$0YBmZR=Y@IEsCWJ9}K@HOu`c4n=$kr9!;n+9We=f8unlK5@c*o|I7(=zG zS}lWvH_fp;i}4qt9>h_?QzN~ap@7nLh*7DJwfvLZAS*_S4BW3*FwwCv)lD1Vg1t7>@!DLp5Su@bc$d;D_VX! z57eDlJm5IkuTyc>aw+Wt#V5hkX-B+t+|!Fa@@x7=`8`3dZm3$aHF*y2VcD$(D>J3y z%YwpO#gY%mqyin8q9uH?7OBC}Uv-@)Hl0)Z2+y3x`{XluljPt{<4TU-m$XDvYU9HDGKSg&cT(@MVe23D z<wCdy-3tTl}j4kzaCmrni9Diizn#a{@0 zu;<>ZBofe$`#ML)Z*Aj8mbPS3-(VgSdF3LA8LJgBPj{3IaB~uLBuylKO*>ev*i|tuS(Wff3`Ogj^{GsE zb-&NcY_)d|4#vv+AW}skSWrN{p$KOp?oug)BPoM*O}*h=wrr-PGYCt{qwP-&I!~hn z&+*{EI5^09slk8i%kFcTjCcO6Oc}M9iiS}cuLLw0fyMPfjZ_M`;HWDHP^ke=f*5^! z(0!2p!N9nZ8J+AP5sfunmbw64l@2)3@53_`Q@g&4FYKeDLbz2F-Pl@Er#INAtM()n z<;MFT;osC}L2=Y4P1?cm)V{7nauSgh zx4)k@#lLT}(uM?{Z&NvzT8pnJk@-MDvv4wOKsY+l9S8OdOw}cex#$t*B6ppAEloQy z8u`9L`Ky$*$*G}A=)h6BjVn=_YuPie5epXe0vLKZP=Pxps%a&uGrdg4y#R{YobnDn zWlMl5J;9|5ev`f`BO3Uh(yuK%@i^`+fAimq+_>*)z(;wrFdXn2nVJw1y_>PcV%p-) zt#ZCU`}~DIE5y1BiI$qSd5XIebq(uRB-FnL#^x=bDHO-ls3({4R}-PgePOPH8ggGN z^EAdT|N11qATWCZQ}ZY#=hum&;_xeyp*|O{VN?%jM$?&+DK=8LzLP4?U!YQ_JT0`M z`m!W@TCB5mlr9&jJ{^cX4Ye=uf(7g!BDG1LQfZM#P5u-^$L1K~rOMk7oWI24W>ZJVoZ8!*NX>jC%2pgw@7$v*amGL8JnA`*TjN)y=JPix$ zHom^xjfr~ZFvhO?xbJHg&jfEZgboIqN@pk*%G-#&cX5DM>_>dMo{P<(#6bOBlgEyG zzi7pMrVsz<*TT+n%Xo*+rUVN ztJqe<9EEg`YU)i$PQ+gn%oA5sG>b*6QUjZVf+Ju4QKWr32^B^>#t1pQ*dNT#SxuC8 zGNJ9mH6R-92O+m#!DECXMrB|3+|cZ$?^qoag;w8-vr)Z5hx@2MVoazOwqdLo2-lu( ziwF(a3SNH{+U_=d6KFp}-N^eInrfTZ{p%Yc;K{NXO^Sl0qy(%)%sUXL zA6Ic}&$4}O+ulD-?|#%otX@aO$zD_RW;|WXeL`u5;iz`2Ja937zabkT*wwyB&2HIU z%>tpNcvDWCsG=IjRr(V|Z7N`9^tphbhiC}fIrVcS>=>)uMStm;g&z-HK{fFz2ud&X zKo;y87PhB3IZGZ&D%bJ2dZ<-Z7I$fCrPWl6O0ir})>f~+rM8k@!Q(EpWCD9f!k>me zRhs@Q3_eZ!{s3QHUMaN0uShhn$y!N+)a z6F#K#O21}#Wz2NGar1LUq3S|3=0~&VTjxVeqcysO1QIGwgglMcjXc3^9R9i556MW! zA%G1+Y)mPX16RcVB#B?R~Xl zIeR6G9EBEoU+Sk|=Q=4gQdT~j9ecG~G45m|E+IkhfuBxT`M%^wZkNkXpL0AjS!$%u zg-={lr6QW@$p<#-f}T{y|0rAEpY~$9`Ffd=BP+`1#;?ge=@mLGkdmI~u?Dai2i+}> zr-d}7M&xTHsK7@<3$tPd^Yg3fLzxDa!oOJ_N!hGt&$}5!9&TOIuzhIP>)^&CM1CIF zY>A-293)fzq2lbB(1wKk=>#3=G1eTT&8(iBSJab=V@AGnDSwOhxKg{m8sa~TR||^x zH?*-f-l|Zq;GkYEt~~O`56i(Z6gi`*^TxMZuc-f=NMlvry~%u2a>kz*@R^tJ@{7%-A@wfa)Z z0~dL|Z*tTjEX7ED(M%2gZ|Zi_L4TWr%=BR=y4%;?-COo)8t(xaW)#f?Uhc9^d-1?K z=UDW-cu8J#%~t1$>T7o{K5NXC7Z^ z;*V>fuSp>3%L~xxUi%mG*w7fJS9FTtSX8!B_=C>_+{-q^3-7Yf_esz?&oN2)zkaPH z=7a>>$VgQh6LDY?h_Px)L~(27=d%@0UX!M7G~G|aMRJuU!|xkIYM`l6jEi407=MtW z4YA)qqP8SmCj;2g*%y+BObhJICRimEtC(yhX|B>fl9#O|OsP0h{YfJM(l{DjCSf;_ zp3jZ#S5YfJ*b;u0&zd}UOl+UMYMCH3kOBnUGziK zo__#J<(BObj|aew5%LI%9K>!}5UVhW*2b`jvkYQXN*iuj#{{Oatl}s!dyhU&jWEAS zdKj4YrSTiJ_b6i{@5nk1r@W#B=YNdtri~y>StL>N%B6Gj6O_fwN*T-2>1Q1KuQAXE zF|^na>CtL$iCMfa_^Bio0%XY3)>F9Uqzf-cky+Lb_H)#4=L+?00yMptx;^o9v}fkd zWN%~1Sj%)#ggvPi=IpG67#q)qrgFi21qV^a=2vb1s|%}692RR2)zei^W-5JD7Y!4^ zKfyt1dP(!slOA11Of$_QuYE4W{Uu+DO027{)!7+#Y;Ih{M{`=>H7?QlKD!&7BO7X`Q1_b071zS3lNp z@&Iv~QHe@3e;q{$b7pE51fo%ee_qC4)CR?kSh?lvkF}D2UuE>>^dhlmktVcN14leO zB6JLU^U}S?*F=`1q~sTqli@HC^RzgoXWg7-*R1R~2xL-K^`XjV%3c-JMf~;^ILFoK z%NIwJAU)}8T@nI{XRp;rWaD7cuvC1dj!R2oZ(K-<6)PFEe1C`3{NVSlqPF<~-&+*O z8>_z7E5JY^sY(1!jgGp<)OE9!*nx9!RX3U}7tvu5m2XXS(V8#x+t;nz?=xkI(DDsz z;3foX=tMqziX%?kztoj_MYP1$@9}OUi0@Z>26`$9-KBzz0d+H_Z`PU9?gSA=7?H}0 z{c+|*bet;11)bfWS<)JER^z_5PKJN$@9unBRX+W*oX^32ly zKo%s>e!Q4Gb1DeiV*R&fzDz|t8*WBSo1ove3somHjybczT^qp^D^Tpx97n^9WuuqI zTCFUlzMc<9(@Ng!L9ebpnk>0!&~jV#PR~Lz8p-9KECK6 z;70`vR#D6@#_MoD+$Wl*AAbY|FVo2J6vOlP={WYAqT#$Q!S^VW9|! zl45qcN$prx36zxggrb_z0Ri=i%$I(SJ6jHx(uR<;b(=zb>GDa-cZTt=EWQ$^+AKKp z!qtgInkt}{k=PN-erHn(dHX?T{g44@;}a%oYTE7q*K<=mU`H~sCkX#6pyH?M+0W>N zUr<(Whv)VbXit9iJY4V&;_6xGNVK9d_P*yRzW_7PgbR$4WPdDP8K}{V;@u;E04A#lw1fxY9qPYX%78 z?2?m&UC)5IREFbfd%r<*8}KZbL-)2J(s!Ni>DER#ah4jLH0*2GPn zFP?pTI`d2+=5;~B0pYU=P03Hk<$aGh?7&|CDV>N4L&S*{xjJ6{dn!jPj@;!U64ZH$ zxcv;Jen{NFvhysj-qs<-RN>Q}{r7$Q(|cMV#FVvG1ygSsC^0)`=DwB(4Z7$pIxu3h zGr}-!!|aUp$DUaVeC9=coIL@I)g|Fm7a7u6JRy|q_3SIEsEkSTn?R2}SIm-}g0D#x zbFUgC%_08XTvJ@!3#F5}f?b~Rz6CpeASaHdWnNs?a*HD&&M#AO;^>?RDV?gRN(Q&_ zi^f)H(H|$Fg#yiJBfM*7wTkz_6un^+@bW^qdO0rV z#80Zot!buo$fS$UtrjI&1`2u(g>;~*Z@I;ZrS@=@)pCyAT-4)ZtWEg^;2QgIBuYCv zCOT@SpdJ=?l}vNgolDevUl8e7VBri&69qGAFHzdn>vz;Z2PIH&X0IdJxq5&G|lD{8;FN) zyoId^NoH`da-U0H$$EV_2u9QwI2NQ6xr#xsr4!7>PZTS-+^Ser28(?H>pkyr(t|p)Hl*Rcc?7hPzry4)mHP zWLX9>Jcr0gs2&(aNg9b2@BIl*r~2X;kn=eI%P577nIX=auf8fXGcC+->CfU?wHoJM z@{%ht0!KC%-tamvUWKvNx!08PvLF(w-EK-u?Lgd+WfX$LOYI=5t00MKD|Q)#@9mL5 zWZQ?eQkgCCqwANoAm&K4|fw*k7ipV zB1v2pR!seE-oV-2@pAb2ne;%k;&0+LB7z16@)VH1MO9)MHU9kSp)dCNVB91j?4mmO zv3NP2%#IeX=+W7Ni#8IjoTpc(!3T*dt7!EX8VlAzQq6uvFcs%W{$71OuAvW8Wpseg+*PVYLv?WMN=C|H@$;E_S5fVp$L;GyupU7jZ$kfvC58X?uLqEZijH!v HqBZh=dic2S literal 16900 zcmaicV|1Ng^k&Q(Hn!T>_Kj`Zc4OO48rwD-Gw*(Xv_UIGaL4*?7e3`t5-R2lSh^xqd84Cs4}W^FDQn9zijsF13M{zVR~<`0dB z;hww3Rk_uLO*yyZ^N(arMN#SjFcHEi60E_fZug`IjtJ^LVtno=lKj+Jze{_WszRIN1X*HUTCH>C_wc;+D)6YYT z*RWmTUi`Puu_Uwkj6-qwu_Ue*kO&$%=o%J?6*rej_Ock3znkGIb6 zWm&yS2Z9LS7slFgUx+?ilDgQBdj7`ruw|IVzJ@wV{&tD)G@SPTMW@9Wl5lcsuU~6` z7raw|%Or|@Pnlh`7!!rA1H$`p;zz}+92Tp2bFmKDAL`nrC>)<{qBHso zvJ6|o^vMxL?frh4XZ`3WdH7s_NI0p@{EElbnX*!yp;Vtx&K&w$&to`sW z79>enm;xWhu;ZKKIN}-h!eBKZM6j$9~*Q(SlE*i_bHS0o#tPY z5-j+ww|x>h9%`RLUixM!e%f0qVAe5GH83X6?!#^_j-M@lO@*-aD%NMF2;Hg^Wgh@}elrPA3o_&(- zeNyws4es~%;K1o+pfG(Z!G-nFWzl7)ejRNxY?M~uI=I&MYuz@4>GLH*ptjlQJ`LYr z*KIIVzBhKHIDwe`X2hc@gsdjzXxX%b<_#kc$vIHFi2)-XM1=fs(`g?0)M{lcJXwp< zBgIdDXM&n-=+_%;1a?sE$oeN{r%w=8tFfAlQopAk z%wrVN=r>)oZ0w7^M~Xi~qp6lEaABgF(ck7V3Un;@cg|ODuD7@fw~OZ;^TQV z$&4AiUj}-4;o`6JV$Y4C2G z8hVweUdzl78hWzD|&J_)oRr2JdJP zA&lca);^P(q@hQb9-kqNXVo9An7Q3NoAtyRQw-@JUDD$oluryjE z3{zzbZhStP-K;xw@Yxf-B=4h(p=4f`k8p2DH$>qQLPR!szD!2|vJ}J`C6=EoRwG^+ z;`ZDv1SGVO+?IqSxpxSM^_V~@2E+~dZQdl+oz;TP1MX+XXwugMy?Z5AoZ7#R33Y@T zM)w4;9L0szO3>6i#4fV3q49@wu&`zcvQ!d8!m*dpn&7pp0Y=;QbiyOzhC7)Ki7tDt zXaIqysWqx53ZgHlO)|YRDG**$7&F{0a8VEECY`3;yx)F>2;4Xr&gC;Iqiqx;orWkF z8xk0Ty-mK&z`^~Fbs#S;;Qd@1ZFJh4R`+H>Wx$xgn>^oka;w9~QfR>rS7lYHG?D#o z6Jo`Qg_-DP zX@kdURs~L5?afF*73QF!=HQ?vIysP;FNCMBfA*}*&%$eDHh5L|y~D=C^v8(wdtcYZ z)8Q|56BuZ~3~KpF-oKg|5Uf@Ac15Z>sP<9hpm(E>^cgr8dMxGhn7mnWA+JPK+EGR; zCfK+V1&Xi1M6CUFIA+oJqr(aF3W_=ph7h;IVlqq&xJ=d(CqczQwL>f*A$gJW_|iZw z&>!^cGyI)UH(_%jFMta0ci8K;?^D#C4_`@%@wP6R4qvs8y@ecdj|*ia7Exg3*BpG4 z%Dqav(-_hWolzv04-3Ygs)Z~U$`R?hQq2Is2`RWS%z4?!GF2CryzMjCEFg_Y%K+yz zG8tm;0X{;XG5?BBT|pMZ296(fGUtoF_$Ryrso&s;Cc!g3a;pYOn-tjPvW+1)iAQ)I zaPyG(wl0MZUqz_Z!4+oEh$t>QIaiZ+J1|fQdfugliOCAg+6D!~3<-k#gA8N#Rk3@5 z&u3Yevetsi3m`sm2Ntt>FV(PfME~wR=LFu+2@Noy&wr###hgP3mjy&H03re#97OQ% zsZ;NtktNoC?s@G44Num-@G1zw*?jMf)dA`SWJHyI-Lp=m zyv8V97L8$~?>Sf(&Ee27TQvEf=-_%~EL56_n`*ZRVS`=4Ka4&HGjr9P8e3rf;8BK& z&0s~H!Z|V-mPt9vUj?5&%Sa@;XK~`TS$ylgW4|1h&I!<9c6_zoDdR2)FLErHw%Sow zwc_2ZKizcAMchMvZ^6OY8)uiUt&RwA(`3@dzgihQ1MSrNi;ruq-C+?oVa@U0x z(>^4ei3Bedg+!LX52G(u@W4P&3sdv45%OawU(*aQat~OuEf?Hi6Zi>__qCd)nw0_j zvUwA_6WQ5tnFsl_AZNz8L8L*=L4?0A>inj9l&C`AC71u=H z?bu{Q_=al@1+|F&El|te2eQB@?#+g(D(LjFx>w=0X;CJ|CQc@tuin_)Rd$KH$Y9P9 z${MAq+Ns2`>_SLAfKm9~%?U2bK6>hiDEbdUD#NMd$hR*wFx8TxWVY3Za zM&tRPhR$htT-*KlZT-SGBy4YD;6aZfAz^Jt1`=ABifztn#D_;u)2WTa-Bo^EKL;=o zDc6Ov2x3ybU1B6gkFjv-UvyFl^(EFkIb4ht2Z(*io4 zW(6^Rp7OMxVh73mYH?bkbxgXB=+TL>U^8OY>=P$oXPkGAmF?6#80T z+e?24uzuJC8?nCu`7)ef&Nu8x+`0%wOB9wmZ^(+|&$!T80~3uj?NRH)aNhf~#vN9e zem1VW#bKd$SZ4ufS0-pzoJ%P7UWdT@8yg`1+kpYLV153t;UJy~P8@7sO+#{ePIXcSgw}v2XayA<>Jxh}D)tMOGRgJY0QEJs` z{>aB;ssVeqKi-6L#(PnBpPuOu<4Rf*GWVk8BdMCd} zc^_!LU3n2YWBEk1?0<%f@MkB;t#h0%&cixNCZn@Lft$eDVl6z=l@Ga}k<7cF5n!!o zXet^Q3;AyG!j)+$=3U>7D5cEf)=YMZ)jSZ?)!6EoSa3kU!3W2Xn`K`PqR|ML`Ju!A)|K2`l1>ErJG>o*qIC72B&jHYe36od@P! zi)qQ9Y7g*>N;Y4;sSLlPxvM;q-Tzw2m;Zx=x>{mk0;Ed5zA?Hb1FrDGc6-;m+iSFU zc22aC&R^-iyw5vE$D?GWWo7A5o@@>d3_uD92sGM_-tlsdQ?ZbAnF4LsSxDj&0TFgO zFbB*@;0<;Y0es>tB&~M12_up)gRS(Ce{seFR$9$~MC8~S%gCTV+2AIiH`gndEW2~H z`z|RK5KuxIccy|!;Bkm8puw0EcWFE{ij71G*o4( z0~y!3%z_nq1kdh3x<;XVQS{_v?Q3|H1so1Z#CL|Zm2Z&7-mTO?&1?U-oogOAE4Cm{ z`d4o(XCnWH-J^hx&?7X^xHns&B`u2*skUy`s~w=0252bVaZy(}U?e5?u>fG!UbYaS z4Gz$YBX|~|U$??YUR+zxw2g5F_OJB7viI^}qx|ouEswnc0o{D4T~~|912EVr9)4P& zS=*@uBmgy>GC)sz_8A$Iga2y-R#LKP$zyVe7P=4Vrn@Q)Fp6mG;Nall=^07<{OPT~ zPDD~5M}Py>^H&ikOMCrXaXjFMyNuyNg$gXaPOE4z3=$o3Jt(guFuvAQbA?*MR;Dx}r~+zsgJ zzCtQ*$r?UAKNl$E39K|(pdcV17*;zU{VtG7{)QDicnC&XAit07AxkJs2xbNxkEh-l ztI=-hZ#0{5e0{huHk5pMKFXUdk-_HT=8j~#**>ze%L-Vq--ELbc7OqlEqqgfDL$7| z^zia3^m~7il#>&4bK{s6W!C%o9eQ_nw_LRXoq&)qk2e`~Carh!_+@C+^?4E@nB?8v zrP(B~aF_-3_5wx4#3EgX2f|T2iDX6dBot9e+}zxz-+7y;fop?^#LWumnJ%(ER<|F> z44(0)x_-m7iZI17bV#w5<;|{V>IZ-R+z|XI2d!L0M$z{_~PzI|b} z_>I9TkwT-USfkDEyuoB7YJe7^SUeW*JCd>d31w)Viag>w zE)Hcnu_U(A@CEh^w;UM0IVsDf+yNUB)lCpiM=a>2dMSVx95URpuHBLGh>h8fgM&77%eeba~6*@>lA8=;7iEw2QP4d^IvP z8fpiWc?lq5kxp*C)nS|HY^i2ov(x?A!{1u(mk%xyJ_nmAsx{Zt=LV=Ta0-O}2|y4O z5yIAhMw5|xp3lvw|Ps$0W*KZd^Wlj=W@{AaG=^es3_){Y~Jis`IYYiWN~ho|DLil1qRD5 zN6xAlvXG=U-8`VKVHr!k-;5Bi)EfnJRTtvY$;jR$#e%~lxMV?xboY;JA{IT_^y}D0 zw1mJ8tVoSO-(}absB6M8b$Zqe)Ok0$OkaA#I z48@e8TAlv;PmB6dbP|{7<%qt@Ea>I;PRL4)=M`_G!A40Y$Xy1Mum)I0#!3<77H4)u zI6c{)TUsy&o^*@2H9Bp>QJA#S8$`zN?+@z^IIQL|VxYEQfVw~Oc}Wq!FS`G2T=aDu z-DMYe(1$x=331oN(i#yV%?Q)lcY`}FpGRp*74@@$fX%pE+dAGOh5QRhJ&mcaXOhk4 zLi_pirw^Zws;d9n^#IE8T1ypZDX|crNABquU?iL2;Ql%4Vg5cNBt}OJdbLKnEi|`g2q%v70%eM&7 z5gdFefu8Ix3n54MC zW40SGT11ajrrm5AI24T?-2$|VMsU%VX}AMmt>Pr~B}#An{>%QG>_1FQYV^)CExzx2 z&7E_9c!fpiCLci|F3H*eM2DQQRtQp4>V2RP=KX3ZVw#OXuFxj$VDmM&HQD{*dc7301976VQyI69%EFvxxn>qC&Lo-`%ImvM zCv>AXKPcD26Z_;m`1pw)uF6Mp=RnShU^yM81!?jbl!v#-kSa#RLhSOG0?yp1YB6Jr zW=GrO|0zIRSHiH?DYiO+$EpdMkwz#4I6V(J12-W0+dAo4J*?nDQrFI<*}a92Y%1bU z`RC_4tyg7>R(8{ zA8*g?PWv##WoF+p0bJe>whg#+(1_+A+)9HS$|n?k;(r=Le*vR;57rn)2& zEkD8KBSZm#3Drt?t!*#s#>0+yUNysIKRg=t`KSOcSHieiUP0z8F_$tZ(ciPnq_o~@ z%-{zhbs{i7 zt~8q8%WO|MF(FE_ye*bl_-@NcA!S9$IMb6x0`e_oNF!hy5a)H^H)5)t(}ek4a1Nc~FF4@f;5aO%aB&3O%B8NuMWWCzYb`d> zQ-&3)G|5M|pzcLy>pA(p=?3&XKn+v0^`HNsS?M0eb+60BxF|&Y{?>MI^x``)Vp}1V z;<0N$BUc(0=p=y>zD3k_I~ zMC>T|rn!T!wN%lqT@ z&Afsj|04$m&CH2M?F|6yeqb+e`&JWTP^~~z(;c>5;z6RuFKe)%3j|YzeZB9c)5E08 zvX9?L9%?PT7Vu(RAIXR}s*=I*@Qp<*vA{&7B2uwdBH$_I`33U5di9weG|3 zx-Iy`1L`R>G-q<+w-{f5qc<7ls}^cT4Y^Qi+meHXFIDgqkt0wpdBZGY?LB+q9&o`T zd18L5%R+44Ml^UNbEw58BXP#{+I#J1$;VGO`#6Grd<=RWgP+T+ktE6H^>C;%(}szj zK;wt^oW!yG4Fz=zm4zKw@$Wdo`VJm=879kp$F&$uMP_qiKSB4L@SV)g55F9Rb=3ocrK>iqIRR9n!X0Do*Ldi{9M&^sg&T_TZz~>`tbXc$p%%BI% z#MahUA?U0t#2ZA4_41*w&52#TXU^_G4)$#uGOnpIb{Gs?Bge_xP|beH;cUSBec^gk zu;a`And#3j5LZ)LALL9lQ0{$A?tzx&K6M(;#M))7n&`7KTkT>KvjI7O4?mTa;X`81yn7WAir6 z^Dv#2{~#3{X=5gyP*2v`3yoLJl)--n2rC2}*3n8(L~4ohHzT6QbyEu{!K3q#&p9Lp z?3#RrZR0JWoh5V%Au%m2?uSB&RO!i99khjDd#7P;NaxJ<_f>mYXQOtXqBZifoWn1d5WC&hmG;&Gv(>!l)|)selJ-m-pz9Og@*rA z%Xl~n+gHI_Rjy513U_dEaq-~ZLm%H7RpVbREoW=Zu*D?n%JFyy6(v}{RCOy z>_wu--o5bv-4rRuWG0oN3a2+(f)C6nR0%>9HdI1mB`d{jE6Q4vSf>>{@~N-bGMc6~ zn=1MB2?XIjZuOC!s@-pN5{60UUw-L4f1L-3Ohud?4)I$4Y&#w^A*ij(1$$3|Vskv} z#YKCOBnHKh5QN8fd|k)wI{^HZj_1!`{L&>R(m@P^tYk*J)5>eCrio9{j>kWLDCGrM z*O<)utCbjQiH>aHzD!~>SNyzV|B?uyizaR*!v`(g6N5ks=aSqWHk#wzbQOx2Ehc(>s zfl`oSK+EzLOKDeK?n#pu;5qF1g-8bXyN##%K`x2R14CxOh8w&P-kz4U}>3Q=A& zwAa>sCXe?|fR^Y+S9_jW;=!_GK`1Bc2HY6Y)*s}A##+#}239~LV&Q~wL&4n_6^@vW z;nGUYJ$5-C#kJr2EtD&Ty$t-H)#GyT->}39LWB1gdo%LwqR8{YbRBL*-FCEc5iY{; z#TpZ~y8yolNKuWi&enqz%<*)Y)j#ff)9q1ezkI|N7|zr3b=T|b>+m?)d% zKJ;1@L~w8ZQn0MxZS*{ew-;Ohn^Jl!+U{m|QvgB~tai**t#d>0E=CMjN*SZ+36QnO z4NrSN!Cd>9SLf?=!Hjh+ek}c}ND_U`vvi9(MS>7nGZ*lPm%4(7(bhfuTHod8y%;N{YO_KMV}N<7D)x5snD;XG zzCOH#WK2$4mAvQWFCCZW#F8TRInJ+=$6eR`V~dES6+!6-=6lkVCHyCW^Bb-$@=b%3 zi%hxQwAp^EOp|zR61~UikJsM89qE@P3@X5J>+K)hO6K`Z$80UqhLV&|mVt3wQ#G4H zi4>T}s*jr9pkN+B@=LbuMW8^kzEFQde*yOdnXiUws9u#OD8dYzm?0F`qCm7pBCNNz zOJB@PR!5?2&9Zw_Jg~i=TwmStKiYq1_@$ zZKB*^u}y2o({7rV#Nl+8$2T5 zthMF3X`+*;4Q-~&-*4NzrU=7>#}h=jB}<^tsAch7Ac~Vq;V7 ziknpCHOP}_P8F&VE%6e`WG~EVa?$ra`knKZrYWbIZ_w@4vO+{B!(Pb&!YhY8pCfe= zjxF8x>Zh3;#gw`fu})grVJcf=Ohg_Xc9m?(57$!NXQ#N%;Q{V}EjtmA$m<@Ie2(h2j9T2Xq=0<2R#daW&$ z85=lCIqjn+?h$SF4u|?#DOOKg9>2c{9GSdlh{<(WR;Mb+bxH>u95roevUiqSmcdG* zEL`{Qv+mA#hjLxuC*l?ROBgDsPYkDNU%;m09$2^ni=SVA=kS_) z_h->URCbhQr89T-a-Gg9Dk?P`CT8-=f%@A28AYMmma&Ks#DNDsr^|eI%nHBQ0Nps* z<{@u^G-9krSD|^{Vm?_nRkW_T!;E*n95To#4sxn;9FH2W%&T043S^Vg_Bk^^&J9*H z=-^Zd6GYUG(CMkA?hy<&4Tc5fn4$3ys+ZiGw!07qHH1zPDzAJY;{8Oj#B1-LTAZ>D zKqX)c%j0#o|H%z2zdkxYKaV6<&nEMgP`q%2&v+2dsa++rFeWoOnf$VkCAY6|8|kw{ zdwe(maC?oeGlx#HVClH?)W&QZ`+=l3PIeQ%9cb~nWxJ9)YD|MPt`v?0-3bMcbZ<2Z zG7xSnH{QoOr#C@?R{C$168|JMfCxcPAVuEhewgQpYO@AfbP3Fw+|Vi7h~L@$6ydj5 zyf7_h9Rp$0Gii0mkT9xddqw>hIVCXV203~$D~swIj_)TV=zX)@-tK6Hb66mM;EywH zsMV;{!i^8fvae3b)iz7_f6$4yU2i-b%Bh|o@eU2$RD^G(AtWlyl0^8dxd<9 zCi_xU0%&wFugtmc%-uOk=xMY?lR%{7BQRZ~b8}1<=DQI)v2*#3|70VNVV*?SK4O}0 z-HEICfCoyTwy@{F=Ac>4KISQEgQLDcj|>j}hzn(*RSn zZw&u6!^Z2~7ae&u`+{IHYm_vxJJ@RRZ!LoCjQ2ecK6E;AqeyJZxfuAC zaFBgBIQO4DawgA~vN)BCS%`;S38kn@9kWOTMq)$V$+z&4nDQvH*{(1#N58$C)v2#; zJW|ch#FaXRBNNj6mX)HNV{_ScADWB7#Jn(Th}B15lvrI|-2fj-=SL1AY zQrI&y#`tyxRIyenc$G7)m}|d;5&h;8q8?ap1~7v{vEXIAhojO|^XI$6=K!f+>;5yx zJJXiq*Z?mW;Ak{?4<=)9$$a@6Q*=1_%}Nx&bGA3oqS%{I)k3y{#DALAzrPw)h(FU zj}8a8Xte($dBpT z_ZLeg50aO#zhmy?M*+dS#c4NyP>CZSyS+OOi>@2;)lr;&A$)(OEO;kV+bz6O57by zyW>9>Ij2^Du|A83(r~$46%S7?Ancv(6R zJK?TL+k$9p$KMJgY}hdrTzyS}0it==hvU?8YM**7M}l@-W{&s26~NM6 z#U8(RCX-=6Lw%{$D&=aKSfE%aJ<__RASP1DaZcJPva<-yi3NH#t$OuNk6wlp&CD~1 zanJ|7AhF;l{a^)Qhr_9Bo;2ZG8=}0whx#r7zZ6W`Fs5 zJEbvhZVJVsORu$w4Y1HyT1E4?Vka&kS*mSpBuKM>OAT~3W;g7KLGzfQWF~QJ1)H6S zFCOXwP_auqzKSygLBPB}EH;Q1gXb@Wm*lZWfM<8NWGZM_*$8Ze)0+^IpqCyco5T+P z>!edzc-RMsx%H6~4%a*u{&6!V2Xf)f8oOKEEtBAhvI#TkSv+Ago-TMSQ(2q}=S0FP zL(1v}1vp6Ya1@zfO!}Dq3ke|~@mmFXu2dHEQWpO$6X$;c8V@V*w>NACSkmSKF-THX zXc85Wu2(uhx0b@}vaeA-YhO(oJ!8ZlugSxzOn{tnI7h@dCB`UVE~EEY_ww_|qDlb| zQh0>qvDy{uar91x0J$!N&ch{3*B*?y730`NAZJT0IXU?T1Oo1Zc+QnB&!+ZYLh%_v zV;)6DQs1sEzvoxu0r{lou-yG%CgwotYzFK>vqr!e>KRehvaz@y)fTge`_wgV2*|2H zVl|vbxEx$3ymn~uGqN65%FYqJ<_)*Uqs49;KY2h*(Xa?Tk7AFfl-xf>irJoUyL*;0 z19&1GQV*5Ni~#kTnaq0ymCiLjk_=0q&=&|cG{r57n*6NwV6zJl5K*ED&DsZy8iEL_rr zgsLXr6cN9-S7dCo0TeKI3ByoGNNBIG{4b4m4=LB^FstU0B?!6TBZ1v~zn%e*Xk=B) z@_rySE6iHcIxSfbe^sRAkjZKFfR!7A5uNa|Q%HSV{);)`X_I$=Rz#g9)RV zjIuDE+A6IDHt@Noy^%sCnU|?kL3tCMU12QN7688MFeYr;%^{CT)BqX<4rY8gFNo(^2<+x6~@> z0Y;8%xJK3sk3si!JoTyNPRqf>i>%mkw_b{g-~}-aAljQww_S1L53kdn=uMDZM5$#ndk z&22o*u=b&^trc3UMGkzzrL*~$;t?gd{w8WCC+z$)6{fY`v4CL%;?|JZtR3}&oLz8* zT?G#HsX)xAYvWho@h=pJpzsjcWp0%LD4s08onG)Nb4)MY=8K^XfVvcKVvP||0{idF zr>Wx=dX&);ID@-|u5Y#BAa0c8rW_t)Xfo4c@By|jKCCPsr7DjJ6t;eTIrmF;CpM`~(ysWB=S@seY-cC;IYp7eGp3%$l} z)oc?3jDrN<0qs>+yfj#>o^%eHp8`K^wUK{qUM_Xl#K;;VHK+>&$DqLQV1~BoxLuBrt&0}DAhEKn_^ER` zz-29QNvC|8F%an87xNYKcn*LCu89T8nVkc&?~&O83)5GbY)slt*#=)i7s;A_C=2r7N7+fk`X1KngTDCyUEafq@X5m_z1=DeiD@Q38P{+Ou8AdwgrjC5 zajlbj!7Ae^jZ~9GGnmvF%|dV*Siz7~1$lG}zFHP5%BV8TD09lQN!w79WRZ;`=PM(z z0;YT`0PcRb5SM~SQ_OKjwTc~?W_G_IPe||U$;Um2U%fe+7X>%Nvy!xcXUbbT1miw0 z=$X7_W&m0ay!h~`ae>C68mu@al*ia7R0saqO=sn$tE@ww372nWLhU^>%{WE>Eoln8 zaeH(5Zly+xlW1Z@B{Z2HqS52V*oh`BC}k&quf19RS}N6$l#0qGWzl9DQkZ@85(#UMH4E) z!&hPrOmR$HRF*}2C{e3A#U3h9d)gN68^|>O9=TO4Ga~u#5kl0}_*QP9IxEl~Ce;Vj zS3zvyQ+p-TKYiV8z>J$akDBH=i$W7}&)8|aN%_17$7$H|;eKWRKgAtrMwoyE;#kJp z>iJ{R+d4p$2q2;Y5EBQ7>@E&mk*MzVW>!EDsQ9Pd1Icl|=0d^U2HU!hP6MLe0bwp2 zA=U!|OQM?{{^8dU?o^&w|I~Y5fw~zw)IT&*mzBRUy1Ljo^-=Z`fvN|N_JgxG~k*Hc%03VftQZkoi*AD{-11-bt2%}_=-R;7ZY`jOzsFyAEWb! zVJNLPL#@4|8iv-c@m4Lu!^Uc7?VOsDWty>@T6^QN67|~9P?w&boWVpR2)d)gI@s*$ zT0uPct)H#x^_Y(_q2El&g2<(pF8niAzCde(;c)XAp3awn@Z)3{qMO$l1?#O_cXL+a zB+yS96Q;w{xIBw9%-h2xp$%a(D0`Noi$$31BbukCM_lu$4sG_+rWsH9U`eD0eY3t3 z@`vkyB5OW$_NhyNPE(&_JPvYO1XVd%SiaJPVza|ZguGogD*p`OzJ!Odk4wR7o=G7; zQFEN*_9WQcO`Vliy5G@VCnZ;Qb~fJ44e1$o^Tw=L_lA;Z-8Dw0CC}X_m5Q_J*xP61 z2tVQGAnU9PA@k;{9QL{c=-~c_joC`W*8qxTI)7}foE-)SU;g6SD;S1P5oGCta0DrC zGXz?khB$Fn{Ycwuk%t&RTyJ!Mz8mnC0U+AYu}PkaA-t-gE*25%;RVKNKyWz!scpu6 zZDKFBX5S4#lCQK!Ip%UxMsP%cC4T!8d`;mo#M{(B)h;Ilk3UVA`-O^+JuQDuUnt-K z=jEH2NuzvVs7mGT0rJ;Nz54;;pVk-{O`o<8h5~yAG9cx)%sJ+#d0-B8j!9{+{>1@9 zYiz-m^g@6wE8^*umZD0JhIN!|&Ok-?2XhJ@B|oI&FfS^$rs90JhlZBoJW`e5b9j^- zWO>uD9oB-o4QKEBn$akVeT1MeUX-s%#m~lPXZR!_h7SU~%Y_rx{QlrO`$o+{oUb!PIS+x5N z+{O+YLa6?IE1#&A?RMZ&J}!O!vj>Os^y>J_BMi^Cu8;>FP)!5eagStg`4k8`f<9)s zLv>uniXJHc5tD}2a*xO+UycHT8lGykAS#tq7H&?$Q|yXO#aH{77;M;}%#Rn*u_i#Q#=kFoCjB zxM)O)sW@_wx=K{lJ|iyESH0iv9Nr111eP3eEA!SenTb%U12{RS*7qj0=;%^Kd#QiJ ziYTEU=jFY{zWsSqmqmw<7L@5T1o7NxWhht`9gu$(b|QZnjVAE)D;lyC=>~hv=8piE3T9#-QVKCSaq-q&xr*zuRbfKtru+;Kkp5Si5+<6{tz}rp zigZWmiiYYR#xdxCbhhJz=wN$k9zPcR8H;AJErv2><3*Bm51h&CEJlpT9yo5`1`w{pnaAJ%0k=ISmg0E zo$J6^H1-w0!^WV5w|yx36dtal`WN}DGpD-gqYjDTfjIaLtR}xxCDSo6v=}KHRM^9@ z&T;nw5x5ee(K3%Z3QQF%sMId_cIRpr&3g$f><9ZoX7X_c7g4f{y)mf(?;`TLI@jLv z?N)ryzDJ)LsBZU+VnRH0X1E}KJ!}%#n_-hEY9w z`8(=7Fd9^wGY;{_ggJK@ZR?yW!1!^^d;F^x%}=DG(7K8XMm$L~K*Np|t>vZmA5%Y| zINrWxnZFq_J7&ksTGEluekfNRCX$8u^xk+?w8Q1iII^7LA8Wc=uh=>E34C14fN(+~ zjb&LKSzG|ur8^cG=n*d|U)DK;5`-D7c>o{;1qb8{cYdL5^ll*Y29ag^ZWs(}{Dq?& z7Vt6fu%BVSoqvD;RYW!I!KS^e-kCz_2@FvAByt<`2mpvxlE{aWp)% z7->KZs4&!M+Z9|_;(QrbPRGNC2zLU&;bq*v@zaDlNR7 zR!OB(0w7?XvMI3w1tc_A&fY$=RO&K>9q)K{?KeL9#X2nl`k!ouFF)XFC@Tui*%L4~ zwNvTu3}=K5TH;uDS!^k3d+!l_hx$f?(hkYU(6NBYx@mz*Y6dZ7D@JF^5^p{aiT5zv z;Xjc--#|sw407DGZz<4^FBXBq5F)zwTQ|65$~FTfyft2wOiY&QG(ydKoz#wa?YKny z)9C@EX0c#XN}}K5dNFdMNo^+Os>0sS^c;E5Ky4zm)q;>J{J+z3sdUj)7tN@@gZSf7 zJ|wiD$oI`e{Xe-gDV9P_(x}i7AaPVJn&m~NMi(84-RGbXy6@{lY?h66ze7!6Ee=i! zInre-6PCHrI9+8v4+)Zge*esLVEy0*)t)o|)801Zf98hgQ=EZH2bpZ=)5NN_2yjw# zP8Ewr(5WN{8DJpt*e!|G(gvZ5Pxywag$Agdns%%4+IH>|FMw9b zKb<-v)*Cb*Ao~hb;B*`Ee&trZYBi`{$ru%gmKbuXcPNb3lD3H3Jimki7;BEFp{bxX zFJ7Rk<~$d5(AGs1%w=$DDrj&3=?C4wX`U{m8^^=Z8R3YTB_A>ZAOkmldWl zwo0ZyTNCB`dfUZA+chm*()HWtA2!JQ3>g${8%Vr% zasf==&095e)fG}M%iIsk{PaQ>2|D59ppz^2pExvb9Ou9EI^`kN!0aXr*u3p0ex0b4 z=AnHH#@v>`#o*LjN-yB0^^l)H2Nm=yD3|>1aNigv$f`s680kxF8B%d>SUG)YF0R~W z$TI5rvll2~&q4RSwu3})*@1!~z4l}@NsY#MwV(2Y=hbLZh-ce*Eq3<#rZ zxra}au9h@`-JaCDeW|)St?N40z`g~4rjZ?xu=?#W;cJyHNPXCV2DuxD%N1A2hAlFH zwTJm(6XPn#dA&{dq>&yd{5Lp=pa<%$*em=~TdQ%rn_v#5`>I!IS>M^uNpl#N|wC@HMBcRTMT#SL;d7 z<(&BuA6dLkkx|8fWw@PXzCeCBgDx@HJs@)L+j8y~gZ)7)${p-|O7{G? z&|M6FI|A*^d_U+Of-3`+w(c~-YsQby|NH)g|G7xv|Nek^|Jex)g~z+)I0xPC0460S LFIp>X81%mY^Bg|U diff --git a/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java index b2d510459bcf90a3611f3d91dae4ccc3d29b4079..7a052f6deaa30f8a177a2aaf172f9da6c308a22b 100644 --- a/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java +++ b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java @@ -37,7 +37,7 @@ public class VanillaMobGoalTest { } List> classes; - try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft").scan()) { + try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft", "org.purpurmc.purpur.entity.ai").scan()) { // Purpur classes = scanResult.getSubclasses(net.minecraft.world.entity.ai.goal.Goal.class.getName()).loadClasses(); } diff --git a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java index 4f43882d930ab8816e75b216d9a61a06b79df265..40fc8b6579cc29e68720a99ac12f8adacc1d95be 100644 --- a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java +++ b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java @@ -45,6 +45,7 @@ public class MinecraftCommandPermissionsTest extends AbstractTestingBase { Set foundPerms = new HashSet<>(); for (CommandNode child : root.getChildren()) { final String vanillaPerm = VanillaCommandWrapper.getPermission(child); + if (TO_SKIP.contains(vanillaPerm)) continue; // Purpur if (!perms.contains(vanillaPerm)) { missing.add("Missing permission for " + child.getName() + " (" + vanillaPerm + ") command"); } else { @@ -57,6 +58,25 @@ public class MinecraftCommandPermissionsTest extends AbstractTestingBase { } private static final List TO_SKIP = List.of( + // Purpur start + "minecraft.command.compass", + "minecraft.command.credits", + "minecraft.command.demo", + "minecraft.command.ping", + "minecraft.command.ram", + "minecraft.command.rambar", + "minecraft.command.tpsbar", + "minecraft.command.uptime", + "minecraft.command.debug", + "minecraft.command.gamemode.adventure", + "minecraft.command.gamemode.adventure.other", + "minecraft.command.gamemode.creative", + "minecraft.command.gamemode.creative.other", + "minecraft.command.gamemode.spectator", + "minecraft.command.gamemode.spectator.other", + "minecraft.command.gamemode.survival", + "minecraft.command.gamemode.survival.other", + // Purpur end "minecraft.command.selector" ); diff --git a/src/test/java/org/bukkit/potion/PotionTest.java b/src/test/java/org/bukkit/potion/PotionTest.java index 83226ec2fa977819e12a499eb3765232543c17b3..a742774dabaee0629f4e6adabee5f3ec4b3be41c 100644 --- a/src/test/java/org/bukkit/potion/PotionTest.java +++ b/src/test/java/org/bukkit/potion/PotionTest.java @@ -9,6 +9,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.item.alchemy.Potion; +import org.bukkit.NamespacedKey; import org.bukkit.support.AbstractTestingBase; import org.junit.Test; @@ -47,4 +48,27 @@ public class PotionTest extends AbstractTestingBase { assertEquals("Same type not returned by name " + key, bukkit, byName); } } + + // Purpur start + @Test + public void testNamespacedKey() { + NamespacedKey key = new NamespacedKey("testnamespace", "testkey"); + PotionEffect namedSpacedEffect = new PotionEffect(PotionEffectType.DOLPHINS_GRACE, 20, 0, true, true, true, key); + assertNotNull(namedSpacedEffect.getKey()); + assertTrue(namedSpacedEffect.hasKey()); + assertFalse(namedSpacedEffect.withKey(null).hasKey()); + + PotionEffect effect = new PotionEffect(PotionEffectType.DOLPHINS_GRACE, 20, 0, true, true, true); + assertNull(effect.getKey()); + assertFalse(effect.hasKey()); + assertTrue(namedSpacedEffect.withKey(key).hasKey()); + + Map s1 = namedSpacedEffect.serialize(); + Map s2 = effect.serialize(); + assertTrue(s1.containsKey("namespacedKey")); + assertFalse(s2.containsKey("namespacedKey")); + assertNotNull(new PotionEffect(s1).getKey()); + assertNull(new PotionEffect(s2).getKey()); + } + // Purpur end }