From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: granny Date: Fri, 13 Dec 2024 07:03:31 +0900 Subject: [PATCH] Purpur Server Changes PurpurMC Copyright (C) 2024 PurpurMC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/build.gradle.kts b/build.gradle.kts index faf3e3fd72e8c915e7a4803dacbe1bb576c6663e..77602e4445b1b45a17e5552ac6a769e0ed46ebc7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,7 @@ abstract class MockitoAgentProvider : CommandLineArgumentProvider { // Paper end - configure mockito agent that is needed in newer java versions dependencies { - implementation(project(":paper-api")) + implementation(project(":purpur-api")) // Pufferfish // Paper // Purpur // Paper start implementation("org.jline:jline-terminal-ffm:3.27.1") // use ffm on java 22+ implementation("org.jline:jline-terminal-jni:3.27.1") // fall back to jni on java 21 @@ -60,6 +60,17 @@ dependencies { runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") + // Pufferfish start + implementation("org.yaml:snakeyaml:1.32") + implementation ("com.github.carleslc.Simple-YAML:Simple-Yaml:1.8.4") { // Purpur + exclude(group="org.yaml", module="snakeyaml") + } + // Pufferfish end + + implementation("org.mozilla:rhino-runtime:1.7.14") // Purpur + implementation("org.mozilla:rhino-engine:1.7.14") // Purpur + implementation("dev.omega24:upnp4j:1.0") // Purpur + testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test testImplementation("org.junit.jupiter:junit-jupiter:5.10.2") testImplementation("org.junit.platform:junit-platform-suite-engine:1.10.0") @@ -86,6 +97,14 @@ paperweight { craftBukkitPackageVersion.set("v1_21_R2") // also needs to be updated in MappingEnvironment } + +// Pufferfish Start +tasks.withType { + val compilerArgs = options.compilerArgs + compilerArgs.add("--add-modules=jdk.incubator.vector") +} +// Pufferfish End + tasks.jar { archiveClassifier.set("dev") @@ -99,14 +118,14 @@ tasks.jar { val gitBranch = git("rev-parse", "--abbrev-ref", "HEAD").getText().trim() // Paper attributes( "Main-Class" to "org.bukkit.craftbukkit.Main", - "Implementation-Title" to "Paper", + "Implementation-Title" to "Purpur", // Pufferfish // Purpur "Implementation-Version" to implementationVersion, "Implementation-Vendor" to date, // Paper - "Specification-Title" to "Paper", + "Specification-Title" to "Purpur", // Pufferfish // Purpur "Specification-Version" to project.version, - "Specification-Vendor" to "Paper Team", - "Brand-Id" to "papermc:paper", - "Brand-Name" to "Paper", + "Specification-Vendor" to "Purpur Team", // Pufferfish // Purpur + "Brand-Id" to "purpurmc:purpur", // Pufferfish // Purpur + "Brand-Name" to "Purpur", // Pufferfish // Purpur "Build-Number" to (build ?: ""), "Build-Time" to Instant.now().toString(), "Git-Branch" to gitBranch, // Paper @@ -172,7 +191,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/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java index c21e00812f1aaa1279834a0562d360d6b89e146c..877d2095a066854939f260ca4b0b8c7b5abb620f 100644 --- a/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java +++ b/src/main/java/ca/spottedleaf/moonrise/common/list/IteratorSafeOrderedReferenceSet.java @@ -18,7 +18,7 @@ public final class IteratorSafeOrderedReferenceSet { private final double maxFragFactor; - private int iteratorCount; + private final java.util.concurrent.atomic.AtomicInteger iteratorCount = new java.util.concurrent.atomic.AtomicInteger(); // Pufferfish - async mob spawning public IteratorSafeOrderedReferenceSet() { this(16, 0.75f, 16, 0.2); @@ -79,7 +79,7 @@ public final class IteratorSafeOrderedReferenceSet { } public int createRawIterator() { - ++this.iteratorCount; + this.iteratorCount.incrementAndGet(); // Pufferfish - async mob spawning if (this.indexMap.isEmpty()) { return -1; } else { @@ -100,7 +100,7 @@ public final class IteratorSafeOrderedReferenceSet { } public void finishRawIterator() { - if (--this.iteratorCount == 0) { + if (this.iteratorCount.decrementAndGet() == 0) { // Pufferfish - async mob spawning if (this.getFragFactor() >= this.maxFragFactor) { this.defrag(); } @@ -117,7 +117,7 @@ public final class IteratorSafeOrderedReferenceSet { throw new IllegalStateException(); } this.listElements[index] = null; - if (this.iteratorCount == 0 && this.getFragFactor() >= this.maxFragFactor) { + if (this.iteratorCount.get() == 0 && this.getFragFactor() >= this.maxFragFactor) { // Pufferfish - async mob spawning this.defrag(); } //this.check(); @@ -219,7 +219,7 @@ public final class IteratorSafeOrderedReferenceSet { } public IteratorSafeOrderedReferenceSet.Iterator iterator(final int flags) { - ++this.iteratorCount; + this.iteratorCount.incrementAndGet(); // Pufferfish - async mob spawning return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); } diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java index 4b002e8b75d117b726b0de274a76d3596fce015b..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("Paper", serverUUID, logFailedRequests, Bukkit.getLogger()); + 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-Paper-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash); - } else { - paperVersion = "unknown"; - } - metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> paperVersion)); + metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ? "bungee" : "offline"))); // Purpur + 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 532306cacd52579cdf37e4aca25887b1ed3ba6a1..6d36fc6d8e22d9b68dea3830f6ecc8763184c343 100644 --- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java @@ -35,7 +35,10 @@ public class PaperVersionFetcher implements VersionFetcher { private static final Logger LOGGER = LogUtils.getClassLogger(); private static final int DISTANCE_ERROR = -1; private static final int DISTANCE_UNKNOWN = -2; - private static final String DOWNLOAD_PAGE = "https://papermc.io/downloads/paper"; + // Purpur start + private static final String DOWNLOAD_PAGE = "https://purpurmc.org/downloads"; + private static int distance = DISTANCE_UNKNOWN; public int distance() { return distance; } + // Purpur end @Override public long getCacheTime() { @@ -49,7 +52,7 @@ public class PaperVersionFetcher implements VersionFetcher { if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) { updateMessage = text("You are running a development version without access to version information", color(0xFF5300)); } else { - updateMessage = getUpdateStatusMessage("PaperMC/Paper", build); + updateMessage = getUpdateStatusMessage("PurpurMC/Purpur", build); // Purpur } final @Nullable Component history = this.getHistory(); @@ -57,7 +60,7 @@ public class PaperVersionFetcher implements VersionFetcher { } private static Component getUpdateStatusMessage(final String repo, final ServerBuildInfo build) { - int distance = DISTANCE_ERROR; + //int distance = DISTANCE_ERROR; // Purpur - use field final OptionalInt buildNumber = build.buildNumber(); if (buildNumber.isPresent()) { @@ -71,10 +74,10 @@ public class PaperVersionFetcher implements VersionFetcher { } return switch (distance) { - case DISTANCE_ERROR -> text("Error obtaining version information", NamedTextColor.YELLOW); - case 0 -> text("You are running the latest version", NamedTextColor.GREEN); - case DISTANCE_UNKNOWN -> text("Unknown version", NamedTextColor.YELLOW); - default -> text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW) + case DISTANCE_ERROR -> text("* Error obtaining version information", NamedTextColor.RED); // Purpur + case 0 -> text("* You are running the latest version", NamedTextColor.GREEN); // Purpur + case DISTANCE_UNKNOWN -> text("* Unknown version", NamedTextColor.YELLOW); // Purpur + default -> text("* You are " + distance + " version(s) behind", NamedTextColor.YELLOW) // Purpur .append(Component.newline()) .append(text("Download the new version at: ") .append(text(DOWNLOAD_PAGE, NamedTextColor.GOLD) @@ -86,18 +89,15 @@ public class PaperVersionFetcher implements VersionFetcher { private static int fetchDistanceFromSiteApi(final ServerBuildInfo build, final int jenkinsBuild) { try { try (final BufferedReader reader = Resources.asCharSource( - URI.create("https://api.papermc.io/v2/projects/paper/versions/" + build.minecraftVersionId()).toURL(), + URI.create("https://api.purpurmc.org/v2/purpur/" + build.minecraftVersionId()).toURL(), // Purpur Charsets.UTF_8 ).openBufferedStream()) { final JsonObject json = new Gson().fromJson(reader, JsonObject.class); - final JsonArray builds = json.getAsJsonArray("builds"); - final int latest = StreamSupport.stream(builds.spliterator(), false) - .mapToInt(JsonElement::getAsInt) - .max() - .orElseThrow(); + //final JsonArray builds = json.getAsJsonArray("builds"); // Purpur + final int latest = json.getAsJsonObject("builds").getAsJsonPrimitive("latest").getAsInt(); // Purpur return latest - jenkinsBuild; } catch (final JsonSyntaxException ex) { - LOGGER.error("Error parsing json from Paper's downloads API", ex); + LOGGER.error("Error parsing json from Purpur's downloads API", ex); // Purpur return DISTANCE_ERROR; } } catch (final IOException e) { @@ -141,6 +141,6 @@ public class PaperVersionFetcher implements VersionFetcher { return null; } - return text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); + return text("Previous: " + oldVersion, NamedTextColor.GRAY); // Purpur } } diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java index 6ee39b534b8d992655bc0cef3c299d12cbae0034..90b3526479320064378f2cde6c2f2b8e48a59ba6 100644 --- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java +++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java @@ -20,7 +20,7 @@ public final class PaperConsole extends SimpleTerminalConsole { @Override protected LineReader buildReader(LineReaderBuilder builder) { builder - .appName("Paper") + .appName("Purpur") // Purpur .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 3470720466fc81f977c18e3a97bb918926025a22..c8651af322927c46d075f88890fcd0476bd85440 100644 --- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java @@ -136,6 +136,10 @@ public class MobGoalHelper { static { // TODO these kinda should be checked on each release, in case obfuscation changes deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); + // Purpur start + deobfuscationMap.put("zombie_1", "zombie_attack_villager"); + deobfuscationMap.put("drowned_1", "drowned_attack_villager"); + // Purpur end ignored.add("goal_selector_1"); ignored.add("goal_selector_2"); diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java index 12b327eea95e0de9e9c39b7d039badee8ec46508..849ecc5af70901f1e40cb6c419f33f1cf6f3b6be 100644 --- a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java +++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java @@ -61,6 +61,7 @@ public class RAMDetails extends JList { // Follows CraftServer#getTPS double[] tps = new double[] { + server.tps5s.getAverage(), // Purpur server.tps1.getAverage(), server.tps5.getAverage(), server.tps15.getAverage() @@ -73,7 +74,7 @@ public class RAMDetails extends JList { vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)"); vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb"); vector.add("Avg tick: " + DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / (double) TimeUtil.NANOSECONDS_PER_MILLISECOND) + " ms"); - vector.add("TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg)); + vector.add("TPS from last 5s, 1m, 5m, 15m: " + String.join(", ", tpsAvg)); // Purpur setListData(vector); } diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishCommand.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..020368da69b9a492155f6de6297f74732f4ab6ea --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishCommand.java @@ -0,0 +1,68 @@ +package gg.pufferfish.pufferfish; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.md_5.bungee.api.ChatColor; +import net.minecraft.server.MinecraftServer; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +public class PufferfishCommand extends Command { + + public PufferfishCommand() { + super("pufferfish"); + this.description = "Pufferfish related commands"; + this.usageMessage = "/pufferfish [reload | version]"; + this.setPermission("bukkit.command.pufferfish"); + } + + public static void init() { + MinecraftServer.getServer().server.getCommandMap().register("pufferfish", "Pufferfish", new PufferfishCommand()); + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { + if (args.length == 1) { + return Stream.of("reload", "version") + .filter(arg -> arg.startsWith(args[0].toLowerCase())) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if (!testPermission(sender)) return true; + String prefix = ChatColor.of("#12fff6") + "" + ChatColor.BOLD + "Pufferfish ยป " + ChatColor.of("#e8f9f9"); + + if (args.length != 1) { + sender.sendMessage(prefix + "Usage: " + usageMessage); + args = new String[]{"version"}; + } + + if (args[0].equalsIgnoreCase("reload")) { + MinecraftServer console = MinecraftServer.getServer(); + try { + PufferfishConfig.load(); + } catch (IOException e) { + sender.sendMessage(Component.text("Failed to reload.", NamedTextColor.RED)); + e.printStackTrace(); + return true; + } + console.server.reloadCount++; + + Command.broadcastCommandMessage(sender, prefix + "Pufferfish configuration has been reloaded."); + } else if (args[0].equalsIgnoreCase("version")) { + Command.broadcastCommandMessage(sender, prefix + "This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")"); + } + + return true; + } +} diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..411c81a88b8a008b2aaf0ca62fb7ffa1dd9e32a4 --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java @@ -0,0 +1,281 @@ +package gg.pufferfish.pufferfish; + +import gg.pufferfish.pufferfish.simd.SIMDDetection; +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.server.MinecraftServer; +import org.apache.logging.log4j.Level; +import org.bukkit.configuration.ConfigurationSection; +import net.minecraft.world.entity.EntityType; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.List; +import net.minecraft.server.MinecraftServer; +import org.apache.logging.log4j.Level; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.MemoryConfiguration; +import org.jetbrains.annotations.Nullable; +import org.simpleyaml.configuration.comments.CommentType; +import org.simpleyaml.configuration.file.YamlFile; +import org.simpleyaml.exceptions.InvalidConfigurationException; + +public class PufferfishConfig { + + private static final YamlFile config = new YamlFile(); + private static int updates = 0; + public static File pufferfishFile; // Purpur + + private static ConfigurationSection convertToBukkit(org.simpleyaml.configuration.ConfigurationSection section) { + ConfigurationSection newSection = new MemoryConfiguration(); + for (String key : section.getKeys(false)) { + if (section.isConfigurationSection(key)) { + newSection.set(key, convertToBukkit(section.getConfigurationSection(key))); + } else { + newSection.set(key, section.get(key)); + } + } + return newSection; + } + + public static ConfigurationSection getConfigCopy() { + return convertToBukkit(config); + } + + public static int getUpdates() { + return updates; + } + + public static void load() throws IOException { + File configFile = pufferfishFile; // Purpur + + if (configFile.exists()) { + try { + config.load(configFile); + } catch (InvalidConfigurationException e) { + throw new IOException(e); + } + } + + getString("info.version", "1.0"); + setComment("info", + "Pufferfish Configuration", + "Check out Pufferfish Host for maximum performance server hosting: https://pufferfish.host", + "Join our Discord for support: https://discord.gg/reZw4vQV9H", + "Download new builds at https://ci.pufferfish.host/job/Pufferfish"); + + for (Method method : PufferfishConfig.class.getDeclaredMethods()) { + if (Modifier.isStatic(method.getModifiers()) && Modifier.isPrivate(method.getModifiers()) && method.getParameterCount() == 0 && + method.getReturnType() == Void.TYPE && !method.getName().startsWith("lambda")) { + method.setAccessible(true); + try { + method.invoke(null); + } catch (Throwable t) { + MinecraftServer.LOGGER.warn("Failed to load configuration option from " + method.getName(), t); + } + } + } + + updates++; + + config.save(configFile); + + // Attempt to detect vectorization + try { + SIMDDetection.isEnabled = SIMDDetection.canEnable(PufferfishLogger.LOGGER); + SIMDDetection.versionLimited = SIMDDetection.getJavaVersion() < 17 || SIMDDetection.getJavaVersion() > 21; + } catch (NoClassDefFoundError | Exception ignored) { + ignored.printStackTrace(); + } + + if (SIMDDetection.isEnabled) { + PufferfishLogger.LOGGER.info("SIMD operations detected as functional. Will replace some operations with faster versions."); + } else if (SIMDDetection.versionLimited) { + PufferfishLogger.LOGGER.warning("Will not enable SIMD! These optimizations are only safely supported on Java 17-21."); + } else { + PufferfishLogger.LOGGER.warning("SIMD operations are available for your server, but are not configured!"); + PufferfishLogger.LOGGER.warning("To enable additional optimizations, add \"--add-modules=jdk.incubator.vector\" to your startup flags, BEFORE the \"-jar\"."); + PufferfishLogger.LOGGER.warning("If you have already added this flag, then SIMD operations are not supported on your JVM or CPU."); + PufferfishLogger.LOGGER.warning("Debug: Java: " + System.getProperty("java.version") + ", test run: " + SIMDDetection.testRun); + } + } + + private static void setComment(String key, String... comment) { + if (config.contains(key)) { + config.setComment(key, String.join("\n", comment), CommentType.BLOCK); + } + } + + private static void ensureDefault(String key, Object defaultValue, String... comment) { + if (!config.contains(key)) { + config.set(key, defaultValue); + config.setComment(key, String.join("\n", comment), CommentType.BLOCK); + } + } + + private static boolean getBoolean(String key, boolean defaultValue, String... comment) { + return getBoolean(key, null, defaultValue, comment); + } + + private static boolean getBoolean(String key, @Nullable String oldKey, boolean defaultValue, String... comment) { + ensureDefault(key, defaultValue, comment); + return config.getBoolean(key, defaultValue); + } + + private static int getInt(String key, int defaultValue, String... comment) { + return getInt(key, null, defaultValue, comment); + } + + private static int getInt(String key, @Nullable String oldKey, int defaultValue, String... comment) { + ensureDefault(key, defaultValue, comment); + return config.getInt(key, defaultValue); + } + + private static double getDouble(String key, double defaultValue, String... comment) { + return getDouble(key, null, defaultValue, comment); + } + + private static double getDouble(String key, @Nullable String oldKey, double defaultValue, String... comment) { + ensureDefault(key, defaultValue, comment); + return config.getDouble(key, defaultValue); + } + + private static String getString(String key, String defaultValue, String... comment) { + return getOldString(key, null, defaultValue, comment); + } + + private static String getOldString(String key, @Nullable String oldKey, String defaultValue, String... comment) { + ensureDefault(key, defaultValue, comment); + return config.getString(key, defaultValue); + } + + private static List getStringList(String key, List defaultValue, String... comment) { + return getStringList(key, null, defaultValue, comment); + } + + private static List getStringList(String key, @Nullable String oldKey, List defaultValue, String... comment) { + ensureDefault(key, defaultValue, comment); + return config.getStringList(key); + } + + public static String sentryDsn; + private static void sentry() { + String sentryEnvironment = System.getenv("SENTRY_DSN"); + String sentryConfig = getString("sentry-dsn", "", "Sentry DSN for improved error logging, leave blank to disable", "Obtain from https://sentry.io/"); + + sentryDsn = sentryEnvironment == null ? sentryConfig : sentryEnvironment; + if (sentryDsn != null && !sentryDsn.isBlank()) { + gg.pufferfish.pufferfish.sentry.SentryManager.init(); + } + } + + public static boolean enableBooks; + private static void books() { + enableBooks = getBoolean("enable-books", true, + "Whether or not books should be writeable.", + "Servers that anticipate being a target for duping may want to consider", + "disabling this option.", + "This can be overridden per-player with the permission pufferfish.usebooks"); + } + + public static boolean tpsCatchup; + private static void tpsCatchup() { + tpsCatchup = getBoolean("tps-catchup", true, + "If this setting is true, the server will run faster after a lag spike in", + "an attempt to maintain 20 TPS. This option (defaults to true per", + "spigot/paper) can cause mobs to move fast after a lag spike."); + } + + public static boolean enableSuffocationOptimization; + private static void suffocationOptimization() { + enableSuffocationOptimization = getBoolean("enable-suffocation-optimization", true, + "Optimizes the suffocation check by selectively skipping", + "the check in a way that still appears vanilla. This should", + "be left enabled on most servers, but is provided as a", + "configuration option if the vanilla deviation is undesirable."); + } + + public static boolean enableAsyncMobSpawning; + public static boolean asyncMobSpawningInitialized; + private static void asyncMobSpawning() { + boolean temp = getBoolean("enable-async-mob-spawning", true, + "Whether or not asynchronous mob spawning should be enabled.", + "On servers with many entities, this can improve performance by up to 15%. You must have", + "paper's per-player-mob-spawns setting set to true for this to work.", + "One quick note - this does not actually spawn mobs async (that would be very unsafe).", + "This just offloads some expensive calculations that are required for mob spawning."); + + // This prevents us from changing the value during a reload. + if (!asyncMobSpawningInitialized) { + asyncMobSpawningInitialized = true; + enableAsyncMobSpawning = temp; + } + } + + public static int maxProjectileLoadsPerTick; + public static int maxProjectileLoadsPerProjectile; + private static void projectileLoading() { + maxProjectileLoadsPerTick = getInt("projectile.max-loads-per-tick", 10, "Controls how many chunks are allowed", "to be sync loaded by projectiles in a tick."); + maxProjectileLoadsPerProjectile = getInt("projectile.max-loads-per-projectile", 10, "Controls how many chunks a projectile", "can load in its lifetime before it gets", "automatically removed."); + + setComment("projectile", "Optimizes projectile settings"); + } + + + public static boolean dearEnabled; + public static int startDistance; + public static int startDistanceSquared; + public static int maximumActivationPrio; + public static int activationDistanceMod; + + private static void dynamicActivationOfBrains() throws IOException { + dearEnabled = getBoolean("dab.enabled", "activation-range.enabled", false); // Purpur + startDistance = getInt("dab.start-distance", "activation-range.start-distance", 12, + "This value determines how far away an entity has to be", + "from the player to start being effected by DEAR."); + startDistanceSquared = startDistance * startDistance; + maximumActivationPrio = getInt("dab.max-tick-freq", "activation-range.max-tick-freq", 20, + "This value defines how often in ticks, the furthest entity", + "will get their pathfinders and behaviors ticked. 20 = 1s"); + activationDistanceMod = getInt("dab.activation-dist-mod", "activation-range.activation-dist-mod", 8, + "This value defines how much distance modifies an entity's", + "tick frequency. freq = (distanceToPlayer^2) / (2^value)", + "If you want further away entities to tick less often, use 7.", + "If you want further away entities to tick more often, try 9."); + + for (EntityType entityType : BuiltInRegistries.ENTITY_TYPE) { + entityType.dabEnabled = true; // reset all, before setting the ones to true + } + getStringList("dab.blacklisted-entities", "activation-range.blacklisted-entities", Collections.emptyList(), "A list of entities to ignore for activation") + .forEach(name -> EntityType.byString(name).ifPresentOrElse(entityType -> { + entityType.dabEnabled = false; + }, () -> MinecraftServer.LOGGER.warn("Unknown entity \"" + name + "\""))); + + setComment("dab", "Optimizes entity brains when", "they're far away from the player"); + } + + public static boolean throttleInactiveGoalSelectorTick; + private static void inactiveGoalSelectorThrottle() { + throttleInactiveGoalSelectorTick = getBoolean("inactive-goal-selector-throttle", "inactive-goal-selector-disable", false, // Purpur + "Throttles the AI goal selector in entity inactive ticks.", + "This can improve performance by a few percent, but has minor gameplay implications."); + } + + public static boolean allowEndCrystalRespawn; + private static void allowEndCrystalRespawn() { + allowEndCrystalRespawn = getBoolean("allow-end-crystal-respawn", true, + "Allows end crystals to respawn the ender dragon.", + "On servers that expect end crystal fights in the end dimension, disabling this", + "will prevent the server from performing an expensive search to attempt respawning", + "the ender dragon whenever a player places an end crystal."); + } + + + public static boolean disableMethodProfiler; + private static void miscSettings() { + disableMethodProfiler = getBoolean("misc.disable-method-profiler", true); + setComment("misc", "Settings for things that don't belong elsewhere"); + } + +} diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishLogger.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishLogger.java new file mode 100644 index 0000000000000000000000000000000000000000..53f2df00c6809618a9ee3d2ea72e85e8052fbcf1 --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishLogger.java @@ -0,0 +1,16 @@ +package gg.pufferfish.pufferfish; + +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.Bukkit; + +public class PufferfishLogger extends Logger { + public static final PufferfishLogger LOGGER = new PufferfishLogger(); + + private PufferfishLogger() { + super("Pufferfish", null); + + setParent(Bukkit.getLogger()); + setLevel(Level.ALL); + } +} diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishVersionFetcher.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishVersionFetcher.java new file mode 100644 index 0000000000000000000000000000000000000000..06323dcc745aed16123980fc559d7b65c42f1e1c --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishVersionFetcher.java @@ -0,0 +1,132 @@ +package gg.pufferfish.pufferfish; + +import com.destroystokyo.paper.VersionHistoryManager; +import com.destroystokyo.paper.util.VersionFetcher; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import io.papermc.paper.ServerBuildInfo; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.JoinConfiguration; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.GREEN; +import static net.kyori.adventure.text.format.NamedTextColor.RED; + +public class PufferfishVersionFetcher implements VersionFetcher { + + private static final Logger LOGGER = Logger.getLogger("PufferfishVersionFetcher"); + private static final HttpClient client = HttpClient.newHttpClient(); + + private static final URI JENKINS_URI = URI.create("https://ci.pufferfish.host/job/Pufferfish-1.21/lastSuccessfulBuild/buildNumber"); + private static final String GITHUB_FORMAT = "https://api.github.com/repos/pufferfish-gg/Pufferfish/compare/ver/1.21...%s"; + + private static final HttpResponse.BodyHandler JSON_OBJECT_BODY_HANDLER = responseInfo -> HttpResponse.BodySubscribers + .mapping( + HttpResponse.BodySubscribers.ofString(StandardCharsets.UTF_8), + string -> new Gson().fromJson(string, JsonObject.class) + ); + + @Override + public long getCacheTime() { + return TimeUnit.MINUTES.toMillis(30); + } + + @Override + public @NotNull Component getVersionMessage(final @NotNull String serverVersion) { + @NotNull Component component; + + if (ServerBuildInfo.buildInfo().buildNumber().isPresent()) { + component = this.fetchJenkinsVersion(ServerBuildInfo.buildInfo().buildNumber().getAsInt()); + } else if (ServerBuildInfo.buildInfo().gitCommit().isPresent()) { + component = this.fetchGithubVersion(ServerBuildInfo.buildInfo().gitCommit().get()); + } else { + component = text("Unknown server version.", RED); + } + + final @Nullable Component history = this.getHistory(); + return history != null ? Component + .join(JoinConfiguration.noSeparators(), component, Component.newline(), this.getHistory()) : component; + } + + private @NotNull Component fetchJenkinsVersion(final int versionNumber) { + final HttpRequest request = HttpRequest.newBuilder(JENKINS_URI).build(); + try { + final HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + return text("Received invalid status code (" + response.statusCode() + ") from server.", RED); + } + + int latestVersionNumber; + try { + latestVersionNumber = Integer.parseInt(response.body()); + } catch (NumberFormatException e) { + LOGGER.log(Level.WARNING, "Received invalid response from Jenkins \"" + response.body() + "\"."); + return text("Received invalid response from server.", RED); + } + + final int versionDiff = latestVersionNumber - versionNumber; + return this.getResponseMessage(versionDiff); + } catch (IOException | InterruptedException e) { + LOGGER.log(Level.WARNING, "Failed to look up version from Jenkins", e); + return text("Failed to retrieve version from server.", RED); + } + } + + // Based off code contributed by Techcable in Paper/GH-65 + private @NotNull Component fetchGithubVersion(final @NotNull String hash) { + final URI uri = URI.create(String.format(GITHUB_FORMAT, hash)); + final HttpRequest request = HttpRequest.newBuilder(uri).build(); + try { + final HttpResponse response = client.send(request, JSON_OBJECT_BODY_HANDLER); + if (response.statusCode() != 200) { + return text("Received invalid status code (" + response.statusCode() + ") from server.", RED); + } + + final JsonObject obj = response.body(); + final int versionDiff = obj.get("behind_by").getAsInt(); + + return this.getResponseMessage(versionDiff); + } catch (IOException | InterruptedException e) { + LOGGER.log(Level.WARNING, "Failed to look up version from GitHub", e); + return text("Failed to retrieve version from server.", RED); + } + } + + private @NotNull Component getResponseMessage(final int versionDiff) { + return switch (Math.max(-1, Math.min(1, versionDiff))) { + case -1 -> text("You are running an unsupported version of Pufferfish.", RED); + case 0 -> text("You are on the latest version!", GREEN); + default -> text("You are running " + versionDiff + " version" + (versionDiff == 1 ? "" : "s") + " behind. " + + "Please update your server when possible to maintain stability and security, and to receive the latest optimizations.", + RED); + }; + } + + private @Nullable Component getHistory() { + final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData(); + if (data == null) { + return null; + } + + final String oldVersion = data.getOldVersion(); + if (oldVersion == null) { + return null; + } + + return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); + } +} \ No newline at end of file diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java new file mode 100644 index 0000000000000000000000000000000000000000..731ef11c7a025ae95ed8a757b530d834733d0621 --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/sentry/PufferfishSentryAppender.java @@ -0,0 +1,135 @@ +package gg.pufferfish.pufferfish.sentry; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import io.sentry.Breadcrumb; +import io.sentry.Sentry; +import io.sentry.SentryEvent; +import io.sentry.SentryLevel; +import io.sentry.protocol.Message; +import io.sentry.protocol.User; +import java.util.Map; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.filter.AbstractFilter; + +public class PufferfishSentryAppender extends AbstractAppender { + + private static final org.apache.logging.log4j.Logger logger = LogManager.getLogger(PufferfishSentryAppender.class); + private static final Gson GSON = new Gson(); + + public PufferfishSentryAppender() { + super("PufferfishSentryAdapter", new SentryFilter(), null); + } + + @Override + public void append(LogEvent logEvent) { + if (logEvent.getThrown() != null && logEvent.getLevel().isMoreSpecificThan(Level.WARN)) { + try { + logException(logEvent); + } catch (Exception e) { + logger.warn("Failed to log event with sentry", e); + } + } else { + try { + logBreadcrumb(logEvent); + } catch (Exception e) { + logger.warn("Failed to log event with sentry", e); + } + } + } + + private void logException(LogEvent e) { + SentryEvent event = new SentryEvent(e.getThrown()); + + Message sentryMessage = new Message(); + sentryMessage.setMessage(e.getMessage().getFormattedMessage()); + + event.setThrowable(e.getThrown()); + event.setLevel(getLevel(e.getLevel())); + event.setLogger(e.getLoggerName()); + event.setTransaction(e.getLoggerName()); + event.setExtra("thread_name", e.getThreadName()); + + boolean hasContext = e.getContextData() != null; + + if (hasContext && e.getContextData().containsKey("pufferfishsentry_playerid")) { + User user = new User(); + user.setId(e.getContextData().getValue("pufferfishsentry_playerid")); + user.setUsername(e.getContextData().getValue("pufferfishsentry_playername")); + event.setUser(user); + } + + if (hasContext && e.getContextData().containsKey("pufferfishsentry_pluginname")) { + event.setExtra("plugin.name", e.getContextData().getValue("pufferfishsentry_pluginname")); + event.setExtra("plugin.version", e.getContextData().getValue("pufferfishsentry_pluginversion")); + event.setTransaction(e.getContextData().getValue("pufferfishsentry_pluginname")); + } + + if (hasContext && e.getContextData().containsKey("pufferfishsentry_eventdata")) { + Map eventFields = GSON.fromJson((String) e.getContextData().getValue("pufferfishsentry_eventdata"), new TypeToken>() {}.getType()); + if (eventFields != null) { + event.setExtra("event", eventFields); + } + } + + Sentry.captureEvent(event); + } + + private void logBreadcrumb(LogEvent e) { + Breadcrumb breadcrumb = new Breadcrumb(); + + breadcrumb.setLevel(getLevel(e.getLevel())); + breadcrumb.setCategory(e.getLoggerName()); + breadcrumb.setType(e.getLoggerName()); + breadcrumb.setMessage(e.getMessage().getFormattedMessage()); + + Sentry.addBreadcrumb(breadcrumb); + } + + private SentryLevel getLevel(Level level) { + switch (level.getStandardLevel()) { + case TRACE: + case DEBUG: + return SentryLevel.DEBUG; + case WARN: + return SentryLevel.WARNING; + case ERROR: + return SentryLevel.ERROR; + case FATAL: + return SentryLevel.FATAL; + case INFO: + default: + return SentryLevel.INFO; + } + } + + private static class SentryFilter extends AbstractFilter { + + @Override + public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, String msg, + Object... params) { + return this.filter(logger.getName()); + } + + @Override + public Result filter(Logger logger, org.apache.logging.log4j.Level level, Marker marker, Object msg, Throwable t) { + return this.filter(logger.getName()); + } + + @Override + public Result filter(LogEvent event) { + return this.filter(event == null ? null : event.getLoggerName()); + } + + private Result filter(String loggerName) { + return loggerName != null && loggerName.startsWith("gg.castaway.pufferfish.sentry") ? Result.DENY + : Result.NEUTRAL; + } + + } +} diff --git a/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java new file mode 100644 index 0000000000000000000000000000000000000000..1b29210ad0bbb4ada150f23357f0c80d331c996d --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/sentry/SentryManager.java @@ -0,0 +1,40 @@ +package gg.pufferfish.pufferfish.sentry; + +import gg.pufferfish.pufferfish.PufferfishConfig; +import io.sentry.Sentry; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class SentryManager { + + private static final Logger logger = LogManager.getLogger(SentryManager.class); + + private SentryManager() { + + } + + private static boolean initialized = false; + + public static synchronized void init() { + if (initialized) { + return; + } + try { + initialized = true; + + Sentry.init(options -> { + options.setDsn(PufferfishConfig.sentryDsn); + options.setMaxBreadcrumbs(100); + }); + + PufferfishSentryAppender appender = new PufferfishSentryAppender(); + appender.start(); + ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()).addAppender(appender); + logger.info("Sentry logging started!"); + } catch (Exception e) { + logger.warn("Failed to initialize sentry!", e); + initialized = false; + } + } + +} diff --git a/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java b/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..8e5323d5d9af25c8a85c4b34a6be76cfc54384cf --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/util/AsyncExecutor.java @@ -0,0 +1,73 @@ +package gg.pufferfish.pufferfish.util; + +import com.google.common.collect.Queues; +import gg.pufferfish.pufferfish.PufferfishLogger; +import java.util.Queue; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; + +public class AsyncExecutor implements Runnable { + + private final Queue jobs = Queues.newArrayDeque(); + private final Lock mutex = new ReentrantLock(); + private final Condition cond = mutex.newCondition(); + private final Thread thread; + private volatile boolean killswitch = false; + + public AsyncExecutor(String threadName) { + this.thread = new Thread(this, threadName); + } + + public void start() { + thread.start(); + } + + public void kill() { + killswitch = true; + cond.signalAll(); + } + + public void submit(Runnable runnable) { + mutex.lock(); + try { + jobs.offer(runnable); + cond.signalAll(); + } finally { + mutex.unlock(); + } + } + + @Override + public void run() { + while (!killswitch) { + try { + Runnable runnable = takeRunnable(); + if (runnable != null) { + runnable.run(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Exception e) { + PufferfishLogger.LOGGER.log(Level.SEVERE, e, () -> "Failed to execute async job for thread " + thread.getName()); + } + } + } + + private Runnable takeRunnable() throws InterruptedException { + mutex.lock(); + try { + while (jobs.isEmpty() && !killswitch) { + cond.await(); + } + + if (jobs.isEmpty()) return null; // We've set killswitch + + return jobs.remove(); + } finally { + mutex.unlock(); + } + } + +} diff --git a/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java b/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..c1929840254a3e6d721816f4a20415bea1742580 --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/util/IterableWrapper.java @@ -0,0 +1,20 @@ +package gg.pufferfish.pufferfish.util; + +import java.util.Iterator; +import org.jetbrains.annotations.NotNull; + +public class IterableWrapper implements Iterable { + + private final Iterator iterator; + + public IterableWrapper(Iterator iterator) { + this.iterator = iterator; + } + + @NotNull + @Override + public Iterator iterator() { + return iterator; + } + +} diff --git a/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java b/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..facd55463d44cb7e3d2ca6892982f5497b8dded1 --- /dev/null +++ b/src/main/java/gg/pufferfish/pufferfish/util/Long2ObjectOpenHashMapWrapper.java @@ -0,0 +1,40 @@ +package gg.pufferfish.pufferfish.util; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import java.util.Map; +import org.jetbrains.annotations.Nullable; + +public class Long2ObjectOpenHashMapWrapper extends Long2ObjectOpenHashMap { + + private final Map backingMap; + + public Long2ObjectOpenHashMapWrapper(Map map) { + backingMap = map; + } + + @Override + public V put(Long key, V value) { + return backingMap.put(key, value); + } + + @Override + public V get(Object key) { + return backingMap.get(key); + } + + @Override + public V remove(Object key) { + return backingMap.remove(key); + } + + @Nullable + @Override + public V putIfAbsent(Long key, V value) { + return backingMap.putIfAbsent(key, value); + } + + @Override + public int size() { + return backingMap.size(); + } +} diff --git a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java index 790bad0494454ca12ee152e3de6da3da634d9b20..3099c21ee589acf6edff5903a23673adf489024f 100644 --- a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java +++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java @@ -31,6 +31,8 @@ public record ServerBuildInfoImpl( private static final String ATTRIBUTE_GIT_COMMIT = "Git-Commit"; private static final String BRAND_PAPER_NAME = "Paper"; + private static final String BRAND_PUFFERFISH_NAME = "Pufferfish"; // Purpur + private static final String BRAND_PURPUR_NAME = "Purpur"; // Purpur private static final String BUILD_DEV = "DEV"; @@ -42,9 +44,9 @@ public record ServerBuildInfoImpl( this( getManifestAttribute(manifest, ATTRIBUTE_BRAND_ID) .map(Key::key) - .orElse(BRAND_PAPER_ID), + .orElse(BRAND_PURPUR_ID), // Purpur getManifestAttribute(manifest, ATTRIBUTE_BRAND_NAME) - .orElse(BRAND_PAPER_NAME), + .orElse(BRAND_PURPUR_NAME), // Purpur SharedConstants.getCurrentVersion().getId(), SharedConstants.getCurrentVersion().getName(), getManifestAttribute(manifest, ATTRIBUTE_BUILD_NUMBER) @@ -61,7 +63,7 @@ public record ServerBuildInfoImpl( @Override public boolean isBrandCompatible(final @NotNull Key brandId) { - return brandId.equals(this.brandId); + return brandId.equals(this.brandId) || brandId.equals(BRAND_PAPER_ID) || brandId.equals(BRAND_PUFFERFISH_ID); // Purpur } @Override diff --git a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java index f0fce4113fb07c64adbec029d177c236cbdcbae8..865dc183276720d54d31d2a54d1bb5c845e80598 100644 --- a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java +++ b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java @@ -78,10 +78,10 @@ public class PaperPluginsCommand extends BukkitCommand { this.setAliases(Arrays.asList("pl")); } - private static List formatProviders(TreeMap> plugins) { + private static List formatProviders(TreeMap> plugins, @NotNull CommandSender sender) { // Purpur List components = new ArrayList<>(plugins.size()); for (PluginProvider entry : plugins.values()) { - components.add(formatProvider(entry)); + components.add(formatProvider(entry, sender)); // Purpur } boolean isFirst = true; @@ -109,7 +109,7 @@ public class PaperPluginsCommand extends BukkitCommand { return formattedSublists; } - private static Component formatProvider(PluginProvider provider) { + private static Component formatProvider(PluginProvider provider, @NotNull CommandSender sender) { // Purpur TextComponent.Builder builder = Component.text(); if (provider instanceof SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) { builder.append(LEGACY_PLUGIN_STAR); @@ -117,13 +117,65 @@ public class PaperPluginsCommand extends BukkitCommand { String name = provider.getMeta().getName(); Component pluginName = Component.text(name, fromStatus(provider)) - .clickEvent(ClickEvent.runCommand("/version " + name)); + // Purpur start + .clickEvent(ClickEvent.suggestCommand("/version " + name)); + + if (sender instanceof org.bukkit.entity.Player && sender.hasPermission("bukkit.command.version")) { + // Event components + String description = provider.getMeta().getDescription(); + TextComponent.Builder hover = Component.text(); + hover.append(Component.text("Version: ", NamedTextColor.WHITE)).append(Component.text(provider.getMeta().getVersion(), NamedTextColor.GREEN)); + + if (description != null) { + hover.append(Component.newline()) + .append(Component.text("Description: ", NamedTextColor.WHITE)) + .append(Component.text(description, NamedTextColor.GREEN)); + } + + if (provider.getMeta().getWebsite() != null) { + hover.append(Component.newline()) + .append(Component.text("Website: ", NamedTextColor.WHITE)) + .append(Component.text(provider.getMeta().getWebsite(), NamedTextColor.GREEN)); + } + + if (!provider.getMeta().getAuthors().isEmpty()) { + hover.append(Component.newline()); + if (provider.getMeta().getAuthors().size() == 1) { + hover.append(Component.text("Author: ")); + } else { + hover.append(Component.text("Authors: ")); + } + + hover.append(getAuthors(provider.getMeta())); + } + + pluginName.hoverEvent(hover.build()); + } + // Purpur end builder.append(pluginName); return builder.build(); } + // Purpur start + @NotNull + private static TextComponent getAuthors(@NotNull final PluginMeta pluginMeta) { + TextComponent.Builder builder = Component.text(); + List authors = pluginMeta.getAuthors(); + + for (int i = 0; i < authors.size(); i++) { + if (i > 0) { + builder.append(Component.text(i < authors.size() - 1 ? ", " : " and ", NamedTextColor.WHITE)); + } + + builder.append(Component.text(authors.get(i), NamedTextColor.GREEN)); + } + + return builder.build(); + } + // Purpur end + private static Component asPlainComponents(String strings) { net.kyori.adventure.text.TextComponent.Builder builder = Component.text(); for (String string : strings.split("\n")) { @@ -182,24 +234,24 @@ public class PaperPluginsCommand extends BukkitCommand { } } - Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE); + //Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE); //.append(INFO_ICON_START.hoverEvent(SERVER_PLUGIN_INFO)); TODO: Add docs - sender.sendMessage(infoMessage); + //sender.sendMessage(infoMessage); // Purpur - if (!paperPlugins.isEmpty()) { - sender.sendMessage(PAPER_HEADER); - } + //if (!paperPlugins.isEmpty()) { // Purpur + sender.sendMessage(PAPER_HEADER.append(Component.text(" (%s):".formatted(paperPlugins.size())))); // Purpur + //} // Purpur - for (Component component : formatProviders(paperPlugins)) { + for (Component component : formatProviders(paperPlugins, sender)) { // Purpur sender.sendMessage(component); } - if (!spigotPlugins.isEmpty()) { - sender.sendMessage(BUKKIT_HEADER); - } + //if (!spigotPlugins.isEmpty()) { // Purpur + sender.sendMessage(BUKKIT_HEADER.append(Component.text(" (%s):".formatted(spigotPlugins.size())))); // Purpur + //} // Purpur - for (Component component : formatProviders(spigotPlugins)) { + for (Component component : formatProviders(spigotPlugins, sender)) { // Purpur sender.sendMessage(component); } diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java index c5644d8d64f12073e39bc6ed79c8714f4560ff89..47a2cba0db36b11548d06ec21f7c7d7c9a962d6e 100644 --- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java @@ -263,6 +263,7 @@ public class PaperConfigurations extends Configurations 0 || SysoutCatcher.NAG_TIMEOUT > 0) { diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java index b24265573fdef5d9a964bcd76146f34542c420cf..710477ae27ebc5afdf0012ef0867d05efd293c24 100644 --- a/src/main/java/net/minecraft/CrashReport.java +++ b/src/main/java/net/minecraft/CrashReport.java @@ -32,6 +32,7 @@ public class CrashReport { private boolean trackingStackTrace = true; private StackTraceElement[] uncategorizedStackTrace = new StackTraceElement[0]; private final SystemReport systemReport = new SystemReport(); + private List extraInfo = List.of("", "DO NOT REPORT THIS TO PAPER! REPORT TO PURPUR INSTEAD!", ""); // Purpur - Rebrand public CrashReport(String message, Throwable cause) { io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper @@ -144,7 +145,7 @@ public class CrashReport { } public String getFriendlyReport(ReportType type) { - return this.getFriendlyReport(type, List.of()); + return this.getFriendlyReport(type, extraInfo); // Purpur - Rebrand } @Nullable @@ -191,7 +192,7 @@ public class CrashReport { } public boolean saveToFile(Path path, ReportType type) { - return this.saveToFile(path, type, List.of()); + return this.saveToFile(path, type, extraInfo); // Purpur - Rebrand } public SystemReport getSystemReport() { diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java index 13bd145b1e8006a53c22f5dc0c78f29b540c7663..0d133cd7993eb40b19e2aabe8e2bfcdcf5352398 100644 --- a/src/main/java/net/minecraft/commands/CommandSourceStack.java +++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java @@ -211,6 +211,19 @@ public class CommandSourceStack implements ExecutionCommandSource").replacement(bukkitPermission).build()))); + } + return false; + } + // Purpur end + public Vec3 getPosition() { return this.worldPosition; } @@ -312,6 +325,30 @@ public class CommandSourceStack implements ExecutionCommandSource io.papermc.paper.adventure.PaperAdventure.asVanilla(message), broadcastToOps); + } + // Purpur end + 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 64bf4444ffba25cb40743a32267aa790ad1738f9..8d82425ccbc30b3f4a59767ca72bf37311ddeda0 100644 --- a/src/main/java/net/minecraft/commands/Commands.java +++ b/src/main/java/net/minecraft/commands/Commands.java @@ -226,8 +226,8 @@ public class Commands { JfrCommand.register(this.dispatcher); } - if (SharedConstants.IS_RUNNING_IN_IDE) { - TestCommand.register(this.dispatcher); + if (org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands || SharedConstants.IS_RUNNING_IN_IDE) { // Purpur + if (!org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands) TestCommand.register(this.dispatcher); // Purpur RaidCommand.register(this.dispatcher, commandRegistryAccess); DebugPathCommand.register(this.dispatcher); DebugMobSpawningCommand.register(this.dispatcher); @@ -255,6 +255,14 @@ public class Commands { StopCommand.register(this.dispatcher); TransferCommand.register(this.dispatcher); WhitelistCommand.register(this.dispatcher); + org.purpurmc.purpur.command.CreditsCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.DemoCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.PingCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.UptimeCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.TPSBarCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.CompassCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.RamBarCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.RamCommand.register(this.dispatcher); // Purpur } if (environment.includeIntegrated) { @@ -515,6 +523,7 @@ public class Commands { private void runSync(ServerPlayer player, Collection bukkit, RootCommandNode rootcommandnode) { // Paper end - Perf: Async command map building new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, true).callEvent(); // Paper - Brigadier API + if (PlayerCommandSendEvent.getHandlerList().getRegisteredListeners().length > 0) { // Purpur - skip all this crap if there's nothing listening PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit)); event.getPlayer().getServer().getPluginManager().callEvent(event); @@ -525,6 +534,7 @@ public class Commands { } } // CraftBukkit end + } // Purpur - skip event player.connection.send(new ClientboundCommandsPacket(rootcommandnode)); } diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java index c8d39e6e1c570c9219f6066da273dc0130920519..b455c7e9d18bac3654daa8510f85cc21202e254b 100644 --- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java +++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java @@ -198,10 +198,10 @@ public class EntitySelector { if (this.playerName != null) { entityplayer = source.getServer().getPlayerList().getPlayerByName(this.playerName); - return entityplayer == null ? List.of() : List.of(entityplayer); + return entityplayer == null || !canSee(source, entityplayer) ? List.of() : List.of(entityplayer); // Purpur } else if (this.entityUUID != null) { entityplayer = source.getServer().getPlayerList().getPlayer(this.entityUUID); - return entityplayer == null ? List.of() : List.of(entityplayer); + return entityplayer == null || !canSee(source, entityplayer) ? List.of() : List.of(entityplayer); // Purpur } else { Vec3 vec3d = (Vec3) this.position.apply(source.getPosition()); AABB axisalignedbb = this.getAbsoluteAabb(vec3d); @@ -214,7 +214,7 @@ public class EntitySelector { ServerPlayer entityplayer1 = (ServerPlayer) entity; if (predicate.test(entityplayer1)) { - return List.of(entityplayer1); + return !canSee(source, entityplayer1) ? List.of() : List.of(entityplayer1); // Purpur } } @@ -225,6 +225,7 @@ public class EntitySelector { if (this.isWorldLimited()) { object = source.getLevel().getPlayers(predicate, i); + ((List) object).removeIf(entityplayer3 -> !canSee(source, (ServerPlayer) entityplayer3)); // Purpur } else { object = new ObjectArrayList(); Iterator iterator = source.getServer().getPlayerList().getPlayers().iterator(); @@ -232,7 +233,7 @@ public class EntitySelector { while (iterator.hasNext()) { ServerPlayer entityplayer2 = (ServerPlayer) iterator.next(); - if (predicate.test(entityplayer2)) { + if (predicate.test(entityplayer2) && canSee(source, entityplayer2)) { // Purpur ((List) object).add(entityplayer2); if (((List) object).size() >= i) { return (List) object; @@ -299,4 +300,10 @@ public class EntitySelector { public static Component joinNames(List entities) { return ComponentUtils.formatList(entities, Entity::getDisplayName); } + + // Purpur start + 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/commands/execution/tasks/BuildContexts.java b/src/main/java/net/minecraft/commands/execution/tasks/BuildContexts.java index b0d26b0eadb2a43924629424a6c13198aace8f69..9f5c3ec2eae9b30bdb8dbcb328d7f701cb7aeb9d 100644 --- a/src/main/java/net/minecraft/commands/execution/tasks/BuildContexts.java +++ b/src/main/java/net/minecraft/commands/execution/tasks/BuildContexts.java @@ -52,7 +52,7 @@ public class BuildContexts> { } RedirectModifier redirectModifier = commandContext.getRedirectModifier(); - if (redirectModifier instanceof CustomModifierExecutor customModifierExecutor) { + if (redirectModifier instanceof CustomModifierExecutor customModifierExecutor) { // Purpur - decompile error customModifierExecutor.apply(baseSource, list, contextChain, chainModifiers, ExecutionControl.create(context, frame)); return; } @@ -92,11 +92,11 @@ public class BuildContexts> { if (list.isEmpty()) { if (chainModifiers.isReturn()) { - context.queueNext(new CommandQueueEntry<>(frame, FallthroughTask.instance())); + context.queueNext(new CommandQueueEntry<>(frame, (EntryAction) FallthroughTask.instance())); // Purpur - decompile error } } else { CommandContext commandContext2 = contextChain.getTopContext(); - if (commandContext2.getCommand() instanceof CustomCommandExecutor customCommandExecutor) { + if (commandContext2.getCommand() instanceof CustomCommandExecutor customCommandExecutor) { // Purpur - decompile error ExecutionControl executionControl = ExecutionControl.create(context, frame); for (T executionCommandSource2 : list) { diff --git a/src/main/java/net/minecraft/commands/synchronization/ArgumentTypeInfos.java b/src/main/java/net/minecraft/commands/synchronization/ArgumentTypeInfos.java index 7b118a92a6eb779f800ae8f5d8f6e3c861fc4f6a..057a038e8dcacd7496a0b2373de2c20255a5c297 100644 --- a/src/main/java/net/minecraft/commands/synchronization/ArgumentTypeInfos.java +++ b/src/main/java/net/minecraft/commands/synchronization/ArgumentTypeInfos.java @@ -119,10 +119,10 @@ public class ArgumentTypeInfos { register(registry, "dimension", DimensionArgument.class, SingletonArgumentInfo.contextFree(DimensionArgument::dimension)); register(registry, "gamemode", GameModeArgument.class, SingletonArgumentInfo.contextFree(GameModeArgument::gameMode)); register(registry, "time", TimeArgument.class, new TimeArgument.Info()); - register(registry, "resource_or_tag", fixClassType(ResourceOrTagArgument.class), new ResourceOrTagArgument.Info()); - register(registry, "resource_or_tag_key", fixClassType(ResourceOrTagKeyArgument.class), new ResourceOrTagKeyArgument.Info()); - register(registry, "resource", fixClassType(ResourceArgument.class), new ResourceArgument.Info()); - register(registry, "resource_key", fixClassType(ResourceKeyArgument.class), new ResourceKeyArgument.Info()); + register(registry, "resource_or_tag", fixClassType(ResourceOrTagArgument.class), new ResourceOrTagArgument.Info<>()); // Purpur - decompile error + register(registry, "resource_or_tag_key", fixClassType(ResourceOrTagKeyArgument.class), new ResourceOrTagKeyArgument.Info<>()); // Purpur - decompile error + register(registry, "resource", fixClassType(ResourceArgument.class), new ResourceArgument.Info<>()); // Purpur - decompile error + register(registry, "resource_key", fixClassType(ResourceKeyArgument.class), new ResourceKeyArgument.Info<>()); // Purpur - decompile error register(registry, "template_mirror", TemplateMirrorArgument.class, SingletonArgumentInfo.contextFree(TemplateMirrorArgument::templateMirror)); register(registry, "template_rotation", TemplateRotationArgument.class, SingletonArgumentInfo.contextFree(TemplateRotationArgument::templateRotation)); register(registry, "heightmap", HeightmapTypeArgument.class, SingletonArgumentInfo.contextFree(HeightmapTypeArgument::heightmap)); diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java index f58a94efafbc01d402cd03a108bb90f60930a316..21ea63da99c5b3e2e1ab9cc1049c903bba6cf288 100644 --- a/src/main/java/net/minecraft/core/BlockPos.java +++ b/src/main/java/net/minecraft/core/BlockPos.java @@ -62,6 +62,12 @@ public class BlockPos extends Vec3i { public static final int MAX_HORIZONTAL_COORDINATE = 33554431; // Paper end - Optimize Bit Operations by inlining + // 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/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java index 0d12605dc84dad49faa18bf1fd058c3c168623ee..c6490554a3025f4de3f3218178fad76cd1848a19 100644 --- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java @@ -930,5 +930,22 @@ public interface DispenseItemBehavior { DispenserBlock.registerBehavior(Items.TNT_MINECART, new MinecartDispenseItemBehavior(EntityType.TNT_MINECART)); DispenserBlock.registerBehavior(Items.HOPPER_MINECART, new MinecartDispenseItemBehavior(EntityType.HOPPER_MINECART)); DispenserBlock.registerBehavior(Items.COMMAND_BLOCK_MINECART, new MinecartDispenseItemBehavior(EntityType.COMMAND_BLOCK_MINECART)); + // Purpur start + DispenserBlock.registerBehavior(Items.ANVIL, (new OptionalDispenseItemBehavior() { + @Override + public ItemStack execute(BlockSource dispenser, ItemStack stack) { + net.minecraft.world.level.Level level = dispenser.level(); + if (!level.purpurConfig.dispenserPlaceAnvils) return super.execute(dispenser, stack); + Direction facing = dispenser.blockEntity().getBlockState().getValue(DispenserBlock.FACING); + BlockPos pos = dispenser.pos().relative(facing); + BlockState state = level.getBlockState(pos); + if (state.isAir()) { + level.setBlockAndUpdate(pos, Blocks.ANVIL.defaultBlockState().setValue(net.minecraft.world.level.block.AnvilBlock.FACING, facing.getAxis() == Direction.Axis.Y ? Direction.NORTH : facing.getClockWise())); + stack.shrink(1); + } + return stack; + } + })); + // Purpur end } } diff --git a/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java index bf8c511739265c6a9cd277752e844481598f8966..ffe2399ab6b1f311536475d8216238b5b01c5dab 100644 --- a/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java @@ -41,7 +41,7 @@ public class EquipmentDispenseItemBehavior extends DefaultDispenseItemBehavior { return false; } else { LivingEntity entityliving = (LivingEntity) list.getFirst(); - EquipmentSlot enumitemslot = entityliving.getEquipmentSlotForItem(stack); + EquipmentSlot enumitemslot = pointer.level().purpurConfig.dispenserApplyCursedArmor ? entityliving.getEquipmentSlotForItem(stack) : entityliving.getEquipmentSlotForDispenserItem(stack); if (enumitemslot == null) return false; // Purpur - Dispenser curse of binding protection ItemStack itemstack1 = stack.copyWithCount(1); // Paper - shrink below and single item in event // CraftBukkit start diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java index e693a003ea8f022eef8b49e4332025b769333b30..44a31f6f44885598ee06e88b940b84b1e89e5430 100644 --- a/src/main/java/net/minecraft/network/Connection.java +++ b/src/main/java/net/minecraft/network/Connection.java @@ -617,11 +617,20 @@ public class Connection extends SimpleChannelInboundHandler> { private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world private static int joinAttemptsThisTick; // Paper - Buffer joins to world private static int currTick; // Paper - Buffer joins to world + private static int tickSecond; // Purpur public void tick() { this.flushQueue(); // Paper start - Buffer joins to world if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) { Connection.currTick = net.minecraft.server.MinecraftServer.currentTick; + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.maxJoinsPerSecond) { + if (++Connection.tickSecond > 20) { + Connection.tickSecond = 0; + Connection.joinAttemptsThisTick = 0; + } + } else + // Purpur end Connection.joinAttemptsThisTick = 0; } // Paper end - Buffer joins to world diff --git a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java index 300929a406905f5ff1ede664d5b99fb0938d4d2e..a4e9ac0e07f08e0b6aa682e8c1587d9c84fc3c6c 100644 --- a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java +++ b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java @@ -45,7 +45,7 @@ public class SignedMessageChain { SignedMessageLink signedMessageLink = SignedMessageChain.this.nextLink; if (signedMessageLink == null) { throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.CHAIN_BROKEN); - } else if (body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) { + } else if (org.purpurmc.purpur.PurpurConfig.kickForOutOfOrderChat && body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) { this.setChainBroken(); throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT, org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event causes } else { diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java index 90ca25c4aaf92a5639839a7cdaee2ffcdb75efa7..ecaec3c7b5768771b00c4c33d48a76ba22653a79 100644 --- a/src/main/java/net/minecraft/server/Main.java +++ b/src/main/java/net/minecraft/server/Main.java @@ -120,6 +120,12 @@ public class Main { JvmProfiler.INSTANCE.start(Environment.SERVER); } + // Purpur start - load config files early + org.bukkit.configuration.file.YamlConfiguration purpurConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("purpur-settings")); + org.purpurmc.purpur.PurpurConfig.clampEnchantLevels = purpurConfiguration.getBoolean("settings.enchantment.clamp-levels", true); + org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands = purpurConfiguration.getBoolean("settings.register-minecraft-debug-commands"); + // Purpur end - load config files early + io.papermc.paper.plugin.PluginInitializerManager.load(optionset); // Paper Bootstrap.bootStrap(); Bootstrap.validate(); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 4158473fd553a16fec23bcbcf9a278d413120600..23505b0f80d643d9ef569b49192f44e0373dfb5f 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -314,6 +314,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); public int autosavePeriod; // Paper - don't store the vanilla dispatcher @@ -324,15 +325,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping + public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("MobSpawning"); // Pufferfish - optimize mob spawning public static S spin(Function serverFactory) { AtomicReference atomicreference = new AtomicReference(); @@ -1054,6 +1058,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - Add EntityMoveEvent net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers worldserver.updateLagCompensationTick(); // Paper - lag compensation + worldserver.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur gameprofilerfiller.push(() -> { String s = String.valueOf(worldserver); @@ -2011,7 +2058,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { this.executeBlocking(() -> { diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java index 8e2eb7b61421ceb063654826941f1a81f6f50bdf..1e85c9318ede93b8e9fe548a8945324b5b00e818 100644 --- a/src/main/java/net/minecraft/server/PlayerAdvancements.java +++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java @@ -199,6 +199,7 @@ public class PlayerAdvancements { if (advancementholder == null) { if (!minecraftkey.getNamespace().equals("minecraft")) return; // CraftBukkit + if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressIgnoredAdvancementWarnings) // Purpur PlayerAdvancements.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", minecraftkey, this.playerSavePath); } else { this.startProgress(advancementholder, advancementprogress); @@ -249,6 +250,7 @@ public class PlayerAdvancements { advancement.value().display().ifPresent((advancementdisplay) -> { // Paper start - Add Adventure message to PlayerAdvancementDoneEvent if (event.message() != null && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) { + if (org.purpurmc.purpur.PurpurConfig.advancementOnlyBroadcastToAffectedPlayer) this.player.sendMessage(message); else // Purpur this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false); // Paper end } diff --git a/src/main/java/net/minecraft/server/commands/EnchantCommand.java b/src/main/java/net/minecraft/server/commands/EnchantCommand.java index cf0a5943f457c532958f40b4989fa18f967abae6..2ab8ff8ca51eb841932ccca4a348acc0141264a8 100644 --- a/src/main/java/net/minecraft/server/commands/EnchantCommand.java +++ b/src/main/java/net/minecraft/server/commands/EnchantCommand.java @@ -70,7 +70,7 @@ public class EnchantCommand { private static int enchant(CommandSourceStack source, Collection targets, Holder enchantment, int level) throws CommandSyntaxException { Enchantment enchantment2 = enchantment.value(); - if (level > enchantment2.getMaxLevel()) { + if (!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && level > enchantment2.getMaxLevel()) { // Purpur - Config to allow unsafe enchants throw ERROR_LEVEL_TOO_HIGH.create(level, enchantment2.getMaxLevel()); } else { int i = 0; @@ -81,7 +81,7 @@ public class EnchantCommand { ItemStack itemStack = livingEntity.getMainHandItem(); if (!itemStack.isEmpty()) { if (enchantment2.canEnchant(itemStack) - && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(itemStack).keySet(), enchantment)) { + && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(itemStack).keySet(), enchantment) || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && !itemStack.hasEnchantment(enchantment))) { // Purpur - Config to allow unsafe enchants itemStack.enchant(enchantment, level); i++; } else if (targets.size() == 1) { diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java index d1da3600dc07107309b20ebe6e7c0c4da0e8de76..244b4719c689f153fa36381a60acc280bb0bd9b3 100644 --- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java +++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java @@ -57,6 +57,18 @@ public class GameModeCommand { } private static int setMode(CommandContext context, Collection targets, GameType gameMode) { + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.commandGamemodeRequiresPermission) { + String gamemode = gameMode.getName(); + CommandSourceStack sender = context.getSource(); + if (!sender.testPermission(2, "minecraft.command.gamemode." + gamemode)) { + return 0; + } + if (sender.getEntity() instanceof ServerPlayer player && (targets.size() > 1 || !targets.contains(player)) && !sender.testPermission(2, "minecraft.command.gamemode." + gamemode + ".other")) { + return 0; + } + } + // Purpur end int i = 0; for (ServerPlayer serverPlayer : targets) { diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java index 0d9de4c61c7b26a6ff37c12fde629161fd0c3d5a..2f7897744f4aea718170698881773e9031a58a51 100644 --- a/src/main/java/net/minecraft/server/commands/GiveCommand.java +++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java @@ -60,6 +60,7 @@ public class GiveCommand { boolean flag = entityplayer.getInventory().add(itemstack1); ItemEntity entityitem; + if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) continue; // Purpur - add config option for toggling give command dropping if (flag && itemstack1.isEmpty()) { entityitem = entityplayer.drop(itemstack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event if (entityitem != null) { diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java index 17a158ff6ce6520b69a5a0032ba4c05449dd0cf8..7a442fb4cf78bcb1268359ee41a6de138edb76b6 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -111,6 +111,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface return; } // Paper start - Use TerminalConsoleAppender + if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - has no GUI or has console (did not double-click) new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start(); /* jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader; @@ -219,6 +220,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface org.spigotmc.SpigotConfig.registerCommands(); // Spigot end io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc. + // Purpur start + try { + org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings")); + } catch (Exception e) { + DedicatedServer.LOGGER.error("Unable to load server configuration", e); + return false; + } + org.purpurmc.purpur.PurpurConfig.registerCommands(); + // Purpur end // Paper start - initialize global and world-defaults configuration this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess()); this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess()); @@ -236,6 +246,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now + 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 this.setPvpAllowed(dedicatedserverproperties.pvp); this.setFlightAllowed(dedicatedserverproperties.allowFlight); @@ -283,6 +296,30 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface if (true) throw new IllegalStateException("Failed to bind to port", ioexception); // Paper - Propagate failed to bind to port error return false; } + // Purpur start + 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 @@ -356,6 +393,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface DedicatedServer.LOGGER.info("JMX monitoring enabled"); } + if (gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) mobSpawnExecutor.start(); // Pufferfish + org.purpurmc.purpur.task.BossBarTask.startAll(); // Purpur + if (org.purpurmc.purpur.PurpurConfig.beeCountPayload) org.purpurmc.purpur.task.BeehiveTask.instance().register(); // Purpur return true; } } diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java index c3ec370b83b895be0f03662e3884fa4a2442a2a6..05e16103af3fd276f0196ddf1a2e5b729b025c34 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java @@ -56,6 +56,7 @@ public class DedicatedServerProperties extends Settings finalizers = Lists.newArrayList(); final AtomicBoolean isClosing = new AtomicBoolean(); + // Purpur start + private final CommandHistory history = new CommandHistory(); + private String currentCommand = ""; + private int historyIndex = 0; + // Purpur end public static MinecraftServerGui showFrameFor(final DedicatedServer server) { try { @@ -51,7 +56,7 @@ public class MinecraftServerGui extends JComponent { ; } - final JFrame jframe = new JFrame("Minecraft server"); + final JFrame jframe = new JFrame("Purpur Minecraft server"); // Purpur final MinecraftServerGui servergui = new MinecraftServerGui(server); jframe.setDefaultCloseOperation(2); @@ -59,7 +64,7 @@ public class MinecraftServerGui extends JComponent { jframe.pack(); jframe.setLocationRelativeTo((Component) null); jframe.setVisible(true); - jframe.setName("Minecraft server"); // Paper - Improve ServerGUI + jframe.setName("Purpur Minecraft server"); // Paper - Improve ServerGUI // Purpur // Paper start - Improve ServerGUI try { @@ -71,7 +76,7 @@ public class MinecraftServerGui extends JComponent { jframe.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent windowevent) { if (!servergui.isClosing.getAndSet(true)) { - jframe.setTitle("Minecraft server - shutting down!"); + jframe.setTitle("Purpur Minecraft server - shutting down!"); // Purpur server.halt(true); servergui.runFinalizers(); } @@ -159,7 +164,7 @@ public class MinecraftServerGui extends JComponent { private JComponent buildChatPanel() { JPanel jpanel = new JPanel(new BorderLayout()); - JTextArea jtextarea = new JTextArea(); + org.purpurmc.purpur.gui.JColorTextPane jtextarea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur JScrollPane jscrollpane = new JScrollPane(jtextarea, 22, 30); jtextarea.setEditable(false); @@ -171,10 +176,43 @@ public class MinecraftServerGui extends JComponent { if (!s.isEmpty()) { this.server.handleConsoleInput(s, this.server.createCommandSourceStack()); + // Purpur start + history.add(s); + historyIndex = -1; + // Purpur end } jtextfield.setText(""); }); + // Purpur start + jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up"); + jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down"); + jtextfield.getActionMap().put("up", new javax.swing.AbstractAction() { + @Override + public void actionPerformed(java.awt.event.ActionEvent actionEvent) { + if (historyIndex < 0) { + currentCommand = jtextfield.getText(); + } + if (historyIndex < history.size() - 1) { + jtextfield.setText(history.get(++historyIndex)); + } + } + }); + jtextfield.getActionMap().put("down", new javax.swing.AbstractAction() { + @Override + public void actionPerformed(java.awt.event.ActionEvent actionEvent) { + if (historyIndex >= 0) { + if (historyIndex == 0) { + --historyIndex; + jtextfield.setText(currentCommand); + } else { + --historyIndex; + jtextfield.setText(history.get(historyIndex)); + } + } + } + }); + // Purpur end jtextarea.addFocusListener(new FocusAdapter() { // CraftBukkit - decompile error public void focusGained(FocusEvent focusevent) {} }); @@ -210,7 +248,7 @@ public class MinecraftServerGui extends JComponent { } private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper - public void print(JTextArea textArea, JScrollPane scrollPane, String message) { + public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String message) { // Purpur if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(() -> { this.print(textArea, scrollPane, message); @@ -224,11 +262,14 @@ public class MinecraftServerGui extends JComponent { flag = (double) jscrollbar.getValue() + jscrollbar.getSize().getHeight() + (double) (MinecraftServerGui.MONOSPACED.getSize() * 4) > (double) jscrollbar.getMaximum(); } + /* // Purpur try { document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(message).replaceAll(""), (AttributeSet) null); // CraftBukkit } catch (BadLocationException badlocationexception) { ; } + */ // Purpur + textArea.append(message); // Purpur if (flag) { jscrollbar.setValue(Integer.MAX_VALUE); @@ -236,4 +277,16 @@ public class MinecraftServerGui extends JComponent { } } + + // Purpur start + public static class CommandHistory extends java.util.LinkedList { + @Override + public boolean add(String command) { + if (size() > 1000) { + remove(); + } + return super.offerFirst(command); + } + } + // Purpur end } diff --git a/src/main/java/net/minecraft/server/gui/StatsComponent.java b/src/main/java/net/minecraft/server/gui/StatsComponent.java index 096c89bd01cec2abd151bf6fffc4847d1bcd548f..cd0a8a6a1be75cab8bbb8ee3ac17bb732b9e7108 100644 --- a/src/main/java/net/minecraft/server/gui/StatsComponent.java +++ b/src/main/java/net/minecraft/server/gui/StatsComponent.java @@ -45,7 +45,7 @@ public class StatsComponent extends JComponent { this.msgs[1] = "Avg tick: " + DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / (double)TimeUtil.NANOSECONDS_PER_MILLISECOND) + " ms"; - this.msgs[2] = "TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg); + this.msgs[2] = "TPS from last 5s, 1m, 5m, 15m: " + String.join(", ", tpsAvg); // Purpur // Paper end - Improve ServerGUI this.values[this.vp++ & 0xFF] = (int)(l * 100L / Runtime.getRuntime().maxMemory()); this.repaint(); diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java index d021cd5b6136f0125076513977f430c6d4dd4f9f..1cb8c9d21eef27492efc4995525c198cbffd4b9b 100644 --- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java +++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java @@ -176,6 +176,9 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon // Paper end - chunk tick iteration optimisations + public boolean firstRunSpawnCounts = true; // Pufferfish + public final java.util.concurrent.atomic.AtomicBoolean _pufferfish_spawnCountsReady = new java.util.concurrent.atomic.AtomicBoolean(false); // Pufferfish - optimize countmobs + public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, int simulationDistance, boolean dsync, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory) { this.level = world; this.mainThreadProcessor = new ServerChunkCache.MainThreadExecutor(world); @@ -504,6 +507,43 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon this.broadcastChangedChunks(gameprofilerfiller); gameprofilerfiller.pop(); } + + // Pufferfish start - optimize mob spawning + if (gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) { + for (ServerPlayer player : this.level.players) { + // Paper start - per player mob spawning backoff + for (int ii = 0; ii < ServerPlayer.MOBCATEGORY_TOTAL_ENUMS; ii++) { + player.mobCounts[ii] = 0; + + int newBackoff = player.mobBackoffCounts[ii] - 1; // TODO make configurable bleed // TODO use nonlinear algorithm? + if (newBackoff < 0) { + newBackoff = 0; + } + player.mobBackoffCounts[ii] = newBackoff; + } + // Paper end - per player mob spawning backoff + } + if (firstRunSpawnCounts) { + firstRunSpawnCounts = false; + _pufferfish_spawnCountsReady.set(true); + } + if (_pufferfish_spawnCountsReady.getAndSet(false)) { + net.minecraft.server.MinecraftServer.getServer().mobSpawnExecutor.submit(() -> { + int mapped = distanceManager.getNaturalSpawnChunkCount(); + ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.Iterator objectiterator = + level.entityTickList.entities.iterator(ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); + try { + gg.pufferfish.pufferfish.util.IterableWrapper wrappedIterator = + new gg.pufferfish.pufferfish.util.IterableWrapper<>(objectiterator); + lastSpawnState = NaturalSpawner.createState(mapped, wrappedIterator, this::getFullChunk, null, true); + } finally { + objectiterator.finishedIterating(); + } + _pufferfish_spawnCountsReady.set(true); + }); + } + } + // Pufferfish end } private void broadcastChangedChunks(ProfilerFiller profiler) { @@ -553,6 +593,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon final int naturalSpawnChunkCount = j; NaturalSpawner.SpawnState spawnercreature_d; // moved down if ((this.spawnFriendlies || this.spawnEnemies) && this.level.paperConfig().entities.spawning.perPlayerMobSpawns) { // don't count mobs when animals and monsters are disabled + if (!gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) { // Pufferfish - moved down when async processing // re-set mob counts for (ServerPlayer player : this.level.players) { // Paper start - per player mob spawning backoff @@ -567,13 +608,17 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon } // Paper end - per player mob spawning backoff } - spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); + lastSpawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); // Pufferfish - async mob spawning + } // Pufferfish - (endif) moved down when async processing } else { - spawnercreature_d = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); + // Pufferfish start - async mob spawning + lastSpawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, !this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new LocalMobCapCalculator(this.chunkMap) : null, false); + _pufferfish_spawnCountsReady.set(true); + // Pufferfish end } // Paper end - Optional per player mob spawns - this.lastSpawnState = spawnercreature_d; + // this.lastSpawnState = spawnercreature_d; // Pufferfish - this is managed asynchronously profiler.popPush("spawnAndTick"); boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING); @@ -590,7 +635,7 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon // Paper end - PlayerNaturallySpawnCreaturesEvent boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit - list1 = NaturalSpawner.getFilteredSpawningCategories(spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1, this.level); // CraftBukkit + list1 = NaturalSpawner.getFilteredSpawningCategories(lastSpawnState, this.spawnFriendlies, this.spawnEnemies, flag1, this.level); // CraftBukkit // Pufferfish } else { list1 = List.of(); } @@ -602,8 +647,8 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon ChunkPos chunkcoordintpair = chunk.getPos(); chunk.incrementInhabitedTime(timeDelta); - if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot - NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, list1); + if (!list1.isEmpty() && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && (!gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning || _pufferfish_spawnCountsReady.get()) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot // Pufferfish + NaturalSpawner.spawnForChunk(this.level, chunk, lastSpawnState, list1); // Pufferfish } if (true) { // Paper - rewrite chunk system diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java index 90eb4927fa51ce3df86aa7b6c71f49150a03e337..c14ce846afb7364e541bfe01b73a06a94e48f55b 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -81,7 +81,7 @@ public class ServerEntity { @Nullable private List> trackedDataValues; // CraftBukkit start - private final Set trackedPlayers; + public final Set trackedPlayers; // Purpur - private -> public public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer> consumer, Set trackedPlayers) { this.trackedPlayers = trackedPlayers; @@ -207,6 +207,7 @@ public class ServerEntity { boolean flag5 = i < -32768L || i > 32767L || j < -32768L || j > 32767L || k < -32768L || k > 32767L; if (!this.forceStateResync && !flag5 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.onGround()) { // Paper - fix desync when a player is added to the tracker + if (flag2 || flag3 || this.entity instanceof AbstractArrow) { // Pufferfish if ((!flag2 || !flag) && !(this.entity instanceof AbstractArrow)) { if (flag2) { packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) i), (short) ((int) j), (short) ((int) k), this.entity.onGround()); @@ -220,6 +221,7 @@ public class ServerEntity { flag3 = true; flag4 = true; } + } // Pufferfish } else { this.wasOnGround = this.entity.onGround(); this.teleportDelay = 0; diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 585e2b43a0326f0b81597fa1234d3c67c76af550..6a359f1ed07aa7b9c78e470fb00661ea03eaec03 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -223,6 +223,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe private final StructureManager structureManager; private final StructureCheck structureCheck; private final boolean tickTime; + private double preciseTime; // Purpur + private boolean forceTime; // Purpur private final RandomSequences randomSequences; // CraftBukkit start @@ -231,6 +233,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) + public boolean hasRidableMoveEvent = false; // Purpur public LevelChunk getChunkIfLoaded(int x, int z) { return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately @@ -604,7 +607,24 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // CraftBukkit end this.tickTime = flag1; this.server = minecraftserver; - this.customSpawners = list; + // Purpur start - enable/disable MobSpawners per world + this.customSpawners = Lists.newArrayList(); + if (purpurConfig.phantomSpawning) { + customSpawners.add(new net.minecraft.world.level.levelgen.PhantomSpawner()); + } + if (purpurConfig.patrolSpawning) { + customSpawners.add(new net.minecraft.world.level.levelgen.PatrolSpawner()); + } + if (purpurConfig.catSpawning) { + customSpawners.add(new net.minecraft.world.entity.npc.CatSpawner()); + } + if (purpurConfig.villageSiegeSpawning) { + customSpawners.add(new net.minecraft.world.entity.ai.village.VillageSiege()); + } + if (purpurConfig.villagerTraderSpawning) { + customSpawners.add(new net.minecraft.world.entity.npc.WanderingTraderSpawner(iworlddataserver)); + } + // Purpur end this.serverLevelData = iworlddataserver; ChunkGenerator chunkgenerator = worlddimension.generator(); // CraftBukkit start @@ -676,6 +696,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler); // Paper end - rewrite chunk system this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit + this.preciseTime = this.serverLevelData.getDayTime(); // Purpur } // Paper start @@ -727,7 +748,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); long j; - if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) { + if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) { // CraftBukkit start j = this.levelData.getDayTime() + 24000L; TimeSkipEvent event = new TimeSkipEvent(this.getWorld(), TimeSkipEvent.SkipReason.NIGHT_SKIP, (j - j % 24000L) - this.getDayTime()); @@ -792,6 +813,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe org.spigotmc.ActivationRange.activateEntities(this); // Spigot this.entityTickList.forEach((entity) -> { + entity.activatedPriorityReset = false; // Pufferfish - DAB if (!entity.isRemoved()) { if (!tickratemanager.isEntityFrozen(entity)) { gameprofilerfiller.push("checkDespawn"); @@ -809,7 +831,20 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe } gameprofilerfiller.push("tick"); - this.guardEntityTick(this::tickNonPassenger, entity); + // Pufferfish start - copied from this.guardEntityTick + try { + this.tickNonPassenger(entity); // Pufferfish - changed + } catch (Throwable throwable) { + if (throwable instanceof ThreadDeath) throw throwable; // Paper + // Paper start - Prevent tile entity and entity crashes + final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); + MinecraftServer.LOGGER.error(msg, throwable); + getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); + entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + // Paper end + } + this.moonrise$midTickTasks(); // Paper - rewrite chunk system + // Pufferfish end gameprofilerfiller.pop(); } } @@ -841,6 +876,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.serverLevelData.getScheduledEvents().tick(this.server, i); Profiler.get().pop(); if (this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { + // Purpur start + 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); } @@ -849,8 +891,22 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe 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(); @@ -930,7 +986,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe ProfilerFiller gameprofilerfiller = Profiler.get(); gameprofilerfiller.push("thunder"); - if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && simpleRandom.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder // Paper - optimise random ticking + if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && /*simpleRandom.nextInt(this.spigotConfig.thunderChance) == 0*/ chunk.shouldDoLightning(this.simpleRandom)) { // Spigot // Paper - Option to disable thunder // Paper - optimise random ticking // Pufferfish - replace random with shouldDoLightning BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); if (this.isRainingAt(blockposition)) { @@ -938,10 +994,18 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper - Configurable spawn chances for skeleton horses if (flag1) { - SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); + // Purpur start + net.minecraft.world.entity.animal.horse.AbstractHorse entityhorseskeleton; + if (purpurConfig.zombieHorseSpawnChance > 0D && random.nextDouble() <= purpurConfig.zombieHorseSpawnChance) { + entityhorseskeleton = EntityType.ZOMBIE_HORSE.create(this, EntitySpawnReason.EVENT); + } else { + entityhorseskeleton = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT); + if (entityhorseskeleton != null) ((SkeletonHorse) entityhorseskeleton).setTrap(true); + } + // Purpur end if (entityhorseskeleton != null) { - entityhorseskeleton.setTrap(true); + //entityhorseskeleton.setTrap(true); // Purpur - moved up entityhorseskeleton.setAge(0); entityhorseskeleton.setPos((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()); this.addFreshEntity(entityhorseskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit @@ -1022,7 +1086,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe return holder.is(PoiTypes.LIGHTNING_ROD); }, (blockposition1) -> { return blockposition1.getY() == this.getHeight(Heightmap.Types.WORLD_SURFACE, blockposition1.getX(), blockposition1.getZ()) - 1; - }, pos, 128, PoiManager.Occupancy.ANY); + }, pos, org.purpurmc.purpur.PurpurConfig.lightningRodRange, PoiManager.Occupancy.ANY); return optional.map((blockposition1) -> { return blockposition1.above(1); @@ -1071,11 +1135,27 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe if (this.canSleepThroughNights()) { if (!this.getServer().isSingleplayer() || this.getServer().isPublished()) { int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); - MutableComponent ichatmutablecomponent; + Component ichatmutablecomponent; if (this.sleepStatus.areEnoughSleeping(i)) { + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.isBlank()) { + return; + } + if (!org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.equalsIgnoreCase("default")) { + ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepSkippingNight)); + } else ichatmutablecomponent = Component.translatable("sleep.skipping_night"); } else { + if (org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.isBlank()) { + return; + } + if (!org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.equalsIgnoreCase("default")) { + ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent, + net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("count", Integer.toString(this.sleepStatus.amountSleeping())), + net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("total", Integer.toString(this.sleepStatus.sleepersNeeded(i))))); + } else + // Purpur end ichatmutablecomponent = Component.translatable("sleep.players_sleeping", this.sleepStatus.amountSleeping(), this.sleepStatus.sleepersNeeded(i)); } @@ -1215,6 +1295,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe @VisibleForTesting public void resetWeatherCycle() { // CraftBukkit start + if (this.purpurConfig.rainStopsAfterSleep) // Purpur this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents // If we stop due to everyone sleeping we should reset the weather duration to some other random value. // Not that everyone ever manages to get the whole server to sleep at the same time.... @@ -1222,6 +1303,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe this.serverLevelData.setRainTime(0); } // CraftBukkit end + if (this.purpurConfig.thunderStopsAfterSleep) // Purpur this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents // CraftBukkit start // If we stop due to everyone sleeping we should reset the weather duration to some other random value. @@ -2762,7 +2844,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe // Spigot Start if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message // Paper start - Fix merchant inventory not closing on entity removal - if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { + if (!entity.level().purpurConfig.playerVoidTrading && entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { // Purpur merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); } // Paper end - Fix merchant inventory not closing on entity removal diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java index 5a8f396d47577f087abb415c972fd4f51e50faba..e6001fadc04b63b82e20652bbb16cb1db5cf44bd 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -329,6 +329,10 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent public @Nullable String clientBrandName = null; // Paper - Brand support public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event + public boolean purpurClient = false; // Purpur + private boolean tpsBar = false; // Purpur + private boolean compassBar = false; // Purpur + private boolean ramBar = false; // Purpur // Paper start - rewrite chunk system private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; @@ -691,6 +695,9 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple }); } + if (nbt.contains("Purpur.TPSBar")) { this.tpsBar = nbt.getBoolean("Purpur.TPSBar"); } // Purpur + if (nbt.contains("Purpur.CompassBar")) { this.compassBar = nbt.getBoolean("Purpur.CompassBar"); } // Purpur + if (nbt.contains("Purpur.RamBar")) { this.ramBar = nbt.getBoolean("Purpur.RamBar"); } // Purpur } @Override @@ -743,6 +750,9 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple } this.saveEnderPearls(nbt); + nbt.putBoolean("Purpur.RamBar", this.ramBar); // Purpur + nbt.putBoolean("Purpur.TPSBar", this.tpsBar); // Purpur + nbt.putBoolean("Purpur.CompassBar", this.compassBar); // Purpur } private void saveParentVehicle(CompoundTag nbt) { @@ -1032,6 +1042,15 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple this.trackEnteredOrExitedLavaOnVehicle(); this.updatePlayerAttributes(); 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 } private void updatePlayerAttributes() { @@ -1329,6 +1348,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple })); PlayerTeam scoreboardteam = this.getTeam(); + if (org.purpurmc.purpur.PurpurConfig.deathMessageOnlyBroadcastToAffectedPlayer) this.sendSystemMessage(ichatbasecomponent); else // Purpur if (scoreboardteam != null && scoreboardteam.getDeathMessageVisibility() != Team.Visibility.ALWAYS) { if (scoreboardteam.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) { this.server.getPlayerList().broadcastSystemToTeam(this, ichatbasecomponent); @@ -1432,6 +1452,16 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple if (this.isInvulnerableTo(world, 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)) { @@ -1664,6 +1694,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); this.unsetRemoved(); // CraftBukkit end + this.portalPos = io.papermc.paper.util.MCUtil.toBlockPosition(exit); // Purpur - Fix stuck in portals this.setServerLevel(worldserver); this.connection.internalTeleport(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives()); // CraftBukkit - use internal teleport without event this.connection.resetPosition(); @@ -1774,7 +1805,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple return entitymonster.isPreventingPlayerRest(this.serverLevel(), this); }); - if (!list.isEmpty()) { + if (!this.level().purpurConfig.playerSleepNearMonsters && !list.isEmpty()) { // Purpur return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_SAFE); } } @@ -1814,7 +1845,19 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple }); if (!this.serverLevel().canSleepThroughNights()) { - this.displayClientMessage(Component.translatable("sleep.not_possible"), true); + // Purpur start + Component clientMessage; + if (org.purpurmc.purpur.PurpurConfig.sleepNotPossible.isBlank()) { + clientMessage = null; + } else if (!org.purpurmc.purpur.PurpurConfig.sleepNotPossible.equalsIgnoreCase("default")) { + clientMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepNotPossible)); + } else { + clientMessage = Component.translatable("sleep.not_possible"); + } + if (clientMessage != null) { + this.displayClientMessage(clientMessage, true); + } + // Purpur end } ((ServerLevel) this.level()).updateSleepingPlayerList(); @@ -1936,6 +1979,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple @Override public void openTextEdit(SignBlockEntity sign, boolean front) { + if (level().purpurConfig.signAllowColors) this.connection.send(sign.getTranslatedUpdatePacket(textFilteringEnabled, front)); // Purpur this.connection.send(new ClientboundBlockUpdatePacket(this.level(), sign.getBlockPos())); this.connection.send(new ClientboundOpenSignEditorPacket(sign.getBlockPos(), front)); } @@ -2252,6 +2296,26 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple 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); @@ -2476,6 +2540,20 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple return new CommandSourceStack(this.commandSource(), this.position(), this.getRotationVector(), this.serverLevel(), this.getPermissionLevel(), this.getName().getString(), this.getDisplayName(), this.server, this); } + // Purpur Start + 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 + public void sendSystemMessage(Component message) { this.sendSystemMessage(message, false); } @@ -2597,8 +2675,68 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple public void resetLastActionTime() { this.lastActionTime = Util.getMillis(); + this.setAfk(false); // Purpur } + // Purpur Start + private boolean isAfk = false; + + @Override + public void setAfk(boolean afk) { + if (this.isAfk == afk) { + return; + } + + String msg = afk ? org.purpurmc.purpur.PurpurConfig.afkBroadcastAway : org.purpurmc.purpur.PurpurConfig.afkBroadcastBack; + + org.purpurmc.purpur.event.PlayerAFKEvent event = new org.purpurmc.purpur.event.PlayerAFKEvent(this.getBukkitEntity(), afk, this.level().purpurConfig.idleTimeoutKick, msg, !Bukkit.isPrimaryThread()); + if (!event.callEvent() || event.shouldKick()) { + return; + } + + this.isAfk = afk; + + if (!afk) { + resetLastActionTime(); + } + + msg = event.getBroadcastMsg(); + if (msg != null && !msg.isEmpty()) { + String playerName = this.getGameProfile().getName(); + if (org.purpurmc.purpur.PurpurConfig.afkBroadcastUseDisplayName) { + net.kyori.adventure.text.Component playerDisplayNameComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(this.getBukkitEntity().getDisplayName()); + playerName = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText().serialize(playerDisplayNameComponent); + } + server.getPlayerList().broadcastMiniMessage(String.format(msg, playerName), false); + } + + if (this.level().purpurConfig.idleTimeoutUpdateTabList) { + String scoreboardName = getScoreboardName(); + String playerListName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().serialize(getBukkitEntity().playerListName()); + String[] split = playerListName.split(scoreboardName); + String prefix = (split.length > 0 ? split[0] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix, ""); + String suffix = (split.length > 1 ? split[1] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, ""); + if (afk) { + getBukkitEntity().setPlayerListName(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix + prefix + scoreboardName + suffix + org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, true); + } else { + getBukkitEntity().setPlayerListName(prefix + scoreboardName + suffix, true); + } + } + + ((ServerLevel) this.level()).updateSleepingPlayerList(); + } + + @Override + public boolean isAfk() { + return this.isAfk; + } + + @Override + public boolean canBeCollidedWith() { + return !this.isAfk() && super.canBeCollidedWith(); + } + // Purpur End + public ServerStatsCounter getStats() { return this.stats; } @@ -3305,4 +3443,50 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple return (CraftPlayer) super.getBukkitEntity(); } // CraftBukkit end + + // Purpur start + public void teleport(Location to) { + this.ejectPassengers(); + this.stopRiding(true); + + if (this.isSleeping()) { + this.stopSleepInBed(true, false); + } + + if (this.containerMenu != this.inventoryMenu) { + this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT); + } + + ServerLevel toLevel = ((CraftWorld) to.getWorld()).getHandle(); + if (this.level() == toLevel) { + this.connection.teleport(to); + } else { + this.server.getPlayerList().respawn(this, true, RemovalReason.KILLED, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH, to); + } + } + + public boolean tpsBar() { + return this.tpsBar; + } + + public void tpsBar(boolean tpsBar) { + this.tpsBar = tpsBar; + } + + public boolean compassBar() { + return this.compassBar; + } + + public void compassBar(boolean compassBar) { + this.compassBar = compassBar; + } + + public boolean ramBar() { + return this.ramBar; + } + + public void ramBar(boolean ramBar) { + this.ramBar = ramBar; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java index a96f859a5d0c6ec692d4627a69f3c9ee49199dbc..88eb3774f688bcff383efa7f113bd0b1b97d8a11 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java @@ -403,6 +403,7 @@ public class ServerPlayerGameMode { } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction return false; } + if (this.player.level().purpurConfig.slabHalfBreak && this.player.isShiftKeyDown() && iblockdata.getBlock() instanceof net.minecraft.world.level.block.SlabBlock && ((net.minecraft.world.level.block.SlabBlock) iblockdata.getBlock()).halfBreak(iblockdata, pos, this.player)) return true; // Purpur } // CraftBukkit end @@ -523,6 +524,7 @@ public class ServerPlayerGameMode { public InteractionHand interactHand; public ItemStack interactItemStack; public InteractionResult useItemOn(ServerPlayer player, Level world, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) { + if (shiftClickMended(stack)) return InteractionResult.SUCCESS; // Purpur BlockPos blockposition = hitResult.getBlockPos(); BlockState iblockdata = world.getBlockState(blockposition); boolean cancelledBlock = false; @@ -583,7 +585,7 @@ public class ServerPlayerGameMode { ItemStack itemstack1 = stack.copy(); InteractionResult enuminteractionresult; - if (!flag1) { + if (!flag1 || (player.level().purpurConfig.composterBulkProcess && iblockdata.is(Blocks.COMPOSTER))) { // Purpur InteractionResult enuminteractionresult1 = iblockdata.useItemOn(player.getItemInHand(hand), world, player, hand, hitResult); if (enuminteractionresult1.consumesAction()) { @@ -631,4 +633,18 @@ public class ServerPlayerGameMode { public void setLevel(ServerLevel world) { this.level = world; } + + // Purpur start + public boolean shiftClickMended(ItemStack itemstack) { + if (this.player.level().purpurConfig.shiftRightClickRepairsMendingPoints > 0 && this.player.isShiftKeyDown() && this.player.getBukkitEntity().hasPermission("purpur.mending_shift_click")) { + int points = Math.min(this.player.totalExperience, this.player.level().purpurConfig.shiftRightClickRepairsMendingPoints); + if (points > 0 && itemstack.isDamaged() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MENDING, itemstack) > 0) { + this.player.giveExperiencePoints(-points); + this.player.level().addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(this.player.level(), this.player.getX(), this.player.getY(), this.player.getZ(), points, org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN, this.player, this.player)); + return true; + } + } + return false; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java index e4b0dc3121101d54394a0c3a413dabf8103b2ea6..a8484b9659f175cc20985bf66082616ceb31df4d 100644 --- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java +++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java @@ -336,6 +336,7 @@ public class WorldGenRegion implements WorldGenLevel { return true; } else { // Paper start - Buffer OOB setBlock calls + if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressSetBlockFarChunk) // Purpur if (!hasSetFarWarned) { Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + String.valueOf(pos) + ", status: " + String.valueOf(this.generatingStep.targetStatus()) + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get())); hasSetFarWarned = true; diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java index b0bc66dc7248aae691dcab68b925b52a1695e63f..7d276c191b391bca24948ddb36b8b7d0f1f03b03 100644 --- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java @@ -80,11 +80,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack private long keepAliveChallenge; private long closedListenerTime; private boolean closed = false; + private it.unimi.dsi.fastutil.longs.LongList keepAlives = new it.unimi.dsi.fastutil.longs.LongArrayList(); // Purpur private int latency; private volatile boolean suspendFlushingOnServerThread = false; public final java.util.Map packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support + protected static final ResourceLocation PURPUR_CLIENT = ResourceLocation.fromNamespaceAndPath("purpur", "client"); // Purpur public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit this.server = minecraftserver; @@ -136,6 +138,16 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack @Override public void handleKeepAlive(ServerboundKeepAlivePacket packet) { + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.useAlternateKeepAlive) { + if (this.keepAlivePending && !keepAlives.isEmpty() && keepAlives.contains(packet.getId())) { + int ping = (int) (Util.getMillis() - packet.getId()); + this.latency = (this.latency * 3 + ping) / 4; + this.keepAlivePending = false; + keepAlives.clear(); // we got a valid response, lets roll with it and forget the rest + } + } else + // Purpur end //PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // CraftBukkit // Paper - handle ServerboundKeepAlivePacket async if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) { int i = (int) (Util.getMillis() - this.keepAliveTime); @@ -179,6 +191,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause } + // Purpur start + } else if (identifier.equals(PURPUR_CLIENT)) { + try { + player.purpurClient = true; + } catch (Exception ignore) { + } + // Purpur end } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) { try { String channels = payload.toString(com.google.common.base.Charsets.UTF_8); @@ -261,6 +280,21 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack long currentTime = Util.getMillis(); long elapsedTime = currentTime - this.keepAliveTime; + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.useAlternateKeepAlive) { + if (elapsedTime >= 1000L) { // 1 second + if (this.keepAlivePending && !this.processedDisconnect && keepAlives.size() * 1000L >= KEEPALIVE_LIMIT) { + this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); + } else if (this.checkIfClosed(currentTime)) { + this.keepAlivePending = true; + this.keepAliveTime = currentTime; // hijack this field for 1 second intervals + this.keepAlives.add(currentTime); // currentTime is ID + this.send(new ClientboundKeepAlivePacket(currentTime)); + } + } + } else + // Purpur end + if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // Paper - use vanilla's 15000L between keep alive packets if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // Paper - check keepalive limit, don't fire if already disconnected this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java index cd1b6b539a62fa5237d6dab2d1c09a2e631d9941..9ae16e24013ea5664d4ea6d52ed7dd188404890f 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -342,6 +342,20 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl private boolean justTeleported = false; // CraftBukkit end + // 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 + @Override public void tick() { if (this.ackBlockChangesUpTo > -1) { @@ -398,6 +412,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl this.recipeSpamPackets.tick(); // Paper - auto recipe limit this.dropSpamThrottler.tick(); if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits + // Purpur start + 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) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause } @@ -663,6 +683,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl this.lastYaw = to.getYaw(); this.lastPitch = to.getPitch(); + if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur + Location oldTo = to.clone(); PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); this.cserver.getPluginManager().callEvent(event); @@ -739,6 +761,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); if (packet.getId() == this.awaitingTeleport) { if (this.awaitingPositionFromClient == null) { + ServerGamePacketListenerImpl.LOGGER.warn("Disconnected on accept teleport packet. Was not expecting position data from client at this time"); // Purpur this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause return; } @@ -1172,6 +1195,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl @Override public void handleEditBook(ServerboundEditBookPacket packet) { + if (!gg.pufferfish.pufferfish.PufferfishConfig.enableBooks && !this.player.getBukkitEntity().hasPermission("pufferfish.usebooks")) return; // Pufferfish // Paper start - Book size limits final io.papermc.paper.configuration.type.number.IntOr.Disabled pageMax = io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.pageMax; if (!this.cserver.isPrimaryThread() && pageMax.enabled()) { @@ -1180,6 +1204,10 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl final int maxBookPageSize = pageMax.intValue(); final double multiplier = Math.clamp(io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier, 0.3D, 1D); long byteAllowed = maxBookPageSize; + // Purpur start + int slot = packet.slot(); + ItemStack itemstack = Inventory.isHotbarSlot(slot) || slot == Inventory.SLOT_OFFHAND ? this.player.getInventory().getItem(slot) : ItemStack.EMPTY; + // Purpur end for (final String page : pageList) { final int byteLength = page.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; byteTotal += byteLength; @@ -1204,7 +1232,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } if (byteTotal > byteAllowed) { - ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send too large of a book. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size()); + org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur this.disconnectAsync(Component.literal("Book too large!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect return; } @@ -1226,10 +1255,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl Objects.requireNonNull(list); optional.ifPresent(list::add); list.addAll(packet.pages()); + // Purpur start + boolean hasEditPerm = getCraftPlayer().hasPermission("purpur.book.color.edit"); + boolean hasSignPerm = hasEditPerm || getCraftPlayer().hasPermission("purpur.book.color.sign"); + // Purpur end Consumer> consumer = optional.isPresent() ? (list1) -> { - this.signBook((FilteredText) list1.get(0), list1.subList(1, list1.size()), i); + this.signBook((FilteredText) list1.get(0), list1.subList(1, list1.size()), i, hasSignPerm); // Purpur } : (list1) -> { - this.updateBookContents(list1, i); + this.updateBookContents(list1, i, hasEditPerm); // Purpur }; this.filterTextPacket((List) list).thenAcceptAsync(consumer, this.server); @@ -1237,13 +1270,18 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } private void updateBookContents(List pages, int slotId) { + // Purpur start + updateBookContents(pages, slotId, false); + } + private void updateBookContents(List pages, int slotId, boolean hasPerm) { + // Purpur end // CraftBukkit start ItemStack handItem = this.player.getInventory().getItem(slotId); ItemStack itemstack = handItem.copy(); // CraftBukkit end if (itemstack.has(DataComponents.WRITABLE_BOOK_CONTENT)) { - List> list1 = pages.stream().map(this::filterableFromOutgoing).toList(); + List> list1 = pages.stream().map(filteredText -> filterableFromOutgoing(filteredText).map(s -> color(s, hasPerm))).toList(); // Purpur itemstack.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list1)); this.player.getInventory().setItem(slotId, CraftEventFactory.handleEditBookEvent(this.player, slotId, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) @@ -1251,6 +1289,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } private void signBook(FilteredText title, List pages, int slotId) { + // Purpur start + signBook(title, pages, slotId, false); + } + private void signBook(FilteredText title, List pages, int slotId, boolean hasPerm) { + // Purpur end ItemStack itemstack = this.player.getInventory().getItem(slotId); if (itemstack.has(DataComponents.WRITABLE_BOOK_CONTENT)) { @@ -1258,10 +1301,10 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl itemstack1.remove(DataComponents.WRITABLE_BOOK_CONTENT); List> list1 = (List>) (List) pages.stream().map((filteredtext1) -> { // CraftBukkit - decompile error - return this.filterableFromOutgoing(filteredtext1).map(Component::literal); + return this.filterableFromOutgoing(filteredtext1).map(s -> hexColor(s, hasPerm)); // Purpur }).toList(); - itemstack1.set(DataComponents.WRITTEN_BOOK_CONTENT, new WrittenBookContent(this.filterableFromOutgoing(title), this.player.getName().getString(), 0, list1, true)); + itemstack1.set(DataComponents.WRITTEN_BOOK_CONTENT, new WrittenBookContent(this.filterableFromOutgoing(title).map(s -> color(s, hasPerm)), this.player.getName().getString(), 0, list1, true)); // Purpur CraftEventFactory.handleEditBookEvent(this.player, slotId, itemstack, itemstack1); // CraftBukkit this.player.getInventory().setItem(slotId, itemstack); // CraftBukkit - event factory updates the hand book } @@ -1271,6 +1314,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl return this.player.isTextFilteringEnabled() ? Filterable.passThrough(message.filteredOrEmpty()) : Filterable.from(message); } + // Purpur start + private Component hexColor(String str, boolean hasPerm) { + return hasPerm ? PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().deserialize(str)) : Component.literal(str); + } + + private String color(String str, boolean hasPerm) { + return hasPerm ? org.bukkit.ChatColor.color(str, false) : str; + } + // Purpur end + @Override public void handleEntityTagQuery(ServerboundEntityTagQueryPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); @@ -1320,7 +1373,15 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl @Override public void handleMovePlayer(ServerboundMovePlayerPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) { + // Purpur start + boolean invalidX = Double.isNaN(packet.getX(0.0D)); + boolean invalidY = Double.isNaN(packet.getY(0.0D)); + boolean invalidZ = Double.isNaN(packet.getZ(0.0D)); + boolean invalidYaw = !Floats.isFinite(packet.getYRot(0.0F)); + boolean invalidPitch = !Floats.isFinite(packet.getXRot(0.0F)); + if (invalidX || invalidY || invalidZ || invalidYaw || invalidPitch) { + ServerGamePacketListenerImpl.LOGGER.warn(String.format("Disconnected on move player packet. Invalid data: x=%b, y=%b, z=%b, yaw=%b, pitch=%b", invalidX, invalidY, invalidZ, invalidYaw, invalidPitch)); // Purpur + // Purpur end this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause } else { ServerLevel worldserver = this.player.serverLevel(); @@ -1500,7 +1561,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl movedWrongly = true; if (event.getLogWarning()) // Paper end - ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); + ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!, ({})", this.player.getName().getString(), d11); // Purpur } // Paper } @@ -1568,6 +1629,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl this.lastYaw = to.getYaw(); this.lastPitch = to.getPitch(); + if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur + Location oldTo = to.clone(); PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); this.cserver.getPluginManager().callEvent(event); @@ -1613,6 +1676,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl this.player.tryResetCurrentImpulseContext(); } + // 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(); @@ -1652,6 +1722,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } } + // Purpur start + public boolean isScissor(ItemStack stack) { + if (!stack.is(Items.SHEARS)) return false; + net.minecraft.world.item.component.CustomModelData customModelData = stack.get(net.minecraft.core.component.DataComponents.CUSTOM_MODEL_DATA); + return customModelData == null || customModelData.value() == 0; + } + // Purpur end + // Paper start - optimise out extra getCubes private boolean hasNewCollision(final ServerLevel world, final Entity entity, final AABB oldBox, final AABB newBox) { final List collisionsBB = new java.util.ArrayList<>(); @@ -2025,6 +2103,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl boolean cancelled; if (movingobjectposition == null || movingobjectposition.getType() != HitResult.Type.BLOCK) { + if (this.player.gameMode.shiftClickMended(itemstack)) return; // Purpur org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack, enumhand); cancelled = event.useItemInHand() == Event.Result.DENY; } else { @@ -2808,6 +2887,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl AABB axisalignedbb = entity.getBoundingBox(); if (this.player.canInteractWithEntity(axisalignedbb, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0D))) { // Paper - configurable lenience value for interact range + if (entity instanceof Mob mob) mob.ticksSinceLastInteraction = 0; // Purpur packet.dispatch(new ServerboundInteractPacket.Handler() { private void performInteraction(InteractionHand enumhand, ServerGamePacketListenerImpl.EntityInteraction playerconnection_a, PlayerInteractEntityEvent event) { // CraftBukkit ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand); @@ -2821,6 +2901,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl 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.resendPossiblyDesyncedEntityData(ServerGamePacketListenerImpl.this.player); // Paper - The entire mob gets deleted, so resend it diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java index 033755682c61c889723c3669b5cff4de147f637e..16069b9cbf6c7679c28a2e9a54e77d23cd10e541 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -322,7 +322,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!"); ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(s1)); // Spigot } else { - ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username")); + ServerLoginPacketListenerImpl.this.disconnect(org.purpurmc.purpur.PurpurConfig.unverifiedUsername.equals("default") ? Component.translatable("multiplayer.disconnect.unverified_username") : io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.unverifiedUsername))); // Purpur ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", s1); } } catch (AuthenticationUnavailableException authenticationunavailableexception) { diff --git a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java index 532f09089b8d6798999cf3f83e852df7479e450e..43c63d203859eaa0999937e2f9254c22510139aa 100644 --- a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java @@ -154,6 +154,7 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene this.connection.send(new ClientboundStatusResponsePacket(ping)); // CraftBukkit end */ + if (MinecraftServer.getServer().getStatus().version().isEmpty()) return; // Purpur - do not respond to pings before we know the protocol version com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(MinecraftServer.getServer(), this.connection); // Paper end } diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index 1a956249828156fdc273888de59128c3d1a0b898..16999fa20c3621e016d1b0fadfb76f2fe03a2a69 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -419,6 +419,7 @@ public abstract class PlayerList { scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); } // Paper end - Configurable player collision + org.purpurmc.purpur.task.BossBarTask.addToAll(player); // Purpur PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); // Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead if (player.isDeadOrDying()) { @@ -540,6 +541,7 @@ public abstract class PlayerList { } public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { // Paper end - Fix kick event leave message not being sent + org.purpurmc.purpur.task.BossBarTask.removeFromAll(entityplayer.getBukkitEntity()); // Purpur ServerLevel worldserver = entityplayer.serverLevel(); entityplayer.awardStat(Stats.LEAVE_GAME); @@ -709,7 +711,7 @@ public abstract class PlayerList { event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure } else { // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null; - if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile)) { + if (this.players.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameprofile))) { // Purpur event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure } } @@ -991,6 +993,20 @@ public abstract class PlayerList { } // CraftBukkit end + // Purpur Start + 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(); @@ -1094,6 +1110,7 @@ public abstract class PlayerList { } else { b0 = (byte) (24 + permissionLevel); } + if (b0 < 28 && player.getBukkitEntity().hasPermission("purpur.debug.f3n")) b0 = 28; // Purpur player.connection.send(new ClientboundEntityEventPacket(player, b0)); } @@ -1102,6 +1119,27 @@ public abstract class PlayerList { player.getBukkitEntity().recalculatePermissions(); // CraftBukkit this.server.getCommands().sendCommands(player); } // Paper - Add sendOpLevel API + + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.enderChestSixRows && org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) { + org.bukkit.craftbukkit.entity.CraftHumanEntity bukkit = player.getBukkitEntity(); + if (bukkit.hasPermission("purpur.enderchest.rows.six")) { + player.sixRowEnderchestSlotCount = 54; + } else if (bukkit.hasPermission("purpur.enderchest.rows.five")) { + player.sixRowEnderchestSlotCount = 45; + } else if (bukkit.hasPermission("purpur.enderchest.rows.four")) { + player.sixRowEnderchestSlotCount = 36; + } else if (bukkit.hasPermission("purpur.enderchest.rows.three")) { + player.sixRowEnderchestSlotCount = 27; + } else if (bukkit.hasPermission("purpur.enderchest.rows.two")) { + player.sixRowEnderchestSlotCount = 18; + } else if (bukkit.hasPermission("purpur.enderchest.rows.one")) { + player.sixRowEnderchestSlotCount = 9; + } + } else { + player.sixRowEnderchestSlotCount = -1; + } + //Purpur end } public boolean isWhiteListed(GameProfile profile) { diff --git a/src/main/java/net/minecraft/server/players/SleepStatus.java b/src/main/java/net/minecraft/server/players/SleepStatus.java index 823efad652d8ff9e96b99375b102fef6f017716e..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 5c7484ce2850a2eb698a2183b81134b89b0bbcc7..fef09e71f73f6f24a01289ef05c20a5ffc5fadb1 100644 --- a/src/main/java/net/minecraft/stats/ServerRecipeBook.java +++ b/src/main/java/net/minecraft/stats/ServerRecipeBook.java @@ -159,6 +159,7 @@ public class ServerRecipeBook extends RecipeBook { ResourceKey> resourcekey = ResourceKey.create(Registries.RECIPE, ResourceLocation.parse(s)); if (!validPredicate.test(resourcekey)) { + if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressUnrecognizedRecipeErrors) // Purpur ServerRecipeBook.LOGGER.error("Tried to load unrecognized recipe: {} removed now.", resourcekey); } else { handler.accept(resourcekey); diff --git a/src/main/java/net/minecraft/util/StringUtil.java b/src/main/java/net/minecraft/util/StringUtil.java index 6c33002dc8bbb3759c3156302ab7d1f26ce5e8ee..c89fc375aff548a2b03eaf4da3b6a075012df012 100644 --- a/src/main/java/net/minecraft/util/StringUtil.java +++ b/src/main/java/net/minecraft/util/StringUtil.java @@ -69,6 +69,7 @@ public class StringUtil { // Paper start - Username validation public static boolean isReasonablePlayerName(final String name) { + if (true) return org.purpurmc.purpur.PurpurConfig.usernameValidCharactersPattern.matcher(name).matches(); // Purpur if (name.isEmpty() || name.length() > 16) { return false; } diff --git a/src/main/java/net/minecraft/world/damagesource/CombatRules.java b/src/main/java/net/minecraft/world/damagesource/CombatRules.java index 064c1e33f3feee77837bb57887877ae1ca39548d..ffd009bca3fdbfd0b14df78072ef8d472a57cd65 100644 --- a/src/main/java/net/minecraft/world/damagesource/CombatRules.java +++ b/src/main/java/net/minecraft/world/damagesource/CombatRules.java @@ -15,7 +15,7 @@ public class CombatRules { public static float getDamageAfterAbsorb(LivingEntity armorWearer, float damageAmount, DamageSource damageSource, float armor, float armorToughness) { float f = 2.0F + armorToughness / 4.0F; - float g = Mth.clamp(armor - damageAmount / f, armor * 0.2F, 20.0F); + float g = Mth.clamp(armor - damageAmount / f, armor * 0.2F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur float h = g / 25.0F; ItemStack itemStack = damageSource.getWeaponItem(); float i; @@ -30,7 +30,7 @@ public class CombatRules { } public static float getDamageAfterMagicAbsorb(float damageDealt, float protection) { - float f = Mth.clamp(protection, 0.0F, 20.0F); + float f = Mth.clamp(protection, 0.0F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur return damageDealt * (1.0F - f / 25.0F); } } diff --git a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java index 99a7e9eb75231c15bd8bb24fbb4e296bc9fdedff..4fb025a63628eb60509d90b680922a0220104bcb 100644 --- a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java +++ b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java @@ -54,7 +54,7 @@ public class CombatTracker { private Component getMessageForAssistedFall(Entity attacker, Component attackerDisplayName, String itemDeathTranslationKey, String deathTranslationKey) { ItemStack itemStack = attacker instanceof LivingEntity livingEntity ? livingEntity.getMainHandItem() : ItemStack.EMPTY; - return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME) + return !itemStack.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemStack.has(DataComponents.CUSTOM_NAME)) // Purpur ? Component.translatable(itemDeathTranslationKey, this.mob.getDisplayName(), attackerDisplayName, itemStack.getDisplayName()) : Component.translatable(deathTranslationKey, this.mob.getDisplayName(), attackerDisplayName); } @@ -98,6 +98,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 bb1a60180e58c1333e7bb33e8acf1b0225eda8a8..4a96d914f8aa6f0c5f13fc85369a311f25835ac2 100644 --- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java +++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java @@ -29,6 +29,8 @@ public class DamageSource { private boolean sweep = false; private boolean melting = false; private boolean poison = false; + private boolean scissors = false; // Purpur + private boolean stonecutter = false; // Purpur @Nullable private Entity customEventDamager = null; // This field is a helper for when causing entity damage is not set by vanilla // Paper - fix DamageSource API @@ -59,6 +61,26 @@ public class DamageSource { return this.poison; } + // Purpur start + public DamageSource scissors() { + this.scissors = true; + return this; + } + + public boolean isScissors() { + return this.scissors; + } + + public DamageSource stonecutter() { + this.stonecutter = true; + return this; + } + + public boolean isStonecutter() { + return this.stonecutter; + } + // Purpur end + // Paper start - fix DamageSource API @Nullable public Entity getCustomEventDamager() { @@ -117,6 +139,8 @@ public class DamageSource { damageSource.sweep = this.isSweep(); damageSource.poison = this.isPoison(); damageSource.melting = this.isMelting(); + damageSource.scissors = this.isScissors(); // Purpur + damageSource.stonecutter = this.isStonecutter(); // Purpur return damageSource; } // CraftBukkit end @@ -194,10 +218,19 @@ public class DamageSource { ItemStack itemstack1 = itemstack; - return !itemstack1.isEmpty() && itemstack1.has(DataComponents.CUSTOM_NAME) ? Component.translatable(s + ".item", killed.getDisplayName(), ichatbasecomponent, itemstack1.getDisplayName()) : Component.translatable(s, killed.getDisplayName(), ichatbasecomponent); + return !itemstack1.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemstack1.has(DataComponents.CUSTOM_NAME)) ? Component.translatable(s + ".item", killed.getDisplayName(), ichatbasecomponent, itemstack1.getDisplayName()) : Component.translatable(s, killed.getDisplayName(), ichatbasecomponent); } } + // Purpur start + 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 be87cb3cfa15a7d889118cdc4b87232e30749023..d343fd5c9f31073f1b3a0f91b8ea61a67077cc12 100644 --- a/src/main/java/net/minecraft/world/damagesource/DamageSources.java +++ b/src/main/java/net/minecraft/world/damagesource/DamageSources.java @@ -46,11 +46,15 @@ public class DamageSources { // CraftBukkit start private final DamageSource melting; private final DamageSource poison; + private final DamageSource scissors; // Purpur + private final DamageSource stonecutter; // Purpur public DamageSources(RegistryAccess registryManager) { this.damageTypes = registryManager.lookupOrThrow(Registries.DAMAGE_TYPE); this.melting = this.source(DamageTypes.ON_FIRE).melting(); this.poison = this.source(DamageTypes.MAGIC).poison(); + this.scissors = this.source(DamageTypes.MAGIC).scissors(); // Purpur + this.stonecutter = this.source(DamageTypes.MAGIC).stonecutter(); // Purpur // CraftBukkit end this.inFire = this.source(DamageTypes.IN_FIRE); this.campfire = this.source(DamageTypes.CAMPFIRE); @@ -101,6 +105,15 @@ public class DamageSources { } // CraftBukkit end + // Purpur start + public DamageSource scissors() { + return this.scissors; + } + public DamageSource stonecutter() { + return this.stonecutter; + } + // Purpur end + public DamageSource inFire() { return this.inFire; } diff --git a/src/main/java/net/minecraft/world/effect/HungerMobEffect.java b/src/main/java/net/minecraft/world/effect/HungerMobEffect.java index 11d1ee8fae7670f02cb3f5d57f4774dbde77f48c..88cf1353892a7ead4e0f16822216b151726ac0e4 100644 --- a/src/main/java/net/minecraft/world/effect/HungerMobEffect.java +++ b/src/main/java/net/minecraft/world/effect/HungerMobEffect.java @@ -13,7 +13,7 @@ class HungerMobEffect extends MobEffect { @Override public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { if (entity instanceof Player entityhuman) { - entityhuman.causeFoodExhaustion(0.005F * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent + entityhuman.causeFoodExhaustion(entity.level().purpurConfig.humanHungerExhaustionAmount * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent // Purpur } return true; diff --git a/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java b/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java index 83c6d17f75c3f0b531bdfd5b5f9bc2a5b3eb7a2c..df43c007e7b9fee58b2cd0892b966ce88cb1f890 100644 --- a/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java +++ b/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java @@ -11,8 +11,8 @@ class PoisonMobEffect extends MobEffect { @Override public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { - if (entity.getHealth() > 1.0F) { - entity.hurtServer(world, entity.damageSources().poison(), 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON + if (entity.getHealth() > entity.level().purpurConfig.entityMinimalHealthPoison) { // Purpur + entity.hurtServer(world, entity.damageSources().poison(), entity.level().purpurConfig.entityPoisonDegenerationAmount); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON // Purpur } return true; diff --git a/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java b/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java index b43e573e91d1f1b407774bb2d60ee5f099f171e7..25aa932fc03eeebc5aabca6c5eae0cbfc8ad8396 100644 --- a/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java +++ b/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java @@ -12,7 +12,7 @@ class RegenerationMobEffect extends MobEffect { @Override public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { if (entity.getHealth() < entity.getMaxHealth()) { - entity.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit + entity.heal(entity.level().purpurConfig.entityHealthRegenAmount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit // Purpur } return true; diff --git a/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java b/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java index 98b74649a667fb9b10afef0ba5383a73022d8c71..0c7c0524e487ff32e16dd9939d92bc6441602747 100644 --- a/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java +++ b/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java @@ -21,7 +21,8 @@ class SaturationMobEffect extends InstantenousMobEffect { int oldFoodLevel = entityhuman.getFoodData().foodLevel; org.bukkit.event.entity.FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(entityhuman, amplifier + 1 + oldFoodLevel); if (!event.isCancelled()) { - entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F); + if (entityhuman.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) entityhuman.burpDelay = entityhuman.level().purpurConfig.playerBurpDelay; // Purpur + entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, entity.level().purpurConfig.humanSaturationRegenAmount); // Purpur } ((CraftPlayer) entityhuman.getBukkitEntity()).sendHealthUpdate(); diff --git a/src/main/java/net/minecraft/world/effect/WitherMobEffect.java b/src/main/java/net/minecraft/world/effect/WitherMobEffect.java index 303cefba51e19ac43b1f6188ad64ef480715ebaf..98ec88751b3e71c2e7aad633096b7f41608c0b33 100644 --- a/src/main/java/net/minecraft/world/effect/WitherMobEffect.java +++ b/src/main/java/net/minecraft/world/effect/WitherMobEffect.java @@ -10,7 +10,7 @@ class WitherMobEffect extends MobEffect { @Override public boolean applyEffectTick(ServerLevel world, LivingEntity entity, int amplifier) { - entity.hurtServer(world, entity.damageSources().wither(), 1.0F); + entity.hurtServer(world, entity.damageSources().wither(), entity.level().purpurConfig.entityWitherDegenerationAmount); // Purpur return true; } diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index a15546e433ebba6c0de01bdaaef201a3d99a87b5..aa171a1afb9800e6ec84f6b4f1b28e44177752f7 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -177,7 +177,7 @@ import org.bukkit.plugin.PluginManager; // CraftBukkit end public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity { // Paper - rewrite chunk system // Paper - optimise entity tracker - + public static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur // CraftBukkit start private static final int CURRENT_LEVEL = 2; public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation @@ -300,6 +300,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess public double xOld; public double yOld; public double zOld; + public float maxUpStep; // Purpur public boolean noPhysics; private boolean wasOnFire; public final RandomSource random; @@ -340,7 +341,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess private final Set tags; private final double[] pistonDeltas; private long pistonDeltasGameTime; - private EntityDimensions dimensions; + protected EntityDimensions dimensions; // Purpur - private -> protected private float eyeHeight; public boolean isInPowderSnow; public boolean wasInPowderSnow; @@ -389,6 +390,10 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess public boolean freezeLocked = false; // Paper - Freeze Tick Lock API public boolean fixedPose = false; // Paper - Expand Pose API private final int despawnTime; // Paper - entity despawn time limit + public boolean activatedPriorityReset = false; // Pufferfish - DAB + public int activatedPriority = gg.pufferfish.pufferfish.PufferfishConfig.maximumActivationPrio; // Pufferfish - DAB (golf score) + public final BlockPos.MutableBlockPos cachedBlockPos = new BlockPos.MutableBlockPos(); // Pufferfish - reduce entity allocations + public @Nullable Boolean immuneToFire = null; // Purpur - Fire immune API public void setOrigin(@javax.annotation.Nonnull Location location) { this.origin = location.toVector(); @@ -568,6 +573,27 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } } // Paper end - optimise entity tracker + // Purpur start + public boolean canSaveToDisk() { + return true; + } + // Purpur end + + // Purpur start - copied from Mob - API for any mob to burn daylight + public boolean isSunBurnTick() { + if (this.level().isDay() && !this.level().isClientSide) { + float f = this.getLightLevelDependentMagicValue(); + BlockPos blockposition = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); + boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; + + if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level().canSeeSky(blockposition)) { + return true; + } + } + + return false; + } + // Purpur end - copied from Mob - API for any mob to burn daylight public Entity(EntityType type, Level world) { this.id = Entity.ENTITY_COUNTER.incrementAndGet(); @@ -577,7 +603,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.bb = Entity.INITIAL_AABB; this.stuckSpeedMultiplier = Vec3.ZERO; this.nextStep = 1.0F; - this.random = SHARED_RANDOM; // Paper - Share random for entities to make them more random + this.random = world == null || world.purpurConfig.entitySharedRandom ? SHARED_RANDOM : RandomSource.create(); // Paper - Share random for entities to make them more random // Purpur this.remainingFireTicks = -this.getFireImmuneTicks(); this.fluidHeight = new Object2DoubleArrayMap(2); this.fluidOnEyes = new HashSet(); @@ -974,6 +1000,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v) && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) { // Paper end - Configurable nether ceiling damage + if (this.level().purpurConfig.teleportOnNetherCeilingDamage && this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER && this instanceof ServerPlayer player) player.teleport(io.papermc.paper.util.MCUtil.toLocation(this.level, this.level.getSharedSpawnPos())); else // Purpur this.onBelowWorld(); } @@ -1935,7 +1962,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public boolean fireImmune() { - return this.getType().fireImmune(); + return this.immuneToFire != null ? immuneToFire : this.getType().fireImmune(); // Purpur - add fire immune API } public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { @@ -2008,7 +2035,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return this.isInWater() || flag; } - void updateInWaterStateAndDoWaterCurrentPushing() { + public void updateInWaterStateAndDoWaterCurrentPushing() { // Purpur - package-private -> public Entity entity = this.getVehicle(); if (entity instanceof AbstractBoat abstractboat) { @@ -2690,6 +2717,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess nbttagcompound.putBoolean("Paper.FreezeLock", true); } // Paper end + // Purpur start + if (immuneToFire != null) { + nbttagcompound.putBoolean("Purpur.FireImmune", immuneToFire); + } + // Purpur end return nbttagcompound; } catch (Throwable throwable) { CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); @@ -2838,6 +2870,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess freezeLocked = nbt.getBoolean("Paper.FreezeLock"); } // Paper end + // Purpur start + if (nbt.contains("Purpur.FireImmune")) { + immuneToFire = nbt.getBoolean("Purpur.FireImmune"); + } + // Purpur end } catch (Throwable throwable) { CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT"); @@ -3089,6 +3126,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess if (this.isAlive() && this instanceof Leashable leashable) { if (leashable.getLeashHolder() == player) { if (!this.level().isClientSide()) { + if (hand == InteractionHand.OFF_HAND && (level().purpurConfig.villagerCanBeLeashed || level().purpurConfig.wanderingTraderCanBeLeashed) && this instanceof net.minecraft.world.entity.npc.AbstractVillager) return InteractionResult.CONSUME; // Purpur // CraftBukkit start - fire PlayerUnleashEntityEvent // Paper start - Expand EntityUnleashEvent org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials()); @@ -3294,6 +3332,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess 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); } } @@ -3333,6 +3378,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return false; } // CraftBukkit 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 { @@ -3413,14 +3466,17 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return Vec3.directionFromRotation(this.getRotationVector()); } + public BlockPos portalPos = BlockPos.ZERO; // Purpur public void setAsInsidePortal(Portal portal, BlockPos pos) { if (this.isOnPortalCooldown()) { + if (!(level().purpurConfig.playerFixStuckPortal && this instanceof Player && !pos.equals(this.portalPos))) // Purpur - Fix stuck in portals this.setPortalCooldown(); - } else { + } else if (this.level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer) { // Purpur - Entities can use portals if (this.portalProcess != null && this.portalProcess.isSamePortal(portal)) { if (!this.portalProcess.isInsidePortalThisTick()) { this.portalProcess.updateEntryPosition(pos.immutable()); this.portalProcess.setAsInsidePortalThisTick(true); + this.portalPos = BlockPos.ZERO; // Purpur - Fix stuck in portals } } else { this.portalProcess = new PortalProcessor(portal, pos.immutable()); @@ -3637,7 +3693,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public int getMaxAirSupply() { - return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() + return this.level == null? this.maxAirTicks : this.level().purpurConfig.drowningAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() // Purpur } public int getAirSupply() { @@ -4135,7 +4191,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess // CraftBukkit end public boolean canUsePortal(boolean allowVehicles) { - return (allowVehicles || !this.isPassenger()) && this.isAlive(); + return (allowVehicles || !this.isPassenger()) && this.isAlive() && (this.level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer); // Purpur - Entities can use portals } public boolean canTeleport(Level from, Level to) { @@ -4723,6 +4779,12 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return Mth.lerp(delta, this.yRotO, this.yRot); } + // Purpur start + public AABB getAxisForFluidCheck() { + return this.getBoundingBox().deflate(0.001D); + } + // Purpur end + // Paper start - optimise collisions public boolean updateFluidHeightAndDoFluidPushing(final TagKey fluid, final double flowScale) { if (this.touchingUnloadedChunk()) { @@ -5124,7 +5186,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } public float maxUpStep() { - return 0.0F; + return maxUpStep; } public void onExplosionHit(@Nullable Entity entity) {} @@ -5325,4 +5387,44 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess return ((net.minecraft.server.level.ServerLevel) this.level).isPositionEntityTicking(this.blockPosition()); } // Paper end - Expose entity id counter + // Purpur start + @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 6bf691fcc6486bde73bae30eff09142802c29eda..59c4d3753c7084e92402608b7fb3c4adbc6c2f65 100644 --- a/src/main/java/net/minecraft/world/entity/EntitySelector.java +++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java @@ -39,6 +39,7 @@ public final class EntitySelector { return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks; }; // Paper end - Ability to control player's insomnia and phantoms + public static Predicate notAfk = (player) -> !player.isAfk(); // Purpur private EntitySelector() {} // Paper start - Affects Spawning API diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java index c8c2394558952d7ca57d29874485251b8f2b3400..1d50b03246d114c6583815205eeacb7ac8549aec 100644 --- a/src/main/java/net/minecraft/world/entity/EntityType.java +++ b/src/main/java/net/minecraft/world/entity/EntityType.java @@ -385,10 +385,12 @@ public class EntityType implements FeatureElement, EntityTypeT private final int clientTrackingRange; private final int updateInterval; private final String descriptionId; + public boolean dabEnabled = false; // Pufferfish @Nullable private Component description; private final Optional> lootTable; - private final EntityDimensions dimensions; + private EntityDimensions dimensions; // Purpur - remove final + public void setDimensions(EntityDimensions dimensions) { this.dimensions = dimensions; } // Purpur private final float spawnDimensionsScale; private final FeatureFlagSet requiredFeatures; @@ -404,6 +406,16 @@ public class EntityType implements FeatureElement, EntityTypeT return EntityType.register(EntityType.vanillaEntityId(id), type); } + // Purpur start + public static EntityType getFromBukkitType(org.bukkit.entity.EntityType bukkitType) { + return getFromKey(ResourceLocation.parse(bukkitType.getKey().toString())); + } + + public static EntityType getFromKey(ResourceLocation location) { + return BuiltInRegistries.ENTITY_TYPE.getValue(location); + } + // Purpur end + public static ResourceLocation getKey(EntityType type) { return BuiltInRegistries.ENTITY_TYPE.getKey(type); } @@ -604,6 +616,16 @@ public class EntityType implements FeatureElement, EntityTypeT return this.category; } + // Purpur start + public String getName() { + return BuiltInRegistries.ENTITY_TYPE.getKey(this).getPath(); + } + + public String getTranslatedName() { + return getDescription().getString(); + } + // Purpur end + public String getDescriptionId() { return this.descriptionId; } @@ -661,6 +683,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 bf0838f574fa3fb9654e087d602b8d380bd7fb28..32a0db7e8f974712bd8a05f16f3bd48ac66142d5 100644 --- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java @@ -333,7 +333,7 @@ public class ExperienceOrb extends Entity { public void playerTouch(Player player) { if (player instanceof ServerPlayer entityplayer) { if (player.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(entityplayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent - player.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(player, 2, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; + player.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(player, this.level().purpurConfig.playerExpPickupDelay, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; // Purpur player.take(this, 1); int i = this.repairPlayerItems(entityplayer, this.value); @@ -351,7 +351,7 @@ public class ExperienceOrb extends Entity { } private int repairPlayerItems(ServerPlayer player, int amount) { - Optional optional = EnchantmentHelper.getRandomItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged); + Optional optional = level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player) : EnchantmentHelper.getRandomItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged); // Purpur - Add option to mend the most damaged equipment first if (optional.isPresent()) { ItemStack itemstack = ((EnchantedItemInUse) optional.get()).itemStack(); diff --git a/src/main/java/net/minecraft/world/entity/GlowSquid.java b/src/main/java/net/minecraft/world/entity/GlowSquid.java index b851c3ee1426bc0a259bf6c4a662af0c9883dd71..3283228d7ebf98ce98780725a0a412bea4200da5 100644 --- a/src/main/java/net/minecraft/world/entity/GlowSquid.java +++ b/src/main/java/net/minecraft/world/entity/GlowSquid.java @@ -25,6 +25,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 f36a075dbee2b96d01899e02460b1d8443e91749..f4604e543a980d6aeb485252da5a4e755aa12bfa 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -248,9 +248,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; @@ -297,6 +297,7 @@ public abstract class LivingEntity extends Entity implements Attackable { public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API + protected boolean shouldBurnInDay = false; public boolean shouldBurnInDay() { return this.shouldBurnInDay; } public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Purpur - API for any mob to burn daylight @Override public float getBukkitYaw() { @@ -325,7 +326,8 @@ public abstract class LivingEntity extends Entity implements Attackable { this.lastClimbablePos = Optional.empty(); this.activeLocationDependentEnchantments = new EnumMap(EquipmentSlot.class); this.appliedScale = 1.0F; - this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type)); + this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type), this); // Purpur + 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()); @@ -340,6 +342,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; } @@ -375,6 +379,7 @@ public abstract class LivingEntity extends Entity implements Attackable { public static AttributeSupplier.Builder createLivingAttributes() { return AttributeSupplier.builder().add(Attributes.MAX_HEALTH).add(Attributes.KNOCKBACK_RESISTANCE).add(Attributes.MOVEMENT_SPEED).add(Attributes.ARMOR).add(Attributes.ARMOR_TOUGHNESS).add(Attributes.MAX_ABSORPTION).add(Attributes.STEP_HEIGHT).add(Attributes.SCALE).add(Attributes.GRAVITY).add(Attributes.SAFE_FALL_DISTANCE).add(Attributes.FALL_DAMAGE_MULTIPLIER).add(Attributes.JUMP_STRENGTH).add(Attributes.OXYGEN_BONUS).add(Attributes.BURNING_TIME).add(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE).add(Attributes.WATER_MOVEMENT_EFFICIENCY).add(Attributes.MOVEMENT_EFFICIENCY).add(Attributes.ATTACK_KNOCKBACK); } + public boolean shouldSendAttribute(Attribute attribute) { return true; } // Purpur @Override protected void checkFallDamage(double heightDifference, boolean onGround, BlockState state, BlockPos landedPosition) { @@ -467,7 +472,7 @@ public abstract class LivingEntity extends Entity implements Attackable { if (world1 instanceof ServerLevel) { worldserver1 = (ServerLevel) world1; - if (this.isInWall()) { + if (shouldCheckForSuffocation() && this.isInWall()) { // Pufferfish - optimize suffocation this.hurtServer(worldserver1, this.damageSources().inWall(), 1.0F); } else if (flag && !this.level().getWorldBorder().isWithinBounds(this.getBoundingBox())) { double d1 = this.level().getWorldBorder().getDistanceToBorder(this) + this.level().getWorldBorder().getDamageSafeZone(); @@ -475,6 +480,7 @@ public abstract class LivingEntity extends Entity implements Attackable { if (d1 < 0.0D) { d0 = this.level().getWorldBorder().getDamagePerBlock(); if (d0 > 0.0D) { + if (level().purpurConfig.teleportIfOutsideBorder && this instanceof ServerPlayer serverPlayer) { serverPlayer.teleport(io.papermc.paper.util.MCUtil.toLocation(level(), ((ServerLevel) level()).getSharedSpawnPos())); return; } // Purpur this.hurtServer(worldserver1, this.damageSources().outOfBorder(), (float) Math.max(1, Mth.floor(-d1 * d0))); } } @@ -486,7 +492,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(); @@ -498,7 +504,7 @@ public abstract class LivingEntity extends Entity implements Attackable { this.level().addParticle(ParticleTypes.BUBBLE, this.getX() + d0, this.getY() + d2, this.getZ() + d3, vec3d.x, vec3d.y, vec3d.z); } - this.hurt(this.damageSources().drown(), 2.0F); + this.hurt(this.damageSources().drown(), (float) this.level().purpurConfig.damageFromDrowning); // Purpur } } @@ -564,6 +570,19 @@ public abstract class LivingEntity extends Entity implements Attackable { gameprofilerfiller.pop(); } + // Pufferfish start - optimize suffocation + public boolean couldPossiblyBeHurt(float amount) { + if ((float) this.invulnerableTime > (float) this.invulnerableDuration / 2.0F && amount <= this.lastHurt) { + return false; + } + return true; + } + + public boolean shouldCheckForSuffocation() { + return !gg.pufferfish.pufferfish.PufferfishConfig.enableSuffocationOptimization || (tickCount % 10 == 0 && couldPossiblyBeHurt(1.0F)); + } + // Pufferfish end + @Override protected float getBlockSpeedFactor() { return Mth.lerp((float) this.getAttributeValue(Attributes.MOVEMENT_EFFICIENCY), super.getBlockSpeedFactor(), 1.0F); @@ -834,6 +853,7 @@ public abstract class LivingEntity extends Entity implements Attackable { dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> { nbt.put("Brain", nbtbase); }); + nbt.putBoolean("Purpur.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - API for any mob to burn daylight } @Override @@ -922,6 +942,11 @@ public abstract class LivingEntity extends Entity implements Attackable { this.brain = this.makeBrain(new Dynamic(NbtOps.INSTANCE, nbt.get("Brain"))); } + // Purpur start - API for any mob to burn daylight + if (nbt.contains("Purpur.ShouldBurnInDay")) { + this.shouldBurnInDay = nbt.getBoolean("Purpur.ShouldBurnInDay"); + } + // Purpur end - API for any mob to burn daylight } // CraftBukkit start @@ -1057,9 +1082,31 @@ public abstract class LivingEntity extends Entity implements Attackable { ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); EntityType entitytypes = entity.getType(); - if (entitytypes == EntityType.SKELETON && itemstack.is(Items.SKELETON_SKULL) || entitytypes == EntityType.ZOMBIE && itemstack.is(Items.ZOMBIE_HEAD) || entitytypes == EntityType.PIGLIN && itemstack.is(Items.PIGLIN_HEAD) || entitytypes == EntityType.PIGLIN_BRUTE && itemstack.is(Items.PIGLIN_HEAD) || entitytypes == EntityType.CREEPER && itemstack.is(Items.CREEPER_HEAD)) { - d0 *= 0.5D; + // Purpur start + if (entitytypes == EntityType.SKELETON && itemstack.is(Items.SKELETON_SKULL)) { + d0 *= entity.level().purpurConfig.skeletonHeadVisibilityPercent; + } + else if (entitytypes == EntityType.ZOMBIE && itemstack.is(Items.ZOMBIE_HEAD)) { + d0 *= entity.level().purpurConfig.zombieHeadVisibilityPercent; + } + else if (entitytypes == EntityType.CREEPER && itemstack.is(Items.CREEPER_HEAD)) { + d0 *= entity.level().purpurConfig.creeperHeadVisibilityPercent; } + else if ((entitytypes == EntityType.PIGLIN || entitytypes == EntityType.PIGLIN_BRUTE) && itemstack.is(Items.PIGLIN_HEAD)) { + d0 *= entity.level().purpurConfig.piglinHeadVisibilityPercent; + } + // Purpur end + + // Purpur start + if (entity instanceof LivingEntity entityliving) { + if (entityliving.hasEffect(MobEffects.BLINDNESS)) { + int amplifier = entityliving.getEffect(MobEffects.BLINDNESS).getAmplifier(); + for (int i = 0; i < amplifier; i++) { + d0 *= this.level().purpurConfig.mobsBlindnessMultiplier; + } + } + } + // Purpur end } return d0; @@ -1115,6 +1162,7 @@ public abstract class LivingEntity extends Entity implements Attackable { while (iterator.hasNext()) { MobEffectInstance effect = iterator.next(); + if (cause == EntityPotionEffectEvent.Cause.MILK && !this.level().purpurConfig.milkClearsBeneficialEffects && effect.getEffect().value().isBeneficial()) continue; // Purpur EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED); if (event.isCancelled()) { continue; @@ -1450,6 +1498,24 @@ public abstract class LivingEntity extends Entity implements Attackable { this.stopSleeping(); } + // Purpur start + if (source.getEntity() instanceof net.minecraft.world.entity.player.Player player && source.getEntity().level().purpurConfig.creativeOnePunch && !source.is(DamageTypeTags.IS_PROJECTILE)) { + if (player.isCreative()) { + org.apache.commons.lang3.mutable.MutableDouble attackDamage = new org.apache.commons.lang3.mutable.MutableDouble(); + player.getMainHandItem().forEachModifier(EquipmentSlot.MAINHAND, (attributeHolder, attributeModifier) -> { + if (attributeModifier.operation() == AttributeModifier.Operation.ADD_VALUE) { + attackDamage.addAndGet(attributeModifier.amount()); + } + }); + + if (attackDamage.doubleValue() == 0.0D) { + // One punch! + amount = 9999F; + } + } + } + // Purpur end + this.noActionTime = 0; if (amount < 0.0F) { amount = 0.0F; @@ -1549,13 +1615,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) { @@ -1702,6 +1768,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); @@ -1873,7 +1951,7 @@ public abstract class LivingEntity extends Entity implements Attackable { boolean flag = false; if (this.dead && adversary instanceof WitherBoss) { // Paper - if (worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (worldserver.purpurConfig.witherBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur BlockPos blockposition = this.blockPosition(); BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState(); @@ -1910,6 +1988,7 @@ public abstract class LivingEntity extends Entity implements Attackable { this.dropEquipment(world); // CraftBukkit - from below if (this.shouldDropLoot() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { + if (!(damageSource.is(net.minecraft.world.damagesource.DamageTypes.CRAMMING) && level().purpurConfig.disableDropsOnCrammingDeath)) { // Purpur this.dropFromLootTable(world, damageSource, flag); // Paper start final boolean prev = this.clearEquipmentSlots; @@ -1918,6 +1997,7 @@ public abstract class LivingEntity extends Entity implements Attackable { // Paper end this.dropCustomDeathLoot(world, damageSource, flag); this.clearEquipmentSlots = prev; // Paper + } // Purpur } // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, damageSource, this.drops, () -> { @@ -2120,6 +2200,20 @@ public abstract class LivingEntity extends Entity implements Attackable { return this.lastClimbablePos; } + + // Pufferfish start + private boolean cachedOnClimable = false; + private BlockPos lastClimbingPosition = null; + + public boolean onClimableCached() { + if (!this.blockPosition().equals(this.lastClimbingPosition)) { + this.cachedOnClimable = this.onClimbable(); + this.lastClimbingPosition = this.blockPosition(); + } + return this.cachedOnClimable; + } + // Pufferfish end + public boolean onClimbable() { if (this.isSpectator()) { return false; @@ -3146,6 +3240,7 @@ public abstract class LivingEntity extends Entity implements Attackable { if (f > 0.0F) { this.playSound(this.getFallDamageSound((int) f), 1.0F, 1.0F); + if (level().purpurConfig.elytraKineticDamage) // Purpur this.hurt(this.damageSources().flyIntoWall(), f); } } @@ -3692,8 +3787,10 @@ public abstract class LivingEntity extends Entity implements Attackable { this.pushEntities(); gameprofilerfiller.pop(); // Paper start - Add EntityMoveEvent - if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { - if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { + // Purpur start + 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()); @@ -3703,6 +3800,21 @@ public abstract class LivingEntity extends Entity implements Attackable { this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); } } + // Purpur start + 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 - Add EntityMoveEvent world = this.level(); @@ -3712,6 +3824,34 @@ public abstract class LivingEntity extends Entity implements Attackable { } } + // Purpur start - copied from Zombie - API for any mob to burn daylight + if (this.isAlive()) { + boolean flag = this.shouldBurnInDay() && this.isSunBurnTick(); // Paper - shouldBurnInDay API // Purpur - use shouldBurnInDay() method to handle Phantoms properly - API for any mob to burn daylight + + if (flag) { + ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); + + if (!itemstack.isEmpty()) { + if (itemstack.isDamageableItem()) { + Item item = itemstack.getItem(); + + itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); + if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { + this.onEquippedItemBroken(item, EquipmentSlot.HEAD); + this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); + } + } + + flag = false; + } + + if (flag) { + if (getRider() == null || !this.isControllable()) // Purpur - ignore mobs which are uncontrollable or without rider - API for any mob to burn daylight + this.igniteForSeconds(8.0F); + } + } + } + // Purpur end - copied from Zombie - API for any mob to burn daylight } public boolean isSensitiveToWater() { @@ -3738,7 +3878,17 @@ public abstract class LivingEntity extends Entity implements Attackable { }).toList(); EquipmentSlot enumitemslot = (EquipmentSlot) Util.getRandom(list, this.random); - this.getItemBySlot(enumitemslot).hurtAndBreak(1, this, enumitemslot); + // Purpur start + int damage = level().purpurConfig.elytraDamagePerSecond; + if (level().purpurConfig.elytraDamageMultiplyBySpeed > 0) { + double speed = getDeltaMovement().lengthSqr(); + if (speed > level().purpurConfig.elytraDamageMultiplyBySpeed) { + damage *= (int) speed; + } + } + + this.getItemBySlot(enumitemslot).hurtAndBreak(damage, this, enumitemslot); + // Purpur end } this.gameEvent(GameEvent.ELYTRA_GLIDE); @@ -3747,7 +3897,7 @@ public abstract class LivingEntity extends Entity implements Attackable { } - protected boolean canGlide() { + public boolean canGlide() { // Purpur if (!this.onGround() && !this.isPassenger() && !this.hasEffect(MobEffects.LEVITATION)) { Iterator iterator = EquipmentSlot.VALUES.iterator(); @@ -4669,7 +4819,7 @@ public abstract class LivingEntity extends Entity implements Attackable { if (equippable != null && equippable.dispensable()) { EquipmentSlot enumitemslot = equippable.slot(); - return this.canUseSlot(enumitemslot) && equippable.canBeEquippedBy(this.getType()) ? this.getItemBySlot(enumitemslot).isEmpty() && this.canDispenserEquipIntoSlot(enumitemslot) : false; + return this.canUseSlot(enumitemslot) && equippable.canBeEquippedBy(this.getType()) && this.getItemBySlot(enumitemslot).isEmpty() && this.canDispenserEquipIntoSlot(enumitemslot); } else { return false; } @@ -4694,6 +4844,12 @@ public abstract class LivingEntity extends Entity implements Attackable { return equippable == null ? slot == EquipmentSlot.MAINHAND && this.canUseSlot(EquipmentSlot.MAINHAND) : slot == equippable.slot() && this.canUseSlot(equippable.slot()) && equippable.canBeEquippedBy(this.getType()); } + // Purpur start - Dispenser curse of binding protection + public @Nullable EquipmentSlot getEquipmentSlotForDispenserItem(ItemStack itemstack) { + return EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.BINDING_CURSE, itemstack) > 0 ? null : this.getEquipmentSlotForItem(itemstack); + } + // Purpur end - Dispenser curse of binding protection + private static SlotAccess createEquipmentSlotAccess(LivingEntity entity, EquipmentSlot slot) { return slot != EquipmentSlot.HEAD && slot != EquipmentSlot.MAINHAND && slot != EquipmentSlot.OFFHAND ? SlotAccess.forEquipmentSlot(entity, slot, (itemstack) -> { return itemstack.isEmpty() || entity.getEquipmentSlotForItem(itemstack) == slot; diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java index dbd321f3dc3cc80737830db63aed47a6935e8e89..7ca12a1128661ea36e2e33cda204c7d2df55a21c 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java @@ -148,6 +148,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab private BlockPos restrictCenter; private float restrictRadius; + public int ticksSinceLastInteraction; // Purpur public boolean aware = true; // CraftBukkit protected Mob(EntityType type, Level world) { @@ -163,8 +164,8 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab this.restrictRadius = -1.0F; this.goalSelector = new GoalSelector(); this.targetSelector = new GoalSelector(); - this.lookControl = new LookControl(this); - this.moveControl = new MoveControl(this); + this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this); // Purpur + this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this); // Purpur this.jumpControl = new JumpControl(this); this.bodyRotationControl = this.createBodyControl(); this.navigation = this.createNavigation(world); @@ -235,14 +236,16 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab return this.lookControl; } + int _pufferfish_inactiveTickDisableCounter = 0; // Pufferfish - throttle inactive goal selector ticking // Paper start @Override public void inactiveTick() { super.inactiveTick(); - if (this.goalSelector.inactiveTick()) { + boolean isThrottled = gg.pufferfish.pufferfish.PufferfishConfig.throttleInactiveGoalSelectorTick && _pufferfish_inactiveTickDisableCounter++ % 20 != 0; // Pufferfish - throttle inactive goal selector ticking + if (this.goalSelector.inactiveTick(this.activatedPriority, true) && !isThrottled) { // Pufferfish - pass activated priroity // Pufferfish - throttle inactive goal selector ticking this.goalSelector.tick(); } - if (this.targetSelector.inactiveTick()) { + if (this.targetSelector.inactiveTick(this.activatedPriority, true)) { // Pufferfish - pass activated priority this.targetSelector.tick(); } } @@ -334,6 +337,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab entityliving = null; } } + if (entityliving instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur this.target = entityliving; return true; // CraftBukkit end @@ -378,8 +382,28 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab } gameprofilerfiller.pop(); + incrementTicksSinceLastInteraction(); // Purpur } + // Purpur start + private void incrementTicksSinceLastInteraction() { + ++this.ticksSinceLastInteraction; + if (getRider() != null) { + this.ticksSinceLastInteraction = 0; + return; + } + if (this.level().purpurConfig.entityLifeSpan <= 0) { + return; // feature disabled + } + if (!this.removeWhenFarAway(0) || isPersistenceRequired() || requiresCustomPersistence() || hasCustomName()) { + return; // mob persistent + } + if (this.ticksSinceLastInteraction > this.level().purpurConfig.entityLifeSpan) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + } + } + // Purpur end + @Override protected void playHurtSound(DamageSource damageSource) { this.resetAmbientSoundTime(); @@ -547,6 +571,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab } nbt.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit + nbt.putInt("Purpur.ticksSinceLastInteraction", this.ticksSinceLastInteraction); // Purpur } @Override @@ -624,6 +649,11 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab this.aware = nbt.getBoolean("Bukkit.Aware"); } // CraftBukkit end + // Purpur start + if (nbt.contains("Purpur.ticksSinceLastInteraction")) { + this.ticksSinceLastInteraction = nbt.getInt("Purpur.ticksSinceLastInteraction"); + } + // Purpur end } @Override @@ -676,7 +706,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab Level world = this.level(); if (world instanceof ServerLevel worldserver) { - if (this.canPickUpLoot() && this.isAlive() && !this.dead && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (this.canPickUpLoot() && this.isAlive() && !this.dead && (worldserver.purpurConfig.entitiesPickUpLootBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur Vec3i baseblockposition = this.getPickupReach(); List list = this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ())); Iterator iterator = list.iterator(); @@ -927,16 +957,20 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab if (i % 2 != 0 && this.tickCount > 1) { gameprofilerfiller.push("targetSelector"); + if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking this.targetSelector.tickRunningGoals(false); gameprofilerfiller.pop(); gameprofilerfiller.push("goalSelector"); + if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking this.goalSelector.tickRunningGoals(false); gameprofilerfiller.pop(); } else { gameprofilerfiller.push("targetSelector"); + if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking this.targetSelector.tick(); gameprofilerfiller.pop(); gameprofilerfiller.push("goalSelector"); + if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking this.goalSelector.tick(); gameprofilerfiller.pop(); } @@ -1394,7 +1428,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab attributemodifiable.addPermanentModifier(new AttributeModifier(Mob.RANDOM_SPAWN_BONUS_ID, randomsource.triangle(0.0D, 0.11485000000000001D), AttributeModifier.Operation.ADD_MULTIPLIED_BASE)); } - this.setLeftHanded(randomsource.nextFloat() < 0.05F); + this.setLeftHanded(randomsource.nextFloat() < world.getLevel().purpurConfig.entityLeftHandedChance); // Purpur return entityData; } @@ -1496,7 +1530,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab protected void onOffspringSpawnedFromEgg(Player player, Mob child) {} protected InteractionResult mobInteract(Player player, InteractionHand hand) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } public boolean isWithinRestriction() { @@ -1735,23 +1769,15 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab this.playAttackSound(); } + if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur return flag; } protected void playAttackSound() {} public boolean isSunBurnTick() { - if (this.level().isDay() && !this.level().isClientSide) { - float f = this.getLightLevelDependentMagicValue(); - BlockPos blockposition = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); - boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; - - if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level().canSeeSky(blockposition)) { - return true; - } - } - - return false; + // Purpur - implemented in Entity - API for any mob to burn daylight + return super.isSunBurnTick(); } @Override @@ -1813,4 +1839,58 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab public float[] getArmorDropChances() { return this.armorDropChances; } + + // 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())) { + if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) { + serverPlayer.sendMiniMessage(org.purpurmc.purpur.PurpurConfig.cannotRideMob); + } + return InteractionResult.PASS; + } + player.setYRot(this.getYRot()); + player.setXRot(this.getXRot()); + if (player.startRiding(this)) { + return InteractionResult.SUCCESS; + } else { + return InteractionResult.PASS; + } + } + // Purpur end } 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 94d04a20f97405e02d7cccaabadc7a7e86e336f7..861591694785a6ff16f839fa91781791c275de14 100644 --- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java +++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java @@ -23,14 +23,23 @@ public class AttributeMap { private final Set attributesToSync = new ObjectOpenHashSet<>(); private final Set attributesToUpdate = new ObjectOpenHashSet<>(); private final AttributeSupplier supplier; + private final java.util.function.Function, AttributeInstance> createInstance; // Pufferfish + private final net.minecraft.world.entity.LivingEntity entity; // Purpur 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 = attributex -> this.supplier.createInstance(this::onAttributeModified, attributex); // Pufferfish } private void onAttributeModified(AttributeInstance instance) { this.attributesToUpdate.add(instance); - if (instance.getAttribute().value().isClientSyncable()) { + if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur this.attributesToSync.add(instance); } } @@ -44,12 +53,13 @@ public class AttributeMap { } public Collection getSyncableAttributes() { - return this.attributes.values().stream().filter(attribute -> attribute.getAttribute().value().isClientSyncable()).collect(Collectors.toList()); + return this.attributes.values().stream().filter(attribute -> attribute.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(attribute.getAttribute().value()))).collect(Collectors.toList()); // Purpur } + @Nullable public AttributeInstance getInstance(Holder attribute) { - return this.attributes.computeIfAbsent(attribute, attributex -> this.supplier.createInstance(this::onAttributeModified, attributex)); + return this.attributes.computeIfAbsent(attribute, this.createInstance); // Pufferfish - cache lambda, as for some reason java allocates it anyways } public boolean hasAttribute(Holder attribute) { diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java index 386f9bca728055f7b75fb690b307ff4510068105..0bb08af954d224a2b4404615bee720ac4bdbac55 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 @@ -130,7 +130,7 @@ public class DefaultAttributes { .put(EntityType.OCELOT, Ocelot.createAttributes().build()) .put(EntityType.PANDA, Panda.createAttributes().build()) .put(EntityType.PARROT, Parrot.createAttributes().build()) - .put(EntityType.PHANTOM, Monster.createMonsterAttributes().build()) + .put(EntityType.PHANTOM, net.minecraft.world.entity.monster.Phantom.createAttributes().build()) // Purpur .put(EntityType.PIG, Pig.createAttributes().build()) .put(EntityType.PIGLIN, Piglin.createAttributes().build()) .put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build()) @@ -161,7 +161,7 @@ public class DefaultAttributes { .put(EntityType.VILLAGER, Villager.createAttributes().build()) .put(EntityType.VINDICATOR, Vindicator.createAttributes().build()) .put(EntityType.WARDEN, Warden.createAttributes().build()) - .put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build()) + .put(EntityType.WANDERING_TRADER, net.minecraft.world.entity.npc.WanderingTrader.createAttributes().build()) // Purpur .put(EntityType.WITCH, Witch.createAttributes().build()) .put(EntityType.WITHER, WitherBoss.createAttributes().build()) .put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build()) diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java b/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java index f0703302e7dbbda88de8c648d20d87c55ed9b1e0..a913ebabaa5f443afa987b972355a8f8d1723c78 100644 --- a/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java +++ b/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java @@ -29,6 +29,7 @@ public class RangedAttribute extends Attribute { @Override public double sanitizeValue(double value) { + if (!org.purpurmc.purpur.PurpurConfig.clampAttributes) return Double.isNaN(value) ? this.minValue : value; // Purpur return Double.isNaN(value) ? this.minValue : Mth.clamp(value, this.minValue, this.maxValue); } } diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java index e1b6fe9ecda25f86431baf414f1bfd3a26a8b2bd..3d7578605812021bc84b99d4db1672a682d897ad 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java @@ -73,7 +73,7 @@ public class AcquirePoi { }; // Paper start - optimise POI access java.util.List, BlockPos>> poiposes = new java.util.ArrayList<>(); - io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, poiPredicate, predicate2, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); + io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, poiPredicate, predicate2, entity.blockPosition(), world.purpurConfig.villagerAcquirePoiSearchRadius, world.purpurConfig.villagerAcquirePoiSearchRadius*world.purpurConfig.villagerAcquirePoiSearchRadius, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); Set, BlockPos>> set = new java.util.HashSet<>(poiposes); // Paper end - optimise POI access Path path = findPathToPois(entity, set); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java index 2ade08d1466660ee1787fa97908002ef56389712..018cc6ff39641157668fca09e64bcddf7d4d3a5c 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java @@ -41,17 +41,19 @@ public class HarvestFarmland extends Behavior { private long nextOkStartTime; private int timeWorkedSoFar; private final List validFarmlandAroundVillager = Lists.newArrayList(); + private boolean clericWartFarmer = false; // Purpur public HarvestFarmland() { super(ImmutableMap.of(MemoryModuleType.LOOK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.SECONDARY_JOB_SITE, MemoryStatus.VALUE_PRESENT)); } protected boolean checkExtraStartConditions(ServerLevel world, Villager entity) { - if (!world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (!world.purpurConfig.villagerBypassMobGriefing == !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur return false; - } else if (entity.getVillagerData().getProfession() != VillagerProfession.FARMER) { + } else if (entity.getVillagerData().getProfession() != VillagerProfession.FARMER && !(world.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC)) { // Purpur return false; } else { + if (!this.clericWartFarmer && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC) this.clericWartFarmer = true; // Purpur BlockPos.MutableBlockPos blockposition_mutableblockposition = entity.blockPosition().mutable(); this.validFarmlandAroundVillager.clear(); @@ -82,6 +84,7 @@ public class HarvestFarmland extends Behavior { Block block = iblockdata.getBlock(); Block block1 = world.getBlockState(pos.below()).getBlock(); + if (this.clericWartFarmer) return block == Blocks.NETHER_WART && iblockdata.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3 || iblockdata.isAir() && block1 == Blocks.SOUL_SAND; // Purpur return block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata) || iblockdata.isAir() && block1 instanceof FarmBlock; } @@ -107,20 +110,20 @@ public class HarvestFarmland extends Behavior { Block block = iblockdata.getBlock(); Block block1 = world.getBlockState(this.aboveFarmlandPos.below()).getBlock(); - if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) { + if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata) && !this.clericWartFarmer || this.clericWartFarmer && block == Blocks.NETHER_WART && iblockdata.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3) { // Purpur if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state world.destroyBlock(this.aboveFarmlandPos, true, entity); } // CraftBukkit } - if (iblockdata.isAir() && block1 instanceof FarmBlock && entity.hasFarmSeeds()) { + if (iblockdata.isAir() && (block1 instanceof FarmBlock && !this.clericWartFarmer || this.clericWartFarmer && block1 == Blocks.SOUL_SAND) && entity.hasFarmSeeds()) { // Purpur SimpleContainer inventorysubcontainer = entity.getInventory(); for (int j = 0; j < inventorysubcontainer.getContainerSize(); ++j) { ItemStack itemstack = inventorysubcontainer.getItem(j); boolean flag = false; - if (!itemstack.isEmpty() && itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS)) { + if (!itemstack.isEmpty() && (itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS) || this.clericWartFarmer && itemstack.getItem() == net.minecraft.world.item.Items.NETHER_WART)) { Item item = itemstack.getItem(); if (item instanceof BlockItem) { @@ -136,7 +139,7 @@ public class HarvestFarmland extends Behavior { } if (flag) { - world.playSound((Player) null, (double) this.aboveFarmlandPos.getX(), (double) this.aboveFarmlandPos.getY(), (double) this.aboveFarmlandPos.getZ(), SoundEvents.CROP_PLANTED, SoundSource.BLOCKS, 1.0F, 1.0F); + world.playSound((Player) null, (double) this.aboveFarmlandPos.getX(), (double) this.aboveFarmlandPos.getY(), (double) this.aboveFarmlandPos.getZ(), this.clericWartFarmer ? SoundEvents.NETHER_WART_PLANTED : SoundEvents.CROP_PLANTED, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur itemstack.shrink(1); if (itemstack.isEmpty()) { inventorysubcontainer.setItem(j, ItemStack.EMPTY); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java b/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java index 3513b15f6622bfc134ecfcd9129f81a8acc2c601..6e70579a58a1bf906b176b81713e55318199cef6 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java @@ -57,7 +57,7 @@ public class InteractWithDoor { if (iblockdata.is(BlockTags.MOB_INTERACTABLE_DOORS, (blockbase_blockdata) -> { return blockbase_blockdata.getBlock() instanceof DoorBlock; - })) { + }) && !DoorBlock.requiresRedstone(entityliving.level(), iblockdata, blockposition)) { // Purpur DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock(); if (!blockdoor.isOpen(iblockdata)) { @@ -79,7 +79,7 @@ public class InteractWithDoor { if (iblockdata1.is(BlockTags.MOB_INTERACTABLE_DOORS, (blockbase_blockdata) -> { return blockbase_blockdata.getBlock() instanceof DoorBlock; - })) { + }) && !DoorBlock.requiresRedstone(entityliving.level(), iblockdata, blockposition1)) { // Purpur DoorBlock blockdoor1 = (DoorBlock) iblockdata1.getBlock(); if (!blockdoor1.isOpen(iblockdata1)) { @@ -122,7 +122,7 @@ public class InteractWithDoor { if (!iblockdata.is(BlockTags.MOB_INTERACTABLE_DOORS, (blockbase_blockdata) -> { return blockbase_blockdata.getBlock() instanceof DoorBlock; - })) { + }) || DoorBlock.requiresRedstone(entity.level(), iblockdata, blockposition)) { // Purpur iterator.remove(); } else { DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock(); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java index 18dad0825616c4167a0a7555689ee64910a87e09..6945992491027d43eca4f1ca697ad45ce06ded55 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java @@ -46,6 +46,7 @@ public class ShowTradesToPlayer extends Behavior { @Override public boolean canStillUse(ServerLevel world, Villager entity, long time) { + if (!entity.level().purpurConfig.villagerDisplayTradeItem) return false; // Purpur return this.checkExtraStartConditions(world, entity) && this.lookTime > 0 && entity.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).isPresent(); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java b/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java index 8508ac7de8cda3127b73e11ff4aee62502e65ead..b1544e028d5a9b84b944e1fb5a12bb163067fb54 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java @@ -59,6 +59,12 @@ public class TradeWithVillager extends Behavior { throwHalfStack(entity, ImmutableSet.of(Items.WHEAT), villager); } + // Purpur start + if (world.purpurConfig.villagerClericsFarmWarts && world.purpurConfig.villagerClericFarmersThrowWarts && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC && entity.getInventory().countItem(Items.NETHER_WART) > Items.NETHER_WART.getDefaultMaxStackSize() / 2) { + throwHalfStack(entity, ImmutableSet.of(Items.NETHER_WART), villager); + } + // Purpur end + if (!this.trades.isEmpty() && entity.getInventory().hasAnyOf(this.trades)) { throwHalfStack(entity, this.trades, villager); } diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java index bb65d46967cb04f611b3c9c97d5732cfb21ede9b..7f4156e4690bbd57f9e9141f008851062cae733d 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 @@ -52,8 +52,13 @@ public class VillagerGoalPackages { } public static ImmutableList>> getWorkPackage(VillagerProfession profession, float speed) { + // Purpur start + return getWorkPackage(profession, speed, false); + } + public static ImmutableList>> getWorkPackage(VillagerProfession profession, float speed, boolean clericsFarmWarts) { + // Purpur end WorkAtPoi workAtPoi; - if (profession == VillagerProfession.FARMER) { + if (profession == VillagerProfession.FARMER || (clericsFarmWarts && profession == VillagerProfession.CLERIC)) { // Purpur workAtPoi = new WorkAtComposter(); } else { workAtPoi = new WorkAtPoi(); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java index 0a608418f87b71d5d71706712e1f82da0d7e4d34..03e7ca83e4c28dfaa5b52bcb100bd542db105970 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java @@ -125,8 +125,10 @@ public class VillagerMakeLove extends Behavior { return Optional.empty(); } // Move age setting down - parent.setAge(6000); - partner.setAge(6000); + // Purpur start + parent.setAge(world.purpurConfig.villagerBreedingTicks); + partner.setAge(world.purpurConfig.villagerBreedingTicks); + // Purpur end world.addFreshEntityWithPassengers(entityvillager2, CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit end world.broadcastEntityEvent(entityvillager2, (byte) 12); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java index 758f62416ca9c02351348ac0d41deeb4624abc0e..69130969c9a434ec2361e573c9a1ec9f462dfda2 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerPanicTrigger.java @@ -36,7 +36,11 @@ public class VillagerPanicTrigger extends Behavior { @Override protected void tick(ServerLevel world, Villager entity, long time) { - if (time % 100L == 0L) { + // Pufferfish start + if (entity.nextGolemPanic < 0) entity.nextGolemPanic = time + 100; + if (--entity.nextGolemPanic < time) { + entity.nextGolemPanic = -1; + // Pufferfish end entity.spawnGolemIfNeeded(world, time, 3); } } diff --git a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java index c8fd5696de7c3623cdb4f498190a5c2708cf843e..e403d9dfeeaa3dcf53be790d761e7e922419efb0 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 fbfc2f2515ad709b2c1212aef9521e795547d66b..e77bd11af62682d5eca41f6c9e1aed30eb6879ce 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 -> this.mob.yHeadRot = this.rotateTowards(this.mob.yHeadRot, yaw + 20.0F, this.yMaxRotSpeed)); diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java index 7324da6b7dd2623ce394e3827ff77ef684a3b98b..d0ba8f74cd0d676640776c46df1913852f4a4a2e 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java @@ -33,7 +33,7 @@ public class BreakDoorGoal extends DoorInteractGoal { @Override public boolean canUse() { - return !super.canUse() ? false : (!getServerLevel((Entity) this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.isValidDifficulty(this.mob.level().getDifficulty()) && !this.isOpen()); + return !super.canUse() ? false : (!this.mob.level().purpurConfig.zombieBypassMobGriefing == !getServerLevel((Entity) this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.isValidDifficulty(this.mob.level().getDifficulty()) && !this.isOpen()); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java index 32bb591371fe78ba10a2bc52389ef33978cbc0eb..13f5e5c199688954c263b9e3397e02c9f77bbb92 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java @@ -74,7 +74,7 @@ public class EatBlockGoal extends Goal { final BlockState blockState = this.level.getBlockState(blockposition); // Paper - fix wrong block state if (EatBlockGoal.IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state - if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state + if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).purpurConfig.sheepBypassMobGriefing == !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state // Purpur this.level.destroyBlock(blockposition, false); } @@ -83,7 +83,7 @@ public class EatBlockGoal extends Goal { BlockPos blockposition1 = blockposition.below(); if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) { - if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state + if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).purpurConfig.sheepBypassMobGriefing == !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state // Purpur this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState())); this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2); } diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java index 29ae74339a4831ccef3d01e8054931715ba192ad..5a439e3b0fdc1010884634c1e046e49d8b9aee17 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java @@ -38,9 +38,12 @@ public class GoalSelector { } // Paper start - EAR 2 - public boolean inactiveTick() { + public boolean inactiveTick(int tickRate, boolean inactive) { // Pufferfish start + if (inactive && !gg.pufferfish.pufferfish.PufferfishConfig.dearEnabled) tickRate = 4; // reset to Paper's + tickRate = Math.min(tickRate, 3); this.curRate++; - return this.curRate % 3 == 0; // TODO newGoalRate was already unused in 1.20.4, check if this is correct + return this.curRate % tickRate == 0; // TODO newGoalRate was already unused in 1.20.4, check if this is correct + // Pufferfish end } public boolean hasTasks() { for (WrappedGoal task : this.availableGoals) { diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java index df695b444fa2a993d381e2f197182c3e91a68502..0f4f546cd0eda4bd82b47446ae23ac32da8a9556 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java @@ -22,6 +22,7 @@ public class LlamaFollowCaravanGoal extends Goal { @Override public boolean canUse() { + if (!this.llama.level().purpurConfig.llamaJoinCaravans || !this.llama.shouldJoinCaravan) return false; // Purpur if (!this.llama.isLeashed() && !this.llama.inCaravan()) { List list = this.llama.level().getEntities(this.llama, this.llama.getBoundingBox().inflate(9.0, 4.0, 9.0), entity -> { EntityType entityType = entity.getType(); @@ -71,6 +72,7 @@ public class LlamaFollowCaravanGoal extends Goal { @Override public boolean canContinueToUse() { + if (!this.llama.shouldJoinCaravan) return false; // Purpur if (this.llama.inCaravan() && this.llama.getCaravanHead().isAlive() && this.firstIsLeashed(this.llama, 0)) { double d = this.llama.distanceToSqr(this.llama.getCaravanHead()); if (d > 676.0) { diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java index aee0147649d458b87d92496eda0c1723ebe570d2..89e9ea999d2fbd81a1d74382ef3fcd675fc8b94e 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java @@ -121,6 +121,7 @@ public abstract class MoveToBlockGoal extends Goal { for (int m = 0; m <= l; m = m > 0 ? -m : 1 - m) { for (int n = m < l && m > -l ? l : 0; n <= l; n = n > 0 ? -n : 1 - n) { mutableBlockPos.setWithOffset(blockPos, m, k - 1, n); + if (!this.mob.level().hasChunkAt(mutableBlockPos)) continue; // Pufferfish - if this block isn't loaded, continue if (this.mob.isWithinRestriction(mutableBlockPos) && this.isValidTarget(this.mob.level(), mutableBlockPos)) { this.blockPos = mutableBlockPos; this.mob.movingTarget = mutableBlockPos == BlockPos.ZERO ? null : mutableBlockPos.immutable(); // Paper diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java index 515c1f671cb2c3a7cc23053aedf404bbbe77af3e..42a1e5b9c08a9245dd0ce6d4025e3bb5d60feb62 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java @@ -116,9 +116,9 @@ public class RangedBowAttackGoal extends Go } this.mob.lookAt(livingEntity, 30.0F, 30.0F); - } else { + } //else { // Purpur - fix MC-121706 this.mob.getLookControl().setLookAt(livingEntity, 30.0F, 30.0F); - } + //} // Purpur if (this.mob.isUsingItem()) { if (!bl && this.seeTime < -60) { diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java index 9d245d08be61d7edee9138196ae3bf52023e3993..771bb96032149a8573d1de14fa2ab19012c82000 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java @@ -41,7 +41,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal { @Override public boolean canUse() { - if (!getServerLevel((Entity) this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (!getServerLevel(this.removerMob).purpurConfig.zombieBypassMobGriefing == !getServerLevel((Entity) this.removerMob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur return false; } else if (this.nextStartTick > 0) { --this.nextStartTick; diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java index b0944fa1f3849dd24cd010fa0a6638f5fd7179d1..d409ae987088df3d47192128401d7491aaabc87c 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java @@ -67,7 +67,7 @@ public class RunAroundLikeCrazyGoal extends Goal { int i = this.horse.getTemper(); int j = this.horse.getMaxTemper(); - if (j > 0 && this.horse.getRandom().nextInt(j) < i && !CraftEventFactory.callEntityTameEvent(this.horse, ((CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent + if ((this.horse.level().purpurConfig.alwaysTameInCreative && entityhuman.hasInfiniteMaterials()) || (j > 0 && this.horse.getRandom().nextInt(j) < i && !CraftEventFactory.callEntityTameEvent(this.horse, ((CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled())) { // CraftBukkit - fire EntityTameEvent // Purpur this.horse.tameWithName(entityhuman); return; } diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java index 137ec75ee803789deb7b1ca93dd9369c9af362b9..ca95d25af3e9a0536868b0c7fd8e7d2ff1154ee3 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java @@ -54,6 +54,14 @@ public class SwellGoal extends Goal { this.creeper.setSwellDir(-1); } else { this.creeper.setSwellDir(1); + // Purpur start + if (this.creeper.level().purpurConfig.creeperEncircleTarget) { + net.minecraft.world.phys.Vec3 relative = this.creeper.position().subtract(this.target.position()); + relative = relative.yRot((float) Math.PI / 3).normalize().multiply(2, 2, 2); + net.minecraft.world.phys.Vec3 destination = this.target.position().add(relative); + this.creeper.getNavigation().moveTo(destination.x, destination.y, destination.z, 1); + } + // Purpur end } } } diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java index 84ab90dd1fe693da71732533ccff940c1007e1b6..179fdf7b7f68db610925a9d7b88879289b1b98b8 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java @@ -67,7 +67,7 @@ public class TemptGoal extends Goal { } private boolean shouldFollow(LivingEntity entity) { - return this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem()); + return (this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem())) && (!(this.mob instanceof net.minecraft.world.entity.npc.Villager villager) || !villager.isSleeping()); // Purpur Fix #512 } @Override diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java index 92731b6b593289e9f583c9b705b219e81fcd8e73..9104d7010bda6f9f73b478c11490ef9c53f76da2 100644 --- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java +++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java @@ -56,7 +56,7 @@ public class NearestBedSensor extends Sensor { // Paper start - optimise POI access java.util.List, BlockPos>> poiposes = new java.util.ArrayList<>(); // don't ask me why it's unbounded. ask mojang. - io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); + io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), world.purpurConfig.villagerNearestBedSensorSearchRadius, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); // Purpur Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes)); // Paper end - optimise POI access if (path != null && path.canReach()) { diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java index a0e0692d17760f440fe81d52887284c787e562db..ab9bebc07b5228dbc0d3ba4b0f7d1bbe41814c9b 100644 --- a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java +++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java @@ -22,6 +22,13 @@ public class SecondaryPoiSensor extends Sensor { @Override protected void doTick(ServerLevel world, Villager entity) { + // Purpur start - make sure clerics don't wander to soul sand when the option is off + Brain brain = entity.getBrain(); + if (!world.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == net.minecraft.world.entity.npc.VillagerProfession.CLERIC) { + brain.eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE); + return; + } + // Purpur end ResourceKey resourceKey = world.dimension(); BlockPos blockPos = entity.blockPosition(); List list = Lists.newArrayList(); @@ -38,7 +45,7 @@ public class SecondaryPoiSensor extends Sensor { } } - Brain brain = entity.getBrain(); + //Brain brain = entity.getBrain(); // Purpur - moved up if (!list.isEmpty()) { brain.setMemory(MemoryModuleType.SECONDARY_JOB_SITE, list); } else { diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java index 52982c1e6a4da36392569c791853279f5f9ac31a..b3d4c61b1769b6fcc98b7af854f774d24bf39d98 100644 --- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java +++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java @@ -64,6 +64,10 @@ public class TargetingConditions { return false; } else if (this.selector != null && !this.selector.test(target, world)) { return false; + // Purpur start + } else if (!world.purpurConfig.idleTimeoutTargetPlayer && target instanceof net.minecraft.server.level.ServerPlayer player && player.isAfk()) { + return false; + // Purpur end } else { if (tester == null) { if (this.isCombat && (!target.canBeSeenAsEnemy() || world.getDifficulty() == Difficulty.PEACEFUL)) { diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java index 60c2868f255d372226e0c1389caaa5477bbef41e..c8ffbbccbf20ce911a3d75fa860d5a1452c5e590 100644 --- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java +++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java @@ -47,12 +47,59 @@ public class Bat extends AmbientCreature { public Bat(EntityType 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.value(); } // Fixes log spam on clients + + @Override + public boolean isRidable() { + return level().purpurConfig.batRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.batRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.batControllable; + } + + @Override + public double getMaxY() { + return level().purpurConfig.batMaxY; + } + + @Override + public void onMount(Player rider) { + super.onMount(rider); + if (isResting()) { + setResting(false); + level().levelEvent(null, 1025, new BlockPos(this).above(), 0); + } + } + + @Override + public void travel(Vec3 vec3) { + super.travel(vec3); + if (getRider() != null && this.isControllable() && !onGround) { + float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2; + setSpeed(speed); + Vec3 mot = getDeltaMovement(); + move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.25, speed)); + setDeltaMovement(mot.scale(0.9D)); + } + } + // Purpur end + @Override public boolean isFlapping() { return !this.isResting() && (float) this.tickCount % 10.0F == 0.0F; @@ -102,7 +149,7 @@ public class Bat extends AmbientCreature { protected void pushEntities() {} public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D); + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur } public boolean isResting() { @@ -135,6 +182,14 @@ public class Bat extends AmbientCreature { @Override protected void customServerAiStep(ServerLevel world) { + // 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(world); BlockPos blockposition = this.blockPosition(); BlockPos blockposition1 = blockposition.above(); @@ -213,6 +268,29 @@ public class Bat extends AmbientCreature { } } + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.batMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.batScale); + this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(this.level().purpurConfig.batFollowRange); + this.getAttribute(Attributes.KNOCKBACK_RESISTANCE).setBaseValue(this.level().purpurConfig.batKnockbackResistance); + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.batMovementSpeed); + this.getAttribute(Attributes.FLYING_SPEED).setBaseValue(this.level().purpurConfig.batFlyingSpeed); + this.getAttribute(Attributes.ARMOR).setBaseValue(this.level().purpurConfig.batArmor); + this.getAttribute(Attributes.ARMOR_TOUGHNESS).setBaseValue(this.level().purpurConfig.batArmorToughness); + this.getAttribute(Attributes.ATTACK_KNOCKBACK).setBaseValue(this.level().purpurConfig.batAttackKnockback); + } + + @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); @@ -232,7 +310,7 @@ public class Bat extends AmbientCreature { int i = world.getMaxLocalRawBrightness(pos); byte b0 = 4; - if (Bat.isHalloween()) { + if (Bat.isHalloweenSeason(world.getMinecraftWorld())) { // Purpur b0 = 7; } else if (random.nextBoolean()) { return false; @@ -242,13 +320,23 @@ public class Bat extends AmbientCreature { } } + // Pufferfish start - only check for spooky season once an hour + private static boolean isSpookySeason = false; + private static final int ONE_HOUR = 20 * 60 * 60; + private static int lastSpookyCheck = -ONE_HOUR; + public static boolean isHalloweenSeason(Level level) { return level.purpurConfig.forceHalloweenSeason || isHalloween(); } // Purpur private static boolean isHalloween() { + if (net.minecraft.server.MinecraftServer.currentTick - lastSpookyCheck > ONE_HOUR) { LocalDate localdate = LocalDate.now(); int i = localdate.get(ChronoField.DAY_OF_MONTH); int j = localdate.get(ChronoField.MONTH_OF_YEAR); - return j == 10 && i >= 20 || j == 11 && i <= 3; + isSpookySeason = j == 10 && i >= 20 || j == 11 && i <= 3; + lastSpookyCheck = net.minecraft.server.MinecraftServer.currentTick; + } + return isSpookySeason; } + // Pufferfish end private void setupAnimationStates() { if (this.isResting()) { diff --git a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java index 9aedc62b1766f6a7db4da7eba55167d21d698791..9708ed3e00059fdf5d1d60e0c607d0ab153d1d3f 100644 --- a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java +++ b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java @@ -87,6 +87,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { @Override protected void registerGoals() { super.registerGoals(); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(0, new PanicGoal(this, 1.25)); this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 1.6, 1.4, EntitySelector.NO_SPECTATORS::test)); this.goalSelector.addGoal(4, new AbstractFish.FishSwimGoal(this)); @@ -100,7 +101,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { @Override public void travel(Vec3 movementInput) { if (this.isControlledByLocalInstance() && this.isInWater()) { - this.moveRelative(0.01F, movementInput); + this.moveRelative(getRider() != null ? getSpeed() : 0.01F, movementInput); // Purpur this.move(MoverType.SELF, this.getDeltaMovement()); this.setDeltaMovement(this.getDeltaMovement().scale(0.9)); if (this.getTarget() == null) { @@ -161,7 +162,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable { protected void playStepSound(BlockPos pos, BlockState state) { } - static class FishMoveControl extends MoveControl { + static class FishMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur private final AbstractFish fish; FishMoveControl(AbstractFish owner) { @@ -169,14 +170,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.0, 0.005, 0.0)); } if (this.operation == MoveControl.Operation.MOVE_TO && !this.fish.getNavigation().isDone()) { - float f = (float)(this.speedModifier * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED)); + float f = (float)(this.getSpeedModifier() * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur this.fish.setSpeed(Mth.lerp(0.125F, this.fish.getSpeed(), f)); double d = this.wantedX - this.fish.getX(); double e = this.wantedY - this.fish.getY(); diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java index 5677dc97ed83652f261100cf391883cfac7d16fe..9987d28ea145f6d0126cb4ea22001e0922fb51bd 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Animal.java +++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java @@ -49,6 +49,7 @@ public abstract class Animal extends AgeableMob { @Nullable public UUID loveCause; public ItemStack breedItem; // CraftBukkit - Add breedItem variable + public abstract int getPurpurBreedTime(); // Purpur protected Animal(EntityType type, Level world) { super(type, world); @@ -157,7 +158,7 @@ public abstract class Animal extends AgeableMob { if (this.isFood(itemstack)) { int i = this.getAge(); - if (!this.level().isClientSide && i == 0 && this.canFallInLove()) { + if (!this.level().isClientSide && i == 0 && this.canFallInLove() && (this.level().purpurConfig.animalBreedingCooldownSeconds <= 0 || !this.level().hasBreedingCooldown(player.getUUID(), this.getClass()))) { // Purpur final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying this.usePlayerItem(player, hand, itemstack); this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying @@ -261,12 +262,20 @@ public abstract class Animal extends AgeableMob { AgeableMob entityageable = this.getBreedOffspring(world, other); if (entityageable != null) { - entityageable.setBaby(true); - entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); - // CraftBukkit start - call EntityBreedEvent + // Purpur start ServerPlayer breeder = Optional.ofNullable(this.getLoveCause()).or(() -> { return Optional.ofNullable(other.getLoveCause()); }).orElse(null); + if (breeder != null && world.purpurConfig.animalBreedingCooldownSeconds > 0) { + if (world.hasBreedingCooldown(breeder.getUUID(), this.getClass())) { + return; + } + world.addBreedingCooldown(breeder.getUUID(), this.getClass()); + } + entityageable.setBaby(true); + entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); + // CraftBukkit start - call EntityBreedEvent + // Purpur end int experience = this.getRandom().nextInt(7) + 1; EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityageable, this, other, breeder, this.breedItem, experience); if (entityBreedEvent.isCancelled()) { @@ -294,8 +303,10 @@ public abstract class Animal extends AgeableMob { entityplayer.awardStat(Stats.ANIMALS_BRED); CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, entityanimal, entityageable); } // Paper - this.setAge(6000); - entityanimal.setAge(6000); + // Purpur start + 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 42276acfeadec6e7aa9a91d3f446f4fedb04829d..dc8df0912c1d18176e18a8f4dc43c4f60f81b659 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Bee.java +++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java @@ -150,6 +150,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 - Fix MC-167279 class BeeFlyingMoveControl extends FlyingMoveControl { public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) { @@ -158,22 +159,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().getMinY()) { 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 - Fix MC-167279 this.lookControl = new Bee.BeeLookControl(this); this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F); - this.setPathfindingMalus(PathType.WATER, -1.0F); + if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur this.setPathfindingMalus(PathType.WATER_BORDER, 16.0F); this.setPathfindingMalus(PathType.COCOA, -1.0F); this.setPathfindingMalus(PathType.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(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, speed, speed)); + setDeltaMovement(mot.scale(0.9D)); + } + } + // Purpur end + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); @@ -188,6 +236,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)); @@ -207,6 +256,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)); @@ -376,7 +426,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { } public static boolean isNightOrRaining(Level world) { - return world.dimensionType().hasSkyLight() && (world.isNight() || world.isRaining()); + return world.dimensionType().hasSkyLight() && ((world.isNight() && !world.purpurConfig.beeCanWorkAtNight) || (world.isRaining() && !world.purpurConfig.beeCanWorkInRain)); // Purpur } public void setStayOutOfHiveCountdown(int cannotEnterHiveTicks) { @@ -411,6 +461,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { this.hurtServer(world, this.damageSources().drown(), 1.0F); } + if (flag && !this.level().purpurConfig.beeDiesAfterSting) setHasStung(false); else // Purpur if (flag) { ++this.timeSinceSting; if (this.timeSinceSting % 5 == 0 && this.random.nextInt(Mth.clamp(1200 - this.timeSinceSting, 1, 1200)) == 0) { @@ -435,6 +486,27 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { return tileentitybeehive != null && tileentitybeehive.isFireNearby(); } + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.beeMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.beeScale); + } + + @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); @@ -701,16 +773,16 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { this.hivePos = pos; } - private class BeeLookControl extends LookControl { + private class BeeLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur BeeLookControl(final Mob entity) { super(entity); } @Override - public void tick() { + public void vanillaTick() { // Purpur if (!Bee.this.isAngry()) { - super.tick(); + super.vanillaTick(); // Purpur } } @@ -882,6 +954,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); @@ -925,6 +998,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 @@ -974,6 +1048,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { this.setWantedPos(); } + if (this.successfulPollinatingTicks == 0) new org.purpurmc.purpur.event.entity.BeeStartedPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos)).callEvent(); // Purpur ++this.successfulPollinatingTicks; if (Bee.this.random.nextFloat() < 0.05F && this.successfulPollinatingTicks > this.lastSoundPlayedTick + 60) { this.lastSoundPlayedTick = this.successfulPollinatingTicks; diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java index 471d5727b964922d8e898be9e1d5c30f9d3bac97..4aad4fdc80070f4000e929fff126714fc67050b0 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Cat.java +++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java @@ -100,12 +100,59 @@ public class Cat extends TamableAnimal implements VariantHolder { return itemstack.is(ItemTags.CAT_FOOD); }, true); this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new TamableAnimal.TamableAnimalPanicGoal(1.5D)); this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this)); this.goalSelector.addGoal(3, new Cat.CatRelaxOnOwnerGoal(this)); @@ -118,6 +165,7 @@ public class Cat extends TamableAnimal implements VariantHolder(this, Rabbit.class, false, (TargetingConditions.Selector) null)); this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR)); } @@ -317,6 +365,14 @@ public class Cat extends TamableAnimal implements VariantHolder { return itemstack.is(ItemTags.CHICKEN_FOOD); @@ -65,6 +107,14 @@ public class Chicken extends Animal { this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); + // Purpur start + 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 @@ -73,7 +123,7 @@ public class Chicken extends Animal { } public static AttributeSupplier.Builder createAttributes() { - return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 4.0D).add(Attributes.MOVEMENT_SPEED, 0.25D); + return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 4.0D).add(Attributes.MOVEMENT_SPEED, 0.25D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur } @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 3e00bbff266fc71b07014e7e047d77b7f809239f..6b517deec01445de4205eedb1557995a92d3ae67 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Cow.java +++ b/src/main/java/net/minecraft/world/entity/animal/Cow.java @@ -37,6 +37,7 @@ import org.bukkit.event.player.PlayerBucketFillEvent; // CraftBukkit end public class Cow extends Animal { + private boolean isNaturallyAggressiveToPlayers; // Purpur private static final EntityDimensions BABY_DIMENSIONS = EntityType.COW.getDimensions().scale(0.5F).withEyeHeight(0.665F); @@ -44,18 +45,66 @@ public class Cow extends Animal { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.cowRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.cowRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.cowControllable; + } + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.cowMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.cowScale); + this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.cowNaturallyAggressiveToPlayersDamage); // Purpur + } + // Purpur end + + @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.EntitySpawnReason spawnReason, net.minecraft.world.entity.SpawnGroupData entityData) { + this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance; + return super.finalizeSpawn(world, difficulty, spawnReason, entityData); + } + + @Override + protected 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)); this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, (itemstack) -> { - return itemstack.is(ItemTags.COW_FOOD); + return level().purpurConfig.cowFeedMushrooms > 0 && (itemstack.is(net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem()) || itemstack.is(net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem())) || itemstack.is(ItemTags.COW_FOOD); // Purpur }, false)); this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25D)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (ignored, ignored2) -> isNaturallyAggressiveToPlayers)); // Purpur } @Override @@ -64,7 +113,7 @@ public class Cow extends Animal { } public static AttributeSupplier.Builder createAttributes() { - return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D); + return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur } @Override @@ -94,6 +143,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()) { @@ -102,7 +152,7 @@ public class Cow extends Animal { if (event.isCancelled()) { player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } // CraftBukkit end @@ -111,6 +161,10 @@ public class Cow extends Animal { player.setItemInHand(hand, itemstack1); return InteractionResult.SUCCESS; + // 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); } @@ -126,4 +180,66 @@ public class Cow extends Animal { public EntityDimensions getDefaultDimensions(Pose pose) { return this.isBaby() ? Cow.BABY_DIMENSIONS : super.getDefaultDimensions(pose); } + + // Purpur start - feed mushroom to change to mooshroom + private int redMushroomsFed = 0; + private int brownMushroomsFed = 0; + + private boolean isMushroom(ItemStack stack) { + return stack.getItem() == net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem() || stack.getItem() == net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem(); + } + + private int incrementFeedCount(ItemStack stack) { + if (stack.getItem() == net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem()) { + return ++redMushroomsFed; + } else { + return ++brownMushroomsFed; + } + } + + private InteractionResult feedMushroom(Player player, ItemStack stack) { + level().broadcastEntityEvent(this, (byte) 18); // hearts + playSound(SoundEvents.COW_MILK, 1.0F, 1.0F); + if (incrementFeedCount(stack) < level().purpurConfig.cowFeedMushrooms) { + if (!player.getAbilities().instabuild) { + stack.shrink(1); + } + return InteractionResult.CONSUME; // require 5 mushrooms to transform (prevents mushroom duping) + } + MushroomCow mooshroom = EntityType.MOOSHROOM.create(level(), EntitySpawnReason.CONVERSION); + if (mooshroom == null) { + return InteractionResult.PASS; + } + if (stack.getItem() == net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem()) { + mooshroom.setVariant(MushroomCow.Variant.BROWN); + } else { + mooshroom.setVariant(MushroomCow.Variant.RED); + } + mooshroom.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); + mooshroom.setHealth(this.getHealth()); + mooshroom.setAge(getAge()); + mooshroom.copyPosition(this); + mooshroom.setYBodyRot(this.yBodyRot); + mooshroom.setYHeadRot(this.getYHeadRot()); + mooshroom.yRotO = this.yRotO; + mooshroom.xRotO = this.xRotO; + if (this.hasCustomName()) { + mooshroom.setCustomName(this.getCustomName()); + } + if (CraftEventFactory.callEntityTransformEvent(this, mooshroom, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) { + return InteractionResult.PASS; + } + this.level().addFreshEntity(mooshroom); + this.remove(RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + if (!player.getAbilities().instabuild) { + stack.shrink(1); + } + for (int i = 0; i < 15; ++i) { + ((ServerLevel) level()).sendParticles(((ServerLevel) level()).players(), null, net.minecraft.core.particles.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 5af4d590a9b0f17ba53c6959d9c18bd1269878a4..c1842894f96a567707992d8ff938dbf689dd0df6 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java @@ -85,14 +85,99 @@ public class Dolphin extends AgeableWaterCreature { return !entityitem.hasPickUpDelay() && entityitem.isAlive() && entityitem.isInWater(); }; public static final float BABY_SCALE = 0.65F; + 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.dolphinScale); + } + + @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, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) { @@ -101,6 +186,7 @@ public class Dolphin extends AgeableWaterCreature { SpawnGroupData groupdataentity1 = (SpawnGroupData) Objects.requireNonNullElseGet(entityData, () -> { return new AgeableMob.AgeableMobGroupData(0.1F); }); + this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance; // Purpur return super.finalizeSpawn(world, difficulty, spawnReason, groupdataentity1); } @@ -177,17 +263,21 @@ public class Dolphin extends AgeableWaterCreature { protected void registerGoals() { this.goalSelector.addGoal(0, new BreathAirGoal(this)); this.goalSelector.addGoal(0, new TryFindWaterGoal(this)); + this.goalSelector.addGoal(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, (ignored, ignored2) -> isNaturallyAggressiveToPlayers)); // Purpur } public static AttributeSupplier.Builder createAttributes() { @@ -231,7 +321,7 @@ public class Dolphin extends AgeableWaterCreature { @Override protected boolean canRide(Entity entity) { - return true; + return boardingCooldown <= 0; // Purpur - make dolphin honor ride cooldown like all other non-boss mobs; } @Override @@ -264,6 +354,11 @@ public class Dolphin extends AgeableWaterCreature { @Override public void tick() { super.tick(); + // Purpur start + if (spitCooldown > 0) { + spitCooldown--; + } + // Purpur end if (this.isNoAi()) { this.setAirSupply(this.getMaxAirSupply()); } else { @@ -412,6 +507,7 @@ public class Dolphin extends AgeableWaterCreature { @Override public boolean canUse() { + if (this.dolphin.level().purpurConfig.dolphinDisableTreasureSearching) return false; // Purpur return this.dolphin.gotFish() && this.dolphin.getAirSupply() >= 100; } diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java index a9a8ebb2cebe668628d5bdb33fa1399e0ab1e08b..ac044be03494c3d6bad6bbc22321445e04d430cc 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Fox.java +++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java @@ -144,6 +144,65 @@ public class Fox extends Animal implements VariantHolder { this.getNavigation().setRequiredPathLength(32.0F); } + // 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.foxScale); + } + + @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(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); @@ -163,6 +222,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)); @@ -189,6 +249,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, worldserver) -> { return Fox.TRUSTED_TARGET_SELECTOR.test(entityliving) && !this.trusts(entityliving.getUUID()); })); @@ -338,6 +399,11 @@ public class Fox extends Animal implements VariantHolder { } private void setTargetGoals() { + // Purpur start - do not add duplicate goals + this.targetSelector.removeGoal(this.landTargetGoal); + this.targetSelector.removeGoal(this.turtleEggTargetGoal); + this.targetSelector.removeGoal(this.fishTargetGoal); + // Purpur end if (this.getVariant() == Fox.Variant.RED) { this.targetSelector.addGoal(4, this.landTargetGoal); this.targetSelector.addGoal(4, this.turtleEggTargetGoal); @@ -367,6 +433,7 @@ public class Fox extends Animal implements VariantHolder { public void setVariant(Fox.Variant variant) { this.entityData.set(Fox.DATA_TYPE_ID, variant.getId()); + this.setTargetGoals(); // Purpur - fix API bug not updating pathfinders on type change } List getTrustedUUIDs() { @@ -702,6 +769,29 @@ public class Fox extends Animal implements VariantHolder { } // Paper end + // Purpur start + @Override + public net.minecraft.world.InteractionResult mobInteract(Player player, net.minecraft.world.InteractionHand hand) { + if (level().purpurConfig.foxTypeChangesWithTulips) { + ItemStack itemstack = player.getItemInHand(hand); + if (getVariant() == Variant.RED && itemstack.getItem() == Items.WHITE_TULIP) { + setVariant(Variant.SNOW); + if (!player.getAbilities().instabuild) { + itemstack.shrink(1); + } + return net.minecraft.world.InteractionResult.SUCCESS; + } else if (getVariant() == Variant.SNOW && itemstack.getItem() == Items.ORANGE_TULIP) { + setVariant(Variant.RED); + if (!player.getAbilities().instabuild) { + itemstack.shrink(1); + } + return net.minecraft.world.InteractionResult.SUCCESS; + } + } + return super.mobInteract(player, hand); + } + // Purpur end + @Override // Paper start - Cancellable death event protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { @@ -754,16 +844,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 } } @@ -774,16 +864,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 } } @@ -901,8 +991,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 @@ -1288,7 +1380,7 @@ public class Fox extends Animal implements VariantHolder { } protected void onReachedTarget() { - if (getServerLevel(Fox.this.level()).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (getServerLevel(Fox.this.level()).purpurConfig.foxBypassMobGriefing ^ getServerLevel(Fox.this.level()).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur BlockState iblockdata = Fox.this.level().getBlockState(this.blockPos); if (iblockdata.is(Blocks.SWEET_BERRY_BUSH)) { diff --git a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java index e07b79ef172095c1800c88342b3ac8dc7703aea2..9a6471d2f1eb1c8af006b70b6bba0b668220fb00 100644 --- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java +++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java @@ -57,13 +57,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); } + // 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ironGolemScale); + } + // 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)); @@ -71,6 +117,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)); @@ -135,6 +182,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); } @@ -142,6 +190,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); } @@ -267,18 +316,19 @@ public class IronGolem extends AbstractGolem implements NeutralMob { ItemStack itemstack = player.getItemInHand(hand); if (!itemstack.is(Items.IRON_INGOT)) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } 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; this.playSound(SoundEvents.IRON_GOLEM_REPAIR, 1.0F, f1); itemstack.consume(1, player); + if (this.level().purpurConfig.ironGolemHealCalm && isAngry() && getHealth() == getMaxHealth()) stopBeingAngry(); // Purpur return InteractionResult.SUCCESS; } } diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java index eea02ea0d99425a60575f5fa729782494d579080..6cbe80e13a590817d3ecf27d89202f10e2cd83dc 100644 --- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java +++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java @@ -65,6 +65,43 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder optional = this.getEffectsFromItemStack(itemstack); if (optional.isEmpty()) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } itemstack.consume(1, player); @@ -195,6 +232,13 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder { world.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(0.5D), this.getZ(), 1, 0.0D, 0.0D, 0.0D, 0.0D); + // Purpur start + entitycow.copyPosition(this); + entitycow.yBodyRot = this.yBodyRot; + entitycow.setYHeadRot(this.getYHeadRot()); + entitycow.yRotO = this.yRotO; + entitycow.xRotO = this.xRotO; + // Purpur end // Paper start - custom shear drops; moved drop generation to separate method drops.forEach(drop -> { ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), drop); diff --git a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java index 0554ee499c452db6c1e6852f5022b1f197adb024..dee59cb4b87845c940ee089aa932aa69dd2539d6 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java +++ b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java @@ -65,6 +65,44 @@ public class Ocelot extends Animal { this.reassessTrustingGoals(); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.ocelotRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ocelotRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.ocelotControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ocelotMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ocelotScale); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.ocelotBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.ocelotTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.ocelotAlwaysDropExp; + } + public boolean isTrusting() { return (Boolean) this.entityData.get(Ocelot.DATA_TRUSTING); } @@ -98,12 +136,14 @@ public class Ocelot extends Animal { return itemstack.is(ItemTags.OCELOT_FOOD); }, true); this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(3, this.temptGoal); this.goalSelector.addGoal(7, new LeapAtTargetGoal(this, 0.3F)); this.goalSelector.addGoal(8, new OcelotAttackGoal(this)); this.goalSelector.addGoal(9, new BreedGoal(this, 0.8D)); this.goalSelector.addGoal(10, new WaterAvoidingRandomStrollGoal(this, 0.8D, 1.0000001E-5F)); this.goalSelector.addGoal(11, new LookAtPlayerGoal(this, Player.class, 10.0F)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Chicken.class, false)); this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, false, false, Turtle.BABY_ON_LAND_SELECTOR)); } @@ -244,7 +284,7 @@ public class Ocelot extends Animal { if (world.isUnobstructed(this) && !world.containsAnyLiquid(this.getBoundingBox())) { BlockPos blockposition = this.blockPosition(); - if (blockposition.getY() < world.getSeaLevel()) { + if (!level().purpurConfig.ocelotSpawnUnderSeaLevel && blockposition.getY() < world.getSeaLevel()) { return false; } diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java index be753557d7ebd6f1e82b1bdb6d60ecc450f72eec..83372c86bd54eedd9b136ddfcbfc67d303058c0a 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Panda.java +++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java @@ -112,6 +112,54 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.pandaScale); + 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 protected boolean canDispenserEquipIntoSlot(EquipmentSlot slot) { return slot == EquipmentSlot.MAINHAND && this.canPickUpLoot(); @@ -271,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)); @@ -288,6 +337,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])); } @@ -617,7 +667,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()) { @@ -640,7 +693,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.SUCCESS; @@ -679,12 +732,12 @@ public class Panda extends Animal { } } - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } return InteractionResult.SUCCESS_SERVER; } else { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } } @@ -729,7 +782,7 @@ public class Panda extends Animal { return itemEntity.getItem().is(ItemTags.PANDA_EATS_FROM_GROUND) && itemEntity.isAlive() && !itemEntity.hasPickUpDelay(); } - private static class PandaMoveControl extends MoveControl { + private static class PandaMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur private final Panda panda; @@ -739,9 +792,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 8883894da73c7d5975a8826d23ee7f542db98b0b..28a9d267099f6c24f71dc5a11179d59c27265a88 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java +++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java @@ -126,12 +126,89 @@ 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(PathType.DANGER_FIRE, -1.0F); this.setPathfindingMalus(PathType.DAMAGE_FIRE, -1.0F); this.setPathfindingMalus(PathType.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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.parrotScale); + } + + @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, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) { @@ -150,8 +227,11 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { @@ -294,13 +375,13 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder { @@ -156,6 +195,17 @@ public class Pig extends Animal implements ItemSteerable, Saddleable { public InteractionResult mobInteract(Player player, InteractionHand hand) { boolean flag = this.isFood(player.getItemInHand(hand)); + if (level().purpurConfig.pigGiveSaddleBack && player.isSecondaryUseActive() && !flag && isSaddled() && !isVehicle()) { + this.steering.setSaddle(false); + if (!player.getAbilities().instabuild) { + ItemStack saddle = new ItemStack(Items.SADDLE); + if (!player.getInventory().add(saddle)) { + player.drop(saddle, false); + } + } + return InteractionResult.SUCCESS; + } + if (!flag && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) { if (!this.level().isClientSide) { player.startRiding(this); diff --git a/src/main/java/net/minecraft/world/entity/animal/PolarBear.java b/src/main/java/net/minecraft/world/entity/animal/PolarBear.java index cd72d8f766069796ce1fe4a83b8646692005ff8c..86c44912a363401bdd716c22d24dfd7e92cfd0be 100644 --- a/src/main/java/net/minecraft/world/entity/animal/PolarBear.java +++ b/src/main/java/net/minecraft/world/entity/animal/PolarBear.java @@ -59,11 +59,82 @@ public class PolarBear extends Animal implements NeutralMob { private int remainingPersistentAngerTime; @Nullable private UUID persistentAngerTarget; + private int standTimer = 0; // Purpur public PolarBear(EntityType 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.polarBearScale); + } + + 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) { @@ -72,20 +143,28 @@ public class PolarBear extends Animal implements NeutralMob { @Override public boolean isFood(ItemStack stack) { - return false; + return level().purpurConfig.polarBearBreedableItem != null && stack.getItem() == level().purpurConfig.polarBearBreedableItem; // Purpur } @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 PanicGoal(this, 2.0, polarBear -> polarBear.isBaby() ? DamageTypeTags.PANIC_CAUSES : DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES)); + // 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.25)); this.goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new PolarBear.PolarBearHurtByTargetGoal()); this.targetSelector.addGoal(2, new PolarBear.PolarBearAttackPlayersGoal()); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); @@ -204,6 +283,12 @@ public class PolarBear extends Animal implements NeutralMob { if (!this.level().isClientSide) { this.updatePersistentAnger((ServerLevel)this.level(), true); } + + // Purpur start + if (isStanding() && --standTimer <= 0) { + setStanding(false); + } + // Purpur end } @Override @@ -223,6 +308,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 cdb74f86ee92ee143af29962a85d45ca585cee44..c5a39ea2ad0e5e5ac434d79c1a57e0068b8bc809 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java +++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java @@ -52,6 +52,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(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java index 8cc6022507c97af62fb2b4455198bc35744137c9..b3a0146ccfcda9fa33b91d33458086b510bb4d7b 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java +++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java @@ -88,6 +88,7 @@ public class Rabbit extends Animal implements VariantHolder { private boolean wasOnGround; private int jumpDelayTicks; public int moreCarrotTicks; + private boolean actualJump; // Purpur public Rabbit(EntityType type, Level world) { super(type, world); @@ -95,9 +96,76 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.rabbitScale); + } + + @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)); @@ -114,6 +182,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) { @@ -188,6 +264,12 @@ public class Rabbit extends Animal implements VariantHolder { @Override public void customServerAiStep(ServerLevel world) { + // Purpur start + if (getRider() != null && this.isControllable()) { + handleJumping(); + return; + } + // Purpur end if (this.jumpDelayTicks > 0) { --this.jumpDelayTicks; } @@ -402,10 +484,23 @@ public class Rabbit extends Animal implements VariantHolder { } this.setVariant(entityrabbit_variant); + + // Purpur start + if (entityrabbit_variant != Variant.EVIL && world.getLevel().purpurConfig.rabbitNaturalToast > 0D && random.nextDouble() <= world.getLevel().purpurConfig.rabbitNaturalToast) { + setCustomName(Component.translatable("Toast")); + } + // Purpur end + return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData); } private static Rabbit.Variant getRandomRabbitVariant(LevelAccessor world, BlockPos pos) { + // Purpur start + Level level = world.getMinecraftWorld(); + if (level.purpurConfig.rabbitNaturalKiller > 0D && world.getRandom().nextDouble() <= level.purpurConfig.rabbitNaturalKiller) { + return Rabbit.Variant.EVIL; + } + // Purpur end Holder holder = world.getBiome(pos); int i = world.getRandom().nextInt(100); @@ -469,7 +564,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; @@ -480,14 +575,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 @@ -549,7 +644,7 @@ public class Rabbit extends Animal implements VariantHolder { @Override public boolean canUse() { if (this.nextStartTick <= 0) { - if (!getServerLevel((Entity) this.rabbit).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (!getServerLevel((Entity) this.rabbit).purpurConfig.rabbitBypassMobGriefing == !getServerLevel((Entity) this.rabbit).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur 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 661997c39df777b6e332f0a5710e7f63a116a499..1a0c71ed6f3bd3d961f9b30ab29a62d683195355 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Salmon.java +++ b/src/main/java/net/minecraft/world/entity/animal/Salmon.java @@ -32,6 +32,33 @@ public class Salmon extends AbstractSchoolingFish implements VariantHolder { diff --git a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java index fd9f6c17448a4d87f940eb8f544ecb9669068582..13eecc676e33623e776d32495969f111bf6c0e44 100644 --- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java +++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java @@ -50,17 +50,57 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM private static final EntityDataAccessor DATA_PUMPKIN_ID = SynchedEntityData.defineId(SnowGolem.class, EntityDataSerializers.BYTE); private static final byte PUMPKIN_FLAG = 16; + @Nullable private java.util.UUID summoner; // Purpur 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.snowGolemScale); + } + + @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, worldserver) -> { return entityliving instanceof Enemy; })); @@ -80,6 +120,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 @@ -88,12 +129,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 @@ -106,10 +148,11 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM this.hurtServer(worldserver, this.damageSources().melting(), 1.0F); // CraftBukkit - DamageSources.ON_FIRE -> CraftEventFactory.MELTING } - if (!worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (!worldserver.purpurConfig.snowGolemBypassMobGriefing == !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur return; } + if (getRider() != null && this.isControllable() && !level().purpurConfig.snowGolemLeaveTrailWhenRidden) return; // Purpur - don't leave snow trail when being ridden BlockState iblockdata = Blocks.SNOW.defaultBlockState(); for (int i = 0; i < 4; ++i) { @@ -166,7 +209,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); if (event != null) { if (event.isCancelled()) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); // Paper end - custom shear drops @@ -178,8 +221,16 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM } return InteractionResult.SUCCESS; + // Purpur start + } else if (level().purpurConfig.snowGolemPutPumpkinBack && !hasPumpkin() && itemstack.getItem() == Blocks.CARVED_PUMPKIN.asItem()) { + setPumpkin(true); + if (!player.getAbilities().instabuild) { + itemstack.shrink(1); + } + return InteractionResult.SUCCESS; + // Purpur end } else { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } } 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 f9fdc600dc680c55219fcbf9bc8f151a733a093c..36a56553702fa6e4a2ac92b3639c210c94faee73 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Squid.java +++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java @@ -46,13 +46,67 @@ public class Squid extends AgeableWaterCreature { public Squid(EntityType type, Level world) { super(type, world); - //this.random.setSeed((long)this.getId()); // Paper - Share random for entities to make them more random + if (!world.purpurConfig.entitySharedRandom) this.random.setSeed((long)this.getId()); // Paper - Share random for entities to make them more random // Purpur this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F; } + // Purpur start + @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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.squidScale); + } + + @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()); } @@ -127,6 +181,7 @@ public class Squid extends AgeableWaterCreature { } if (this.isInWaterOrBubble()) { + if (canFly()) setNoGravity(!wasTouchingWater); // Purpur if (this.tentacleMovement < (float) Math.PI) { float f = this.tentacleMovement / (float) Math.PI; this.tentacleAngle = Mth.sin(f * f * (float) Math.PI) * (float) Math.PI * 0.25F; @@ -305,10 +360,41 @@ public class Squid extends AgeableWaterCreature { @Override public void tick() { + // Purpur start + net.minecraft.world.entity.player.Player rider = squid.getRider(); + if (rider != null && squid.isControllable()) { + if (rider.jumping) { + squid.onSpacebar(); + } + float forward = rider.getForwardMot(); + float strafe = rider.getStrafeMot(); + float speed = (float) squid.getAttributeValue(Attributes.MOVEMENT_SPEED) * 10F; + if (forward < 0.0F) { + speed *= -0.5; + } + org.bukkit.util.Vector dir = rider.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(speed / 20.0F); + if (strafe != 0.0F) { + if (forward == 0.0F) { + dir.setY(0); + rotateVectorAroundY(dir, strafe > 0.0F ? -90 : 90); + } else if (forward < 0.0F) { + rotateVectorAroundY(dir, strafe > 0.0F ? 45 : -45); + } else { + rotateVectorAroundY(dir, strafe > 0.0F ? -45 : 45); + } + } + if (forward != 0.0F || strafe != 0.0F) { + squid.movementVector = new Vec3((float) dir.getX(), (float) dir.getY(), (float) dir.getZ()); + } else { + squid.movementVector = Vec3.ZERO; + } + return; + } + // Purpur end int i = this.squid.getNoActionTime(); if (i > 100) { this.squid.movementVector = Vec3.ZERO; - } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.wasTouchingWater || !this.squid.hasMovementVector()) { + } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.isInWater() || !this.squid.hasMovementVector()) { // Purpur float f = this.squid.getRandom().nextFloat() * (float) (Math.PI * 2); this.squid.movementVector = new Vec3( (double)(Mth.cos(f) * 0.2F), (double)(-0.1F + this.squid.getRandom().nextFloat() * 0.2F), (double)(Mth.sin(f) * 0.2F) diff --git a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java index 8d59d606bdaaea7c64389572b2810b65414a1533..7f9d3177285f6496c4313da63f0fd0cc78266586 100644 --- a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java +++ b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java @@ -67,6 +67,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 d6605c15111dbdb6ee61a24822bc0a9aed7198d6..c9e307452a097329c26893673055cfb72a43e4c7 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java +++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java @@ -86,6 +86,44 @@ public class Turtle extends Animal { this.moveControl = new Turtle.TurtleMoveControl(this); } + // 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.turtleScale); + } + + @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); } @@ -188,6 +226,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)); @@ -349,13 +388,15 @@ public class Turtle extends Animal { return this.isBaby() ? Turtle.BABY_DIMENSIONS : super.getDefaultDimensions(pose); } - private static class TurtleMoveControl extends MoveControl { + private static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur 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() { @@ -375,7 +416,7 @@ public class Turtle extends Animal { } @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(); @@ -391,7 +432,7 @@ public class Turtle extends Animal { this.turtle.setYRot(this.rotlerp(this.turtle.getYRot(), f, 90.0F)); this.turtle.yBodyRot = this.turtle.getYRot(); - float f1 = (float) (this.speedModifier * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); + float f1 = (float) (this.getSpeedModifier() * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); this.turtle.setSpeed(Mth.lerp(0.125F, this.turtle.getSpeed(), f1)); this.turtle.setDeltaMovement(this.turtle.getDeltaMovement().add(0.0D, (double) this.turtle.getSpeed() * d1 * 0.1D, 0.0D)); diff --git a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java index 8c4532a250f8679d729a35c17e9b5bd339264450..2b8336bd88641cfb29e94c8f01abfbdb39938bf3 100644 --- a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java +++ b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java @@ -74,6 +74,6 @@ public abstract class WaterAnimal extends PathfinderMob { i = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(i); j = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(j); // Paper end - Make water animal spawn height configurable - return pos.getY() >= j && pos.getY() <= i && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER); + return ((reason == EntitySpawnReason.SPAWNER && world.getMinecraftWorld().purpurConfig.spawnerFixMC238526) || (pos.getY() >= j && pos.getY() <= i)) && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER); // Purpur } } diff --git a/src/main/java/net/minecraft/world/entity/animal/Wolf.java b/src/main/java/net/minecraft/world/entity/animal/Wolf.java index fb84ee1225cd762ef306d1fc3e1baed42c034a3c..420345f130a40c4f59a021a4bdce3e218dc87cde 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Wolf.java +++ b/src/main/java/net/minecraft/world/entity/animal/Wolf.java @@ -103,6 +103,37 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder entity instanceof net.minecraft.server.level.ServerPlayer || entity instanceof net.minecraft.world.entity.Mob; + private final net.minecraft.world.entity.ai.goal.Goal PATHFINDER_VANILLA = new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR); + private final net.minecraft.world.entity.ai.goal.Goal PATHFINDER_RABID = new NonTameRandomTargetGoal<>(this, LivingEntity.class, false, RABID_PREDICATE); + private static final class AvoidRabidWolfGoal extends AvoidEntityGoal { + private final Wolf wolf; + + public AvoidRabidWolfGoal(Wolf wolf, float distance, double minSpeed, double maxSpeed) { + super(wolf, Wolf.class, distance, minSpeed, maxSpeed); + this.wolf = wolf; + } + + @Override + public boolean canUse() { + return super.canUse() && !this.wolf.isRabid() && this.toAvoid != null && this.toAvoid.isRabid(); // wolves which are not rabid run away from rabid wolves + } + + @Override + public void start() { + this.wolf.setTarget(null); + super.start(); + } + + @Override + public void tick() { + this.wolf.setTarget(null); + super.tick(); + } + } + // Purpur end private static final float START_HEALTH = 8.0F; private static final float TAME_HEALTH = 40.0F; private static final float ARMOR_REPAIR_UNIT = 0.125F; @@ -124,12 +155,87 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder(this, Llama.class, 24.0F, 1.5D, 1.5D)); + this.goalSelector.addGoal(3, new AvoidRabidWolfGoal(this, 24.0F, 1.5D, 1.5D)); // Purpur this.goalSelector.addGoal(4, new LeapAtTargetGoal(this, 0.4F)); this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0D, true)); this.goalSelector.addGoal(6, new FollowOwnerGoal(this, 1.0D, 10.0F, 2.0F)); @@ -138,11 +244,12 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder(this, Player.class, 10, true, false, this::isAngryAt)); - this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, Wolf.PREY_SELECTOR)); + // this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, Wolf.PREY_SELECTOR)); // Purpur - moved to updatePathfinders() this.targetSelector.addGoal(6, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR)); this.targetSelector.addGoal(7, new NearestAttackableTargetGoal<>(this, AbstractSkeleton.class, false)); this.targetSelector.addGoal(8, new ResetUniversalAngerTargetGoal<>(this, true)); @@ -191,6 +298,7 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder { nbt.putString("variant", resourcekey.location().toString()); }); @@ -208,6 +316,10 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.wolfNaturalRabid; + this.updatePathfinders(false); + // Purpur end + return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData); } @@ -269,6 +387,11 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder 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(); @@ -121,6 +134,34 @@ 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 + } + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.allayMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.allayScale); + } + // Purpur end + @Override protected Brain.Provider brainProvider() { return Brain.provider(Allay.MEMORY_TYPES, Allay.SENSOR_TYPES); @@ -219,11 +260,13 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS return 0.4F; } + private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep(ServerLevel world) { ProfilerFiller gameprofilerfiller = Profiler.get(); gameprofilerfiller.push("allayBrain"); + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick(world, this); gameprofilerfiller.pop(); gameprofilerfiller.push("allayActivityUpdate"); diff --git a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java index c1ef714096159608752d744b98f615cd45fe459a..9ef53608f1608e88de7a7b3e96b43d8d2b4230ae 100644 --- a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java +++ b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java @@ -82,6 +82,34 @@ public class Armadillo extends Animal { return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 12.0D).add(Attributes.MOVEMENT_SPEED, 0.14D); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.armadilloRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.armadilloRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.armadilloControllable; + } + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.armadilloMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.armadilloScale); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.armadilloBreedingTicks; + } + // Purpur end + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java index 31b10cd404b672d7ce21c2107d8f83e32de26ef4..155f03818d91dbd4ef9f5549784472f2bffca5d8 100644 --- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java +++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java @@ -100,6 +100,44 @@ public class Axolotl extends Animal implements VariantHolder, B this.lookControl = new Axolotl.AxolotlLookControl(this, 20); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.axolotlRidable; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.axolotlControllable; + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + } + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.axolotlMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.axolotlScale); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.axolotlBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.axolotlTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.axolotlAlwaysDropExp; + } + // Purpur end + @Override public float getWalkTargetValue(BlockPos pos, LevelReader world) { return 0.0F; @@ -292,11 +330,13 @@ public class Axolotl extends Animal implements VariantHolder, B return true; } + private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep(ServerLevel world) { ProfilerFiller gameprofilerfiller = Profiler.get(); gameprofilerfiller.push("axolotlBrain"); + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick(world, this); gameprofilerfiller.pop(); gameprofilerfiller.push("axolotlActivityUpdate"); @@ -520,14 +560,22 @@ public class Axolotl extends Animal implements VariantHolder, B private static class AxolotlMoveControl extends SmoothSwimmingMoveControl { private final Axolotl axolotl; + private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur public AxolotlMoveControl(Axolotl axolotl) { super(axolotl, 85, 10, 0.1F, 0.5F, false); this.axolotl = axolotl; + waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(axolotl, 0.5D); // Purpur } @Override public void tick() { + // Purpur start + if (axolotl.getRider() != null && axolotl.isControllable()) { + waterController.purpurTick(axolotl.getRider()); + return; + } + // Purpur end if (!this.axolotl.isPlayingDead()) { super.tick(); } @@ -542,9 +590,9 @@ public class Axolotl extends Animal implements VariantHolder, B } @Override - public void tick() { + public void vanillaTick() { // Purpur if (!Axolotl.this.isPlayingDead()) { - super.tick(); + super.vanillaTick(); // Purpur } } diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java index f3c884ab9c09f04dd01cabf2ee9de3b5b620563d..dbce71f45c008da883b6d244a06343d83ae25e7e 100644 --- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java +++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java @@ -87,6 +87,17 @@ public class Camel extends AbstractHorse { navigation.setCanWalkOverFences(true); } + // Purpur start + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.camelRidableInWater; + } + + public int getPurpurBreedTime() { + return this.level().purpurConfig.camelBreedingTicks; + } + // Purpur end + @Override public void addAdditionalSaveData(CompoundTag nbt) { super.addAdditionalSaveData(nbt); @@ -314,6 +325,23 @@ public class Camel extends AbstractHorse { 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 36846ba6b6c7494c745ebd8b221479a9d02ff318..02581f47d1ca58c2b8abffc478adf48343585c8b 100644 --- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java +++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java @@ -106,6 +106,8 @@ public class Frog extends Animal implements VariantHolder> { public final AnimationState croakAnimationState = new AnimationState(); public final AnimationState tongueAnimationState = new AnimationState(); public final AnimationState swimIdleAnimationState = new AnimationState(); + private org.purpurmc.purpur.controller.MoveControllerWASD purpurLandController; // Purpur + private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurWaterController; // Purpur public Frog(EntityType type, Level world) { super(type, world); @@ -113,6 +115,58 @@ public class Frog extends Animal implements VariantHolder> { this.setPathfindingMalus(PathType.WATER, 4.0F); this.setPathfindingMalus(PathType.TRAPDOOR, -1.0F); this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); + // Purpur start + 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 + } + + // 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 @@ -184,10 +238,12 @@ public class Frog extends Animal implements VariantHolder> { .ifPresent(this::setVariant); } + private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep(ServerLevel world) { ProfilerFiller profilerFiller = Profiler.get(); profilerFiller.push("frogBrain"); + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick(world, this); profilerFiller.pop(); profilerFiller.push("frogActivityUpdate"); @@ -384,7 +440,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(final Mob entity) { super(entity); } diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java index 48ac8c3f6e00c3c2dc67b6c994be7c0ac6dfcf81..7e92e8c4fec36c8838604f17cf366cc80e82b19b 100644 --- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java +++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java @@ -51,13 +51,50 @@ public class Tadpole extends AbstractFish { protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS); protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING); public boolean ageLocked; // Paper + private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurController; // Purpur 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); @@ -83,11 +120,13 @@ public class Tadpole extends AbstractFish { return SoundEvents.TADPOLE_FLOP; } + private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep(ServerLevel world) { ProfilerFiller gameprofilerfiller = Profiler.get(); gameprofilerfiller.push("tadpoleBrain"); + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick(world, this); gameprofilerfiller.pop(); gameprofilerfiller.push("tadpoleActivityUpdate"); diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java index 76aca47d8638d5c37c57d3a59fa7f8ceaa5a53b4..d508cb2a61866c8fd7f381f8deb558ed86f7c845 100644 --- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java @@ -93,6 +93,38 @@ public class Goat extends Animal { }); } + // 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,11 +224,13 @@ public class Goat extends Animal { return (Brain) super.getBrain(); // CraftBukkit - decompile error } + private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep(ServerLevel world) { ProfilerFiller gameprofilerfiller = Profiler.get(); gameprofilerfiller.push("goatBrain"); + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick(world, this); gameprofilerfiller.pop(); gameprofilerfiller.push("goatActivityUpdate"); @@ -399,6 +433,7 @@ public class Goat extends Animal { // Paper start - Goat ram API public void ram(net.minecraft.world.entity.LivingEntity entity) { + if(!new org.purpurmc.purpur.event.entity.GoatRamEntityEvent((org.bukkit.entity.Goat) getBukkitEntity(), (org.bukkit.entity.LivingEntity) entity.getBukkitLivingEntity()).callEvent()) return; // Purpur Brain brain = this.getBrain(); brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position()); brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS); diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java index 8aed30cdbbfdd42c20dcd4c8773c8a0ee21a980d..b258be16f32ffd58ac8406deac9423cb01ca9a5c 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java @@ -228,11 +228,59 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, protected AbstractHorse(EntityType type, Level world) { super(type, world); + this.moveControl = new net.minecraft.world.entity.ai.control.MoveControl(this); // Purpur - use vanilla controller + this.lookControl = new net.minecraft.world.entity.ai.control.LookControl(this); // Purpur - use vanilla controller this.createInventory(); } + // Purpur start + @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)); @@ -243,6 +291,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(); } @@ -1269,7 +1318,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, entityData = new AgeableMob.AgeableMobGroupData(0.2F); } - this.randomizeAttributes(world.getRandom()); + // this.randomizeAttributes(world.getRandom()); // Purpur - replaced by initAttributes() return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData); } diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java index 5cafdde956d7a5b00cd5aec5c44849639307363d..dbf9fa551023cc9bf634fd5f5d504c4d689264cd 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java @@ -16,6 +16,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 b5ec7c8ad0e482930d1a54b590b26093f4e477ea..780cad91fff78bda6264cfd78ff7a408a79e8a2f 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java @@ -43,6 +43,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 18bd483fe46de3d9dc129bffbccfba9d4cab9550..8401c7ae749f6300281cbd6b2bfc77f03d5eb9ea 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java @@ -72,11 +72,86 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) { super(type, world); this.getNavigation().setRequiredPathLength(40.0F); this.maxDomestication = 30; // Paper - Missing entity API; configure max temper instead of a hardcoded value + // Purpur start + 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()); + } + // 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() { @@ -107,6 +182,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); public int time; public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals + // Purpur start + 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); @@ -43,6 +49,22 @@ public class EndCrystal extends Entity { this.setPos(x, y, z); } + public boolean shouldExplode() { + return showsBottom() ? level().purpurConfig.basedEndCrystalExplode : level().purpurConfig.baselessEndCrystalExplode; + } + + public float getExplosionPower() { + return (float) (showsBottom() ? level().purpurConfig.basedEndCrystalExplosionPower : level().purpurConfig.baselessEndCrystalExplosionPower); + } + + public boolean hasExplosionFire() { + return showsBottom() ? level().purpurConfig.basedEndCrystalExplosionFire : level().purpurConfig.baselessEndCrystalExplosionFire; + } + + public Level.ExplosionInteraction getExplosionEffect() { + return showsBottom() ? level().purpurConfig.basedEndCrystalExplosionEffect : level().purpurConfig.baselessEndCrystalExplosionEffect; + } + @Override protected Entity.MovementEmission getMovementEmission() { return Entity.MovementEmission.NONE; @@ -80,8 +102,52 @@ public class EndCrystal extends Entity { } } // Paper end - Fix invulnerable end crystals + if (this.level().purpurConfig.endCrystalCramming > 0 && this.level().getEntitiesOfClass(EndCrystal.class, getBoundingBox()).size() > this.level().purpurConfig.endCrystalCramming) this.hurt(this.damageSources().cramming(), 6.0F); // Purpur + } + + // Purpur start + 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 @@ -128,16 +194,18 @@ public class EndCrystal extends Entity { } // CraftBukkit end if (!source.is(DamageTypeTags.IS_EXPLOSION)) { + if (shouldExplode()) {// Purpur DamageSource damagesource1 = source.getEntity() != null ? this.damageSources().explosion(this, source.getEntity()) : null; // CraftBukkit start - ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false); + ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, getExplosionPower(), hasExplosionFire()); // Purpur if (event.isCancelled()) { return false; } this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause - world.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK); + world.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), getExplosionEffect()); // Purpur + } else this.unsetRemoved(); // Purpur } else { this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause } diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java index 2df8bf818345246cc1f88b93e4a3b62e61772efb..c9e3bb91ff506a35551a58f469ad2849e6058668 100644 --- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java @@ -108,6 +108,7 @@ public class EnderDragon extends Mob implements Enemy { @Nullable private BlockPos podium; // Paper end - Allow changing the EnderDragon podium + private boolean hadRider; // Purpur public EnderDragon(EntityType entitytypes, Level world) { super(EntityType.ENDER_DRAGON, world); @@ -129,6 +130,37 @@ public class EnderDragon extends Mob implements Enemy { this.noPhysics = true; this.phaseManager = new EnderDragonPhaseManager(this); this.explosionSource = new ServerExplosion(world.getMinecraftWorld(), this, null, null, new Vec3(Double.NaN, Double.NaN, Double.NaN), Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit + + // Purpur start + 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) { @@ -143,6 +175,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); } @@ -184,6 +237,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()); @@ -210,6 +294,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; @@ -222,9 +308,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; @@ -240,7 +326,7 @@ public class EnderDragon extends Mob implements Enemy { float f4; float f5; - if (world1 instanceof ServerLevel) { + if (world1 instanceof ServerLevel && !hasRider) { // Purpur ServerLevel worldserver1 = (ServerLevel) world1; DragonPhaseInstance idragoncontroller = this.phaseManager.getCurrentPhase(); @@ -326,7 +412,7 @@ public class EnderDragon extends Mob implements Enemy { if (world2 instanceof ServerLevel) { ServerLevel worldserver2 = (ServerLevel) world2; - if (this.hurtTime == 0) { + if (!hasRider && this.hurtTime == 0) { // Purpur this.knockBack(worldserver2, worldserver2.getEntities((Entity) this, this.wing1.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); this.knockBack(worldserver2, worldserver2.getEntities((Entity) this, this.wing2.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); this.hurt(worldserver2, worldserver2.getEntities((Entity) this, this.head.getBoundingBox().inflate(1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR)); @@ -374,7 +460,7 @@ public class EnderDragon extends Mob implements Enemy { if (world3 instanceof ServerLevel) { ServerLevel worldserver3 = (ServerLevel) world3; - this.inWall = this.checkWalls(worldserver3, this.head.getBoundingBox()) | this.checkWalls(worldserver3, this.neck.getBoundingBox()) | this.checkWalls(worldserver3, this.body.getBoundingBox()); + this.inWall = !hasRider && this.checkWalls(worldserver3, this.head.getBoundingBox()) | this.checkWalls(worldserver3, this.neck.getBoundingBox()) | this.checkWalls(worldserver3, this.body.getBoundingBox()); // Purpur if (this.dragonFight != null) { this.dragonFight.updateDragon(this); } @@ -510,7 +596,7 @@ public class EnderDragon extends Mob implements Enemy { BlockState iblockdata = world.getBlockState(blockposition); if (!iblockdata.isAir() && !iblockdata.is(BlockTags.DRAGON_TRANSPARENT)) { - if (world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) { + if ((world.purpurConfig.enderDragonBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) { // Purpur // CraftBukkit start - Add blocks to list rather than destroying them // flag1 = worldserver.removeBlock(blockposition, false) || flag1; flag1 = true; @@ -651,7 +737,7 @@ public class EnderDragon extends Mob implements Enemy { boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT); short short0 = 500; - if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) { + if (this.dragonFight != null && (level().purpurConfig.enderDragonAlwaysDropsFullExp || !this.dragonFight.hasPreviouslyKilledDragon())) { short0 = 12000; } @@ -1069,6 +1155,7 @@ public class EnderDragon extends Mob implements Enemy { @Override protected boolean canRide(Entity entity) { + if (this.level().purpurConfig.enderDragonCanRideVehicles) return this.boardingCooldown <= 0; // Purpur return false; } @@ -1095,6 +1182,6 @@ public class EnderDragon extends Mob implements Enemy { @Override protected float sanitizeScale(float scale) { - return 1.0F; + return 1.0F; // Purpur - diff on change } } 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 244e38db508efa3eebebb6392c4ebb0805367baf..5e134b3cf939fe396c060797883613812ad73c31 100644 --- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java @@ -86,20 +86,60 @@ public class WitherBoss extends Monster implements RangedAttackMob { return !entityliving.getType().is(EntityTypeTags.WITHER_FRIENDS) && entityliving.attackable(); }; private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR); + @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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witherScale); + } + + @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); @@ -110,13 +150,114 @@ public class WitherBoss extends Monster implements 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); + Vec3 vec3d = new Vec3(x - headX, y - headY, z - headZ); + WitherSkull skull = new WitherSkull(level(), this, vec3d.normalize()); + skull.setPosRaw(headX, headY, headZ); + level().addFreshEntity(skull); + } + // Purpur end + @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)); } @@ -134,6 +275,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { public void addAdditionalSaveData(CompoundTag nbt) { super.addAdditionalSaveData(nbt); nbt.putInt("Invul", this.getInvulnerableTicks()); + if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur } @Override @@ -143,6 +285,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { if (this.hasCustomName()) { this.bossEvent.setName(this.getDisplayName()); } + if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur } @@ -152,6 +295,13 @@ public class WitherBoss extends Monster implements RangedAttackMob { this.bossEvent.setName(this.getDisplayName()); } + // Pufferfish start - optimize suffocation + @Override + public boolean shouldCheckForSuffocation() { + return true; + } + // Pufferfish end + @Override protected SoundEvent getAmbientSound() { return SoundEvents.WITHER_AMBIENT; @@ -261,6 +411,15 @@ public class WitherBoss extends Monster implements RangedAttackMob { @Override protected void customServerAiStep(ServerLevel world) { + // 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) { @@ -277,7 +436,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { } // CraftBukkit end - if (!this.isSilent()) { + if (!this.isSilent() && level().purpurConfig.witherPlaySpawnSound) { // CraftBukkit start - Use relative location for far away sounds // worldserver.globalLevelEvent(1023, new BlockPosition(this), 0); int viewDistance = world.getCraftServer().getViewDistance() * 16; @@ -302,7 +461,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { this.setInvulnerableTicks(i); if (this.tickCount % 10 == 0) { - this.heal(10.0F, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit + this.heal(this.getMaxHealth() / 33, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit // Purpur } } else { @@ -362,7 +521,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { if (this.destroyBlocksTick > 0) { --this.destroyBlocksTick; - if (this.destroyBlocksTick == 0 && world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (this.destroyBlocksTick == 0 && (world.purpurConfig.witherBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur boolean flag = false; j = Mth.floor(this.getBbWidth() / 2.0F + 1.0F); @@ -389,8 +548,10 @@ public class WitherBoss extends Monster implements RangedAttackMob { } } - if (this.tickCount % 20 == 0) { - this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit + // Purpur start - customizable heal rate and amount + if (this.tickCount % level().purpurConfig.witherHealthRegenDelay == 0) { + this.heal(level().purpurConfig.witherHealthRegenAmount, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit + // Purpur end } this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth()); @@ -579,11 +740,11 @@ public class WitherBoss extends Monster implements RangedAttackMob { } public int getAlternativeTarget(int headIndex) { - return (Integer) this.entityData.get((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex)); + return getRider() != null && this.isControllable() ? 0 : this.entityData.get(WitherBoss.DATA_TARGETS.get(headIndex)); // Purpur } 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 } public boolean isPowered() { @@ -592,6 +753,7 @@ public class WitherBoss extends Monster implements RangedAttackMob { @Override protected boolean canRide(Entity entity) { + if (this.level().purpurConfig.witherCanRideVehicles) return this.boardingCooldown <= 0; // Purpur return false; } diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java index 70b8023c3badc745f342d5b0ab54699e3923826a..037586c0fdb42a02660aba89dd741a647c67e52b 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java @@ -114,10 +114,12 @@ public class ArmorStand extends LivingEntity { private boolean noTickPoseDirty = false; private boolean noTickEquipmentDirty = false; // Paper end - Allow ArmorStands not to tick + public boolean canMovementTick = true; // Purpur public ArmorStand(EntityType type, Level world) { super(type, world); if (world != null) this.canTick = world.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick + if (world != null) this.canMovementTick = world.purpurConfig.armorstandMovement; // Purpur this.handItems = NonNullList.withSize(2, ItemStack.EMPTY); this.armorItems = NonNullList.withSize(4, ItemStack.EMPTY); this.headPose = ArmorStand.DEFAULT_HEAD_POSE; @@ -126,6 +128,7 @@ public class ArmorStand extends LivingEntity { this.rightArmPose = ArmorStand.DEFAULT_RIGHT_ARM_POSE; this.leftLegPose = ArmorStand.DEFAULT_LEFT_LEG_POSE; this.rightLegPose = ArmorStand.DEFAULT_RIGHT_LEG_POSE; + this.setShowArms(world != null && world.purpurConfig.armorstandPlaceWithArms); // Purpur } public ArmorStand(Level world, double x, double y, double z) { @@ -618,6 +621,7 @@ public class ArmorStand extends LivingEntity { private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(ServerLevel world, DamageSource damageSource) { // Paper ItemStack itemstack = new ItemStack(Items.ARMOR_STAND); + if (this.level().purpurConfig.persistentDroppableEntityDisplayNames) itemstack.set(DataComponents.CUSTOM_NAME, this.getCustomName()); this.drops.add(new DefaultDrop(itemstack, stack -> Block.popResource(this.level(), this.blockPosition(), stack))); // CraftBukkit - add to drops // Paper - Restore vanilla drops behavior return this.brokenByAnything(world, damageSource); // Paper @@ -681,6 +685,7 @@ public class ArmorStand extends LivingEntity { @Override public void tick() { + maxUpStep = level().purpurConfig.armorstandStepHeight; // Paper start - Allow ArmorStands not to tick if (!this.canTick) { if (this.noTickPoseDirty) { @@ -1013,4 +1018,18 @@ public class ArmorStand extends LivingEntity { } } // Paper end + + // Purpur start + @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 bbdaaa1cc0b4aed28bc39385508d221055b99d4d..bd5e034ce58ebe53d2121209d76ae60134ce72fe 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java @@ -240,7 +240,13 @@ public class ItemFrame extends HangingEntity { } if (dropSelf) { - this.spawnAtLocation(world, this.getFrameItemStack()); + // Purpur start + final ItemStack itemFrame = this.getFrameItemStack(); + if (!this.level().purpurConfig.persistentDroppableEntityDisplayNames) { + itemFrame.set(DataComponents.CUSTOM_NAME, null); + } + this.spawnAtLocation(world, itemFrame); + // Purpur end } if (!itemstack.isEmpty()) { diff --git a/src/main/java/net/minecraft/world/entity/decoration/Painting.java b/src/main/java/net/minecraft/world/entity/decoration/Painting.java index fd0e78a2318e3950d011c17358245e107b38154a..0fcab828e81176323cbdf16c0ec714d9a2846ae5 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/Painting.java +++ b/src/main/java/net/minecraft/world/entity/decoration/Painting.java @@ -179,7 +179,13 @@ public class Painting extends HangingEntity implements VariantHolder type, Level world) { super(type, world); @@ -384,7 +390,16 @@ public class ItemEntity extends Entity implements TraceableEntity { @Override public final boolean hurtServer(ServerLevel world, DamageSource source, float amount) { - if (this.isInvulnerableToBase(source)) { + // Purpur start + if ( + (immuneToCactus && source.is(net.minecraft.world.damagesource.DamageTypes.CACTUS)) || + (immuneToFire && (source.is(net.minecraft.tags.DamageTypeTags.IS_FIRE) || source.is(net.minecraft.world.damagesource.DamageTypes.ON_FIRE) || source.is(net.minecraft.world.damagesource.DamageTypes.IN_FIRE))) || + (immuneToLightning && source.is(net.minecraft.world.damagesource.DamageTypes.LIGHTNING_BOLT)) || + (immuneToExplosion && source.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION)) + ) { + return false; + } else if (this.isInvulnerableToBase(source)) { + // Purpur end return false; } else if (!world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && source.getEntity() instanceof Mob) { return false; @@ -595,6 +610,12 @@ public class ItemEntity extends Entity implements TraceableEntity { public void setItem(ItemStack stack) { this.getEntityData().set(ItemEntity.DATA_ITEM, stack); this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate + // Purpur start + 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 de87483600e55d88176fe25db621bbd3e464729f..287ba483614e79e78022e703ef891f59f41ac455 100644 --- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java @@ -249,4 +249,31 @@ public class PrimedTnt extends Entity implements TraceableEntity { return !level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid(); } // Paper end - Option to prevent TNT from moving in water + // Purpur start - Shears can defuse TNT + @Override + public net.minecraft.world.InteractionResult interact(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) { + Level world = this.level(); + + if (world instanceof ServerLevel serverWorld && level().purpurConfig.shearsCanDefuseTnt) { + final net.minecraft.world.item.ItemStack inHand = player.getItemInHand(hand); + + if (!inHand.is(net.minecraft.world.item.Items.SHEARS) || !player.getBukkitEntity().hasPermission("purpur.tnt.defuse") || + serverWorld.random.nextFloat() > serverWorld.purpurConfig.shearsCanDefuseTntChance) return net.minecraft.world.InteractionResult.PASS; + + net.minecraft.world.entity.item.ItemEntity tntItem = new net.minecraft.world.entity.item.ItemEntity(serverWorld, getX(), getY(), getZ(), + new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.TNT)); + tntItem.setPickUpDelay(10); + + inHand.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand)); + serverWorld.addFreshEntity(tntItem, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CUSTOM); + + this.playSound(net.minecraft.sounds.SoundEvents.SHEEP_SHEAR); + + this.kill(serverWorld); + return net.minecraft.world.InteractionResult.SUCCESS; + } + + return super.interact(player, hand); + } + // Purpur end - Shears can defuse TNT } diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java index 32670a3cb4b54b66d655197e3fde834d2b2b6d34..39d02cf0e31832e30c4f034b0b5385e3e0057e60 100644 --- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java @@ -68,16 +68,19 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo protected AbstractSkeleton(EntityType type, Level world) { super(type, world); this.reassessWeaponGoal(); + this.setShouldBurnInDay(true); // Purpur - API for any mob to burn daylight } @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur 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)); @@ -96,37 +99,14 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo abstract SoundEvent getStepSound(); // Paper start - shouldBurnInDay API - private boolean shouldBurnInDay = true; + //private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity; keep methods for ABI compatibility - API for any mob to burn daylight public boolean shouldBurnInDay() { return shouldBurnInDay; } public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Paper end - shouldBurnInDay API @Override public void aiStep() { - boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - shouldBurnInDay API - - if (flag) { - ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); - - if (!itemstack.isEmpty()) { - if (itemstack.isDamageableItem()) { - Item item = itemstack.getItem(); - - itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); - if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { - this.onEquippedItemBroken(item, EquipmentSlot.HEAD); - this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); - } - } - - flag = false; - } - - if (flag) { - this.igniteForSeconds(8.0F); - } - } - + // Purpur - implemented in LivingEntity - API for any mob to burn daylight super.aiStep(); } @@ -158,11 +138,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo this.reassessWeaponGoal(); this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - Add world settings for mobs picking up loot if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { - LocalDate localdate = LocalDate.now(); - int i = localdate.get(ChronoField.DAY_OF_MONTH); - int j = localdate.get(ChronoField.MONTH_OF_YEAR); - - if (j == 10 && i == 31 && randomsource.nextFloat() < 0.25F) { + if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(world.getMinecraftWorld()) && this.random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(randomsource.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN)); this.armorDropChances[EquipmentSlot.HEAD.getIndex()] = 0.0F; } @@ -221,7 +197,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo } if (event.getProjectile() == entityarrow.getBukkitEntity()) { - Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - worldserver.getDifficulty().getId() * 4)); + Projectile.spawnProjectileUsingShoot(entityarrow, worldserver, itemstack1, d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, this.level().purpurConfig.skeletonBowAccuracyMap.getOrDefault(this.level().getDifficulty().getId(), (float) (14 - this.level().getDifficulty().getId() * 4))); // Purpur } // CraftBukkit end } @@ -243,7 +219,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo super.readAdditionalSaveData(nbt); this.reassessWeaponGoal(); // Paper start - shouldBurnInDay API - if (nbt.contains("Paper.ShouldBurnInDay")) { + if (false && nbt.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); } // Paper end - shouldBurnInDay API @@ -253,7 +229,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo @Override public void addAdditionalSaveData(CompoundTag nbt) { super.addAdditionalSaveData(nbt); - nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); + //nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - implemented in LivingEntity - API for any mob to burn daylight } // Paper end - shouldBurnInDay API diff --git a/src/main/java/net/minecraft/world/entity/monster/Blaze.java b/src/main/java/net/minecraft/world/entity/monster/Blaze.java index e33fa82ca1332b95bb067fd621212d3026eee1b7..07db4557ab0d7a4a0f5432257bd18195d2de7255 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Blaze.java +++ b/src/main/java/net/minecraft/world/entity/monster/Blaze.java @@ -33,26 +33,74 @@ public class Blaze extends Monster { public Blaze(EntityType type, Level world) { super(type, world); - this.setPathfindingMalus(PathType.WATER, -1.0F); + this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); // Purpur + if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur this.setPathfindingMalus(PathType.LAVA, 8.0F); this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F); this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F); this.xpReward = 10; } + // Purpur start + @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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.blazeScale); + } + + @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.0)); this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0, 0.0F)); this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); } public static AttributeSupplier.Builder createAttributes() { - return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0).add(Attributes.MOVEMENT_SPEED, 0.23F).add(Attributes.FOLLOW_RANGE, 48.0); + return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0).add(Attributes.MOVEMENT_SPEED, 0.23F).add(Attributes.FOLLOW_RANGE, 48.0).add(Attributes.FLYING_SPEED, 0.6D); // Purpur } @Override @@ -112,11 +160,18 @@ public class Blaze extends Monster { @Override public boolean isSensitiveToWater() { - return true; + return this.level().purpurConfig.blazeTakeDamageFromWater; // Purpur } @Override protected void customServerAiStep(ServerLevel world) { + // 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/Bogged.java b/src/main/java/net/minecraft/world/entity/monster/Bogged.java index 975477663b6d76a69c006a89e440e21471b39b89..ca63ab37bc6b5b4cb5abf2848dae476b5d937f2a 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Bogged.java +++ b/src/main/java/net/minecraft/world/entity/monster/Bogged.java @@ -44,6 +44,29 @@ public class Bogged extends AbstractSkeleton implements Shearable { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.boggedRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.boggedRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.boggedControllable; + } + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.boggedMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.boggedScale); + } + // Purpur end + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); diff --git a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java index 4e621a7f36b3d718695434a2a4e3060283667bb2..9a274b83a3a7726cac421856dbc7be01de45d29b 100644 --- a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java +++ b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java @@ -27,6 +27,39 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.caveSpiderScale); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.caveSpiderTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.caveSpiderAlwaysDropExp; + } + @Override public boolean doHurtTarget(ServerLevel world, Entity target) { if (super.doHurtTarget(world, target)) { diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java index 1906dfc22af208d6e801ad4a8f2f9e9702432691..38cbe2fce9c36195aa9bea2af26d14364b216825 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java +++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java @@ -60,21 +60,99 @@ public class Creeper extends Monster { public int explosionRadius = 3; private int droppedSkulls; public Entity entityIgniter; // CraftBukkit + // Purpur start + private int spacebarCharge = 0; + private int prevSpacebarCharge = 0; + private int powerToggleDelay = 0; + // Purpur end + private boolean exploding = false; // Purpur - Config to make Creepers explode on death 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(ServerLevel world) { + if (powerToggleDelay > 0) { + powerToggleDelay--; + } + if (getRider() != null && this.isControllable()) { + if (getRider().getForwardMot() != 0 || getRider().getStrafeMot() != 0) { + spacebarCharge = 0; + setIgnited(false); + setSwellDir(-1); + } + if (spacebarCharge == prevSpacebarCharge) { + spacebarCharge = 0; + } + prevSpacebarCharge = spacebarCharge; + } + super.customServerAiStep(world); + } + + @Override + public void onMount(Player rider) { + super.onMount(rider); + setIgnited(false); + setSwellDir(-1); + } + + @Override + public boolean onSpacebar() { + if (powerToggleDelay > 0) { + return true; // just toggled power, do not jump or ignite + } + spacebarCharge++; + if (spacebarCharge > maxSwell - 2) { + spacebarCharge = 0; + if (getRider() != null && getRider().getBukkitEntity().hasPermission("allow.powered.creeper")) { + powerToggleDelay = 20; + setPowered(!isPowered()); + setIgnited(false); + setSwellDir(-1); + return true; + } + } + if (!isIgnited()) { + if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0 && + getRider().getBukkitEntity().hasPermission("allow.special.creeper")) { + setIgnited(true); + setSwellDir(1); + return true; + } + } + return getForwardMot() == 0 && getStrafeMot() == 0; // do not jump if standing still + } + // Purpur end + @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])); } @@ -174,6 +252,40 @@ public class Creeper extends Monster { } } + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.creeperMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.creeperScale); + } + + public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.EntitySpawnReason spawnReason, @Nullable net.minecraft.world.entity.SpawnGroupData entityData) { + double chance = world.getLevel().purpurConfig.creeperChargedChance; + if (chance > 0D && random.nextDouble() <= chance) { + setPowered(true); + } + return super.finalizeSpawn(world, difficulty, spawnReason, entityData); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.creeperTakeDamageFromWater; + } + + // Purpur start - Config to make Creepers explode on death + @Override + protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) { + if (!this.exploding && this.level().purpurConfig.creeperExplodeWhenKilled && damageSource.getEntity() instanceof net.minecraft.server.level.ServerPlayer) { + this.explodeCreeper(); + } + return super.dropAllDeathLoot(world, damageSource); + } + // Purpur end - Config to make Creepers explode on death + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.creeperAlwaysDropExp; + } + @Override protected SoundEvent getHurtSound(DamageSource source) { return SoundEvents.CREEPER_HURT; @@ -262,16 +374,18 @@ public class Creeper extends Monster { public void explodeCreeper() { Level world = this.level(); + this.exploding = true; // Purpur - Config to make Creepers explode on death if (world instanceof ServerLevel worldserver) { float f = this.isPowered() ? 2.0F : 1.0F; + float multiplier = worldserver.purpurConfig.creeperHealthRadius ? this.getHealth() / this.getMaxHealth() : 1; // Purpur // CraftBukkit start - ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, this.explosionRadius * f, false); + ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, (this.explosionRadius * f) * multiplier, false); // Purpur if (!event.isCancelled()) { // CraftBukkit end this.dead = true; - worldserver.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) + worldserver.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), worldserver.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) && level().purpurConfig.creeperAllowGriefing ? Level.ExplosionInteraction.MOB : Level.ExplosionInteraction.NONE); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) // Purpur this.spawnLingeringCloud(); this.triggerOnDeathMobEffects(worldserver, Entity.RemovalReason.KILLED); this.discard(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause @@ -283,6 +397,7 @@ public class Creeper extends Monster { // CraftBukkit end } + this.exploding = false; // Purpur - Config to make Creepers explode on death } private void spawnLingeringCloud() { @@ -324,6 +439,7 @@ public class Creeper extends Monster { com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited); if (event.callEvent()) { this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited()); + if (!event.isIgnited()) setSwellDir(-1); // Purpur } } // Paper end - CreeperIgniteEvent diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java index 2e73917ce9270de7483bb1d4e9bf312a31ec9b1e..949207eda199c874f2f14074b5a4fff5462b86b9 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java +++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java @@ -72,6 +72,59 @@ public class Drowned extends Zombie implements RangedAttackMob { return Zombie.createAttributes().add(Attributes.STEP_HEIGHT, 1.0); } + // 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.drownedScale); + } + + @Override + protected void randomizeReinforcementsChance() { + this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.drownedSpawnReinforcements); + } + + @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.0)); @@ -79,10 +132,23 @@ public class Drowned extends Zombie implements RangedAttackMob { this.goalSelector.addGoal(2, new Drowned.DrownedAttackGoal(this, 1.0, false)); this.goalSelector.addGoal(5, new Drowned.DrownedGoToBeachGoal(this, 1.0)); this.goalSelector.addGoal(6, new Drowned.DrownedSwimUpGoal(this, 1.0, this.level().getSeaLevel())); + if (level().purpurConfig.drownedBreakDoors) this.goalSelector.addGoal(6, new net.minecraft.world.entity.ai.goal.MoveThroughVillageGoal(this, 1.0D, true, 4, this::canBreakDoors)); this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0)); this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Drowned.class).setAlertOthers(ZombifiedPiglin.class)); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (target, world) -> this.okTarget(target))); - if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper - Check drowned for villager aggression config + // Purpur start + if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false) { // Paper - Check drowned for villager aggression config + @Override + public boolean canUse() { + return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canUse(); + } + + @Override + public boolean canContinueToUse() { + return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canContinueToUse(); + } + }); + // Purpur end this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false)); this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); @@ -396,7 +462,7 @@ public class Drowned extends Zombie implements RangedAttackMob { } } - static class DrownedMoveControl extends MoveControl { + static class DrownedMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur private final Drowned drowned; public DrownedMoveControl(Drowned drowned) { @@ -405,7 +471,7 @@ public class Drowned extends Zombie implements RangedAttackMob { } @Override - public void tick() { + public void vanillaTick() { // Purpur LivingEntity livingEntity = this.drowned.getTarget(); if (this.drowned.wantsToSwim() && this.drowned.isInWater()) { if (livingEntity != null && livingEntity.getY() > this.drowned.getY() || this.drowned.searchingForLand) { @@ -425,7 +491,7 @@ public class Drowned extends Zombie implements RangedAttackMob { float h = (float)(Mth.atan2(f, d) * 180.0F / (float)Math.PI) - 90.0F; this.drowned.setYRot(this.rotlerp(this.drowned.getYRot(), h, 90.0F)); this.drowned.yBodyRot = this.drowned.getYRot(); - float i = (float)(this.speedModifier * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); + float i = (float)(this.getSpeedModifier() * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur float j = Mth.lerp(0.125F, this.drowned.getSpeed(), i); this.drowned.setSpeed(j); this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add((double)j * d * 0.005, (double)j * e * 0.1, (double)j * f * 0.005)); @@ -434,7 +500,7 @@ public class Drowned extends Zombie implements RangedAttackMob { this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(0.0, -0.008, 0.0)); } - super.tick(); + super.vanillaTick(); // Purpur } } } diff --git a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java index 378694a38115c012978e1fea59d049d1ebd04110..a000304e3ac4c34b020f7457aa2589c87f140d8c 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,34 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.elderGuardianScale); + } + + @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 48dcd2bc12ce1d08cc5195bff5460dc0dd9902d3..476aaa0d9899eb56ea29b1fcf15565b58f78479d 100644 --- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java @@ -92,12 +92,41 @@ public class EnderMan extends Monster implements NeutralMob { public EnderMan(EntityType type, Level world) { super(type, world); - this.setPathfindingMalus(PathType.WATER, -1.0F); + if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur + } + + // 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.endermanScale); + } + + @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)); @@ -105,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, ignored) -> entityliving.level().purpurConfig.endermanAggroEndermites && entityliving instanceof Endermite endermite && (!entityliving.level().purpurConfig.endermanAggroEndermitesOnlyIfPlayerSpawned || endermite.isPlayerSpawned()))); // Purpur this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal<>(this, false)); } @@ -235,7 +265,7 @@ public class EnderMan extends Monster implements NeutralMob { boolean isBeingStaredBy(Player player) { // Paper start - EndermanAttackPlayerEvent - boolean shouldAttack = isBeingStaredBy0(player); + boolean shouldAttack = !this.level().purpurConfig.endermanDisableStareAggro && isBeingStaredBy0(player); // Purpur com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) player.getBukkitEntity()); event.setCancelled(!shouldAttack); return event.callEvent(); @@ -263,12 +293,12 @@ public class EnderMan extends Monster implements NeutralMob { @Override public boolean isSensitiveToWater() { - return true; + return this.level().purpurConfig.endermanTakeDamageFromWater; // Purpur } @Override protected void customServerAiStep(ServerLevel world) { - if (world.isDay() && this.tickCount >= this.targetChangeTime + 600) { + if ((getRider() == null || !this.isControllable()) && world.isDay() && this.tickCount >= this.targetChangeTime + 600) { // Purpur - no random teleporting float f = this.getLightLevelDependentMagicValue(); if (f > 0.5F && world.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper - EndermanEscapeEvent @@ -307,11 +337,17 @@ public class EnderMan extends Monster implements NeutralMob { private boolean teleport(double x, double y, double z) { BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(x, y, z); - while (blockposition_mutableblockposition.getY() > this.level().getMinY() && !this.level().getBlockState(blockposition_mutableblockposition).blocksMotion()) { + // Pufferfish start - single chunk lookup + net.minecraft.world.level.chunk.LevelChunk chunk = this.level().getChunkIfLoaded(blockposition_mutableblockposition); + if (chunk == null) { + return false; + } + // Pufferfish end + while (blockposition_mutableblockposition.getY() > this.level().getMinY() && !chunk.getBlockState(blockposition_mutableblockposition).blocksMotion()) { // Pufferfish blockposition_mutableblockposition.move(Direction.DOWN); } - BlockState iblockdata = this.level().getBlockState(blockposition_mutableblockposition); + BlockState iblockdata = chunk.getBlockState(blockposition_mutableblockposition); // Pufferfish boolean flag = iblockdata.blocksMotion(); boolean flag1 = iblockdata.getFluidState().is(FluidTags.WATER); @@ -383,6 +419,8 @@ public class EnderMan extends Monster implements NeutralMob { public boolean hurtServer(ServerLevel world, DamageSource source, float amount) { if (this.isInvulnerableTo(world, source)) { return false; + } else if (getRider() != null && this.isControllable()) { return super.hurtServer(world, source, amount); // Purpur - no teleporting on damage + } else if (org.purpurmc.purpur.PurpurConfig.endermanShortHeight && source.is(net.minecraft.world.damagesource.DamageTypes.IN_WALL)) { return false; // Purpur - no suffocation damage if short height } else { boolean flag = source.getDirectEntity() instanceof ThrownPotion; boolean flag1; @@ -397,6 +435,7 @@ public class EnderMan extends Monster implements NeutralMob { } else { flag1 = flag && this.hurtWithCleanWater(world, source, (ThrownPotion) source.getDirectEntity(), amount); + if (!flag1 && world.purpurConfig.endermanIgnoreProjectiles) return super.hurtServer(world, source, amount); // Purpur if (this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.INDIRECT)) { // Paper - EndermanEscapeEvent for (int i = 0; i < 64; ++i) { if (this.teleport()) { @@ -441,7 +480,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 { @@ -488,7 +527,16 @@ public class EnderMan extends Monster implements NeutralMob { @Override public boolean canUse() { - return this.enderman.getCarriedBlock() == null ? false : (!getServerLevel((Entity) this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0); + if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur + // Purpur start + if (this.enderman.getCarriedBlock() == null) { + return false; + } + if (!getServerLevel((Entity) this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) == !this.enderman.level().purpurConfig.endermanBypassMobGriefing) { + return false; + } + return this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0; + // Purpur end } @Override @@ -533,7 +581,16 @@ public class EnderMan extends Monster implements NeutralMob { @Override public boolean canUse() { - return this.enderman.getCarriedBlock() != null ? false : (!getServerLevel((Entity) this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0); + if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur + // Purpur start + if (this.enderman.getCarriedBlock() != null) { + return false; + } + if (!getServerLevel((Entity) this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) == !this.enderman.level().purpurConfig.endermanBypassMobGriefing) { + return false; + } + return this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0; + // Purpur end } @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Endermite.java b/src/main/java/net/minecraft/world/entity/monster/Endermite.java index 534e98dd7291e09dee1d0f77cbf221b15708590f..f8373fc9839fccb31e3dd090de70e2cd7c9e6cfc 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Endermite.java +++ b/src/main/java/net/minecraft/world/entity/monster/Endermite.java @@ -32,20 +32,64 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.endermiteScale); + } + + @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, new Class[0])).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); } @@ -83,12 +127,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 6592baa53ecb4e364d1c1b6f64178fc86c59a982..e231bb33b7e6a00d7c1a6c3540b4b48cfd4c15e3 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Evoker.java +++ b/src/main/java/net/minecraft/world/entity/monster/Evoker.java @@ -53,10 +53,44 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.evokerScale); + } + + @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(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D)); @@ -66,6 +100,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)); @@ -342,7 +377,7 @@ public class Evoker extends SpellcasterIllager { } else { ServerLevel worldserver = getServerLevel(Evoker.this.level()); - if (!worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (!worldserver.purpurConfig.evokerBypassMobGriefing == !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur return false; } else { List list = worldserver.getNearbyEntities(Sheep.class, this.wololoTargeting, Evoker.this, Evoker.this.getBoundingBox().inflate(16.0D, 4.0D, 16.0D)); diff --git a/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java index a8c8c03e972aa6352843cf4c3e4aebfb8f493125..173b10fa553db30c321bfd9eabe13915b63cf920 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, worldserver) -> { return Math.abs(entityliving.getY() - this.getY()) <= 4.0D; })); @@ -96,6 +132,22 @@ public class Ghast extends FlyingMob implements Enemy { } } + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ghastMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ghastScale); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.ghastTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.ghastAlwaysDropExp; + } + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); @@ -103,7 +155,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 @@ -155,7 +207,7 @@ public class Ghast extends FlyingMob implements Enemy { } - private static class GhastMoveControl extends MoveControl { + private static class GhastMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur private final Ghast ghast; private int floatDuration; @@ -166,7 +218,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 118521ae54254b0a73bb7cba7b2871c9c26f89fc..859d316825658c11f58dd92912edbee75eaeabb9 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Giant.java +++ b/src/main/java/net/minecraft/world/entity/monster/Giant.java @@ -12,12 +12,95 @@ public class Giant extends Monster { super(type, world); } + // 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 net.minecraft.world.entity.ai.goal.FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); + this.goalSelector.addGoal(7, new net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal(this, 1.0D)); + this.goalSelector.addGoal(8, new net.minecraft.world.entity.ai.goal.LookAtPlayerGoal(this, net.minecraft.world.entity.player.Player.class, 16.0F)); + this.goalSelector.addGoal(8, new net.minecraft.world.entity.ai.goal.RandomLookAroundGoal(this)); + this.goalSelector.addGoal(5, new net.minecraft.world.entity.ai.goal.MoveTowardsRestrictionGoal(this, 1.0D)); + if (level().purpurConfig.giantHaveHostileAI) { + this.goalSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.0D, false)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); + this.targetSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class)); + this.targetSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.player.Player.class, true)); + this.targetSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.npc.Villager.class, false)); + this.targetSelector.addGoal(4, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.animal.IronGolem.class, true)); + this.targetSelector.addGoal(5, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, net.minecraft.world.entity.animal.Turtle.class, true)); + } + } + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.giantTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.giantAlwaysDropExp; + } + + @Override + protected void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.giantMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.giantScale); + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.giantMovementSpeed); + this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.giantAttackDamage); + } + + // Purpur end + public static AttributeSupplier.Builder createAttributes() { return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 100.0).add(Attributes.MOVEMENT_SPEED, 0.5).add(Attributes.ATTACK_DAMAGE, 50.0); } + @Override + public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.EntitySpawnReason spawnReason, @org.jetbrains.annotations.Nullable net.minecraft.world.entity.SpawnGroupData entityData) { + net.minecraft.world.entity.SpawnGroupData groupData = super.finalizeSpawn(world, difficulty, spawnReason, entityData); + if (groupData == null) { + populateDefaultEquipmentSlots(this.random, difficulty); + populateDefaultEquipmentEnchantments(world, this.random, difficulty); + } + return groupData; + } + + @Override + protected void populateDefaultEquipmentSlots(net.minecraft.util.RandomSource random, net.minecraft.world.DifficultyInstance difficulty) { + super.populateDefaultEquipmentSlots(this.random, difficulty); + // TODO make configurable + if (random.nextFloat() < (level().getDifficulty() == net.minecraft.world.Difficulty.HARD ? 0.1F : 0.05F)) { + this.setItemSlot(net.minecraft.world.entity.EquipmentSlot.MAINHAND, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.IRON_SWORD)); + } + } + + @Override + public float getJumpPower() { + // make giants jump as high as everything else relative to their size + // 1.0 makes bottom of feet about as high as their waist when they jump + return level().purpurConfig.giantJumpHeight; + } + @Override public float getWalkTargetValue(BlockPos pos, LevelReader world) { - return world.getPathfindingCostFromLightLevels(pos); + return super.getWalkTargetValue(pos, world); // Purpur - fix light requirements for natural spawns } } diff --git a/src/main/java/net/minecraft/world/entity/monster/Guardian.java b/src/main/java/net/minecraft/world/entity/monster/Guardian.java index 951f46684623582980901c1ebc1870aa5bcf25a1..da833bf35342f771ecccd5dcac4fe87f72d047b0 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Guardian.java +++ b/src/main/java/net/minecraft/world/entity/monster/Guardian.java @@ -67,15 +67,52 @@ public class Guardian extends Monster { this.xpReward = 10; this.setPathfindingMalus(PathType.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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.guardianScale); + } + + @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); @@ -84,6 +121,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))); } @@ -330,7 +368,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) { @@ -342,7 +380,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; @@ -351,8 +389,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(); @@ -363,7 +410,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 184fa759db065fb345f3623752229430816d8ad3..7c8ec5cd88fb2083f458a945e716b6f118555db8 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Husk.java +++ b/src/main/java/net/minecraft/world/entity/monster/Husk.java @@ -21,6 +21,59 @@ public class Husk extends Zombie { public Husk(EntityType type, Level world) { super(type, world); + this.setShouldBurnInDay(false); // Purpur - API for any mob to burn daylight + } + + // Purpur start + @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, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { @@ -29,7 +82,7 @@ public class Husk extends Zombie { @Override public boolean isSunSensitive() { - return false; + return this.shouldBurnInDay; // Purpur - moved to LivingEntity; keep methods for ABI compatibility - API for any mob to burn daylight } @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java index db3aac9ba711dcd18ffc35c4a745ecaec89d0166..2ca241344efc6320d2018bdc772f74470080eeed 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,46 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.illusionerScale); + } + + @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(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D)); this.goalSelector.addGoal(4, new Illusioner.IllusionerMirrorSpellGoal()); @@ -71,6 +107,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 ae710c3fffc7840a9ff2cbc5cdacef8a2e248253..63caf20256a3deae98b9cd9f54650def172f0e57 100644 --- a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java +++ b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java @@ -24,6 +24,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, 0.2F); } @@ -71,6 +123,7 @@ public class MagmaCube extends Slime { float f = (float)this.getSize() * 0.1F; this.setDeltaMovement(vec3.x, (double)(this.getJumpPower() + f), vec3.z); this.hasImpulse = true; + this.actualJump = false; // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java index e2de074bbe7bab0e5a7aecc1fae4c5914a203dd4..c2061f575c731ecc6071384b007517c08e0cf983 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Monster.java +++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java @@ -88,6 +88,14 @@ public abstract class Monster extends PathfinderMob implements Enemy { } public static boolean isDarkEnoughToSpawn(ServerLevelAccessor world, BlockPos pos, RandomSource random) { + // Purpur start + if (!world.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce || !world.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce) { + net.minecraft.world.level.block.state.BlockState spawnBlock = world.getBlockState(pos.below()); + if ((!world.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.PACKED_ICE)) || (!world.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.BLUE_ICE))) { + return false; + } + } + // Purpur end if (world.getBrightness(LightLayer.SKY, pos) > random.nextInt(32)) { return false; } else { diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java index 150fd890ac65097b5434fd88e8d2b24a89dca79a..cda6cb5b10b895bab48d2212f259ba4ca40e1ed6 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); + this.setShouldBurnInDay(true); // Purpur - API for any mob to burn daylight + } + + // 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(ServerLevel world, DamageSource damageSource, boolean causedByPlayer) { + boolean dropped = false; + if (lastHurtByPlayer == null && damageSource.getEntity() instanceof net.minecraft.world.entity.boss.enderdragon.EndCrystal) { + if (random.nextInt(5) < 1) { + dropped = spawnAtLocation(world, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.PHANTOM_MEMBRANE)) != null; + } + } + if (!dropped) { + super.dropFromLootTable(world, damageSource, causedByPlayer); + } + } + + public boolean isCirclingCrystal() { + return crystalPosition != null; + } + // Purpur end + + @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() { @@ -115,6 +214,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(); @@ -135,21 +249,23 @@ public class Phantom extends FlyingMob implements Enemy { this.level().addParticle(ParticleTypes.MYCELIUM, this.getX() - (double) f3, this.getY() + (double) f5, this.getZ() - (double) f4, 0.0D, 0.0D, 0.0D); } + if (level().purpurConfig.phantomFlamesOnSwoop && attackPhase == AttackPhase.SWOOP) shoot(); // Purpur } @Override public void aiStep() { - if (this.isAlive() && this.shouldBurnInDay && this.isSunBurnTick()) { // Paper - shouldBurnInDay API - this.igniteForSeconds(8.0F); - } - + // Purpur - implemented in LivingEntity; moved down to shouldBurnInDay() - API for any mob to burn daylight super.aiStep(); } @Override public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) { this.anchorPoint = this.blockPosition().above(5); - this.setPhantomSize(0); + // Purpur start + int min = world.getLevel().purpurConfig.phantomMinSize; + int max = world.getLevel().purpurConfig.phantomMaxSize; + this.setPhantomSize(min == max ? min : world.getRandom().nextInt(max + 1 - min) + min); + // Purpur end return super.finalizeSpawn(world, difficulty, spawnReason, entityData); } @@ -165,7 +281,7 @@ public class Phantom extends FlyingMob implements Enemy { if (nbt.hasUUID("Paper.SpawningEntity")) { this.spawningEntity = nbt.getUUID("Paper.SpawningEntity"); } - if (nbt.contains("Paper.ShouldBurnInDay")) { + if (false && nbt.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); } // Paper end @@ -182,7 +298,7 @@ public class Phantom extends FlyingMob implements Enemy { if (this.spawningEntity != null) { nbt.putUUID("Paper.SpawningEntity", this.spawningEntity); } - nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); + //nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); // Purpur - implemented in LivingEntity - API for any mob to burn daylight // Paper end } @@ -242,8 +358,14 @@ public class Phantom extends FlyingMob implements Enemy { return this.spawningEntity; } public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } - private boolean shouldBurnInDay = true; - public boolean shouldBurnInDay() { return shouldBurnInDay; } + //private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity; keep methods for ABI compatibility - API for any mob to burn daylight + // Purpur start - API for any mob to burn daylight + public boolean shouldBurnInDay() { + boolean burnFromDaylight = this.shouldBurnInDay && this.level().purpurConfig.phantomBurnInDaylight; + boolean burnFromLightSource = this.level().purpurConfig.phantomBurnInLight > 0 && this.level().getMaxLocalRawBrightness(blockPosition()) >= this.level().purpurConfig.phantomBurnInLight; + return burnFromDaylight || burnFromLightSource; + } + // Purpur end - API for any mob to burn daylight public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Paper end @@ -254,7 +376,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; @@ -262,8 +502,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; @@ -309,14 +560,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(final Phantom entity, final Mob phantom) { super(phantom); } + // 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 { @@ -403,6 +660,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; @@ -549,6 +812,7 @@ public class Phantom extends FlyingMob implements Enemy { ServerLevel worldserver = getServerLevel(Phantom.this.level()); List list = worldserver.getNearbyPlayers(this.attackTargeting, Phantom.this, Phantom.this.getBoundingBox().inflate(16.0D, 64.0D, 16.0D)); + if (level().purpurConfig.phantomIgnorePlayersWithTorch) list.removeIf(human -> TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND)));// Purpur 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 3e8631c7bd1e7591051ca21c6ae7acd87d3c7529..85d5c84a8861905e4546901aa6707078571eb402 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Pillager.java +++ b/src/main/java/net/minecraft/world/entity/monster/Pillager.java @@ -64,16 +64,51 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.pillagerScale); + } + + @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(1, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D)); this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, 10.0F)); // Paper - decomp fix 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, new Class[]{Raider.class})).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java index c96fbfe448b3e7b722a8db0e1688276776abd94e..98c1934b4895a86cd8748edf906aaa721a87a123 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java +++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java @@ -77,15 +77,57 @@ public class Ravager extends Raider { this.setPathfindingMalus(PathType.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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ravagerScale); + } + + @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(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D)); + if (level().purpurConfig.ravagerAvoidRabbits) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.AvoidEntityGoal<>(this, net.minecraft.world.entity.animal.Rabbit.class, 6.0F, 1.0D, 1.2D)); // Purpur this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, true)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.4D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(2, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true, (entityliving, worldserver) -> { @@ -138,7 +180,7 @@ public class Ravager extends Raider { @Override public void aiStep() { super.aiStep(); - if (this.isAlive()) { + if (this.isAlive() && (getRider() == null || !this.isControllable())) { // Purpur if (this.isImmobile()) { this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.0D); } else { @@ -153,7 +195,7 @@ public class Ravager extends Raider { if (world instanceof ServerLevel) { ServerLevel worldserver = (ServerLevel) world; - if (this.horizontalCollision && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (this.horizontalCollision && (worldserver.purpurConfig.ravagerBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur 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(); @@ -163,7 +205,7 @@ public class Ravager extends Raider { BlockState iblockdata = worldserver.getBlockState(blockposition); Block block = iblockdata.getBlock(); - if (block instanceof LeavesBlock) { + if (this.level().purpurConfig.ravagerGriefableBlocks.contains(block)) { // Purpur // 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 6e0f2f6573ed6be9b91de960d55c269417ad8907..e3fefd52c83079fe3eab1a96dd81a183f718192b 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java +++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java @@ -84,7 +84,7 @@ public class Shulker extends AbstractGolem implements VariantHolder= f) { + if ((!this.level().purpurConfig.shulkerSpawnFromBulletRequireOpenLid || !this.isClosed()) && this.teleportSomewhere()) { + // Purpur start + float chance = this.level().purpurConfig.shulkerSpawnFromBulletBaseChance; + if (!this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation.isBlank()) { + int nearby = this.level().getEntities((EntityTypeTest) EntityType.SHULKER, axisalignedbb.inflate(this.level().purpurConfig.shulkerSpawnFromBulletNearbyRange), Entity::isAlive).size(); + try { + chance -= ((Number) scriptEngine.eval("let nearby = " + nearby + "; " + this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation)).floatValue(); + } catch (javax.script.ScriptException e) { + e.printStackTrace(); + chance -= (nearby - 1) / 5.0F; + } + } + if (this.level().random.nextFloat() <= chance) { Shulker entityshulker = (Shulker) EntityType.SHULKER.create(this.level(), EntitySpawnReason.BREEDING); + // Purpur end if (entityshulker != null) { entityshulker.setVariant(this.getVariant()); @@ -590,7 +647,7 @@ public class Shulker extends AbstractGolem implements VariantHolder variant) { @@ -601,7 +658,7 @@ public class Shulker extends AbstractGolem implements VariantHolder getVariant() { - return Optional.ofNullable(this.getColor()); + return Optional.ofNullable(this.level().purpurConfig.shulkerSpawnFromBulletRandomColor ? DyeColor.random(this.level().random) : this.getColor()); // Purpur } @Nullable @@ -611,7 +668,7 @@ public class Shulker extends AbstractGolem implements VariantHolder(this, Player.class, true)); } @@ -165,12 +202,12 @@ public class Silverfish extends Monster { if (block instanceof InfestedBlock) { // CraftBukkit start - BlockState afterState = getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state + BlockState afterState = (getServerLevel(world).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, afterState)) { // Paper - fix wrong block state continue; } // CraftBukkit end - if (getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (getServerLevel(world).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur world.destroyBlock(blockposition1, true, this.silverfish); } else { world.setBlock(blockposition1, ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)), 3); @@ -208,7 +245,7 @@ public class Silverfish extends Monster { } else { RandomSource randomsource = this.mob.getRandom(); - if (getServerLevel((Entity) this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && randomsource.nextInt(reducedTickDelay(10)) == 0) { + if (getServerLevel((Entity) this.mob).purpurConfig.silverfishBypassMobGriefing ^ getServerLevel((Entity) this.mob).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && randomsource.nextInt(reducedTickDelay(10)) == 0) { // Purpur this.selectedDirection = Direction.getRandom(randomsource); BlockPos blockposition = BlockPos.containing(this.mob.getX(), this.mob.getY() + 0.5D, this.mob.getZ()).relative(this.selectedDirection); BlockState iblockdata = this.mob.level().getBlockState(blockposition); diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java index 3972e2ed0554e2550519e994888e068df0a151e5..3cbe4c1ed514936a00e0181cb1e647fbb58032bb 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java +++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java @@ -17,6 +17,16 @@ import net.minecraft.world.item.Items; import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.Level; +// Purpur start +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Blocks; +import org.bukkit.craftbukkit.event.CraftEventFactory; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.core.particles.ParticleTypes; +// Purpur end + public class Skeleton extends AbstractSkeleton { private static final int TOTAL_CONVERSION_TIME = 300; @@ -29,6 +39,40 @@ 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); + } + + // Purpur start + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.skeletonTakeDamageFromWater; + } + // Purpur end + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.skeletonAlwaysDropExp; + } + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); @@ -147,4 +191,63 @@ public class Skeleton extends AbstractSkeleton { } } + + // Purpur start + private int witherRosesFed = 0; + + @Override + public InteractionResult mobInteract(Player player, InteractionHand hand) { + ItemStack stack = player.getItemInHand(hand); + + if (level().purpurConfig.skeletonFeedWitherRoses > 0 && this.getType() != EntityType.WITHER_SKELETON && stack.getItem() == Blocks.WITHER_ROSE.asItem()) { + return this.feedWitherRose(player, stack); + } + + return super.mobInteract(player, hand); + } + + private InteractionResult feedWitherRose(Player player, ItemStack stack) { + if (++witherRosesFed < level().purpurConfig.skeletonFeedWitherRoses) { + if (!player.getAbilities().instabuild) { + stack.shrink(1); + } + return InteractionResult.CONSUME; + } + + WitherSkeleton skeleton = EntityType.WITHER_SKELETON.create(level(), net.minecraft.world.entity.EntitySpawnReason.CONVERSION); + if (skeleton == null) { + return InteractionResult.PASS; + } + + skeleton.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); + skeleton.setHealth(this.getHealth()); + skeleton.setAggressive(this.isAggressive()); + skeleton.copyPosition(this); + skeleton.setYBodyRot(this.yBodyRot); + skeleton.setYHeadRot(this.getYHeadRot()); + skeleton.yRotO = this.yRotO; + skeleton.xRotO = this.xRotO; + + if (this.hasCustomName()) { + skeleton.setCustomName(this.getCustomName()); + } + + if (CraftEventFactory.callEntityTransformEvent(this, skeleton, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) { + return InteractionResult.PASS; + } + + this.level().addFreshEntity(skeleton); + this.remove(RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + if (!player.getAbilities().instabuild) { + stack.shrink(1); + } + + for (int i = 0; i < 15; ++i) { + ((ServerLevel) level()).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 72346a7e5269c91e3143933ac37e65ad9639b791..dad4ef9c672eb4247142de5d045678795951164c 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Slime.java +++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java @@ -65,6 +65,7 @@ public class Slime extends Mob implements Enemy { public float squish; public float oSquish; private boolean wasOnGround; + protected boolean actualJump; // Purpur public Slime(EntityType type, Level world) { super(type, world); @@ -72,12 +73,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, worldserver) -> { return Math.abs(entityliving.getY() - this.getY()) <= 4.0D; })); @@ -102,9 +180,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()); } @@ -386,6 +464,7 @@ public class Slime extends Mob implements Enemy { this.setDeltaMovement(vec3d.x, (double) this.getJumpPower(), vec3d.z); this.hasImpulse = true; + this.actualJump = false; // Purpur } @Nullable @@ -419,7 +498,7 @@ public class Slime extends Mob implements Enemy { return super.getDefaultDimensions(pose).scale((float) this.getSize()); } - private static class SlimeMoveControl extends MoveControl { + private static class SlimeMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur private float yRot; private int jumpDelay; @@ -438,21 +517,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) { @@ -469,7 +560,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 91e521414c3ea5722aac7506b7589fbb399e9636..1669acbcf97bee0fa6b0ee91cf53217c53cf55d8 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Spider.java +++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java @@ -51,9 +51,43 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.spiderScale); + } + + @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(2, new AvoidEntityGoal<>(this, Armadillo.class, 6.0F, 1.0D, 1.2D, (entityliving) -> { return !((Armadillo) entityliving).isScared(); })); @@ -62,6 +96,7 @@ public class Spider extends Monster { this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); this.targetSelector.addGoal(2, new Spider.SpiderTargetGoal<>(this, Player.class)); this.targetSelector.addGoal(3, new Spider.SpiderTargetGoal<>(this, IronGolem.class)); diff --git a/src/main/java/net/minecraft/world/entity/monster/Stray.java b/src/main/java/net/minecraft/world/entity/monster/Stray.java index baaf17107584b253d7e268749849bf5b0d0c88ab..bb6984d82e6c0a83f456e725b20e0f21e0cac602 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Stray.java +++ b/src/main/java/net/minecraft/world/entity/monster/Stray.java @@ -22,6 +22,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, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random ) { diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java index 711b7eb8e9fdedbc87965828e573fe8d5c357d53..c3b5b34a54de945071692293645b8a8865aed961 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Strider.java +++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java @@ -91,12 +91,45 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { super(type, world); this.steering = new ItemBasedSteering(this.entityData, Strider.DATA_BOOST_TIME, Strider.DATA_SADDLE_ID); this.blocksBuilding = true; - this.setPathfindingMalus(PathType.WATER, -1.0F); + if (isSensitiveToWater()) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur this.setPathfindingMalus(PathType.LAVA, 0.0F); this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F); this.setPathfindingMalus(PathType.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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.striderScale); + } + + @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, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) { BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); @@ -158,6 +191,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { @Override protected void registerGoals() { this.goalSelector.addGoal(1, new PanicGoal(this, 1.65D)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); this.temptGoal = new TemptGoal(this, 1.4D, (itemstack) -> { return itemstack.is(ItemTags.STRIDER_TEMPT_ITEMS); @@ -412,7 +446,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { @Override public boolean isSensitiveToWater() { - return true; + return this.level().purpurConfig.striderTakeDamageFromWater; // Purpur } @Override @@ -454,6 +488,19 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { public InteractionResult mobInteract(Player player, InteractionHand hand) { boolean flag = this.isFood(player.getItemInHand(hand)); + // Purpur start + if (level().purpurConfig.striderGiveSaddleBack && player.isSecondaryUseActive() && !flag && isSaddled() && !isVehicle()) { + this.steering.setSaddle(false); + if (!player.getAbilities().instabuild) { + ItemStack saddle = new ItemStack(Items.SADDLE); + if (!player.getInventory().add(saddle)) { + player.drop(saddle, false); + } + } + return InteractionResult.SUCCESS; + } + // Purpur end + if (!flag && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) { if (!this.level().isClientSide) { player.startRiding(this); @@ -466,7 +513,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { if (!enuminteractionresult.consumesAction()) { ItemStack itemstack = player.getItemInHand(hand); - return (InteractionResult) (itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : InteractionResult.PASS); + return (InteractionResult) (itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : tryRide(player, hand)); // Purpur } else { if (flag && !this.isSilent()) { this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.STRIDER_EAT, this.getSoundSource(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F); diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java index 183a33b7d666d652b455baa7e8339e9c4a870a58..fe19c0cf6a2c81b547158179518bf26be388cc7a 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Vex.java +++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java @@ -59,6 +59,66 @@ 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(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed)); + setDeltaMovement(mot.scale(0.9D)); + } + } + + @Override + public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { + return false; // no fall damage please + } + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vexMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.vexScale); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.vexTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.vexAlwaysDropExp; + } + // Purpur end + @Override public boolean isFlapping() { return this.tickCount % Vex.TICKS_PER_FLAP == 0; @@ -71,7 +131,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); @@ -86,17 +146,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 @@ -228,14 +290,14 @@ public class Vex extends Monster implements TraceableEntity { this.setDropChance(EquipmentSlot.MAINHAND, 0.0F); } - private class VexMoveControl extends MoveControl { + private class VexMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur public VexMoveControl(final 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(); @@ -244,7 +306,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 96b105697c91314148fd1b783501389214b1a3f0..a2c81d2a1077b2977f1595fd592044baf3e81bab 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java +++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java @@ -55,15 +55,50 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.vindicatorScale); + } + + @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 AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0, 1.2)); this.goalSelector.addGoal(2, new Vindicator.VindicatorBreakDoorGoal(this)); this.goalSelector.addGoal(3, new AbstractIllager.RaiderOpenDoorGoal(this)); this.goalSelector.addGoal(4, new Raider.HoldGroundAttackGoal(this, 10.0F)); this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0, false)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true)); @@ -132,6 +167,11 @@ public class Vindicator extends AbstractIllager { RandomSource randomSource = world.getRandom(); this.populateDefaultEquipmentSlots(randomSource, difficulty); this.populateDefaultEquipmentEnchantments(world, randomSource, difficulty); + // Purpur start + if (level().purpurConfig.vindicatorJohnnySpawnChance > 0D && random.nextDouble() <= level().purpurConfig.vindicatorJohnnySpawnChance) { + setCustomName(Component.translatable("Johnny")); + } + // Purpur end return spawnGroupData; } diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java index a03fa8a3e648532a7ffaaf523ca87c13e8af4c0a..313228811d1eff478887511f99b49706efc49774 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,39 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witchScale); + } + + @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 +98,12 @@ public class Witch extends Raider implements RangedAttackMob { }); this.attackPlayersGoal = new NearestAttackableWitchTargetGoal<>(this, Player.class, 10, true, false, (TargetingConditions.Selector) null); this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur 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 37d3acda84a984bf4f1c44b3d27e2102839d3e8e..23fc36780a3e15260f8cb1001c8d676464a9df3a 100644 --- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java +++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java @@ -33,6 +33,39 @@ public class WitherSkeleton extends AbstractSkeleton { this.setPathfindingMalus(PathType.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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witherSkeletonScale); + } + + @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 35b0c5c322864e2f5ae5a412296072f268adcd05..cb20c018c11a0e707c2083cf964bd5303d216edd 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java @@ -85,6 +85,39 @@ public class Zoglin extends Monster implements 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zoglinScale); + } + + @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); @@ -250,6 +283,7 @@ public class Zoglin extends Monster implements HoglinBase { protected void customServerAiStep(ServerLevel world) { ProfilerFiller profilerFiller = Profiler.get(); profilerFiller.push("zoglinBrain"); + if (getRider() == null || !this.isControllable()) // Purpur - only use brain if no rider this.getBrain().tick(world, this); profilerFiller.pop(); this.updateActivity(); diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java index a12461907278cfbfa3b1c0aa74b9f07a31768b8a..85b03e0bf7436cb846df13c575ad78ac6a17a151 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java @@ -99,22 +99,70 @@ public class Zombie extends Monster { private int inWaterTime; public int conversionTime; // private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field // Paper - remove anti tick skipping measures / wall time - private boolean shouldBurnInDay = true; // Paper - Add more Zombie API + //private boolean shouldBurnInDay = true; // Paper - Add more Zombie API // Purpur - implemented in LivingEntity - API for any mob to burn daylight public Zombie(EntityType type, Level world) { super(type, world); this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(type, world.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper - Configurable door breaking difficulty + this.setShouldBurnInDay(true); // Purpur - API for any mob to burn daylight } public Zombie(Level world) { this(EntityType.ZOMBIE, world); } + // Purpur start + @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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zombieScale); + } + + 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 - Add zombie targets turtle egg config this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.addBehaviourGoals(); } @@ -124,7 +172,19 @@ public class Zombie extends Monster { this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers(ZombifiedPiglin.class)); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); - if ( this.level().spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Spigot + // Purpur start + if ( this.level().spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false) { // Spigot + @Override + public boolean canUse() { + return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canUse(); + } + + @Override + public boolean canContinueToUse() { + return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canContinueToUse(); + } + }); + // Purpur end this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); } @@ -239,32 +299,7 @@ public class Zombie extends Monster { @Override public void aiStep() { - if (this.isAlive()) { - boolean flag = this.isSunSensitive() && this.isSunBurnTick(); - - if (flag) { - ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); - - if (!itemstack.isEmpty()) { - if (itemstack.isDamageableItem()) { - Item item = itemstack.getItem(); - - itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2)); - if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) { - this.onEquippedItemBroken(item, EquipmentSlot.HEAD); - this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY); - } - } - - flag = false; - } - - if (flag) { - this.igniteForSeconds(8.0F); - } - } - } - + // Purpur - implemented in LivingEntity - API for any mob to burn daylight super.aiStep(); } @@ -324,6 +359,7 @@ public class Zombie extends Monster { // CraftBukkit end } + public boolean shouldBurnInDay() { return this.isSunSensitive(); } // Purpur - for ABI compatibility - API for any mob to burn daylight public boolean isSunSensitive() { return this.shouldBurnInDay; // Paper - Add more Zombie API } @@ -462,7 +498,7 @@ public class Zombie extends Monster { nbt.putBoolean("CanBreakDoors", this.canBreakDoors()); nbt.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1); nbt.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1); - nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API + //nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API // Purpur - implemented in LivingEntity - API for any mob to burn daylight } @Override @@ -475,7 +511,7 @@ public class Zombie extends Monster { this.startUnderWaterConversion(nbt.getInt("DrownedConversionTime")); } // Paper start - Add more Zombie API - if (nbt.contains("Paper.ShouldBurnInDay")) { + if (false && nbt.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity - API for any mob to burn daylight this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); } // Paper end - Add more Zombie API @@ -528,19 +564,20 @@ public class Zombie extends Monster { } if (object instanceof Zombie.ZombieGroupData entityzombie_groupdatazombie) { - if (entityzombie_groupdatazombie.isBaby) { - this.setBaby(true); + // Purpur start + if (!jockeyOnlyBaby() || entityzombie_groupdatazombie.isBaby) { + this.setBaby(entityzombie_groupdatazombie.isBaby); if (entityzombie_groupdatazombie.canSpawnJockey) { - if ((double) randomsource.nextFloat() < 0.05D) { - List list = world.getEntitiesOfClass(Chicken.class, this.getBoundingBox().inflate(5.0D, 3.0D, 5.0D), EntitySelector.ENTITY_NOT_BEING_RIDDEN); + if ((double) randomsource.nextFloat() < jockeyChance()) { + List list = jockeyTryExistingChickens() ? world.getEntitiesOfClass(Chicken.class, this.getBoundingBox().inflate(5.0D, 3.0D, 5.0D), EntitySelector.ENTITY_NOT_BEING_RIDDEN) : java.util.Collections.emptyList(); + // Purpur end if (!list.isEmpty()) { Chicken entitychicken = (Chicken) list.get(0); entitychicken.setChickenJockey(true); this.startRiding(entitychicken); - } - } else if ((double) randomsource.nextFloat() < 0.05D) { + } else { // Purpur Chicken entitychicken1 = (Chicken) EntityType.CHICKEN.create(this.level(), EntitySpawnReason.JOCKEY); if (entitychicken1 != null) { @@ -550,6 +587,7 @@ public class Zombie extends Monster { this.startRiding(entitychicken1); world.addFreshEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit } + } // Purpur } } } @@ -562,11 +600,7 @@ public class Zombie extends Monster { } if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) { - LocalDate localdate = LocalDate.now(); - int i = localdate.get(ChronoField.DAY_OF_MONTH); - int j = localdate.get(ChronoField.MONTH_OF_YEAR); - - if (j == 10 && i == 31 && randomsource.nextFloat() < 0.25F) { + if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(world.getMinecraftWorld()) && this.random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(randomsource.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN)); this.armorDropChances[EquipmentSlot.HEAD.getIndex()] = 0.0F; } @@ -608,7 +642,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 18c19e4b675000aacb74344909fc104964231008..6f6b32bf7f68d05e4173c31f2e631a409b858a05 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java @@ -85,6 +85,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(SynchedEntityData.Builder builder) { super.defineSynchedData(builder); @@ -177,10 +229,10 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { ItemStack itemstack = player.getItemInHand(hand); if (itemstack.is(Items.GOLDEN_APPLE)) { - if (this.hasEffect(MobEffects.WEAKNESS)) { + if (this.hasEffect(MobEffects.WEAKNESS) && level().purpurConfig.zombieVillagerCureEnabled) { // Purpur itemstack.consume(1, player); if (!this.level().isClientSide) { - this.startConverting(player.getUUID(), this.random.nextInt(2401) + 3600); + this.startConverting(player.getUUID(), this.random.nextInt(level().purpurConfig.zombieVillagerCuringTimeMax - level().purpurConfig.zombieVillagerCuringTimeMin + 1) + level().purpurConfig.zombieVillagerCuringTimeMin); // Purpur } return InteractionResult.SUCCESS_SERVER; diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java index 03e3cbe73119ca76417d4dd192e1560bdfc373ec..8c3271dcc8c9aa58e2e007eba282c11e42b4e0c9 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,54 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { this.setPathfindingMalus(PathType.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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zombifiedPiglinScale); + } + + @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; @@ -110,7 +158,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { this.maybeAlertOthers(); } - if (this.isAngry()) { + if (this.isAngry() && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur this.lastHurtByPlayerTime = this.tickCount; } @@ -165,7 +213,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { this.ticksUntilNextAlert = ZombifiedPiglin.ALERT_INTERVAL.sample(this.random); } - if (entityliving instanceof Player) { + if (entityliving instanceof Player && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur this.setLastHurtByPlayer((Player) entityliving); } @@ -245,7 +293,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/creaking/Creaking.java b/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java index 7b5f9284972b3a6bd8125891b23f73438e875c08..ea1655d2cbc631d80dd66f7e3e24998530df66a2 100644 --- a/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java +++ b/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java @@ -63,6 +63,29 @@ public class Creaking extends Monster { this.xpReward = 0; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.creakingRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.creakingRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.creakingControllable; + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + } + // Purpur end + @Override protected BodyRotationControl createBodyControl() { return new Creaking.CreakingBodyRotationControl(this); @@ -185,6 +208,14 @@ public class Creaking extends Monster { return this.isActive() ? null : SoundEvents.CREAKING_AMBIENT; } + // Purpur start + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.creakingMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.creakingScale); + } + // Purpur end + @Override protected SoundEvent getHurtSound(DamageSource source) { return SoundEvents.CREAKING_SWAY; @@ -296,28 +327,28 @@ public class Creaking extends Monster { } } - class CreakingLookControl extends LookControl { + class CreakingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur public CreakingLookControl(final Creaking creaking) { super(creaking); } @Override - public void tick() { + public void vanillaTick() { // Purpur if (Creaking.this.canMove()) { - super.tick(); + super.vanillaTick(); // Purpur } } } - class CreakingMoveControl extends MoveControl { + class CreakingMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur public CreakingMoveControl(final Creaking creaking) { super(creaking); } @Override - public void tick() { + public void vanillaTick() { // Purpur if (Creaking.this.canMove()) { - super.tick(); + super.vanillaTick(); // Purpur } } } diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java index 92270912ef26924f611a1df7cb3d5b485b0a262d..6144613e834236ec0ebb12872ef9e47255b76b5b 100644 --- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java +++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java @@ -71,11 +71,49 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { this.xpReward = 5; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.hoglinRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.hoglinRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.hoglinControllable; + } + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.hoglinMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.hoglinScale); + } + // Purpur end + @VisibleForTesting public void setTimeInOverworld(int timeInOverworld) { this.timeInOverworld = timeInOverworld; } + @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() { return true; @@ -138,11 +176,13 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { return (Brain) super.getBrain(); // CraftBukkit - decompile error } + private int behaviorTick; // Pufferfish @Override protected void customServerAiStep(ServerLevel world) { ProfilerFiller gameprofilerfiller = Profiler.get(); gameprofilerfiller.push("hoglinBrain"); + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick(world, this); gameprofilerfiller.pop(); HoglinAi.updateActivity(this); diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java index e04d2c5e75dc774fe893a552474fdb8045c32693..20608fb46a0af85739e3c0caa843c1a9d45c9659 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,39 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.piglinScale); + } + + @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); @@ -304,11 +337,13 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento return !this.cannotHunt; } + private int behaviorTick; // Pufferfish @Override protected void customServerAiStep(ServerLevel world) { ProfilerFiller gameprofilerfiller = Profiler.get(); gameprofilerfiller.push("piglinBrain"); + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick(world, this); gameprofilerfiller.pop(); PiglinAi.updateActivity(this); @@ -405,7 +440,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento @Override public boolean wantsToPickUp(ServerLevel world, ItemStack stack) { - return world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); + return (world.purpurConfig.piglinBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); // Purpur } protected boolean canReplaceCurrentItem(ItemStack stack) { diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java index e283b1296c1e831376bfe9491cbf02ed4b3fffe4..27a6de70530c2a1cbe2f77a7fb493038121710ea 100644 --- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java +++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java @@ -605,11 +605,18 @@ public class PiglinAi { } itemstack = (ItemStack) iterator.next(); - } while (!itemstack.is(ItemTags.PIGLIN_SAFE_ARMOR)); + } while (!itemstack.is(ItemTags.PIGLIN_SAFE_ARMOR) && (!entity.level().purpurConfig.piglinIgnoresArmorWithGoldTrim || !isWearingGoldTrim(itemstack.getItem()))); // Purpur return true; } + // Purpur start + private static boolean isWearingGoldTrim(Item itemstack) { + net.minecraft.world.item.equipment.trim.ArmorTrim armorTrim = itemstack.components().get(net.minecraft.core.component.DataComponents.TRIM); + return armorTrim != null && armorTrim.material().is(net.minecraft.world.item.equipment.trim.TrimMaterials.GOLD); + } + // Purpur end + private static void stopWalking(Piglin piglin) { piglin.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET); piglin.getNavigation().stop(); diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java index 24eaeb93284fe1a573026b85818a93a34fd9e1ec..bc3b0eb21200eae7e419b2571871b36fc1e07c0f 100644 --- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java +++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java @@ -65,6 +65,39 @@ 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); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.piglinBruteScale); + } + + @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.0) @@ -117,6 +150,7 @@ public class PiglinBrute extends AbstractPiglin { protected void customServerAiStep(ServerLevel world) { ProfilerFiller profilerFiller = Profiler.get(); profilerFiller.push("piglinBruteBrain"); + if (getRider() == null || this.isControllable()) // Purpur - only use brain if no rider this.getBrain().tick(world, this); profilerFiller.pop(); PiglinBruteAi.updateActivity(this); diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java index 6180019da58b19d2595da508aed3196af922d587..5a62fe9c4f0cee9e8b4a48564f76afec609335b1 100644 --- a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java +++ b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java @@ -127,8 +127,32 @@ public class Warden extends Monster implements VibrationSystem { this.setPathfindingMalus(PathType.LAVA, 8.0F); this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F); this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F); + this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.5F); // Purpur } + // 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(ServerEntity entityTrackerEntry) { return new ClientboundAddEntityPacket(this, entityTrackerEntry, this.hasPose(Pose.EMERGING) ? 1 : 0); @@ -275,11 +299,13 @@ public class Warden extends Monster implements VibrationSystem { } + private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep(ServerLevel world) { ProfilerFiller gameprofilerfiller = Profiler.get(); gameprofilerfiller.push("wardenBrain"); + if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish this.getBrain().tick(world, this); gameprofilerfiller.pop(); super.customServerAiStep(world); @@ -396,17 +422,14 @@ public class Warden extends Monster implements VibrationSystem { @Contract("null->false") public boolean canTargetEntity(@Nullable Entity entity) { - boolean flag; - + if (getRider() != null && isControllable()) return false; // Purpur if (entity instanceof LivingEntity entityliving) { if (this.level() == entity.level() && EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(entity) && !this.isAlliedTo(entity) && entityliving.getType() != EntityType.ARMOR_STAND && entityliving.getType() != EntityType.WARDEN && !entityliving.isInvulnerable() && !entityliving.isDeadOrDying() && this.level().getWorldBorder().isWithinBounds(entityliving.getBoundingBox())) { - flag = true; - return flag; + return true; // Purpur - wtf } } - flag = false; - return flag; + return false; // Purpur - wtf } public static void applyDarknessAround(ServerLevel world, Vec3 pos, @Nullable Entity entity, int range) { diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java index 5f656fc726a1dc5f42657095a2f2b7cf85b92d7c..6c74cf1dea99b3b967b8c3d76f405f823c881fb9 100644 --- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java @@ -48,6 +48,7 @@ import org.bukkit.event.entity.VillagerAcquireTradeEvent; // CraftBukkit end public abstract class AbstractVillager extends AgeableMob implements InventoryCarrier, Npc, Merchant { + static final net.minecraft.world.item.crafting.Ingredient TEMPT_ITEMS = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.level.block.Blocks.EMERALD_BLOCK.asItem()); // Purpur // CraftBukkit start @Override diff --git a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java index b0236c7bf9441aa84d3795ffed05dd6099f29636..796dcc0dcf9022b455b8847e045266b8802da0cf 100644 --- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java +++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java @@ -27,7 +27,7 @@ public class CatSpawner implements CustomSpawner { if (this.nextTick > 0) { return 0; } else { - this.nextTick = 1200; + this.nextTick = world.purpurConfig.catSpawnDelay; // Purpur Player player = world.getRandomPlayer(); if (player == null) { return 0; @@ -61,8 +61,12 @@ public class CatSpawner implements CustomSpawner { private int spawnInVillage(ServerLevel world, BlockPos pos) { int i = 48; - if (world.getPoiManager().getCountInRange(entry -> entry.is(PoiTypes.HOME), pos, 48, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { - List list = world.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(48.0, 8.0, 48.0)); + // Purpur start + int range = world.purpurConfig.catSpawnVillageScanRange; + if (range <= 0) return 0; + if (world.getPoiManager().getCountInRange(entry -> entry.is(PoiTypes.HOME), pos, range, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { + List list = world.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(range, 8.0, range)); + // Purpur end if (list.size() < 5) { return this.spawnCat(pos, world); } @@ -73,7 +77,11 @@ public class CatSpawner implements CustomSpawner { private int spawnInHut(ServerLevel world, BlockPos pos) { int i = 16; - List list = world.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(16.0, 8.0, 16.0)); + // Purpur start + int range = world.purpurConfig.catSpawnSwampHutScanRange; + if (range <= 0) return 0; + List list = world.getEntitiesOfClass(Cat.class, new AABB(pos).inflate(range, 8.0, range)); + // Purpur end 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 b7a34f1c4d7b5ef3f7a843d152e33c839dcdedd5..0ee456972923e90905732ffc5d77bb027cf6c0fc 100644 --- a/src/main/java/net/minecraft/world/entity/npc/Villager.java +++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java @@ -141,6 +141,10 @@ public class Villager extends AbstractVillager implements ReputationEventHandler }, MemoryModuleType.MEETING_POINT, (entityvillager, holder) -> { return holder.is(PoiTypes.MEETING); }); + private boolean isLobotomized = false; public boolean isLobotomized() { return this.isLobotomized; } // Purpur + private int notLobotomizedCount = 0; // Purpur + + public long nextGolemPanic = -1; // Pufferfish public Villager(EntityType entityType, Level world) { this(entityType, world, VillagerType.PLAINS); @@ -156,6 +160,93 @@ public class Villager extends AbstractVillager implements ReputationEventHandler this.setVillagerData(this.getVillagerData().setType(type).setProfession(VillagerProfession.NONE)); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.villagerRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.villagerRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.villagerControllable; + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); + if (level().purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); + } + + @Override + public boolean canBeLeashed() { + return level().purpurConfig.villagerCanBeLeashed; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.villagerMaxHealth); + this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.villagerScale); + this.getAttribute(Attributes.TEMPT_RANGE).setBaseValue(this.level().purpurConfig.villagerTemptRange); // Purpur + } + + @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; + boolean shouldCheckForTradeLocked = this.level().purpurConfig.villagerLobotomizeWaitUntilTradeLocked; + if (this.notLobotomizedCount > 3) { + // check half as often if not lobotomized for the last 3+ consecutive checks + interval *= 2; + } + if (this.level().getGameTime() % interval == 0) { + // offset Y for short blocks like dirt_path/farmland + this.isLobotomized = !(shouldCheckForTradeLocked && this.getVillagerXp() == 0) && !canTravelFrom(BlockPos.containing(this.position().x, this.getBoundingBox().minY + 0.0625D, this.position().z)); + + if (this.isLobotomized) { + this.notLobotomizedCount = 0; + } else { + this.notLobotomizedCount++; + } + } + return this.isLobotomized; + } + + private boolean canTravelFrom(BlockPos pos) { + return canTravelTo(pos.east()) || canTravelTo(pos.west()) || canTravelTo(pos.north()) || canTravelTo(pos.south()); + } + + private boolean canTravelTo(BlockPos pos) { + net.minecraft.world.level.block.state.BlockState state = this.level().getBlockStateIfLoaded(pos); + if (state == null) { + // chunk not loaded + return false; + } + net.minecraft.world.level.block.Block bottom = state.getBlock(); + if (bottom instanceof net.minecraft.world.level.block.FenceBlock || + bottom instanceof net.minecraft.world.level.block.FenceGateBlock || + bottom instanceof net.minecraft.world.level.block.WallBlock) { + // bottom block is too tall to get over + return false; + } + net.minecraft.world.level.block.Block top = level().getBlockState(pos.above()).getBlock(); + // only if both blocks have no collision + return !bottom.hasCollision && !top.hasCollision; + } + @Override public Brain getBrain() { return (Brain) super.getBrain(); // CraftBukkit - decompile error @@ -190,7 +281,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler brain.addActivity(Activity.PLAY, VillagerGoalPackages.getPlayPackage(0.5F)); } else { brain.setSchedule(Schedule.VILLAGER_DEFAULT); - brain.addActivityWithConditions(Activity.WORK, VillagerGoalPackages.getWorkPackage(villagerprofession, 0.5F), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))); + brain.addActivityWithConditions(Activity.WORK, VillagerGoalPackages.getWorkPackage(villagerprofession, 0.5F, this.level().purpurConfig.villagerClericsFarmWarts), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))); // Purpur } brain.addActivity(Activity.CORE, VillagerGoalPackages.getCorePackage(villagerprofession, 0.5F)); @@ -217,7 +308,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler } public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.5D); + return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.5D).add(Attributes.TEMPT_RANGE, 10.0D); // Purpur - add TEMPT_RANGE } public boolean assignProfessionWhenSpawned() { @@ -245,17 +336,30 @@ public class Villager extends AbstractVillager implements ReputationEventHandler } // Spigot End + private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep(ServerLevel world) { // Paper start - EAR 2 this.customServerAiStep(world, false); } - protected void customServerAiStep(ServerLevel world, final boolean inactive) { + protected void customServerAiStep(ServerLevel world, boolean inactive) { // Purpur - not final // Paper end - EAR 2 ProfilerFiller gameprofilerfiller = Profiler.get(); gameprofilerfiller.push("villagerBrain"); - if (!inactive) this.getBrain().tick(world, this); + // Purpur start + if (this.level().purpurConfig.villagerLobotomizeEnabled) { + // treat as inactive if lobotomized + inactive = inactive || checkLobotomized(); + } else { + this.isLobotomized = false; + } + // Purpur end + // Pufferfish start + if (!inactive && (getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) { + this.getBrain().tick(world, this); // Paper // Purpur + } + // Pufferfish end gameprofilerfiller.pop(); if (this.assignProfessionWhenSpawned) { this.assignProfessionWhenSpawned = false; @@ -312,7 +416,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler if (!itemstack.is(Items.VILLAGER_SPAWN_EGG) && this.isAlive() && !this.isTrading() && !this.isSleeping()) { if (this.isBaby()) { this.setUnhappy(); - return InteractionResult.SUCCESS; + return tryRide(player, hand, InteractionResult.SUCCESS); // Purpur } else { if (!this.level().isClientSide) { boolean flag = this.getOffers().isEmpty(); @@ -326,9 +430,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler } if (flag) { - return InteractionResult.CONSUME; + return tryRide(player, hand, InteractionResult.CONSUME); // Purpur } + if (level().purpurConfig.villagerRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur + if (this.level().purpurConfig.villagerAllowTrading) // Purpur this.startTrading(player); } @@ -493,7 +599,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler while (iterator.hasNext()) { MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); - merchantrecipe.updateDemand(); + merchantrecipe.updateDemand(this.level().purpurConfig.villagerMinimumDemand); // Purpur } } @@ -726,7 +832,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler @Override public boolean canBreed() { - return this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0; + return this.level().purpurConfig.villagerCanBreed && this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0; // Purpur } private boolean hungry() { @@ -905,6 +1011,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler public boolean hasFarmSeeds() { return this.getInventory().hasAnyMatching((itemstack) -> { + // Purpur start + if (this.level().purpurConfig.villagerClericsFarmWarts && this.getVillagerData().getProfession() == VillagerProfession.CLERIC) { + return itemstack.is(Items.NETHER_WART); + } + // Purpur end return itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS); }); } @@ -962,6 +1073,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler } public void spawnGolemIfNeeded(ServerLevel world, long time, int requiredCount) { + if (world.purpurConfig.villagerSpawnIronGolemRadius > 0 && world.getEntitiesOfClass(net.minecraft.world.entity.animal.IronGolem.class, getBoundingBox().inflate(world.purpurConfig.villagerSpawnIronGolemRadius)).size() > world.purpurConfig.villagerSpawnIronGolemLimit) return; // Purpur if (this.wantsToSpawnGolem(time)) { AABB axisalignedbb = this.getBoundingBox().inflate(10.0D, 10.0D, 10.0D); List list = world.getEntitiesOfClass(Villager.class, axisalignedbb); @@ -1026,6 +1138,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler @Override public void startSleeping(BlockPos pos) { + // Purpur start + if (level().purpurConfig.bedExplodeOnVillagerSleep && this.level().getBlockState(pos).getBlock() instanceof net.minecraft.world.level.block.BedBlock) { + this.level().explode(null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (float) this.level().purpurConfig.bedExplosionPower, this.level().purpurConfig.bedExplosionFire, this.level().purpurConfig.bedExplosionEffect); + return; + } + // Purpur end super.startSleeping(pos); this.brain.setMemory(MemoryModuleType.LAST_SLEPT, this.level().getGameTime()); // CraftBukkit - decompile error this.brain.eraseMemory(MemoryModuleType.WALK_TARGET); diff --git a/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java b/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java index 8734ab1bd8299bbf43906d81a349c2a13e0981a7..3ca83269311cbc18c9ef3ce62cff6a2d4dc0a683 100644 --- a/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java +++ b/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java @@ -31,7 +31,7 @@ public record VillagerProfession( public static final VillagerProfession ARMORER = register("armorer", PoiTypes.ARMORER, SoundEvents.VILLAGER_WORK_ARMORER); public static final VillagerProfession BUTCHER = register("butcher", PoiTypes.BUTCHER, SoundEvents.VILLAGER_WORK_BUTCHER); public static final VillagerProfession CARTOGRAPHER = register("cartographer", PoiTypes.CARTOGRAPHER, SoundEvents.VILLAGER_WORK_CARTOGRAPHER); - public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, SoundEvents.VILLAGER_WORK_CLERIC); + public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, ImmutableSet.of(Items.NETHER_WART), ImmutableSet.of(Blocks.SOUL_SAND), SoundEvents.VILLAGER_WORK_CLERIC); // Purpur public static final VillagerProfession FARMER = register( "farmer", PoiTypes.FARMER, diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java index 1e77cce428d9e53142aaa2cf780b7f862d536eca..42c91e52060fad4a7a598f9e9ef88fd0e0ff8475 100644 --- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java @@ -72,6 +72,50 @@ 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; + } + + @Override + public boolean canBeLeashed() { + return level().purpurConfig.wanderingTraderCanBeLeashed; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.wanderingTraderMaxHealth); + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.TEMPT_RANGE).setBaseValue(this.level().purpurConfig.wanderingTraderTemptRange); // Purpur + } + + // Purpur start + public static net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder createAttributes() { + return Mob.createMobAttributes().add(net.minecraft.world.entity.ai.attributes.Attributes.TEMPT_RANGE, 10.0D); + } + // Purpur end + + @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)); @@ -79,7 +123,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill return this.canDrinkPotion && this.level().isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API })); this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> { - return this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API + return level().purpurConfig.milkClearsBeneficialEffects && this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API // Purpur })); this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D)); @@ -92,6 +136,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)); @@ -120,11 +165,13 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill if (!this.level().isClientSide) { if (this.getOffers().isEmpty()) { - return InteractionResult.CONSUME; + return tryRide(player, hand, InteractionResult.CONSUME); // Purpur } - - this.setTradingPlayer(player); - this.openTradingScreen(player, this.getDisplayName(), 1); + if (level().purpurConfig.wanderingTraderRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur + if (this.level().purpurConfig.wanderingTraderAllowTrading) { // Purpur + this.setTradingPlayer(player); + this.openTradingScreen(player, this.getDisplayName(), 1); + } // Purpur } return InteractionResult.SUCCESS; diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java index a728dcbf956f108f01c966c7531449a506a14a87..4c1378132201c1e5d1bc01f8c0cbba91629bcffa 100644 --- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java +++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java @@ -160,7 +160,17 @@ public class WanderingTraderSpawner implements CustomSpawner { int k = pos.getX() + this.random.nextInt(range * 2) - range; int l = pos.getZ() + this.random.nextInt(range * 2) - range; int i1 = world.getHeight(Heightmap.Types.WORLD_SURFACE, k, l); - BlockPos blockposition2 = new BlockPos(k, i1, l); + // Purpur start - allow traders to spawn below nether roof + BlockPos.MutableBlockPos blockposition2 = new BlockPos.MutableBlockPos(k, i1, l); + if (world.dimensionType().hasCeiling()) { + do { + blockposition2.relative(net.minecraft.core.Direction.DOWN); + } while (!world.getBlockState(blockposition2).isAir()); + do { + blockposition2.relative(net.minecraft.core.Direction.DOWN); + } while (world.getBlockState(blockposition2).isAir() && blockposition2.getY() > 0); + } + // Purpur end if (spawnplacementtype.isSpawnPositionOk(world, blockposition2, EntityType.WANDERING_TRADER)) { blockposition1 = blockposition2; diff --git a/src/main/java/net/minecraft/world/entity/player/Inventory.java b/src/main/java/net/minecraft/world/entity/player/Inventory.java index 0990bcf65f484b9a019c91fcdae1783bac6388da..99feda30fb85a7615560b6e9a3701d5dfeb3e524 100644 --- a/src/main/java/net/minecraft/world/entity/player/Inventory.java +++ b/src/main/java/net/minecraft/world/entity/player/Inventory.java @@ -649,6 +649,8 @@ public class Inventory implements Container, Nameable { } public boolean contains(ItemStack stack) { + // Pufferfish start - don't allocate iterators + /* Iterator iterator = this.compartments.iterator(); while (iterator.hasNext()) { @@ -663,6 +665,18 @@ public class Inventory implements Container, Nameable { } } } + */ + for (int i = 0; i < this.compartments.size(); i++) { + List list = this.compartments.get(i); + for (int j = 0; j < list.size(); j++) { + ItemStack itemstack1 = list.get(j); + + if (!itemstack1.isEmpty() && ItemStack.isSameItemSameComponents(itemstack1, stack)) { + return true; + } + } + } + // Pufferfish end return false; } diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java index 61d412c4f1ebd55661cc3f0260468e3ac0efe0bb..c1d068fa99ec0fd5685bbb627a775e4d1587feab 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -197,17 +197,40 @@ public abstract class Player extends LivingEntity { private int currentImpulseContextResetGraceTime; public boolean affectsSpawning = true; // Paper - Affects Spawning API public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage + public int sixRowEnderchestSlotCount = -1; // Purpur + public int burpDelay = 0; // Purpur + public boolean canPortalInstant = false; // Purpur // CraftBukkit start public boolean fauxSleeping; public int oldLevel = -1; + 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; @@ -252,6 +275,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); @@ -332,6 +361,17 @@ public abstract class Player extends LivingEntity { this.turtleHelmetTick(); } + // Purpur start + if (this.level().purpurConfig.playerNetheriteFireResistanceDuration > 0 && this.level().getGameTime() % 20 == 0) { + if (this.getItemBySlot(EquipmentSlot.HEAD).is(Items.NETHERITE_HELMET) + && this.getItemBySlot(EquipmentSlot.CHEST).is(Items.NETHERITE_CHESTPLATE) + && this.getItemBySlot(EquipmentSlot.LEGS).is(Items.NETHERITE_LEGGINGS) + && this.getItemBySlot(EquipmentSlot.FEET).is(Items.NETHERITE_BOOTS)) { + this.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, this.level().purpurConfig.playerNetheriteFireResistanceDuration, this.level().purpurConfig.playerNetheriteFireResistanceAmplifier, this.level().purpurConfig.playerNetheriteFireResistanceAmbient, this.level().purpurConfig.playerNetheriteFireResistanceShowParticles, this.level().purpurConfig.playerNetheriteFireResistanceShowIcon), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.NETHERITE_ARMOR); + } + } + // Purpur end + this.cooldowns.tick(); this.updatePlayerPose(); if (this.currentImpulseContextResetGraceTime > 0) { @@ -622,7 +662,7 @@ public abstract class Player extends LivingEntity { while (iterator.hasNext()) { Entity entity = (Entity) iterator.next(); - if (entity.getType() == EntityType.EXPERIENCE_ORB) { + if (entity.getType() == EntityType.EXPERIENCE_ORB && entity.level().purpurConfig.playerExpPickupDelay >= 0) { // Purpur list1.add(entity); } else if (!entity.isRemoved()) { this.touch(entity); @@ -1274,7 +1314,7 @@ public abstract class Player extends LivingEntity { flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits if (flag2) { damagesource = damagesource.critical(true); // Paper start - critical damage API - f *= 1.5F; + f *= this.level().purpurConfig.playerCriticalDamageMultiplier; // Purpur } float f3 = f + f1; @@ -1640,7 +1680,7 @@ public abstract class Player extends LivingEntity { } @Override - protected boolean canGlide() { + public boolean canGlide() { // Purpur return !this.abilities.flying && super.canGlide(); } @@ -1900,7 +1940,23 @@ public abstract class Player extends LivingEntity { @Override protected int getBaseExperienceReward(ServerLevel world) { - return !world.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator() ? Math.min(this.experienceLevel * 7, 100) : 0; + // Purpur start + if (!world.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator()) { + int toDrop; + try { + toDrop = Math.round(((Number) scriptEngine.eval("let expLevel = " + experienceLevel + "; " + + "let expTotal = " + totalExperience + "; " + + "let exp = " + experienceProgress + "; " + + level().purpurConfig.playerDeathExpDropEquation)).floatValue()); + } catch (javax.script.ScriptException e) { + e.printStackTrace(); + toDrop = experienceLevel * 7; + } + return Math.min(toDrop, level().purpurConfig.playerDeathExpDropMax); + } else { + return 0; + } + // Purpur end } @Override @@ -1978,6 +2034,13 @@ public abstract class Player extends LivingEntity { return slot != EquipmentSlot.BODY; } + // Purpur start + @Override + public boolean dismountsUnderwater() { + return !level().purpurConfig.playerRidableInWater; + } + // Purpur end + public boolean setEntityOnShoulder(CompoundTag entityNbt) { if (!this.isPassenger() && this.onGround() && !this.isInWater() && !this.isInPowderSnow) { if (this.getShoulderEntityLeft().isEmpty()) { diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java index 8ce573042ea87631eb6539f2a0d5036655c2b809..5006a59734c5fc7525fba86f7d74e74f8f1b54b0 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java @@ -80,6 +80,7 @@ public abstract class AbstractArrow extends Projectile { public ItemStack pickupItemStack; @Nullable public ItemStack firedFromWeapon; + public net.minecraft.world.item.enchantment.ItemEnchantments actualEnchantments = net.minecraft.world.item.enchantment.ItemEnchantments.EMPTY; // Purpur - Add an option to fix MC-3304 projectile looting // Spigot Start @Override @@ -371,7 +372,7 @@ public abstract class AbstractArrow extends Projectile { Vec3 vec3d = this.getDeltaMovement(); this.setDeltaMovement(vec3d.multiply((double) (this.random.nextFloat() * 0.2F), (double) (this.random.nextFloat() * 0.2F), (double) (this.random.nextFloat() * 0.2F))); - this.life = 0; + if (this.level().purpurConfig.arrowMovementResetsDespawnCounter) this.life = 0; // Purpur - do not reset despawn counter } public boolean isInGround() { @@ -623,6 +624,12 @@ public abstract class AbstractArrow extends Projectile { return this.firedFromWeapon; } + // Purpur start - Add an option to fix MC-3304 projectile looting + public void setActualEnchantments(net.minecraft.world.item.enchantment.ItemEnchantments actualEnchantments) { + this.actualEnchantments = actualEnchantments; + } + // Purpur end - Add an option to fix MC-3304 projectile looting + protected SoundEvent getDefaultHitGroundSoundEvent() { return SoundEvents.ARROW_HIT; } diff --git a/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java b/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java index 2f00676f62478897ae4931ea06e047567c407535..55ea7f82fac9a3de6d7e0725a9b6ea08088bc85c 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java +++ b/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java @@ -23,13 +23,13 @@ public class LargeFireball extends Fireball { public LargeFireball(EntityType type, Level world) { super(type, world); - this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit + this.isIncendiary = (world instanceof ServerLevel worldserver) && (worldserver.purpurConfig.fireballsBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)); // CraftBukkit // Purpur } public LargeFireball(Level world, LivingEntity owner, Vec3 velocity, int explosionPower) { super(EntityType.FIREBALL, owner, velocity, world); this.explosionPower = explosionPower; - this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit + this.isIncendiary = (world instanceof ServerLevel worldserver) && (worldserver.purpurConfig.fireballsBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)); // CraftBukkit // Purpur } @Override @@ -38,7 +38,7 @@ public class LargeFireball extends Fireball { Level world = this.level(); if (world instanceof ServerLevel worldserver) { - boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + boolean flag = worldserver.purpurConfig.fireballsBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur // CraftBukkit start - fire ExplosionPrimeEvent ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity()); diff --git a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java index 958ea103cc80da7366cc33dc385b76d4f5c809f2..0b7f27a6cc6be58fa5b60002059c9fbb3b1b7b67 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java +++ b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java @@ -33,6 +33,12 @@ public class LlamaSpit extends Projectile { this.setPos(owner.getX() - (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(owner.yBodyRot * 0.017453292F), owner.getEyeY() - 0.10000000149011612D, owner.getZ() + (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(owner.yBodyRot * 0.017453292F)); } + // Purpur start + public void super_tick() { + super.tick(); + } + // Purpur end + @Override protected double getDefaultGravity() { return 0.06D; diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java index 6c2d4d6f3a36ab452dfd3c33f66e54f152906639..a7df9c2a3f210bc944cf76cdcc8d2a9484704bd1 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java @@ -58,6 +58,36 @@ public abstract class Projectile extends Entity implements TraceableEntity { super(type, world); } + // Pufferfish start + private static int loadedThisTick = 0; + private static int loadedTick; + + private int loadedLifetime = 0; + @Override + public void setPos(double x, double y, double z) { + int currentTick = net.minecraft.server.MinecraftServer.currentTick; + if (loadedTick != currentTick) { + loadedTick = currentTick; + loadedThisTick = 0; + } + int previousX = Mth.floor(this.getX()) >> 4, previousZ = Mth.floor(this.getZ()) >> 4; + int newX = Mth.floor(x) >> 4, newZ = Mth.floor(z) >> 4; + if (previousX != newX || previousZ != newZ) { + boolean isLoaded = ((net.minecraft.server.level.ServerChunkCache) this.level().getChunkSource()).getChunkAtIfLoadedImmediately(newX, newZ) != null; + if (!isLoaded) { + if (Projectile.loadedThisTick > gg.pufferfish.pufferfish.PufferfishConfig.maxProjectileLoadsPerTick) { + if (++this.loadedLifetime > gg.pufferfish.pufferfish.PufferfishConfig.maxProjectileLoadsPerProjectile) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Purpur + } + return; + } + Projectile.loadedThisTick++; + } + } + super.setPos(x, y, z); + } + // Pufferfish end + public void setOwner(@Nullable Entity entity) { if (entity != null) { this.ownerUUID = entity.getUUID(); @@ -480,7 +510,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { public boolean mayInteract(ServerLevel world, BlockPos pos) { Entity entity = this.getOwner(); - return entity instanceof Player ? entity.mayInteract(world, pos) : entity == null || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + return entity instanceof Player ? entity.mayInteract(world, pos) : entity == null || world.purpurConfig.projectilesBypassMobGriefing ^ world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); } public boolean mayBreak(ServerLevel world) { diff --git a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java index bb159ea4baf208aab6d6fcfbbddacd5b089b55c8..588b07ec4501924a49264183b414a7fd64bb6550 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java +++ b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java @@ -30,7 +30,7 @@ public class SmallFireball extends Fireball { super(EntityType.SMALL_FIREBALL, owner, velocity, world); // CraftBukkit start if (this.getOwner() != null && this.getOwner() instanceof Mob) { - this.isIncendiary = (world instanceof ServerLevel worldserver) && worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + this.isIncendiary = (world instanceof ServerLevel worldserver) && (worldserver.purpurConfig.fireballsBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)); // Purpur } // 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 70961e151666a0ecf5b791853f4581eaebbdcc8b..0db58e7d63a5c1b43a2224c247979f23a1d3f899 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java @@ -58,11 +58,41 @@ public class Snowball extends ThrowableItemProjectile { protected void onHitEntity(EntityHitResult entityHitResult) { super.onHitEntity(entityHitResult); Entity entity = entityHitResult.getEntity(); - int i = entity instanceof Blaze ? 3 : 0; + int i = entity.level().purpurConfig.snowballDamage >= 0 ? entity.level().purpurConfig.snowballDamage : entity instanceof Blaze ? 3 : 0; // Purpur entity.hurt(this.damageSources().thrown(this, this.getOwner()), (float) i); } + // Purpur start - borrowed and modified code from ThrownPotion#onHitBlock and ThrownPotion#dowseFire + @Override + protected void onHitBlock(net.minecraft.world.phys.BlockHitResult blockHitResult) { + super.onHitBlock(blockHitResult); + + if (!this.level().isClientSide) { + net.minecraft.core.BlockPos blockposition = blockHitResult.getBlockPos(); + net.minecraft.core.BlockPos blockposition1 = blockposition.relative(blockHitResult.getDirection()); + + net.minecraft.world.level.block.state.BlockState iblockdata = this.level().getBlockState(blockposition); + + if (this.level().purpurConfig.snowballExtinguishesFire && this.level().getBlockState(blockposition1).is(net.minecraft.world.level.block.Blocks.FIRE)) { + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition1, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { + this.level().removeBlock(blockposition1, false); + } + } else if (this.level().purpurConfig.snowballExtinguishesCandles && net.minecraft.world.level.block.AbstractCandleBlock.isLit(iblockdata)) { + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.setValue(net.minecraft.world.level.block.AbstractCandleBlock.LIT, false))) { + net.minecraft.world.level.block.AbstractCandleBlock.extinguish(null, iblockdata, this.level(), blockposition); + } + } else if (this.level().purpurConfig.snowballExtinguishesCampfires && net.minecraft.world.level.block.CampfireBlock.isLitCampfire(iblockdata)) { + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false))) { + this.level().levelEvent(null, 1009, blockposition, 0); + net.minecraft.world.level.block.CampfireBlock.dowse(this.getOwner(), this.level(), blockposition, iblockdata); + this.level().setBlockAndUpdate(blockposition, iblockdata.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false)); + } + } + } + } + // Purpur end + @Override protected void onHit(HitResult hitResult) { super.onHit(hitResult); diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java index bd2684528157f928460f2143dd71a48e11983123..0720df603b4f89dd6aa346091b13033ad5d62907 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java @@ -152,10 +152,11 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { return; } // CraftBukkit end - if (this.random.nextFloat() < 0.05F && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { + if (this.random.nextFloat() < worldserver.purpurConfig.enderPearlEndermiteChance && worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { // Purpur Endermite entityendermite = (Endermite) EntityType.ENDERMITE.create(worldserver, EntitySpawnReason.TRIGGERED); if (entityendermite != null) { + entityendermite.setPlayerSpawned(true); // Purpur entityendermite.moveTo(entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot()); worldserver.addFreshEntity(entityendermite, CreatureSpawnEvent.SpawnReason.ENDER_PEARL); } @@ -170,7 +171,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { if (entityplayer1 != null) { entityplayer1.resetFallDistance(); entityplayer1.resetCurrentImpulseContext(); - entityplayer1.hurtServer(entityplayer.serverLevel(), this.damageSources().enderPearl().customEventDamager(this), 5.0F); // CraftBukkit // Paper - fix DamageSource API + entityplayer1.hurtServer(entityplayer.serverLevel(), this.damageSources().enderPearl().customEventDamager(this), this.level().purpurConfig.enderPearlDamage); // CraftBukkit // Paper - fix DamageSource API // Purpur } this.playSound(worldserver, vec3d); diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java index 322733266fdca8ce43434a8ffea304c51794bcbb..489c26423a7f5bc9da45d247de57ec989cc74119 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java +++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java @@ -69,7 +69,7 @@ public class ThrownTrident extends AbstractArrow { Entity entity = this.getOwner(); byte b0 = (Byte) this.entityData.get(ThrownTrident.ID_LOYALTY); - if (b0 > 0 && (this.dealtDamage || this.isNoPhysics()) && entity != null) { + if (b0 > 0 && (this.dealtDamage || this.isNoPhysics() || (level().purpurConfig.tridentLoyaltyVoidReturnHeight < 0.0D && getY() < level().purpurConfig.tridentLoyaltyVoidReturnHeight)) && entity != null) { // Purpur if (!this.isAcceptibleReturnOwner()) { Level world = this.level(); diff --git a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java index 4c47b30867e30d84908abf93dbefc252bc8c3453..e63b408594b5d2673148e39c1deafc8510537bee 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java +++ b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java @@ -103,7 +103,7 @@ public class WitherSkull extends AbstractHurtingProjectile { if (!this.level().isClientSide) { // CraftBukkit start // this.level().explode(this, this.getX(), this.getY(), this.getZ(), 1.0F, false, World.a.MOB); - ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 1.0F, false); + ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), this.level().purpurConfig.witherExplosionRadius, false); // Purpur this.level().getCraftServer().getPluginManager().callEvent(event); if (!event.isCancelled()) { @@ -115,6 +115,19 @@ public class WitherSkull extends AbstractHurtingProjectile { } + // Purpur start + @Override + public boolean canHitEntity(Entity target) { + // do not hit rider + return target != this.getRider() && super.canHitEntity(target); + } + + @Override + public boolean canSaveToDisk() { + return false; + } + // Purpur end + @Override protected void defineSynchedData(SynchedEntityData.Builder builder) { builder.define(WitherSkull.DATA_DANGEROUS, false); diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java index ab132041982df2a701e4baea8195873f31b4a5fb..722c1660cf6b93d0f9c05cafe587b1834c5c3a22 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raider.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java @@ -345,7 +345,7 @@ public abstract class Raider extends PatrollingMonster { } private boolean cannotPickUpBanner() { - if (!getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items + if ((!this.mob.level().purpurConfig.pillagerBypassMobGriefing == !getServerLevel(this.mob).getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING)) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items // Purpur if (!this.mob.hasActiveRaid()) { return true; } else if (this.mob.getCurrentRaid().isOver()) { diff --git a/src/main/java/net/minecraft/world/entity/raid/Raids.java b/src/main/java/net/minecraft/world/entity/raid/Raids.java index 439d61d8689fabe940006b9b317a6810175dccfb..6b30941a84054efb5fcccb5d9e6c80d713a23889 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raids.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raids.java @@ -26,6 +26,7 @@ import net.minecraft.world.phys.Vec3; public class Raids extends SavedData { private static final String RAID_FILE_ID = "raids"; + public final Map playerCooldowns = Maps.newHashMap(); public final Map raidMap = Maps.newHashMap(); private final ServerLevel level; private int nextAvailableID; @@ -51,6 +52,17 @@ public class Raids extends SavedData { public void tick() { ++this.tick; + // Purpur start + if (level.purpurConfig.raidCooldownSeconds != 0 && this.tick % 20 == 0) { + com.google.common.collect.ImmutableMap.copyOf(playerCooldowns).forEach((uuid, i) -> { + if (i < 1) { + playerCooldowns.remove(uuid); + } else { + playerCooldowns.put(uuid, i - 1); + } + }); + } + // Purpur end Iterator iterator = this.raidMap.values().iterator(); while (iterator.hasNext()) { @@ -122,11 +134,13 @@ public class Raids extends SavedData { */ if (!raid.isStarted() || (raid.isInProgress() && raid.getRaidOmenLevel() < raid.getMaxRaidOmenLevel())) { // CraftBukkit - fixed a bug with raid: players could add up Bad Omen level even when the raid had finished + if (level.purpurConfig.raidCooldownSeconds != 0 && playerCooldowns.containsKey(player.getUUID())) return null; // Purpur // CraftBukkit start if (!org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(raid, player)) { player.removeEffect(net.minecraft.world.effect.MobEffects.RAID_OMEN); return null; } + if (level.purpurConfig.raidCooldownSeconds != 0) playerCooldowns.put(player.getUUID(), level.purpurConfig.raidCooldownSeconds); // Purpur if (!raid.isStarted() && !this.raidMap.containsKey(raid.getId())) { this.raidMap.put(raid.getId(), raid); diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java index 1fdbef16cd29c8fc74578ac3328f985eca61088d..56c265940208bc94f531a5af94f564b59f35ebf3 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractBoat.java @@ -499,6 +499,7 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable { if (f > 0.0F) { this.landFriction = f; + if (level().purpurConfig.boatEjectPlayersOnLand) ejectPassengers(); // Purpur return AbstractBoat.Status.ON_LAND; } else { return AbstractBoat.Status.IN_AIR; @@ -929,7 +930,13 @@ public abstract class AbstractBoat extends VehicleEntity implements Leashable { @Override public final ItemStack getPickResult() { - return new ItemStack((ItemLike) this.dropItem.get()); + // Purpur start + final ItemStack boat = new ItemStack((ItemLike) this.dropItem.get()); + if (!this.level().purpurConfig.persistentDroppableEntityDisplayNames) { + boat.set(net.minecraft.core.component.DataComponents.CUSTOM_NAME, null); + } + return boat; + // Purpur end } public static enum Status { diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java index cdc8606ffe5c75ee19d92e9f86f26b2a502d765e..b31940441596079aae1cd2a38b9d22be18358448 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java @@ -92,6 +92,10 @@ public abstract class AbstractMinecart extends VehicleEntity { private double flyingY = 0.95; private double flyingZ = 0.95; public Double maxSpeed; + // Purpur start + public double storedMaxSpeed; + public boolean isNewBehavior; + // Purpur end // CraftBukkit end public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API @@ -100,8 +104,13 @@ public abstract class AbstractMinecart extends VehicleEntity { this.blocksBuilding = true; if (AbstractMinecart.useExperimentalMovement(world)) { this.behavior = new NewMinecartBehavior(this); + this.isNewBehavior = true; // Purpur } else { this.behavior = new OldMinecartBehavior(this); + // Purpur start + this.isNewBehavior = false; + maxSpeed = storedMaxSpeed = world.purpurConfig.minecartMaxSpeed; + // Purpur end } } @@ -289,6 +298,14 @@ public abstract class AbstractMinecart extends VehicleEntity { @Override public void tick() { + // Purpur start + if (!this.isNewBehavior) { + if (storedMaxSpeed != level().purpurConfig.minecartMaxSpeed) { + maxSpeed = storedMaxSpeed = level().purpurConfig.minecartMaxSpeed; + } + } + // Purpur end + // CraftBukkit start double prevX = this.getX(); double prevY = this.getY(); @@ -426,16 +443,62 @@ public abstract class AbstractMinecart extends VehicleEntity { this.behavior.moveAlongTrack(world); } + // 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(ServerLevel world) { double d0 = this.getMaxSpeed(world); Vec3 vec3d = this.getDeltaMovement(); this.setDeltaMovement(Mth.clamp(vec3d.x, -d0, d0), vec3d.y, Mth.clamp(vec3d.z, -d0, d0)); + + // Purpur start + 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()) { diff --git a/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java b/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java index f43439b31a14b9db4744512465d81134ebe5b3e1..0b9741dfebd0bb95e8c0e1f55ce18dfb353f693a 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/NewMinecartBehavior.java @@ -426,7 +426,7 @@ public class NewMinecartBehavior extends MinecartBehavior { private Vec3 calculateBoostTrackSpeed(Vec3 velocity, BlockPos railPos, BlockState railState) { if (railState.is(Blocks.POWERED_RAIL) && (Boolean) railState.getValue(PoweredRailBlock.POWERED)) { if (velocity.length() > 0.01D) { - return velocity.normalize().scale(velocity.length() + 0.06D); + return velocity.normalize().scale(velocity.length() + this.level().purpurConfig.poweredRailBoostModifier); // Purpur } else { Vec3 vec3d1 = this.minecart.getRedstoneDirection(railPos); diff --git a/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java b/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java index 04a622f52353ebcc21f41c233f5a0fd67690cf4a..f10ce069ef427df16fd0ce0e60b85c805ca703f0 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/OldMinecartBehavior.java @@ -310,9 +310,9 @@ public class OldMinecartBehavior extends MinecartBehavior { vec3d5 = this.getDeltaMovement(); d17 = vec3d5.horizontalDistance(); if (d17 > 0.01D) { - double d19 = 0.06D; + double d19 = world.purpurConfig.poweredRailBoostModifier; // Purpur - this.setDeltaMovement(vec3d5.add(vec3d5.x / d17 * 0.06D, 0.0D, vec3d5.z / d17 * 0.06D)); + this.setDeltaMovement(vec3d5.add(vec3d5.x / d17 * world.purpurConfig.poweredRailBoostModifier, 0.0D, vec3d5.z / d17 * world.purpurConfig.poweredRailBoostModifier)); // Purpur } else { Vec3 vec3d6 = this.getDeltaMovement(); double d20 = vec3d6.x; diff --git a/src/main/java/net/minecraft/world/food/FoodData.java b/src/main/java/net/minecraft/world/food/FoodData.java index 6a686be6a69ae890d519a54ca099d4ba14e5b9e1..b8b0b89b7f0a21ecff4ab6286f8a114e2d6b6b39 100644 --- a/src/main/java/net/minecraft/world/food/FoodData.java +++ b/src/main/java/net/minecraft/world/food/FoodData.java @@ -44,6 +44,7 @@ public class FoodData { org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(entityplayer, foodinfo.nutrition() + oldFoodLevel, itemstack); if (!event.isCancelled()) { + if (entityplayer.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) entityplayer.burpDelay = entityplayer.level().purpurConfig.playerBurpDelay; // Purpur this.add(event.getFoodLevel() - oldFoodLevel, foodinfo.saturation()); } @@ -96,7 +97,7 @@ public class FoodData { ++this.tickTimer; if (this.tickTimer >= this.starvationRate) { // CraftBukkit - add regen rate manipulation if (player.getHealth() > 10.0F || enumdifficulty == Difficulty.HARD || player.getHealth() > 1.0F && enumdifficulty == Difficulty.NORMAL) { - player.hurtServer(worldserver, player.damageSources().starve(), 1.0F); + player.hurtServer(worldserver, player.damageSources().starve(), player.level().purpurConfig.hungerStarvationDamage); // Purpur } this.tickTimer = 0; diff --git a/src/main/java/net/minecraft/world/food/FoodProperties.java b/src/main/java/net/minecraft/world/food/FoodProperties.java index 882b72799ae532f4e181214d5756ec024af223e2..9a3f2a95debcf8b94f7deb375922ea09b30aabab 100644 --- a/src/main/java/net/minecraft/world/food/FoodProperties.java +++ b/src/main/java/net/minecraft/world/food/FoodProperties.java @@ -33,7 +33,7 @@ public record FoodProperties(int nutrition, float saturation, boolean canAlwaysE world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), (SoundEvent) consumable.sound().value(), SoundSource.NEUTRAL, 1.0F, randomsource.triangle(1.0F, 0.4F)); if (user instanceof Player entityhuman) { entityhuman.getFoodData().eat(this, stack, (ServerPlayer) entityhuman); // CraftBukkit - world.playSound((Player) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(randomsource, 0.9F, 1.0F)); + //world.playSound((Player) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, Mth.randomBetween(randomsource, 0.9F, 1.0F)); // Purpur - moved to Player#tick() } } diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java index 4680f77a275d8d2b226018db89a571ac25998dd8..bfc90524bd739ed1d91fe9912e38093b3c28928f 100644 --- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java @@ -80,6 +80,7 @@ public abstract class AbstractContainerMenu { @Nullable private ContainerSynchronizer synchronizer; private boolean suppressRemoteUpdates; + @Nullable protected ItemStack activeQuickItem = null; // Purpur - Anvil API // CraftBukkit start public boolean checkReachable = true; diff --git a/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java index 1240df9368855f836412b06cf564926a18bfe90d..e559eabed82d2f402908e5b80d1505076ccc53a2 100644 --- a/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java @@ -114,7 +114,13 @@ public abstract class AbstractFurnaceMenu extends RecipeBookMenu { } else if (slot != 1 && slot != 0) { if (this.canSmelt(itemstack1)) { if (!this.moveItemStackTo(itemstack1, 0, 1, false)) { - return ItemStack.EMPTY; + // Purpur start - 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 286ae002e1711ad9e800b7f2091988d66cd572a7..5959e1df9374bcb889be1726abb83b8c58962d05 100644 --- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java @@ -25,6 +25,12 @@ import org.slf4j.Logger; import org.bukkit.craftbukkit.inventory.view.CraftAnvilView; // CraftBukkit end +// Purpur start - Anvil API +import net.minecraft.network.protocol.game.ClientboundContainerSetDataPacket; +import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; +import net.minecraft.server.level.ServerPlayer; +// Purpur end - Anvil API + public class AnvilMenu extends ItemCombinerMenu { public static final int INPUT_SLOT = 0; @@ -55,6 +61,10 @@ public class AnvilMenu extends ItemCombinerMenu { private CraftAnvilView bukkitEntity; // CraftBukkit end public boolean bypassEnchantmentLevelRestriction = false; // Paper - bypass anvil level restrictions + // Purpur start - Anvil API + public boolean bypassCost = false; + public boolean canDoUnsafeEnchants = false; + // Purpur end - Anvil API public AnvilMenu(int syncId, Inventory inventory) { this(syncId, inventory, ContainerLevelAccess.NULL); @@ -82,12 +92,17 @@ public class AnvilMenu extends ItemCombinerMenu { @Override protected boolean mayPickup(Player player, boolean present) { - return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && present; // CraftBukkit - allow cost 0 like a free item + return (player.hasInfiniteMaterials() || player.experienceLevel >= this.cost.get()) && (this.bypassCost || this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST) && present; // CraftBukkit - allow cost 0 like a free item // Purpur - Anvil API } @Override protected void onTake(Player player, ItemStack stack) { + // Purpur start - Anvil API + ItemStack itemstack = this.activeQuickItem != null ? this.activeQuickItem : stack; + if (org.purpurmc.purpur.event.inventory.AnvilTakeResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)).callEvent(); + // Purpur end - Anvil API if (!player.getAbilities().instabuild) { + if (this.bypassCost) ((ServerPlayer) player).lastSentExp = -1; else // Purpur - Anvil API player.giveExperienceLevels(-this.cost.get()); } @@ -138,6 +153,12 @@ public class AnvilMenu extends ItemCombinerMenu { @Override public void createResult() { + // Purpur start - Anvil API + this.bypassCost = false; + this.canDoUnsafeEnchants = false; + if (org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent(getBukkitView()).callEvent(); + // Purpur end - Anvil API + ItemStack itemstack = this.inputSlots.getItem(0); this.onlyRenaming = false; @@ -146,7 +167,7 @@ public class AnvilMenu extends ItemCombinerMenu { long j = 0L; byte b0 = 0; - if (!itemstack.isEmpty() && EnchantmentHelper.canStoreEnchantments(itemstack)) { + if (!itemstack.isEmpty() && this.canDoUnsafeEnchants || EnchantmentHelper.canStoreEnchantments(itemstack)) { // Purpur - Anvil API ItemStack itemstack1 = itemstack.copy(); ItemStack itemstack2 = this.inputSlots.getItem(1); ItemEnchantments.Mutable itemenchantments_a = new ItemEnchantments.Mutable(EnchantmentHelper.getEnchantmentsForCrafting(itemstack1)); @@ -213,7 +234,10 @@ public class AnvilMenu extends ItemCombinerMenu { i2 = l1 == i2 ? i2 + 1 : Math.max(i2, l1); Enchantment enchantment = (Enchantment) holder.value(); - boolean flag3 = enchantment.canEnchant(itemstack); + // Purpur start - Config to allow unsafe enchants + boolean flag3 = this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants || enchantment.canEnchant(itemstack); // whether the enchantment can be applied on specific item type + boolean flag4 = true; // whether two incompatible enchantments can be applied on a single item + // Purpur end - Config to allow unsafe enchants if (this.player.getAbilities().instabuild || itemstack.is(Items.ENCHANTED_BOOK)) { flag3 = true; @@ -225,16 +249,22 @@ public class AnvilMenu extends ItemCombinerMenu { Holder holder1 = (Holder) iterator1.next(); if (!holder1.equals(holder) && !Enchantment.areCompatible(holder, holder1)) { - flag3 = false; + flag4 = this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants; // Purpur - Anvil API // Purpur - flag3 -> flag4 - Config to allow unsafe enchants + // Purpur start - Config to allow unsafe enchants + if (!flag4 && org.purpurmc.purpur.PurpurConfig.replaceIncompatibleEnchants) { + iterator1.remove(); // replace current enchant with the incompatible one trying to be applied + flag4 = true; + } + // Purpur end - Config to allow unsafe enchants ++i; } } - if (!flag3) { + if (!flag3 || !flag4) { // Purpur - Config to allow unsafe enchants flag2 = true; } else { flag1 = true; - if (i2 > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions + if (!org.purpurmc.purpur.PurpurConfig.allowHigherEnchantsLevels && i2 > enchantment.getMaxLevel() && !this.bypassEnchantmentLevelRestriction) { // Paper - bypass anvil level restrictions // Purpur - Config to allow unsafe enchants i2 = enchantment.getMaxLevel(); } @@ -264,6 +294,54 @@ public class AnvilMenu extends ItemCombinerMenu { if (!this.itemName.equals(itemstack.getHoverName().getString())) { b0 = 1; i += b0; + // Purpur start + if (this.player != null) { + org.bukkit.craftbukkit.entity.CraftHumanEntity player = this.player.getBukkitEntity(); + String name = this.itemName; + boolean removeItalics = false; + if (player.hasPermission("purpur.anvil.remove_italics")) { + if (name.startsWith("&r")) { + name = name.substring(2); + removeItalics = true; + } else if (name.startsWith("")) { + name = name.substring(3); + removeItalics = true; + } else if (name.startsWith("")) { + name = name.substring(7); + removeItalics = true; + } + } + if (this.player.level().purpurConfig.anvilAllowColors) { + if (player.hasPermission("purpur.anvil.color")) { + java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?i)&([0-9a-fr])").matcher(name); + while (matcher.find()) { + String match = matcher.group(1); + name = name.replace("&" + match, "\u00a7" + match.toLowerCase(java.util.Locale.ROOT)); + } + //name = name.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1"); + } + if (player.hasPermission("purpur.anvil.format")) { + java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?i)&([k-or])").matcher(name); + while (matcher.find()) { + String match = matcher.group(1); + name = name.replace("&" + match, "\u00a7" + match.toLowerCase(java.util.Locale.ROOT)); + } + //name = name.replaceAll("(?i)&([l-or])", "\u00a7$1"); + } + } + net.kyori.adventure.text.Component component; + if (this.player.level().purpurConfig.anvilColorsUseMiniMessage && player.hasPermission("purpur.anvil.minimessage")) { + component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.bukkit.ChatColor.stripColor(name)); + } else { + component = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(name); + } + if (removeItalics) { + component = component.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); + } + itemstack1.set(DataComponents.CUSTOM_NAME, io.papermc.paper.adventure.PaperAdventure.asVanilla(component)); + } + else + // Purpur end itemstack1.set(DataComponents.CUSTOM_NAME, Component.literal(this.itemName)); } } else if (itemstack.has(DataComponents.CUSTOM_NAME)) { @@ -287,6 +365,12 @@ public class AnvilMenu extends ItemCombinerMenu { this.onlyRenaming = true; } + // Purpur start - Anvil API + if (this.bypassCost && this.cost.get() >= this.maximumRepairCost) { + this.cost.set(this.maximumRepairCost - 1); + } + // Purpur end - Anvil API + if (this.cost.get() >= this.maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit itemstack1 = ItemStack.EMPTY; } @@ -307,6 +391,13 @@ public class AnvilMenu extends ItemCombinerMenu { org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemstack1); // CraftBukkit this.broadcastChanges(); + + // Purpur start - Anvil API + if ((this.canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants || org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants) && itemstack1 != ItemStack.EMPTY) { // Purpur - Config to allow unsafe enchants + ((ServerPlayer) this.player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), 2, itemstack1)); + ((ServerPlayer) this.player).connection.send(new ClientboundContainerSetDataPacket(this.containerId, 0, this.cost.get())); + } + // Purpur end - Anvil API } else { org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), ItemStack.EMPTY); // CraftBukkit this.cost.set(AnvilMenu.DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item @@ -315,7 +406,7 @@ public class AnvilMenu extends ItemCombinerMenu { } public static int calculateIncreasedRepairCost(int cost) { - return (int) Math.min((long) cost * 2L + 1L, 2147483647L); + return org.purpurmc.purpur.PurpurConfig.anvilCumulativeCost ? (int) Math.min((long) cost * 2L + 1L, 2147483647L) : 0; // Purpur - Make anvil cumulative cost configurable } public boolean setItemName(String newItemName) { diff --git a/src/main/java/net/minecraft/world/inventory/ArmorSlot.java b/src/main/java/net/minecraft/world/inventory/ArmorSlot.java index 6c0b6abb1698fac9bb902f695b725d4ab783ee90..091e3c3514fcb378b68098114106d09f04d8fb0d 100644 --- a/src/main/java/net/minecraft/world/inventory/ArmorSlot.java +++ b/src/main/java/net/minecraft/world/inventory/ArmorSlot.java @@ -45,7 +45,7 @@ class ArmorSlot extends Slot { @Override public boolean mayPickup(Player playerEntity) { ItemStack itemStack = this.getItem(); - return (itemStack.isEmpty() || playerEntity.isCreative() || !EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE)) + return (itemStack.isEmpty() || playerEntity.isCreative() || (!EnchantmentHelper.has(itemStack, EnchantmentEffectComponents.PREVENT_ARMOR_CHANGE) || playerEntity.level().purpurConfig.playerRemoveBindingWithWeakness && playerEntity.hasEffect(net.minecraft.world.effect.MobEffects.WEAKNESS))) && super.mayPickup(playerEntity); } diff --git a/src/main/java/net/minecraft/world/inventory/ChestMenu.java b/src/main/java/net/minecraft/world/inventory/ChestMenu.java index 48a6b6136ac3414ca735f93a14b1a8d76210603c..27321b07cd04814bc1ff720c65770d7755625bb6 100644 --- a/src/main/java/net/minecraft/world/inventory/ChestMenu.java +++ b/src/main/java/net/minecraft/world/inventory/ChestMenu.java @@ -66,10 +66,30 @@ public class ChestMenu extends AbstractContainerMenu { return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, 6); } + // Purpur start + public static ChestMenu oneRow(int syncId, Inventory playerInventory, Container inventory) { + return new ChestMenu(MenuType.GENERIC_9x1, syncId, playerInventory, inventory, 1); + } + + public static ChestMenu twoRows(int syncId, Inventory playerInventory, Container inventory) { + return new ChestMenu(MenuType.GENERIC_9x2, syncId, playerInventory, inventory, 2); + } + // Purpur end + public static ChestMenu threeRows(int syncId, Inventory playerInventory, Container inventory) { return new ChestMenu(MenuType.GENERIC_9x3, syncId, playerInventory, inventory, 3); } + // Purpur start + public static ChestMenu fourRows(int syncId, Inventory playerInventory, Container inventory) { + return new ChestMenu(MenuType.GENERIC_9x4, syncId, playerInventory, inventory, 4); + } + + public static ChestMenu fiveRows(int syncId, Inventory playerInventory, Container inventory) { + return new ChestMenu(MenuType.GENERIC_9x5, syncId, playerInventory, inventory, 5); + } + // Purpur end + public static ChestMenu sixRows(int syncId, Inventory playerInventory, Container inventory) { return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, inventory, 6); } diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java index 50a735dd97daab4fb9579f922a4c63de60204f29..5b8ad051347f73553acb65c5ddc690d2b7eaa754 100644 --- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java @@ -42,6 +42,12 @@ import org.bukkit.event.enchantment.PrepareItemEnchantEvent; import org.bukkit.entity.Player; // CraftBukkit end +// Purpur start +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.EnchantingTableBlockEntity; +import org.bukkit.craftbukkit.entity.CraftHumanEntity; +// Purpur end + public class EnchantmentMenu extends AbstractContainerMenu { static final ResourceLocation EMPTY_SLOT_LAPIS_LAZULI = ResourceLocation.withDefaultNamespace("item/empty_slot_lapis_lazuli"); @@ -76,6 +82,22 @@ public class EnchantmentMenu extends AbstractContainerMenu { return context.getLocation(); } // CraftBukkit end + + // Purpur start + @Override + public void onClose(CraftHumanEntity who) { + super.onClose(who); + + if (who.getHandle().level().purpurConfig.enchantmentTableLapisPersists) { + access.execute((level, pos) -> { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof EnchantingTableBlockEntity enchantmentTable) { + enchantmentTable.setLapis(this.getItem(1).getCount()); + } + }); + } + } + // Purpur end }; this.random = RandomSource.create(); this.enchantmentSeed = DataSlot.standalone(); @@ -100,6 +122,16 @@ public class EnchantmentMenu extends AbstractContainerMenu { return Pair.of(InventoryMenu.BLOCK_ATLAS, EnchantmentMenu.EMPTY_SLOT_LAPIS_LAZULI); } }); + // Purpur start + access.execute((level, pos) -> { + if (level.purpurConfig.enchantmentTableLapisPersists) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof EnchantingTableBlockEntity enchantmentTable) { + this.getSlot(1).set(new ItemStack(Items.LAPIS_LAZULI, enchantmentTable.getLapis())); + } + } + }); + // Purpur end this.addStandardInventorySlots(playerInventory, 8, 84); this.addDataSlot(DataSlot.shared(this.costs, 0)); this.addDataSlot(DataSlot.shared(this.costs, 1)); @@ -329,6 +361,7 @@ public class EnchantmentMenu extends AbstractContainerMenu { public void removed(net.minecraft.world.entity.player.Player player) { super.removed(player); this.access.execute((world, blockposition) -> { + if (world.purpurConfig.enchantmentTableLapisPersists) this.getSlot(1).set(ItemStack.EMPTY); // Purpur this.clearContainer(player, this.enchantSlots); }); } diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java index 5687f492fc76f699e2a388790ca5380d9b8c8d0a..111da7435f0abb5a57bd2c5fecead2380ac4347a 100644 --- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java @@ -96,12 +96,14 @@ public class GrindstoneMenu extends AbstractContainerMenu { @Override public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) { + ItemStack itemstack = activeQuickItem == null ? stack : activeQuickItem; // Purpur context.execute((world, blockposition) -> { if (world instanceof ServerLevel) { // Paper start - Fire BlockExpEvent on grindstone use org.bukkit.event.block.BlockExpEvent event = new org.bukkit.event.block.BlockExpEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition), this.getExperienceAmount(world)); event.callEvent(); - ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), event.getExpToDrop(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); + org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent grindstoneTakeResultEvent = new org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), event.getExpToDrop()); grindstoneTakeResultEvent.callEvent(); // Purpur + ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), grindstoneTakeResultEvent.getExperienceAmount(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Purpur // Paper end - Fire BlockExpEvent on grindstone use } @@ -135,7 +137,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { Holder holder = (Holder) entry.getKey(); int k = entry.getIntValue(); - if (!holder.is(EnchantmentTags.CURSE)) { + if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value())) { // Purpur j += ((Enchantment) holder.value()).getMinCost(k); } } @@ -222,7 +224,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { Entry> entry = (Entry) iterator.next(); Holder holder = (Holder) entry.getKey(); - if (!holder.is(EnchantmentTags.CURSE) || itemenchantments_a.getLevel(holder) == 0) { + if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value()) || itemenchantments_a.getLevel(holder) == 0) { // Purpur itemenchantments_a.upgrade(holder, entry.getIntValue()); } } @@ -230,10 +232,70 @@ public class GrindstoneMenu extends AbstractContainerMenu { }); } + // Purpur start + private java.util.List> GRINDSTONE_REMOVE_ATTRIBUTES_REMOVAL_LIST = java.util.List.of( + // DataComponents.MAX_STACK_SIZE, + // DataComponents.DAMAGE, + // DataComponents.BLOCK_STATE, + DataComponents.CUSTOM_DATA, + // DataComponents.MAX_DAMAGE, + // DataComponents.UNBREAKABLE, + // DataComponents.CUSTOM_NAME, + // DataComponents.ITEM_NAME, + // DataComponents.LORE, + // DataComponents.RARITY, + // DataComponents.ENCHANTMENTS, + // DataComponents.CAN_PLACE_ON, + // DataComponents.CAN_BREAK, + DataComponents.ATTRIBUTE_MODIFIERS, + DataComponents.CUSTOM_MODEL_DATA, + // DataComponents.HIDE_ADDITIONAL_TOOLTIP, + // DataComponents.HIDE_TOOLTIP, + // DataComponents.REPAIR_COST, + // DataComponents.CREATIVE_SLOT_LOCK, + // DataComponents.ENCHANTMENT_GLINT_OVERRIDE, + // DataComponents.INTANGIBLE_PROJECTILE, + // DataComponents.FOOD, + // DataComponents.FIRE_RESISTANT, + // DataComponents.TOOL, + // DataComponents.STORED_ENCHANTMENTS, + DataComponents.DYED_COLOR, + // DataComponents.MAP_COLOR, + // DataComponents.MAP_ID, + // DataComponents.MAP_DECORATIONS, + // DataComponents.MAP_POST_PROCESSING, + // DataComponents.CHARGED_PROJECTILES, + // DataComponents.BUNDLE_CONTENTS, + // DataComponents.POTION_CONTENTS, + DataComponents.SUSPICIOUS_STEW_EFFECTS + // DataComponents.WRITABLE_BOOK_CONTENT, + // DataComponents.WRITTEN_BOOK_CONTENT, + // DataComponents.TRIM, + // DataComponents.DEBUG_STICK_STATE, + // DataComponents.ENTITY_DATA, + // DataComponents.BUCKET_ENTITY_DATA, + // DataComponents.BLOCK_ENTITY_DATA, + // DataComponents.INSTRUMENT, + // DataComponents.OMINOUS_BOTTLE_AMPLIFIER, + // DataComponents.RECIPES, + // DataComponents.LODESTONE_TRACKER, + // DataComponents.FIREWORK_EXPLOSION, + // DataComponents.FIREWORKS, + // DataComponents.PROFILE, + // DataComponents.NOTE_BLOCK_SOUND, + // DataComponents.BANNER_PATTERNS, + // DataComponents.BASE_COLOR, + // DataComponents.POT_DECORATIONS, + // DataComponents.CONTAINER, + // DataComponents.BEES, + // DataComponents.LOCK, + // DataComponents.CONTAINER_LOOT, + ); + // Purpur end private ItemStack removeNonCursesFrom(ItemStack item) { ItemEnchantments itemenchantments = EnchantmentHelper.updateEnchantments(item, (itemenchantments_a) -> { itemenchantments_a.removeIf((holder) -> { - return !holder.is(EnchantmentTags.CURSE); + return !org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(holder.value()); // Purpur }); }); @@ -248,6 +310,23 @@ public class GrindstoneMenu extends AbstractContainerMenu { } item.set(DataComponents.REPAIR_COST, i); + + // Purpur start + net.minecraft.core.component.DataComponentPatch.Builder builder = net.minecraft.core.component.DataComponentPatch.builder(); + if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveAttributes) { + item.getComponents().forEach(typedDataComponent -> { + if (GRINDSTONE_REMOVE_ATTRIBUTES_REMOVAL_LIST.contains(typedDataComponent.type())) { + builder.remove(typedDataComponent.type()); + } + }); + } + if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveDisplay) { + builder.remove(DataComponents.CUSTOM_NAME); + builder.remove(DataComponents.LORE); + } + item.applyComponents(builder.build()); + // Purpur end + return item; } @@ -309,7 +388,9 @@ public class GrindstoneMenu extends AbstractContainerMenu { return ItemStack.EMPTY; } + this.activeQuickItem = itemstack; // Purpur slot1.onTake(player, itemstack1); + this.activeQuickItem = null; // Purpur } return itemstack; diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java index a5d53a656513ae81cc3f9fc506caf6adaba62a8e..ac9df238ef0f3d009f25976b95e0b750e963e952 100644 --- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java @@ -164,7 +164,9 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { return ItemStack.EMPTY; } + this.activeQuickItem = itemstack; // Purpur - Anvil API slot1.onTake(player, itemstack1); + this.activeQuickItem = null; // Purpur - Anvil API } return itemstack; diff --git a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java index a15d5ff872dbd77f3c3145e0328f3d02e431ff8c..1dcf36d502990d32fc4cd3ea69c3ea334baed69a 100644 --- a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java +++ b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java @@ -31,11 +31,18 @@ public class PlayerEnderChestContainer extends SimpleContainer { } public PlayerEnderChestContainer(Player owner) { - super(27); + super(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? 54 : 27); // Purpur this.owner = owner; // CraftBukkit end } + // Purpur start + @Override + public int getContainerSize() { + return owner.sixRowEnderchestSlotCount < 0 ? super.getContainerSize() : owner.sixRowEnderchestSlotCount; + } + // Purpur end + public void setActiveChest(EnderChestBlockEntity blockEntity) { this.activeChest = blockEntity; } diff --git a/src/main/java/net/minecraft/world/item/ArmorStandItem.java b/src/main/java/net/minecraft/world/item/ArmorStandItem.java index cb4baebe22eeab17aed67a5ecc506b932fe2230b..c1a6734cc08de1c9fc413b1fa81a199ac9547ec2 100644 --- a/src/main/java/net/minecraft/world/item/ArmorStandItem.java +++ b/src/main/java/net/minecraft/world/item/ArmorStandItem.java @@ -59,6 +59,14 @@ public class ArmorStandItem extends Item { return InteractionResult.FAIL; } // CraftBukkit end + // Purpur start + if (!world.purpurConfig.persistentDroppableEntityDisplayNames) { + entityarmorstand.setCustomName(null); + } + if (world.purpurConfig.armorstandSetNameVisible && entityarmorstand.getCustomName() != null) { + entityarmorstand.setCustomNameVisible(true); + } + // Purpur end worldserver.addFreshEntityWithPassengers(entityarmorstand); world.playSound((Player) null, entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F); entityarmorstand.gameEvent(GameEvent.ENTITY_PLACE, context.getPlayer()); diff --git a/src/main/java/net/minecraft/world/item/AxeItem.java b/src/main/java/net/minecraft/world/item/AxeItem.java index abff08f2d61014944235ffe2f5494a718a28cc10..dc2c415ab227e1357533079ada4903e9f69d4f55 100644 --- a/src/main/java/net/minecraft/world/item/AxeItem.java +++ b/src/main/java/net/minecraft/world/item/AxeItem.java @@ -62,13 +62,15 @@ public class AxeItem extends DiggerItem { if (playerHasShieldUseIntent(context)) { return InteractionResult.PASS; } else { - Optional optional = this.evaluateNewBlockState(level, blockPos, player, level.getBlockState(blockPos)); + Optional optional = this.evaluateActionable(level, blockPos, player, level.getBlockState(blockPos)); // Purpur if (optional.isEmpty()) { return InteractionResult.PASS; } else { + org.purpurmc.purpur.tool.Actionable actionable = optional.get(); // Purpur + BlockState state = actionable.into().withPropertiesOf(level.getBlockState(blockPos)); // Purpur ItemStack itemStack = context.getItemInHand(); // Paper start - EntityChangeBlockEvent - if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional.get())) { + if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state)) { // Purpur return InteractionResult.PASS; } // Paper end @@ -76,8 +78,15 @@ public class AxeItem extends DiggerItem { CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); } - level.setBlock(blockPos, optional.get(), 11); - level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, optional.get())); + // Purpur start + level.setBlock(blockPos, state, 11); + actionable.drops().forEach((drop, chance) -> { + if (level.random.nextDouble() < chance) { + Block.popResourceFromFace(level, blockPos, context.getClickedFace(), new ItemStack(drop)); + } + }); + level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, state)); + // Purpur end if (player != null) { itemStack.hurtAndBreak(1, player, LivingEntity.getSlotForHand(context.getHand())); } @@ -92,22 +101,24 @@ public class AxeItem extends DiggerItem { return context.getHand().equals(InteractionHand.MAIN_HAND) && player.getOffhandItem().is(Items.SHIELD) && !player.isSecondaryUseActive(); } - private Optional evaluateNewBlockState(Level world, BlockPos pos, @Nullable Player player, BlockState state) { - Optional optional = this.getStripped(state); + private Optional evaluateActionable(Level world, BlockPos pos, @Nullable Player player, BlockState state) { // Purpur + Optional optional = Optional.ofNullable(world.purpurConfig.axeStrippables.get(state.getBlock())); // Purpur if (optional.isPresent()) { - world.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); + world.playSound(STRIPPABLES.containsKey(state.getBlock()) ? player : null, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound return optional; } else { - Optional optional2 = WeatheringCopper.getPrevious(state); + Optional optional2 = Optional.ofNullable(world.purpurConfig.axeWeatherables.get(state.getBlock())); // Purpur if (optional2.isPresent()) { - world.playSound(player, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); + world.playSound(WeatheringCopper.getPrevious(state).isPresent() ? player : null, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound world.levelEvent(player, 3005, pos, 0); return optional2; } else { - Optional optional3 = Optional.ofNullable(HoneycombItem.WAX_OFF_BY_BLOCK.get().get(state.getBlock())) - .map(block -> block.withPropertiesOf(state)); + // Purpur start + Optional optional3 = Optional.ofNullable(world.purpurConfig.axeWaxables.get(state.getBlock())); + // .map(block -> block.withPropertiesOf(state)); + // Purpur end if (optional3.isPresent()) { - world.playSound(player, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); + world.playSound(HoneycombItem.WAX_OFF_BY_BLOCK.get().containsKey(state.getBlock()) ? player : null, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound world.levelEvent(player, 3004, pos, 0); return optional3; } else { diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java index c816c935ecc74a811ffdffbe6ded73c06e92324a..d58619d1d63a03598b8740dd789d4b6f2c93f8d0 100644 --- a/src/main/java/net/minecraft/world/item/BlockItem.java +++ b/src/main/java/net/minecraft/world/item/BlockItem.java @@ -151,7 +151,16 @@ public class BlockItem extends Item { } protected boolean updateCustomBlockEntityTag(BlockPos pos, Level world, @Nullable Player player, ItemStack stack, BlockState state) { - return BlockItem.updateCustomBlockEntityTag(world, player, pos, stack); + // Purpur start + boolean handled = updateCustomBlockEntityTag(world, player, pos, stack); + if (world.purpurConfig.persistentTileEntityLore) { + BlockEntity blockEntity1 = world.getBlockEntity(pos); + if (blockEntity1 != null) { + blockEntity1.setPersistentLore(stack.getOrDefault(DataComponents.LORE, net.minecraft.world.item.component.ItemLore.EMPTY)); + } + } + return handled; + // Purpur end } @Nullable @@ -213,6 +222,7 @@ public class BlockItem extends Item { if (tileentity != null) { if (!world.isClientSide && tileentity.onlyOpCanSetNbt() && (player == null || !(player.canUseGameMasterBlocks() || (player.getAbilities().instabuild && player.getBukkitEntity().hasPermission("minecraft.nbt.place"))))) { // Spigot - add permission + if (!(!world.isClientSide && world.purpurConfig.silkTouchEnabled && tileentity instanceof net.minecraft.world.level.block.entity.SpawnerBlockEntity && player.getBukkitEntity().hasPermission("purpur.drop.spawners"))) return false; } @@ -248,6 +258,7 @@ public class BlockItem extends Item { ItemContainerContents itemcontainercontents = (ItemContainerContents) entity.getItem().set(DataComponents.CONTAINER, ItemContainerContents.EMPTY); if (itemcontainercontents != null) { + if (entity.level().purpurConfig.shulkerBoxItemDropContentsWhenDestroyed && this.getBlock() instanceof ShulkerBoxBlock) // Purpur ItemUtils.onContainerDestroyed(entity, itemcontainercontents.nonEmptyItemsCopy()); } diff --git a/src/main/java/net/minecraft/world/item/BoatItem.java b/src/main/java/net/minecraft/world/item/BoatItem.java index e51ffd6c5047ee907a58f3029f0ea7fc66aedfa7..78d41d57df9cb61b295f1f54db1e1d62c13db701 100644 --- a/src/main/java/net/minecraft/world/item/BoatItem.java +++ b/src/main/java/net/minecraft/world/item/BoatItem.java @@ -71,6 +71,11 @@ public class BoatItem extends Item { return InteractionResult.FAIL; } else { abstractboat.setYRot(user.getYRot()); + // Purpur start + if (!world.purpurConfig.persistentDroppableEntityDisplayNames) { + abstractboat.setCustomName(null); + } + // Purpur end if (!world.noCollision(abstractboat, abstractboat.getBoundingBox())) { return InteractionResult.FAIL; } else { diff --git a/src/main/java/net/minecraft/world/item/BowItem.java b/src/main/java/net/minecraft/world/item/BowItem.java index bb593209c95c9cf1f9c5d52d52fab4a33ddbabcf..58fa528e4b2589d362eb976afd6221cd94f2623c 100644 --- a/src/main/java/net/minecraft/world/item/BowItem.java +++ b/src/main/java/net/minecraft/world/item/BowItem.java @@ -28,6 +28,11 @@ public class BowItem extends ProjectileWeaponItem { return false; } else { ItemStack itemStack = player.getProjectile(stack); + // Purpur start + if (world.purpurConfig.infinityWorksWithoutArrows && itemStack.isEmpty() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.INFINITY, stack) > 0) { + itemStack = new ItemStack(Items.ARROW); + } + // Purpur end if (itemStack.isEmpty()) { return false; } else { @@ -38,7 +43,7 @@ public class BowItem extends ProjectileWeaponItem { } else { List list = draw(stack, itemStack, player); if (world instanceof ServerLevel serverLevel && !list.isEmpty()) { - this.shoot(serverLevel, player, player.getUsedItemHand(), stack, list, f * 3.0F, 1.0F, f == 1.0F, null); + this.shoot(serverLevel, player, player.getUsedItemHand(), stack, list, f * 3.0F, (float) world.purpurConfig.bowProjectileOffset, f == 1.0F, null); // Purpur } world.playSound( @@ -89,7 +94,7 @@ public class BowItem extends ProjectileWeaponItem { public InteractionResult use(Level world, Player user, InteractionHand hand) { ItemStack itemStack = user.getItemInHand(hand); boolean bl = !user.getProjectile(itemStack).isEmpty(); - if (!user.hasInfiniteMaterials() && !bl) { + if (!user.hasInfiniteMaterials() && !bl && !(world.purpurConfig.infinityWorksWithoutArrows && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.INFINITY, itemStack) > 0)) { // Purpur return InteractionResult.FAIL; } else { user.startUsingItem(hand); diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java index 3bddfb6f7412ab86e0c090d0cbc6cf254b3f891c..6aa8ee091d3a7d2826d08ab9a03f970ef71a81ea 100644 --- a/src/main/java/net/minecraft/world/item/BucketItem.java +++ b/src/main/java/net/minecraft/world/item/BucketItem.java @@ -196,7 +196,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { // CraftBukkit end if (!flag2) { return movingobjectpositionblock != null && this.emptyContents(entityhuman, world, movingobjectpositionblock.getBlockPos().relative(movingobjectpositionblock.getDirection()), (BlockHitResult) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit - } else if (world.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) { + } else if ((world.dimensionType().ultraWarm() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) && this.content.is(FluidTags.WATER)) { // Purpur int i = blockposition.getX(); int j = blockposition.getY(); int k = blockposition.getZ(); @@ -204,7 +204,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { world.playSound(entityhuman, blockposition, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F); for (int l = 0; l < 8; ++l) { - world.addParticle(ParticleTypes.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 0.0D, 0.0D, 0.0D); + ((ServerLevel) world).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 52c40eafc77e50a6fd21b9a7a250cea501f11690..86204c2ab5bbd5d45ddb1d626f844d91ccae6b4f 100644 --- a/src/main/java/net/minecraft/world/item/CrossbowItem.java +++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java @@ -69,7 +69,7 @@ public class CrossbowItem extends ProjectileWeaponItem { ItemStack itemStack = user.getItemInHand(hand); ChargedProjectiles chargedProjectiles = itemStack.get(DataComponents.CHARGED_PROJECTILES); if (chargedProjectiles != null && !chargedProjectiles.isEmpty()) { - this.performShooting(world, user, hand, itemStack, getShootingPower(chargedProjectiles), 1.0F, null); + this.performShooting(world, user, hand, itemStack, getShootingPower(chargedProjectiles), (float) world.purpurConfig.crossbowProjectileOffset, null); // Purpur return InteractionResult.CONSUME; } else if (!user.getProjectile(itemStack).isEmpty()) { this.startSoundPlayed = false; diff --git a/src/main/java/net/minecraft/world/item/DyeColor.java b/src/main/java/net/minecraft/world/item/DyeColor.java index 79dc7cf5bfe92b4df21d164f39726dfe618331e4..6721432f9cdd11c9658c34f0ac407be217f9d276 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 3ddd34e5d05fa1355a2affd329d72dea216cd0e4..770bdb3fb2426083ff6785f1c38ffe9d11f898e5 100644 --- a/src/main/java/net/minecraft/world/item/EggItem.java +++ b/src/main/java/net/minecraft/world/item/EggItem.java @@ -27,7 +27,7 @@ public class EggItem extends Item implements ProjectileItem { if (world instanceof ServerLevel worldserver) { // CraftBukkit start // Paper start - PlayerLaunchProjectileEvent - final Projectile.Delayed thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, worldserver, itemstack, user, 0.0F, 1.5F, 1.0F); + final Projectile.Delayed thrownEgg = Projectile.spawnProjectileFromRotationDelayed(ThrownEgg::new, worldserver, itemstack, user, 0.0F, 1.5F, (float) worldserver.purpurConfig.eggProjectileOffset); // Purpur com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) thrownEgg.projectile().getBukkitEntity()); if (event.callEvent() && thrownEgg.attemptSpawn()) { if (event.shouldConsume()) { diff --git a/src/main/java/net/minecraft/world/item/EndCrystalItem.java b/src/main/java/net/minecraft/world/item/EndCrystalItem.java index b62db8c7c8c57e43869ee239ebf4b02f112355d9..e75930f26604b772a141a93c6f4b77828abc4503 100644 --- a/src/main/java/net/minecraft/world/item/EndCrystalItem.java +++ b/src/main/java/net/minecraft/world/item/EndCrystalItem.java @@ -27,7 +27,7 @@ public class EndCrystalItem extends Item { BlockPos blockposition = context.getClickedPos(); BlockState iblockdata = world.getBlockState(blockposition); - if (!iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) { + if (!world.purpurConfig.endCrystalPlaceAnywhere && !iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) { return InteractionResult.FAIL; } else { BlockPos blockposition1 = blockposition.above(); final BlockPos aboveBlockPosition = blockposition1; // Paper - OBFHELPER @@ -57,7 +57,7 @@ public class EndCrystalItem extends Item { world.gameEvent((Entity) context.getPlayer(), (Holder) GameEvent.ENTITY_PLACE, blockposition1); EndDragonFight enderdragonbattle = ((ServerLevel) world).getDragonFight(); - if (enderdragonbattle != null) { + if (enderdragonbattle != null && gg.pufferfish.pufferfish.PufferfishConfig.allowEndCrystalRespawn) { // Pufferfish enderdragonbattle.tryRespawn(aboveBlockPosition); // Paper - Perf: Do crystal-portal proximity check before entity lookup } } diff --git a/src/main/java/net/minecraft/world/item/EnderpearlItem.java b/src/main/java/net/minecraft/world/item/EnderpearlItem.java index b232390d8ee8e449e61c0ea7f3af60df507abb97..4039d300debadf29e6c544e8b4c950b7121a02d1 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 { if (world instanceof ServerLevel worldserver) { // CraftBukkit start // Paper start - PlayerLaunchProjectileEvent - final Projectile.Delayed thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, worldserver, itemstack, user, 0.0F, 1.5F, 1.0F); + final Projectile.Delayed thrownEnderpearl = Projectile.spawnProjectileFromRotationDelayed(ThrownEnderpearl::new, worldserver, itemstack, user, 0.0F, 1.5F, (float) worldserver.purpurConfig.enderPearlProjectileOffset); // Purpur com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) thrownEnderpearl.projectile().getBukkitEntity()); if (event.callEvent() && thrownEnderpearl.attemptSpawn()) { if (event.shouldConsume()) { @@ -35,6 +35,7 @@ public class EnderpearlItem extends Item { world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); user.awardStat(Stats.ITEM_USED.get(this)); + user.getCooldowns().addCooldown(itemstack, user.getAbilities().instabuild ? world.purpurConfig.enderPearlCooldownCreative : world.purpurConfig.enderPearlCooldown); // Purpur } else { // Paper end - PlayerLaunchProjectileEvent if (user instanceof net.minecraft.server.level.ServerPlayer) { diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java index 29a048a9b09166838616ac7ba1d31625d56b0bca..184e6d9bf393188fc1f1c7acd545b4ac6d31f6a4 100644 --- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java @@ -66,6 +66,18 @@ public class FireworkRocketItem extends Item implements ProjectileItem { com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) delayed.projectile().getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand)); if (event.callEvent() && delayed.attemptSpawn()) { user.awardStat(Stats.ITEM_USED.get(this)); // Moved up from below + + // Purpur start + if (world.purpurConfig.elytraDamagePerFireworkBoost > 0) { + List list = net.minecraft.world.entity.EquipmentSlot.VALUES.stream().filter((enumitemslot) -> net.minecraft.world.entity.LivingEntity.canGlideUsing(user.getItemBySlot(enumitemslot), enumitemslot)).toList(); + net.minecraft.world.entity.EquipmentSlot enumitemslot = net.minecraft.Util.getRandom(list, user.random); + + ItemStack glideItem = user.getItemBySlot(enumitemslot); + if (user.canGlide()) { + glideItem.hurtAndBreak(world.purpurConfig.elytraDamagePerFireworkBoost, user, enumitemslot); + } + } + // Purpur end if (event.shouldConsume() && !user.hasInfiniteMaterials()) { itemStack.shrink(1); // Moved up from below } else ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); diff --git a/src/main/java/net/minecraft/world/item/HangingEntityItem.java b/src/main/java/net/minecraft/world/item/HangingEntityItem.java index cdc17ad948d8ac5de62f14b1a561433d33211f32..44a7cee7df2927a923455e8cedaab59307b42506 100644 --- a/src/main/java/net/minecraft/world/item/HangingEntityItem.java +++ b/src/main/java/net/minecraft/world/item/HangingEntityItem.java @@ -75,6 +75,11 @@ public class HangingEntityItem extends Item { if (!customdata.isEmpty()) { EntityType.updateCustomEntityTag(world, entityhuman, (Entity) object, customdata); + // Purpur start + if (!world.purpurConfig.persistentDroppableEntityDisplayNames) { + ((Entity) object).setCustomName(null); + } + // Purpur end } if (((HangingEntity) object).survives()) { diff --git a/src/main/java/net/minecraft/world/item/HoeItem.java b/src/main/java/net/minecraft/world/item/HoeItem.java index d2871bb4fd670ae4133d13f290b3256c9177d8e6..0936bdc945f73c7750c20a34276aead2921eeb61 100644 --- a/src/main/java/net/minecraft/world/item/HoeItem.java +++ b/src/main/java/net/minecraft/world/item/HoeItem.java @@ -46,15 +46,23 @@ public class HoeItem extends DiggerItem { public InteractionResult useOn(UseOnContext context) { Level level = context.getLevel(); BlockPos blockPos = context.getClickedPos(); - Pair, Consumer> pair = TILLABLES.get(level.getBlockState(blockPos).getBlock()); - if (pair == null) { - return InteractionResult.PASS; - } else { - Predicate predicate = pair.getFirst(); - Consumer consumer = pair.getSecond(); + // Purpur start + Block clickedBlock = level.getBlockState(blockPos).getBlock(); + var tillable = level.purpurConfig.hoeTillables.get(clickedBlock); + if (tillable == null) { return InteractionResult.PASS; } else { + Predicate predicate = tillable.condition().predicate(); + Consumer consumer = (ctx) -> { + level.setBlock(blockPos, tillable.into().defaultBlockState(), 11); + tillable.drops().forEach((drop, chance) -> { + if (level.random.nextDouble() < chance) { + Block.popResourceFromFace(level, blockPos, ctx.getClickedFace(), new ItemStack(drop)); + } + }); + }; + // Purpur end if (predicate.test(context)) { Player player = context.getPlayer(); - level.playSound(player, blockPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F); + if (!TILLABLES.containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound if (!level.isClientSide) { consumer.accept(context); if (player != null) { diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java index 33e7d2884195677c4d6340d8b84c1dd85c636ec1..42b259c3878bd34b8e7cb768887fd1456be8db35 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -503,6 +503,7 @@ public final class ItemStack implements DataComponentHolder { world.isBlockPlaceCancelled = true; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent for (BlockState blockstate : blocks) { blockstate.update(true, false); + ((CraftBlock) blockstate.getBlock()).getNMS().getBlock().forgetPlacer(); // Purpur } world.isBlockPlaceCancelled = false; // Paper - prevent calling cleanup logic when undoing a block place upon a cancelled BlockPlaceEvent world.preventPoiUpdated = false; @@ -535,6 +536,7 @@ public final class ItemStack implements DataComponentHolder { if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically block.onPlace(world, newblockposition, oldBlock, true, context); } + block.getBlock().forgetPlacer(); // Purpur world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point } @@ -681,6 +683,26 @@ public final class ItemStack implements DataComponentHolder { return this.isDamageableItem() && this.getDamageValue() > 0; } + // Purpur start - Add option to mend the most damaged equipment first + public float getDamagePercent() { + if (this.has(DataComponents.UNBREAKABLE)) { + return 0.0F; + } + + final int maxDamage = this.getOrDefault(DataComponents.MAX_DAMAGE, 0); + if (maxDamage == 0) { + return 0.0F; + } + + final int damage = this.getOrDefault(DataComponents.DAMAGE, 0); + if (damage == 0) { + return 0.0F; + } + + return (float) damage / maxDamage; + } + // Purpur end - Add option to mend the most damaged equipment first + public int getDamageValue() { return Mth.clamp((Integer) this.getOrDefault(DataComponents.DAMAGE, 0), 0, this.getMaxDamage()); } @@ -761,6 +783,12 @@ public final class ItemStack implements DataComponentHolder { org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(serverPlayer, this); // Paper - Add EntityDamageItemEvent } // CraftBukkit end + // Purpur start + if (this.has(DataComponents.GLIDER)) { + setDamageValue(this.getMaxDamage() - 1); + return; + } + // Purpur end this.shrink(1); breakCallback.accept(item); @@ -1325,6 +1353,12 @@ public final class ItemStack implements DataComponentHolder { return !((ItemEnchantments) this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY)).isEmpty(); } + // Purpur start - Config to allow unsafe enchants + public boolean hasEnchantment(Holder enchantment) { + return this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY).getLevel(enchantment) > 0; + } + // Purpur end - Config to allow unsafe enchants + public ItemEnchantments getEnchantments() { return (ItemEnchantments) this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); } diff --git a/src/main/java/net/minecraft/world/item/Items.java b/src/main/java/net/minecraft/world/item/Items.java index 5a70111cd39af50981cfd440c021340da1de5eab..580bd63fdbf9555f867362d3c1f39f41fd750089 100644 --- a/src/main/java/net/minecraft/world/item/Items.java +++ b/src/main/java/net/minecraft/world/item/Items.java @@ -363,7 +363,7 @@ public class Items { public static final Item PURPUR_BLOCK = registerBlock(Blocks.PURPUR_BLOCK); public static final Item PURPUR_PILLAR = registerBlock(Blocks.PURPUR_PILLAR); public static final Item PURPUR_STAIRS = registerBlock(Blocks.PURPUR_STAIRS); - public static final Item SPAWNER = registerBlock(Blocks.SPAWNER); + public static final Item SPAWNER = registerBlock(Blocks.SPAWNER, org.purpurmc.purpur.item.SpawnerItem::new, new Item.Properties().rarity(Rarity.EPIC)); // Purpur public static final Item CREAKING_HEART = registerBlock(Blocks.CREAKING_HEART); public static final Item CHEST = registerBlock(Blocks.CHEST, settings -> settings.component(DataComponents.CONTAINER, ItemContainerContents.EMPTY)); public static final Item CRAFTING_TABLE = registerBlock(Blocks.CRAFTING_TABLE); @@ -2104,7 +2104,7 @@ public class Items { "sweet_berries", createBlockItemWithCustomItemName(Blocks.SWEET_BERRY_BUSH), new Item.Properties().food(Foods.SWEET_BERRIES) ); public static final Item GLOW_BERRIES = registerItem( - "glow_berries", createBlockItemWithCustomItemName(Blocks.CAVE_VINES), new Item.Properties().food(Foods.GLOW_BERRIES) + "glow_berries", settings -> new org.purpurmc.purpur.item.GlowBerryItem(Blocks.CAVE_VINES, settings.useItemDescriptionPrefix()), new Item.Properties().food(Foods.GLOW_BERRIES) // Purpur ); public static final Item CAMPFIRE = registerBlock(Blocks.CAMPFIRE, settings -> settings.component(DataComponents.CONTAINER, ItemContainerContents.EMPTY)); public static final Item SOUL_CAMPFIRE = registerBlock( diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java index 571f2540a1e9422025efe651167e26b44b437daa..c2f3c8b3d8eeb609b6d6067c4fb404aefbf94ec5 100644 --- a/src/main/java/net/minecraft/world/item/MapItem.java +++ b/src/main/java/net/minecraft/world/item/MapItem.java @@ -194,6 +194,7 @@ public class MapItem extends Item { public static void renderBiomePreviewMap(ServerLevel world, ItemStack map) { MapItemSavedData mapItemSavedData = getSavedData(map, world); if (mapItemSavedData != null) { + mapItemSavedData.isExplorerMap = true; // Purpur if (world.dimension() == mapItemSavedData.dimension) { int i = 1 << mapItemSavedData.scale; int j = mapItemSavedData.centerX; diff --git a/src/main/java/net/minecraft/world/item/MinecartItem.java b/src/main/java/net/minecraft/world/item/MinecartItem.java index 7153d9ed12276a0f2d8b8a17c79734aa25ed1fa5..dc49ea6454e04ae8ec68af12c4bf2ff022540671 100644 --- a/src/main/java/net/minecraft/world/item/MinecartItem.java +++ b/src/main/java/net/minecraft/world/item/MinecartItem.java @@ -35,8 +35,9 @@ public class MinecartItem extends Item { BlockState iblockdata = world.getBlockState(blockposition); if (!iblockdata.is(BlockTags.RAILS)) { - return InteractionResult.FAIL; - } else { + if (!world.purpurConfig.minecartPlaceAnywhere) return InteractionResult.FAIL; // Purpur + if (iblockdata.isSolid()) blockposition = blockposition.relative(context.getClickedFace()); + } // else { // Purpur - place minecarts anywhere ItemStack itemstack = context.getItemInHand(); RailShape blockpropertytrackposition = iblockdata.getBlock() instanceof BaseRailBlock ? (RailShape) iblockdata.getValue(((BaseRailBlock) iblockdata.getBlock()).getShapeProperty()) : RailShape.NORTH_SOUTH; double d0 = 0.0D; @@ -80,6 +81,6 @@ public class MinecartItem extends Item { itemstack.shrink(1); return InteractionResult.SUCCESS; } - } + // } // Purpur - 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 000d1863bfba98b5132dfc6743362d687b2f54f3..20fece9908382f40b4082f7b1fb7d41914ae31be 100644 --- a/src/main/java/net/minecraft/world/item/NameTagItem.java +++ b/src/main/java/net/minecraft/world/item/NameTagItem.java @@ -23,6 +23,7 @@ public class NameTagItem extends Item { if (!event.callEvent()) return InteractionResult.PASS; LivingEntity newEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); newEntity.setCustomName(event.getName() != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getName()) : null); + if (user.level().purpurConfig.armorstandFixNametags && entity instanceof net.minecraft.world.entity.decoration.ArmorStand) entity.setCustomNameVisible(true); // Purpur if (event.isPersistent() && newEntity instanceof Mob mob) { // Paper end - Add PlayerNameEntityEvent mob.setPersistenceRequired(); diff --git a/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java b/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java index 78ba170a83f8c026bd110eae494c52577182ed61..c2ae50872cead7202246b9cce4db6e0a81e1cf5f 100644 --- a/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java +++ b/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java @@ -105,6 +105,8 @@ public abstract class ProjectileWeaponItem extends Item { entityarrow.setCritArrow(true); } + entityarrow.setActualEnchantments(weaponStack.getEnchantments()); // Purpur - Add an option to fix MC-3304 projectile looting + return entityarrow; } diff --git a/src/main/java/net/minecraft/world/item/ShovelItem.java b/src/main/java/net/minecraft/world/item/ShovelItem.java index 55c18f182166f4905d623d6f5e909eefd5ed2483..d10c4705cc9e7faabd4a5619e1da107231bdb37e 100644 --- a/src/main/java/net/minecraft/world/item/ShovelItem.java +++ b/src/main/java/net/minecraft/world/item/ShovelItem.java @@ -47,9 +47,12 @@ public class ShovelItem extends DiggerItem { BlockState blockState2 = FLATTENABLES.get(blockState.getBlock()); BlockState blockState3 = null; Runnable afterAction = null; // Paper - if (blockState2 != null && level.getBlockState(blockPos.above()).isAir()) { - afterAction = () -> level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper - blockState3 = blockState2; + // Purpur start + var flattenable = level.purpurConfig.shovelFlattenables.get(blockState.getBlock()); + if (flattenable != null && level.getBlockState(blockPos.above()).isAir()) { + afterAction = () -> {if (!FLATTENABLES.containsKey(blockState.getBlock())) level.playSound(null, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F);}; // Paper + blockState3 = flattenable.into().defaultBlockState(); + // Purpur end } else if (blockState.getBlock() instanceof CampfireBlock && blockState.getValue(CampfireBlock.LIT)) { afterAction = () -> { // Paper if (!level.isClientSide()) { diff --git a/src/main/java/net/minecraft/world/item/SnowballItem.java b/src/main/java/net/minecraft/world/item/SnowballItem.java index 57872ebef6beb8cdc03c9f8f19de94652ee19062..60a064c26de0566aaf9f8be886402e291c03ca3b 100644 --- a/src/main/java/net/minecraft/world/item/SnowballItem.java +++ b/src/main/java/net/minecraft/world/item/SnowballItem.java @@ -27,7 +27,7 @@ public class SnowballItem extends Item implements ProjectileItem { // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.SNOWBALL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); if (world instanceof ServerLevel worldserver) { // Paper start - PlayerLaunchProjectileEvent - final Projectile.Delayed snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, worldserver, itemstack, user, 0.0F, 1.5F, 1.0F); + final Projectile.Delayed snowball = Projectile.spawnProjectileFromRotationDelayed(Snowball::new, worldserver, itemstack, user, 0.0F, 1.5F, (float) worldserver.purpurConfig.snowballProjectileOffset); // Purpur com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) snowball.projectile().getBukkitEntity()); if (event.callEvent() && snowball.attemptSpawn()) { user.awardStat(Stats.ITEM_USED.get(this)); diff --git a/src/main/java/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java index 9956ed42df55daa6d97fd6e3ab5368dad91cfaf0..e0e746d6c78421b40777125ba49f0a04809f5415 100644 --- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java +++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java @@ -73,6 +73,24 @@ public class SpawnEggItem extends Item { Spawner spawner = (Spawner) tileentity; entitytypes = this.getType(itemstack); + + // Purpur start + if (spawner instanceof net.minecraft.world.level.block.entity.SpawnerBlockEntity) { + org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); + org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent event = new org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), bukkitBlock, (org.bukkit.block.CreatureSpawner) bukkitBlock.getState(), org.bukkit.entity.EntityType.fromName(entitytypes.getName())); + if (!event.callEvent()) { + return InteractionResult.FAIL; + } + entitytypes = EntityType.getFromBukkitType(event.getEntityType()); + } else if (spawner instanceof net.minecraft.world.level.block.entity.TrialSpawnerBlockEntity) { + org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); + org.purpurmc.purpur.event.PlayerSetTrialSpawnerTypeWithEggEvent event = new org.purpurmc.purpur.event.PlayerSetTrialSpawnerTypeWithEggEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), bukkitBlock, (org.bukkit.block.TrialSpawner) bukkitBlock.getState(), org.bukkit.entity.EntityType.fromName(entitytypes.getName())); + if (!event.callEvent()) { + return InteractionResult.FAIL; + } + entitytypes = EntityType.getFromBukkitType(event.getEntityType()); + } + // Purpur end spawner.setEntityId(entitytypes, world.getRandom()); world.sendBlockUpdated(blockposition, iblockdata, iblockdata, 3); world.gameEvent((Entity) context.getPlayer(), (Holder) GameEvent.BLOCK_CHANGE, blockposition); diff --git a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java index fa9d2ae44fcdd06f8f33cd14ffca422b20a01451..ffbc71ca2a27800d7758e3db339bf06a39ef1f11 100644 --- a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java +++ b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java @@ -21,7 +21,7 @@ public class ThrowablePotionItem extends PotionItem implements ProjectileItem { ItemStack itemStack = user.getItemInHand(hand); if (world instanceof ServerLevel serverLevel) { // Paper start - PlayerLaunchProjectileEvent - final Projectile.Delayed thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemStack, user, -20.0F, 0.5F, 1.0F); + final Projectile.Delayed thrownPotion = Projectile.spawnProjectileFromRotationDelayed(ThrownPotion::new, serverLevel, itemStack, user, -20.0F, 0.5F, (float) serverLevel.purpurConfig.throwablePotionProjectileOffset); // Purpur // Paper start - PlayerLaunchProjectileEvent com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownPotion.projectile().getBukkitEntity()); if (event.callEvent() && thrownPotion.attemptSpawn()) { diff --git a/src/main/java/net/minecraft/world/item/TridentItem.java b/src/main/java/net/minecraft/world/item/TridentItem.java index 8b9a93ef71164cce8a616735b71d96d37e83b1a8..23e04d0d68ffa0e07ab295e7121f8a4963f4914a 100644 --- a/src/main/java/net/minecraft/world/item/TridentItem.java +++ b/src/main/java/net/minecraft/world/item/TridentItem.java @@ -88,7 +88,7 @@ public class TridentItem extends Item implements ProjectileItem { // itemstack.hurtWithoutBreaking(1, entityhuman); // CraftBukkit - moved down if (f == 0.0F) { // Paper start - PlayerLaunchProjectileEvent - Projectile.Delayed tridentDelayed = Projectile.spawnProjectileFromRotationDelayed(ThrownTrident::new, worldserver, stack, entityhuman, 0.0F, 2.5F, 1.0F); + Projectile.Delayed tridentDelayed = Projectile.spawnProjectileFromRotationDelayed(ThrownTrident::new, worldserver, stack, entityhuman, 0.0F, 2.5F, (float) worldserver.purpurConfig.tridentProjectileOffset); // Purpur // Paper start - PlayerLaunchProjectileEvent com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) tridentDelayed.projectile().getBukkitEntity()); if (!event.callEvent() || !tridentDelayed.attemptSpawn()) { @@ -100,6 +100,9 @@ public class TridentItem extends Item implements ProjectileItem { return false; } ThrownTrident entitythrowntrident = tridentDelayed.projectile(); // Paper - PlayerLaunchProjectileEvent + + entitythrowntrident.setActualEnchantments(stack.getEnchantments()); // Purpur - Add an option to fix MC-3304 projectile looting + if (event.shouldConsume()) stack.hurtWithoutBreaking(1, entityhuman); // Paper - PlayerLaunchProjectileEvent entitythrowntrident.pickupItemStack = stack.copy(); // SPIGOT-4511 update since damage call moved // CraftBukkit end @@ -132,6 +135,18 @@ public class TridentItem extends Item implements ProjectileItem { f4 *= f / f6; f5 *= f / f6; org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerRiptideEvent(entityhuman, stack, f3, f4, f5); // CraftBukkit + + // Purpur start + List list = EquipmentSlot.VALUES.stream().filter((enumitemslot) -> LivingEntity.canGlideUsing(entityhuman.getItemBySlot(enumitemslot), enumitemslot)).toList(); + if (!list.isEmpty()) { + EquipmentSlot enumitemslot = net.minecraft.Util.getRandom(list, entityhuman.random); + ItemStack glideItem = entityhuman.getItemBySlot(enumitemslot); + if (glideItem.has(net.minecraft.core.component.DataComponents.GLIDER) && world.purpurConfig.elytraDamagePerTridentBoost > 0) { + glideItem.hurtAndBreak(world.purpurConfig.elytraDamagePerTridentBoost, entityhuman, enumitemslot); + } + } + // Purpur end + entityhuman.push((double) f3, (double) f4, (double) f5); entityhuman.startAutoSpinAttack(20, 8.0F, stack); if (entityhuman.onGround()) { diff --git a/src/main/java/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java b/src/main/java/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java index 0651c2af040e3f248860cfb3c5effce91589380e..d884df481b4bbb978113a4ac7a1feac31cf2f951 100644 --- a/src/main/java/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java +++ b/src/main/java/net/minecraft/world/item/consume_effects/ClearAllStatusEffectsConsumeEffect.java @@ -24,6 +24,12 @@ public record ClearAllStatusEffectsConsumeEffect() implements ConsumeEffect { @Override // CraftBukkit start public boolean apply(Level world, ItemStack itemstack, LivingEntity entityliving, EntityPotionEffectEvent.Cause cause) { + // Purpur start + net.minecraft.world.effect.MobEffectInstance badOmen = entityliving.getEffect(net.minecraft.world.effect.MobEffects.BAD_OMEN); + if (!world.purpurConfig.milkCuresBadOmen && itemstack.is(net.minecraft.world.item.Items.MILK_BUCKET) && badOmen != null) { + return entityliving.removeAllEffects(cause) && entityliving.addEffect(badOmen); + } + // Purpur end return entityliving.removeAllEffects(cause); // CraftBukkit end } diff --git a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java index 812f919a7a7e309c8513f44104f092496037608f..10730b307971915f52b3e41068a864b8ee1352b4 100644 --- a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java +++ b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java @@ -45,6 +45,7 @@ public final class Ingredient implements Predicate { // CraftBukkit start @Nullable private List itemStacks; + public Predicate predicate; // Purpur public boolean isExact() { return this.itemStacks != null; @@ -100,6 +101,11 @@ public final class Ingredient implements Predicate { return false; } + // Purpur start + if (predicate != null) { + return predicate.test(itemstack.asBukkitCopy()); + } + // Purpur end // CraftBukkit end List> list = this.items(); diff --git a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java index 12f95bee2a69fd5df7c4a165537e01299e60c5f6..d7ce86752e4138cdd3844b3374609753aa20f9ea 100644 --- a/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java +++ b/src/main/java/net/minecraft/world/item/crafting/ShapelessRecipe.java @@ -31,8 +31,13 @@ public class ShapelessRecipe implements CraftingRecipe { final List ingredients; @Nullable private PlacementInfo placementInfo; + private final boolean isBukkit; // Pufferfish + // Pufferfish start public ShapelessRecipe(String group, CraftingBookCategory category, ItemStack result, List ingredients) { + this(group, category, result, ingredients, false); + } + public ShapelessRecipe(String group, CraftingBookCategory category, ItemStack result, List ingredients, boolean isBukkit) { this.isBukkit = isBukkit; // Pufferfish end this.group = group; this.category = category; this.result = result; @@ -80,6 +85,28 @@ public class ShapelessRecipe implements CraftingRecipe { } public boolean matches(CraftingInput input, Level world) { + // Pufferfish start + if (!this.isBukkit) { + java.util.List ingredients = com.google.common.collect.Lists.newArrayList(this.ingredients.toArray(new Ingredient[0])); + + inventory: for (int index = 0; index < input.size(); index++) { + ItemStack itemStack = input.getItem(index); + + if (!itemStack.isEmpty()) { + for (int i = 0; i < ingredients.size(); i++) { + if (ingredients.get(i).test(itemStack)) { + ingredients.remove(i); + continue inventory; + } + } + return false; + } + } + + return ingredients.isEmpty(); + } + // Pufferfish end + // Paper start - Improve exact choice recipe ingredients & unwrap ternary if (input.ingredientCount() != this.ingredients.size()) { return false; diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java index f99b87cf70df7eaac13d46f4e0234b1e6483d342..73241113e50dc8be89ef8850d49d95ec31fb194f 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java +++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java @@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap.Entry; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -578,4 +579,58 @@ public class EnchantmentHelper { interface EnchantmentVisitor { void accept(Holder enchantment, int level); } + + // Purpur start - Enchantment convenience methods + public static Holder.Reference getEnchantmentHolder(ResourceKey enchantment) { + return net.minecraft.server.MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT).getOrThrow(enchantment); + } + + public static int getItemEnchantmentLevel(ResourceKey enchantment, ItemStack stack) { + return getItemEnchantmentLevel(getEnchantmentHolder(enchantment), stack); + } + // Purpur end - Enchantment convenience methods + + // Purpur start - Add option to mend the most damaged equipment first + public static Optional getMostDamagedItemWith(DataComponentType componentType, LivingEntity entity) { + ItemStack maxStack = null; + EquipmentSlot maxSlot = null; + float maxPercent = 0.0F; + + equipmentSlotLoop: + for (EquipmentSlot equipmentSlot : EquipmentSlot.values()) { + ItemStack stack = entity.getItemBySlot(equipmentSlot); + + // do not even check enchantments for item with lower or equal damage percent + float percent = stack.getDamagePercent(); + if (percent <= maxPercent) { + continue; + } + + ItemEnchantments itemEnchantments = stack.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); + + for (Entry> entry : itemEnchantments.entrySet()) { + Enchantment enchantment = entry.getKey().value(); + + net.minecraft.core.component.DataComponentMap effects = enchantment.effects(); + if (!effects.has(componentType)) { + // try with another enchantment + continue; + } + + if (enchantment.matchingSlot(equipmentSlot)) { + maxStack = stack; + maxSlot = equipmentSlot; + maxPercent = percent; + + // check another slot now + continue equipmentSlotLoop; + } + } + } + + return maxStack != null + ? Optional.of(new EnchantedItemInUse(maxStack, maxSlot, entity)) + : Optional.empty(); + } + // Purpur end - Add option to mend the most damaged equipment first } diff --git a/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java b/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java index cfc6a657cae92c68868a76c1b7b1febe2a16e9f4..a12c08da793139e39dc11c213c94796b83bd8240 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java +++ b/src/main/java/net/minecraft/world/item/enchantment/ItemEnchantments.java @@ -35,7 +35,7 @@ public class ItemEnchantments implements TooltipProvider { private static final java.util.Comparator> ENCHANTMENT_ORDER = java.util.Comparator.comparing(Holder::getRegisteredName); public static final ItemEnchantments EMPTY = new ItemEnchantments(new Object2IntAVLTreeMap<>(ENCHANTMENT_ORDER), true); // Paper end - private static final Codec LEVEL_CODEC = Codec.intRange(1, 255); + private static final Codec LEVEL_CODEC = Codec.intRange(1, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)); // Purpur private static final Codec>> LEVELS_CODEC = Codec.unboundedMap( Enchantment.CODEC, LEVEL_CODEC )// Paper start - sort enchantments @@ -69,7 +69,7 @@ public class ItemEnchantments implements TooltipProvider { for (Entry> entry : enchantments.object2IntEntrySet()) { int i = entry.getIntValue(); - if (i < 0 || i > 255) { + if (i < 0 || i > (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)) { // Purpur throw new IllegalArgumentException("Enchantment " + entry.getKey() + " has invalid level " + i); } } @@ -164,13 +164,13 @@ public class ItemEnchantments implements TooltipProvider { if (level <= 0) { this.enchantments.removeInt(enchantment); } else { - this.enchantments.put(enchantment, Math.min(level, 255)); + this.enchantments.put(enchantment, Math.min(level, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767))); // Purpur } } public void upgrade(Holder enchantment, int level) { if (level > 0) { - this.enchantments.merge(enchantment, Math.min(level, 255), Integer::max); + this.enchantments.merge(enchantment, Math.min(level, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels ? 255 : 32767)), Integer::max); // Purpur } } diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java index 0efc8d997b34302c3e0a5d7ec73a11a940dbeefe..af157881d440b34cfe79fbc9b03cc9ef28515eb8 100644 --- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java @@ -131,7 +131,12 @@ public class MerchantOffer { } public void updateDemand() { - this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962 + // Purpur start + this.updateDemand(0); + } + public void updateDemand(int minimumDemand) { + this.demand = Math.max(minimumDemand, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962 + // Purpur end } public ItemStack assemble() { diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java index 7de66aa435dd36899b80f4ecc64480680e474d94..bb4411cfdf1bc7adc12c2f918d2eec830299f38b 100644 --- a/src/main/java/net/minecraft/world/level/BaseSpawner.java +++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java @@ -59,6 +59,7 @@ public abstract class BaseSpawner { } public boolean isNearPlayer(Level world, BlockPos pos) { + if (world.purpurConfig.spawnerDeactivateByRedstone && world.hasNeighborSignal(pos)) return false; // Purpur return world.hasNearbyAlivePlayerThatAffectsSpawning((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper - Affects Spawning API } diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java index 5d7a6e4b73f032db356e7ec369b150013e940ee6..6b2cda6d578a0983b2401ea20629275431018433 100644 --- a/src/main/java/net/minecraft/world/level/EntityGetter.java +++ b/src/main/java/net/minecraft/world/level/EntityGetter.java @@ -184,7 +184,7 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) { for (Player player : this.players()) { - if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) { + if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player) && EntitySelector.notAfk.test(player)) { // Purpur double d = player.distanceToSqr(x, y, z); if (range < 0.0 || d < range * range) { return true; diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java index 0de2b79481352b52438dde284262019b29949ad8..34e8eb828be517d9ee3973597c74c5cac3d75c99 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -174,6 +174,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl // Paper end - add paper world config public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur public static BlockPos lastPhysicsProblem; // Spigot private org.spigotmc.TickLimiter entityLimiter; private org.spigotmc.TickLimiter tileLimiter; @@ -181,6 +182,49 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here + // Purpur start + private com.google.common.cache.Cache playerBreedingCooldowns; + + private com.google.common.cache.Cache getNewBreedingCooldownCache() { + return com.google.common.cache.CacheBuilder.newBuilder().expireAfterWrite(this.purpurConfig.animalBreedingCooldownSeconds, java.util.concurrent.TimeUnit.SECONDS).build(); + } + + public void resetBreedingCooldowns() { + this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); + } + + public boolean hasBreedingCooldown(java.util.UUID player, Class animalType) { // Purpur + return this.playerBreedingCooldowns.getIfPresent(new BreedingCooldownPair(player, animalType)) != null; + } + + public void addBreedingCooldown(java.util.UUID player, Class animalType) { + this.playerBreedingCooldowns.put(new BreedingCooldownPair(player, animalType), new Object()); + } + + private static final class BreedingCooldownPair { + private final java.util.UUID playerUUID; + private final Class animalType; + + public BreedingCooldownPair(java.util.UUID playerUUID, Class animalType) { + this.playerUUID = playerUUID; + this.animalType = animalType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BreedingCooldownPair that = (BreedingCooldownPair) o; + return playerUUID.equals(that.playerUUID) && animalType.equals(that.animalType); + } + + @Override + public int hashCode() { + return java.util.Objects.hash(playerUUID, animalType); + } + } + // Purpur end + public CraftWorld getWorld() { return this.world; } @@ -842,6 +886,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl // Paper end - getblock optimisations - cache world height/sections this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config + this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur + this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur this.generator = gen; this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); @@ -1487,16 +1533,16 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl public void guardEntityTick(Consumer tickConsumer, T entity) { try { tickConsumer.accept(entity); - } catch (Throwable throwable) { + } catch (Throwable throwable) { // Pufferfish - diff on change ServerLevel.tick if (throwable instanceof ThreadDeath) throw throwable; // Paper // Paper start - Prevent block entity and entity crashes final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); MinecraftServer.LOGGER.error(msg, throwable); getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); // Paper - ServerExceptionEvent - entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Pufferfish - diff on change ServerLevel.tick // Paper end - Prevent block entity and entity crashes } - this.moonrise$midTickTasks(); // Paper - rewrite chunk system + this.moonrise$midTickTasks(); // Paper - rewrite chunk system // Pufferfish - diff on change ServerLevel.tick } // Paper start - Option to prevent armor stands from doing entity lookups @Override @@ -2042,4 +2088,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl return this.id; } } + + // 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 c1b76a1ebc1eea7ab70cf61d8175a31794dd122a..dc15c15951e4ca30b8341d24f813259a77f41c77 100644 --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java +++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java @@ -280,7 +280,7 @@ public final class NaturalSpawner { blockposition_mutableblockposition.set(l, i, i1); double d0 = (double) l + 0.5D; double d1 = (double) i1 + 0.5D; - Player entityhuman = world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); + Player entityhuman = world.getNearestPlayer(d0, (double) i, d1, -1.0D, world.purpurConfig.mobSpawningIgnoreCreativePlayers); // Purpur if (entityhuman != null) { double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); diff --git a/src/main/java/net/minecraft/world/level/ServerExplosion.java b/src/main/java/net/minecraft/world/level/ServerExplosion.java index bbbd451ff184be8fa13bd93d53c89a9502f9951a..3c3d219c9339f64c23ec0b31783bf68a4423636c 100644 --- a/src/main/java/net/minecraft/world/level/ServerExplosion.java +++ b/src/main/java/net/minecraft/world/level/ServerExplosion.java @@ -311,7 +311,7 @@ public class ServerExplosion implements Explosion { public ServerExplosion(ServerLevel world, @Nullable Entity entity, @Nullable DamageSource damageSource, @Nullable ExplosionDamageCalculator behavior, Vec3 pos, float power, boolean createFire, Explosion.BlockInteraction destructionType) { this.level = world; this.source = entity; - this.radius = (float) Math.max(power, 0.0); // CraftBukkit - clamp bad values + this.radius = (float) (world == null || world.purpurConfig.explosionClampRadius ? Math.max(power, 0.0) : power); // CraftBukkit - clamp bad values // Purpur this.center = pos; this.fire = createFire; this.blockInteraction = destructionType; @@ -666,10 +666,27 @@ public class ServerExplosion implements Explosion { public void explode() { // CraftBukkit start - if (this.radius < 0.1F) { + if ((this.level == null || this.level.purpurConfig.explosionClampRadius) && this.radius < 0.1F) { // Purpur return; } // CraftBukkit end + // Purpur start - add PreExplodeEvents + if (this.source != null) { + Location location = new Location(this.level.getWorld(), this.center.x, this.center.y, this.center.z); + if(!new org.purpurmc.purpur.event.entity.PreEntityExplodeEvent(this.source.getBukkitEntity(), location, this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F, org.bukkit.craftbukkit.CraftExplosionResult.toBukkit(getBlockInteraction())).callEvent()) { + this.wasCanceled = true; + return; + } + } else { + Location location = new Location(this.level.getWorld(), this.center.x, this.center.y, this.center.z); + org.bukkit.block.Block block = location.getBlock(); + org.bukkit.block.BlockState blockState = (this.damageSource.getDirectBlockState() != null) ? this.damageSource.getDirectBlockState() : block.getState(); + if(!new org.purpurmc.purpur.event.PreBlockExplodeEvent(location.getBlock(), this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F, blockState, org.bukkit.craftbukkit.CraftExplosionResult.toBukkit(getBlockInteraction())).callEvent()) { + this.wasCanceled = true; + return; + } + } + // Purpur end // Paper start - collision optimisations this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; diff --git a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java index 50c907c962f936d2035bb7550750cdbd220b29c2..f9a2d2d4f798efa0d691996ec5ff7fe00260b36c 100644 --- a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java +++ b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java @@ -59,6 +59,53 @@ public class AnvilBlock extends FallingBlock { return this.defaultBlockState().setValue(FACING, ctx.getHorizontalDirection().getClockWise()); } + // Purpur start - repairable/damageable anvils + @Override + protected net.minecraft.world.InteractionResult useItemOn(final net.minecraft.world.item.ItemStack stack, final BlockState state, final Level world, final BlockPos pos, final Player player, final net.minecraft.world.InteractionHand hand, final BlockHitResult hit) { + if (world.purpurConfig.anvilRepairIngotsAmount > 0 && stack.is(net.minecraft.world.item.Items.IRON_INGOT)) { + if (stack.getCount() < world.purpurConfig.anvilRepairIngotsAmount) { + // not enough iron ingots, play "error" sound and consume + world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); + return net.minecraft.world.InteractionResult.CONSUME; + } + if (state.is(Blocks.DAMAGED_ANVIL)) { + world.setBlock(pos, Blocks.CHIPPED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3); + } else if (state.is(Blocks.CHIPPED_ANVIL)) { + world.setBlock(pos, Blocks.ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3); + } else if (state.is(Blocks.ANVIL)) { + // anvil is already fully repaired, play "error" sound and consume + world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); + return net.minecraft.world.InteractionResult.CONSUME; + } + if (!player.getAbilities().instabuild) { + stack.shrink(world.purpurConfig.anvilRepairIngotsAmount); + } + world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_PLACE, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); + return net.minecraft.world.InteractionResult.CONSUME; + } + if (world.purpurConfig.anvilDamageObsidianAmount > 0 && stack.is(net.minecraft.world.item.Items.OBSIDIAN)) { + if (stack.getCount() < world.purpurConfig.anvilDamageObsidianAmount) { + // not enough obsidian, play "error" sound and consume + world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); + return net.minecraft.world.InteractionResult.CONSUME; + } + if (state.is(Blocks.DAMAGED_ANVIL)) { + world.destroyBlock(pos, false); + } else if (state.is(Blocks.CHIPPED_ANVIL)) { + world.setBlock(pos, Blocks.DAMAGED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3); + } else if (state.is(Blocks.ANVIL)) { + world.setBlock(pos, Blocks.CHIPPED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3); + } + if (!player.getAbilities().instabuild) { + stack.shrink(world.purpurConfig.anvilDamageObsidianAmount); + } + world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_LAND, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); + return net.minecraft.world.InteractionResult.CONSUME; + } + return net.minecraft.world.InteractionResult.TRY_WITH_EMPTY_HAND; + } + // Purpur end + @Override protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { if (!world.isClientSide) { diff --git a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java index affbbf6abc6bc09ecb652c1dee92aa297458bc39..febc05dc39741807127cba4a2a55aaad62b0800c 100644 --- a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java +++ b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java @@ -50,6 +50,20 @@ public class AzaleaBlock extends BushBlock implements BonemealableBlock { @Override public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + // Purpur start + 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 TreeGrower.AZALEA.growTree(world, world.getChunkSource().getGenerator(), pos, state, random); } diff --git a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java index d7ca7a43d2d5f8cad416156fd40588cdd6634f52..231338adda19f57bf1a95379cc2e14341d4068d0 100644 --- a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java @@ -39,6 +39,7 @@ public abstract class BaseCoralPlantTypeBlock extends Block implements SimpleWat } protected static boolean scanForWater(BlockState state, BlockGetter world, BlockPos pos) { + if (!((net.minecraft.world.level.LevelAccessor) world).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur 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 c02c4834ace843633b77fb43eeadd3ddc7b1f743..c130d316e87f1f896d33ab43831063a89e3bef2b 100644 --- a/src/main/java/net/minecraft/world/level/block/BedBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java @@ -106,7 +106,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock Vec3 vec3d = pos.getCenter(); - world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); + if (world.purpurConfig.bedExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // Purpur return InteractionResult.SUCCESS_SERVER; } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) { if (!BedBlock.canSetSpawn(world)) return this.explodeBed(state, world, pos); // Paper - check explode first @@ -159,7 +159,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock Vec3 vec3d = blockposition.getCenter(); - world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state + if (world.purpurConfig.bedExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), (ExplosionDamageCalculator) null, vec3d, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // CraftBukkit - add state // Purpur return InteractionResult.SUCCESS; } } @@ -183,7 +183,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock @Override public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { - super.fallOn(world, state, pos, entity, fallDistance * 0.5F); + super.fallOn(world, state, pos, entity, fallDistance); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java index 9e3f1441d62128535112621bf259c24f1a90595b..2535e6d71b690f8dfde41a7d9cb76b6f010f5aa7 100644 --- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java @@ -246,7 +246,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone BigDripleafBlock.playTiltSound(world, blockposition, soundeffect); } - int i = BigDripleafBlock.DELAY_UNTIL_NEXT_TILT_STATE.getInt(tilt); + int i = world.purpurConfig.bigDripleafTiltDelay.getOrDefault(tilt, -1); // Purpur if (i != -1) { world.scheduleTick(blockposition, (Block) this, i); diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java index b6d6c2cb9b227a17fb4ce42bc75f92206fbea043..1748aea6613e0c8081f70092c9431e7e04907383 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java @@ -88,6 +88,10 @@ public class Block extends BlockBehaviour implements ItemLike { public static final int UPDATE_LIMIT = 512; protected final StateDefinition stateDefinition; private BlockState defaultBlockState; + // Purpur start + public float fallDamageMultiplier = 1.0F; + public float fallDistanceMultiplier = 1.0F; + // Purpur end // Paper start - Protect Bedrock and End Portal/Frames from being destroyed public final boolean isDestroyable() { return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || @@ -303,7 +307,7 @@ public class Block extends BlockBehaviour implements ItemLike { public static void dropResources(BlockState state, LevelAccessor world, BlockPos pos, @Nullable BlockEntity blockEntity) { if (world instanceof ServerLevel) { Block.getDrops(state, (ServerLevel) world, pos, blockEntity).forEach((itemstack) -> { - Block.popResource((ServerLevel) world, pos, itemstack); + Block.popResource((ServerLevel) world, pos, applyLoreFromTile(itemstack, blockEntity)); // Purpur }); state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true); } @@ -322,7 +326,7 @@ public class Block extends BlockBehaviour implements ItemLike { event.setExpToDrop(block.getExpDrop(state, serverLevel, pos, net.minecraft.world.item.ItemStack.EMPTY, true)); // Paper - Properly handle xp dropping event.callEvent(); for (org.bukkit.inventory.ItemStack drop : event.getDrops()) { - popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); + popResource(serverLevel, pos, applyLoreFromTile(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop), blockEntity)); // Purpur } state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping block.popExperience(serverLevel, pos, event.getExpToDrop()); // Paper - Properly handle xp dropping @@ -339,13 +343,32 @@ public class Block extends BlockBehaviour implements ItemLike { // Paper end - Properly handle xp dropping if (world instanceof ServerLevel) { Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> { - Block.popResource(world, pos, itemstack1); + Block.popResource(world, pos, applyLoreFromTile(itemstack1, blockEntity)); // Purpur }); state.spawnAfterBreak((ServerLevel) world, pos, tool, dropExperience); // Paper - Properly handle xp dropping } } + // Purpur start + private static ItemStack applyLoreFromTile(ItemStack stack, @Nullable BlockEntity blockEntity) { + if (stack.getItem() instanceof BlockItem) { + if (blockEntity != null && blockEntity.getLevel() instanceof ServerLevel) { + net.minecraft.world.item.component.ItemLore lore = blockEntity.getPersistentLore(); + net.minecraft.core.component.DataComponentPatch.Builder builder = net.minecraft.core.component.DataComponentPatch.builder(); + if (blockEntity.getLevel().purpurConfig.persistentTileEntityLore && lore != null) { + builder.set(net.minecraft.core.component.DataComponents.LORE, lore); + } + if (!blockEntity.getLevel().purpurConfig.persistentTileEntityDisplayName) { + builder.remove(net.minecraft.core.component.DataComponents.CUSTOM_NAME); + } + stack.applyComponents(builder.build()); + } + } + return stack; + } + // Purpur end + public static void popResource(Level world, BlockPos pos, ItemStack stack) { double d0 = (double) EntityType.ITEM.getHeight() / 2.0D; double d1 = (double) pos.getX() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D); @@ -433,7 +456,17 @@ public class Block extends BlockBehaviour implements ItemLike { } // Paper - fix drops not preventing stats/food exhaustion } - public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {} + // Purpur start + @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(); @@ -444,7 +477,7 @@ public class Block extends BlockBehaviour implements ItemLike { } public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { - entity.causeFallDamage(fallDistance, 1.0F, entity.damageSources().fall()); + entity.causeFallDamage(fallDistance * fallDistanceMultiplier, fallDamageMultiplier, entity.damageSources().fall()); // Purpur } public void updateEntityMovementAfterFallOn(BlockGetter world, Entity entity) { diff --git a/src/main/java/net/minecraft/world/level/block/Blocks.java b/src/main/java/net/minecraft/world/level/block/Blocks.java index 66a07f7cbf1c1d6ecbe055cbf4f63eb07d93e90c..63d67d46d30ed8ed57cdc0e59b6cb6b75ab22c1f 100644 --- a/src/main/java/net/minecraft/world/level/block/Blocks.java +++ b/src/main/java/net/minecraft/world/level/block/Blocks.java @@ -6465,6 +6465,7 @@ public class Blocks { BlockBehaviour.Properties.of() .mapColor(MapColor.PLANT) .forceSolidOff() + .randomTicks() // Purpur .instabreak() .sound(SoundType.AZALEA) .noOcclusion() @@ -6476,6 +6477,7 @@ public class Blocks { BlockBehaviour.Properties.of() .mapColor(MapColor.PLANT) .forceSolidOff() + .randomTicks() // Purpur .instabreak() .sound(SoundType.FLOWERING_AZALEA) .noOcclusion() diff --git a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java index 385da0585f409ee453f10d45f5837cdc09adc21b..c65016cba376a41c267fb4b6499ec0a263851558 100644 --- a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java @@ -123,10 +123,10 @@ public class BubbleColumnBlock extends Block implements BucketPickup { if (state.is(Blocks.BUBBLE_COLUMN)) { return state; } else if (state.is(Blocks.SOUL_SAND)) { - return Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(false)); + return Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(org.purpurmc.purpur.PurpurConfig.soulSandBlockReverseBubbleColumnFlow)); // Purpur } else { return state.is(Blocks.MAGMA_BLOCK) - ? Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(true)) + ? Blocks.BUBBLE_COLUMN.defaultBlockState().setValue(DRAG_DOWN, Boolean.valueOf(!org.purpurmc.purpur.PurpurConfig.magmaBlockReverseBubbleColumnFlow)) // Purpur : Blocks.WATER.defaultBlockState(); } } diff --git a/src/main/java/net/minecraft/world/level/block/BushBlock.java b/src/main/java/net/minecraft/world/level/block/BushBlock.java index eb324fda54ada3ed7941713a784ed2d686ec8c4b..09cc76f3fee4a767c9ec3fa592f2c3c6146344ec 100644 --- a/src/main/java/net/minecraft/world/level/block/BushBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BushBlock.java @@ -55,4 +55,24 @@ public abstract class BushBlock extends Block { protected boolean isPathfindable(BlockState state, PathComputationType type) { return type == PathComputationType.AIR && !this.hasCollision ? true : super.isPathfindable(state, type); } + + // Purpur start + public void playerDestroyAndReplant(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, net.minecraft.world.item.ItemStack itemInHand, net.minecraft.world.level.ItemLike itemToReplant) { + player.awardStat(net.minecraft.stats.Stats.BLOCK_MINED.get(this)); + player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); + java.util.List dropList = Block.getDrops(state, (net.minecraft.server.level.ServerLevel) world, pos, blockEntity, player, itemInHand); + + boolean planted = false; + for (net.minecraft.world.item.ItemStack itemToDrop : dropList) { + if (!planted && itemToDrop.getItem() == itemToReplant) { + world.setBlock(pos, defaultBlockState(), 3); + itemToDrop.setCount(itemToDrop.getCount() - 1); + planted = true; + } + Block.popResource(world, pos, itemToDrop); + } + + state.spawnAfterBreak((net.minecraft.server.level.ServerLevel) world, pos, itemInHand, true); + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java index c045b1cccf0047dbef8c04d5a28d31d53389054f..9f163ed07f8e6a5370c4c355b4e910f7a49b6bcd 100644 --- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java @@ -24,7 +24,7 @@ import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit -public class CactusBlock extends Block { +public class CactusBlock extends Block implements BonemealableBlock { // Purpur public static final MapCodec CODEC = simpleCodec(CactusBlock::new); public static final IntegerProperty AGE = BlockStateProperties.AGE_15; @@ -115,7 +115,7 @@ public class CactusBlock extends Block { enumdirection = (Direction) iterator.next(); iblockdata1 = world.getBlockState(pos.relative(enumdirection)); - } while (!iblockdata1.isSolid() && !world.getFluidState(pos.relative(enumdirection)).is(FluidTags.LAVA)); + } while ((!world.getWorldBorder().world.purpurConfig.cactusBreaksFromSolidNeighbors || !iblockdata1.isSolid()) && !world.getFluidState(pos.relative(enumdirection)).is(FluidTags.LAVA)); // Purpur return false; } @@ -135,4 +135,34 @@ public class CactusBlock extends Block { protected boolean isPathfindable(BlockState state, PathComputationType type) { return false; } + + // Purpur start + @Override + public boolean isValidBonemealTarget(final LevelReader world, final BlockPos pos, final BlockState state) { + if (!((Level) world).purpurConfig.cactusAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false; + + int cactusHeight = 0; + while (world.getBlockState(pos.below(cactusHeight)).is(this)) { + cactusHeight++; + } + + return cactusHeight < ((Level) world).paperConfig().maxGrowthHeight.cactus; + } + + @Override + public boolean isBonemealSuccess(Level world, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + int cactusHeight = 0; + while (world.getBlockState(pos.below(cactusHeight)).is(this)) { + cactusHeight++; + } + for (int i = 0; i <= world.paperConfig().maxGrowthHeight.cactus - cactusHeight; i++) { + world.setBlockAndUpdate(pos.above(i), state.setValue(CactusBlock.AGE, 0)); + } + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/CakeBlock.java b/src/main/java/net/minecraft/world/level/block/CakeBlock.java index 648c2510beb162e73aed236a3169d0bbb8fc5050..3563a241c0b697dc0167cf7b1aa73fef7d1e7934 100644 --- a/src/main/java/net/minecraft/world/level/block/CakeBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CakeBlock.java @@ -119,6 +119,7 @@ public class CakeBlock extends Block { org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, 2 + oldFoodLevel); if (!event.isCancelled()) { + if (player.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) player.burpDelay = player.level().purpurConfig.playerBurpDelay; // Purpur player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 0.1F); } diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java index 18d4020017d76303d3179fad8974574777ea6305..2ee2b1485f848ac5270bc3f7e1e5b1bc3029b0bb 100644 --- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java @@ -140,7 +140,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB BlockPos blockposition = ctx.getClickedPos(); boolean flag = world.getFluidState(blockposition).getType() == Fluids.WATER; - return (BlockState) ((BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(CampfireBlock.WATERLOGGED, flag)).setValue(CampfireBlock.SIGNAL_FIRE, this.isSmokeSource(world.getBlockState(blockposition.below())))).setValue(CampfireBlock.LIT, !flag)).setValue(CampfireBlock.FACING, ctx.getHorizontalDirection()); + return (BlockState) ((BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(CampfireBlock.WATERLOGGED, flag)).setValue(CampfireBlock.SIGNAL_FIRE, this.isSmokeSource(world.getBlockState(blockposition.below())))).setValue(CampfireBlock.LIT, world.purpurConfig.campFireLitWhenPlaced ? !flag : world.purpurConfig.campFireLitWhenPlaced)).setValue(CampfireBlock.FACING, ctx.getHorizontalDirection()); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java b/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java index 22242d11e009acab4c9738a1c6ada8b9ba678a0c..828fa78a89f13ef81c53b4d6ea6ef86c7b53024e 100644 --- a/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java @@ -72,7 +72,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { SnowGolem entitysnowman = (SnowGolem) EntityType.SNOW_GOLEM.create(world, EntitySpawnReason.TRIGGERED); if (entitysnowman != null) { - CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection, entitysnowman, shapedetector_shapedetectorcollection.getBlock(0, 2, 0).getPos()); + CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection, entitysnowman, shapedetector_shapedetectorcollection.getBlock(0, 2, 0).getPos(), this.placer); // Purpur } } else { BlockPattern.BlockPatternMatch shapedetector_shapedetectorcollection1 = this.getOrCreateIronGolemFull().find(world, pos); @@ -82,7 +82,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock { if (entityirongolem != null) { entityirongolem.setPlayerCreated(true); - CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection1, entityirongolem, shapedetector_shapedetectorcollection1.getBlock(1, 2, 0).getPos()); + CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection1, entityirongolem, shapedetector_shapedetectorcollection1.getBlock(1, 2, 0).getPos(), this.placer); // Purpur } } } @@ -90,6 +90,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 c9968934f4ecaa8d81e545f279b3001c7b1ce545..03e4fce6f8226451365fc2831b5bf1e5e6091730 100644 --- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java @@ -37,7 +37,7 @@ public class CauldronBlock extends AbstractCauldronBlock { } protected static boolean shouldHandlePrecipitation(Level world, Biome.Precipitation precipitation) { - return precipitation == Biome.Precipitation.RAIN ? world.getRandom().nextFloat() < 0.05F : (precipitation == Biome.Precipitation.SNOW ? world.getRandom().nextFloat() < 0.1F : false); + return precipitation == Biome.Precipitation.RAIN ? world.getRandom().nextFloat() < world.purpurConfig.cauldronRainChance : (precipitation == Biome.Precipitation.SNOW ? world.getRandom().nextFloat() < world.purpurConfig.cauldronPowderSnowChance : false); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java index c4473c2a778116d48bc3e4796afd901f455070e6..e62217c0bfa6cc426c7d41154f3ccc34d8f242ca 100644 --- a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java @@ -94,4 +94,11 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements CaveVines { public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { world.setBlock(pos, state.setValue(BERRIES, Boolean.valueOf(true)), 2); } + + // Purpur start + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.caveVinesMaxGrowthAge; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java b/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java index daae7fd6e0148cfba8e359d990748a0c83a3376e..0e06b1bcd906e92c083dc74d56d6d0a2a36f62a7 100644 --- a/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java @@ -67,7 +67,7 @@ public interface ChangeOverTimeBlock> { } float f = (float) (k + 1) / (float) (k + j + 1); - float f1 = f * f * this.getChanceModifier(); + float f1 = world.purpurConfig.disableOxidationProximityPenalty ? this.getChanceModifier() : f * f * this.getChanceModifier(); // Purpur return random.nextFloat() < f1 ? this.getNext(state) : Optional.empty(); } diff --git a/src/main/java/net/minecraft/world/level/block/ChestBlock.java b/src/main/java/net/minecraft/world/level/block/ChestBlock.java index edef8fc62f8dba1b57214d8d7d805ff0d83f4114..663eb96b8227f000448957b5d8ea13ca78cee329 100644 --- a/src/main/java/net/minecraft/world/level/block/ChestBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ChestBlock.java @@ -341,6 +341,7 @@ public class ChestBlock extends AbstractChestBlock implements } public static boolean isBlockedChestByBlock(BlockGetter world, BlockPos pos) { + if (world instanceof Level && ((Level) world).purpurConfig.chestOpenWithBlockOnTop) return false; // Purpur BlockPos blockposition1 = pos.above(); return world.getBlockState(blockposition1).isRedstoneConductor(world, blockposition1); diff --git a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java index db837b250fc35af5b528bf973b3b07f63e79bc46..ed3a310d56a0cca8c4c11c3768ac2056cd11edea 100644 --- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java @@ -241,18 +241,27 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { int i = (Integer) state.getValue(ComposterBlock.LEVEL); if (i < 8 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) { - if (i < 7 && !world.isClientSide) { - BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, stack); - // Paper start - handle cancelled events - if (iblockdata1 == null) { - return InteractionResult.PASS; - } - // Paper end - - world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); - player.awardStat(Stats.ITEM_USED.get(stack.getItem())); - stack.consume(1, player); + // Purpur start - sneak to bulk process composter + BlockState newState = process(i, player, state, world, pos, stack); + if (newState == null) { + return InteractionResult.PASS; } + if (world.purpurConfig.composterBulkProcess && player.isShiftKeyDown() && newState != state) { + BlockState oldState; + int oldCount, newCount, oldLevel, newLevel; + do { + oldState = newState; + oldCount = stack.getCount(); + oldLevel = oldState.getValue(ComposterBlock.LEVEL); + newState = process(oldLevel, player, oldState, world, pos, stack); + if (newState == null) { + return InteractionResult.PASS; + } + newCount = stack.getCount(); + newLevel = newState.getValue(ComposterBlock.LEVEL); + } while (newCount > 0 && (newCount != oldCount || newLevel != oldLevel || newState != oldState)); + } + // Purpur end return InteractionResult.SUCCESS; } else { @@ -260,6 +269,25 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { } } + // Purpur start - sneak to bulk process composter + private static @Nullable BlockState process(int level, Player player, BlockState state, Level world, BlockPos pos, ItemStack stack) { + if (level < 7 && !world.isClientSide) { + BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, stack); + // Paper start - handle cancelled events + if (iblockdata1 == null) { + return null; + } + // Paper end + + world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); + player.awardStat(Stats.ITEM_USED.get(stack.getItem())); + stack.consume(1, player); + return iblockdata1; + } + return state; + } + // Purpur end + @Override protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { int i = (Integer) state.getValue(ComposterBlock.LEVEL); diff --git a/src/main/java/net/minecraft/world/level/block/CoralBlock.java b/src/main/java/net/minecraft/world/level/block/CoralBlock.java index a59b23f4062fa896836dec72cbd5097411774ad1..c526ea13a726624adaa654f09ff84c899c13ab98 100644 --- a/src/main/java/net/minecraft/world/level/block/CoralBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CoralBlock.java @@ -60,6 +60,7 @@ public class CoralBlock extends Block { } protected boolean scanForWater(BlockGetter world, BlockPos pos) { + if (!((net.minecraft.world.level.LevelAccessor) world).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur 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 1ada5ed825501666addacf527a513ab7bd4a3a58..33c27909290ff3ab483226cf65b1a1bc2e983cbc 100644 --- a/src/main/java/net/minecraft/world/level/block/CropBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java @@ -180,7 +180,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent if (world instanceof ServerLevel worldserver) { - if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit + if (entity instanceof Ravager && world.purpurConfig.ravagerGriefableBlocks.contains(world.getBlockState(pos).getBlock()) && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !world.purpurConfig.ravagerBypassMobGriefing == !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Purpur worldserver.destroyBlock(pos, true, entity); } } @@ -216,4 +216,15 @@ public class CropBlock extends BushBlock implements BonemealableBlock { protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(CropBlock.AGE); } + + // Purpur start + @Override + public void playerDestroy(Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops, boolean dropExp) { + if (world.purpurConfig.hoeReplantsCrops && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) { + super.playerDestroyAndReplant(world, player, pos, state, blockEntity, itemInHand, getBaseSeedId()); + } else { + super.playerDestroy(world, player, pos, state, blockEntity, itemInHand, includeDrops, dropExp); + } + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/DoorBlock.java b/src/main/java/net/minecraft/world/level/block/DoorBlock.java index 077b99caf0ec0ee098786d23194d88e1dc4481ce..daf865c20cc193a12db0d98e3c0472eefdf635c2 100644 --- a/src/main/java/net/minecraft/world/level/block/DoorBlock.java +++ b/src/main/java/net/minecraft/world/level/block/DoorBlock.java @@ -200,6 +200,7 @@ public class DoorBlock extends Block { protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) { if (!this.type.canOpenByHand()) { return InteractionResult.PASS; + } else if (requiresRedstone(world, state, pos)) { return InteractionResult.CONSUME; // Purpur } else { state = (BlockState) state.cycle(DoorBlock.OPEN); world.setBlock(pos, state, 10); @@ -301,4 +302,18 @@ public class DoorBlock extends Block { flag = false; return flag; } + + // Purpur start + 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 30d15686b1a81de7ac28feb0c6188eb007c6f2fd..b6799db00e157892dd4339a01d2ca36092c8e491 100644 --- a/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java +++ b/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java @@ -48,8 +48,8 @@ public class DragonEggBlock extends FallingBlock { } private void teleport(BlockState state, Level world, BlockPos pos) { + if (!world.purpurConfig.dragonEggTeleport) return; // Purpur WorldBorder worldborder = world.getWorldBorder(); - for (int i = 0; i < 1000; ++i) { BlockPos blockposition1 = pos.offset(world.random.nextInt(16) - world.random.nextInt(16), world.random.nextInt(8) - world.random.nextInt(8), world.random.nextInt(16) - world.random.nextInt(16)); diff --git a/src/main/java/net/minecraft/world/level/block/EnchantingTableBlock.java b/src/main/java/net/minecraft/world/level/block/EnchantingTableBlock.java index a7fb500d950687743d1fc0b3ad3e6d10dcc6e31a..ce6a9e15ae0114623e79b5d8c244c2c490a3f74e 100644 --- a/src/main/java/net/minecraft/world/level/block/EnchantingTableBlock.java +++ b/src/main/java/net/minecraft/world/level/block/EnchantingTableBlock.java @@ -123,4 +123,18 @@ public class EnchantingTableBlock extends BaseEntityBlock { protected boolean isPathfindable(BlockState state, PathComputationType type) { return false; } + + // Purpur start + @Override + public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean moved) { + BlockEntity blockEntity = level.getBlockEntity(pos); + + if (level.purpurConfig.enchantmentTableLapisPersists && blockEntity instanceof EnchantingTableBlockEntity enchantmentTable) { + net.minecraft.world.Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.LAPIS_LAZULI, enchantmentTable.getLapis())); + level.updateNeighbourForOutputSignal(pos, this); + } + + super.onRemove(state, level, pos, newState, moved); + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java index a7a21f071161fb7e73a046717d2462f871ab653c..abb75f9389167a1f51a2c50831664d50181749de 100644 --- a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java +++ b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java @@ -104,6 +104,13 @@ public class EndGatewayBlock extends BaseEntityBlock implements Portal { TheEndGatewayBlockEntity tileentityendgateway = (TheEndGatewayBlockEntity) tileentity; if (!tileentityendgateway.isCoolingDown()) { + // Purpur start + if (world.purpurConfig.imposeTeleportRestrictionsOnGateways && (entity.isVehicle() || entity.isPassenger())) { + if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, PlayerTeleportEvent.TeleportCause.END_GATEWAY).callEvent()) { + return; + } + } + // Purpur end entity.setAsInsidePortal(this, pos); TheEndGatewayBlockEntity.triggerCooldown(world, pos, state, tileentityendgateway); } diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java index 4aa14f975e1ceedf3d4a427e0daefb58b12fcafe..2dffc3990d9ae3d595d923239885e3a7d8ec04f3 100644 --- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java @@ -70,6 +70,13 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal { protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent if (entity.canUsePortal(false)) { + // Purpur start + if (world.purpurConfig.imposeTeleportRestrictionsOnEndPortals && (entity.isVehicle() || entity.isPassenger())) { + if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, PlayerTeleportEvent.TeleportCause.END_PORTAL).callEvent()) { + return; + } + } + // Purpur end // CraftBukkit start - Entity in portal EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.ENDER); // Paper - add portal type world.getCraftServer().getPluginManager().callEvent(event); diff --git a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java index 2a207fb2e1c26b562de42240e11c856bd2a23458..6ad4aa371607ab92616626285a7e71757c76a3db 100644 --- a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java +++ b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java @@ -89,7 +89,7 @@ public class EnderChestBlock extends AbstractChestBlock i // Paper start - Fix InventoryOpenEvent cancellation - moved up; playerEnderChestContainer.setActiveChest(enderChestBlockEntity); // Needs to happen before ChestMenu.threeRows as it is required for opening animations if (world instanceof ServerLevel serverLevel && player.openMenu( - new SimpleMenuProvider((i, inventory, playerx) -> ChestMenu.threeRows(i, inventory, playerEnderChestContainer), CONTAINER_TITLE) + new SimpleMenuProvider((i, inventory, playerx) -> org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? getEnderChestSixRows(i, inventory, player, playerEnderChestContainer) : ChestMenu.threeRows(i, inventory, playerEnderChestContainer), CONTAINER_TITLE) // Purpur ).isPresent()) { // Paper end - Fix InventoryOpenEvent cancellation - moved up; // Paper - Fix InventoryOpenEvent cancellation - moved up; @@ -104,6 +104,35 @@ public class EnderChestBlock extends AbstractChestBlock i } } + // Purpur start + private ChestMenu getEnderChestSixRows(int syncId, net.minecraft.world.entity.player.Inventory inventory, Player player, PlayerEnderChestContainer playerEnderChestContainer) { + if (org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) { + org.bukkit.craftbukkit.entity.CraftHumanEntity bukkitPlayer = player.getBukkitEntity(); + if (bukkitPlayer.hasPermission("purpur.enderchest.rows.six")) { + player.sixRowEnderchestSlotCount = 54; + return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer); + } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.five")) { + player.sixRowEnderchestSlotCount = 45; + return ChestMenu.fiveRows(syncId, inventory, playerEnderChestContainer); + } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.four")) { + player.sixRowEnderchestSlotCount = 36; + return ChestMenu.fourRows(syncId, inventory, playerEnderChestContainer); + } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.three")) { + player.sixRowEnderchestSlotCount = 27; + return ChestMenu.threeRows(syncId, inventory, playerEnderChestContainer); + } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.two")) { + player.sixRowEnderchestSlotCount = 18; + return ChestMenu.twoRows(syncId, inventory, playerEnderChestContainer); + } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.one")) { + player.sixRowEnderchestSlotCount = 9; + return ChestMenu.oneRow(syncId, inventory, playerEnderChestContainer); + } + } + player.sixRowEnderchestSlotCount = -1; + return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer); + } + // Purpur end + @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return new EnderChestBlockEntity(pos, state); diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java index c3dba0c2c94f3804338f86621dc42405e380a6b3..eaac00e2534aca4eab92c7b9f9248e04b35b47df 100644 --- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java +++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java @@ -112,7 +112,7 @@ public class FarmBlock extends Block { public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { super.fallOn(world, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage. if (world instanceof ServerLevel worldserver) { - if (world.random.nextFloat() < fallDistance - 0.5F && entity instanceof LivingEntity && (entity instanceof Player || worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { + if ((worldserver.purpurConfig.farmlandTrampleHeight >= 0D ? fallDistance >= worldserver.purpurConfig.farmlandTrampleHeight : world.random.nextFloat() < fallDistance - 0.5F) && entity instanceof LivingEntity && (entity instanceof Player || worldserver.purpurConfig.farmlandBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { // Purpur // CraftBukkit start - Interact soil org.bukkit.event.Cancellable cancellable; if (entity instanceof Player) { @@ -126,6 +126,22 @@ public class FarmBlock extends Block { return; } + // Purpur start + if (world.purpurConfig.farmlandTramplingDisabled) return; + if (world.purpurConfig.farmlandTramplingOnlyPlayers && !(entity instanceof Player)) return; + if (world.purpurConfig.farmlandAlpha) { + Block block = world.getBlockState(pos.below()).getBlock(); + if (block instanceof FenceBlock || block instanceof WallBlock) { + return; + } + } + if (world.purpurConfig.farmlandTramplingFeatherFalling) { + Iterator armor = ((LivingEntity) entity).getArmorSlots().iterator(); + if (armor.hasNext() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FEATHER_FALLING, armor.next()) >= (int) entity.fallDistance) { + return; + } + } + // Purpur end if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) { return; } @@ -174,7 +190,7 @@ public class FarmBlock extends Block { } } - return false; + return ((ServerLevel) world).purpurConfig.farmlandGetsMoistFromBelow && world.getFluidState(pos.relative(Direction.DOWN)).is(FluidTags.WATER); // Purpur; // Paper end - Perf: remove abstract block iteration } diff --git a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java index 9b424d7661fedf8ee1eb9f3167c62e563f04d4d1..2af311847a085a8073e9bcb26c762d1bbe1eae2c 100644 --- a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +++ b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java @@ -34,12 +34,12 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements @Override public BlockState getStateForPlacement(RandomSource random) { - return (BlockState) this.defaultBlockState().setValue(GrowingPlantHeadBlock.AGE, random.nextInt(25)); + return (BlockState) this.defaultBlockState().setValue(GrowingPlantHeadBlock.AGE, random.nextInt(getMaxGrowthAge())); // Purpur } @Override protected boolean isRandomlyTicking(BlockState state) { - return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25; + return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) < getMaxGrowthAge(); // Purpur } @Override @@ -55,7 +55,7 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements } else { modifier = world.spigotConfig.caveVinesModifier; } - if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution + if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < getMaxGrowthAge() && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution // Purpur // Spigot end BlockPos blockposition1 = pos.relative(this.growthDirection); @@ -77,11 +77,11 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements } public BlockState getMaxAgeState(BlockState state) { - return (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, 25); + return (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, getMaxGrowthAge()); // Purpur } public boolean isMaxAge(BlockState state) { - return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) == 25; + return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) >= getMaxGrowthAge(); // Purpur } protected BlockState updateBodyAfterConvertedFromHead(BlockState from, BlockState to) { @@ -123,13 +123,13 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements @Override public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { BlockPos blockposition1 = pos.relative(this.growthDirection); - int i = Math.min((Integer) state.getValue(GrowingPlantHeadBlock.AGE) + 1, 25); + int i = Math.min((Integer) state.getValue(GrowingPlantHeadBlock.AGE) + 1, getMaxGrowthAge()); // Purpur int j = this.getBlocksToGrowWhenBonemealed(random); for (int k = 0; k < j && this.canGrowInto(world.getBlockState(blockposition1)); ++k) { world.setBlockAndUpdate(blockposition1, (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, i)); blockposition1 = blockposition1.relative(this.growthDirection); - i = Math.min(i + 1, 25); + i = Math.min(i + 1, getMaxGrowthAge()); // Purpur } } @@ -142,4 +142,6 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements protected GrowingPlantHeadBlock getHeadBlock() { return this; } + + public abstract int getMaxGrowthAge(); // Purpur } diff --git a/src/main/java/net/minecraft/world/level/block/HayBlock.java b/src/main/java/net/minecraft/world/level/block/HayBlock.java index ef364aa171a48482a45bc18cfe730ec20c3f7be6..74971d90506aa253d5ee821b5390fb2551a3a393 100644 --- a/src/main/java/net/minecraft/world/level/block/HayBlock.java +++ b/src/main/java/net/minecraft/world/level/block/HayBlock.java @@ -23,6 +23,6 @@ public class HayBlock extends RotatedPillarBlock { @Override public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { - entity.causeFallDamage(fallDistance, 0.2F, world.damageSources().fall()); + super.fallOn(world, state, pos, entity, fallDistance); // Purpur } } diff --git a/src/main/java/net/minecraft/world/level/block/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java index a94762e65853ccad38cf90b0049ca256106c0c9f..38633e168a9b36e37feea00964d53e657926639e 100644 --- a/src/main/java/net/minecraft/world/level/block/IceBlock.java +++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java @@ -42,7 +42,7 @@ public class IceBlock extends HalfTransparentBlock { public void afterDestroy(Level world, BlockPos pos, ItemStack tool) { // Paper end - Improve Block#breakNaturally API if (!EnchantmentHelper.hasTag(tool, EnchantmentTags.PREVENTS_ICE_MELTING)) { - if (world.dimensionType().ultraWarm()) { + if (world.isNether() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur world.removeBlock(pos, false); return; } @@ -70,7 +70,7 @@ public class IceBlock extends HalfTransparentBlock { return; } // CraftBukkit end - if (world.dimensionType().ultraWarm()) { + if (world.isNether() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur world.removeBlock(pos, false); } else { world.setBlockAndUpdate(pos, IceBlock.meltsInto()); diff --git a/src/main/java/net/minecraft/world/level/block/KelpBlock.java b/src/main/java/net/minecraft/world/level/block/KelpBlock.java index 784b19bc78c8ad9476b6dac37b6778a409a7c675..d49dd8b20d3785cc9482ed2a34fbd7aed4c9e537 100644 --- a/src/main/java/net/minecraft/world/level/block/KelpBlock.java +++ b/src/main/java/net/minecraft/world/level/block/KelpBlock.java @@ -72,4 +72,11 @@ public class KelpBlock extends GrowingPlantHeadBlock implements LiquidBlockConta protected FluidState getFluidState(BlockState state) { return Fluids.WATER.getSource(false); } + + // Purpur start + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.kelpMaxGrowthAge; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java index a2d023ff011f71f80032f02430a53d6a08a23623..399441dd8388dcdec08768665d3cfa189aca0a95 100644 --- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java +++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java @@ -140,7 +140,7 @@ public class LiquidBlock extends Block implements BucketPickup { @Override protected void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { - if (this.shouldSpreadLiquid(world, pos, state)) { + if (world.purpurConfig.tickFluids && this.shouldSpreadLiquid(world, pos, state)) { // Purpur world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava } @@ -168,7 +168,7 @@ public class LiquidBlock extends Block implements BucketPickup { @Override protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { - if (state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { + if (world.getWorldBorder().world.purpurConfig.tickFluids && state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { // Purpur tickView.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world)); } @@ -177,7 +177,7 @@ public class LiquidBlock extends Block implements BucketPickup { @Override protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) { - if (this.shouldSpreadLiquid(world, pos, state)) { + if (world.purpurConfig.tickFluids && this.shouldSpreadLiquid(world, pos, state)) { // Purpur world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - Configurable speed for water flowing over lava } diff --git a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java index 7ffdcf18bf4bd8b5325c76945b2d80ca3fe52958..dfa931316fde0b2e80068a0edd1427ffd096b15b 100644 --- a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java +++ b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java @@ -29,7 +29,7 @@ public class MagmaBlock extends Block { @Override public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) { - if (!entity.isSteppingCarefully() && entity instanceof LivingEntity) { + if ((!entity.isSteppingCarefully() || world.purpurConfig.magmaBlockDamageWhenSneaking) && entity instanceof LivingEntity) { // Purpur entity.hurt(world.damageSources().hotFloor().directBlock(world, pos), 1.0F); // CraftBukkit } diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java index 2b31bf586c1c0bd393d2aa8d0b6635dd9f22f21c..02bcba52e28f757b59e2f384d5744834962870f0 100644 --- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java @@ -78,7 +78,7 @@ public class NetherPortalBlock extends Block implements Portal { @Override protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { - if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(2000) < world.getDifficulty().getId()) { // Spigot + if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(world.purpurConfig.piglinPortalSpawnModifier) < world.getDifficulty().getId()) { // Spigot // Purpur while (world.getBlockState(pos).is((Block) this)) { pos = pos.below(); } @@ -117,6 +117,13 @@ public class NetherPortalBlock extends Block implements Portal { protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent if (entity.canUsePortal(false)) { + // Purpur start + if (world.purpurConfig.imposeTeleportRestrictionsOnNetherPortals && (entity.isVehicle() || entity.isPassenger())) { + if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.NETHER_PORTAL).callEvent()) { + return; + } + } + // Purpur end // CraftBukkit start - Entity in portal EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()), org.bukkit.PortalType.NETHER); // Paper - add portal type world.getCraftServer().getPluginManager().callEvent(event); @@ -130,7 +137,7 @@ public class NetherPortalBlock extends Block implements Portal { @Override public int getPortalTransitionTime(ServerLevel world, Entity entity) { if (entity instanceof Player entityhuman) { - return Math.max(0, world.getGameRules().getInt(entityhuman.getAbilities().invulnerable ? GameRules.RULE_PLAYERS_NETHER_PORTAL_CREATIVE_DELAY : GameRules.RULE_PLAYERS_NETHER_PORTAL_DEFAULT_DELAY)); + return Math.max(0, entityhuman.canPortalInstant ? 1 : world.getGameRules().getInt(entityhuman.getAbilities().invulnerable ? GameRules.RULE_PLAYERS_NETHER_PORTAL_CREATIVE_DELAY : GameRules.RULE_PLAYERS_NETHER_PORTAL_DEFAULT_DELAY)); // Purpur } else { return 0; } diff --git a/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java b/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java index acbd60a2f162fe0e254e36d0e8e7face3fc8a7b3..b8355ea1de26c4b6905f477fb4e110f1762447b4 100644 --- a/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java +++ b/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java @@ -16,7 +16,7 @@ import net.minecraft.world.level.block.state.properties.IntegerProperty; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; -public class NetherWartBlock extends BushBlock { +public class NetherWartBlock extends BushBlock implements BonemealableBlock { // Purpur public static final MapCodec CODEC = simpleCodec(NetherWartBlock::new); public static final int MAX_AGE = 3; @@ -68,4 +68,32 @@ public class NetherWartBlock extends BushBlock { protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(NetherWartBlock.AGE); } + + // Purpur start + @Override + public void playerDestroy(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops, boolean dropExp) { + if (world.purpurConfig.hoeReplantsNetherWarts && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) { + super.playerDestroyAndReplant(world, player, pos, state, blockEntity, itemInHand, Items.NETHER_WART); + } else { + super.playerDestroy(world, player, pos, state, blockEntity, itemInHand, includeDrops, dropExp); + } + } + + @Override + public boolean isValidBonemealTarget(final net.minecraft.world.level.LevelReader world, final BlockPos pos, final BlockState state) { + return ((net.minecraft.world.level.Level) world).purpurConfig.netherWartAffectedByBonemeal && state.getValue(NetherWartBlock.AGE) < 3; + } + + @Override + public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + int i = Math.min(3, state.getValue(NetherWartBlock.AGE) + 1); + state = state.setValue(NetherWartBlock.AGE, i); + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state, 2); // CraftBukkit + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java index 6582db84c5307257f16c321453491cf24e40c9c7..f9015d4e478efeec8a796b7a897638f76064db20 100644 --- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java +++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java @@ -97,7 +97,7 @@ public class NoteBlock extends Block { } private void playNote(@Nullable Entity entity, BlockState state, Level world, BlockPos pos) { - if (((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || world.getBlockState(pos.above()).isAir()) { + if (world.purpurConfig.noteBlockIgnoreAbove || ((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || world.getBlockState(pos.above()).isAir()) { // Purpur // CraftBukkit start // org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE)); // if (event.isCancelled()) { diff --git a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java index 93ed9406c34804831b86d006dbd6087db9948f08..26cb9990b91991e0a2eadc2dcbbf229e2e88fb2d 100644 --- a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java @@ -75,6 +75,7 @@ public class ObserverBlock extends DirectionalBlock { @Override protected BlockState updateShape(BlockState state, LevelReader world, ScheduledTickAccess tickView, BlockPos pos, Direction direction, BlockPos neighborPos, BlockState neighborState, RandomSource random) { if (state.getValue(ObserverBlock.FACING) == direction && !(Boolean) state.getValue(ObserverBlock.POWERED)) { + if (!world.getWorldBorder().world.purpurConfig.disableObserverClocks || !(neighborState.getBlock() instanceof ObserverBlock) || neighborState.getValue(ObserverBlock.FACING).getOpposite() != direction) // Purpur this.startSignal(world, tickView, pos); } diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java index 53cea36ec931de89e0060613acf87beb51dc16ec..fd5489993dca0f940da69e9163f78e5c2e6ee063 100644 --- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java @@ -194,7 +194,7 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate @VisibleForTesting public static void maybeTransferFluid(BlockState state, ServerLevel world, BlockPos pos, float dripChance) { - if (dripChance <= 0.17578125F || dripChance <= 0.05859375F) { + if (dripChance <= world.purpurConfig.cauldronDripstoneWaterFillChance || dripChance <= world.purpurConfig.cauldronDripstoneLavaFillChance) { // Purpur if (PointedDripstoneBlock.isStalactiteStartPos(state, world, pos)) { Optional optional = PointedDripstoneBlock.getFluidAboveStalactite(world, pos, state); @@ -203,13 +203,13 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate float f1; if (fluidtype == Fluids.WATER) { - f1 = 0.17578125F; + f1 = world.purpurConfig.cauldronDripstoneWaterFillChance; // Purpur } else { if (fluidtype != Fluids.LAVA) { return; } - f1 = 0.05859375F; + f1 = world.purpurConfig.cauldronDripstoneLavaFillChance; // Purpur } if (dripChance < f1) { diff --git a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java index 53f1a7ed6b4bd6e2d8460531226aabf249994c02..3760c3c9ab45d7152661edd5a48893e1b583fb95 100644 --- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java +++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java @@ -76,7 +76,7 @@ public class PowderSnowBlock extends Block implements BucketPickup { if (world instanceof ServerLevel worldserver) { // CraftBukkit start if (entity.isOnFire() && entity.mayInteract(worldserver, pos)) { - if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) { + if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !((worldserver.purpurConfig.powderSnowBypassMobGriefing ^ worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) || entity instanceof Player))) { // Purpur return; } // CraftBukkit end diff --git a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java index b763361a8f0f1b46093d5dd9afe8dba0cadf9c78..bd14c08defe8afc5ceca59d16a5b1dbad178f594 100644 --- a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java +++ b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java @@ -30,7 +30,7 @@ public class PoweredRailBlock extends BaseRailBlock { } protected boolean findPoweredRailSignal(Level world, BlockPos pos, BlockState state, boolean flag, int distance) { - if (distance >= 8) { + if (distance >= world.purpurConfig.railActivationRange) { // Purpur return false; } else { int j = pos.getX(); diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java index 9117c035d5a6ff114b028fad3380ceb1fc2b9691..2c5e394156dbf76107adb4913a094dfd4a598dd7 100644 --- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java +++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java @@ -149,7 +149,7 @@ public class RespawnAnchorBlock extends Block { }; Vec3 vec3d = explodedPos.getCenter(); - world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // CraftBukkit - add state + if (world.purpurConfig.respawnAnchorExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, blockState), explosiondamagecalculator, vec3d, (float) world.purpurConfig.respawnAnchorExplosionPower, world.purpurConfig.respawnAnchorExplosionFire, world.purpurConfig.respawnAnchorExplosionEffect); // CraftBukkit - add state // Purpur } public static boolean canSetSpawn(Level world) { diff --git a/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java b/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java index 0e05bfef55dcacb50766bba8328ffeb8a5394c31..e00fcea07e74de647c26ff9eb32bc682738c15b7 100644 --- a/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java @@ -135,7 +135,7 @@ public class SculkShriekerBlock extends BaseEntityBlock implements SimpleWaterlo @Nullable @Override public BlockState getStateForPlacement(BlockPlaceContext ctx) { - return (BlockState) this.defaultBlockState().setValue(SculkShriekerBlock.WATERLOGGED, ctx.getLevel().getFluidState(ctx.getClickedPos()).getType() == Fluids.WATER); + return (BlockState) this.defaultBlockState().setValue(SculkShriekerBlock.WATERLOGGED, ctx.getLevel().getFluidState(ctx.getClickedPos()).getType() == Fluids.WATER).setValue(SculkShriekerBlock.CAN_SUMMON, ctx.getLevel().purpurConfig.sculkShriekerCanSummonDefault); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/level/block/SlabBlock.java b/src/main/java/net/minecraft/world/level/block/SlabBlock.java index 9274fd639c22e305dda567b303f9b01068adb52c..4433e432ea0ee8d11045b87e68dac3ed43e8cf82 100644 --- a/src/main/java/net/minecraft/world/level/block/SlabBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SlabBlock.java @@ -150,4 +150,25 @@ public class SlabBlock extends Block implements SimpleWaterloggedBlock { return false; } } + + // Purpur start + public boolean halfBreak(BlockState state, BlockPos pos, net.minecraft.server.level.ServerPlayer player) { + if (state.getValue(SlabBlock.TYPE) != SlabType.DOUBLE) { + return false; + } + net.minecraft.world.phys.HitResult result = player.getRayTrace(16, net.minecraft.world.level.ClipContext.Fluid.NONE); + if (result.getType() != net.minecraft.world.phys.HitResult.Type.BLOCK) { + return false; + } + double hitY = result.getLocation().y(); + int blockY = org.bukkit.util.NumberConversions.floor(hitY); + player.level().setBlock(pos, state.setValue(SlabBlock.TYPE, (hitY - blockY > 0.5 || blockY - pos.getY() == 1) ? SlabType.BOTTOM : SlabType.TOP), 3); + if (!player.getAbilities().instabuild) { + net.minecraft.world.entity.item.ItemEntity item = new net.minecraft.world.entity.item.ItemEntity(player.level(), pos.getX(), pos.getY(), pos.getZ(), new ItemStack(asItem())); + item.setDefaultPickUpDelay(); + player.level().addFreshEntity(item); + } + return true; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java index 9908a0b5b1fec5f9de518a733f7abbbff7e1a9f9..0ad444cf7f798f63e9140a42c5d5d8cab0fcf14f 100644 --- a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java @@ -88,6 +88,12 @@ public class SnowLayerBlock extends Block { protected boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { BlockState iblockdata1 = world.getBlockState(pos.below()); + // Purpur start + if (iblockdata1.is(Blocks.BLUE_ICE) && !world.getWorldBorder().world.purpurConfig.snowOnBlueIce) { + return false; + } + // Purpur end + return iblockdata1.is(BlockTags.SNOW_LAYER_CANNOT_SURVIVE_ON) ? false : (iblockdata1.is(BlockTags.SNOW_LAYER_CAN_SURVIVE_ON) ? true : Block.isFaceFull(iblockdata1.getCollisionShape(world, pos.below()), Direction.UP) || iblockdata1.is((Block) this) && (Integer) iblockdata1.getValue(SnowLayerBlock.LAYERS) == 8); } diff --git a/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java b/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java index 4f190a40b8474aa06a92c8afcc06d0044120ff7b..80ee7a6f010cc838625674007a3ea908f2f9dadd 100644 --- a/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java @@ -42,6 +42,57 @@ public class SpawnerBlock extends BaseEntityBlock { return createTickerHelper(type, BlockEntityType.MOB_SPAWNER, world.isClientSide ? SpawnerBlockEntity::clientTick : SpawnerBlockEntity::serverTick); } + // Purpur start + @Override + public void playerDestroy(Level level, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack stack, boolean includeDrops, boolean dropExp) { + if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.drop.spawners") && isSilkTouch(level, stack)) { + ItemStack item = new ItemStack(Blocks.SPAWNER.asItem()); + + net.minecraft.world.level.SpawnData nextSpawnData = blockEntity instanceof SpawnerBlockEntity spawnerBlock ? spawnerBlock.getSpawner().nextSpawnData : null; + java.util.Optional> type = java.util.Optional.empty(); + if (nextSpawnData != null) { + type = net.minecraft.world.entity.EntityType.by(nextSpawnData.getEntityToSpawn()); + net.minecraft.world.level.SpawnData.CODEC.encodeStart(net.minecraft.nbt.NbtOps.INSTANCE, nextSpawnData).result().ifPresent(tag -> item.set(net.minecraft.core.component.DataComponents.CUSTOM_DATA, net.minecraft.world.item.component.CustomData.EMPTY.update(compoundTag -> compoundTag.put("Purpur.SpawnData", tag)))); + } + + if (type.isPresent()) { + final net.kyori.adventure.text.Component mobName = io.papermc.paper.adventure.PaperAdventure.asAdventure(type.get().getDescription()); + + String name = level.purpurConfig.silkTouchSpawnerName; + if (name != null && !name.isEmpty() && !name.equals("Monster Spawner")) { + net.kyori.adventure.text.Component displayName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName)); + if (name.startsWith("")) { + displayName = displayName.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); + } + item.set(net.minecraft.core.component.DataComponents.CUSTOM_NAME, io.papermc.paper.adventure.PaperAdventure.asVanilla(displayName)); + } + + List lore = level.purpurConfig.silkTouchSpawnerLore; + if (lore != null && !lore.isEmpty()) { + + List loreComponentList = new java.util.ArrayList<>(); + for (String line : lore) { + net.kyori.adventure.text.Component lineComponent = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(line, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName)); + if (line.startsWith("")) { + lineComponent = lineComponent.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); + } + loreComponentList.add(io.papermc.paper.adventure.PaperAdventure.asVanilla(lineComponent)); + } + + item.set(net.minecraft.core.component.DataComponents.LORE, new net.minecraft.world.item.component.ItemLore(loreComponentList, loreComponentList)); + } + item.set(net.minecraft.core.component.DataComponents.HIDE_ADDITIONAL_TOOLTIP, net.minecraft.util.Unit.INSTANCE); + } + popResource(level, pos, item); + } + super.playerDestroy(level, player, pos, state, blockEntity, stack, includeDrops, dropExp); + } + + private boolean isSilkTouch(Level level, ItemStack stack) { + return stack != null && level.purpurConfig.silkTouchTools.contains(stack.getItem()) && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.SILK_TOUCH, stack) >= level.purpurConfig.minimumSilkTouchSpawnerRequire; + } + // Purpur end + @Override protected void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { super.spawnAfterBreak(state, world, pos, tool, dropExperience); @@ -50,6 +101,7 @@ public class SpawnerBlock extends BaseEntityBlock { @Override public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) { + if (worldserver.purpurConfig.silkTouchEnabled && isSilkTouch(worldserver, itemstack)) return 0; // Purpur if (flag) { int i = 15 + worldserver.random.nextInt(15) + worldserver.random.nextInt(15); diff --git a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java index 59cf905b1b5686f6f4f2bad94730ffa69d3a2834..4c5bc71fef307d13b640e534ace0b4411f6e97e2 100644 --- a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java @@ -61,7 +61,7 @@ public class SpongeBlock extends Block { private boolean removeWaterBreadthFirstSearch(Level world, BlockPos pos) { BlockStateListPopulator blockList = new BlockStateListPopulator(world); // CraftBukkit - Use BlockStateListPopulator - BlockPos.breadthFirstTraversal(pos, 6, 65, (blockposition1, consumer) -> { + BlockPos.breadthFirstTraversal(pos, world.purpurConfig.spongeAbsorptionRadius, world.purpurConfig.spongeAbsorptionArea, (blockposition1, consumer) -> { // Purpur Direction[] aenumdirection = SpongeBlock.ALL_DIRECTIONS; int i = aenumdirection.length; @@ -80,7 +80,7 @@ public class SpongeBlock extends Block { FluidState fluid = blockList.getFluidState(blockposition1); // CraftBukkit end - if (!fluid.is(FluidTags.WATER)) { + if (!fluid.is(FluidTags.WATER) && (!world.purpurConfig.spongeAbsorbsLava || !fluid.is(FluidTags.LAVA)) && (!world.purpurConfig.spongeAbsorbsWaterFromMud || !iblockdata.is(Blocks.MUD))) { // Purpur return false; } else { Block block = iblockdata.getBlock(); @@ -95,6 +95,10 @@ public class SpongeBlock extends Block { if (iblockdata.getBlock() instanceof LiquidBlock) { blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit + // Purpur start + } else if (iblockdata.is(Blocks.MUD)) { + blockList.setBlock(blockposition1, Blocks.CLAY.defaultBlockState(), 3); + // Purpur end } else { if (!iblockdata.is(Blocks.KELP) && !iblockdata.is(Blocks.KELP_PLANT) && !iblockdata.is(Blocks.SEAGRASS) && !iblockdata.is(Blocks.TALL_SEAGRASS)) { return 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 e61644241f24b42bb4f702d3eef5b590b4d107c8..0bf6503819b02e5ba2c346d9d563a93f2946d89b 100644 --- a/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java +++ b/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java @@ -98,4 +98,14 @@ public class StonecutterBlock extends Block { protected boolean isPathfindable(BlockState state, 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) { + entity.hurt(entity.damageSources().stonecutter().directBlock(level, pos), level.purpurConfig.stonecutterDamage); + } + 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 547ea09ed84595286c97c128b3b96f6d387ae25f..d0f8a13f27132257ece6dadf736c2dc6b1e5720e 100644 --- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java @@ -20,7 +20,7 @@ import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; -public class SugarCaneBlock extends Block { +public class SugarCaneBlock extends Block implements BonemealableBlock { // Purpur public static final MapCodec CODEC = simpleCodec(SugarCaneBlock::new); public static final IntegerProperty AGE = BlockStateProperties.AGE_15; @@ -113,4 +113,34 @@ public class SugarCaneBlock extends Block { protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(SugarCaneBlock.AGE); } + + // Purpur start + @Override + public boolean isValidBonemealTarget(final LevelReader world, final BlockPos pos, final BlockState state) { + if (!((net.minecraft.world.level.Level) world).purpurConfig.sugarCanAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false; + + int reedHeight = 0; + while (world.getBlockState(pos.below(reedHeight)).is(this)) { + reedHeight++; + } + + return reedHeight < ((net.minecraft.world.level.Level) world).paperConfig().maxGrowthHeight.reeds; + } + + @Override + public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + int reedHeight = 0; + while (world.getBlockState(pos.below(reedHeight)).is(this)) { + reedHeight++; + } + for (int i = 0; i <= world.paperConfig().maxGrowthHeight.reeds - reedHeight; i++) { + world.setBlockAndUpdate(pos.above(i), state.setValue(SugarCaneBlock.AGE, 0)); + } + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java index 953ddb2ea6fd48e57712e30a6addf23e188e5312..f1dfb23160ff70e0da4dd2af2d83e879527c6651 100644 --- a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java +++ b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java @@ -171,7 +171,7 @@ public class TurtleEggBlock extends Block { private boolean shouldUpdateHatchLevel(Level world) { float f = world.getTimeOfDay(1.0F); - return (double) f < 0.69D && (double) f > 0.65D ? true : world.random.nextInt(500) == 0; + return (double) f < 0.69D && (double) f > 0.65D ? true : world.random.nextInt(world.purpurConfig.turtleEggsRandomTickCrackChance) == 0; } @Override @@ -204,6 +204,31 @@ public class TurtleEggBlock extends Block { } private boolean canDestroyEgg(ServerLevel world, Entity entity) { - return !(entity instanceof Turtle) && !(entity instanceof Bat) ? (!(entity instanceof LivingEntity) ? false : entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) : false; + // Purpur start + 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 = ((LivingEntity) entity).getArmorSlots().iterator(); + return !armor.hasNext() || net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FEATHER_FALLING, armor.next()) < (int) entity.fallDistance; + } + 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 6342bb11a162b9e6d9475c5989b1670d77e8f0fb..f8be92512446d3f0e5f0f21222bbefd04ab2838a 100644 --- a/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java +++ b/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java @@ -34,4 +34,11 @@ public class TwistingVinesBlock extends GrowingPlantHeadBlock { protected boolean canGrowInto(BlockState state) { return NetherVines.isValidGrowthState(state); } + + // Purpur start + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.twistingVinesMaxGrowthAge; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java index 3dec5a082606ee35a8c8d7f746480262d6a189c5..b2f6ccae9576c176263e51a232e17a08d54543b3 100644 --- a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java +++ b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java @@ -34,4 +34,11 @@ public class WeepingVinesBlock extends GrowingPlantHeadBlock { protected boolean canGrowInto(BlockState state) { return NetherVines.isValidGrowthState(state); } + + // Purpur start + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.weepingVinesMaxGrowthAge; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java index 0fbe66cc02bd3d95c0a5dcd55380a1b4a2f17ca6..3a7126883f11ac5a647947eaf060df15536a6cb2 100644 --- a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java +++ b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java @@ -80,6 +80,7 @@ public class WitherSkullBlock extends SkullBlock { entitywither.moveTo((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.55D, (double) blockposition1.getZ() + 0.5D, shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F, 0.0F); entitywither.yBodyRot = shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F; entitywither.makeInvulnerable(); + entitywither.setSummoner(iblockdata.getBlock().placer == null ? null : iblockdata.getBlock().placer.getUUID()); // Purpur // 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 15e0861486a2bda3e2f4049b1b5a299c870acd31..91a158ed90b7ce3eac7277fd962682a0226c08ed 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 @@ -219,6 +219,21 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit } ItemStack itemstack = (ItemStack) blockEntity.items.get(1); + // Purpur start + boolean usedLavaFromUnderneath = false; + if (world.purpurConfig.furnaceUseLavaFromUnderneath && !blockEntity.isLit() && itemstack.isEmpty() && !blockEntity.items.get(0).isEmpty() && world.getGameTime() % 20 == 0) { + BlockPos below = blockEntity.getBlockPos().below(); + BlockState belowState = world.getBlockStateIfLoaded(below); + if (belowState != null && belowState.is(Blocks.LAVA)) { + net.minecraft.world.level.material.FluidState fluidState = belowState.getFluidState(); + if (fluidState != null && fluidState.isSource()) { + world.setBlock(below, Blocks.AIR.defaultBlockState(), 3); + itemstack = Items.LAVA_BUCKET.getDefaultInstance(); + usedLavaFromUnderneath = true; + } + } + } + // Purpur end ItemStack itemstack1 = (ItemStack) blockEntity.items.get(0); boolean flag2 = !itemstack1.isEmpty(); boolean flag3 = !itemstack.isEmpty(); @@ -308,6 +323,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit setChanged(world, pos, state); } + if (usedLavaFromUnderneath) blockEntity.items.set(1, ItemStack.EMPTY); // Purpur } private static boolean canBurn(RegistryAccess dynamicRegistryManager, @Nullable RecipeHolder recipe, SingleRecipeInput input, NonNullList inventory, int maxCount) { diff --git a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java index 618552afbdacc919c33b30a6bf4834fb71ab3d5b..7a059d20abdcc0073a314311d78f63ae4bd0365e 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java @@ -68,7 +68,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { public BarrelBlockEntity(BlockPos pos, BlockState state) { super(BlockEntityType.BARREL, pos, state); - this.items = NonNullList.withSize(27, ItemStack.EMPTY); + // Purpur start + this.items = NonNullList.withSize(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { + case 6 -> 54; + case 5 -> 45; + case 4 -> 36; + case 2 -> 18; + case 1 -> 9; + default -> 27; + }, ItemStack.EMPTY); + // Purpur end this.openersCounter = new ContainerOpenersCounter() { @Override protected void onOpen(Level world, BlockPos pos, BlockState state) { @@ -119,7 +128,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { @Override public int getContainerSize() { - return 27; + // Purpur start + return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { + case 6 -> 54; + case 5 -> 45; + case 4 -> 36; + case 2 -> 18; + case 1 -> 9; + default -> 27; + }; + // Purpur end } @Override @@ -139,7 +157,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { @Override protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { - return ChestMenu.threeRows(syncId, playerInventory, this); + // Purpur start + return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { + case 6 -> ChestMenu.sixRows(syncId, playerInventory, this); + case 5 -> ChestMenu.fiveRows(syncId, playerInventory, this); + case 4 -> ChestMenu.fourRows(syncId, playerInventory, this); + case 2 -> ChestMenu.twoRows(syncId, playerInventory, this); + case 1 -> ChestMenu.oneRow(syncId, playerInventory, this); + default -> ChestMenu.threeRows(syncId, playerInventory, this); + }; + // Purpur end } @Override diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java index 0e0d178f2793ab014358f534c8dc53218b89f083..2d190b3a6378b8cbadfa65510df1ccfbd5882ef8 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java @@ -92,6 +92,16 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name public double getEffectRange() { if (this.effectRange < 0) { + // Purpur Start + 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; @@ -168,6 +178,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name int j = pos.getY(); int k = pos.getZ(); BlockPos blockposition1; + boolean isTintedGlass = false; if (blockEntity.lastCheckY < j) { blockposition1 = pos; @@ -201,6 +212,9 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name } } } else { + if (world.purpurConfig.beaconAllowEffectsWithTintedGlass && block.equals(Blocks.TINTED_GLASS)) { + isTintedGlass = true; + } if (tileentitybeacon_beaconcolortracker == null || iblockdata1.getLightBlock() >= 15 && !iblockdata1.is(Blocks.BEDROCK)) { blockEntity.checkingBeamSections.clear(); blockEntity.lastCheckY = l; @@ -220,7 +234,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name blockEntity.levels = BeaconBlockEntity.updateBase(world, i, j, k); } - if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) { + if (blockEntity.levels > 0 && (!blockEntity.beamSections.isEmpty() || (world.purpurConfig.beaconAllowEffectsWithTintedGlass && isTintedGlass))) { BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); } diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java index 83ad45aed0894e90825d22e078632352c3a06816..def408384cbd571b7bee23f5cecf430a5d690c4b 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java @@ -60,7 +60,7 @@ public class BeehiveBlockEntity extends BlockEntity { private List stored = Lists.newArrayList(); @Nullable public BlockPos savedFlowerPos; - public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold + public int maxBees = org.purpurmc.purpur.PurpurConfig.beeInsideBeeHive; // CraftBukkit - allow setting max amount of bees a hive can hold // Purpur public BeehiveBlockEntity(BlockPos pos, BlockState state) { super(BlockEntityType.BEEHIVE, pos, state); @@ -147,11 +147,33 @@ public class BeehiveBlockEntity extends BlockEntity { return list; } + // Purpur start + public List releaseBee(BlockState iblockdata, BeehiveBlockEntity.BeeData data, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, boolean force) { + List list = Lists.newArrayList(); + + BeehiveBlockEntity.releaseOccupant(this.level, this.worldPosition, iblockdata, data.occupant, list, tileentitybeehive_releasestatus, this.savedFlowerPos, force); + + if (!list.isEmpty()) { + stored.remove(data); + + super.setChanged(); + } + + return list; + } + // Purpur end + @VisibleForDebug public int getOccupantCount() { return this.stored.size(); } + // Purpur start + public List getStored() { + return stored; + } + // Purpur end + // Paper start - Add EntityBlockStorage clearEntities public void clearBees() { this.stored.clear(); @@ -472,9 +494,9 @@ public class BeehiveBlockEntity extends BlockEntity { } } - private static class BeeData { + public static class BeeData { // Purpur - change from private to public - private final BeehiveBlockEntity.Occupant occupant; + public final BeehiveBlockEntity.Occupant occupant; // Purpur - make public private int exitTickCounter; // Paper - Fix bees aging inside hives; separate counter for checking if bee should exit to reduce exit attempts private int ticksInHive; diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java index 1f929b467a0ece3143af58a657cf5983c07a8d51..eaa6ece956f90632831f0558924eaf18680a252b 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 @@ -95,6 +95,12 @@ public abstract class BlockEntity { if (persistentDataTag instanceof CompoundTag) { this.persistentDataContainer.putAll((CompoundTag) persistentDataTag); } + // Purpur start + if (nbt.contains("Purpur.persistentLore")) { + net.minecraft.world.item.component.ItemLore.CODEC.decode(net.minecraft.nbt.NbtOps.INSTANCE, nbt.getCompound("Purpur.persistentLore")).result() + .ifPresent(tag -> this.persistentLore = tag.getFirst()); + } + // Purpur end } // CraftBukkit end @@ -111,6 +117,15 @@ public abstract class BlockEntity { this.loadAdditional(nbt, registries); } + // Purpur start + protected void saveAdditional(CompoundTag nbt) { + if (this.persistentLore != null) { + net.minecraft.world.item.component.ItemLore.CODEC.encodeStart(net.minecraft.nbt.NbtOps.INSTANCE, this.persistentLore).result() + .ifPresent(tag -> nbt.put("Purpur.persistentLore", tag)); + } + } + // Purpur end + protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) {} public final CompoundTag saveWithFullMetadata(HolderLookup.Provider registries) { @@ -419,4 +434,16 @@ public abstract class BlockEntity { T getOrDefault(DataComponentType type, T fallback); } + // Purpur start + @Nullable + private net.minecraft.world.item.component.ItemLore persistentLore = null; + + public void setPersistentLore(net.minecraft.world.item.component.ItemLore lore) { + this.persistentLore = lore; + } + + public @org.jetbrains.annotations.Nullable net.minecraft.world.item.component.ItemLore getPersistentLore() { + return this.persistentLore; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java index d354c2ad41533ec8d52f7c5f63f8f74a0b1ce235..a43c41a26c1e34a2f4eda1c498a562664a783c77 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java @@ -169,7 +169,7 @@ public class ConduitBlockEntity extends BlockEntity { if ((l > 1 || i1 > 1 || j1 > 1) && (i == 0 && (i1 == 2 || j1 == 2) || j == 0 && (l == 2 || j1 == 2) || k == 0 && (l == 2 || i1 == 2))) { BlockPos blockposition2 = pos.offset(i, j, k); BlockState iblockdata = world.getBlockState(blockposition2); - Block[] ablock = ConduitBlockEntity.VALID_BLOCKS; + Block[] ablock = world.purpurConfig.conduitBlocks; // Purpur int k1 = ablock.length; for (int l1 = 0; l1 < k1; ++l1) { @@ -189,13 +189,13 @@ public class ConduitBlockEntity extends BlockEntity { private static void applyEffects(Level world, BlockPos pos, List activatingBlocks) { // CraftBukkit start - ConduitBlockEntity.applyEffects(world, pos, ConduitBlockEntity.getRange(activatingBlocks)); + ConduitBlockEntity.applyEffects(world, pos, ConduitBlockEntity.getRange(activatingBlocks, world)); // Purpur } - public static int getRange(List list) { + public static int getRange(List list, Level world) { // Purpur // CraftBukkit end int i = list.size(); - int j = i / 7 * 16; + int j = i / 7 * world.purpurConfig.conduitDistance; // Purpur // CraftBukkit start return j; } @@ -238,20 +238,20 @@ public class ConduitBlockEntity extends BlockEntity { tileentityconduit.destroyTarget = ConduitBlockEntity.findDestroyTarget(world, blockposition, tileentityconduit.destroyTargetUUID); tileentityconduit.destroyTargetUUID = null; } else if (tileentityconduit.destroyTarget == null) { - List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(blockposition), (entityliving1) -> { + List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(blockposition, world), (entityliving1) -> { // Purpur return entityliving1 instanceof Enemy && entityliving1.isInWaterOrRain(); }); if (!list1.isEmpty()) { tileentityconduit.destroyTarget = (LivingEntity) list1.get(world.random.nextInt(list1.size())); } - } else if (!tileentityconduit.destroyTarget.isAlive() || !blockposition.closerThan(tileentityconduit.destroyTarget.blockPosition(), 8.0D)) { + } else if (!tileentityconduit.destroyTarget.isAlive() || !blockposition.closerThan(tileentityconduit.destroyTarget.blockPosition(), world.purpurConfig.conduitDamageDistance)) { // Purpur tileentityconduit.destroyTarget = null; } // CraftBukkit start if (damageTarget && tileentityconduit.destroyTarget != null) { - if (tileentityconduit.destroyTarget.hurtServer((ServerLevel) world, world.damageSources().magic().directBlock(world, blockposition), 4.0F)) { + if (tileentityconduit.destroyTarget.hurtServer((ServerLevel) world, world.damageSources().magic().directBlock(world, blockposition), world.purpurConfig.conduitDamageAmount)) { // Purpur world.playSound(null, tileentityconduit.destroyTarget.getX(), tileentityconduit.destroyTarget.getY(), tileentityconduit.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F); } // CraftBukkit end @@ -276,16 +276,22 @@ public class ConduitBlockEntity extends BlockEntity { } public static AABB getDestroyRangeAABB(BlockPos pos) { + // Purpur start + return getDestroyRangeAABB(pos, null); + } + + private static AABB getDestroyRangeAABB(BlockPos pos, Level level) { + // Purpur end int i = pos.getX(); int j = pos.getY(); int k = pos.getZ(); - return (new AABB((double) i, (double) j, (double) k, (double) (i + 1), (double) (j + 1), (double) (k + 1))).inflate(8.0D); + return (new AABB((double) i, (double) j, (double) k, (double) (i + 1), (double) (j + 1), (double) (k + 1))).inflate(level == null ? 8.0D : level.purpurConfig.conduitDamageDistance); // Purpur } @Nullable private static LivingEntity findDestroyTarget(Level world, BlockPos pos, UUID uuid) { - List list = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos), (entityliving) -> { + List list = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos, world), (entityliving) -> { // Purpur return entityliving.getUUID().equals(uuid); }); diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java index 39aac959775afeaeea211f21d498cb0ddf0a3fcb..6349a342c023f378af431a73a62fb017882e257d 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/EnchantingTableBlockEntity.java @@ -28,6 +28,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable private static final RandomSource RANDOM = RandomSource.create(); @Nullable private Component name; + private int lapis = 0; // Purpur public EnchantingTableBlockEntity(BlockPos pos, BlockState state) { super(BlockEntityType.ENCHANTING_TABLE, pos, state); @@ -39,6 +40,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable if (this.hasCustomName()) { nbt.putString("CustomName", Component.Serializer.toJson(this.name, registries)); } + nbt.putInt("Purpur.Lapis", this.lapis); // Purpur } @Override @@ -47,6 +49,7 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable if (nbt.contains("CustomName", 8)) { this.name = parseCustomNameSafe(nbt.getString("CustomName"), registries); } + this.lapis = nbt.getInt("Purpur.Lapis"); // Purpur } public static void bookAnimationTick(Level world, BlockPos pos, BlockState state, EnchantingTableBlockEntity blockEntity) { @@ -138,4 +141,14 @@ public class EnchantingTableBlockEntity extends BlockEntity implements Nameable public void removeComponentsFromTag(CompoundTag nbt) { nbt.remove("CustomName"); } + + // Purpur start + public int getLapis() { + return this.lapis; + } + + public void setLapis(int lapis) { + this.lapis = lapis; + } + // Purpur } diff --git a/src/main/java/net/minecraft/world/level/block/entity/FuelValues.java b/src/main/java/net/minecraft/world/level/block/entity/FuelValues.java index 61ef08ac941b1e8988d001241780d3a1582f7a2d..2eab5b43ab654966d26424597c1f3baa919e8434 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/FuelValues.java +++ b/src/main/java/net/minecraft/world/level/block/entity/FuelValues.java @@ -17,7 +17,7 @@ import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.block.Blocks; public class FuelValues { - private final Object2IntSortedMap values; + public final Object2IntSortedMap values; // Purpur - private -> public FuelValues(Object2IntSortedMap fuelValues) { this.values = fuelValues; diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java index 8ac19e1e052e73ff3fd09089bb8e3fd687390ee4..6da1eec98c08e4909ecbd48fe90b3fd62011e284 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java @@ -201,16 +201,31 @@ public class SignBlockEntity extends BlockEntity { return this.setText((SignText) textChanger.apply(signtext), front); } + // Purpur start + private Component translateColors(org.bukkit.entity.Player player, String line, Style style) { + if (level.purpurConfig.signAllowColors) { + if (player.hasPermission("purpur.sign.color")) line = line.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1"); + if (player.hasPermission("purpur.sign.style")) line = line.replaceAll("(?i)&([l-or])", "\u00a7$1"); + if (player.hasPermission("purpur.sign.magic")) line = line.replaceAll("(?i)&([kr])", "\u00a7$1"); + + return io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(line)); + } else { + return Component.literal(line).setStyle(style); + } + } + // Purpur end + private SignText setMessages(net.minecraft.world.entity.player.Player entityhuman, List list, SignText signtext, boolean front) { // CraftBukkit SignText originalText = signtext; // CraftBukkit for (int i = 0; i < list.size(); ++i) { FilteredText filteredtext = (FilteredText) list.get(i); Style chatmodifier = signtext.getMessage(i, entityhuman.isTextFilteringEnabled()).getStyle(); + org.bukkit.entity.Player player = (org.bukkit.craftbukkit.entity.CraftPlayer) entityhuman.getBukkitEntity(); // Purpur if (entityhuman.isTextFilteringEnabled()) { - signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only + signtext = signtext.setMessage(i, translateColors(player, net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty()), chatmodifier)); // Paper - filter sign text to chat only // Purpur } else { - signtext = signtext.setMessage(i, Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.raw())).setStyle(chatmodifier), Component.literal(net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only + signtext = signtext.setMessage(i, translateColors(player, net.minecraft.util.StringUtil.filterText(filteredtext.raw()), chatmodifier), translateColors(player, net.minecraft.util.StringUtil.filterText(filteredtext.filteredOrEmpty()), chatmodifier)); // Paper - filter sign text to chat only // Purpur } } @@ -348,6 +363,28 @@ public class SignBlockEntity extends BlockEntity { return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player); // Paper - Fix commands from signs not firing command events } + // Purpur start + public ClientboundBlockEntityDataPacket getTranslatedUpdatePacket(boolean filtered, boolean front) { + final CompoundTag nbt = new CompoundTag(); + this.saveAdditional(nbt, this.getLevel().registryAccess()); + final Component[] lines = front ? frontText.getMessages(filtered) : backText.getMessages(filtered); + final String side = front ? "front_text" : "back_text"; + for (int i = 0; i < 4; i++) { + final var component = io.papermc.paper.adventure.PaperAdventure.asAdventure(lines[i]); + final String line = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().serialize(component); + final var text = net.kyori.adventure.text.Component.text(line); + final String json = net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(text); + if (!nbt.contains(side)) nbt.put(side, new CompoundTag()); + final CompoundTag sideNbt = nbt.getCompound(side); + if (!sideNbt.contains("messages")) sideNbt.put("messages", new net.minecraft.nbt.ListTag()); + final net.minecraft.nbt.ListTag messagesNbt = sideNbt.getList("messages", Tag.TAG_STRING); + messagesNbt.set(i, net.minecraft.nbt.StringTag.valueOf(json)); + } + nbt.putString("PurpurEditor", "true"); + return ClientboundBlockEntityDataPacket.create(this, (blockEntity, registryAccess) -> nbt); + } + // Purpur end + @Override public ClientboundBlockEntityDataPacket getUpdatePacket() { return ClientboundBlockEntityDataPacket.create(this); diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java b/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java index 205e223c356634bd6bc6bd58c6f0b7fda61a6f5f..bea05cb928d540a2f19b51bb7352d032b2dd69cd 100644 --- a/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java +++ b/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java @@ -81,7 +81,7 @@ public class PistonStructureResolver { return true; } else { int i = 1; - if (i + this.toPush.size() > 12) { + if (i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur return false; } else { while (isSticky(blockState)) { @@ -95,7 +95,7 @@ public class PistonStructureResolver { break; } - if (++i + this.toPush.size() > 12) { + if (++i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur return false; } } @@ -140,7 +140,7 @@ public class PistonStructureResolver { return true; } - if (this.toPush.size() >= 12) { + if (this.toPush.size() >= this.level.purpurConfig.pistonBlockPushLimit) { // Purpur return false; } diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java index 5b6fbfd1a7a2d87fb2b87d5d1e674206cdf9b280..dcf2dcece3e995ce4646b931329246be19a4e1c2 100644 --- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java @@ -91,7 +91,7 @@ public abstract class BlockBehaviour implements FeatureElement { protected static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP}; public final boolean hasCollision; - protected final float explosionResistance; + public float explosionResistance; // Purpur - protected final -> public protected final boolean isRandomlyTicking; protected final SoundType soundType; protected final float friction; @@ -99,7 +99,7 @@ public abstract class BlockBehaviour implements FeatureElement { protected final float jumpFactor; protected final boolean dynamicShape; protected final FeatureFlagSet requiredFeatures; - protected final BlockBehaviour.Properties properties; + public final BlockBehaviour.Properties properties; // Purpur - protected -> public protected final Optional> drops; protected final String descriptionId; diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java index 4640baec5bed6c2d53cc0f8ca1d273cc115abe9b..5c4843f6cfd13790a44fc0f928f1fbcef8a648ce 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -88,6 +88,18 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p private final LevelChunkTicks fluidTicks; private LevelChunk.UnsavedListener unsavedListener; + // Pufferfish start - instead of using a random every time the chunk is ticked, define when lightning strikes preemptively + private int lightningTick; + // shouldDoLightning compiles down to 29 bytes, which with the default of 35 byte inlining should guarantee an inline + public final boolean shouldDoLightning(net.minecraft.util.RandomSource random) { + if (this.lightningTick-- <= 0) { + this.lightningTick = random.nextInt(this.level.spigotConfig.thunderChance) << 1; + return true; + } + return false; + } + // Pufferfish end + public LevelChunk(Level world, ChunkPos pos) { this(world, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, (LevelChunkSection[]) null, (LevelChunk.PostLoadProcessor) null, (BlendingData) null); } @@ -121,6 +133,8 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p this.debug = !empty && this.level.isDebug(); this.defaultBlockState = empty ? VOID_AIR_BLOCKSTATE : AIR_BLOCKSTATE; // Paper end - get block chunk optimisation + + this.lightningTick = java.util.concurrent.ThreadLocalRandom.current().nextInt(100000) << 1; // Pufferfish - initialize lightning tick // Purpur - any random will do } // CraftBukkit start diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java index 356d010506fd21f3c752e4aa86c46c1106fdde3b..86e16dd6b905af31795fda8002f2e1f857ddcb9f 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java @@ -106,6 +106,7 @@ public class EntityStorage implements EntityPersistentStorage { } // Paper end - Entity load/save limit per chunk CompoundTag compoundTagx = new CompoundTag(); + if (!entity.canSaveToDisk()) return; // Purpur if (entity.save(compoundTagx)) { listTag.add(compoundTagx); } diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java index e40665cead218502b44dd49051a53326ed94f061..cf0f3a14584b9280211f88808cb1ddfb10105ec5 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java @@ -289,7 +289,7 @@ public class RegionFileStorage implements AutoCloseable, ca.spottedleaf.moonrise // Paper start private static void printOversizedLog(String msg, Path file, int x, int z) { - org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PAPER - You may ask for help on Discord, but do not file an issue. These error messages can not be removed."); + org.apache.logging.log4j.LogManager.getLogger().fatal(msg + " (" + file.toString().replaceAll(".+[\\\\/]", "") + " - " + x + "," + z + ") Go clean it up to remove this message. /minecraft:tp " + (x<<4)+" 128 "+(z<<4) + " - DO NOT REPORT THIS TO PURPUR - You may ask for help on Discord, but do not file an issue. These error messages can not be removed."); // Purpur } private static CompoundTag readOversizedChunk(RegionFile regionfile, ChunkPos chunkCoordinate) throws IOException { diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java index d8b4196adf955f8d414688dc451caac2d9c609d9..80a43def4912a3228cd95117d5c2aac68798b4ec 100644 --- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java +++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java @@ -9,7 +9,7 @@ import javax.annotation.Nullable; import net.minecraft.world.entity.Entity; public class EntityTickList { - private final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system + public final ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet entities = new ca.spottedleaf.moonrise.common.list.IteratorSafeOrderedReferenceSet<>(); // Paper - rewrite chunk system // Pufferfish - private->public private void ensureActiveIsNotIterated() { // Paper - rewrite chunk system diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java index 021221da5d0315f6e371380a705ac6b3f6ac18d3..27eb9a365006884c85603dc6d9dd8eee009c98b3 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java +++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java @@ -48,7 +48,7 @@ public class PhantomSpawner implements CustomSpawner { int spawnAttemptMaxSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds; this.nextTick += (spawnAttemptMinSeconds + randomsource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; // Paper end - Ability to control player's insomnia and phantoms - if (world.getSkyDarken() < 5 && world.dimensionType().hasSkyLight()) { + if (world.getSkyDarken() < world.purpurConfig.phantomSpawnMinSkyDarkness && world.dimensionType().hasSkyLight()) { // Purpur return 0; } else { int i = 0; @@ -60,10 +60,10 @@ public class PhantomSpawner implements CustomSpawner { if (!entityplayer.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityplayer.isCreative())) { // Paper - Add phantom creative and insomniac controls BlockPos blockposition = entityplayer.blockPosition(); - if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) { + if (!world.dimensionType().hasSkyLight() || (!world.purpurConfig.phantomSpawnOnlyAboveSeaLevel || blockposition.getY() >= world.getSeaLevel()) && (!world.purpurConfig.phantomSpawnOnlyWithVisibleSky || world.canSeeSky(blockposition))) { // Purpur DifficultyInstance difficultydamagescaler = world.getCurrentDifficultyAt(blockposition); - if (difficultydamagescaler.isHarderThan(randomsource.nextFloat() * 3.0F)) { + if (difficultydamagescaler.isHarderThan(randomsource.nextFloat() * (float) world.purpurConfig.phantomSpawnLocalDifficultyChance)) { // Purpur ServerStatsCounter serverstatisticmanager = entityplayer.getStats(); int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); boolean flag2 = true; @@ -75,7 +75,7 @@ public class PhantomSpawner implements CustomSpawner { if (NaturalSpawner.isValidEmptySpawnBlock(world, blockposition1, iblockdata, fluid, EntityType.PHANTOM)) { SpawnGroupData groupdataentity = null; - int k = 1 + randomsource.nextInt(difficultydamagescaler.getDifficulty().getId() + 1); + int k = world.purpurConfig.phantomSpawnMinPerAttempt + world.random.nextInt((world.purpurConfig.phantomSpawnMaxPerAttempt < 0 ? difficultydamagescaler.getDifficulty().getId() : world.purpurConfig.phantomSpawnMaxPerAttempt - world.purpurConfig.phantomSpawnMinPerAttempt) + 1); // Purpur for (int l = 0; l < k; ++l) { // Paper start - PhantomPreSpawnEvent diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java index f4fbcbb8ff6d2677af1a02a0801a323c06dce9b1..6f024a29e8824a604ff0c74f88522d23423beb5c 100644 --- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java @@ -266,7 +266,7 @@ public abstract class FlowingFluid extends Fluid { } } - if (j >= 2 && this.canConvertToSource(world)) { + if (j >= getRequiredSources(world) && this.canConvertToSource(world)) { // Purpur BlockState iblockdata2 = world.getBlockState(blockposition_mutableblockposition.setWithOffset(pos, Direction.DOWN)); FluidState fluid1 = iblockdata2.getFluidState(); @@ -356,6 +356,12 @@ public abstract class FlowingFluid extends Fluid { protected abstract boolean canConvertToSource(ServerLevel world); + // Purpur start + protected int getRequiredSources(Level level) { + return 2; + } + // Purpur end + protected void spreadTo(LevelAccessor world, BlockPos pos, BlockState state, Direction direction, FluidState fluidState) { Block block = state.getBlock(); diff --git a/src/main/java/net/minecraft/world/level/material/LavaFluid.java b/src/main/java/net/minecraft/world/level/material/LavaFluid.java index 884db3e64cb22ed765beec8f11ea309fcf810207..6e643c1a7f7e71cfd20603facaf224985ee81716 100644 --- a/src/main/java/net/minecraft/world/level/material/LavaFluid.java +++ b/src/main/java/net/minecraft/world/level/material/LavaFluid.java @@ -181,7 +181,7 @@ public abstract class LavaFluid extends FlowingFluid { @Override public int getTickDelay(LevelReader world) { - return world.dimensionType().ultraWarm() ? 10 : 30; + return world.dimensionType().ultraWarm() ? world.getWorldBorder().world.purpurConfig.lavaSpeedNether : world.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur } @Override @@ -199,6 +199,13 @@ public abstract class LavaFluid extends FlowingFluid { world.levelEvent(1501, pos, 0); } + // Purpur start + @Override + protected int getRequiredSources(Level level) { + return level.purpurConfig.lavaInfiniteRequiredSources; + } + // Purpur end + @Override protected boolean canConvertToSource(ServerLevel world) { return world.getGameRules().getBoolean(GameRules.RULE_LAVA_SOURCE_CONVERSION); diff --git a/src/main/java/net/minecraft/world/level/material/WaterFluid.java b/src/main/java/net/minecraft/world/level/material/WaterFluid.java index 552925ba47c7475e2e1ec2ded0966f28ed3e50a5..1e741f36b79585f33abe413beafe00cf5205d54f 100644 --- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java +++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java @@ -81,6 +81,13 @@ public abstract class WaterFluid extends FlowingFluid { return world.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION); } + // Purpur start + @Override + protected int getRequiredSources(Level level) { + return level.purpurConfig.waterInfiniteRequiredSources; + } + // Purpur end + // Paper start - Add BlockBreakBlockEvent @Override protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { diff --git a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java index c84fd369d92932903c76bb2012602617d3e2d213..224896124706764412033c8726c822e116f2c0f1 100644 --- a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java +++ b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java @@ -240,7 +240,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { if ((node == null || node.costMalus < 0.0F) && maxYStep > 0 && (pathType != PathType.FENCE || this.canWalkOverFences()) - && pathType != PathType.UNPASSABLE_RAIL + && (this.mob.level().purpurConfig.mobsIgnoreRails || pathType != PathType.UNPASSABLE_RAIL) // Purpur && pathType != PathType.TRAPDOOR && pathType != PathType.POWDER_SNOW) { node = this.tryJumpOn(x, y, z, maxYStep, prevFeetY, direction, nodeType, mutableBlockPos); @@ -491,7 +491,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { return PathType.TRAPDOOR; } else if (blockState.is(Blocks.POWDER_SNOW)) { return PathType.POWDER_SNOW; - } else if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH)) { + } else if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH) || blockState.is(Blocks.STONECUTTER)) { // Purpur return PathType.DAMAGE_OTHER; } else if (blockState.is(Blocks.HONEY_BLOCK)) { return PathType.STICKY_HONEY; diff --git a/src/main/java/net/minecraft/world/level/portal/PortalShape.java b/src/main/java/net/minecraft/world/level/portal/PortalShape.java index 90056822cd17f3d33d14b3f94b34750ee522a0a9..acdff7b4a00d563739fd301c3633a266875296fa 100644 --- a/src/main/java/net/minecraft/world/level/portal/PortalShape.java +++ b/src/main/java/net/minecraft/world/level/portal/PortalShape.java @@ -35,7 +35,7 @@ public class PortalShape { private static final int MIN_HEIGHT = 3; public static final int MAX_HEIGHT = 21; private static final BlockBehaviour.StatePredicate FRAME = (iblockdata, iblockaccess, blockposition) -> { - return iblockdata.is(Blocks.OBSIDIAN); + return iblockdata.is(Blocks.OBSIDIAN) || (org.purpurmc.purpur.PurpurConfig.cryingObsidianValidForPortalFrame && iblockdata.is(Blocks.CRYING_OBSIDIAN)); // Purpur }; private static final float SAFE_TRAVEL_MAX_ENTITY_XY = 4.0F; private static final double SAFE_TRAVEL_MAX_VERTICAL_DELTA = 1.0D; diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java index ae321b3b8d98e42ef07fd1f0f738c1a2b428f6db..26da9e7c25ef6a89482838010d8ed6bcf8c87511 100644 --- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java +++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java @@ -81,6 +81,7 @@ public class MapItemSavedData extends SavedData { private final Map frameMarkers = Maps.newHashMap(); private int trackedDecorationCount; private org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper + public boolean isExplorerMap; // Purpur // CraftBukkit start public final CraftMapView mapView; diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java index 5f27e1ce23f2ed68e4c8af1986fafce940dbf826..d8cf49cbd82ed12d23fa10a81a88cc4bcf1c0f10 100644 --- a/src/main/java/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java +++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/EnchantedCountIncreaseFunction.java @@ -66,6 +66,11 @@ public class EnchantedCountIncreaseFunction extends LootItemConditionalFunction Entity entity = context.getOptionalParameter(LootContextParams.ATTACKING_ENTITY); if (entity instanceof LivingEntity livingEntity) { int i = EnchantmentHelper.getEnchantmentLevel(this.enchantment, livingEntity); + // Purpur start - Add an option to fix MC-3304 projectile looting + if (org.purpurmc.purpur.PurpurConfig.fixProjectileLootingTransfer && context.getOptionalParameter(LootContextParams.DIRECT_ATTACKING_ENTITY) instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) { + i = arrow.actualEnchantments.getLevel(this.enchantment); + } + // Purpur end - Add an option to fix MC-3304 projectile looting if (i == 0) { return stack; } diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java index 6cf6d4ec7b9e43c7b2b4c0e2fb080964ff588130..e74866e5195a5eeae7666ad7be750edac5947094 100644 --- a/src/main/java/net/minecraft/world/phys/AABB.java +++ b/src/main/java/net/minecraft/world/phys/AABB.java @@ -551,4 +551,10 @@ public class AABB { public static AABB ofSize(Vec3 center, double dx, double dy, double dz) { return new AABB(center.x - dx / 2.0, center.y - dy / 2.0, center.z - dz / 2.0, center.x + dx / 2.0, center.y + dy / 2.0, center.z + dz / 2.0); } + + // Purpur - tuinity added method + public final AABB offsetY(double dy) { + return new AABB(this.minX, this.minY + dy, this.minZ, this.maxX, this.maxY + dy, this.maxZ); + } + // Purpur } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java index 94ca0407303c4493ab4928b12ec6ecc75aaca549..a138e1b6b66d99f2035de054137a607aa6b7f0b9 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java @@ -363,14 +363,26 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa @Override public Location getLocation() { + // Purpur start + if (this.isOnline()) { + return this.getPlayer().getLocation(); + } + // Purpur end + CompoundTag data = this.getData(); if (data == null) { return null; } - if (data.contains("Pos") && data.contains("Rotation")) { - ListTag position = (ListTag) data.get("Pos"); - ListTag rotation = (ListTag) data.get("Rotation"); + // Purpur start - OfflinePlayer API + //if (data.contains("Pos") && data.contains("Rotation")) { + ListTag position = data.getList("Pos", net.minecraft.nbt.Tag.TAG_DOUBLE); + ListTag rotation = data.getList("Rotation", net.minecraft.nbt.Tag.TAG_FLOAT); + + if (position.isEmpty() && rotation.isEmpty()) { + return null; + } + // Purpur end - OfflinePlayer API UUID uuid = new UUID(data.getLong("WorldUUIDMost"), data.getLong("WorldUUIDLeast")); @@ -381,9 +393,9 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa rotation.getFloat(0), rotation.getFloat(1) ); - } + //} // Purpur - OfflinePlayer API - return null; + //return null; // Purpur - OfflinePlayer API } @Override @@ -626,4 +638,191 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa manager.save(); } } + + // Purpur start - OfflinePlayer API + @Override + public boolean getAllowFlight() { + if (this.isOnline()) { + return this.getPlayer().getAllowFlight(); + } else { + CompoundTag data = this.getData(); + if (data == null) return false; + if (!data.contains("abilities")) return false; + CompoundTag abilities = data.getCompound("abilities"); + return abilities.getByte("mayfly") == (byte) 1; + } + } + + @Override + public void setAllowFlight(boolean flight) { + if (this.isOnline()) { + this.getPlayer().setAllowFlight(flight); + } else { + CompoundTag data = this.getData(); + if (data == null) return; + if (!data.contains("abilities")) return; + CompoundTag abilities = data.getCompound("abilities"); + abilities.putByte("mayfly", (byte) (flight ? 1 : 0)); + data.put("abilities", abilities); + save(data); + } + } + + @Override + public boolean isFlying() { + if (this.isOnline()) { + return this.isFlying(); + } else { + CompoundTag data = this.getData(); + if (data == null) return false; + if (!data.contains("abilities")) return false; + CompoundTag abilities = data.getCompound("abilities"); + return abilities.getByte("flying") == (byte) 1; + } + } + + @Override + public void setFlying(boolean value) { + if (this.isOnline()) { + this.getPlayer().setFlying(value); + } else { + CompoundTag data = this.getData(); + if (data == null) return; + if (!data.contains("abilities")) return; + CompoundTag abilities = data.getCompound("abilities"); + abilities.putByte("mayfly", (byte) (value ? 1 : 0)); + data.put("abilities", abilities); + save(data); + } + } + + @Override + public void setFlySpeed(float value) throws IllegalArgumentException { + if (value < -1f || value > 1f) throw new IllegalArgumentException("FlySpeed needs to be between -1 and 1"); + if (this.isOnline()) { + this.getPlayer().setFlySpeed(value); + } else { + CompoundTag data = this.getData(); + if (data == null) return; + if (!data.contains("abilities")) return; + CompoundTag abilities = data.getCompound("abilities"); + abilities.putFloat("flySpeed", value); + data.put("abilities", abilities); + save(data); + } + } + + @Override + public float getFlySpeed() { + if (this.isOnline()) { + return this.getPlayer().getFlySpeed(); + } else { + CompoundTag data = this.getData(); + if (data == null) return 0; + if (!data.contains("abilities")) return 0; + CompoundTag abilities = data.getCompound("abilities"); + return abilities.getFloat("flySpeed"); + } + } + + @Override + public void setWalkSpeed(float value) throws IllegalArgumentException { + if (value < -1f || value > 1f) throw new IllegalArgumentException("WalkSpeed needs to be between -1 and 1"); + if (this.isOnline()) { + this.getPlayer().setWalkSpeed(value); + } else { + CompoundTag data = this.getData(); + if (data == null) return; + if (!data.contains("abilities")) return; + CompoundTag abilities = data.getCompound("abilities"); + abilities.putFloat("walkSpeed", value); + data.put("abilities", abilities); + save(data); + } + } + + @Override + public float getWalkSpeed() { + if (this.isOnline()) { + return this.getPlayer().getWalkSpeed(); + } else { + CompoundTag data = this.getData(); + if (data == null) return 0; + if (!data.contains("abilities")) return 0; + CompoundTag abilities = data.getCompound("abilities"); + return abilities.getFloat("walkSpeed"); + } + } + + @Override + public boolean teleportOffline(Location destination) { + if (this.isOnline()) { + return this.getPlayer().teleport(destination); + } else { + return setLocation(destination); + } + } + + @Override + public boolean teleportOffline(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause){ + if (this.isOnline()) { + return this.getPlayer().teleport(destination, cause); + } else { + return setLocation(destination); + } + } + + @Override + public java.util.concurrent.CompletableFuture teleportOfflineAsync(Location destination) { + if (this.isOnline()) { + return this.getPlayer().teleportAsync(destination); + } else { + return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination)); + } + } + + @Override + public java.util.concurrent.CompletableFuture teleportOfflineAsync(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { + if (this.isOnline()) { + return this.getPlayer().teleportAsync(destination, cause); + } else { + return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination)); + } + } + + private boolean setLocation(Location location) { + CompoundTag data = this.getData(); + if (data == null) return false; + data.putLong("WorldUUIDMost", location.getWorld().getUID().getMostSignificantBits()); + data.putLong("WorldUUIDLeast", location.getWorld().getUID().getLeastSignificantBits()); + net.minecraft.nbt.ListTag position = new net.minecraft.nbt.ListTag(); + position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getX())); + position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getY())); + position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getZ())); + data.put("Pos", position); + net.minecraft.nbt.ListTag rotation = new net.minecraft.nbt.ListTag(); + rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getYaw())); + rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getPitch())); + data.put("Rotation", rotation); + save(data); + return true; + } + + /** + * Safely replaces player's .dat file with provided CompoundTag + * @param compoundTag + */ + private void save(CompoundTag compoundTag) { + File playerDir = server.console.playerDataStorage.getPlayerDir(); + try { + File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir); + net.minecraft.nbt.NbtIo.writeCompressed(compoundTag, tempFile.toPath()); + File playerDataFile = new File(playerDir, this.getUniqueId()+".dat"); + File playerDataFileOld = new File(playerDir, this.getUniqueId()+".dat_old"); + net.minecraft.Util.safeReplaceFile(playerDataFile.toPath(), tempFile.toPath(), playerDataFileOld.toPath()); + } catch (java.io.IOException e) { + e.printStackTrace(); + } + } + // Purpur end - OfflinePlayer API } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index ac8af406180bc680d46e8edc3da0fc2e5211345a..81922bf93f8a96e6cf05944ca98a82cc5945a6c6 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -426,6 +426,20 @@ public final class CraftServer implements Server { this.paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager); this.pluginManager.paperPluginManager = this.paperPluginManager; // Paper end + // Purpur start + org.purpurmc.purpur.language.Language.setLanguage(new org.purpurmc.purpur.language.Language() { + private net.minecraft.locale.Language language = net.minecraft.locale.Language.getInstance(); + @Override + public boolean has(@org.jetbrains.annotations.NotNull String key) { + return language.has(key); + } + + @Override + public @org.jetbrains.annotations.NotNull String getOrDefault(@org.jetbrains.annotations.NotNull String key) { + return language.getOrDefault(key); + } + }); + // Purpur end CraftRegistry.setMinecraftRegistry(console.registryAccess()); @@ -1086,6 +1100,7 @@ public final class CraftServer implements Server { org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot this.console.paperConfigurations.reloadConfigs(this.console); + org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur for (ServerLevel world : this.console.getAllLevels()) { // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) @@ -1101,6 +1116,7 @@ public final class CraftServer implements Server { } } world.spigotConfig.init(); // Spigot + world.purpurConfig.init(); // Purpur } Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper @@ -1118,6 +1134,7 @@ public final class CraftServer implements Server { org.spigotmc.SpigotConfig.registerCommands(); // Spigot io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper this.spark.registerCommandBeforePlugins(this); // Paper - spark + org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); @@ -1626,6 +1643,58 @@ 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.item.ItemStack itemStack = net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)); + MinecraftServer.getServer().fuelValues().values.put(itemStack.getItem(), burnTime); + } + + @Override + public void removeFuel(org.bukkit.Material material) { + net.minecraft.world.item.ItemStack itemStack = net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)); + MinecraftServer.getServer().fuelValues().values.keySet().removeIf(itemStack::is); + } + + @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"); @@ -3031,6 +3100,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(); @@ -3060,6 +3141,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() @@ -3270,4 +3352,16 @@ public final class CraftServer implements Server { this.console.addPluginAllowingSleep(plugin.getName(), value); } // Paper end - API to check if the server is sleeping + + // Purpur start + @Override + public String getServerName() { + return this.getProperties().serverName; + } + + @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 57da11c0da7322e74810e7108e9c8000b0c36520..907fe00ab752ad124020755f211567af771b9d70 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -2384,6 +2384,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 Collection getStructures(int x, int z) { return this.getStructures(x, z, struct -> true); diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java index be0d38544395a9b3befb898bb961f34e32fe9509..430407b0cf06ba337c59cd576c7e40a8e5fe8229 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -176,6 +176,20 @@ public class Main { .describedAs("Jar file"); // Paper end + // Purpur Start + 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() @@ -259,7 +273,7 @@ public class Main { System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper } - if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { + if (false && Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { // Purpur Date buildDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(Main.class.getPackage().getImplementationVendor()); // Paper Calendar deadline = Calendar.getInstance(); diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java index 1a2a05160ba51d9c75f1ae6ae61d944d81428722..3beb26ad2ef0fded49a8da8c5dec64f9508c1995 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 : tileEntity.getStored()) { + storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(data, this)); + } + // Purpur end } protected CraftBeehive(CraftBeehive state, Location location) { @@ -75,15 +82,54 @@ public class CraftBeehive extends CraftBlockEntityState impl 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 = ((BeehiveBlockEntity) this.getTileEntityFromWorld()); + BeehiveBlockEntity.BeeData data = ((org.purpurmc.purpur.entity.PurpurStoredBee) entity).getHandle(); + + List list = beehive.releaseBee(getHandle(), data, BeeReleaseStatus.BEE_RELEASED, true); + + if (list.size() == 1) { + storage.remove(entity); + + return (Bee) list.get(0).getBukkitEntity(); + } + } + + return null; + } + + @Override + public List> getEntities() { + return new ArrayList<>(storage); + } + // Purpur end + @Override public void addEntity(Bee entity) { Preconditions.checkArgument(entity != null, "Entity must not be null"); - this.getSnapshot().addOccupant(((CraftBee) entity).getHandle()); + int length = this.getSnapshot().getStored().size(); // Purpur + getSnapshot().addOccupant(((CraftBee) entity).getHandle()); + + // Purpur start - check if new bee was added, and if yes, add to stored bees + List storedBeeData = this.getSnapshot().getStored(); + if(length < storedBeeData.size()) { + storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(storedBeeData.getLast(), this)); + } + // Purpur end } @Override @@ -100,6 +146,7 @@ public class CraftBeehive extends CraftBlockEntityState impl @Override public void clearEntities() { getSnapshot().clearBees(); + storage.clear(); // Purpur } // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java index c1759aeb3e6ad0e4eb66cba3da1b120dd1dce812..1a91bc2e422db0eba65694ac046f1b362c6b0cd6 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java @@ -73,7 +73,7 @@ public class CraftConduit extends CraftBlockEntityState impl public int getRange() { this.ensureNoWorldGeneration(); ConduitBlockEntity conduit = (ConduitBlockEntity) this.getTileEntityFromWorld(); - return (conduit != null) ? ConduitBlockEntity.getRange(conduit.effectBlocks) : 0; + return (conduit != null) ? ConduitBlockEntity.getRange(conduit.effectBlocks, this.world.getHandle()) : 0; // Purpur } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java index 4e56018b64d11f76c8da43fd8f85c6de72204e36..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/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 ddabaed899c755925ad8618b78c33dacaf2126ac..51aee9a468f4ebfa9672fd9ce84883cf080859e3 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -87,6 +87,23 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { this.entityType = CraftEntityType.minecraftToBukkit(entity.getType()); } + // Purpur start - API for any mob to burn daylight + @Override + public boolean isImmuneToFire() { + return getHandle().fireImmune(); + } + + @Override + public void setImmuneToFire(Boolean fireImmune) { + getHandle().immuneToFire = fireImmune; + } + + @Override + public boolean isInDaylight() { + return getHandle().isSunBurnTick(); + } + // Purpur end - API for any mob to burn daylight + public static CraftEntity getEntity(CraftServer server, T entity) { Preconditions.checkArgument(entity != null, "Unknown entity"); @@ -246,6 +263,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { boolean ignorePassengers = flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS); // Don't allow teleporting between worlds while keeping passengers if (flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS) && this.entity.isVehicle() && location.getWorld() != this.getWorld()) { + if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur return false; } @@ -1306,4 +1324,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { } } // Paper end - broadcast hurt animation + + // Purpur start + @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 e345cdbfab44a0f5da80d738798dbb4424b7ab5c..856f12eb276c214f2f57a58a89a4da9eea34db2d 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java @@ -273,6 +273,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { @Override public void recalculatePermissions() { this.perm.recalculatePermissions(); + getHandle().canPortalInstant = hasPermission("purpur.portal.instant"); // Purpur } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java index 63cae1a2e95d8da17c45c4404a8dd0ca6a413c39..966587c2788b5c93be83259ddc962a89cde7cbaa 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 30d62ee4d5cd2ddacb8783b5bbbf475d592b3e02..5c1cda88080850314dac196dbe71ff12e48a8aca 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java @@ -151,4 +151,51 @@ public class CraftItem extends CraftEntity implements Item { public String toString() { return "CraftItem"; } + + // Purpur start + @Override + public void setImmuneToCactus(boolean immuneToCactus) { + this.getHandle().immuneToCactus = immuneToCactus; + } + + @Override + public boolean isImmuneToCactus() { + return this.getHandle().immuneToCactus; + } + + @Override + public void setImmuneToExplosion(boolean immuneToExplosion) { + this.getHandle().immuneToExplosion = immuneToExplosion; + } + + @Override + public boolean isImmuneToExplosion() { + return this.getHandle().immuneToExplosion; + } + + @Override + public void setImmuneToFire(@org.jetbrains.annotations.Nullable Boolean immuneToFire) { + this.getHandle().immuneToFire = (immuneToFire != null && immuneToFire); + } + + @Override + public void setImmuneToFire(boolean immuneToFire) { + this.setImmuneToFire((Boolean) immuneToFire); + } + + @Override + public boolean isImmuneToFire() { + return this.getHandle().immuneToFire; + } + + @Override + public void setImmuneToLightning(boolean immuneToLightning) { + this.getHandle().immuneToLightning = immuneToLightning; + } + + @Override + public boolean isImmuneToLightning() { + return this.getHandle().immuneToLightning; + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java index d0c409f4efad289e3e325f44b500fc72589d89d4..051ffc663317fe5a4fafe0750c89fafdece4d316 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -523,7 +523,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { net.minecraft.server.level.ServerPlayer entityPlayer = killer == null ? null : ((CraftPlayer) killer).getHandle(); getHandle().lastHurtByPlayer = entityPlayer; getHandle().lastHurtByMob = entityPlayer; - getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : 100; // 100 value taken from EntityLiving#damageEntity + getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : getHandle().level().purpurConfig.mobLastHurtByPlayerTime; // 100 value taken from EntityLiving#damageEntity // Purpur } // Paper end @@ -1211,4 +1211,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { return this.getHandle().canUseSlot(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); } // Paper end - Expose canUseSlot + + // Purpur start - API for any mob to burn daylight + @Override + public boolean shouldBurnInDay() { + return this.getHandle().shouldBurnInDay(); + } + + @Override + public void setShouldBurnInDay(final boolean shouldBurnInDay) { + this.getHandle().setShouldBurnInDay(shouldBurnInDay); + } + // Purpur end - API for any mob to burn daylight } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java index 351f42842b780d053cd2e5bad9ae299449141b10..4860574e7fad7a9527dda599703c573c5b4b234b 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java @@ -90,4 +90,16 @@ public class CraftLlama extends CraftChestedHorse implements Llama, com.destroys return this.getHandle().caravanTail == null ? null : (Llama) this.getHandle().caravanTail.getBukkitEntity(); } // Paper end + + // Purpur start + @Override + public boolean shouldJoinCaravan() { + return getHandle().shouldJoinCaravan; + } + + @Override + public void setShouldJoinCaravan(boolean shouldJoinCaravan) { + getHandle().shouldJoinCaravan = shouldJoinCaravan; + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index d4e497961578bb693275cdf95915b60b2cc76eb7..63065a22ff359c142bab23fccacfd5ebd86f81a5 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -583,10 +583,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 = this.getName(); } - this.getHandle().listName = name.equals(this.getName()) ? null : CraftChatMessage.fromStringOrNull(name); + this.getHandle().listName = name.equals(this.getName()) ? null : useMM ? io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name)) : CraftChatMessage.fromStringOrNull(name); // Purpur if (this.getHandle().connection == null) return; // Paper - Updates are possible before the player has fully joined for (ServerPlayer player : (List) this.server.getHandle().players) { if (player.getBukkitEntity().canSee(this)) { @@ -1435,6 +1440,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { // Paper start - Teleport passenger API // Don't allow teleporting between worlds while keeping passengers if (ignorePassengers && entity.isVehicle() && location.getWorld() != this.getWorld()) { + if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur start return false; } @@ -1456,6 +1462,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API + if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur start return false; } @@ -2769,6 +2776,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); } @@ -3580,4 +3609,70 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundEntityEventPacket(((CraftEntity) target).getHandle(), effect.getData())); } // Paper end - entity effect API + + // Purpur start + @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 void sendBlockHighlight(Location location, int duration) { + sendBlockHighlight(location, duration, "", 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, int argb) { + sendBlockHighlight(location, duration, "", argb); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text) { + sendBlockHighlight(location, duration, text, 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, int argb) { + if (this.getHandle().connection == null) return; + this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket(new net.minecraft.network.protocol.common.custom.GameTestAddMarkerDebugPayload(io.papermc.paper.util.MCUtil.toBlockPosition(location), argb, text, duration))); + } + + @Override + public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { + sendBlockHighlight(location, duration, "", color, transparency); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { + if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); + sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); + } + + @Override + public void clearBlockHighlights() { + if (this.getHandle().connection == null) return; + this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket(new net.minecraft.network.protocol.common.custom.GameTestClearMarkersDebugPayload())); + } + + @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 4ce2373ff71c3c1b8951646e057587a3ab09e145..4f7f6cf6ca24406570d2d29dc63dc89401119961 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 8e895d6f84f7d84b219f2424909dd42e5f08dec4..53dcce0701d713c5dd097340a91b8be4806de4b8 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java @@ -375,4 +375,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { getHandle().getGossips().gossips.clear(); } // Paper end + + // Purpur start + @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 7881c6253c1d652c0c0d54a9a8accdf0a1ff0f3e..da6ccb2a38df76770821a1a2203e54e722f541ca 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 ecd33b4add46acbe4e4f8879c0601220423d66ca..1506a8c0fa490726eb4a4ae14f3aa194dc81d9ab 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java @@ -146,4 +146,16 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf { return this.getKey().hashCode(); } } + + // 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 e37aaf77f94b97b736cc20ef070cefdff0400188..763d06265c7d0000e4c641c3aaba785bb0efb23e 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -602,6 +602,15 @@ public class CraftEventFactory { // Paper end craftServer.getPluginManager().callEvent(event); + // Purpur start + 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; } @@ -1131,7 +1140,7 @@ public class CraftEventFactory { return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.LAVA, bukkitDamageSource, modifiers, modifierFunctions, cancelled); } else if (source.getDirectBlock() != null) { DamageCause cause; - if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL)) { + if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL) || source.isStonecutter()) { // Purpur cause = DamageCause.CONTACT; } else if (source.is(DamageTypes.HOT_FLOOR)) { cause = DamageCause.HOT_FLOOR; @@ -1191,6 +1200,7 @@ public class CraftEventFactory { EntityDamageEvent event; if (damager != null) { event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, critical); + damager.processClick(InteractionHand.MAIN_HAND); // Purpur } else { event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions); } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java index 6d3f9d5dab6c9a2860ae31cae24310aa2d62da7c..4f29c579f94efe59a8c78520d75676fc4875e2f0 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java @@ -145,8 +145,19 @@ public class CraftContainer extends AbstractContainerMenu { case PLAYER: case CHEST: case ENDER_CHEST: + // Purpur start + this.delegate = new ChestMenu(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? net.minecraft.world.inventory.MenuType.GENERIC_9x6 : net.minecraft.world.inventory.MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9); + break; case BARREL: - this.delegate = new ChestMenu(net.minecraft.world.inventory.MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9); + this.delegate = new ChestMenu(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { + case 6 -> net.minecraft.world.inventory.MenuType.GENERIC_9x6; + case 5 -> net.minecraft.world.inventory.MenuType.GENERIC_9x5; + case 4 -> net.minecraft.world.inventory.MenuType.GENERIC_9x4; + case 2 -> net.minecraft.world.inventory.MenuType.GENERIC_9x2; + case 1 -> net.minecraft.world.inventory.MenuType.GENERIC_9x1; + default -> net.minecraft.world.inventory.MenuType.GENERIC_9x3; + }, windowId, bottom, top, top.getContainerSize() / 9); + // Purpur end break; case DISPENSER: case DROPPER: diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java index c6159c70f7a37b9bffe268b91905ce848d1d2927..d02adaaa6fbdc1c0eff44cb4a1f1642f9575a821 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java @@ -84,7 +84,7 @@ public class CraftInventory implements Inventory { @Override public void setContents(ItemStack[] items) { - Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize()); + // Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize()); // Purpur for (int i = 0; i < this.getSize(); i++) { if (i >= items.length) { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java index 792cb6adf0c7a6335cc5985fce8bed2e0f1149af..5734c5caffda79383ae30df20c3defb51b87f39e 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java @@ -19,6 +19,10 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn private int repairCost; private int repairCostAmount; private int maximumRepairCost; + // Purpur start - Anvil API + private boolean bypassCost; + private boolean canDoUnsafeEnchants; + // Purpur end - Anvil API public CraftInventoryAnvil(Location location, Container inventory, Container resultInventory) { super(inventory, resultInventory); @@ -27,6 +31,10 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn this.repairCost = CraftInventoryAnvil.DEFAULT_REPAIR_COST; this.repairCostAmount = CraftInventoryAnvil.DEFAULT_REPAIR_COST_AMOUNT; this.maximumRepairCost = CraftInventoryAnvil.DEFAULT_MAXIMUM_REPAIR_COST; + // Purpur start - Anvil API + this.bypassCost = false; + this.canDoUnsafeEnchants = false; + // Purpur end - Anvil API } @Override @@ -113,4 +121,30 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn consumer.accept(cav); } } + + // Purpur start - Anvil API + @Override + public boolean canBypassCost() { + this.syncWithArbitraryViewValue((cav) -> this.bypassCost = cav.canBypassCost()); + return this.bypassCost; + } + + @Override + public void setBypassCost(boolean bypassCost) { + this.bypassCost = bypassCost; + this.syncViews((cav) -> cav.setBypassCost(bypassCost)); + } + + @Override + public boolean canDoUnsafeEnchants() { + this.syncWithArbitraryViewValue((cav) -> this.canDoUnsafeEnchants = cav.canDoUnsafeEnchants()); + return this.canDoUnsafeEnchants; + } + + @Override + public void setDoUnsafeEnchants(boolean canDoUnsafeEnchants) { + this.canDoUnsafeEnchants = canDoUnsafeEnchants; + this.syncViews((cav) -> cav.setDoUnsafeEnchants(canDoUnsafeEnchants)); + } + // Purpur end - Anvil API } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java index 78975412da0f0c2b802bfce6d30d56b26d8023e2..4ec6a07796023aab2f8f84f131f48108c235c852 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java @@ -658,4 +658,285 @@ public final class CraftItemStack extends ItemStack { } // Paper end - data component API + + // Purpur start + @Override + public String getDisplayName() { + return getItemMeta().getDisplayName(); + } + + @Override + public void setDisplayName(String name) { + ItemMeta itemMeta = getItemMeta(); + itemMeta.setDisplayName(name); + setItemMeta(itemMeta); + } + + @Override + public boolean hasDisplayName() { + return hasItemMeta() && getItemMeta().hasDisplayName(); + } + + @Override + public String getLocalizedName() { + return getItemMeta().getLocalizedName(); + } + + @Override + public void setLocalizedName(String name) { + ItemMeta itemMeta = getItemMeta(); + itemMeta.setLocalizedName(name); + setItemMeta(itemMeta); + } + + @Override + public boolean hasLocalizedName() { + return hasItemMeta() && getItemMeta().hasLocalizedName(); + } + + @Override + public boolean hasLore() { + return hasItemMeta() && getItemMeta().hasLore(); + } + + @Override + public boolean hasEnchant(Enchantment ench) { + return hasItemMeta() && getItemMeta().hasEnchant(ench); + } + + @Override + public int getEnchantLevel(Enchantment ench) { + return getItemMeta().getEnchantLevel(ench); + } + + @Override + public Map getEnchants() { + return getItemMeta().getEnchants(); + } + + @Override + public boolean addEnchant(Enchantment ench, int level, boolean ignoreLevelRestriction) { + ItemMeta itemMeta = getItemMeta(); + boolean result = itemMeta.addEnchant(ench, level, ignoreLevelRestriction); + setItemMeta(itemMeta); + return result; + } + + @Override + public boolean removeEnchant(Enchantment ench) { + ItemMeta itemMeta = getItemMeta(); + boolean result = itemMeta.removeEnchant(ench); + setItemMeta(itemMeta); + return result; + } + + @Override + public boolean hasEnchants() { + return hasItemMeta() && getItemMeta().hasEnchants(); + } + + @Override + public boolean hasConflictingEnchant(Enchantment ench) { + return hasItemMeta() && getItemMeta().hasConflictingEnchant(ench); + } + + @Override + public void setCustomModelData(Integer data) { + ItemMeta itemMeta = getItemMeta(); + itemMeta.setCustomModelData(data); + setItemMeta(itemMeta); + } + + @Override + public int getCustomModelData() { + return getItemMeta().getCustomModelData(); + } + + @Override + public boolean hasCustomModelData() { + return hasItemMeta() && getItemMeta().hasCustomModelData(); + } + + @Override + public boolean hasBlockData() { + return hasItemMeta() && ((org.bukkit.inventory.meta.BlockDataMeta) getItemMeta()).hasBlockData(); + } + + @Override + public org.bukkit.block.data.BlockData getBlockData(Material material) { + return ((org.bukkit.inventory.meta.BlockDataMeta) getItemMeta()).getBlockData(material); + } + + @Override + public void setBlockData(org.bukkit.block.data.BlockData blockData) { + ItemMeta itemMeta = getItemMeta(); + ((org.bukkit.inventory.meta.BlockDataMeta) itemMeta).setBlockData(blockData); + setItemMeta(itemMeta); + } + + @Override + public int getRepairCost() { + return ((org.bukkit.inventory.meta.Repairable) getItemMeta()).getRepairCost(); + } + + @Override + public void setRepairCost(int cost) { + ItemMeta itemMeta = getItemMeta(); + ((org.bukkit.inventory.meta.Repairable) itemMeta).setRepairCost(cost); + setItemMeta(itemMeta); + } + + @Override + public boolean hasRepairCost() { + return hasItemMeta() && ((org.bukkit.inventory.meta.Repairable) getItemMeta()).hasRepairCost(); + } + + @Override + public boolean isUnbreakable() { + return hasItemMeta() && getItemMeta().isUnbreakable(); + } + + @Override + public void setUnbreakable(boolean unbreakable) { + ItemMeta itemMeta = getItemMeta(); + itemMeta.setUnbreakable(unbreakable); + setItemMeta(itemMeta); + } + + @Override + public boolean hasAttributeModifiers() { + return hasItemMeta() && getItemMeta().hasAttributeModifiers(); + } + + @Override + public com.google.common.collect.Multimap getAttributeModifiers() { + return getItemMeta().getAttributeModifiers(); + } + + @Override + public com.google.common.collect.Multimap getAttributeModifiers(org.bukkit.inventory.EquipmentSlot slot) { + return getItemMeta().getAttributeModifiers(slot); + } + + @Override + public java.util.Collection getAttributeModifiers(org.bukkit.attribute.Attribute attribute) { + return getItemMeta().getAttributeModifiers(attribute); + } + + @Override + public boolean addAttributeModifier(org.bukkit.attribute.Attribute attribute, org.bukkit.attribute.AttributeModifier modifier) { + ItemMeta itemMeta = getItemMeta(); + boolean result = itemMeta.addAttributeModifier(attribute, modifier); + setItemMeta(itemMeta); + return result; + } + + @Override + public void setAttributeModifiers(com.google.common.collect.Multimap attributeModifiers) { + ItemMeta itemMeta = getItemMeta(); + itemMeta.setAttributeModifiers(attributeModifiers); + setItemMeta(itemMeta); + } + + @Override + public boolean removeAttributeModifier(org.bukkit.attribute.Attribute attribute) { + ItemMeta itemMeta = getItemMeta(); + boolean result = itemMeta.removeAttributeModifier(attribute); + setItemMeta(itemMeta); + return result; + } + + @Override + public boolean removeAttributeModifier(org.bukkit.inventory.EquipmentSlot slot) { + ItemMeta itemMeta = getItemMeta(); + boolean result = itemMeta.removeAttributeModifier(slot); + setItemMeta(itemMeta); + return result; + } + + @Override + public boolean removeAttributeModifier(org.bukkit.attribute.Attribute attribute, org.bukkit.attribute.AttributeModifier modifier) { + ItemMeta itemMeta = getItemMeta(); + boolean result = itemMeta.removeAttributeModifier(attribute, modifier); + setItemMeta(itemMeta); + return result; + } + + @Override + public boolean hasDamage() { + return hasItemMeta() && ((org.bukkit.inventory.meta.Damageable) getItemMeta()).hasDamage(); + } + + @Override + public int getDamage() { + return ((org.bukkit.inventory.meta.Damageable) getItemMeta()).getDamage(); + } + + @Override + public void setDamage(int damage) { + ItemMeta itemMeta = getItemMeta(); + ((org.bukkit.inventory.meta.Damageable) itemMeta).setDamage(damage); + setItemMeta(itemMeta); + } + + @Override + public void repair() { + repair(1); + } + + @Override + public boolean damage() { + return damage(1); + } + + @Override + public void repair(int amount) { + damage(-amount); + } + + @Override + public boolean damage(int amount) { + return damage(amount, false); + } + + @Override + public boolean damage(int amount, boolean ignoreUnbreaking) { + org.bukkit.inventory.meta.Damageable damageable = (org.bukkit.inventory.meta.Damageable) getItemMeta(); + if (amount > 0) { + int unbreaking = getEnchantLevel(Enchantment.UNBREAKING); + int reduce = 0; + for (int i = 0; unbreaking > 0 && i < amount; ++i) { + if (reduceDamage(java.util.concurrent.ThreadLocalRandom.current(), unbreaking)) { + ++reduce; + } + } + amount -= reduce; + if (amount <= 0) { + return isBroke(damageable.getDamage()); + } + } + int damage = damageable.getDamage() + amount; + damageable.setDamage(damage); + setItemMeta((ItemMeta) damageable); + return isBroke(damage); + } + + private boolean isBroke(int damage) { + if (damage > getType().getMaxDurability()) { + if (getAmount() > 0) { + // ensure it "breaks" + setAmount(0); + } + return true; + } + return false; + } + + private boolean reduceDamage(java.util.Random random, int unbreaking) { + if (getType().isArmor()) { + return random.nextFloat() < 0.6F; + } + return random.nextInt(unbreaking + 1) > 0; + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java index 3592091c6d1371224e82e1f95b003951ad2f8779..4fdc78a9c74b42a8894030221e0452493d68020e 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java @@ -38,6 +38,7 @@ public interface CraftRecipe extends Recipe { stack = Ingredient.of(((RecipeChoice.MaterialChoice) bukkit).getChoices().stream().map((mat) -> CraftItemType.bukkitToMinecraft(mat))); } else if (bukkit instanceof RecipeChoice.ExactChoice) { stack = Ingredient.ofStacks(((RecipeChoice.ExactChoice) bukkit).getChoices().stream().map((mat) -> CraftItemStack.asNMSCopy(mat)).toList()); + stack.predicate = ((RecipeChoice.ExactChoice) bukkit).getPredicate(); // Purpur // Paper start - support "empty" choices - legacy method that spigot might incorrectly call // Their impl of Ingredient.of() will error, ingredients need at least one entry. // Callers running into this exception may have passed an incorrect empty() recipe choice to a non-empty slot or diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java index 7c989318dc7ad89bb0d9143fcaac1e4bba6f5907..143a4d4efcc989ed4a4c73cc304e1978ad8f0699 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java @@ -44,6 +44,6 @@ public class CraftShapelessRecipe extends ShapelessRecipe implements CraftRecipe data.add(this.toNMS(i, true)); } - MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftRecipe.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.ShapelessRecipe(this.getGroup(), CraftRecipe.getCategory(this.getCategory()), CraftItemStack.asNMSCopy(this.getResult()), data))); + MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftRecipe.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.ShapelessRecipe(this.getGroup(), CraftRecipe.getCategory(this.getCategory()), CraftItemStack.asNMSCopy(this.getResult()), data, true))); // Pufferfish } } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java b/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java index f86c95a13dff012de5db3e41ac261e9e8d44d9f3..1db0b790d824e419bb5fb6ab1f3003e120f9763b 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java @@ -75,4 +75,26 @@ public class CraftAnvilView extends CraftInventoryView experience DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "defaultgamemode", "Allows the user to change the default gamemode of the server", PermissionDefault.OP, commands); DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "seed", "Allows the user to view the seed of the world", PermissionDefault.OP, commands); diff --git a/src/main/java/org/purpurmc/purpur/PurpurConfig.java b/src/main/java/org/purpurmc/purpur/PurpurConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..95cd1156766895546ef5574b33a60806bff08fa2 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java @@ -0,0 +1,606 @@ +package org.purpurmc.purpur; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import com.mojang.datafixers.util.Pair; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.food.FoodProperties; +import net.minecraft.world.food.Foods; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockBehaviour; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.purpurmc.purpur.command.PurpurCommand; +import org.purpurmc.purpur.task.TPSBarTask; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +@SuppressWarnings("unused") +public class PurpurConfig { + private static final String HEADER = "This is the main configuration file for Purpur.\n" + + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n" + + "with caution, and make sure you know what each option does before configuring.\n" + + "\n" + + "If you need help with the configuration or have any questions related to Purpur,\n" + + "join us in our Discord guild.\n" + + "\n" + + "Website: https://purpurmc.org \n" + + "Docs: https://purpurmc.org/docs \n"; + private static File CONFIG_FILE; + public static YamlConfiguration config; + + private static Map commands; + + public static int version; + static boolean verbose; + + public static void init(File configFile) { + CONFIG_FILE = configFile; + config = new YamlConfiguration(); + try { + config.load(CONFIG_FILE); + } catch (IOException ignore) { + } catch (InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Could not load purpur.yml, please correct your syntax errors", ex); + throw Throwables.propagate(ex); + } + config.options().header(HEADER); + config.options().copyDefaults(true); + verbose = getBoolean("verbose", false); + + commands = new HashMap<>(); + commands.put("purpur", new PurpurCommand("purpur")); + + version = getInt("config-version", 37); + set("config-version", 37); + + readConfig(PurpurConfig.class, null); + + Block.BLOCK_STATE_REGISTRY.forEach(BlockBehaviour.BlockStateBase::initCache); + } + + protected static void log(String s) { + if (verbose) { + log(Level.INFO, s); + } + } + + protected static void log(Level level, String s) { + Bukkit.getLogger().log(level, s); + } + + public static void registerCommands() { + for (Map.Entry entry : commands.entrySet()) { + MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Purpur", entry.getValue()); + } + } + + static void readConfig(Class clazz, Object instance) { + for (Method method : clazz.getDeclaredMethods()) { + if (Modifier.isPrivate(method.getModifiers())) { + if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) { + try { + method.setAccessible(true); + method.invoke(instance); + } catch (InvocationTargetException ex) { + throw Throwables.propagate(ex.getCause()); + } catch (Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex); + } + } + } + } + + try { + config.save(CONFIG_FILE); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex); + } + } + + private static void set(String path, Object val) { + config.addDefault(path, val); + config.set(path, val); + } + + private static String getString(String path, String def) { + config.addDefault(path, def); + return config.getString(path, config.getString(path)); + } + + private static boolean getBoolean(String path, boolean def) { + config.addDefault(path, def); + return config.getBoolean(path, config.getBoolean(path)); + } + + private static double getDouble(String path, double def) { + config.addDefault(path, def); + return config.getDouble(path, config.getDouble(path)); + } + + private static int getInt(String path, int def) { + config.addDefault(path, def); + return config.getInt(path, config.getInt(path)); + } + + private static List getList(String path, T def) { + config.addDefault(path, def); + return config.getList(path, config.getList(path)); + } + + static Map getMap(String path, Map def) { + if (def != null && config.getConfigurationSection(path) == null) { + config.addDefault(path, def); + return def; + } + return toMap(config.getConfigurationSection(path)); + } + + private static Map toMap(ConfigurationSection section) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + if (section != null) { + for (String key : section.getKeys(false)) { + Object obj = section.get(key); + if (obj != null) { + builder.put(key, obj instanceof ConfigurationSection val ? toMap(val) : obj); + } + } + } + return builder.build(); + } + + public static String cannotRideMob = "You cannot mount that mob"; + public static String afkBroadcastAway = "%s is now AFK"; + public static String afkBroadcastBack = "%s is no longer AFK"; + public static boolean afkBroadcastUseDisplayName = false; + public static String afkTabListPrefix = "[AFK] "; + public static String afkTabListSuffix = ""; + public static String creditsCommandOutput = "%s has been shown the end credits"; + public static String demoCommandOutput = "%s has been shown the demo screen"; + public static String pingCommandOutput = "%s's ping is %sms"; + public static String ramCommandOutput = "Ram Usage: / ()"; + public static String rambarCommandOutput = "Rambar toggled for "; + public static String tpsbarCommandOutput = "Tpsbar toggled for "; + public static String dontRunWithScissors = "Don't run with scissors!"; + public static String uptimeCommandOutput = "Server uptime is "; + public static String unverifiedUsername = "default"; + public static String sleepSkippingNight = "default"; + public static String sleepingPlayersPercent = "default"; + public static String sleepNotPossible = "default"; + private static void messages() { + cannotRideMob = getString("settings.messages.cannot-ride-mob", cannotRideMob); + afkBroadcastAway = getString("settings.messages.afk-broadcast-away", afkBroadcastAway); + afkBroadcastBack = getString("settings.messages.afk-broadcast-back", afkBroadcastBack); + afkBroadcastUseDisplayName = getBoolean("settings.messages.afk-broadcast-use-display-name", afkBroadcastUseDisplayName); + afkTabListPrefix = MiniMessage.miniMessage().serialize(MiniMessage.miniMessage().deserialize(getString("settings.messages.afk-tab-list-prefix", afkTabListPrefix))); + afkTabListSuffix = MiniMessage.miniMessage().serialize(MiniMessage.miniMessage().deserialize(getString("settings.messages.afk-tab-list-suffix", afkTabListSuffix))); + creditsCommandOutput = getString("settings.messages.credits-command-output", creditsCommandOutput); + demoCommandOutput = getString("settings.messages.demo-command-output", demoCommandOutput); + pingCommandOutput = getString("settings.messages.ping-command-output", pingCommandOutput); + ramCommandOutput = getString("settings.messages.ram-command-output", ramCommandOutput); + rambarCommandOutput = getString("settings.messages.rambar-command-output", rambarCommandOutput); + tpsbarCommandOutput = getString("settings.messages.tpsbar-command-output", tpsbarCommandOutput); + dontRunWithScissors = getString("settings.messages.dont-run-with-scissors", dontRunWithScissors); + uptimeCommandOutput = getString("settings.messages.uptime-command-output", uptimeCommandOutput); + unverifiedUsername = getString("settings.messages.unverified-username", unverifiedUsername); + sleepSkippingNight = getString("settings.messages.sleep-skipping-night", sleepSkippingNight); + sleepingPlayersPercent = getString("settings.messages.sleeping-players-percent", sleepingPlayersPercent); + sleepNotPossible = getString("settings.messages.sleep-not-possible", sleepNotPossible); + } + + public static String deathMsgRunWithScissors = " slipped and fell on their shears"; + public static String deathMsgStonecutter = " has sawed themself in half"; + private static void deathMessages() { + deathMsgRunWithScissors = getString("settings.messages.death-message.run-with-scissors", deathMsgRunWithScissors); + deathMsgStonecutter = getString("settings.messages.death-message.stonecutter", deathMsgStonecutter); + } + + public static boolean advancementOnlyBroadcastToAffectedPlayer = false; + public static boolean deathMessageOnlyBroadcastToAffectedPlayer = false; + private static void broadcastSettings() { + if (version < 13) { + boolean oldValue = getBoolean("settings.advancement.only-broadcast-to-affected-player", false); + set("settings.broadcasts.advancement.only-broadcast-to-affected-player", oldValue); + set("settings.advancement.only-broadcast-to-affected-player", null); + } + advancementOnlyBroadcastToAffectedPlayer = getBoolean("settings.broadcasts.advancement.only-broadcast-to-affected-player", advancementOnlyBroadcastToAffectedPlayer); + deathMessageOnlyBroadcastToAffectedPlayer = getBoolean("settings.broadcasts.death.only-broadcast-to-affected-player", deathMessageOnlyBroadcastToAffectedPlayer); + } + + public static String serverModName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); + private static void serverModName() { + serverModName = getString("settings.server-mod-name", serverModName); + } + + public static double laggingThreshold = 19.0D; + private static void tickLoopSettings() { + laggingThreshold = getDouble("settings.lagging-threshold", laggingThreshold); + } + + public static boolean useAlternateKeepAlive = false; + private static void useAlternateKeepAlive() { + useAlternateKeepAlive = getBoolean("settings.use-alternate-keepalive", useAlternateKeepAlive); + } + + public static boolean disableGiveCommandDrops = false; + private static void disableGiveCommandDrops() { + disableGiveCommandDrops = getBoolean("settings.disable-give-dropping", disableGiveCommandDrops); + } + + public static String commandRamBarTitle = "Ram: / ()"; + public static BossBar.Overlay commandRamBarProgressOverlay = BossBar.Overlay.NOTCHED_20; + public static BossBar.Color commandRamBarProgressColorGood = BossBar.Color.GREEN; + public static BossBar.Color commandRamBarProgressColorMedium = BossBar.Color.YELLOW; + public static BossBar.Color commandRamBarProgressColorLow = BossBar.Color.RED; + public static String commandRamBarTextColorGood = ""; + public static String commandRamBarTextColorMedium = ""; + public static String commandRamBarTextColorLow = ""; + public static int commandRamBarTickInterval = 20; + public static String commandTPSBarTitle = "TPS: MSPT: Ping: ms"; + public static BossBar.Overlay commandTPSBarProgressOverlay = BossBar.Overlay.NOTCHED_20; + public static TPSBarTask.FillMode commandTPSBarProgressFillMode = TPSBarTask.FillMode.MSPT; + public static BossBar.Color commandTPSBarProgressColorGood = BossBar.Color.GREEN; + public static BossBar.Color commandTPSBarProgressColorMedium = BossBar.Color.YELLOW; + public static BossBar.Color commandTPSBarProgressColorLow = BossBar.Color.RED; + public static String commandTPSBarTextColorGood = ""; + public static String commandTPSBarTextColorMedium = ""; + public static String commandTPSBarTextColorLow = ""; + public static int commandTPSBarTickInterval = 20; + public static String commandCompassBarTitle = "S \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 W \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 N \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 E \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 S \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 W \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 N \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 E \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 "; + public static BossBar.Overlay commandCompassBarProgressOverlay = BossBar.Overlay.PROGRESS; + public static BossBar.Color commandCompassBarProgressColor = BossBar.Color.BLUE; + public static float commandCompassBarProgressPercent = 1.0F; + public static int commandCompassBarTickInterval = 5; + public static boolean commandGamemodeRequiresPermission = false; + public static boolean hideHiddenPlayersFromEntitySelector = false; + public static String uptimeFormat = ""; + public static String uptimeDay = "%02d day, "; + public static String uptimeDays = "%02d days, "; + public static String uptimeHour = "%02d hour, "; + public static String uptimeHours = "%02d hours, "; + public static String uptimeMinute = "%02d minute, and "; + public static String uptimeMinutes = "%02d minutes, and "; + public static String uptimeSecond = "%02d second"; + public static String uptimeSeconds = "%02d seconds"; + private static void commandSettings() { + commandRamBarTitle = getString("settings.command.rambar.title", commandRamBarTitle); + commandRamBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.rambar.overlay", commandRamBarProgressOverlay.name())); + commandRamBarProgressColorGood = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.good", commandRamBarProgressColorGood.name())); + commandRamBarProgressColorMedium = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.medium", commandRamBarProgressColorMedium.name())); + commandRamBarProgressColorLow = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.low", commandRamBarProgressColorLow.name())); + commandRamBarTextColorGood = getString("settings.command.rambar.text-color.good", commandRamBarTextColorGood); + commandRamBarTextColorMedium = getString("settings.command.rambar.text-color.medium", commandRamBarTextColorMedium); + commandRamBarTextColorLow = getString("settings.command.rambar.text-color.low", commandRamBarTextColorLow); + commandRamBarTickInterval = getInt("settings.command.rambar.tick-interval", commandRamBarTickInterval); + + commandTPSBarTitle = getString("settings.command.tpsbar.title", commandTPSBarTitle); + commandTPSBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.tpsbar.overlay", commandTPSBarProgressOverlay.name())); + commandTPSBarProgressFillMode = TPSBarTask.FillMode.valueOf(getString("settings.command.tpsbar.fill-mode", commandTPSBarProgressFillMode.name())); + commandTPSBarProgressColorGood = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.good", commandTPSBarProgressColorGood.name())); + commandTPSBarProgressColorMedium = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.medium", commandTPSBarProgressColorMedium.name())); + commandTPSBarProgressColorLow = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.low", commandTPSBarProgressColorLow.name())); + commandTPSBarTextColorGood = getString("settings.command.tpsbar.text-color.good", commandTPSBarTextColorGood); + commandTPSBarTextColorMedium = getString("settings.command.tpsbar.text-color.medium", commandTPSBarTextColorMedium); + commandTPSBarTextColorLow = getString("settings.command.tpsbar.text-color.low", commandTPSBarTextColorLow); + commandTPSBarTickInterval = getInt("settings.command.tpsbar.tick-interval", commandTPSBarTickInterval); + + commandCompassBarTitle = getString("settings.command.compass.title", commandCompassBarTitle); + commandCompassBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.compass.overlay", commandCompassBarProgressOverlay.name())); + commandCompassBarProgressColor = BossBar.Color.valueOf(getString("settings.command.compass.progress-color", commandCompassBarProgressColor.name())); + commandCompassBarProgressPercent = (float) getDouble("settings.command.compass.percent", commandCompassBarProgressPercent); + commandCompassBarTickInterval = getInt("settings.command.compass.tick-interval", commandCompassBarTickInterval); + + commandGamemodeRequiresPermission = getBoolean("settings.command.gamemode.requires-specific-permission", commandGamemodeRequiresPermission); + hideHiddenPlayersFromEntitySelector = getBoolean("settings.command.hide-hidden-players-from-entity-selector", hideHiddenPlayersFromEntitySelector); + uptimeFormat = getString("settings.command.uptime.format", uptimeFormat); + uptimeDay = getString("settings.command.uptime.day", uptimeDay); + uptimeDays = getString("settings.command.uptime.days", uptimeDays); + uptimeHour = getString("settings.command.uptime.hour", uptimeHour); + uptimeHours = getString("settings.command.uptime.hours", uptimeHours); + uptimeMinute = getString("settings.command.uptime.minute", uptimeMinute); + uptimeMinutes = getString("settings.command.uptime.minutes", uptimeMinutes); + uptimeSecond = getString("settings.command.uptime.second", uptimeSecond); + uptimeSeconds = getString("settings.command.uptime.seconds", uptimeSeconds); + } + + public static int barrelRows = 3; + public static boolean enderChestSixRows = false; + public static boolean enderChestPermissionRows = false; + public static boolean cryingObsidianValidForPortalFrame = false; + public static int beeInsideBeeHive = 3; + public static boolean anvilCumulativeCost = true; + public static int lightningRodRange = 128; + public static Set grindstoneIgnoredEnchants = new HashSet<>(); + public static boolean grindstoneRemoveAttributes = false; + public static boolean grindstoneRemoveDisplay = false; + public static int caveVinesMaxGrowthAge = 25; + public static int kelpMaxGrowthAge = 25; + public static int twistingVinesMaxGrowthAge = 25; + public static int weepingVinesMaxGrowthAge = 25; + public static boolean magmaBlockReverseBubbleColumnFlow = false; + public static boolean soulSandBlockReverseBubbleColumnFlow = false; + private static void blockSettings() { + if (version < 3) { + boolean oldValue = getBoolean("settings.barrel.packed-barrels", true); + set("settings.blocks.barrel.six-rows", oldValue); + set("settings.packed-barrels", null); + oldValue = getBoolean("settings.large-ender-chests", true); + set("settings.blocks.ender_chest.six-rows", oldValue); + set("settings.large-ender-chests", null); + } + if (version < 20) { + boolean oldValue = getBoolean("settings.blocks.barrel.six-rows", false); + set("settings.blocks.barrel.rows", oldValue ? 6 : 3); + set("settings.blocks.barrel.six-rows", null); + } + barrelRows = getInt("settings.blocks.barrel.rows", barrelRows); + if (barrelRows < 1 || barrelRows > 6) { + Bukkit.getLogger().severe("settings.blocks.barrel.rows must be 1-6, resetting to default"); + barrelRows = 3; + } + org.bukkit.event.inventory.InventoryType.BARREL.setDefaultSize(switch (barrelRows) { + case 6 -> 54; + case 5 -> 45; + case 4 -> 36; + case 2 -> 18; + case 1 -> 9; + default -> 27; + }); + enderChestSixRows = getBoolean("settings.blocks.ender_chest.six-rows", enderChestSixRows); + org.bukkit.event.inventory.InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27); + enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); + cryingObsidianValidForPortalFrame = getBoolean("settings.blocks.crying_obsidian.valid-for-portal-frame", cryingObsidianValidForPortalFrame); + beeInsideBeeHive = getInt("settings.blocks.beehive.max-bees-inside", beeInsideBeeHive); + anvilCumulativeCost = getBoolean("settings.blocks.anvil.cumulative-cost", anvilCumulativeCost); + lightningRodRange = getInt("settings.blocks.lightning_rod.range", lightningRodRange); + ArrayList defaultCurses = new ArrayList<>(){{ + add("minecraft:binding_curse"); + add("minecraft:vanishing_curse"); + }}; + if (version < 24 && !getBoolean("settings.blocks.grindstone.ignore-curses", true)) { + defaultCurses.clear(); + } + getList("settings.blocks.grindstone.ignored-enchants", defaultCurses).forEach(key -> { + Registry registry = MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT); + Enchantment enchantment = registry.getValue(ResourceLocation.parse(key.toString())); + if (enchantment == null) return; + grindstoneIgnoredEnchants.add(enchantment); + }); + grindstoneRemoveAttributes = getBoolean("settings.blocks.grindstone.remove-attributes", grindstoneRemoveAttributes); + grindstoneRemoveDisplay = getBoolean("settings.blocks.grindstone.remove-name-and-lore", grindstoneRemoveDisplay); + caveVinesMaxGrowthAge = getInt("settings.blocks.cave_vines.max-growth-age", caveVinesMaxGrowthAge); + if (caveVinesMaxGrowthAge > 25) { + caveVinesMaxGrowthAge = 25; + log(Level.WARNING, "blocks.cave_vines.max-growth-age is set to above maximum allowed value of 25"); + log(Level.WARNING, "Using value of 25 to prevent issues"); + } + kelpMaxGrowthAge = getInt("settings.blocks.kelp.max-growth-age", kelpMaxGrowthAge); + if (kelpMaxGrowthAge > 25) { + kelpMaxGrowthAge = 25; + log(Level.WARNING, "blocks.kelp.max-growth-age is set to above maximum allowed value of 25"); + log(Level.WARNING, "Using value of 25 to prevent issues"); + } + twistingVinesMaxGrowthAge = getInt("settings.blocks.twisting_vines.max-growth-age", twistingVinesMaxGrowthAge); + if (twistingVinesMaxGrowthAge > 25) { + twistingVinesMaxGrowthAge = 25; + log(Level.WARNING, "blocks.twisting_vines.max-growth-age is set to above maximum allowed value of 25"); + log(Level.WARNING, "Using value of 25 to prevent issues"); + } + weepingVinesMaxGrowthAge = getInt("settings.blocks.weeping_vines.max-growth-age", weepingVinesMaxGrowthAge); + if (weepingVinesMaxGrowthAge > 25) { + weepingVinesMaxGrowthAge = 25; + log(Level.WARNING, "blocks.weeping_vines.max-growth-age is set to above maximum allowed value of 25"); + log(Level.WARNING, "Using value of 25 to prevent issues"); + } + magmaBlockReverseBubbleColumnFlow = getBoolean("settings.blocks.magma-block.reverse-bubble-column-flow", magmaBlockReverseBubbleColumnFlow); + soulSandBlockReverseBubbleColumnFlow = getBoolean("settings.blocks.soul-sand.reverse-bubble-column-flow", soulSandBlockReverseBubbleColumnFlow); + } + + public static boolean allowInapplicableEnchants = false; + public static boolean allowIncompatibleEnchants = false; + public static boolean allowHigherEnchantsLevels = false; + public static boolean allowUnsafeEnchantCommand = false; + public static boolean replaceIncompatibleEnchants = false; + public static boolean clampEnchantLevels = true; + private static void enchantmentSettings() { + if (version < 30) { + boolean oldValue = getBoolean("settings.enchantment.allow-unsafe-enchants", false); + set("settings.enchantment.anvil.allow-unsafe-enchants", oldValue); + set("settings.enchantment.anvil.allow-inapplicable-enchants", true); + set("settings.enchantment.anvil.allow-incompatible-enchants", true); + set("settings.enchantment.anvil.allow-higher-enchants-levels", true); + set("settings.enchantment.allow-unsafe-enchants", null); + } + if (version < 37) { + boolean allowUnsafeEnchants = getBoolean("settings.enchantment.anvil.allow-unsafe-enchants", false); + if (!allowUnsafeEnchants) { + set("settings.enchantment.anvil.allow-inapplicable-enchants", false); + set("settings.enchantment.anvil.allow-incompatible-enchants", false); + set("settings.enchantment.anvil.allow-higher-enchants-levels", false); + } + set("settings.enchantment.anvil.allow-unsafe-enchants", null); + } + allowInapplicableEnchants = getBoolean("settings.enchantment.anvil.allow-inapplicable-enchants", allowInapplicableEnchants); + allowIncompatibleEnchants = getBoolean("settings.enchantment.anvil.allow-incompatible-enchants", allowIncompatibleEnchants); + allowHigherEnchantsLevels = getBoolean("settings.enchantment.anvil.allow-higher-enchants-levels", allowHigherEnchantsLevels); + allowUnsafeEnchantCommand = getBoolean("settings.enchantment.allow-unsafe-enchant-command", allowUnsafeEnchantCommand); + replaceIncompatibleEnchants = getBoolean("settings.enchantment.anvil.replace-incompatible-enchants", replaceIncompatibleEnchants); + clampEnchantLevels = getBoolean("settings.enchantment.clamp-levels", clampEnchantLevels); + } + + public static boolean endermanShortHeight = false; + private static void entitySettings() { + endermanShortHeight = getBoolean("settings.entity.enderman.short-height", endermanShortHeight); + if (endermanShortHeight) EntityType.ENDERMAN.setDimensions(EntityDimensions.scalable(0.6F, 1.9F)); + } + + public static boolean allowWaterPlacementInTheEnd = true; + private static void allowWaterPlacementInEnd() { + allowWaterPlacementInTheEnd = getBoolean("settings.allow-water-placement-in-the-end", allowWaterPlacementInTheEnd); + } + + public static boolean beeCountPayload = false; + private static void beeCountPayload() { + beeCountPayload = getBoolean("settings.bee-count-payload", beeCountPayload); + } + + public static boolean loggerSuppressInitLegacyMaterialError = false; + public static boolean loggerSuppressIgnoredAdvancementWarnings = false; + public static boolean loggerSuppressUnrecognizedRecipeErrors = false; + public static boolean loggerSuppressSetBlockFarChunk = false; + public static boolean loggerSuppressLibraryLoader = false; + private static void loggerSettings() { + loggerSuppressInitLegacyMaterialError = getBoolean("settings.logger.suppress-init-legacy-material-errors", loggerSuppressInitLegacyMaterialError); + loggerSuppressIgnoredAdvancementWarnings = getBoolean("settings.logger.suppress-ignored-advancement-warnings", loggerSuppressIgnoredAdvancementWarnings); + loggerSuppressUnrecognizedRecipeErrors = getBoolean("settings.logger.suppress-unrecognized-recipe-errors", loggerSuppressUnrecognizedRecipeErrors); + loggerSuppressSetBlockFarChunk = getBoolean("settings.logger.suppress-setblock-in-far-chunk-errors", loggerSuppressSetBlockFarChunk); + loggerSuppressLibraryLoader = getBoolean("settings.logger.suppress-library-loader", loggerSuppressLibraryLoader); + org.bukkit.plugin.java.JavaPluginLoader.SuppressLibraryLoaderLogger = loggerSuppressLibraryLoader; + } + + public static boolean tpsCatchup = true; + private static void tpsCatchup() { + tpsCatchup = getBoolean("settings.tps-catchup", tpsCatchup); + } + + public static boolean useUPnP = false; + public static boolean maxJoinsPerSecond = false; + public static boolean kickForOutOfOrderChat = true; + private static void networkSettings() { + useUPnP = getBoolean("settings.network.upnp-port-forwarding", useUPnP); + maxJoinsPerSecond = getBoolean("settings.network.max-joins-per-second", maxJoinsPerSecond); + kickForOutOfOrderChat = getBoolean("settings.network.kick-for-out-of-order-chat", kickForOutOfOrderChat); + } + + public static java.util.regex.Pattern usernameValidCharactersPattern; + private static void usernameValidationSettings() { + String defaultPattern = "^[a-zA-Z0-9_.]*$"; + String setPattern = getString("settings.username-valid-characters", defaultPattern); + usernameValidCharactersPattern = java.util.regex.Pattern.compile(setPattern == null || setPattern.isBlank() ? defaultPattern : setPattern); + } + + public static boolean fixProjectileLootingTransfer = false; + private static void fixProjectileLootingTransfer() { + fixProjectileLootingTransfer = getBoolean("settings.fix-projectile-looting-transfer", fixProjectileLootingTransfer); + } + + public static boolean clampAttributes = true; + private static void clampAttributes() { + clampAttributes = getBoolean("settings.clamp-attributes", clampAttributes); + } + + public static boolean limitArmor = true; + private static void limitArmor() { + limitArmor = getBoolean("settings.limit-armor", limitArmor); + } + + private static void blastResistanceSettings() { + getMap("settings.blast-resistance-overrides", Collections.emptyMap()).forEach((blockId, value) -> { + Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId)); + if (block == Blocks.AIR) { + log(Level.SEVERE, "Invalid block for `settings.blast-resistance-overrides`: " + blockId); + return; + } + if (!(value instanceof Number blastResistance)) { + log(Level.SEVERE, "Invalid blast resistance for `settings.blast-resistance-overrides." + blockId + "`: " + value); + return; + } + block.explosionResistance = blastResistance.floatValue(); + }); + } + private static void blockFallMultiplierSettings() { + getMap("settings.block-fall-multipliers", Map.ofEntries( + Map.entry("minecraft:hay_block", Map.of("damage", 0.2F)), + Map.entry("minecraft:white_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:light_gray_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:gray_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:black_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:brown_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:pink_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:red_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:orange_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:yellow_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:green_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:lime_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:cyan_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:light_blue_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:blue_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:purple_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:magenta_bed", Map.of("distance", 0.5F)) + )).forEach((blockId, value) -> { + Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId)); + if (block == Blocks.AIR) { + log(Level.SEVERE, "Invalid block for `settings.block-fall-multipliers`: " + blockId); + return; + } + if (!(value instanceof Map map)) { + log(Level.SEVERE, "Invalid fall multiplier for `settings.block-fall-multipliers." + blockId + "`: " + value + + ", expected a map with keys `damage` and `distance` to floats."); + return; + } + Object rawFallDamageMultiplier = map.get("damage"); + if (rawFallDamageMultiplier == null) rawFallDamageMultiplier = 1F; + if (!(rawFallDamageMultiplier instanceof Number fallDamageMultiplier)) { + log(Level.SEVERE, "Invalid multiplier for `settings.block-fall-multipliers." + blockId + ".damage`: " + map.get("damage")); + return; + } + Object rawFallDistanceMultiplier = map.get("distance"); + if (rawFallDistanceMultiplier == null) rawFallDistanceMultiplier = 1F; + if (!(rawFallDistanceMultiplier instanceof Number fallDistanceMultiplier)) { + log(Level.SEVERE, "Invalid multiplier for `settings.block-fall-multipliers." + blockId + ".distance`: " + map.get("distance")); + return; + } + block.fallDamageMultiplier = fallDamageMultiplier.floatValue(); + block.fallDistanceMultiplier = fallDistanceMultiplier.floatValue(); + }); + } + + public static boolean playerDeathsAlwaysShowItem = false; + private static void playerDeathsAlwaysShowItem() { + playerDeathsAlwaysShowItem = getBoolean("settings.player-deaths-always-show-item", playerDeathsAlwaysShowItem); + } + + public static boolean registerMinecraftDebugCommands = false; + private static void registerMinecraftDebugCommands() { + registerMinecraftDebugCommands = getBoolean("settings.register-minecraft-debug-commands", registerMinecraftDebugCommands); + } + + public static List startupCommands = new ArrayList<>(); + private static void startupCommands() { + startupCommands.clear(); + getList("settings.startup-commands", new ArrayList()).forEach(line -> { + String command = line.toString(); + if (command.startsWith("/")) { + command = command.substring(1); + } + startupCommands.add(command); + }); + } +} diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..57ddb4e6a464567cc67e09ce06218cc25af4ddf9 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java @@ -0,0 +1,3438 @@ +package org.purpurmc.purpur; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.monster.Shulker; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.properties.Tilt; +import org.purpurmc.purpur.tool.Flattenable; +import org.purpurmc.purpur.tool.Strippable; +import org.purpurmc.purpur.tool.Tillable; +import org.purpurmc.purpur.tool.Waxable; +import org.purpurmc.purpur.tool.Weatherable; +import org.apache.commons.lang.BooleanUtils; +import org.bukkit.ChatColor; +import org.bukkit.World; +import org.bukkit.configuration.ConfigurationSection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.logging.Level; +import static org.purpurmc.purpur.PurpurConfig.log; + +@SuppressWarnings("unused") +public class PurpurWorldConfig { + + private final String worldName; + private final World.Environment environment; + + public PurpurWorldConfig(String worldName, World.Environment environment) { + this.worldName = worldName; + this.environment = environment; + init(); + } + + public void init() { + log("-------- World Settings For [" + worldName + "] --------"); + PurpurConfig.readConfig(PurpurWorldConfig.class, this); + } + + private void set(String path, Object val) { + PurpurConfig.config.addDefault("world-settings.default." + path, val); + PurpurConfig.config.set("world-settings.default." + path, val); + if (PurpurConfig.config.get("world-settings." + worldName + "." + path) != null) { + PurpurConfig.config.addDefault("world-settings." + worldName + "." + path, val); + PurpurConfig.config.set("world-settings." + worldName + "." + path, val); + } + } + + private ConfigurationSection getConfigurationSection(String path) { + ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + "." + path); + return section != null ? section : PurpurConfig.config.getConfigurationSection("world-settings.default." + path); + } + + private String getString(String path, String def) { + PurpurConfig.config.addDefault("world-settings.default." + path, def); + return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path)); + } + + private boolean getBoolean(String path, boolean def) { + PurpurConfig.config.addDefault("world-settings.default." + path, def); + return PurpurConfig.config.getBoolean("world-settings." + worldName + "." + path, PurpurConfig.config.getBoolean("world-settings.default." + path)); + } + + private boolean getBoolean(String path, Predicate predicate) { + String val = getString(path, "default").toLowerCase(); + Boolean bool = BooleanUtils.toBooleanObject(val, "true", "false", "default"); + return predicate.test(bool); + } + + private double getDouble(String path, double def) { + PurpurConfig.config.addDefault("world-settings.default." + path, def); + return PurpurConfig.config.getDouble("world-settings." + worldName + "." + path, PurpurConfig.config.getDouble("world-settings.default." + path)); + } + + private int getInt(String path, int def) { + PurpurConfig.config.addDefault("world-settings.default." + path, def); + return PurpurConfig.config.getInt("world-settings." + worldName + "." + path, PurpurConfig.config.getInt("world-settings.default." + path)); + } + + private List getList(String path, T def) { + PurpurConfig.config.addDefault("world-settings.default." + path, def); + return PurpurConfig.config.getList("world-settings." + worldName + "." + path, PurpurConfig.config.getList("world-settings.default." + path)); + } + + private Map getMap(String path, Map def) { + final Map fallback = PurpurConfig.getMap("world-settings.default." + path, def); + final Map value = PurpurConfig.getMap("world-settings." + worldName + "." + path, null); + return value.isEmpty() ? fallback : value; + } + + public float armorstandStepHeight = 0.0F; + public boolean armorstandSetNameVisible = false; + public boolean armorstandFixNametags = false; + public boolean armorstandMovement = true; + public boolean armorstandWaterMovement = true; + public boolean armorstandWaterFence = true; + public boolean armorstandPlaceWithArms = false; + private void armorstandSettings() { + armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight); + armorstandSetNameVisible = getBoolean("gameplay-mechanics.armorstand.set-name-visible-when-placing-with-custom-name", armorstandSetNameVisible); + armorstandFixNametags = getBoolean("gameplay-mechanics.armorstand.fix-nametags", armorstandFixNametags); + armorstandMovement = getBoolean("gameplay-mechanics.armorstand.can-movement-tick", armorstandMovement); + armorstandWaterMovement = getBoolean("gameplay-mechanics.armorstand.can-move-in-water", armorstandWaterMovement); + armorstandWaterFence = getBoolean("gameplay-mechanics.armorstand.can-move-in-water-over-fence", armorstandWaterFence); + armorstandPlaceWithArms = getBoolean("gameplay-mechanics.armorstand.place-with-arms-visible", armorstandPlaceWithArms); + } + + public boolean arrowMovementResetsDespawnCounter = true; + private void arrowSettings() { + arrowMovementResetsDespawnCounter = getBoolean("gameplay-mechanics.arrow.movement-resets-despawn-counter", arrowMovementResetsDespawnCounter); + } + + public boolean useBetterMending = false; + public boolean alwaysTameInCreative = false; + public boolean boatEjectPlayersOnLand = false; + public boolean boatsDoFallDamage = false; + public boolean disableDropsOnCrammingDeath = false; + public boolean milkCuresBadOmen = true; + public double tridentLoyaltyVoidReturnHeight = 0.0D; + public boolean entitiesCanUsePortals = true; + public int raidCooldownSeconds = 0; + public int animalBreedingCooldownSeconds = 0; + public boolean persistentDroppableEntityDisplayNames = true; + public boolean entitiesPickUpLootBypassMobGriefing = false; + public boolean fireballsBypassMobGriefing = false; + public boolean projectilesBypassMobGriefing = false; + public boolean noteBlockIgnoreAbove = false; + public boolean imposeTeleportRestrictionsOnGateways = false; + public boolean imposeTeleportRestrictionsOnNetherPortals = false; + public boolean imposeTeleportRestrictionsOnEndPortals = false; + public boolean tickFluids = true; + public double mobsBlindnessMultiplier = 1; + public boolean mobsIgnoreRails = false; + public boolean rainStopsAfterSleep = true; + public boolean thunderStopsAfterSleep = true; + public boolean persistentTileEntityLore = false; + public boolean persistentTileEntityDisplayName = true; + public int mobLastHurtByPlayerTime = 100; + public boolean milkClearsBeneficialEffects = true; + public boolean disableOxidationProximityPenalty = false; + private void miscGameplayMechanicsSettings() { + useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending); + alwaysTameInCreative = getBoolean("gameplay-mechanics.always-tame-in-creative", alwaysTameInCreative); + boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); + boatsDoFallDamage = getBoolean("gameplay-mechanics.boat.do-fall-damage", boatsDoFallDamage); + disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); + milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); + tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); + entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); + raidCooldownSeconds = getInt("gameplay-mechanics.raid-cooldown-seconds", raidCooldownSeconds); + animalBreedingCooldownSeconds = getInt("gameplay-mechanics.animal-breeding-cooldown-seconds", animalBreedingCooldownSeconds); + persistentDroppableEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-droppable-entity-display-names", persistentDroppableEntityDisplayNames); + entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); + fireballsBypassMobGriefing = getBoolean("gameplay-mechanics.fireballs-bypass-mob-griefing", fireballsBypassMobGriefing); + projectilesBypassMobGriefing = getBoolean("gameplay-mechanics.projectiles-bypass-mob-griefing", projectilesBypassMobGriefing); + noteBlockIgnoreAbove = getBoolean("gameplay-mechanics.note-block-ignore-above", noteBlockIgnoreAbove); + imposeTeleportRestrictionsOnGateways = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-gateways", imposeTeleportRestrictionsOnGateways); + imposeTeleportRestrictionsOnNetherPortals = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-nether-portals", imposeTeleportRestrictionsOnNetherPortals); + imposeTeleportRestrictionsOnEndPortals = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-end-portals", imposeTeleportRestrictionsOnEndPortals); + tickFluids = getBoolean("gameplay-mechanics.tick-fluids", tickFluids); + mobsBlindnessMultiplier = getDouble("gameplay-mechanics.entity-blindness-multiplier", mobsBlindnessMultiplier); + mobsIgnoreRails = getBoolean("gameplay-mechanics.mobs-ignore-rails", mobsIgnoreRails); + rainStopsAfterSleep = getBoolean("gameplay-mechanics.rain-stops-after-sleep", rainStopsAfterSleep); + thunderStopsAfterSleep = getBoolean("gameplay-mechanics.thunder-stops-after-sleep", thunderStopsAfterSleep); + if (PurpurConfig.version < 35) { + boolean oldVal = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityLore); + set("gameplay-mechanics.persistent-tileentity-display-names-and-lore", null); + set("gameplay-mechanics.persistent-tileentity-lore", oldVal); + set("gameplay-mechanics.persistent-tileentity-display-name", !oldVal); + } + persistentTileEntityLore = getBoolean("gameplay-mechanics.persistent-tileentity-lore", persistentTileEntityLore); + persistentTileEntityDisplayName = getBoolean("gameplay-mechanics.persistent-tileentity-display-name", persistentTileEntityDisplayName); + mobLastHurtByPlayerTime = getInt("gameplay-mechanics.mob-last-hurt-by-player-time", mobLastHurtByPlayerTime); + milkClearsBeneficialEffects = getBoolean("gameplay-mechanics.milk-clears-beneficial-effects", milkClearsBeneficialEffects); + disableOxidationProximityPenalty = getBoolean("gameplay-mechanics.disable-oxidation-proximity-penalty", disableOxidationProximityPenalty); + } + + public int daytimeTicks = 12000; + public int nighttimeTicks = 12000; + private void daytimeCycleSettings() { + daytimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.daytime", daytimeTicks); + nighttimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.nighttime", nighttimeTicks); + } + + public int drowningAirTicks = 300; + public int drowningDamageInterval = 20; + public double damageFromDrowning = 2.0F; + private void drowningSettings() { + drowningAirTicks = getInt("gameplay-mechanics.drowning.air-ticks", drowningAirTicks); + drowningDamageInterval = getInt("gameplay-mechanics.drowning.ticks-per-damage", drowningDamageInterval); + damageFromDrowning = getDouble("gameplay-mechanics.drowning.damage-from-drowning", damageFromDrowning); + } + + public int elytraDamagePerSecond = 1; + public double elytraDamageMultiplyBySpeed = 0; + public int elytraDamagePerFireworkBoost = 0; + public int elytraDamagePerTridentBoost = 0; + public boolean elytraKineticDamage = true; + private void elytraSettings() { + elytraDamagePerSecond = getInt("gameplay-mechanics.elytra.damage-per-second", elytraDamagePerSecond); + elytraDamageMultiplyBySpeed = getDouble("gameplay-mechanics.elytra.damage-multiplied-by-speed", elytraDamageMultiplyBySpeed); + elytraDamagePerFireworkBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.firework", elytraDamagePerFireworkBoost); + elytraDamagePerTridentBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.trident", elytraDamagePerTridentBoost); + elytraKineticDamage = getBoolean("gameplay-mechanics.elytra.kinetic-damage", elytraKineticDamage); + } + + public int entityLifeSpan = 0; + public float entityLeftHandedChance = 0.05f; + public boolean entitySharedRandom = true; + private void entitySettings() { + entityLifeSpan = getInt("gameplay-mechanics.entity-lifespan", entityLifeSpan); + entityLeftHandedChance = (float) getDouble("gameplay-mechanics.entity-left-handed-chance", entityLeftHandedChance); + entitySharedRandom = getBoolean("settings.entity.shared-random", entitySharedRandom); + } + + public boolean infinityWorksWithoutArrows = false; + private void infinityArrowsSettings() { + infinityWorksWithoutArrows = getBoolean("gameplay-mechanics.infinity-bow.works-without-arrows", infinityWorksWithoutArrows); + } + + public boolean explosionClampRadius = true; + private void explosionSettings() { + explosionClampRadius = getBoolean("gameplay-mechanics.clamp-explosion-radius", explosionClampRadius); + } + + public List itemImmuneToCactus = new ArrayList<>(); + public List itemImmuneToExplosion = new ArrayList<>(); + public List itemImmuneToFire = new ArrayList<>(); + public List itemImmuneToLightning = new ArrayList<>(); + public boolean dontRunWithScissors = false; + public boolean ignoreScissorsInWater = false; + public boolean ignoreScissorsInLava = false; + public double scissorsRunningDamage = 1D; + public float enderPearlDamage = 5.0F; + public int enderPearlCooldown = 20; + public int enderPearlCooldownCreative = 20; + public float enderPearlEndermiteChance = 0.05F; + public int glowBerriesEatGlowDuration = 0; + public boolean shulkerBoxItemDropContentsWhenDestroyed = true; + public boolean compassItemShowsBossBar = false; + public boolean snowballExtinguishesFire = false; + public boolean snowballExtinguishesCandles = false; + public boolean snowballExtinguishesCampfires = false; + private void itemSettings() { + itemImmuneToCactus.clear(); + getList("gameplay-mechanics.item.immune.cactus", new ArrayList<>()).forEach(key -> { + if (key.toString().equals("*")) { + BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToCactus.add(item)); + return; + } + Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(key.toString())); + if (item != Items.AIR) itemImmuneToCactus.add(item); + }); + itemImmuneToExplosion.clear(); + getList("gameplay-mechanics.item.immune.explosion", new ArrayList<>()).forEach(key -> { + if (key.toString().equals("*")) { + BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToExplosion.add(item)); + return; + } + Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(key.toString())); + if (item != Items.AIR) itemImmuneToExplosion.add(item); + }); + itemImmuneToFire.clear(); + getList("gameplay-mechanics.item.immune.fire", new ArrayList<>()).forEach(key -> { + if (key.toString().equals("*")) { + BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToFire.add(item)); + return; + } + Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(key.toString())); + if (item != Items.AIR) itemImmuneToFire.add(item); + }); + itemImmuneToLightning.clear(); + getList("gameplay-mechanics.item.immune.lightning", new ArrayList<>()).forEach(key -> { + if (key.toString().equals("*")) { + BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToLightning.add(item)); + return; + } + Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(key.toString())); + if (item != Items.AIR) itemImmuneToLightning.add(item); + }); + dontRunWithScissors = getBoolean("gameplay-mechanics.item.shears.damage-if-sprinting", dontRunWithScissors); + ignoreScissorsInWater = getBoolean("gameplay-mechanics.item.shears.ignore-in-water", ignoreScissorsInWater); + ignoreScissorsInLava = getBoolean("gameplay-mechanics.item.shears.ignore-in-lava", ignoreScissorsInLava); + scissorsRunningDamage = getDouble("gameplay-mechanics.item.shears.sprinting-damage", scissorsRunningDamage); + enderPearlDamage = (float) getDouble("gameplay-mechanics.item.ender-pearl.damage", enderPearlDamage); + enderPearlCooldown = getInt("gameplay-mechanics.item.ender-pearl.cooldown", enderPearlCooldown); + enderPearlCooldownCreative = getInt("gameplay-mechanics.item.ender-pearl.creative-cooldown", enderPearlCooldownCreative); + enderPearlEndermiteChance = (float) getDouble("gameplay-mechanics.item.ender-pearl.endermite-spawn-chance", enderPearlEndermiteChance); + glowBerriesEatGlowDuration = getInt("gameplay-mechanics.item.glow_berries.eat-glow-duration", glowBerriesEatGlowDuration); + shulkerBoxItemDropContentsWhenDestroyed = getBoolean("gameplay-mechanics.item.shulker_box.drop-contents-when-destroyed", shulkerBoxItemDropContentsWhenDestroyed); + compassItemShowsBossBar = getBoolean("gameplay-mechanics.item.compass.holding-shows-bossbar", compassItemShowsBossBar); + snowballExtinguishesFire = getBoolean("gameplay-mechanics.item.snowball.extinguish.fire", snowballExtinguishesFire); + snowballExtinguishesCandles = getBoolean("gameplay-mechanics.item.snowball.extinguish.candles", snowballExtinguishesCandles); + snowballExtinguishesCampfires = getBoolean("gameplay-mechanics.item.snowball.extinguish.campfires", snowballExtinguishesCampfires); + } + + public double minecartMaxSpeed = 0.4D; + public boolean minecartPlaceAnywhere = false; + public boolean minecartControllable = false; + public float minecartControllableStepHeight = 1.0F; + public double minecartControllableHopBoost = 0.5D; + public boolean minecartControllableFallDamage = true; + public double minecartControllableBaseSpeed = 0.1D; + public Map minecartControllableBlockSpeeds = new HashMap<>(); + public double poweredRailBoostModifier = 0.06; + private void minecartSettings() { + if (PurpurConfig.version < 12) { + boolean oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.place-anywhere", minecartPlaceAnywhere); + set("gameplay-mechanics.controllable-minecarts.place-anywhere", null); + set("gameplay-mechanics.minecart.place-anywhere", oldBool); + oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.enabled", minecartControllable); + set("gameplay-mechanics.controllable-minecarts.enabled", null); + set("gameplay-mechanics.minecart.controllable.enabled", oldBool); + double oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.step-height", minecartControllableStepHeight); + set("gameplay-mechanics.controllable-minecarts.step-height", null); + set("gameplay-mechanics.minecart.controllable.step-height", oldDouble); + oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.hop-boost", minecartControllableHopBoost); + set("gameplay-mechanics.controllable-minecarts.hop-boost", null); + set("gameplay-mechanics.minecart.controllable.hop-boost", oldDouble); + oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.fall-damage", minecartControllableFallDamage); + set("gameplay-mechanics.controllable-minecarts.fall-damage", null); + set("gameplay-mechanics.minecart.controllable.fall-damage", oldBool); + oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.base-speed", minecartControllableBaseSpeed); + set("gameplay-mechanics.controllable-minecarts.base-speed", null); + set("gameplay-mechanics.minecart.controllable.base-speed", oldDouble); + ConfigurationSection section = getConfigurationSection("gameplay-mechanics.controllable-minecarts.block-speed"); + if (section != null) { + for (String key : section.getKeys(false)) { + if ("grass-block".equals(key)) key = "grass_block"; // oopsie + oldDouble = section.getDouble(key, minecartControllableBaseSpeed); + set("gameplay-mechanics.controllable-minecarts.block-speed." + key, null); + set("gameplay-mechanics.minecart.controllable.block-speed." + key, oldDouble); + } + set("gameplay-mechanics.controllable-minecarts.block-speed", null); + } + set("gameplay-mechanics.controllable-minecarts", null); + } + + minecartMaxSpeed = getDouble("gameplay-mechanics.minecart.max-speed", minecartMaxSpeed); + minecartPlaceAnywhere = getBoolean("gameplay-mechanics.minecart.place-anywhere", minecartPlaceAnywhere); + minecartControllable = getBoolean("gameplay-mechanics.minecart.controllable.enabled", minecartControllable); + minecartControllableStepHeight = (float) getDouble("gameplay-mechanics.minecart.controllable.step-height", minecartControllableStepHeight); + minecartControllableHopBoost = getDouble("gameplay-mechanics.minecart.controllable.hop-boost", minecartControllableHopBoost); + minecartControllableFallDamage = getBoolean("gameplay-mechanics.minecart.controllable.fall-damage", minecartControllableFallDamage); + minecartControllableBaseSpeed = getDouble("gameplay-mechanics.minecart.controllable.base-speed", minecartControllableBaseSpeed); + ConfigurationSection section = getConfigurationSection("gameplay-mechanics.minecart.controllable.block-speed"); + if (section != null) { + for (String key : section.getKeys(false)) { + Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(key)); + if (block != Blocks.AIR) { + minecartControllableBlockSpeeds.put(block, section.getDouble(key, minecartControllableBaseSpeed)); + } + } + } else { + set("gameplay-mechanics.minecart.controllable.block-speed.grass_block", 0.3D); + set("gameplay-mechanics.minecart.controllable.block-speed.stone", 0.5D); + } + poweredRailBoostModifier = getDouble("gameplay-mechanics.minecart.powered-rail.boost-modifier", poweredRailBoostModifier); + } + + public float entityHealthRegenAmount = 1.0F; + public float entityMinimalHealthPoison = 1.0F; + public float entityPoisonDegenerationAmount = 1.0F; + public float entityWitherDegenerationAmount = 1.0F; + public float humanHungerExhaustionAmount = 0.005F; + public float humanSaturationRegenAmount = 1.0F; + private void mobEffectSettings() { + entityHealthRegenAmount = (float) getDouble("gameplay-mechanics.mob-effects.health-regen-amount", entityHealthRegenAmount); + entityMinimalHealthPoison = (float) getDouble("gameplay-mechanics.mob-effects.minimal-health-poison-amount", entityMinimalHealthPoison); + entityPoisonDegenerationAmount = (float) getDouble("gameplay-mechanics.mob-effects.poison-degeneration-amount", entityPoisonDegenerationAmount); + entityWitherDegenerationAmount = (float) getDouble("gameplay-mechanics.mob-effects.wither-degeneration-amount", entityWitherDegenerationAmount); + humanHungerExhaustionAmount = (float) getDouble("gameplay-mechanics.mob-effects.hunger-exhaustion-amount", humanHungerExhaustionAmount); + humanSaturationRegenAmount = (float) getDouble("gameplay-mechanics.mob-effects.saturation-regen-amount", humanSaturationRegenAmount); + } + + public boolean catSpawning; + public boolean patrolSpawning; + public boolean phantomSpawning; + public boolean villagerTraderSpawning; + public boolean villageSiegeSpawning; + public boolean mobSpawningIgnoreCreativePlayers = false; + private void mobSpawnerSettings() { + // values of "default" or null will default to true only if the world environment is normal (aka overworld) + Predicate predicate = (bool) -> (bool != null && bool) || (bool == null && environment == World.Environment.NORMAL); + catSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-cats", predicate); + patrolSpawning = getBoolean("gameplay-mechanics.mob-spawning.raid-patrols", predicate); + phantomSpawning = getBoolean("gameplay-mechanics.mob-spawning.phantoms", predicate); + villagerTraderSpawning = getBoolean("gameplay-mechanics.mob-spawning.wandering-traders", predicate); + villageSiegeSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-sieges", predicate); + mobSpawningIgnoreCreativePlayers = getBoolean("gameplay-mechanics.mob-spawning.ignore-creative-players", mobSpawningIgnoreCreativePlayers); + } + + public boolean disableObserverClocks = false; + private void observerSettings() { + disableObserverClocks = getBoolean("blocks.observer.disable-clock", disableObserverClocks); + } + + public int playerNetheriteFireResistanceDuration = 0; + public int playerNetheriteFireResistanceAmplifier = 0; + public boolean playerNetheriteFireResistanceAmbient = false; + public boolean playerNetheriteFireResistanceShowParticles = false; + public boolean playerNetheriteFireResistanceShowIcon = true; + private void playerNetheriteFireResistance() { + playerNetheriteFireResistanceDuration = getInt("gameplay-mechanics.player.netherite-fire-resistance.duration", playerNetheriteFireResistanceDuration); + playerNetheriteFireResistanceAmplifier = getInt("gameplay-mechanics.player.netherite-fire-resistance.amplifier", playerNetheriteFireResistanceAmplifier); + playerNetheriteFireResistanceAmbient = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.ambient", playerNetheriteFireResistanceAmbient); + playerNetheriteFireResistanceShowParticles = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-particles", playerNetheriteFireResistanceShowParticles); + playerNetheriteFireResistanceShowIcon = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-icon", playerNetheriteFireResistanceShowIcon); + } + + public boolean idleTimeoutKick = true; + public boolean idleTimeoutTickNearbyEntities = true; + public boolean idleTimeoutCountAsSleeping = false; + public boolean idleTimeoutUpdateTabList = false; + public boolean idleTimeoutTargetPlayer = true; + public String playerDeathExpDropEquation = "expLevel * 7"; + public int playerDeathExpDropMax = 100; + public boolean teleportIfOutsideBorder = false; + public boolean teleportOnNetherCeilingDamage = false; + public boolean totemOfUndyingWorksInInventory = false; + public boolean playerFixStuckPortal = false; + public boolean creativeOnePunch = false; + public boolean playerSleepNearMonsters = false; + public boolean playersSkipNight = true; + public double playerCriticalDamageMultiplier = 1.5D; + public int playerBurpDelay = 10; + public boolean playerBurpWhenFull = false; + public boolean playerRidableInWater = false; + public boolean playerRemoveBindingWithWeakness = false; + public int shiftRightClickRepairsMendingPoints = 0; + public int playerExpPickupDelay = 2; + public boolean playerVoidTrading = false; + private void playerSettings() { + if (PurpurConfig.version < 19) { + boolean oldVal = getBoolean("gameplay-mechanics.player.idle-timeout.mods-target", idleTimeoutTargetPlayer); + set("gameplay-mechanics.player.idle-timeout.mods-target", null); + set("gameplay-mechanics.player.idle-timeout.mobs-target", oldVal); + } + idleTimeoutKick = System.getenv("PURPUR_FORCE_IDLE_KICK") == null ? getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick) : Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")); + idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); + idleTimeoutCountAsSleeping = getBoolean("gameplay-mechanics.player.idle-timeout.count-as-sleeping", idleTimeoutCountAsSleeping); + idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList); + idleTimeoutTargetPlayer = getBoolean("gameplay-mechanics.player.idle-timeout.mobs-target", idleTimeoutTargetPlayer); + playerDeathExpDropEquation = getString("gameplay-mechanics.player.exp-dropped-on-death.equation", playerDeathExpDropEquation); + playerDeathExpDropMax = getInt("gameplay-mechanics.player.exp-dropped-on-death.maximum", playerDeathExpDropMax); + teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder); + teleportOnNetherCeilingDamage = getBoolean("gameplay-mechanics.player.teleport-on-nether-ceiling-damage", teleportOnNetherCeilingDamage); + totemOfUndyingWorksInInventory = getBoolean("gameplay-mechanics.player.totem-of-undying-works-in-inventory", totemOfUndyingWorksInInventory); + playerFixStuckPortal = getBoolean("gameplay-mechanics.player.fix-stuck-in-portal", playerFixStuckPortal); + creativeOnePunch = getBoolean("gameplay-mechanics.player.one-punch-in-creative", creativeOnePunch); + playerSleepNearMonsters = getBoolean("gameplay-mechanics.player.sleep-ignore-nearby-mobs", playerSleepNearMonsters); + playersSkipNight = getBoolean("gameplay-mechanics.player.can-skip-night", playersSkipNight); + playerCriticalDamageMultiplier = getDouble("gameplay-mechanics.player.critical-damage-multiplier", playerCriticalDamageMultiplier); + playerBurpDelay = getInt("gameplay-mechanics.player.burp-delay", playerBurpDelay); + playerBurpWhenFull = getBoolean("gameplay-mechanics.player.burp-when-full", playerBurpWhenFull); + playerRidableInWater = getBoolean("gameplay-mechanics.player.ridable-in-water", playerRidableInWater); + playerRemoveBindingWithWeakness = getBoolean("gameplay-mechanics.player.curse-of-binding.remove-with-weakness", playerRemoveBindingWithWeakness); + shiftRightClickRepairsMendingPoints = getInt("gameplay-mechanics.player.shift-right-click-repairs-mending-points", shiftRightClickRepairsMendingPoints); + playerExpPickupDelay = getInt("gameplay-mechanics.player.exp-pickup-delay-ticks", playerExpPickupDelay); + playerVoidTrading = getBoolean("gameplay-mechanics.player.allow-void-trading", playerVoidTrading); + } + + public boolean silkTouchEnabled = false; + public String silkTouchSpawnerName = "Monster Spawner"; + public List silkTouchSpawnerLore = new ArrayList<>(); + public List silkTouchTools = new ArrayList<>(); + public int minimumSilkTouchSpawnerRequire = 1; + private void silkTouchSettings() { + if (PurpurConfig.version < 21) { + String oldName = getString("gameplay-mechanics.silk-touch.spawner-name", silkTouchSpawnerName); + set("gameplay-mechanics.silk-touch.spawner-name", "" + ChatColor.toMM(oldName.replace("{mob}", ""))); + List list = new ArrayList<>(); + getList("gameplay-mechanics.silk-touch.spawner-lore", List.of("Spawns a ")) + .forEach(line -> list.add("" + ChatColor.toMM(line.toString().replace("{mob}", "")))); + set("gameplay-mechanics.silk-touch.spawner-lore", list); + } + silkTouchEnabled = getBoolean("gameplay-mechanics.silk-touch.enabled", silkTouchEnabled); + silkTouchSpawnerName = getString("gameplay-mechanics.silk-touch.spawner-name", silkTouchSpawnerName); + minimumSilkTouchSpawnerRequire = getInt("gameplay-mechanics.silk-touch.minimal-level", minimumSilkTouchSpawnerRequire); + silkTouchSpawnerLore.clear(); + getList("gameplay-mechanics.silk-touch.spawner-lore", List.of("Spawns a ")) + .forEach(line -> silkTouchSpawnerLore.add(line.toString())); + silkTouchTools.clear(); + getList("gameplay-mechanics.silk-touch.tools", List.of( + "minecraft:iron_pickaxe", + "minecraft:golden_pickaxe", + "minecraft:diamond_pickaxe", + "minecraft:netherite_pickaxe" + )).forEach(key -> { + Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(key.toString())); + if (item != Items.AIR) silkTouchTools.add(item); + }); + } + + public double bowProjectileOffset = 1.0D; + public double crossbowProjectileOffset = 1.0D; + public double eggProjectileOffset = 1.0D; + public double enderPearlProjectileOffset = 1.0D; + public double throwablePotionProjectileOffset = 1.0D; + public double tridentProjectileOffset = 1.0D; + public double snowballProjectileOffset = 1.0D; + private void projectileOffsetSettings() { + bowProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.bow", bowProjectileOffset); + crossbowProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.crossbow", crossbowProjectileOffset); + eggProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.egg", eggProjectileOffset); + enderPearlProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.ender-pearl", enderPearlProjectileOffset); + throwablePotionProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.throwable-potion", throwablePotionProjectileOffset); + tridentProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.trident", tridentProjectileOffset); + snowballProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.snowball", snowballProjectileOffset); + } + + public int snowballDamage = -1; + private void snowballSettings() { + snowballDamage = getInt("gameplay-mechanics.projectile-damage.snowball", snowballDamage); + } + + public Map axeStrippables = new HashMap<>(); + public Map axeWaxables = new HashMap<>(); + public Map axeWeatherables = new HashMap<>(); + public Map hoeTillables = new HashMap<>(); + public Map shovelFlattenables = new HashMap<>(); + public boolean hoeReplantsCrops = false; + public boolean hoeReplantsNetherWarts = false; + private void toolSettings() { + axeStrippables.clear(); + axeWaxables.clear(); + axeWeatherables.clear(); + hoeTillables.clear(); + shovelFlattenables.clear(); + if (PurpurConfig.version < 18) { + ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + ".tools.hoe.tilling"); + if (section != null) { + PurpurConfig.config.set("world-settings." + worldName + ".tools.hoe.tillables", section); + PurpurConfig.config.set("world-settings." + worldName + ".tools.hoe.tilling", null); + } + section = PurpurConfig.config.getConfigurationSection("world-settings.default.tools.hoe.tilling"); + if (section != null) { + PurpurConfig.config.set("world-settings.default.tools.hoe.tillables", section); + PurpurConfig.config.set("world-settings.default.tools.hoe.tilling", null); + } + } + if (PurpurConfig.version < 29) { + PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:mangrove_log", Map.of("into", "minecraft:stripped_mangrove_log", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:mangrove_wood", Map.of("into", "minecraft:stripped_mangrove_wood", "drops", new HashMap())); + } + if (PurpurConfig.version < 32) { + PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:cherry_log", Map.of("into", "minecraft:stripped_cherry_log", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap())); + } + if (PurpurConfig.version < 33) { + getList("gameplay-mechanics.shovel-turns-block-to-grass-path", new ArrayList(){{ + add("minecraft:coarse_dirt"); + add("minecraft:dirt"); + add("minecraft:grass_block"); + add("minecraft:mycelium"); + add("minecraft:podzol"); + add("minecraft:rooted_dirt"); + }}).forEach(key -> { + PurpurConfig.config.set("world-settings.default.tools.shovel.flattenables." + key.toString(), Map.of("into", "minecraft:dirt_path", "drops", new HashMap())); + }); + set("gameplay-mechanics.shovel-turns-block-to-grass-path", null); + } + if (PurpurConfig.version < 34) { + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_chiseled_copper", Map.of("into", "minecraft:oxidized_chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_door", Map.of("into", "minecraft:oxidized_copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_trapdoor", Map.of("into", "minecraft:oxidized_copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_grate", Map.of("into", "minecraft:oxidized_copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_bulb", Map.of("into", "minecraft:oxidized_copper_bulb", "drops", new HashMap())); + + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap())); + } + 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.getValue(ResourceLocation.parse(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.strippables." + blockId + "`"); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.strippables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.strippables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + axeStrippables.put(block, new Strippable(into, drops)); + }); + getMap("tools.axe.waxables", Map.ofEntries( + Map.entry("minecraft:waxed_copper_block", Map.of("into", "minecraft:copper_block", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_copper", Map.of("into", "minecraft:exposed_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_copper", Map.of("into", "minecraft:weathered_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_copper", Map.of("into", "minecraft:oxidized_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_cut_copper", Map.of("into", "minecraft:cut_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_cut_copper", Map.of("into", "minecraft:exposed_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_cut_copper", Map.of("into", "minecraft:weathered_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_cut_copper", Map.of("into", "minecraft:oxidized_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_cut_copper_slab", Map.of("into", "minecraft:cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_cut_copper_slab", Map.of("into", "minecraft:exposed_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_cut_copper_slab", Map.of("into", "minecraft:weathered_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_cut_copper_slab", Map.of("into", "minecraft:oxidized_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:waxed_cut_copper_stairs", Map.of("into", "minecraft:cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_cut_copper_stairs", Map.of("into", "minecraft:exposed_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_cut_copper_stairs", Map.of("into", "minecraft:oxidized_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:waxed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_chiseled_copper", Map.of("into", "minecraft:oxidized_chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_copper_door", Map.of("into", "minecraft:oxidized_copper_door", "drops", new HashMap())), + Map.entry("minecraft:waxed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_copper_trapdoor", Map.of("into", "minecraft:oxidized_copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:waxed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_copper_grate", Map.of("into", "minecraft:oxidized_copper_grate", "drops", new HashMap())), + Map.entry("minecraft:waxed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_copper_bulb", Map.of("into", "minecraft:oxidized_copper_bulb", "drops", new HashMap()))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.waxables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.waxables." + blockId + "`"); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.waxables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.waxables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.waxables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + axeWaxables.put(block, new Waxable(into, drops)); + }); + getMap("tools.axe.weatherables", Map.ofEntries( + Map.entry("minecraft:exposed_copper", Map.of("into", "minecraft:copper_block", "drops", new HashMap())), + Map.entry("minecraft:weathered_copper", Map.of("into", "minecraft:exposed_copper", "drops", new HashMap())), + Map.entry("minecraft:oxidized_copper", Map.of("into", "minecraft:weathered_copper", "drops", new HashMap())), + Map.entry("minecraft:exposed_cut_copper", Map.of("into", "minecraft:cut_copper", "drops", new HashMap())), + Map.entry("minecraft:weathered_cut_copper", Map.of("into", "minecraft:exposed_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:oxidized_cut_copper", Map.of("into", "minecraft:weathered_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:exposed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:weathered_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:oxidized_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:exposed_cut_copper_slab", Map.of("into", "minecraft:cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:weathered_cut_copper_slab", Map.of("into", "minecraft:exposed_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:oxidized_cut_copper_slab", Map.of("into", "minecraft:weathered_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:exposed_cut_copper_stairs", Map.of("into", "minecraft:cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:weathered_cut_copper_stairs", Map.of("into", "minecraft:exposed_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:oxidized_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:exposed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap())), + Map.entry("minecraft:weathered_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap())), + Map.entry("minecraft:oxidized_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap())), + Map.entry("minecraft:exposed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:weathered_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:oxidized_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:exposed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap())), + Map.entry("minecraft:weathered_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap())), + Map.entry("minecraft:oxidized_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap())), + Map.entry("minecraft:exposed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap())), + Map.entry("minecraft:weathered_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())), + Map.entry("minecraft:oxidized_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap()))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.weatherables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.weatherables." + blockId + "`"); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.weatherables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.weatherables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.weatherables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + axeWeatherables.put(block, new Weatherable(into, drops)); + }); + getMap("tools.hoe.tillables", Map.ofEntries( + Map.entry("minecraft:grass_block", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap())), + Map.entry("minecraft:dirt_path", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap())), + Map.entry("minecraft:dirt", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap())), + Map.entry("minecraft:coarse_dirt", Map.of("condition", "air_above", "into", "minecraft:dirt", "drops", new HashMap())), + Map.entry("minecraft:rooted_dirt", Map.of("condition", "always", "into", "minecraft:dirt", "drops", Map.of("minecraft:hanging_roots", 1.0D)))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.hoe.tillables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.hoe.tillables." + blockId + "`"); return; } + String conditionId = (String) map.get("condition"); + Tillable.Condition condition = Tillable.Condition.get(conditionId); + if (condition == null) { PurpurConfig.log(Level.SEVERE, "Invalid condition for `tools.hoe.tillables." + blockId + ".condition`: " + conditionId); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.hoe.tillables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.hoe.tillables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.hoe.tillables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + hoeTillables.put(block, new Tillable(condition, into, drops)); + }); + getMap("tools.shovel.flattenables", Map.ofEntries( + Map.entry("minecraft:grass_block", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), + Map.entry("minecraft:dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), + Map.entry("minecraft:podzol", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), + Map.entry("minecraft:coarse_dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), + Map.entry("minecraft:mycelium", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), + Map.entry("minecraft:rooted_dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap()))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.shovel.flattenables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.shovel.flattenables." + blockId + "`"); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.shovel.flattenables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.shovel.flattenables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.shovel.flattenables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + shovelFlattenables.put(block, new Flattenable(into, drops)); + }); + hoeReplantsCrops = getBoolean("tools.hoe.replant-crops", hoeReplantsCrops); + hoeReplantsNetherWarts = getBoolean("tools.hoe.replant-nether-warts", hoeReplantsNetherWarts); + } + + public boolean anvilAllowColors = false; + public boolean anvilColorsUseMiniMessage; + public int anvilRepairIngotsAmount = 0; + public int anvilDamageObsidianAmount = 0; + private void anvilSettings() { + anvilAllowColors = getBoolean("blocks.anvil.allow-colors", anvilAllowColors); + anvilColorsUseMiniMessage = getBoolean("blocks.anvil.use-mini-message", anvilColorsUseMiniMessage); + anvilRepairIngotsAmount = getInt("blocks.anvil.iron-ingots-used-for-repair", anvilRepairIngotsAmount); + anvilDamageObsidianAmount = getInt("blocks.anvil.obsidian-used-for-damage", anvilDamageObsidianAmount); + } + + public double azaleaGrowthChance = 0.0D; + private void azaleaSettings() { + azaleaGrowthChance = getDouble("blocks.azalea.growth-chance", azaleaGrowthChance); + } + + public int beaconLevelOne = 20; + public int beaconLevelTwo = 30; + public int beaconLevelThree = 40; + public int beaconLevelFour = 50; + public boolean beaconAllowEffectsWithTintedGlass = false; + private void beaconSettings() { + beaconLevelOne = getInt("blocks.beacon.effect-range.level-1", beaconLevelOne); + beaconLevelTwo = getInt("blocks.beacon.effect-range.level-2", beaconLevelTwo); + beaconLevelThree = getInt("blocks.beacon.effect-range.level-3", beaconLevelThree); + beaconLevelFour = getInt("blocks.beacon.effect-range.level-4", beaconLevelFour); + beaconAllowEffectsWithTintedGlass = getBoolean("blocks.beacon.allow-effects-with-tinted-glass", beaconAllowEffectsWithTintedGlass); + } + + public boolean bedExplode = true; + public boolean bedExplodeOnVillagerSleep = false; + public double bedExplosionPower = 5.0D; + public boolean bedExplosionFire = true; + public net.minecraft.world.level.Level.ExplosionInteraction bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + private void bedSettings() { + if (PurpurConfig.version < 31) { + if ("DESTROY".equals(getString("blocks.bed.explosion-effect", bedExplosionEffect.name()))) { + set("blocks.bed.explosion-effect", "BLOCK"); + } + } + bedExplode = getBoolean("blocks.bed.explode", bedExplode); + bedExplodeOnVillagerSleep = getBoolean("blocks.bed.explode-on-villager-sleep", bedExplodeOnVillagerSleep); + bedExplosionPower = getDouble("blocks.bed.explosion-power", bedExplosionPower); + bedExplosionFire = getBoolean("blocks.bed.explosion-fire", bedExplosionFire); + try { + bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.bed.explosion-effect", bedExplosionEffect.name())); + } catch (IllegalArgumentException e) { + log(Level.SEVERE, "Unknown value for `blocks.bed.explosion-effect`! Using default of `BLOCK`"); + bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + } + } + + public Map bigDripleafTiltDelay = new HashMap<>(); + private void bigDripleafSettings() { + bigDripleafTiltDelay.clear(); + getMap("blocks.big_dripleaf.tilt-delay", Map.ofEntries( + Map.entry("UNSTABLE", 10), + Map.entry("PARTIAL", 10), + Map.entry("FULL", 100)) + ).forEach((tilt, delay) -> { + try { + bigDripleafTiltDelay.put(Tilt.valueOf(tilt), (int) delay); + } catch (IllegalArgumentException e) { + PurpurConfig.log(Level.SEVERE, "Invalid big_dripleaf tilt key: " + tilt); + } + }); + } + + public boolean cactusBreaksFromSolidNeighbors = true; + public boolean cactusAffectedByBonemeal = false; + private void cactusSettings() { + cactusBreaksFromSolidNeighbors = getBoolean("blocks.cactus.breaks-from-solid-neighbors", cactusBreaksFromSolidNeighbors); + cactusAffectedByBonemeal = getBoolean("blocks.cactus.affected-by-bonemeal", cactusAffectedByBonemeal); + } + + public boolean sugarCanAffectedByBonemeal = false; + private void sugarCaneSettings() { + sugarCanAffectedByBonemeal = getBoolean("blocks.sugar_cane.affected-by-bonemeal", sugarCanAffectedByBonemeal); + } + + public boolean netherWartAffectedByBonemeal = false; + private void netherWartSettings() { + netherWartAffectedByBonemeal = getBoolean("blocks.nether_wart.affected-by-bonemeal", netherWartAffectedByBonemeal); + } + + public boolean campFireLitWhenPlaced = true; + private void campFireSettings() { + campFireLitWhenPlaced = getBoolean("blocks.campfire.lit-when-placed", campFireLitWhenPlaced); + } + + public boolean chestOpenWithBlockOnTop = false; + private void chestSettings() { + chestOpenWithBlockOnTop = getBoolean("blocks.chest.open-with-solid-block-on-top", chestOpenWithBlockOnTop); + } + + public boolean composterBulkProcess = false; + private void composterSettings() { + composterBulkProcess = getBoolean("blocks.composter.sneak-to-bulk-process", composterBulkProcess); + } + + public boolean coralDieOutsideWater = true; + private void coralSettings() { + coralDieOutsideWater = getBoolean("blocks.coral.die-outside-water", coralDieOutsideWater); + } + + public boolean dispenserApplyCursedArmor = true; + public boolean dispenserPlaceAnvils = false; + private void dispenserSettings() { + dispenserApplyCursedArmor = getBoolean("blocks.dispenser.apply-cursed-to-armor-slots", dispenserApplyCursedArmor); + dispenserPlaceAnvils = getBoolean("blocks.dispenser.place-anvils", dispenserPlaceAnvils); + } + + public List doorRequiresRedstone = new ArrayList<>(); + private void doorSettings() { + getList("blocks.door.requires-redstone", new ArrayList()).forEach(key -> { + Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(key.toString())); + if (!block.defaultBlockState().isAir()) { + doorRequiresRedstone.add(block); + } + }); + } + + public boolean dragonEggTeleport = true; + private void dragonEggSettings() { + dragonEggTeleport = getBoolean("blocks.dragon_egg.teleport", dragonEggTeleport); + } + + public boolean baselessEndCrystalExplode = true; + public double baselessEndCrystalExplosionPower = 6.0D; + public boolean baselessEndCrystalExplosionFire = false; + public net.minecraft.world.level.Level.ExplosionInteraction baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + public boolean basedEndCrystalExplode = true; + public double basedEndCrystalExplosionPower = 6.0D; + public boolean basedEndCrystalExplosionFire = false; + public net.minecraft.world.level.Level.ExplosionInteraction basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + public int endCrystalCramming = 0; + public boolean endCrystalPlaceAnywhere = false; + private void endCrystalSettings() { + if (PurpurConfig.version < 31) { + if ("DESTROY".equals(getString("blocks.end-crystal.baseless.explosion-effect", baselessEndCrystalExplosionEffect.name()))) { + set("blocks.end-crystal.baseless.explosion-effect", "BLOCK"); + } + if ("DESTROY".equals(getString("blocks.end-crystal.base.explosion-effect", basedEndCrystalExplosionEffect.name()))) { + set("blocks.end-crystal.base.explosion-effect", "BLOCK"); + } + } + baselessEndCrystalExplode = getBoolean("blocks.end-crystal.baseless.explode", baselessEndCrystalExplode); + baselessEndCrystalExplosionPower = getDouble("blocks.end-crystal.baseless.explosion-power", baselessEndCrystalExplosionPower); + baselessEndCrystalExplosionFire = getBoolean("blocks.end-crystal.baseless.explosion-fire", baselessEndCrystalExplosionFire); + try { + baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.end-crystal.baseless.explosion-effect", baselessEndCrystalExplosionEffect.name())); + } catch (IllegalArgumentException e) { + log(Level.SEVERE, "Unknown value for `blocks.end-crystal.baseless.explosion-effect`! Using default of `BLOCK`"); + baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + } + basedEndCrystalExplode = getBoolean("blocks.end-crystal.base.explode", basedEndCrystalExplode); + basedEndCrystalExplosionPower = getDouble("blocks.end-crystal.base.explosion-power", basedEndCrystalExplosionPower); + basedEndCrystalExplosionFire = getBoolean("blocks.end-crystal.base.explosion-fire", basedEndCrystalExplosionFire); + try { + basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.end-crystal.base.explosion-effect", basedEndCrystalExplosionEffect.name())); + } catch (IllegalArgumentException e) { + log(Level.SEVERE, "Unknown value for `blocks.end-crystal.base.explosion-effect`! Using default of `BLOCK`"); + basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + } + endCrystalCramming = getInt("blocks.end-crystal.cramming-amount", endCrystalCramming); + endCrystalPlaceAnywhere = getBoolean("gameplay-mechanics.item.end-crystal.place-anywhere", endCrystalPlaceAnywhere); + } + + public boolean farmlandBypassMobGriefing = false; + public boolean farmlandGetsMoistFromBelow = false; + public boolean farmlandAlpha = false; + public boolean farmlandTramplingDisabled = false; + public boolean farmlandTramplingOnlyPlayers = false; + public boolean farmlandTramplingFeatherFalling = false; + public double farmlandTrampleHeight = -1D; + private void farmlandSettings() { + farmlandBypassMobGriefing = getBoolean("blocks.farmland.bypass-mob-griefing", farmlandBypassMobGriefing); + farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); + farmlandAlpha = getBoolean("blocks.farmland.use-alpha-farmland", farmlandAlpha); + farmlandTramplingDisabled = getBoolean("blocks.farmland.disable-trampling", farmlandTramplingDisabled); + farmlandTramplingOnlyPlayers = getBoolean("blocks.farmland.only-players-trample", farmlandTramplingOnlyPlayers); + farmlandTramplingFeatherFalling = getBoolean("blocks.farmland.feather-fall-distance-affects-trampling", farmlandTramplingFeatherFalling); + farmlandTrampleHeight = getDouble("blocks.farmland.trample-height", farmlandTrampleHeight); + } + + public double floweringAzaleaGrowthChance = 0.0D; + private void floweringAzaleaSettings() { + floweringAzaleaGrowthChance = getDouble("blocks.flowering_azalea.growth-chance", floweringAzaleaGrowthChance); + } + + public boolean furnaceUseLavaFromUnderneath = false; + private void furnaceSettings() { + if (PurpurConfig.version < 17) { + furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.infinite-fuel", furnaceUseLavaFromUnderneath); + boolean oldValue = getBoolean("blocks.furnace.infinite-fuel", furnaceUseLavaFromUnderneath); + set("blocks.furnace.infinite-fuel", null); + set("blocks.furnace.use-lava-from-underneath", oldValue); + } + furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.use-lava-from-underneath", furnaceUseLavaFromUnderneath); + } + + public boolean mobsSpawnOnPackedIce = true; + public boolean mobsSpawnOnBlueIce = true; + public boolean snowOnBlueIce = true; + private void iceSettings() { + mobsSpawnOnPackedIce = getBoolean("blocks.packed_ice.allow-mob-spawns", mobsSpawnOnPackedIce); + mobsSpawnOnBlueIce = getBoolean("blocks.blue_ice.allow-mob-spawns", mobsSpawnOnBlueIce); + snowOnBlueIce = getBoolean("blocks.blue_ice.allow-snow-formation", snowOnBlueIce); + } + + public int lavaInfiniteRequiredSources = 2; + public int lavaSpeedNether = 10; + public int lavaSpeedNotNether = 30; + private void lavaSettings() { + lavaInfiniteRequiredSources = getInt("blocks.lava.infinite-required-sources", lavaInfiniteRequiredSources); + lavaSpeedNether = getInt("blocks.lava.speed.nether", lavaSpeedNether); + lavaSpeedNotNether = getInt("blocks.lava.speed.not-nether", lavaSpeedNotNether); + } + + public int pistonBlockPushLimit = 12; + private void pistonSettings() { + pistonBlockPushLimit = getInt("blocks.piston.block-push-limit", pistonBlockPushLimit); + } + + public boolean magmaBlockDamageWhenSneaking = false; + private void magmaBlockSettings() { + magmaBlockDamageWhenSneaking = getBoolean("blocks.magma-block.damage-when-sneaking", magmaBlockDamageWhenSneaking); + } + + public boolean powderSnowBypassMobGriefing = false; + private void powderSnowSettings() { + powderSnowBypassMobGriefing = getBoolean("blocks.powder_snow.bypass-mob-griefing", powderSnowBypassMobGriefing); + } + + public int railActivationRange = 8; + private void railSettings() { + railActivationRange = getInt("blocks.powered-rail.activation-range", railActivationRange); + } + + public boolean respawnAnchorExplode = true; + public double respawnAnchorExplosionPower = 5.0D; + public boolean respawnAnchorExplosionFire = true; + public net.minecraft.world.level.Level.ExplosionInteraction respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + private void respawnAnchorSettings() { + if (PurpurConfig.version < 31) { + if ("DESTROY".equals(getString("blocks.respawn_anchor.explosion-effect", respawnAnchorExplosionEffect.name()))) { + set("blocks.respawn_anchor.explosion-effect", "BLOCK"); + } + } + respawnAnchorExplode = getBoolean("blocks.respawn_anchor.explode", respawnAnchorExplode); + respawnAnchorExplosionPower = getDouble("blocks.respawn_anchor.explosion-power", respawnAnchorExplosionPower); + respawnAnchorExplosionFire = getBoolean("blocks.respawn_anchor.explosion-fire", respawnAnchorExplosionFire); + try { + respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.respawn_anchor.explosion-effect", respawnAnchorExplosionEffect.name())); + } catch (IllegalArgumentException e) { + log(Level.SEVERE, "Unknown value for `blocks.respawn_anchor.explosion-effect`! Using default of `BLOCK`"); + respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + } + } + + public boolean sculkShriekerCanSummonDefault = false; + private void sculkShriekerSettings() { + sculkShriekerCanSummonDefault = getBoolean("blocks.sculk_shrieker.can-summon-default", sculkShriekerCanSummonDefault); + } + + public boolean signAllowColors = false; + private void signSettings() { + signAllowColors = getBoolean("blocks.sign.allow-colors", signAllowColors); + } + + public boolean slabHalfBreak = false; + private void slabSettings() { + slabHalfBreak = getBoolean("blocks.slab.break-individual-slabs-when-sneaking", slabHalfBreak); + } + + public boolean spawnerDeactivateByRedstone = false; + public boolean spawnerFixMC238526 = false; + private void spawnerSettings() { + spawnerDeactivateByRedstone = getBoolean("blocks.spawner.deactivate-by-redstone", spawnerDeactivateByRedstone); + spawnerFixMC238526 = getBoolean("blocks.spawner.fix-mc-238526", spawnerFixMC238526); + } + + public int spongeAbsorptionArea = 65; + public int spongeAbsorptionRadius = 6; + public boolean spongeAbsorbsLava = false; + public boolean spongeAbsorbsWaterFromMud = false; + private void spongeSettings() { + spongeAbsorptionArea = getInt("blocks.sponge.absorption.area", spongeAbsorptionArea); + spongeAbsorptionRadius = getInt("blocks.sponge.absorption.radius", spongeAbsorptionRadius); + spongeAbsorbsLava = getBoolean("blocks.sponge.absorbs-lava", spongeAbsorbsLava); + spongeAbsorbsWaterFromMud = getBoolean("blocks.sponge.absorbs-water-from-mud", spongeAbsorbsWaterFromMud); + } + + public float stonecutterDamage = 0.0F; + private void stonecutterSettings() { + stonecutterDamage = (float) getDouble("blocks.stonecutter.damage", stonecutterDamage); + } + + public boolean turtleEggsBreakFromExpOrbs = false; + public boolean turtleEggsBreakFromItems = false; + public boolean turtleEggsBreakFromMinecarts = false; + public boolean turtleEggsBypassMobGriefing = false; + public int turtleEggsRandomTickCrackChance = 500; + public boolean turtleEggsTramplingFeatherFalling = false; + private void turtleEggSettings() { + turtleEggsBreakFromExpOrbs = getBoolean("blocks.turtle_egg.break-from-exp-orbs", turtleEggsBreakFromExpOrbs); + turtleEggsBreakFromItems = getBoolean("blocks.turtle_egg.break-from-items", turtleEggsBreakFromItems); + turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); + turtleEggsBypassMobGriefing = getBoolean("blocks.turtle_egg.bypass-mob-griefing", turtleEggsBypassMobGriefing); + turtleEggsRandomTickCrackChance = getInt("blocks.turtle_egg.random-tick-crack-chance", turtleEggsRandomTickCrackChance); + turtleEggsTramplingFeatherFalling = getBoolean("blocks.turtle_egg.feather-fall-distance-affects-trampling", turtleEggsTramplingFeatherFalling); + } + + public int waterInfiniteRequiredSources = 2; + private void waterSources() { + waterInfiniteRequiredSources = getInt("blocks.water.infinite-required-sources", waterInfiniteRequiredSources); + } + + public boolean babiesAreRidable = true; + public boolean untamedTamablesAreRidable = true; + public boolean useNightVisionWhenRiding = false; + public boolean useDismountsUnderwaterTag = true; + private void ridableSettings() { + babiesAreRidable = getBoolean("ridable-settings.babies-are-ridable", babiesAreRidable); + untamedTamablesAreRidable = getBoolean("ridable-settings.untamed-tamables-are-ridable", untamedTamablesAreRidable); + useNightVisionWhenRiding = getBoolean("ridable-settings.use-night-vision", useNightVisionWhenRiding); + useDismountsUnderwaterTag = getBoolean("ridable-settings.use-dismounts-underwater-tag", useDismountsUnderwaterTag); + } + + public boolean allayRidable = false; + public boolean allayRidableInWater = true; + public boolean allayControllable = true; + public double allayMaxHealth = 20.0D; + public double allayScale = 1.0D; + private void allaySettings() { + allayRidable = getBoolean("mobs.allay.ridable", allayRidable); + allayRidableInWater = getBoolean("mobs.allay.ridable-in-water", allayRidableInWater); + allayControllable = getBoolean("mobs.allay.controllable", allayControllable); + allayMaxHealth = getDouble("mobs.allay.attributes.max_health", allayMaxHealth); + allayScale = Mth.clamp(getDouble("mobs.allay.attributes.scale", allayScale), 0.0625D, 16.0D); + } + + public boolean armadilloRidable = false; + public boolean armadilloRidableInWater = true; + public boolean armadilloControllable = true; + public double armadilloMaxHealth = 12.0D; + public double armadilloScale = 1.0D; + public int armadilloBreedingTicks = 6000; + private void armadilloSettings() { + armadilloRidable = getBoolean("mobs.armadillo.ridable", armadilloRidable); + armadilloRidableInWater = getBoolean("mobs.armadillo.ridable-in-water", armadilloRidableInWater); + armadilloControllable = getBoolean("mobs.armadillo.controllable", armadilloControllable); + armadilloMaxHealth = getDouble("mobs.armadillo.attributes.max_health", armadilloMaxHealth); + armadilloScale = Mth.clamp(getDouble("mobs.armadillo.attributes.scale", armadilloScale), 0.0625D, 16.0D); + armadilloBreedingTicks = getInt("mobs.armadillo.breeding-delay-ticks", armadilloBreedingTicks); + } + + public boolean axolotlRidable = false; + public boolean axolotlControllable = true; + public double axolotlMaxHealth = 14.0D; + public double axolotlScale = 1.0D; + public int axolotlBreedingTicks = 6000; + public boolean axolotlTakeDamageFromWater = false; + public boolean axolotlAlwaysDropExp = false; + private void axolotlSettings() { + axolotlRidable = getBoolean("mobs.axolotl.ridable", axolotlRidable); + axolotlControllable = getBoolean("mobs.axolotl.controllable", axolotlControllable); + axolotlMaxHealth = getDouble("mobs.axolotl.attributes.max_health", axolotlMaxHealth); + axolotlScale = Mth.clamp(getDouble("mobs.axolotl.attributes.scale", axolotlScale), 0.0625D, 16.0D); + axolotlBreedingTicks = getInt("mobs.axolotl.breeding-delay-ticks", axolotlBreedingTicks); + axolotlTakeDamageFromWater = getBoolean("mobs.axolotl.takes-damage-from-water", axolotlTakeDamageFromWater); + axolotlAlwaysDropExp = getBoolean("mobs.axolotl.always-drop-exp", axolotlAlwaysDropExp); + } + + public boolean batRidable = false; + public boolean batRidableInWater = true; + public boolean batControllable = true; + public double batMaxY = 320D; + public double batMaxHealth = 6.0D; + public double batScale = 1.0D; + public double batFollowRange = 16.0D; + public double batKnockbackResistance = 0.0D; + public double batMovementSpeed = 0.6D; + public double batFlyingSpeed = 0.6D; + public double batArmor = 0.0D; + public double batArmorToughness = 0.0D; + public double batAttackKnockback = 0.0D; + public boolean batTakeDamageFromWater = false; + public boolean batAlwaysDropExp = false; + private void batSettings() { + batRidable = getBoolean("mobs.bat.ridable", batRidable); + batRidableInWater = getBoolean("mobs.bat.ridable-in-water", batRidableInWater); + batControllable = getBoolean("mobs.bat.controllable", batControllable); + batMaxY = getDouble("mobs.bat.ridable-max-y", batMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.bat.attributes.max-health", batMaxHealth); + set("mobs.bat.attributes.max-health", null); + set("mobs.bat.attributes.max_health", oldValue); + } + batMaxHealth = getDouble("mobs.bat.attributes.max_health", batMaxHealth); + batScale = Mth.clamp(getDouble("mobs.bat.attributes.scale", batScale), 0.0625D, 16.0D); + batFollowRange = getDouble("mobs.bat.attributes.follow_range", batFollowRange); + batKnockbackResistance = getDouble("mobs.bat.attributes.knockback_resistance", batKnockbackResistance); + batMovementSpeed = getDouble("mobs.bat.attributes.movement_speed", batMovementSpeed); + batFlyingSpeed = getDouble("mobs.bat.attributes.flying_speed", batFlyingSpeed); + batArmor = getDouble("mobs.bat.attributes.armor", batArmor); + batArmorToughness = getDouble("mobs.bat.attributes.armor_toughness", batArmorToughness); + batAttackKnockback = getDouble("mobs.bat.attributes.attack_knockback", batAttackKnockback); + batTakeDamageFromWater = getBoolean("mobs.bat.takes-damage-from-water", batTakeDamageFromWater); + batAlwaysDropExp = getBoolean("mobs.bat.always-drop-exp", batAlwaysDropExp); + } + + public boolean beeRidable = false; + public boolean beeRidableInWater = true; + public boolean beeControllable = true; + public double beeMaxY = 320D; + public double beeMaxHealth = 10.0D; + public double beeScale = 1.0D; + public int beeBreedingTicks = 6000; + public boolean beeTakeDamageFromWater = true; + public boolean beeCanWorkAtNight = false; + public boolean beeCanWorkInRain = false; + public boolean beeAlwaysDropExp = false; + public boolean beeDiesAfterSting = true; + private void beeSettings() { + beeRidable = getBoolean("mobs.bee.ridable", beeRidable); + beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater); + beeControllable = getBoolean("mobs.bee.controllable", beeControllable); + beeMaxY = getDouble("mobs.bee.ridable-max-y", beeMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.bee.attributes.max-health", beeMaxHealth); + set("mobs.bee.attributes.max-health", null); + set("mobs.bee.attributes.max_health", oldValue); + } + beeMaxHealth = getDouble("mobs.bee.attributes.max_health", beeMaxHealth); + beeScale = Mth.clamp(getDouble("mobs.bee.attributes.scale", beeScale), 0.0625D, 16.0D); + beeBreedingTicks = getInt("mobs.bee.breeding-delay-ticks", beeBreedingTicks); + beeTakeDamageFromWater = getBoolean("mobs.bee.takes-damage-from-water", beeTakeDamageFromWater); + beeCanWorkAtNight = getBoolean("mobs.bee.can-work-at-night", beeCanWorkAtNight); + beeCanWorkInRain = getBoolean("mobs.bee.can-work-in-rain", beeCanWorkInRain); + beeAlwaysDropExp = getBoolean("mobs.bee.always-drop-exp", beeAlwaysDropExp); + beeDiesAfterSting = getBoolean("mobs.bee.dies-after-sting", beeDiesAfterSting); + } + + public boolean blazeRidable = false; + public boolean blazeRidableInWater = true; + public boolean blazeControllable = true; + public double blazeMaxY = 320D; + public double blazeMaxHealth = 20.0D; + public double blazeScale = 1.0D; + public boolean blazeTakeDamageFromWater = true; + public boolean blazeAlwaysDropExp = false; + private void blazeSettings() { + blazeRidable = getBoolean("mobs.blaze.ridable", blazeRidable); + blazeRidableInWater = getBoolean("mobs.blaze.ridable-in-water", blazeRidableInWater); + blazeControllable = getBoolean("mobs.blaze.controllable", blazeControllable); + blazeMaxY = getDouble("mobs.blaze.ridable-max-y", blazeMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.blaze.attributes.max-health", blazeMaxHealth); + set("mobs.blaze.attributes.max-health", null); + set("mobs.blaze.attributes.max_health", oldValue); + } + blazeMaxHealth = getDouble("mobs.blaze.attributes.max_health", blazeMaxHealth); + blazeScale = Mth.clamp(getDouble("mobs.blaze.attributes.scale", blazeScale), 0.0625D, 16.0D); + blazeTakeDamageFromWater = getBoolean("mobs.blaze.takes-damage-from-water", blazeTakeDamageFromWater); + blazeAlwaysDropExp = getBoolean("mobs.blaze.always-drop-exp", blazeAlwaysDropExp); + } + + public boolean boggedRidable = false; + public boolean boggedRidableInWater = true; + public boolean boggedControllable = true; + public double boggedMaxHealth = 16.0D; + public double boggedScale = 1.0D; + private void boggedSettings() { + boggedRidable = getBoolean("mobs.bogged.ridable", boggedRidable); + boggedRidableInWater = getBoolean("mobs.bogged.ridable-in-water", boggedRidableInWater); + boggedControllable = getBoolean("mobs.bogged.controllable", boggedControllable); + boggedMaxHealth = getDouble("mobs.bogged.attributes.max_health", boggedMaxHealth); + boggedScale = Mth.clamp(getDouble("mobs.bogged.attributes.scale", boggedScale), 0.0625D, 16.0D); + } + + public boolean camelRidableInWater = false; + public double camelMaxHealthMin = 32.0D; + public double camelMaxHealthMax = 32.0D; + public double camelJumpStrengthMin = 0.42D; + public double camelJumpStrengthMax = 0.42D; + public double camelMovementSpeedMin = 0.09D; + public double camelMovementSpeedMax = 0.09D; + public int camelBreedingTicks = 6000; + private void camelSettings() { + camelRidableInWater = getBoolean("mobs.camel.ridable-in-water", camelRidableInWater); + camelMaxHealthMin = getDouble("mobs.camel.attributes.max_health.min", camelMaxHealthMin); + camelMaxHealthMax = getDouble("mobs.camel.attributes.max_health.max", camelMaxHealthMax); + camelJumpStrengthMin = getDouble("mobs.camel.attributes.jump_strength.min", camelJumpStrengthMin); + camelJumpStrengthMax = getDouble("mobs.camel.attributes.jump_strength.max", camelJumpStrengthMax); + camelMovementSpeedMin = getDouble("mobs.camel.attributes.movement_speed.min", camelMovementSpeedMin); + camelMovementSpeedMax = getDouble("mobs.camel.attributes.movement_speed.max", camelMovementSpeedMax); + camelBreedingTicks = getInt("mobs.camel.breeding-delay-ticks", camelBreedingTicks); + } + + public boolean catRidable = false; + public boolean catRidableInWater = true; + public boolean catControllable = true; + public double catMaxHealth = 10.0D; + public double catScale = 1.0D; + public int catSpawnDelay = 1200; + public int catSpawnSwampHutScanRange = 16; + public int catSpawnVillageScanRange = 48; + public int catBreedingTicks = 6000; + public DyeColor catDefaultCollarColor = DyeColor.RED; + public boolean catTakeDamageFromWater = false; + public boolean catAlwaysDropExp = false; + private void catSettings() { + catRidable = getBoolean("mobs.cat.ridable", catRidable); + catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater); + catControllable = getBoolean("mobs.cat.controllable", catControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.cat.attributes.max-health", catMaxHealth); + set("mobs.cat.attributes.max-health", null); + set("mobs.cat.attributes.max_health", oldValue); + } + catMaxHealth = getDouble("mobs.cat.attributes.max_health", catMaxHealth); + catScale = Mth.clamp(getDouble("mobs.cat.attributes.scale", catScale), 0.0625D, 16.0D); + catSpawnDelay = getInt("mobs.cat.spawn-delay", catSpawnDelay); + catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange); + catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange); + catBreedingTicks = getInt("mobs.cat.breeding-delay-ticks", catBreedingTicks); + try { + catDefaultCollarColor = DyeColor.valueOf(getString("mobs.cat.default-collar-color", catDefaultCollarColor.name())); + } catch (IllegalArgumentException ignore) { + catDefaultCollarColor = DyeColor.RED; + } + catTakeDamageFromWater = getBoolean("mobs.cat.takes-damage-from-water", catTakeDamageFromWater); + catAlwaysDropExp = getBoolean("mobs.cat.always-drop-exp", catAlwaysDropExp); + } + + public boolean caveSpiderRidable = false; + public boolean caveSpiderRidableInWater = true; + public boolean caveSpiderControllable = true; + public double caveSpiderMaxHealth = 12.0D; + public double caveSpiderScale = 1.0D; + public boolean caveSpiderTakeDamageFromWater = false; + public boolean caveSpiderAlwaysDropExp = false; + private void caveSpiderSettings() { + caveSpiderRidable = getBoolean("mobs.cave_spider.ridable", caveSpiderRidable); + caveSpiderRidableInWater = getBoolean("mobs.cave_spider.ridable-in-water", caveSpiderRidableInWater); + caveSpiderControllable = getBoolean("mobs.cave_spider.controllable", caveSpiderControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.cave_spider.attributes.max-health", caveSpiderMaxHealth); + set("mobs.cave_spider.attributes.max-health", null); + set("mobs.cave_spider.attributes.max_health", oldValue); + } + caveSpiderMaxHealth = getDouble("mobs.cave_spider.attributes.max_health", caveSpiderMaxHealth); + caveSpiderScale = Mth.clamp(getDouble("mobs.cave_spider.attributes.scale", caveSpiderScale), 0.0625D, 16.0D); + caveSpiderTakeDamageFromWater = getBoolean("mobs.cave_spider.takes-damage-from-water", caveSpiderTakeDamageFromWater); + caveSpiderAlwaysDropExp = getBoolean("mobs.cave_spider.always-drop-exp", caveSpiderAlwaysDropExp); + } + + public boolean chickenRidable = false; + public boolean chickenRidableInWater = false; + public boolean chickenControllable = true; + public double chickenMaxHealth = 4.0D; + public double chickenScale = 1.0D; + public boolean chickenRetaliate = false; + public int chickenBreedingTicks = 6000; + public boolean chickenTakeDamageFromWater = false; + public boolean chickenAlwaysDropExp = false; + private void chickenSettings() { + chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable); + chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater); + chickenControllable = getBoolean("mobs.chicken.controllable", chickenControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.chicken.attributes.max-health", chickenMaxHealth); + set("mobs.chicken.attributes.max-health", null); + set("mobs.chicken.attributes.max_health", oldValue); + } + chickenMaxHealth = getDouble("mobs.chicken.attributes.max_health", chickenMaxHealth); + chickenScale = Mth.clamp(getDouble("mobs.chicken.attributes.scale", chickenScale), 0.0625D, 16.0D); + chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate); + chickenBreedingTicks = getInt("mobs.chicken.breeding-delay-ticks", chickenBreedingTicks); + chickenTakeDamageFromWater = getBoolean("mobs.chicken.takes-damage-from-water", chickenTakeDamageFromWater); + chickenAlwaysDropExp = getBoolean("mobs.chicken.always-drop-exp", chickenAlwaysDropExp); + } + + public boolean codRidable = false; + public boolean codControllable = true; + public double codMaxHealth = 3.0D; + public double codScale = 1.0D; + public boolean codTakeDamageFromWater = false; + public boolean codAlwaysDropExp = false; + private void codSettings() { + codRidable = getBoolean("mobs.cod.ridable", codRidable); + codControllable = getBoolean("mobs.cod.controllable", codControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.cod.attributes.max-health", codMaxHealth); + set("mobs.cod.attributes.max-health", null); + set("mobs.cod.attributes.max_health", oldValue); + } + codMaxHealth = getDouble("mobs.cod.attributes.max_health", codMaxHealth); + codScale = Mth.clamp(getDouble("mobs.cod.attributes.scale", codScale), 0.0625D, 16.0D); + codTakeDamageFromWater = getBoolean("mobs.cod.takes-damage-from-water", codTakeDamageFromWater); + codAlwaysDropExp = getBoolean("mobs.cod.always-drop-exp", codAlwaysDropExp); + } + + public boolean cowRidable = false; + public boolean cowRidableInWater = true; + public boolean cowControllable = true; + public double cowMaxHealth = 10.0D; + public double cowScale = 1.0D; + public int cowFeedMushrooms = 0; + public int cowBreedingTicks = 6000; + public boolean cowTakeDamageFromWater = false; + public double cowNaturallyAggressiveToPlayersChance = 0.0D; + public double cowNaturallyAggressiveToPlayersDamage = 2.0D; + public boolean cowAlwaysDropExp = false; + private void cowSettings() { + if (PurpurConfig.version < 22) { + double oldValue = getDouble("mobs.cow.naturally-aggressive-to-players-chance", cowNaturallyAggressiveToPlayersChance); + set("mobs.cow.naturally-aggressive-to-players-chance", null); + set("mobs.cow.naturally-aggressive-to-players.chance", oldValue); + } + cowRidable = getBoolean("mobs.cow.ridable", cowRidable); + cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater); + cowControllable = getBoolean("mobs.cow.controllable", cowControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.cow.attributes.max-health", cowMaxHealth); + set("mobs.cow.attributes.max-health", null); + set("mobs.cow.attributes.max_health", oldValue); + } + cowMaxHealth = getDouble("mobs.cow.attributes.max_health", cowMaxHealth); + cowScale = Mth.clamp(getDouble("mobs.cow.attributes.scale", cowScale), 0.0625D, 16.0D); + cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms); + cowBreedingTicks = getInt("mobs.cow.breeding-delay-ticks", cowBreedingTicks); + cowTakeDamageFromWater = getBoolean("mobs.cow.takes-damage-from-water", cowTakeDamageFromWater); + cowNaturallyAggressiveToPlayersChance = getDouble("mobs.cow.naturally-aggressive-to-players.chance", cowNaturallyAggressiveToPlayersChance); + cowNaturallyAggressiveToPlayersDamage = getDouble("mobs.cow.naturally-aggressive-to-players.damage", cowNaturallyAggressiveToPlayersDamage); + cowAlwaysDropExp = getBoolean("mobs.cow.always-drop-exp", cowAlwaysDropExp); + } + + public boolean creakingRidable = false; + public boolean creakingRidableInWater = true; + public boolean creakingControllable = true; + public double creakingMaxHealth = 1.0D; + public double creakingScale = 1.0D; + private void creakingSettings() { + creakingRidable = getBoolean("mobs.creaking.ridable", creakingRidable); + creakingRidableInWater = getBoolean("mobs.creaking.ridable-in-water", creakingRidableInWater); + creakingControllable = getBoolean("mobs.creaking.controllable", creakingControllable); + creakingMaxHealth = getDouble("mobs.creaking.attributes.max_health", creakingMaxHealth); + creakingScale = Mth.clamp(getDouble("mobs.creaking.attributes.scale", creakingScale), 0.0625D, 16.0D); + } + + public boolean creeperRidable = false; + public boolean creeperRidableInWater = true; + public boolean creeperControllable = true; + public double creeperMaxHealth = 20.0D; + public double creeperScale = 1.0D; + public double creeperChargedChance = 0.0D; + public boolean creeperAllowGriefing = true; + public boolean creeperBypassMobGriefing = false; + public boolean creeperTakeDamageFromWater = false; + public boolean creeperExplodeWhenKilled = false; + public boolean creeperHealthRadius = false; + public boolean creeperAlwaysDropExp = false; + public double creeperHeadVisibilityPercent = 0.5D; + public boolean creeperEncircleTarget = false; + private void creeperSettings() { + creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable); + creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater); + creeperControllable = getBoolean("mobs.creeper.controllable", creeperControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.creeper.attributes.max-health", creeperMaxHealth); + set("mobs.creeper.attributes.max-health", null); + set("mobs.creeper.attributes.max_health", oldValue); + } + creeperMaxHealth = getDouble("mobs.creeper.attributes.max_health", creeperMaxHealth); + creeperScale = Mth.clamp(getDouble("mobs.creeper.attributes.scale", creeperScale), 0.0625D, 16.0D); + creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); + creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing); + creeperBypassMobGriefing = getBoolean("mobs.creeper.bypass-mob-griefing", creeperBypassMobGriefing); + creeperTakeDamageFromWater = getBoolean("mobs.creeper.takes-damage-from-water", creeperTakeDamageFromWater); + creeperExplodeWhenKilled = getBoolean("mobs.creeper.explode-when-killed", creeperExplodeWhenKilled); + creeperHealthRadius = getBoolean("mobs.creeper.health-impacts-explosion", creeperHealthRadius); + creeperAlwaysDropExp = getBoolean("mobs.creeper.always-drop-exp", creeperAlwaysDropExp); + creeperHeadVisibilityPercent = getDouble("mobs.creeper.head-visibility-percent", creeperHeadVisibilityPercent); + creeperEncircleTarget = getBoolean("mobs.creeper.encircle-target", creeperEncircleTarget); + } + + public boolean dolphinRidable = false; + public boolean dolphinControllable = true; + public int dolphinSpitCooldown = 20; + public float dolphinSpitSpeed = 1.0F; + public float dolphinSpitDamage = 2.0F; + public double dolphinMaxHealth = 10.0D; + public double dolphinScale = 1.0D; + public boolean dolphinDisableTreasureSearching = false; + public boolean dolphinTakeDamageFromWater = false; + public double dolphinNaturallyAggressiveToPlayersChance = 0.0D; + public boolean dolphinAlwaysDropExp = false; + private void dolphinSettings() { + dolphinRidable = getBoolean("mobs.dolphin.ridable", dolphinRidable); + dolphinControllable = getBoolean("mobs.dolphin.controllable", dolphinControllable); + dolphinSpitCooldown = getInt("mobs.dolphin.spit.cooldown", dolphinSpitCooldown); + dolphinSpitSpeed = (float) getDouble("mobs.dolphin.spit.speed", dolphinSpitSpeed); + dolphinSpitDamage = (float) getDouble("mobs.dolphin.spit.damage", dolphinSpitDamage); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.dolphin.attributes.max-health", dolphinMaxHealth); + set("mobs.dolphin.attributes.max-health", null); + set("mobs.dolphin.attributes.max_health", oldValue); + } + dolphinMaxHealth = getDouble("mobs.dolphin.attributes.max_health", dolphinMaxHealth); + dolphinScale = Mth.clamp(getDouble("mobs.dolphin.attributes.scale", dolphinScale), 0.0625D, 16.0D); + dolphinDisableTreasureSearching = getBoolean("mobs.dolphin.disable-treasure-searching", dolphinDisableTreasureSearching); + dolphinTakeDamageFromWater = getBoolean("mobs.dolphin.takes-damage-from-water", dolphinTakeDamageFromWater); + dolphinNaturallyAggressiveToPlayersChance = getDouble("mobs.dolphin.naturally-aggressive-to-players-chance", dolphinNaturallyAggressiveToPlayersChance); + dolphinAlwaysDropExp = getBoolean("mobs.dolphin.always-drop-exp", dolphinAlwaysDropExp); + } + + public boolean donkeyRidableInWater = false; + public double donkeyMaxHealthMin = 15.0D; + public double donkeyMaxHealthMax = 30.0D; + public double donkeyJumpStrengthMin = 0.5D; + public double donkeyJumpStrengthMax = 0.5D; + public double donkeyMovementSpeedMin = 0.175D; + public double donkeyMovementSpeedMax = 0.175D; + public int donkeyBreedingTicks = 6000; + public boolean donkeyTakeDamageFromWater = false; + public boolean donkeyAlwaysDropExp = false; + private void donkeySettings() { + donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.donkey.attributes.max-health.min", donkeyMaxHealthMin); + double oldMax = getDouble("mobs.donkey.attributes.max-health.max", donkeyMaxHealthMax); + set("mobs.donkey.attributes.max-health", null); + set("mobs.donkey.attributes.max_health.min", oldMin); + set("mobs.donkey.attributes.max_health.max", oldMax); + } + donkeyMaxHealthMin = getDouble("mobs.donkey.attributes.max_health.min", donkeyMaxHealthMin); + donkeyMaxHealthMax = getDouble("mobs.donkey.attributes.max_health.max", donkeyMaxHealthMax); + donkeyJumpStrengthMin = getDouble("mobs.donkey.attributes.jump_strength.min", donkeyJumpStrengthMin); + donkeyJumpStrengthMax = getDouble("mobs.donkey.attributes.jump_strength.max", donkeyJumpStrengthMax); + donkeyMovementSpeedMin = getDouble("mobs.donkey.attributes.movement_speed.min", donkeyMovementSpeedMin); + donkeyMovementSpeedMax = getDouble("mobs.donkey.attributes.movement_speed.max", donkeyMovementSpeedMax); + donkeyBreedingTicks = getInt("mobs.donkey.breeding-delay-ticks", donkeyBreedingTicks); + donkeyTakeDamageFromWater = getBoolean("mobs.donkey.takes-damage-from-water", donkeyTakeDamageFromWater); + donkeyAlwaysDropExp = getBoolean("mobs.donkey.always-drop-exp", donkeyAlwaysDropExp); + } + + public boolean drownedRidable = false; + public boolean drownedRidableInWater = true; + public boolean drownedControllable = true; + public double drownedMaxHealth = 20.0D; + public double drownedScale = 1.0D; + public double drownedSpawnReinforcements = 0.1D; + public boolean drownedJockeyOnlyBaby = true; + public double drownedJockeyChance = 0.05D; + public boolean drownedJockeyTryExistingChickens = true; + public boolean drownedTakeDamageFromWater = false; + public boolean drownedBreakDoors = false; + public boolean drownedAlwaysDropExp = false; + private void drownedSettings() { + drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable); + drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater); + drownedControllable = getBoolean("mobs.drowned.controllable", drownedControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.drowned.attributes.max-health", drownedMaxHealth); + set("mobs.drowned.attributes.max-health", null); + set("mobs.drowned.attributes.max_health", oldValue); + } + drownedMaxHealth = getDouble("mobs.drowned.attributes.max_health", drownedMaxHealth); + drownedScale = Mth.clamp(getDouble("mobs.drowned.attributes.scale", drownedScale), 0.0625D, 16.0D); + drownedSpawnReinforcements = getDouble("mobs.drowned.attributes.spawn_reinforcements", drownedSpawnReinforcements); + drownedJockeyOnlyBaby = getBoolean("mobs.drowned.jockey.only-babies", drownedJockeyOnlyBaby); + drownedJockeyChance = getDouble("mobs.drowned.jockey.chance", drownedJockeyChance); + drownedJockeyTryExistingChickens = getBoolean("mobs.drowned.jockey.try-existing-chickens", drownedJockeyTryExistingChickens); + drownedTakeDamageFromWater = getBoolean("mobs.drowned.takes-damage-from-water", drownedTakeDamageFromWater); + drownedBreakDoors = getBoolean("mobs.drowned.can-break-doors", drownedBreakDoors); + drownedAlwaysDropExp = getBoolean("mobs.drowned.always-drop-exp", drownedAlwaysDropExp); + } + + public boolean elderGuardianRidable = false; + public boolean elderGuardianControllable = true; + public double elderGuardianMaxHealth = 80.0D; + public double elderGuardianScale = 1.0D; + public boolean elderGuardianTakeDamageFromWater = false; + public boolean elderGuardianAlwaysDropExp = false; + private void elderGuardianSettings() { + elderGuardianRidable = getBoolean("mobs.elder_guardian.ridable", elderGuardianRidable); + elderGuardianControllable = getBoolean("mobs.elder_guardian.controllable", elderGuardianControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.elder_guardian.attributes.max-health", elderGuardianMaxHealth); + set("mobs.elder_guardian.attributes.max-health", null); + set("mobs.elder_guardian.attributes.max_health", oldValue); + } + elderGuardianMaxHealth = getDouble("mobs.elder_guardian.attributes.max_health", elderGuardianMaxHealth); + elderGuardianScale = Mth.clamp(getDouble("mobs.elder_guardian.attributes.scale", elderGuardianScale), 0.0625D, 16.0D); + elderGuardianTakeDamageFromWater = getBoolean("mobs.elder_guardian.takes-damage-from-water", elderGuardianTakeDamageFromWater); + elderGuardianAlwaysDropExp = getBoolean("mobs.elder_guardian.always-drop-exp", elderGuardianAlwaysDropExp); + } + + public boolean enchantmentTableLapisPersists = false; + private void enchantmentTableSettings() { + enchantmentTableLapisPersists = getBoolean("blocks.enchantment-table.lapis-persists", enchantmentTableLapisPersists); + } + + public boolean enderDragonRidable = false; + public boolean enderDragonRidableInWater = true; + public boolean enderDragonControllable = true; + public double enderDragonMaxY = 320D; + public double enderDragonMaxHealth = 200.0D; + public boolean enderDragonAlwaysDropsFullExp = false; + public boolean enderDragonBypassMobGriefing = false; + public boolean enderDragonTakeDamageFromWater = false; + public boolean enderDragonCanRideVehicles = false; + private void enderDragonSettings() { + enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable); + enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater); + enderDragonControllable = getBoolean("mobs.ender_dragon.controllable", enderDragonControllable); + enderDragonMaxY = getDouble("mobs.ender_dragon.ridable-max-y", enderDragonMaxY); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.ender_dragon.max-health", enderDragonMaxHealth); + set("mobs.ender_dragon.max-health", null); + set("mobs.ender_dragon.attributes.max_health", oldValue); + } else if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.ender_dragon.attributes.max-health", enderDragonMaxHealth); + set("mobs.ender_dragon.attributes.max-health", null); + set("mobs.ender_dragon.attributes.max_health", oldValue); + } + enderDragonMaxHealth = getDouble("mobs.ender_dragon.attributes.max_health", enderDragonMaxHealth); + enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp); + enderDragonBypassMobGriefing = getBoolean("mobs.ender_dragon.bypass-mob-griefing", enderDragonBypassMobGriefing); + enderDragonTakeDamageFromWater = getBoolean("mobs.ender_dragon.takes-damage-from-water", enderDragonTakeDamageFromWater); + enderDragonCanRideVehicles = getBoolean("mobs.ender_dragon.can-ride-vehicles", enderDragonCanRideVehicles); + } + + public boolean endermanRidable = false; + public boolean endermanRidableInWater = true; + public boolean endermanControllable = true; + public double endermanMaxHealth = 40.0D; + public double endermanScale = 1.0D; + public boolean endermanAllowGriefing = true; + public boolean endermanDespawnEvenWithBlock = false; + public boolean endermanBypassMobGriefing = false; + public boolean endermanTakeDamageFromWater = true; + public boolean endermanAggroEndermites = true; + public boolean endermanAggroEndermitesOnlyIfPlayerSpawned = false; + public boolean endermanDisableStareAggro = false; + public boolean endermanIgnoreProjectiles = false; + public boolean endermanAlwaysDropExp = false; + private void endermanSettings() { + endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); + endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); + endermanControllable = getBoolean("mobs.enderman.controllable", endermanControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.enderman.attributes.max-health", endermanMaxHealth); + set("mobs.enderman.attributes.max-health", null); + set("mobs.enderman.attributes.max_health", oldValue); + } + if (PurpurConfig.version < 15) { + // remove old option + set("mobs.enderman.aggressive-towards-spawned-endermites", null); + } + endermanMaxHealth = getDouble("mobs.enderman.attributes.max_health", endermanMaxHealth); + endermanScale = Mth.clamp(getDouble("mobs.enderman.attributes.scale", endermanScale), 0.0625D, 16.0D); + endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing); + endermanDespawnEvenWithBlock = getBoolean("mobs.enderman.can-despawn-with-held-block", endermanDespawnEvenWithBlock); + endermanBypassMobGriefing = getBoolean("mobs.enderman.bypass-mob-griefing", endermanBypassMobGriefing); + endermanTakeDamageFromWater = getBoolean("mobs.enderman.takes-damage-from-water", endermanTakeDamageFromWater); + endermanAggroEndermites = getBoolean("mobs.enderman.aggressive-towards-endermites", endermanAggroEndermites); + endermanAggroEndermitesOnlyIfPlayerSpawned = getBoolean("mobs.enderman.aggressive-towards-endermites-only-spawned-by-player-thrown-ender-pearls", endermanAggroEndermitesOnlyIfPlayerSpawned); + endermanDisableStareAggro = getBoolean("mobs.enderman.disable-player-stare-aggression", endermanDisableStareAggro); + endermanIgnoreProjectiles = getBoolean("mobs.enderman.ignore-projectiles", endermanIgnoreProjectiles); + endermanAlwaysDropExp = getBoolean("mobs.enderman.always-drop-exp", endermanAlwaysDropExp); + } + + public boolean endermiteRidable = false; + public boolean endermiteRidableInWater = true; + public boolean endermiteControllable = true; + public double endermiteMaxHealth = 8.0D; + public double endermiteScale = 1.0D; + public boolean endermiteTakeDamageFromWater = false; + public boolean endermiteAlwaysDropExp = false; + private void endermiteSettings() { + endermiteRidable = getBoolean("mobs.endermite.ridable", endermiteRidable); + endermiteRidableInWater = getBoolean("mobs.endermite.ridable-in-water", endermiteRidableInWater); + endermiteControllable = getBoolean("mobs.endermite.controllable", endermiteControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.endermite.attributes.max-health", endermiteMaxHealth); + set("mobs.endermite.attributes.max-health", null); + set("mobs.endermite.attributes.max_health", oldValue); + } + endermiteMaxHealth = getDouble("mobs.endermite.attributes.max_health", endermiteMaxHealth); + endermiteScale = Mth.clamp(getDouble("mobs.endermite.attributes.scale", endermiteScale), 0.0625D, 16.0D); + endermiteTakeDamageFromWater = getBoolean("mobs.endermite.takes-damage-from-water", endermiteTakeDamageFromWater); + endermiteAlwaysDropExp = getBoolean("mobs.endermite.always-drop-exp", endermiteAlwaysDropExp); + } + + public boolean evokerRidable = false; + public boolean evokerRidableInWater = true; + public boolean evokerControllable = true; + public double evokerMaxHealth = 24.0D; + public double evokerScale = 1.0D; + public boolean evokerBypassMobGriefing = false; + public boolean evokerTakeDamageFromWater = false; + public boolean evokerAlwaysDropExp = false; + private void evokerSettings() { + evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable); + evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater); + evokerControllable = getBoolean("mobs.evoker.controllable", evokerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.evoker.attributes.max-health", evokerMaxHealth); + set("mobs.evoker.attributes.max-health", null); + set("mobs.evoker.attributes.max_health", oldValue); + } + evokerMaxHealth = getDouble("mobs.evoker.attributes.max_health", evokerMaxHealth); + evokerScale = Mth.clamp(getDouble("mobs.evoker.attributes.scale", evokerScale), 0.0625D, 16.0D); + evokerBypassMobGriefing = getBoolean("mobs.evoker.bypass-mob-griefing", evokerBypassMobGriefing); + evokerTakeDamageFromWater = getBoolean("mobs.evoker.takes-damage-from-water", evokerTakeDamageFromWater); + evokerAlwaysDropExp = getBoolean("mobs.evoker.always-drop-exp", evokerAlwaysDropExp); + } + + public boolean foxRidable = false; + public boolean foxRidableInWater = true; + public boolean foxControllable = true; + public double foxMaxHealth = 10.0D; + public double foxScale = 1.0D; + public boolean foxTypeChangesWithTulips = false; + public int foxBreedingTicks = 6000; + public boolean foxBypassMobGriefing = false; + public boolean foxTakeDamageFromWater = false; + public boolean foxAlwaysDropExp = false; + private void foxSettings() { + foxRidable = getBoolean("mobs.fox.ridable", foxRidable); + foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater); + foxControllable = getBoolean("mobs.fox.controllable", foxControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.fox.attributes.max-health", foxMaxHealth); + set("mobs.fox.attributes.max-health", null); + set("mobs.fox.attributes.max_health", oldValue); + } + foxMaxHealth = getDouble("mobs.fox.attributes.max_health", foxMaxHealth); + foxScale = Mth.clamp(getDouble("mobs.fox.attributes.scale", foxScale), 0.0625D, 16.0D); + foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); + foxBreedingTicks = getInt("mobs.fox.breeding-delay-ticks", foxBreedingTicks); + foxBypassMobGriefing = getBoolean("mobs.fox.bypass-mob-griefing", foxBypassMobGriefing); + foxTakeDamageFromWater = getBoolean("mobs.fox.takes-damage-from-water", foxTakeDamageFromWater); + foxAlwaysDropExp = getBoolean("mobs.fox.always-drop-exp", foxAlwaysDropExp); + } + + public boolean frogRidable = false; + public boolean frogRidableInWater = true; + public boolean frogControllable = true; + public float frogRidableJumpHeight = 0.65F; + public int frogBreedingTicks = 6000; + private void frogSettings() { + frogRidable = getBoolean("mobs.frog.ridable", frogRidable); + frogRidableInWater = getBoolean("mobs.frog.ridable-in-water", frogRidableInWater); + frogControllable = getBoolean("mobs.frog.controllable", frogControllable); + frogRidableJumpHeight = (float) getDouble("mobs.frog.ridable-jump-height", frogRidableJumpHeight); + frogBreedingTicks = getInt("mobs.frog.breeding-delay-ticks", frogBreedingTicks); + } + + public boolean ghastRidable = false; + public boolean ghastRidableInWater = true; + public boolean ghastControllable = true; + public double ghastMaxY = 320D; + public double ghastMaxHealth = 10.0D; + public double ghastScale = 1.0D; + public boolean ghastTakeDamageFromWater = false; + public boolean ghastAlwaysDropExp = false; + private void ghastSettings() { + ghastRidable = getBoolean("mobs.ghast.ridable", ghastRidable); + ghastRidableInWater = getBoolean("mobs.ghast.ridable-in-water", ghastRidableInWater); + ghastControllable = getBoolean("mobs.ghast.controllable", ghastControllable); + ghastMaxY = getDouble("mobs.ghast.ridable-max-y", ghastMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.ghast.attributes.max-health", ghastMaxHealth); + set("mobs.ghast.attributes.max-health", null); + set("mobs.ghast.attributes.max_health", oldValue); + } + ghastMaxHealth = getDouble("mobs.ghast.attributes.max_health", ghastMaxHealth); + ghastScale = Mth.clamp(getDouble("mobs.ghast.attributes.scale", ghastScale), 0.0625D, 16.0D); + ghastTakeDamageFromWater = getBoolean("mobs.ghast.takes-damage-from-water", ghastTakeDamageFromWater); + ghastAlwaysDropExp = getBoolean("mobs.ghast.always-drop-exp", ghastAlwaysDropExp); + } + + public boolean giantRidable = false; + public boolean giantRidableInWater = true; + public boolean giantControllable = true; + public double giantMovementSpeed = 0.5D; + public double giantAttackDamage = 50.0D; + public double giantMaxHealth = 100.0D; + public double giantScale = 1.0D; + public float giantStepHeight = 2.0F; + public float giantJumpHeight = 1.0F; + public boolean giantHaveAI = false; + public boolean giantHaveHostileAI = false; + public boolean giantTakeDamageFromWater = false; + public boolean giantAlwaysDropExp = false; + private void giantSettings() { + giantRidable = getBoolean("mobs.giant.ridable", giantRidable); + giantRidableInWater = getBoolean("mobs.giant.ridable-in-water", giantRidableInWater); + giantControllable = getBoolean("mobs.giant.controllable", giantControllable); + giantMovementSpeed = getDouble("mobs.giant.movement-speed", giantMovementSpeed); + giantAttackDamage = getDouble("mobs.giant.attack-damage", giantAttackDamage); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.giant.max-health", giantMaxHealth); + set("mobs.giant.max-health", null); + set("mobs.giant.attributes.max_health", oldValue); + } else if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.giant.attributes.max-health", giantMaxHealth); + set("mobs.giant.attributes.max-health", null); + set("mobs.giant.attributes.max_health", oldValue); + } + giantMaxHealth = getDouble("mobs.giant.attributes.max_health", giantMaxHealth); + giantScale = Mth.clamp(getDouble("mobs.giant.attributes.scale", giantScale), 0.0625D, 16.0D); + giantStepHeight = (float) getDouble("mobs.giant.step-height", giantStepHeight); + giantJumpHeight = (float) getDouble("mobs.giant.jump-height", giantJumpHeight); + giantHaveAI = getBoolean("mobs.giant.have-ai", giantHaveAI); + giantHaveHostileAI = getBoolean("mobs.giant.have-hostile-ai", giantHaveHostileAI); + giantTakeDamageFromWater = getBoolean("mobs.giant.takes-damage-from-water", giantTakeDamageFromWater); + giantAlwaysDropExp = getBoolean("mobs.giant.always-drop-exp", giantAlwaysDropExp); + } + + public boolean glowSquidRidable = false; + public boolean glowSquidControllable = true; + public double glowSquidMaxHealth = 10.0D; + public double glowSquidScale = 1.0D; + public boolean glowSquidsCanFly = false; + public boolean glowSquidTakeDamageFromWater = false; + public boolean glowSquidAlwaysDropExp = false; + private void glowSquidSettings() { + glowSquidRidable = getBoolean("mobs.glow_squid.ridable", glowSquidRidable); + glowSquidControllable = getBoolean("mobs.glow_squid.controllable", glowSquidControllable); + glowSquidMaxHealth = getDouble("mobs.glow_squid.attributes.max_health", glowSquidMaxHealth); + glowSquidScale = Mth.clamp(getDouble("mobs.glow_squid.attributes.scale", glowSquidScale), 0.0625D, 16.0D); + glowSquidsCanFly = getBoolean("mobs.glow_squid.can-fly", glowSquidsCanFly); + glowSquidTakeDamageFromWater = getBoolean("mobs.glow_squid.takes-damage-from-water", glowSquidTakeDamageFromWater); + glowSquidAlwaysDropExp = getBoolean("mobs.glow_squid.always-drop-exp", glowSquidAlwaysDropExp); + } + + public boolean goatRidable = false; + public boolean goatRidableInWater = true; + public boolean goatControllable = true; + public double goatMaxHealth = 10.0D; + public double goatScale = 1.0D; + public int goatBreedingTicks = 6000; + public boolean goatTakeDamageFromWater = false; + public boolean goatAlwaysDropExp = false; + private void goatSettings() { + goatRidable = getBoolean("mobs.goat.ridable", goatRidable); + goatRidableInWater = getBoolean("mobs.goat.ridable-in-water", goatRidableInWater); + goatControllable = getBoolean("mobs.goat.controllable", goatControllable); + goatMaxHealth = getDouble("mobs.goat.attributes.max_health", goatMaxHealth); + goatScale = Mth.clamp(getDouble("mobs.goat.attributes.scale", goatScale), 0.0625D, 16.0D); + goatBreedingTicks = getInt("mobs.goat.breeding-delay-ticks", goatBreedingTicks); + goatTakeDamageFromWater = getBoolean("mobs.goat.takes-damage-from-water", goatTakeDamageFromWater); + goatAlwaysDropExp = getBoolean("mobs.goat.always-drop-exp", goatAlwaysDropExp); + } + + public boolean guardianRidable = false; + public boolean guardianControllable = true; + public double guardianMaxHealth = 30.0D; + public double guardianScale = 1.0D; + public boolean guardianTakeDamageFromWater = false; + public boolean guardianAlwaysDropExp = false; + private void guardianSettings() { + guardianRidable = getBoolean("mobs.guardian.ridable", guardianRidable); + guardianControllable = getBoolean("mobs.guardian.controllable", guardianControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.guardian.attributes.max-health", guardianMaxHealth); + set("mobs.guardian.attributes.max-health", null); + set("mobs.guardian.attributes.max_health", oldValue); + } + guardianMaxHealth = getDouble("mobs.guardian.attributes.max_health", guardianMaxHealth); + guardianScale = Mth.clamp(getDouble("mobs.guardian.attributes.scale", guardianScale), 0.0625D, 16.0D); + guardianTakeDamageFromWater = getBoolean("mobs.guardian.takes-damage-from-water", guardianTakeDamageFromWater); + guardianAlwaysDropExp = getBoolean("mobs.guardian.always-drop-exp", guardianAlwaysDropExp); + } + + public boolean forceHalloweenSeason = false; + public float chanceHeadHalloweenOnEntity = 0.25F; + private void halloweenSetting() { + forceHalloweenSeason = getBoolean("gameplay-mechanics.halloween.force", forceHalloweenSeason); + chanceHeadHalloweenOnEntity = (float) getDouble("gameplay-mechanics.halloween.head-chance", chanceHeadHalloweenOnEntity); + } + + public boolean hoglinRidable = false; + public boolean hoglinRidableInWater = true; + public boolean hoglinControllable = true; + public double hoglinMaxHealth = 40.0D; + public double hoglinScale = 1.0D; + public int hoglinBreedingTicks = 6000; + public boolean hoglinTakeDamageFromWater = false; + public boolean hoglinAlwaysDropExp = false; + private void hoglinSettings() { + hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable); + hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater); + hoglinControllable = getBoolean("mobs.hoglin.controllable", hoglinControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.hoglin.attributes.max-health", hoglinMaxHealth); + set("mobs.hoglin.attributes.max-health", null); + set("mobs.hoglin.attributes.max_health", oldValue); + } + hoglinMaxHealth = getDouble("mobs.hoglin.attributes.max_health", hoglinMaxHealth); + hoglinScale = Mth.clamp(getDouble("mobs.hoglin.attributes.scale", hoglinScale), 0.0625D, 16.0D); + hoglinBreedingTicks = getInt("mobs.hoglin.breeding-delay-ticks", hoglinBreedingTicks); + hoglinTakeDamageFromWater = getBoolean("mobs.hoglin.takes-damage-from-water", hoglinTakeDamageFromWater); + hoglinAlwaysDropExp = getBoolean("mobs.hoglin.always-drop-exp", hoglinAlwaysDropExp); + } + + public boolean horseRidableInWater = false; + public double horseMaxHealthMin = 15.0D; + public double horseMaxHealthMax = 30.0D; + public double horseJumpStrengthMin = 0.4D; + public double horseJumpStrengthMax = 1.0D; + public double horseMovementSpeedMin = 0.1125D; + public double horseMovementSpeedMax = 0.3375D; + public int horseBreedingTicks = 6000; + public boolean horseTakeDamageFromWater = false; + public boolean horseAlwaysDropExp = false; + private void horseSettings() { + horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.horse.attributes.max-health.min", horseMaxHealthMin); + double oldMax = getDouble("mobs.horse.attributes.max-health.max", horseMaxHealthMax); + set("mobs.horse.attributes.max-health", null); + set("mobs.horse.attributes.max_health.min", oldMin); + set("mobs.horse.attributes.max_health.max", oldMax); + } + horseMaxHealthMin = getDouble("mobs.horse.attributes.max_health.min", horseMaxHealthMin); + horseMaxHealthMax = getDouble("mobs.horse.attributes.max_health.max", horseMaxHealthMax); + horseJumpStrengthMin = getDouble("mobs.horse.attributes.jump_strength.min", horseJumpStrengthMin); + horseJumpStrengthMax = getDouble("mobs.horse.attributes.jump_strength.max", horseJumpStrengthMax); + horseMovementSpeedMin = getDouble("mobs.horse.attributes.movement_speed.min", horseMovementSpeedMin); + horseMovementSpeedMax = getDouble("mobs.horse.attributes.movement_speed.max", horseMovementSpeedMax); + horseBreedingTicks = getInt("mobs.horse.breeding-delay-ticks", horseBreedingTicks); + horseTakeDamageFromWater = getBoolean("mobs.horse.takes-damage-from-water", horseTakeDamageFromWater); + horseAlwaysDropExp = getBoolean("mobs.horse.always-drop-exp", horseAlwaysDropExp); + } + + public boolean huskRidable = false; + public boolean huskRidableInWater = true; + public boolean huskControllable = true; + public double huskMaxHealth = 20.0D; + public double huskScale = 1.0D; + public double huskSpawnReinforcements = 0.1D; + public boolean huskJockeyOnlyBaby = true; + public double huskJockeyChance = 0.05D; + public boolean huskJockeyTryExistingChickens = true; + public boolean huskTakeDamageFromWater = false; + public boolean huskAlwaysDropExp = false; + private void huskSettings() { + huskRidable = getBoolean("mobs.husk.ridable", huskRidable); + huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater); + huskControllable = getBoolean("mobs.husk.controllable", huskControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.husk.attributes.max-health", huskMaxHealth); + set("mobs.husk.attributes.max-health", null); + set("mobs.husk.attributes.max_health", oldValue); + } + huskMaxHealth = getDouble("mobs.husk.attributes.max_health", huskMaxHealth); + huskScale = Mth.clamp(getDouble("mobs.husk.attributes.scale", huskScale), 0.0625D, 16.0D); + huskSpawnReinforcements = getDouble("mobs.husk.attributes.spawn_reinforcements", huskSpawnReinforcements); + huskJockeyOnlyBaby = getBoolean("mobs.husk.jockey.only-babies", huskJockeyOnlyBaby); + huskJockeyChance = getDouble("mobs.husk.jockey.chance", huskJockeyChance); + huskJockeyTryExistingChickens = getBoolean("mobs.husk.jockey.try-existing-chickens", huskJockeyTryExistingChickens); + huskTakeDamageFromWater = getBoolean("mobs.husk.takes-damage-from-water", huskTakeDamageFromWater); + huskAlwaysDropExp = getBoolean("mobs.husk.always-drop-exp", huskAlwaysDropExp); + } + + public boolean illusionerRidable = false; + public boolean illusionerRidableInWater = true; + public boolean illusionerControllable = true; + public double illusionerMovementSpeed = 0.5D; + public double illusionerFollowRange = 18.0D; + public double illusionerMaxHealth = 32.0D; + public double illusionerScale = 1.0D; + public boolean illusionerTakeDamageFromWater = false; + public boolean illusionerAlwaysDropExp = false; + private void illusionerSettings() { + illusionerRidable = getBoolean("mobs.illusioner.ridable", illusionerRidable); + illusionerRidableInWater = getBoolean("mobs.illusioner.ridable-in-water", illusionerRidableInWater); + illusionerControllable = getBoolean("mobs.illusioner.controllable", illusionerControllable); + illusionerMovementSpeed = getDouble("mobs.illusioner.movement-speed", illusionerMovementSpeed); + illusionerFollowRange = getDouble("mobs.illusioner.follow-range", illusionerFollowRange); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.illusioner.max-health", illusionerMaxHealth); + set("mobs.illusioner.max-health", null); + set("mobs.illusioner.attributes.max_health", oldValue); + } else if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth); + set("mobs.illusioner.attributes.max-health", null); + set("mobs.illusioner.attributes.max_health", oldValue); + } + illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max_health", illusionerMaxHealth); + illusionerScale = Mth.clamp(getDouble("mobs.illusioner.attributes.scale", illusionerScale), 0.0625D, 16.0D); + illusionerTakeDamageFromWater = getBoolean("mobs.illusioner.takes-damage-from-water", illusionerTakeDamageFromWater); + illusionerAlwaysDropExp = getBoolean("mobs.illusioner.always-drop-exp", illusionerAlwaysDropExp); + } + + public boolean ironGolemRidable = false; + public boolean ironGolemRidableInWater = true; + public boolean ironGolemControllable = true; + public boolean ironGolemCanSwim = false; + public double ironGolemMaxHealth = 100.0D; + public double ironGolemScale = 1.0D; + public boolean ironGolemTakeDamageFromWater = false; + public boolean ironGolemPoppyCalm = false; + public boolean ironGolemHealCalm = false; + public boolean ironGolemAlwaysDropExp = false; + private void ironGolemSettings() { + ironGolemRidable = getBoolean("mobs.iron_golem.ridable", ironGolemRidable); + ironGolemRidableInWater = getBoolean("mobs.iron_golem.ridable-in-water", ironGolemRidableInWater); + ironGolemControllable = getBoolean("mobs.iron_golem.controllable", ironGolemControllable); + ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.iron_golem.attributes.max-health", ironGolemMaxHealth); + set("mobs.iron_golem.attributes.max-health", null); + set("mobs.iron_golem.attributes.max_health", oldValue); + } + ironGolemMaxHealth = getDouble("mobs.iron_golem.attributes.max_health", ironGolemMaxHealth); + ironGolemScale = Mth.clamp(getDouble("mobs.iron_golem.attributes.scale", ironGolemScale), 0.0625D, 16.0D); + ironGolemTakeDamageFromWater = getBoolean("mobs.iron_golem.takes-damage-from-water", ironGolemTakeDamageFromWater); + ironGolemPoppyCalm = getBoolean("mobs.iron_golem.poppy-calms-anger", ironGolemPoppyCalm); + ironGolemHealCalm = getBoolean("mobs.iron_golem.healing-calms-anger", ironGolemHealCalm); + ironGolemAlwaysDropExp = getBoolean("mobs.iron_golem.always-drop-exp", ironGolemAlwaysDropExp); + } + + public boolean llamaRidable = false; + public boolean llamaRidableInWater = false; + public boolean llamaControllable = true; + public double llamaMaxHealthMin = 15.0D; + public double llamaMaxHealthMax = 30.0D; + public double llamaJumpStrengthMin = 0.5D; + public double llamaJumpStrengthMax = 0.5D; + public double llamaMovementSpeedMin = 0.175D; + public double llamaMovementSpeedMax = 0.175D; + public int llamaBreedingTicks = 6000; + public boolean llamaTakeDamageFromWater = false; + public boolean llamaJoinCaravans = true; + public boolean llamaAlwaysDropExp = false; + private void llamaSettings() { + llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable); + llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater); + llamaControllable = getBoolean("mobs.llama.controllable", llamaControllable); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.llama.attributes.max-health.min", llamaMaxHealthMin); + double oldMax = getDouble("mobs.llama.attributes.max-health.max", llamaMaxHealthMax); + set("mobs.llama.attributes.max-health", null); + set("mobs.llama.attributes.max_health.min", oldMin); + set("mobs.llama.attributes.max_health.max", oldMax); + } + llamaMaxHealthMin = getDouble("mobs.llama.attributes.max_health.min", llamaMaxHealthMin); + llamaMaxHealthMax = getDouble("mobs.llama.attributes.max_health.max", llamaMaxHealthMax); + llamaJumpStrengthMin = getDouble("mobs.llama.attributes.jump_strength.min", llamaJumpStrengthMin); + llamaJumpStrengthMax = getDouble("mobs.llama.attributes.jump_strength.max", llamaJumpStrengthMax); + llamaMovementSpeedMin = getDouble("mobs.llama.attributes.movement_speed.min", llamaMovementSpeedMin); + llamaMovementSpeedMax = getDouble("mobs.llama.attributes.movement_speed.max", llamaMovementSpeedMax); + llamaBreedingTicks = getInt("mobs.llama.breeding-delay-ticks", llamaBreedingTicks); + llamaTakeDamageFromWater = getBoolean("mobs.llama.takes-damage-from-water", llamaTakeDamageFromWater); + llamaJoinCaravans = getBoolean("mobs.llama.join-caravans", llamaJoinCaravans); + llamaAlwaysDropExp = getBoolean("mobs.llama.always-drop-exp", llamaAlwaysDropExp); + } + + public boolean magmaCubeRidable = false; + public boolean magmaCubeRidableInWater = true; + public boolean magmaCubeControllable = true; + public String magmaCubeMaxHealth = "size * size"; + public String magmaCubeAttackDamage = "size"; + public Map magmaCubeMaxHealthCache = new HashMap<>(); + public Map magmaCubeAttackDamageCache = new HashMap<>(); + public boolean magmaCubeTakeDamageFromWater = false; + public boolean magmaCubeAlwaysDropExp = false; + private void magmaCubeSettings() { + magmaCubeRidable = getBoolean("mobs.magma_cube.ridable", magmaCubeRidable); + magmaCubeRidableInWater = getBoolean("mobs.magma_cube.ridable-in-water", magmaCubeRidableInWater); + magmaCubeControllable = getBoolean("mobs.magma_cube.controllable", magmaCubeControllable); + if (PurpurConfig.version < 10) { + String oldValue = getString("mobs.magma_cube.attributes.max-health", magmaCubeMaxHealth); + set("mobs.magma_cube.attributes.max-health", null); + set("mobs.magma_cube.attributes.max_health", oldValue); + } + magmaCubeMaxHealth = getString("mobs.magma_cube.attributes.max_health", magmaCubeMaxHealth); + magmaCubeAttackDamage = getString("mobs.magma_cube.attributes.attack_damage", magmaCubeAttackDamage); + magmaCubeMaxHealthCache.clear(); + magmaCubeAttackDamageCache.clear(); + magmaCubeTakeDamageFromWater = getBoolean("mobs.magma_cube.takes-damage-from-water", magmaCubeTakeDamageFromWater); + magmaCubeAlwaysDropExp = getBoolean("mobs.magma_cube.always-drop-exp", magmaCubeAlwaysDropExp); + } + + public boolean mooshroomRidable = false; + public boolean mooshroomRidableInWater = true; + public boolean mooshroomControllable = true; + public double mooshroomMaxHealth = 10.0D; + public double mooshroomScale = 1.0D; + public int mooshroomBreedingTicks = 6000; + public boolean mooshroomTakeDamageFromWater = false; + public boolean mooshroomAlwaysDropExp = false; + private void mooshroomSettings() { + mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable); + mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater); + mooshroomControllable = getBoolean("mobs.mooshroom.controllable", mooshroomControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.mooshroom.attributes.max-health", mooshroomMaxHealth); + set("mobs.mooshroom.attributes.max-health", null); + set("mobs.mooshroom.attributes.max_health", oldValue); + } + mooshroomMaxHealth = getDouble("mobs.mooshroom.attributes.max_health", mooshroomMaxHealth); + mooshroomScale = Mth.clamp(getDouble("mobs.mooshroom.attributes.scale", mooshroomScale), 0.0625D, 16.0D); + mooshroomBreedingTicks = getInt("mobs.mooshroom.breeding-delay-ticks", mooshroomBreedingTicks); + mooshroomTakeDamageFromWater = getBoolean("mobs.mooshroom.takes-damage-from-water", mooshroomTakeDamageFromWater); + mooshroomAlwaysDropExp = getBoolean("mobs.mooshroom.always-drop-exp", mooshroomAlwaysDropExp); + } + + public boolean muleRidableInWater = false; + public double muleMaxHealthMin = 15.0D; + public double muleMaxHealthMax = 30.0D; + public double muleJumpStrengthMin = 0.5D; + public double muleJumpStrengthMax = 0.5D; + public double muleMovementSpeedMin = 0.175D; + public double muleMovementSpeedMax = 0.175D; + public int muleBreedingTicks = 6000; + public boolean muleTakeDamageFromWater = false; + public boolean muleAlwaysDropExp = false; + private void muleSettings() { + muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.mule.attributes.max-health.min", muleMaxHealthMin); + double oldMax = getDouble("mobs.mule.attributes.max-health.max", muleMaxHealthMax); + set("mobs.mule.attributes.max-health", null); + set("mobs.mule.attributes.max_health.min", oldMin); + set("mobs.mule.attributes.max_health.max", oldMax); + } + muleMaxHealthMin = getDouble("mobs.mule.attributes.max_health.min", muleMaxHealthMin); + muleMaxHealthMax = getDouble("mobs.mule.attributes.max_health.max", muleMaxHealthMax); + muleJumpStrengthMin = getDouble("mobs.mule.attributes.jump_strength.min", muleJumpStrengthMin); + muleJumpStrengthMax = getDouble("mobs.mule.attributes.jump_strength.max", muleJumpStrengthMax); + muleMovementSpeedMin = getDouble("mobs.mule.attributes.movement_speed.min", muleMovementSpeedMin); + muleMovementSpeedMax = getDouble("mobs.mule.attributes.movement_speed.max", muleMovementSpeedMax); + muleBreedingTicks = getInt("mobs.mule.breeding-delay-ticks", muleBreedingTicks); + muleTakeDamageFromWater = getBoolean("mobs.mule.takes-damage-from-water", muleTakeDamageFromWater); + muleAlwaysDropExp = getBoolean("mobs.mule.always-drop-exp", muleAlwaysDropExp); + } + + public boolean ocelotRidable = false; + public boolean ocelotRidableInWater = true; + public boolean ocelotControllable = true; + public double ocelotMaxHealth = 10.0D; + public double ocelotScale = 1.0D; + public int ocelotBreedingTicks = 6000; + public boolean ocelotTakeDamageFromWater = false; + public boolean ocelotAlwaysDropExp = false; + public boolean ocelotSpawnUnderSeaLevel = false; + private void ocelotSettings() { + ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable); + ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater); + ocelotControllable = getBoolean("mobs.ocelot.controllable", ocelotControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.ocelot.attributes.max-health", ocelotMaxHealth); + set("mobs.ocelot.attributes.max-health", null); + set("mobs.ocelot.attributes.max_health", oldValue); + } + ocelotMaxHealth = getDouble("mobs.ocelot.attributes.max_health", ocelotMaxHealth); + ocelotScale = Mth.clamp(getDouble("mobs.ocelot.attributes.scale", ocelotScale), 0.0625D, 16.0D); + ocelotBreedingTicks = getInt("mobs.ocelot.breeding-delay-ticks", ocelotBreedingTicks); + ocelotTakeDamageFromWater = getBoolean("mobs.ocelot.takes-damage-from-water", ocelotTakeDamageFromWater); + ocelotAlwaysDropExp = getBoolean("mobs.ocelot.always-drop-exp", ocelotAlwaysDropExp); + ocelotSpawnUnderSeaLevel = getBoolean("mobs.ocelot.spawn-below-sea-level", ocelotSpawnUnderSeaLevel); + } + + public boolean pandaRidable = false; + public boolean pandaRidableInWater = true; + public boolean pandaControllable = true; + public double pandaMaxHealth = 20.0D; + public double pandaScale = 1.0D; + public int pandaBreedingTicks = 6000; + public boolean pandaTakeDamageFromWater = false; + public boolean pandaAlwaysDropExp = false; + private void pandaSettings() { + pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable); + pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater); + pandaControllable = getBoolean("mobs.panda.controllable", pandaControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.panda.attributes.max-health", pandaMaxHealth); + set("mobs.panda.attributes.max-health", null); + set("mobs.panda.attributes.max_health", oldValue); + } + pandaMaxHealth = getDouble("mobs.panda.attributes.max_health", pandaMaxHealth); + pandaScale = Mth.clamp(getDouble("mobs.panda.attributes.scale", pandaScale), 0.0625D, 16.0D); + pandaBreedingTicks = getInt("mobs.panda.breeding-delay-ticks", pandaBreedingTicks); + pandaTakeDamageFromWater = getBoolean("mobs.panda.takes-damage-from-water", pandaTakeDamageFromWater); + pandaAlwaysDropExp = getBoolean("mobs.panda.always-drop-exp", pandaAlwaysDropExp); + } + + public boolean parrotRidable = false; + public boolean parrotRidableInWater = true; + public boolean parrotControllable = true; + public double parrotMaxY = 320D; + public double parrotMaxHealth = 6.0D; + public double parrotScale = 1.0D; + public boolean parrotTakeDamageFromWater = false; + public boolean parrotBreedable = false; + public boolean parrotAlwaysDropExp = false; + private void parrotSettings() { + parrotRidable = getBoolean("mobs.parrot.ridable", parrotRidable); + parrotRidableInWater = getBoolean("mobs.parrot.ridable-in-water", parrotRidableInWater); + parrotControllable = getBoolean("mobs.parrot.controllable", parrotControllable); + parrotMaxY = getDouble("mobs.parrot.ridable-max-y", parrotMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.parrot.attributes.max-health", parrotMaxHealth); + set("mobs.parrot.attributes.max-health", null); + set("mobs.parrot.attributes.max_health", oldValue); + } + parrotMaxHealth = getDouble("mobs.parrot.attributes.max_health", parrotMaxHealth); + parrotScale = Mth.clamp(getDouble("mobs.parrot.attributes.scale", parrotScale), 0.0625D, 16.0D); + parrotTakeDamageFromWater = getBoolean("mobs.parrot.takes-damage-from-water", parrotTakeDamageFromWater); + parrotBreedable = getBoolean("mobs.parrot.can-breed", parrotBreedable); + parrotAlwaysDropExp = getBoolean("mobs.parrot.always-drop-exp", parrotAlwaysDropExp); + } + + public boolean phantomRidable = false; + public boolean phantomRidableInWater = true; + public boolean phantomControllable = true; + public double phantomMaxY = 320D; + public float phantomFlameDamage = 1.0F; + public int phantomFlameFireTime = 8; + public boolean phantomAllowGriefing = false; + public String phantomMaxHealth = "20.0"; + public String phantomAttackDamage = "6 + size"; + public Map phantomMaxHealthCache = new HashMap<>(); + public Map phantomAttackDamageCache = new HashMap<>(); + public double phantomAttackedByCrystalRadius = 0.0D; + public float phantomAttackedByCrystalDamage = 1.0F; + public double phantomOrbitCrystalRadius = 0.0D; + public int phantomSpawnMinSkyDarkness = 5; + public boolean phantomSpawnOnlyAboveSeaLevel = true; + public boolean phantomSpawnOnlyWithVisibleSky = true; + public double phantomSpawnLocalDifficultyChance = 3.0D; + public int phantomSpawnMinPerAttempt = 1; + public int phantomSpawnMaxPerAttempt = -1; + public int phantomBurnInLight = 0; + public boolean phantomIgnorePlayersWithTorch = false; + public boolean phantomBurnInDaylight = true; + public boolean phantomFlamesOnSwoop = false; + public boolean phantomTakeDamageFromWater = false; + public boolean phantomAlwaysDropExp = false; + public int phantomMinSize = 0; + public int phantomMaxSize = 0; + private void phantomSettings() { + phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable); + phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater); + phantomControllable = getBoolean("mobs.phantom.controllable", phantomControllable); + phantomMaxY = getDouble("mobs.phantom.ridable-max-y", phantomMaxY); + phantomFlameDamage = (float) getDouble("mobs.phantom.flames.damage", phantomFlameDamage); + phantomFlameFireTime = getInt("mobs.phantom.flames.fire-time", phantomFlameFireTime); + phantomAllowGriefing = getBoolean("mobs.phantom.allow-griefing", phantomAllowGriefing); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.phantom.attributes.max-health", Double.parseDouble(phantomMaxHealth)); + set("mobs.phantom.attributes.max-health", null); + set("mobs.phantom.attributes.max_health", String.valueOf(oldValue)); + } + if (PurpurConfig.version < 25) { + double oldValue = getDouble("mobs.phantom.attributes.max_health", Double.parseDouble(phantomMaxHealth)); + set("mobs.phantom.attributes.max_health", String.valueOf(oldValue)); + } + phantomMaxHealth = getString("mobs.phantom.attributes.max_health", phantomMaxHealth); + phantomAttackDamage = getString("mobs.phantom.attributes.attack_damage", phantomAttackDamage); + phantomMaxHealthCache.clear(); + phantomAttackDamageCache.clear(); + phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius); + phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage); + phantomOrbitCrystalRadius = getDouble("mobs.phantom.orbit-crystal-radius", phantomOrbitCrystalRadius); + phantomSpawnMinSkyDarkness = getInt("mobs.phantom.spawn.min-sky-darkness", phantomSpawnMinSkyDarkness); + phantomSpawnOnlyAboveSeaLevel = getBoolean("mobs.phantom.spawn.only-above-sea-level", phantomSpawnOnlyAboveSeaLevel); + phantomSpawnOnlyWithVisibleSky = getBoolean("mobs.phantom.spawn.only-with-visible-sky", phantomSpawnOnlyWithVisibleSky); + phantomSpawnLocalDifficultyChance = getDouble("mobs.phantom.spawn.local-difficulty-chance", phantomSpawnLocalDifficultyChance); + phantomSpawnMinPerAttempt = getInt("mobs.phantom.spawn.per-attempt.min", phantomSpawnMinPerAttempt); + phantomSpawnMaxPerAttempt = getInt("mobs.phantom.spawn.per-attempt.max", phantomSpawnMaxPerAttempt); + phantomBurnInLight = getInt("mobs.phantom.burn-in-light", phantomBurnInLight); + phantomBurnInDaylight = getBoolean("mobs.phantom.burn-in-daylight", phantomBurnInDaylight); + phantomIgnorePlayersWithTorch = getBoolean("mobs.phantom.ignore-players-with-torch", phantomIgnorePlayersWithTorch); + phantomFlamesOnSwoop = getBoolean("mobs.phantom.flames-on-swoop", phantomFlamesOnSwoop); + phantomTakeDamageFromWater = getBoolean("mobs.phantom.takes-damage-from-water", phantomTakeDamageFromWater); + phantomAlwaysDropExp = getBoolean("mobs.phantom.always-drop-exp", phantomAlwaysDropExp); + phantomMinSize = Mth.clamp(getInt("mobs.phantom.size.min", phantomMinSize), 0, 64); + phantomMaxSize = Mth.clamp(getInt("mobs.phantom.size.max", phantomMaxSize), 0, 64); + if (phantomMinSize > phantomMaxSize) { + phantomMinSize = phantomMinSize ^ phantomMaxSize; + phantomMaxSize = phantomMinSize ^ phantomMaxSize; + phantomMinSize = phantomMinSize ^ phantomMaxSize; + } + } + + public boolean pigRidable = false; + public boolean pigRidableInWater = false; + public boolean pigControllable = true; + public double pigMaxHealth = 10.0D; + public double pigScale = 1.0D; + public boolean pigGiveSaddleBack = false; + public int pigBreedingTicks = 6000; + public boolean pigTakeDamageFromWater = false; + public boolean pigAlwaysDropExp = false; + private void pigSettings() { + pigRidable = getBoolean("mobs.pig.ridable", pigRidable); + pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater); + pigControllable = getBoolean("mobs.pig.controllable", pigControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.pig.attributes.max-health", pigMaxHealth); + set("mobs.pig.attributes.max-health", null); + set("mobs.pig.attributes.max_health", oldValue); + } + pigMaxHealth = getDouble("mobs.pig.attributes.max_health", pigMaxHealth); + pigScale = Mth.clamp(getDouble("mobs.pig.attributes.scale", pigScale), 0.0625D, 16.0D); + pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack); + pigBreedingTicks = getInt("mobs.pig.breeding-delay-ticks", pigBreedingTicks); + pigTakeDamageFromWater = getBoolean("mobs.pig.takes-damage-from-water", pigTakeDamageFromWater); + pigAlwaysDropExp = getBoolean("mobs.pig.always-drop-exp", pigAlwaysDropExp); + } + + public boolean piglinRidable = false; + public boolean piglinRidableInWater = true; + public boolean piglinControllable = true; + public double piglinMaxHealth = 16.0D; + public double piglinScale = 1.0D; + public boolean piglinBypassMobGriefing = false; + public boolean piglinTakeDamageFromWater = false; + public int piglinPortalSpawnModifier = 2000; + public boolean piglinAlwaysDropExp = false; + public double piglinHeadVisibilityPercent = 0.5D; + public boolean piglinIgnoresArmorWithGoldTrim = false; + private void piglinSettings() { + piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable); + piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater); + piglinControllable = getBoolean("mobs.piglin.controllable", piglinControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.piglin.attributes.max-health", piglinMaxHealth); + set("mobs.piglin.attributes.max-health", null); + set("mobs.piglin.attributes.max_health", oldValue); + } + piglinMaxHealth = getDouble("mobs.piglin.attributes.max_health", piglinMaxHealth); + piglinScale = Mth.clamp(getDouble("mobs.piglin.attributes.scale", piglinScale), 0.0625D, 16.0D); + piglinBypassMobGriefing = getBoolean("mobs.piglin.bypass-mob-griefing", piglinBypassMobGriefing); + piglinTakeDamageFromWater = getBoolean("mobs.piglin.takes-damage-from-water", piglinTakeDamageFromWater); + piglinPortalSpawnModifier = getInt("mobs.piglin.portal-spawn-modifier", piglinPortalSpawnModifier); + piglinAlwaysDropExp = getBoolean("mobs.piglin.always-drop-exp", piglinAlwaysDropExp); + piglinHeadVisibilityPercent = getDouble("mobs.piglin.head-visibility-percent", piglinHeadVisibilityPercent); + piglinIgnoresArmorWithGoldTrim = getBoolean("mobs.piglin.ignores-armor-with-gold-trim", piglinIgnoresArmorWithGoldTrim); + } + + public boolean piglinBruteRidable = false; + public boolean piglinBruteRidableInWater = true; + public boolean piglinBruteControllable = true; + public double piglinBruteMaxHealth = 50.0D; + public double piglinBruteScale = 1.0D; + public boolean piglinBruteTakeDamageFromWater = false; + public boolean piglinBruteAlwaysDropExp = false; + private void piglinBruteSettings() { + piglinBruteRidable = getBoolean("mobs.piglin_brute.ridable", piglinBruteRidable); + piglinBruteRidableInWater = getBoolean("mobs.piglin_brute.ridable-in-water", piglinBruteRidableInWater); + piglinBruteControllable = getBoolean("mobs.piglin_brute.controllable", piglinBruteControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.piglin_brute.attributes.max-health", piglinBruteMaxHealth); + set("mobs.piglin_brute.attributes.max-health", null); + set("mobs.piglin_brute.attributes.max_health", oldValue); + } + piglinBruteMaxHealth = getDouble("mobs.piglin_brute.attributes.max_health", piglinBruteMaxHealth); + piglinBruteScale = Mth.clamp(getDouble("mobs.piglin_brute.attributes.scale", piglinBruteScale), 0.0625D, 16.0D); + piglinBruteTakeDamageFromWater = getBoolean("mobs.piglin_brute.takes-damage-from-water", piglinBruteTakeDamageFromWater); + piglinBruteAlwaysDropExp = getBoolean("mobs.piglin_brute.always-drop-exp", piglinBruteAlwaysDropExp); + } + + public boolean pillagerRidable = false; + public boolean pillagerRidableInWater = true; + public boolean pillagerControllable = true; + public double pillagerMaxHealth = 24.0D; + public double pillagerScale = 1.0D; + public boolean pillagerBypassMobGriefing = false; + public boolean pillagerTakeDamageFromWater = false; + public boolean pillagerAlwaysDropExp = false; + private void pillagerSettings() { + pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable); + pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater); + pillagerControllable = getBoolean("mobs.pillager.controllable", pillagerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.pillager.attributes.max-health", pillagerMaxHealth); + set("mobs.pillager.attributes.max-health", null); + set("mobs.pillager.attributes.max_health", oldValue); + } + pillagerMaxHealth = getDouble("mobs.pillager.attributes.max_health", pillagerMaxHealth); + pillagerScale = Mth.clamp(getDouble("mobs.pillager.attributes.scale", pillagerScale), 0.0625D, 16.0D); + pillagerBypassMobGriefing = getBoolean("mobs.pillager.bypass-mob-griefing", pillagerBypassMobGriefing); + pillagerTakeDamageFromWater = getBoolean("mobs.pillager.takes-damage-from-water", pillagerTakeDamageFromWater); + pillagerAlwaysDropExp = getBoolean("mobs.pillager.always-drop-exp", pillagerAlwaysDropExp); + } + + public boolean polarBearRidable = false; + public boolean polarBearRidableInWater = true; + public boolean polarBearControllable = true; + public double polarBearMaxHealth = 30.0D; + public double polarBearScale = 1.0D; + public String polarBearBreedableItemString = ""; + public Item polarBearBreedableItem = null; + public int polarBearBreedingTicks = 6000; + public boolean polarBearTakeDamageFromWater = false; + public boolean polarBearAlwaysDropExp = false; + private void polarBearSettings() { + polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable); + polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater); + polarBearControllable = getBoolean("mobs.polar_bear.controllable", polarBearControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.polar_bear.attributes.max-health", polarBearMaxHealth); + set("mobs.polar_bear.attributes.max-health", null); + set("mobs.polar_bear.attributes.max_health", oldValue); + } + polarBearMaxHealth = getDouble("mobs.polar_bear.attributes.max_health", polarBearMaxHealth); + polarBearScale = Mth.clamp(getDouble("mobs.polar_bear.attributes.scale", polarBearScale), 0.0625D, 16.0D); + polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString); + Item item = BuiltInRegistries.ITEM.getValue(ResourceLocation.parse(polarBearBreedableItemString)); + if (item != Items.AIR) polarBearBreedableItem = item; + polarBearBreedingTicks = getInt("mobs.polar_bear.breeding-delay-ticks", polarBearBreedingTicks); + polarBearTakeDamageFromWater = getBoolean("mobs.polar_bear.takes-damage-from-water", polarBearTakeDamageFromWater); + polarBearAlwaysDropExp = getBoolean("mobs.polar_bear.always-drop-exp", polarBearAlwaysDropExp); + } + + public boolean pufferfishRidable = false; + public boolean pufferfishControllable = true; + public double pufferfishMaxHealth = 3.0D; + public double pufferfishScale = 1.0D; + public boolean pufferfishTakeDamageFromWater = false; + public boolean pufferfishAlwaysDropExp = false; + private void pufferfishSettings() { + pufferfishRidable = getBoolean("mobs.pufferfish.ridable", pufferfishRidable); + pufferfishControllable = getBoolean("mobs.pufferfish.controllable", pufferfishControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.pufferfish.attributes.max-health", pufferfishMaxHealth); + set("mobs.pufferfish.attributes.max-health", null); + set("mobs.pufferfish.attributes.max_health", oldValue); + } + pufferfishMaxHealth = getDouble("mobs.pufferfish.attributes.max_health", pufferfishMaxHealth); + pufferfishScale = Mth.clamp(getDouble("mobs.pufferfish.attributes.scale", pufferfishScale), 0.0625D, 16.0D); + pufferfishTakeDamageFromWater = getBoolean("mobs.pufferfish.takes-damage-from-water", pufferfishTakeDamageFromWater); + pufferfishAlwaysDropExp = getBoolean("mobs.pufferfish.always-drop-exp", pufferfishAlwaysDropExp); + } + + public boolean rabbitRidable = false; + public boolean rabbitRidableInWater = true; + public boolean rabbitControllable = true; + public double rabbitMaxHealth = 3.0D; + public double rabbitScale = 1.0D; + public double rabbitNaturalToast = 0.0D; + public double rabbitNaturalKiller = 0.0D; + public int rabbitBreedingTicks = 6000; + public boolean rabbitBypassMobGriefing = false; + public boolean rabbitTakeDamageFromWater = false; + public boolean rabbitAlwaysDropExp = false; + private void rabbitSettings() { + rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable); + rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater); + rabbitControllable = getBoolean("mobs.rabbit.controllable", rabbitControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.rabbit.attributes.max-health", rabbitMaxHealth); + set("mobs.rabbit.attributes.max-health", null); + set("mobs.rabbit.attributes.max_health", oldValue); + } + rabbitMaxHealth = getDouble("mobs.rabbit.attributes.max_health", rabbitMaxHealth); + rabbitScale = Mth.clamp(getDouble("mobs.rabbit.attributes.scale", rabbitScale), 0.0625D, 16.0D); + rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast); + rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller); + rabbitBreedingTicks = getInt("mobs.rabbit.breeding-delay-ticks", rabbitBreedingTicks); + rabbitBypassMobGriefing = getBoolean("mobs.rabbit.bypass-mob-griefing", rabbitBypassMobGriefing); + rabbitTakeDamageFromWater = getBoolean("mobs.rabbit.takes-damage-from-water", rabbitTakeDamageFromWater); + rabbitAlwaysDropExp = getBoolean("mobs.rabbit.always-drop-exp", rabbitAlwaysDropExp); + } + + public boolean ravagerRidable = false; + public boolean ravagerRidableInWater = false; + public boolean ravagerControllable = true; + public double ravagerMaxHealth = 100.0D; + public double ravagerScale = 1.0D; + public boolean ravagerBypassMobGriefing = false; + public boolean ravagerTakeDamageFromWater = false; + public List ravagerGriefableBlocks = new ArrayList<>(); + public boolean ravagerAlwaysDropExp = false; + public boolean ravagerAvoidRabbits = false; + private void ravagerSettings() { + ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable); + ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater); + ravagerControllable = getBoolean("mobs.ravager.controllable", ravagerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.ravager.attributes.max-health", ravagerMaxHealth); + set("mobs.ravager.attributes.max-health", null); + set("mobs.ravager.attributes.max_health", oldValue); + } + ravagerMaxHealth = getDouble("mobs.ravager.attributes.max_health", ravagerMaxHealth); + ravagerScale = Mth.clamp(getDouble("mobs.ravager.attributes.scale", ravagerScale), 0.0625D, 16.0D); + ravagerBypassMobGriefing = getBoolean("mobs.ravager.bypass-mob-griefing", ravagerBypassMobGriefing); + ravagerTakeDamageFromWater = getBoolean("mobs.ravager.takes-damage-from-water", ravagerTakeDamageFromWater); + getList("mobs.ravager.griefable-blocks", new ArrayList(){{ + add("minecraft:oak_leaves"); + add("minecraft:spruce_leaves"); + add("minecraft:birch_leaves"); + add("minecraft:jungle_leaves"); + add("minecraft:acacia_leaves"); + add("minecraft:dark_oak_leaves"); + add("minecraft:beetroots"); + add("minecraft:carrots"); + add("minecraft:potatoes"); + add("minecraft:wheat"); + }}).forEach(key -> { + Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(key.toString())); + if (!block.defaultBlockState().isAir()) { + ravagerGriefableBlocks.add(block); + } + }); + ravagerAlwaysDropExp = getBoolean("mobs.ravager.always-drop-exp", ravagerAlwaysDropExp); + ravagerAvoidRabbits = getBoolean("mobs.ravager.avoid-rabbits", ravagerAvoidRabbits); + } + + public boolean salmonRidable = false; + public boolean salmonControllable = true; + public double salmonMaxHealth = 3.0D; + public double salmonScale = 1.0D; + public boolean salmonTakeDamageFromWater = false; + public boolean salmonAlwaysDropExp = false; + private void salmonSettings() { + salmonRidable = getBoolean("mobs.salmon.ridable", salmonRidable); + salmonControllable = getBoolean("mobs.salmon.controllable", salmonControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.salmon.attributes.max-health", salmonMaxHealth); + set("mobs.salmon.attributes.max-health", null); + set("mobs.salmon.attributes.max_health", oldValue); + } + salmonMaxHealth = getDouble("mobs.salmon.attributes.max_health", salmonMaxHealth); + salmonScale = Mth.clamp(getDouble("mobs.salmon.attributes.scale", salmonScale), 0.0625D, 16.0D); + salmonTakeDamageFromWater = getBoolean("mobs.salmon.takes-damage-from-water", salmonTakeDamageFromWater); + salmonAlwaysDropExp = getBoolean("mobs.salmon.always-drop-exp", salmonAlwaysDropExp); + } + + public boolean sheepRidable = false; + public boolean sheepRidableInWater = true; + public boolean sheepControllable = true; + public double sheepMaxHealth = 8.0D; + public double sheepScale = 1.0D; + public int sheepBreedingTicks = 6000; + public boolean sheepBypassMobGriefing = false; + public boolean sheepTakeDamageFromWater = false; + public boolean sheepAlwaysDropExp = false; + private void sheepSettings() { + sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable); + sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater); + sheepControllable = getBoolean("mobs.sheep.controllable", sheepControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.sheep.attributes.max-health", sheepMaxHealth); + set("mobs.sheep.attributes.max-health", null); + set("mobs.sheep.attributes.max_health", oldValue); + } + sheepMaxHealth = getDouble("mobs.sheep.attributes.max_health", sheepMaxHealth); + sheepScale = Mth.clamp(getDouble("mobs.sheep.attributes.scale", sheepScale), 0.0625D, 16.0D); + sheepBreedingTicks = getInt("mobs.sheep.breeding-delay-ticks", sheepBreedingTicks); + sheepBypassMobGriefing = getBoolean("mobs.sheep.bypass-mob-griefing", sheepBypassMobGriefing); + sheepTakeDamageFromWater = getBoolean("mobs.sheep.takes-damage-from-water", sheepTakeDamageFromWater); + sheepAlwaysDropExp = getBoolean("mobs.sheep.always-drop-exp", sheepAlwaysDropExp); + } + + public boolean shulkerRidable = false; + public boolean shulkerRidableInWater = true; + public boolean shulkerControllable = true; + public double shulkerMaxHealth = 30.0D; + public double shulkerScale = 1.0D; + public boolean shulkerTakeDamageFromWater = false; + public float shulkerSpawnFromBulletBaseChance = 1.0F; + public boolean shulkerSpawnFromBulletRequireOpenLid = true; + public double shulkerSpawnFromBulletNearbyRange = 8.0D; + public String shulkerSpawnFromBulletNearbyEquation = "(nearby - 1) / 5.0"; + public boolean shulkerSpawnFromBulletRandomColor = false; + public boolean shulkerChangeColorWithDye = false; + public boolean shulkerAlwaysDropExp = false; + private void shulkerSettings() { + shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable); + shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater); + shulkerControllable = getBoolean("mobs.shulker.controllable", shulkerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.shulker.attributes.max-health", shulkerMaxHealth); + set("mobs.shulker.attributes.max-health", null); + set("mobs.shulker.attributes.max_health", oldValue); + } + shulkerMaxHealth = getDouble("mobs.shulker.attributes.max_health", shulkerMaxHealth); + shulkerScale = Mth.clamp(getDouble("mobs.shulker.attributes.scale", shulkerScale), 0.0625D, Shulker.MAX_SCALE); + shulkerTakeDamageFromWater = getBoolean("mobs.shulker.takes-damage-from-water", shulkerTakeDamageFromWater); + shulkerSpawnFromBulletBaseChance = (float) getDouble("mobs.shulker.spawn-from-bullet.base-chance", shulkerSpawnFromBulletBaseChance); + shulkerSpawnFromBulletRequireOpenLid = getBoolean("mobs.shulker.spawn-from-bullet.require-open-lid", shulkerSpawnFromBulletRequireOpenLid); + shulkerSpawnFromBulletNearbyRange = getDouble("mobs.shulker.spawn-from-bullet.nearby-range", shulkerSpawnFromBulletNearbyRange); + shulkerSpawnFromBulletNearbyEquation = getString("mobs.shulker.spawn-from-bullet.nearby-equation", shulkerSpawnFromBulletNearbyEquation); + shulkerSpawnFromBulletRandomColor = getBoolean("mobs.shulker.spawn-from-bullet.random-color", shulkerSpawnFromBulletRandomColor); + shulkerChangeColorWithDye = getBoolean("mobs.shulker.change-color-with-dye", shulkerChangeColorWithDye); + shulkerAlwaysDropExp = getBoolean("mobs.shulker.always-drop-exp", shulkerAlwaysDropExp); + } + + public boolean silverfishRidable = false; + public boolean silverfishRidableInWater = true; + public boolean silverfishControllable = true; + public double silverfishMaxHealth = 8.0D; + public double silverfishScale = 1.0D; + public double silverfishMovementSpeed = 0.25D; + public double silverfishAttackDamage = 1.0D; + public boolean silverfishBypassMobGriefing = false; + public boolean silverfishTakeDamageFromWater = false; + public boolean silverfishAlwaysDropExp = false; + private void silverfishSettings() { + silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable); + silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater); + silverfishControllable = getBoolean("mobs.silverfish.controllable", silverfishControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.silverfish.attributes.max-health", silverfishMaxHealth); + set("mobs.silverfish.attributes.max-health", null); + set("mobs.silverfish.attributes.max_health", oldValue); + } + silverfishMaxHealth = getDouble("mobs.silverfish.attributes.max_health", silverfishMaxHealth); + silverfishScale = Mth.clamp(getDouble("mobs.silverfish.attributes.scale", silverfishScale), 0.0625D, 16.0D); + silverfishMovementSpeed = getDouble("mobs.silverfish.attributes.movement_speed", silverfishMovementSpeed); + silverfishAttackDamage = getDouble("mobs.silverfish.attributes.attack_damage", silverfishAttackDamage); + silverfishBypassMobGriefing = getBoolean("mobs.silverfish.bypass-mob-griefing", silverfishBypassMobGriefing); + silverfishTakeDamageFromWater = getBoolean("mobs.silverfish.takes-damage-from-water", silverfishTakeDamageFromWater); + silverfishAlwaysDropExp = getBoolean("mobs.silverfish.always-drop-exp", silverfishAlwaysDropExp); + } + + public boolean skeletonRidable = false; + public boolean skeletonRidableInWater = true; + public boolean skeletonControllable = true; + public double skeletonMaxHealth = 20.0D; + public double skeletonScale = 1.0D; + public boolean skeletonTakeDamageFromWater = false; + public boolean skeletonAlwaysDropExp = false; + public double skeletonHeadVisibilityPercent = 0.5D; + public int skeletonFeedWitherRoses = 0; + public String skeletonBowAccuracy = "14 - difficulty * 4"; + public Map skeletonBowAccuracyMap = new HashMap<>(); + private void skeletonSettings() { + skeletonRidable = getBoolean("mobs.skeleton.ridable", skeletonRidable); + skeletonRidableInWater = getBoolean("mobs.skeleton.ridable-in-water", skeletonRidableInWater); + skeletonControllable = getBoolean("mobs.skeleton.controllable", skeletonControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.skeleton.attributes.max-health", skeletonMaxHealth); + set("mobs.skeleton.attributes.max-health", null); + set("mobs.skeleton.attributes.max_health", oldValue); + } + skeletonMaxHealth = getDouble("mobs.skeleton.attributes.max_health", skeletonMaxHealth); + skeletonScale = Mth.clamp(getDouble("mobs.skeleton.attributes.scale", skeletonScale), 0.0625D, 16.0D); + skeletonTakeDamageFromWater = getBoolean("mobs.skeleton.takes-damage-from-water", skeletonTakeDamageFromWater); + skeletonAlwaysDropExp = getBoolean("mobs.skeleton.always-drop-exp", skeletonAlwaysDropExp); + skeletonHeadVisibilityPercent = getDouble("mobs.skeleton.head-visibility-percent", skeletonHeadVisibilityPercent); + skeletonFeedWitherRoses = getInt("mobs.skeleton.feed-wither-roses", skeletonFeedWitherRoses); + final String defaultSkeletonBowAccuracy = skeletonBowAccuracy; + skeletonBowAccuracy = getString("mobs.skeleton.bow-accuracy", skeletonBowAccuracy); + for (int i = 1; i < 4; i++) { + final float divergence; + try { + divergence = ((Number) Entity.scriptEngine.eval("let difficulty = " + i + "; " + skeletonBowAccuracy)).floatValue(); + } catch (javax.script.ScriptException e) { + e.printStackTrace(); + break; + } + skeletonBowAccuracyMap.put(i, divergence); + } + } + + public boolean skeletonHorseRidable = false; + public boolean skeletonHorseRidableInWater = true; + public boolean skeletonHorseCanSwim = false; + public double skeletonHorseMaxHealthMin = 15.0D; + public double skeletonHorseMaxHealthMax = 15.0D; + public double skeletonHorseJumpStrengthMin = 0.4D; + public double skeletonHorseJumpStrengthMax = 1.0D; + public double skeletonHorseMovementSpeedMin = 0.2D; + public double skeletonHorseMovementSpeedMax = 0.2D; + public boolean skeletonHorseTakeDamageFromWater = false; + public boolean skeletonHorseAlwaysDropExp = false; + private void skeletonHorseSettings() { + skeletonHorseRidable = getBoolean("mobs.skeleton_horse.ridable", skeletonHorseRidable); + skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater); + skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.skeleton_horse.attributes.max-health", skeletonHorseMaxHealthMin); + set("mobs.skeleton_horse.attributes.max-health", null); + set("mobs.skeleton_horse.attributes.max_health.min", oldValue); + set("mobs.skeleton_horse.attributes.max_health.max", oldValue); + } + skeletonHorseMaxHealthMin = getDouble("mobs.skeleton_horse.attributes.max_health.min", skeletonHorseMaxHealthMin); + skeletonHorseMaxHealthMax = getDouble("mobs.skeleton_horse.attributes.max_health.max", skeletonHorseMaxHealthMax); + skeletonHorseJumpStrengthMin = getDouble("mobs.skeleton_horse.attributes.jump_strength.min", skeletonHorseJumpStrengthMin); + skeletonHorseJumpStrengthMax = getDouble("mobs.skeleton_horse.attributes.jump_strength.max", skeletonHorseJumpStrengthMax); + skeletonHorseMovementSpeedMin = getDouble("mobs.skeleton_horse.attributes.movement_speed.min", skeletonHorseMovementSpeedMin); + skeletonHorseMovementSpeedMax = getDouble("mobs.skeleton_horse.attributes.movement_speed.max", skeletonHorseMovementSpeedMax); + skeletonHorseTakeDamageFromWater = getBoolean("mobs.skeleton_horse.takes-damage-from-water", skeletonHorseTakeDamageFromWater); + skeletonHorseAlwaysDropExp = getBoolean("mobs.skeleton_horse.always-drop-exp", skeletonHorseAlwaysDropExp); + } + + public boolean slimeRidable = false; + public boolean slimeRidableInWater = true; + public boolean slimeControllable = true; + public String slimeMaxHealth = "size * size"; + public String slimeAttackDamage = "size"; + public Map slimeMaxHealthCache = new HashMap<>(); + public Map slimeAttackDamageCache = new HashMap<>(); + public boolean slimeTakeDamageFromWater = false; + public boolean slimeAlwaysDropExp = false; + private void slimeSettings() { + slimeRidable = getBoolean("mobs.slime.ridable", slimeRidable); + slimeRidableInWater = getBoolean("mobs.slime.ridable-in-water", slimeRidableInWater); + slimeControllable = getBoolean("mobs.slime.controllable", slimeControllable); + if (PurpurConfig.version < 10) { + String oldValue = getString("mobs.slime.attributes.max-health", slimeMaxHealth); + set("mobs.slime.attributes.max-health", null); + set("mobs.slime.attributes.max_health", oldValue); + } + slimeMaxHealth = getString("mobs.slime.attributes.max_health", slimeMaxHealth); + slimeAttackDamage = getString("mobs.slime.attributes.attack_damage", slimeAttackDamage); + slimeMaxHealthCache.clear(); + slimeAttackDamageCache.clear(); + slimeTakeDamageFromWater = getBoolean("mobs.slime.takes-damage-from-water", slimeTakeDamageFromWater); + slimeAlwaysDropExp = getBoolean("mobs.slime.always-drop-exp", slimeAlwaysDropExp); + } + + public boolean snowGolemRidable = false; + public boolean snowGolemRidableInWater = true; + public boolean snowGolemControllable = true; + public boolean snowGolemLeaveTrailWhenRidden = false; + public double snowGolemMaxHealth = 4.0D; + public double snowGolemScale = 1.0D; + public boolean snowGolemPutPumpkinBack = false; + public int snowGolemSnowBallMin = 20; + public int snowGolemSnowBallMax = 20; + public float snowGolemSnowBallModifier = 10.0F; + public double snowGolemAttackDistance = 1.25D; + public boolean snowGolemBypassMobGriefing = false; + public boolean snowGolemTakeDamageFromWater = true; + public boolean snowGolemAlwaysDropExp = false; + private void snowGolemSettings() { + snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable); + snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater); + snowGolemControllable = getBoolean("mobs.snow_golem.controllable", snowGolemControllable); + snowGolemLeaveTrailWhenRidden = getBoolean("mobs.snow_golem.leave-trail-when-ridden", snowGolemLeaveTrailWhenRidden); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.snow_golem.attributes.max-health", snowGolemMaxHealth); + set("mobs.snow_golem.attributes.max-health", null); + set("mobs.snow_golem.attributes.max_health", oldValue); + } + snowGolemMaxHealth = getDouble("mobs.snow_golem.attributes.max_health", snowGolemMaxHealth); + snowGolemScale = Mth.clamp(getDouble("mobs.snow_golem.attributes.scale", snowGolemScale), 0.0625D, 16.0D); + snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack); + snowGolemSnowBallMin = getInt("mobs.snow_golem.min-shoot-interval-ticks", snowGolemSnowBallMin); + snowGolemSnowBallMax = getInt("mobs.snow_golem.max-shoot-interval-ticks", snowGolemSnowBallMax); + snowGolemSnowBallModifier = (float) getDouble("mobs.snow_golem.snow-ball-modifier", snowGolemSnowBallModifier); + snowGolemAttackDistance = getDouble("mobs.snow_golem.attack-distance", snowGolemAttackDistance); + snowGolemBypassMobGriefing = getBoolean("mobs.snow_golem.bypass-mob-griefing", snowGolemBypassMobGriefing); + snowGolemTakeDamageFromWater = getBoolean("mobs.snow_golem.takes-damage-from-water", snowGolemTakeDamageFromWater); + snowGolemAlwaysDropExp = getBoolean("mobs.snow_golem.always-drop-exp", snowGolemAlwaysDropExp); + } + + public boolean snifferRidable = false; + public boolean snifferRidableInWater = true; + public boolean snifferControllable = true; + public double snifferMaxHealth = 14.0D; + public double snifferScale = 1.0D; + public int snifferBreedingTicks = 6000; + private void snifferSettings() { + snifferRidable = getBoolean("mobs.sniffer.ridable", snifferRidable); + snifferRidableInWater = getBoolean("mobs.sniffer.ridable-in-water", snifferRidableInWater); + snifferControllable = getBoolean("mobs.sniffer.controllable", snifferControllable); + snifferMaxHealth = getDouble("mobs.sniffer.attributes.max_health", snifferMaxHealth); + snifferScale = Mth.clamp(getDouble("mobs.sniffer.attributes.scale", snifferScale), 0.0625D, 16.0D); + snifferBreedingTicks = getInt("mobs.sniffer.breeding-delay-ticks", snifferBreedingTicks); + } + + public boolean squidRidable = false; + public boolean squidControllable = true; + public double squidMaxHealth = 10.0D; + public double squidScale = 1.0D; + public boolean squidImmuneToEAR = true; + public double squidOffsetWaterCheck = 0.0D; + public boolean squidsCanFly = false; + public boolean squidTakeDamageFromWater = false; + public boolean squidAlwaysDropExp = false; + private void squidSettings() { + squidRidable = getBoolean("mobs.squid.ridable", squidRidable); + squidControllable = getBoolean("mobs.squid.controllable", squidControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.squid.attributes.max-health", squidMaxHealth); + set("mobs.squid.attributes.max-health", null); + set("mobs.squid.attributes.max_health", oldValue); + } + squidMaxHealth = getDouble("mobs.squid.attributes.max_health", squidMaxHealth); + squidScale = Mth.clamp(getDouble("mobs.squid.attributes.scale", squidScale), 0.0625D, 16.0D); + squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR); + squidOffsetWaterCheck = getDouble("mobs.squid.water-offset-check", squidOffsetWaterCheck); + squidsCanFly = getBoolean("mobs.squid.can-fly", squidsCanFly); + squidTakeDamageFromWater = getBoolean("mobs.squid.takes-damage-from-water", squidTakeDamageFromWater); + squidAlwaysDropExp = getBoolean("mobs.squid.always-drop-exp", squidAlwaysDropExp); + } + + public boolean spiderRidable = false; + public boolean spiderRidableInWater = false; + public boolean spiderControllable = true; + public double spiderMaxHealth = 16.0D; + public double spiderScale = 1.0D; + public boolean spiderTakeDamageFromWater = false; + public boolean spiderAlwaysDropExp = false; + private void spiderSettings() { + spiderRidable = getBoolean("mobs.spider.ridable", spiderRidable); + spiderRidableInWater = getBoolean("mobs.spider.ridable-in-water", spiderRidableInWater); + spiderControllable = getBoolean("mobs.spider.controllable", spiderControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.spider.attributes.max-health", spiderMaxHealth); + set("mobs.spider.attributes.max-health", null); + set("mobs.spider.attributes.max_health", oldValue); + } + spiderMaxHealth = getDouble("mobs.spider.attributes.max_health", spiderMaxHealth); + spiderScale = Mth.clamp(getDouble("mobs.spider.attributes.scale", spiderScale), 0.0625D, 16.0D); + spiderTakeDamageFromWater = getBoolean("mobs.spider.takes-damage-from-water", spiderTakeDamageFromWater); + spiderAlwaysDropExp = getBoolean("mobs.spider.always-drop-exp", spiderAlwaysDropExp); + } + + public boolean strayRidable = false; + public boolean strayRidableInWater = true; + public boolean strayControllable = true; + public double strayMaxHealth = 20.0D; + public double strayScale = 1.0D; + public boolean strayTakeDamageFromWater = false; + public boolean strayAlwaysDropExp = false; + private void straySettings() { + strayRidable = getBoolean("mobs.stray.ridable", strayRidable); + strayRidableInWater = getBoolean("mobs.stray.ridable-in-water", strayRidableInWater); + strayControllable = getBoolean("mobs.stray.controllable", strayControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.stray.attributes.max-health", strayMaxHealth); + set("mobs.stray.attributes.max-health", null); + set("mobs.stray.attributes.max_health", oldValue); + } + strayMaxHealth = getDouble("mobs.stray.attributes.max_health", strayMaxHealth); + strayScale = Mth.clamp(getDouble("mobs.stray.attributes.scale", strayScale), 0.0625D, 16.0D); + strayTakeDamageFromWater = getBoolean("mobs.stray.takes-damage-from-water", strayTakeDamageFromWater); + strayAlwaysDropExp = getBoolean("mobs.stray.always-drop-exp", strayAlwaysDropExp); + } + + public boolean striderRidable = false; + public boolean striderRidableInWater = false; + public boolean striderControllable = true; + public double striderMaxHealth = 20.0D; + public double striderScale = 1.0D; + public int striderBreedingTicks = 6000; + public boolean striderGiveSaddleBack = false; + public boolean striderTakeDamageFromWater = true; + public boolean striderAlwaysDropExp = false; + private void striderSettings() { + striderRidable = getBoolean("mobs.strider.ridable", striderRidable); + striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater); + striderControllable = getBoolean("mobs.strider.controllable", striderControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.strider.attributes.max-health", striderMaxHealth); + set("mobs.strider.attributes.max-health", null); + set("mobs.strider.attributes.max_health", oldValue); + } + striderMaxHealth = getDouble("mobs.strider.attributes.max_health", striderMaxHealth); + striderScale = Mth.clamp(getDouble("mobs.strider.attributes.scale", striderScale), 0.0625D, 16.0D); + striderBreedingTicks = getInt("mobs.strider.breeding-delay-ticks", striderBreedingTicks); + striderGiveSaddleBack = getBoolean("mobs.strider.give-saddle-back", striderGiveSaddleBack); + striderTakeDamageFromWater = getBoolean("mobs.strider.takes-damage-from-water", striderTakeDamageFromWater); + striderAlwaysDropExp = getBoolean("mobs.strider.always-drop-exp", striderAlwaysDropExp); + } + + public boolean tadpoleRidable = false; + public boolean tadpoleRidableInWater = true; + public boolean tadpoleControllable = true; + private void tadpoleSettings() { + tadpoleRidable = getBoolean("mobs.tadpole.ridable", tadpoleRidable); + tadpoleRidableInWater = getBoolean("mobs.tadpole.ridable-in-water", tadpoleRidableInWater); + tadpoleControllable = getBoolean("mobs.tadpole.controllable", tadpoleControllable); + } + + public boolean traderLlamaRidable = false; + public boolean traderLlamaRidableInWater = false; + public boolean traderLlamaControllable = true; + public double traderLlamaMaxHealthMin = 15.0D; + public double traderLlamaMaxHealthMax = 30.0D; + public double traderLlamaJumpStrengthMin = 0.5D; + public double traderLlamaJumpStrengthMax = 0.5D; + public double traderLlamaMovementSpeedMin = 0.175D; + public double traderLlamaMovementSpeedMax = 0.175D; + public int traderLlamaBreedingTicks = 6000; + public boolean traderLlamaTakeDamageFromWater = false; + public boolean traderLlamaAlwaysDropExp = false; + private void traderLlamaSettings() { + traderLlamaRidable = getBoolean("mobs.trader_llama.ridable", traderLlamaRidable); + traderLlamaRidableInWater = getBoolean("mobs.trader_llama.ridable-in-water", traderLlamaRidableInWater); + traderLlamaControllable = getBoolean("mobs.trader_llama.controllable", traderLlamaControllable); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.trader_llama.attributes.max-health.min", traderLlamaMaxHealthMin); + double oldMax = getDouble("mobs.trader_llama.attributes.max-health.max", traderLlamaMaxHealthMax); + set("mobs.trader_llama.attributes.max-health", null); + set("mobs.trader_llama.attributes.max_health.min", oldMin); + set("mobs.trader_llama.attributes.max_health.max", oldMax); + } + traderLlamaMaxHealthMin = getDouble("mobs.trader_llama.attributes.max_health.min", traderLlamaMaxHealthMin); + traderLlamaMaxHealthMax = getDouble("mobs.trader_llama.attributes.max_health.max", traderLlamaMaxHealthMax); + traderLlamaJumpStrengthMin = getDouble("mobs.trader_llama.attributes.jump_strength.min", traderLlamaJumpStrengthMin); + traderLlamaJumpStrengthMax = getDouble("mobs.trader_llama.attributes.jump_strength.max", traderLlamaJumpStrengthMax); + traderLlamaMovementSpeedMin = getDouble("mobs.trader_llama.attributes.movement_speed.min", traderLlamaMovementSpeedMin); + traderLlamaMovementSpeedMax = getDouble("mobs.trader_llama.attributes.movement_speed.max", traderLlamaMovementSpeedMax); + traderLlamaBreedingTicks = getInt("mobs.trader_llama.breeding-delay-ticks", traderLlamaBreedingTicks); + traderLlamaTakeDamageFromWater = getBoolean("mobs.trader_llama.takes-damage-from-water", traderLlamaTakeDamageFromWater); + traderLlamaAlwaysDropExp = getBoolean("mobs.trader_llama.always-drop-exp", traderLlamaAlwaysDropExp); + } + + public boolean tropicalFishRidable = false; + public boolean tropicalFishControllable = true; + public double tropicalFishMaxHealth = 3.0D; + public double tropicalFishScale = 1.0D; + public boolean tropicalFishTakeDamageFromWater = false; + public boolean tropicalFishAlwaysDropExp = false; + private void tropicalFishSettings() { + tropicalFishRidable = getBoolean("mobs.tropical_fish.ridable", tropicalFishRidable); + tropicalFishControllable = getBoolean("mobs.tropical_fish.controllable", tropicalFishControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.tropical_fish.attributes.max-health", tropicalFishMaxHealth); + set("mobs.tropical_fish.attributes.max-health", null); + set("mobs.tropical_fish.attributes.max_health", oldValue); + } + tropicalFishMaxHealth = getDouble("mobs.tropical_fish.attributes.max_health", tropicalFishMaxHealth); + tropicalFishScale = Mth.clamp(getDouble("mobs.tropical_fish.attributes.scale", tropicalFishScale), 0.0625D, 16.0D); + tropicalFishTakeDamageFromWater = getBoolean("mobs.tropical_fish.takes-damage-from-water", tropicalFishTakeDamageFromWater); + tropicalFishAlwaysDropExp = getBoolean("mobs.tropical_fish.always-drop-exp", tropicalFishAlwaysDropExp); + } + + public boolean turtleRidable = false; + public boolean turtleRidableInWater = true; + public boolean turtleControllable = true; + public double turtleMaxHealth = 30.0D; + public double turtleScale = 1.0D; + public int turtleBreedingTicks = 6000; + public boolean turtleTakeDamageFromWater = false; + public boolean turtleAlwaysDropExp = false; + private void turtleSettings() { + turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable); + turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater); + turtleControllable = getBoolean("mobs.turtle.controllable", turtleControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.turtle.attributes.max-health", turtleMaxHealth); + set("mobs.turtle.attributes.max-health", null); + set("mobs.turtle.attributes.max_health", oldValue); + } + turtleMaxHealth = getDouble("mobs.turtle.attributes.max_health", turtleMaxHealth); + turtleScale = Mth.clamp(getDouble("mobs.turtle.attributes.scale", turtleScale), 0.0625D, 16.0D); + turtleBreedingTicks = getInt("mobs.turtle.breeding-delay-ticks", turtleBreedingTicks); + turtleTakeDamageFromWater = getBoolean("mobs.turtle.takes-damage-from-water", turtleTakeDamageFromWater); + turtleAlwaysDropExp = getBoolean("mobs.turtle.always-drop-exp", turtleAlwaysDropExp); + } + + public boolean vexRidable = false; + public boolean vexRidableInWater = true; + public boolean vexControllable = true; + public double vexMaxY = 320D; + public double vexMaxHealth = 14.0D; + public double vexScale = 1.0D; + public boolean vexTakeDamageFromWater = false; + public boolean vexAlwaysDropExp = false; + private void vexSettings() { + vexRidable = getBoolean("mobs.vex.ridable", vexRidable); + vexRidableInWater = getBoolean("mobs.vex.ridable-in-water", vexRidableInWater); + vexControllable = getBoolean("mobs.vex.controllable", vexControllable); + vexMaxY = getDouble("mobs.vex.ridable-max-y", vexMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.vex.attributes.max-health", vexMaxHealth); + set("mobs.vex.attributes.max-health", null); + set("mobs.vex.attributes.max_health", oldValue); + } + vexMaxHealth = getDouble("mobs.vex.attributes.max_health", vexMaxHealth); + vexScale = Mth.clamp(getDouble("mobs.vex.attributes.scale", vexScale), 0.0625D, 16.0D); + vexTakeDamageFromWater = getBoolean("mobs.vex.takes-damage-from-water", vexTakeDamageFromWater); + vexAlwaysDropExp = getBoolean("mobs.vex.always-drop-exp", vexAlwaysDropExp); + } + + public boolean villagerRidable = false; + public boolean villagerRidableInWater = true; + public boolean villagerControllable = true; + public double villagerMaxHealth = 20.0D; + public double villagerScale = 1.0D; + public boolean villagerFollowEmeraldBlock = false; + public double villagerTemptRange = 10.0D; + public boolean villagerCanBeLeashed = false; + public boolean villagerCanBreed = true; + public int villagerBreedingTicks = 6000; + public boolean villagerClericsFarmWarts = false; + public boolean villagerClericFarmersThrowWarts = true; + public boolean villagerBypassMobGriefing = false; + public boolean villagerTakeDamageFromWater = false; + public boolean villagerAllowTrading = true; + public boolean villagerAlwaysDropExp = false; + public int villagerMinimumDemand = 0; + public boolean villagerLobotomizeEnabled = false; + public int villagerLobotomizeCheckInterval = 100; + public boolean villagerLobotomizeWaitUntilTradeLocked = false; + public boolean villagerDisplayTradeItem = true; + public int villagerSpawnIronGolemRadius = 0; + public int villagerSpawnIronGolemLimit = 0; + public int villagerAcquirePoiSearchRadius = 48; + public int villagerNearestBedSensorSearchRadius = 48; + private void villagerSettings() { + villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); + villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); + villagerControllable = getBoolean("mobs.villager.controllable", villagerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.villager.attributes.max-health", villagerMaxHealth); + set("mobs.villager.attributes.max-health", null); + set("mobs.villager.attributes.max_health", oldValue); + } + villagerMaxHealth = getDouble("mobs.villager.attributes.max_health", villagerMaxHealth); + villagerScale = Mth.clamp(getDouble("mobs.villager.attributes.scale", villagerScale), 0.0625D, 16.0D); + villagerFollowEmeraldBlock = getBoolean("mobs.villager.follow-emerald-blocks", villagerFollowEmeraldBlock); + villagerTemptRange = getDouble("mobs.villager.attributes.tempt_range", villagerTemptRange); + villagerCanBeLeashed = getBoolean("mobs.villager.can-be-leashed", villagerCanBeLeashed); + villagerCanBreed = getBoolean("mobs.villager.can-breed", villagerCanBreed); + villagerBreedingTicks = getInt("mobs.villager.breeding-delay-ticks", villagerBreedingTicks); + villagerClericsFarmWarts = getBoolean("mobs.villager.clerics-farm-warts", villagerClericsFarmWarts); + villagerClericFarmersThrowWarts = getBoolean("mobs.villager.cleric-wart-farmers-throw-warts-at-villagers", villagerClericFarmersThrowWarts); + villagerBypassMobGriefing = getBoolean("mobs.villager.bypass-mob-griefing", villagerBypassMobGriefing); + villagerTakeDamageFromWater = getBoolean("mobs.villager.takes-damage-from-water", villagerTakeDamageFromWater); + villagerAllowTrading = getBoolean("mobs.villager.allow-trading", villagerAllowTrading); + villagerAlwaysDropExp = getBoolean("mobs.villager.always-drop-exp", villagerAlwaysDropExp); + villagerMinimumDemand = getInt("mobs.villager.minimum-demand", villagerMinimumDemand); + if (PurpurConfig.version < 9) { + boolean oldValue = getBoolean("mobs.villager.lobotomize-1x1", villagerLobotomizeEnabled); + set("mobs.villager.lobotomize.enabled", oldValue); + set("mobs.villager.lobotomize-1x1", null); + } + if (PurpurConfig.version < 27) { + int oldValue = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheckInterval); + set("mobs.villager.lobotomize.check-interval", oldValue == 60 ? 100 : oldValue); + } + villagerLobotomizeEnabled = getBoolean("mobs.villager.lobotomize.enabled", villagerLobotomizeEnabled); + villagerLobotomizeCheckInterval = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheckInterval); + villagerLobotomizeWaitUntilTradeLocked = getBoolean("mobs.villager.lobotomize.wait-until-trade-locked", villagerLobotomizeWaitUntilTradeLocked); + villagerDisplayTradeItem = getBoolean("mobs.villager.display-trade-item", villagerDisplayTradeItem); + villagerSpawnIronGolemRadius = getInt("mobs.villager.spawn-iron-golem.radius", villagerSpawnIronGolemRadius); + villagerSpawnIronGolemLimit = getInt("mobs.villager.spawn-iron-golem.limit", villagerSpawnIronGolemLimit); + villagerAcquirePoiSearchRadius = getInt("mobs.villager.search-radius.acquire-poi", villagerAcquirePoiSearchRadius); + villagerNearestBedSensorSearchRadius = getInt("mobs.villager.search-radius.nearest-bed-sensor", villagerNearestBedSensorSearchRadius); + } + + public boolean vindicatorRidable = false; + public boolean vindicatorRidableInWater = true; + public boolean vindicatorControllable = true; + public double vindicatorMaxHealth = 24.0D; + public double vindicatorScale = 1.0D; + public double vindicatorJohnnySpawnChance = 0D; + public boolean vindicatorTakeDamageFromWater = false; + public boolean vindicatorAlwaysDropExp = false; + private void vindicatorSettings() { + vindicatorRidable = getBoolean("mobs.vindicator.ridable", vindicatorRidable); + vindicatorRidableInWater = getBoolean("mobs.vindicator.ridable-in-water", vindicatorRidableInWater); + vindicatorControllable = getBoolean("mobs.vindicator.controllable", vindicatorControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.vindicator.attributes.max-health", vindicatorMaxHealth); + set("mobs.vindicator.attributes.max-health", null); + set("mobs.vindicator.attributes.max_health", oldValue); + } + vindicatorMaxHealth = getDouble("mobs.vindicator.attributes.max_health", vindicatorMaxHealth); + vindicatorScale = Mth.clamp(getDouble("mobs.vindicator.attributes.scale", vindicatorScale), 0.0625D, 16.0D); + vindicatorJohnnySpawnChance = getDouble("mobs.vindicator.johnny.spawn-chance", vindicatorJohnnySpawnChance); + vindicatorTakeDamageFromWater = getBoolean("mobs.vindicator.takes-damage-from-water", vindicatorTakeDamageFromWater); + vindicatorAlwaysDropExp = getBoolean("mobs.vindicator.always-drop-exp", vindicatorAlwaysDropExp); + } + + public boolean wanderingTraderRidable = false; + public boolean wanderingTraderRidableInWater = true; + public boolean wanderingTraderControllable = true; + public double wanderingTraderMaxHealth = 20.0D; + public double wanderingTraderScale = 1.0D; + public boolean wanderingTraderFollowEmeraldBlock = false; + public double wanderingTraderTemptRange = 10.0D; + public boolean wanderingTraderCanBeLeashed = false; + public boolean wanderingTraderTakeDamageFromWater = false; + public boolean wanderingTraderAllowTrading = true; + public boolean wanderingTraderAlwaysDropExp = false; + private void wanderingTraderSettings() { + wanderingTraderRidable = getBoolean("mobs.wandering_trader.ridable", wanderingTraderRidable); + wanderingTraderRidableInWater = getBoolean("mobs.wandering_trader.ridable-in-water", wanderingTraderRidableInWater); + wanderingTraderControllable = getBoolean("mobs.wandering_trader.controllable", wanderingTraderControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.wandering_trader.attributes.max-health", wanderingTraderMaxHealth); + set("mobs.wandering_trader.attributes.max-health", null); + set("mobs.wandering_trader.attributes.max_health", oldValue); + } + wanderingTraderMaxHealth = getDouble("mobs.wandering_trader.attributes.max_health", wanderingTraderMaxHealth); + wanderingTraderScale = Mth.clamp(getDouble("mobs.wandering_trader.attributes.scale", wanderingTraderScale), 0.0625D, 16.0D); + wanderingTraderFollowEmeraldBlock = getBoolean("mobs.wandering_trader.follow-emerald-blocks", wanderingTraderFollowEmeraldBlock); + wanderingTraderTemptRange = getDouble("mobs.wandering_trader.attributes.tempt_range", wanderingTraderTemptRange); + wanderingTraderCanBeLeashed = getBoolean("mobs.wandering_trader.can-be-leashed", wanderingTraderCanBeLeashed); + wanderingTraderTakeDamageFromWater = getBoolean("mobs.wandering_trader.takes-damage-from-water", wanderingTraderTakeDamageFromWater); + wanderingTraderAllowTrading = getBoolean("mobs.wandering_trader.allow-trading", wanderingTraderAllowTrading); + wanderingTraderAlwaysDropExp = getBoolean("mobs.wandering_trader.always-drop-exp", wanderingTraderAlwaysDropExp); + } + + public boolean wardenRidable = false; + public boolean wardenRidableInWater = true; + public boolean wardenControllable = true; + private void wardenSettings() { + wardenRidable = getBoolean("mobs.warden.ridable", wardenRidable); + wardenRidableInWater = getBoolean("mobs.warden.ridable-in-water", wardenRidableInWater); + wardenControllable = getBoolean("mobs.warden.controllable", wardenControllable); + } + + public boolean witchRidable = false; + public boolean witchRidableInWater = true; + public boolean witchControllable = true; + public double witchMaxHealth = 26.0D; + public double witchScale = 1.0D; + public boolean witchTakeDamageFromWater = false; + public boolean witchAlwaysDropExp = false; + private void witchSettings() { + witchRidable = getBoolean("mobs.witch.ridable", witchRidable); + witchRidableInWater = getBoolean("mobs.witch.ridable-in-water", witchRidableInWater); + witchControllable = getBoolean("mobs.witch.controllable", witchControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.witch.attributes.max-health", witchMaxHealth); + set("mobs.witch.attributes.max-health", null); + set("mobs.witch.attributes.max_health", oldValue); + } + witchMaxHealth = getDouble("mobs.witch.attributes.max_health", witchMaxHealth); + witchScale = Mth.clamp(getDouble("mobs.witch.attributes.scale", witchScale), 0.0625D, 16.0D); + witchTakeDamageFromWater = getBoolean("mobs.witch.takes-damage-from-water", witchTakeDamageFromWater); + witchAlwaysDropExp = getBoolean("mobs.witch.always-drop-exp", witchAlwaysDropExp); + } + + public boolean witherRidable = false; + public boolean witherRidableInWater = true; + public boolean witherControllable = true; + public double witherMaxY = 320D; + public double witherMaxHealth = 300.0D; + public double witherScale = 1.0D; + public float witherHealthRegenAmount = 1.0f; + public int witherHealthRegenDelay = 20; + public boolean witherBypassMobGriefing = false; + public boolean witherTakeDamageFromWater = false; + public boolean witherCanRideVehicles = false; + public float witherExplosionRadius = 1.0F; + public boolean witherPlaySpawnSound = true; + public boolean witherAlwaysDropExp = false; + private void witherSettings() { + witherRidable = getBoolean("mobs.wither.ridable", witherRidable); + witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater); + witherControllable = getBoolean("mobs.wither.controllable", witherControllable); + witherMaxY = getDouble("mobs.wither.ridable-max-y", witherMaxY); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.wither.max-health", witherMaxHealth); + set("mobs.wither.max_health", null); + set("mobs.wither.attributes.max-health", oldValue); + } else if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.wither.attributes.max-health", witherMaxHealth); + set("mobs.wither.attributes.max-health", null); + set("mobs.wither.attributes.max_health", oldValue); + } + witherMaxHealth = getDouble("mobs.wither.attributes.max_health", witherMaxHealth); + witherScale = Mth.clamp(getDouble("mobs.wither.attributes.scale", witherScale), 0.0625D, 16.0D); + witherHealthRegenAmount = (float) getDouble("mobs.wither.health-regen-amount", witherHealthRegenAmount); + witherHealthRegenDelay = getInt("mobs.wither.health-regen-delay", witherHealthRegenDelay); + witherBypassMobGriefing = getBoolean("mobs.wither.bypass-mob-griefing", witherBypassMobGriefing); + witherTakeDamageFromWater = getBoolean("mobs.wither.takes-damage-from-water", witherTakeDamageFromWater); + witherCanRideVehicles = getBoolean("mobs.wither.can-ride-vehicles", witherCanRideVehicles); + witherExplosionRadius = (float) getDouble("mobs.wither.explosion-radius", witherExplosionRadius); + witherPlaySpawnSound = getBoolean("mobs.wither.play-spawn-sound", witherPlaySpawnSound); + witherAlwaysDropExp = getBoolean("mobs.wither.always-drop-exp", witherAlwaysDropExp); + } + + public boolean witherSkeletonRidable = false; + public boolean witherSkeletonRidableInWater = true; + public boolean witherSkeletonControllable = true; + public double witherSkeletonMaxHealth = 20.0D; + public double witherSkeletonScale = 1.0D; + public boolean witherSkeletonTakeDamageFromWater = false; + public boolean witherSkeletonAlwaysDropExp = false; + private void witherSkeletonSettings() { + witherSkeletonRidable = getBoolean("mobs.wither_skeleton.ridable", witherSkeletonRidable); + witherSkeletonRidableInWater = getBoolean("mobs.wither_skeleton.ridable-in-water", witherSkeletonRidableInWater); + witherSkeletonControllable = getBoolean("mobs.wither_skeleton.controllable", witherSkeletonControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.wither_skeleton.attributes.max-health", witherSkeletonMaxHealth); + set("mobs.wither_skeleton.attributes.max-health", null); + set("mobs.wither_skeleton.attributes.max_health", oldValue); + } + witherSkeletonMaxHealth = getDouble("mobs.wither_skeleton.attributes.max_health", witherSkeletonMaxHealth); + witherSkeletonScale = Mth.clamp(getDouble("mobs.wither_skeleton.attributes.scale", witherSkeletonScale), 0.0625D, 16.0D); + witherSkeletonTakeDamageFromWater = getBoolean("mobs.wither_skeleton.takes-damage-from-water", witherSkeletonTakeDamageFromWater); + witherSkeletonAlwaysDropExp = getBoolean("mobs.wither_skeleton.always-drop-exp", witherSkeletonAlwaysDropExp); + } + + public boolean wolfRidable = false; + public boolean wolfRidableInWater = true; + public boolean wolfControllable = true; + public double wolfMaxHealth = 8.0D; + public double wolfScale = 1.0D; + public DyeColor wolfDefaultCollarColor = DyeColor.RED; + public boolean wolfMilkCuresRabies = true; + public double wolfNaturalRabid = 0.0D; + public int wolfBreedingTicks = 6000; + public boolean wolfTakeDamageFromWater = false; + public boolean wolfAlwaysDropExp = false; + private void wolfSettings() { + wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable); + wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater); + wolfControllable = getBoolean("mobs.wolf.controllable", wolfControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.wolf.attributes.max-health", wolfMaxHealth); + set("mobs.wolf.attributes.max-health", null); + set("mobs.wolf.attributes.max_health", oldValue); + } + wolfMaxHealth = getDouble("mobs.wolf.attributes.max_health", wolfMaxHealth); + wolfScale = Mth.clamp(getDouble("mobs.wolf.attributes.scale", wolfScale), 0.0625D, 16.0D); + try { + wolfDefaultCollarColor = DyeColor.valueOf(getString("mobs.wolf.default-collar-color", wolfDefaultCollarColor.name())); + } catch (IllegalArgumentException ignore) { + wolfDefaultCollarColor = DyeColor.RED; + } + wolfMilkCuresRabies = getBoolean("mobs.wolf.milk-cures-rabid-wolves", wolfMilkCuresRabies); + wolfNaturalRabid = getDouble("mobs.wolf.spawn-rabid-chance", wolfNaturalRabid); + wolfBreedingTicks = getInt("mobs.wolf.breeding-delay-ticks", wolfBreedingTicks); + wolfTakeDamageFromWater = getBoolean("mobs.wolf.takes-damage-from-water", wolfTakeDamageFromWater); + wolfAlwaysDropExp = getBoolean("mobs.wolf.always-drop-exp", wolfAlwaysDropExp); + } + + public boolean zoglinRidable = false; + public boolean zoglinRidableInWater = true; + public boolean zoglinControllable = true; + public double zoglinMaxHealth = 40.0D; + public double zoglinScale = 1.0D; + public boolean zoglinTakeDamageFromWater = false; + public boolean zoglinAlwaysDropExp = false; + private void zoglinSettings() { + zoglinRidable = getBoolean("mobs.zoglin.ridable", zoglinRidable); + zoglinRidableInWater = getBoolean("mobs.zoglin.ridable-in-water", zoglinRidableInWater); + zoglinControllable = getBoolean("mobs.zoglin.controllable", zoglinControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zoglin.attributes.max-health", zoglinMaxHealth); + set("mobs.zoglin.attributes.max-health", null); + set("mobs.zoglin.attributes.max_health", oldValue); + } + zoglinMaxHealth = getDouble("mobs.zoglin.attributes.max_health", zoglinMaxHealth); + zoglinScale = Mth.clamp(getDouble("mobs.zoglin.attributes.scale", zoglinScale), 0.0625D, 16.0D); + zoglinTakeDamageFromWater = getBoolean("mobs.zoglin.takes-damage-from-water", zoglinTakeDamageFromWater); + zoglinAlwaysDropExp = getBoolean("mobs.zoglin.always-drop-exp", zoglinAlwaysDropExp); + } + + public boolean zombieRidable = false; + public boolean zombieRidableInWater = true; + public boolean zombieControllable = true; + public double zombieMaxHealth = 20.0D; + public double zombieScale = 1.0D; + public double zombieSpawnReinforcements = 0.1D; + public boolean zombieJockeyOnlyBaby = true; + public double zombieJockeyChance = 0.05D; + public boolean zombieJockeyTryExistingChickens = true; + public boolean zombieAggressiveTowardsVillagerWhenLagging = true; + public boolean zombieBypassMobGriefing = false; + public boolean zombieTakeDamageFromWater = false; + public boolean zombieAlwaysDropExp = false; + public double zombieHeadVisibilityPercent = 0.5D; + private void zombieSettings() { + zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); + zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); + zombieControllable = getBoolean("mobs.zombie.controllable", zombieControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zombie.attributes.max-health", zombieMaxHealth); + set("mobs.zombie.attributes.max-health", null); + set("mobs.zombie.attributes.max_health", oldValue); + } + zombieMaxHealth = getDouble("mobs.zombie.attributes.max_health", zombieMaxHealth); + zombieScale = Mth.clamp(getDouble("mobs.zombie.attributes.scale", zombieScale), 0.0625D, 16.0D); + zombieSpawnReinforcements = getDouble("mobs.zombie.attributes.spawn_reinforcements", zombieSpawnReinforcements); + zombieJockeyOnlyBaby = getBoolean("mobs.zombie.jockey.only-babies", zombieJockeyOnlyBaby); + zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance); + zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens); + zombieAggressiveTowardsVillagerWhenLagging = getBoolean("mobs.zombie.aggressive-towards-villager-when-lagging", zombieAggressiveTowardsVillagerWhenLagging); + zombieBypassMobGriefing = getBoolean("mobs.zombie.bypass-mob-griefing", zombieBypassMobGriefing); + zombieTakeDamageFromWater = getBoolean("mobs.zombie.takes-damage-from-water", zombieTakeDamageFromWater); + zombieAlwaysDropExp = getBoolean("mobs.zombie.always-drop-exp", zombieAlwaysDropExp); + zombieHeadVisibilityPercent = getDouble("mobs.zombie.head-visibility-percent", zombieHeadVisibilityPercent); + } + + public boolean zombieHorseRidable = false; + public boolean zombieHorseRidableInWater = false; + public boolean zombieHorseCanSwim = false; + public double zombieHorseMaxHealthMin = 15.0D; + public double zombieHorseMaxHealthMax = 15.0D; + public double zombieHorseJumpStrengthMin = 0.4D; + public double zombieHorseJumpStrengthMax = 1.0D; + public double zombieHorseMovementSpeedMin = 0.2D; + public double zombieHorseMovementSpeedMax = 0.2D; + public double zombieHorseSpawnChance = 0.0D; + public boolean zombieHorseTakeDamageFromWater = false; + public boolean zombieHorseAlwaysDropExp = false; + private void zombieHorseSettings() { + zombieHorseRidable = getBoolean("mobs.zombie_horse.ridable", zombieHorseRidable); + zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater); + zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zombie_horse.attributes.max-health", zombieHorseMaxHealthMin); + set("mobs.zombie_horse.attributes.max-health", null); + set("mobs.zombie_horse.attributes.max_health.min", oldValue); + set("mobs.zombie_horse.attributes.max_health.max", oldValue); + } + zombieHorseMaxHealthMin = getDouble("mobs.zombie_horse.attributes.max_health.min", zombieHorseMaxHealthMin); + zombieHorseMaxHealthMax = getDouble("mobs.zombie_horse.attributes.max_health.max", zombieHorseMaxHealthMax); + zombieHorseJumpStrengthMin = getDouble("mobs.zombie_horse.attributes.jump_strength.min", zombieHorseJumpStrengthMin); + zombieHorseJumpStrengthMax = getDouble("mobs.zombie_horse.attributes.jump_strength.max", zombieHorseJumpStrengthMax); + zombieHorseMovementSpeedMin = getDouble("mobs.zombie_horse.attributes.movement_speed.min", zombieHorseMovementSpeedMin); + zombieHorseMovementSpeedMax = getDouble("mobs.zombie_horse.attributes.movement_speed.max", zombieHorseMovementSpeedMax); + zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance); + zombieHorseTakeDamageFromWater = getBoolean("mobs.zombie_horse.takes-damage-from-water", zombieHorseTakeDamageFromWater); + zombieHorseAlwaysDropExp = getBoolean("mobs.zombie_horse.always-drop-exp", zombieHorseAlwaysDropExp); + } + + public boolean zombieVillagerRidable = false; + public boolean zombieVillagerRidableInWater = true; + public boolean zombieVillagerControllable = true; + public double zombieVillagerMaxHealth = 20.0D; + public double zombieVillagerScale = 1.0D; + public double zombieVillagerSpawnReinforcements = 0.1D; + public boolean zombieVillagerJockeyOnlyBaby = true; + public double zombieVillagerJockeyChance = 0.05D; + public boolean zombieVillagerJockeyTryExistingChickens = true; + public boolean zombieVillagerTakeDamageFromWater = false; + public int zombieVillagerCuringTimeMin = 3600; + public int zombieVillagerCuringTimeMax = 6000; + public boolean zombieVillagerCureEnabled = true; + public boolean zombieVillagerAlwaysDropExp = false; + private void zombieVillagerSettings() { + zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable); + zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater); + zombieVillagerControllable = getBoolean("mobs.zombie_villager.controllable", zombieVillagerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zombie_villager.attributes.max-health", zombieVillagerMaxHealth); + set("mobs.zombie_villager.attributes.max-health", null); + set("mobs.zombie_villager.attributes.max_health", oldValue); + } + zombieVillagerMaxHealth = getDouble("mobs.zombie_villager.attributes.max_health", zombieVillagerMaxHealth); + zombieVillagerScale = Mth.clamp(getDouble("mobs.zombie_villager.attributes.scale", zombieVillagerScale), 0.0625D, 16.0D); + zombieVillagerSpawnReinforcements = getDouble("mobs.zombie_villager.attributes.spawn_reinforcements", zombieVillagerSpawnReinforcements); + zombieVillagerJockeyOnlyBaby = getBoolean("mobs.zombie_villager.jockey.only-babies", zombieVillagerJockeyOnlyBaby); + zombieVillagerJockeyChance = getDouble("mobs.zombie_villager.jockey.chance", zombieVillagerJockeyChance); + zombieVillagerJockeyTryExistingChickens = getBoolean("mobs.zombie_villager.jockey.try-existing-chickens", zombieVillagerJockeyTryExistingChickens); + zombieVillagerTakeDamageFromWater = getBoolean("mobs.zombie_villager.takes-damage-from-water", zombieVillagerTakeDamageFromWater); + zombieVillagerCuringTimeMin = getInt("mobs.zombie_villager.curing_time.min", zombieVillagerCuringTimeMin); + zombieVillagerCuringTimeMax = getInt("mobs.zombie_villager.curing_time.max", zombieVillagerCuringTimeMax); + zombieVillagerCureEnabled = getBoolean("mobs.zombie_villager.cure.enabled", zombieVillagerCureEnabled); + zombieVillagerAlwaysDropExp = getBoolean("mobs.zombie_villager.always-drop-exp", zombieVillagerAlwaysDropExp); + } + + public boolean zombifiedPiglinRidable = false; + public boolean zombifiedPiglinRidableInWater = true; + public boolean zombifiedPiglinControllable = true; + public double zombifiedPiglinMaxHealth = 20.0D; + public double zombifiedPiglinScale = 1.0D; + public double zombifiedPiglinSpawnReinforcements = 0.0D; + public boolean zombifiedPiglinJockeyOnlyBaby = true; + public double zombifiedPiglinJockeyChance = 0.05D; + public boolean zombifiedPiglinJockeyTryExistingChickens = true; + public boolean zombifiedPiglinCountAsPlayerKillWhenAngry = true; + public boolean zombifiedPiglinTakeDamageFromWater = false; + public boolean zombifiedPiglinAlwaysDropExp = false; + private void zombifiedPiglinSettings() { + zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable); + zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater); + zombifiedPiglinControllable = getBoolean("mobs.zombified_piglin.controllable", zombifiedPiglinControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zombified_piglin.attributes.max-health", zombifiedPiglinMaxHealth); + set("mobs.zombified_piglin.attributes.max-health", null); + set("mobs.zombified_piglin.attributes.max_health", oldValue); + } + zombifiedPiglinMaxHealth = getDouble("mobs.zombified_piglin.attributes.max_health", zombifiedPiglinMaxHealth); + zombifiedPiglinScale = Mth.clamp(getDouble("mobs.zombified_piglin.attributes.scale", zombifiedPiglinScale), 0.0625D, 16.0D); + zombifiedPiglinSpawnReinforcements = getDouble("mobs.zombified_piglin.attributes.spawn_reinforcements", zombifiedPiglinSpawnReinforcements); + zombifiedPiglinJockeyOnlyBaby = getBoolean("mobs.zombified_piglin.jockey.only-babies", zombifiedPiglinJockeyOnlyBaby); + zombifiedPiglinJockeyChance = getDouble("mobs.zombified_piglin.jockey.chance", zombifiedPiglinJockeyChance); + zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens); + zombifiedPiglinCountAsPlayerKillWhenAngry = getBoolean("mobs.zombified_piglin.count-as-player-kill-when-angry", zombifiedPiglinCountAsPlayerKillWhenAngry); + zombifiedPiglinTakeDamageFromWater = getBoolean("mobs.zombified_piglin.takes-damage-from-water", zombifiedPiglinTakeDamageFromWater); + zombifiedPiglinAlwaysDropExp = getBoolean("mobs.zombified_piglin.always-drop-exp", zombifiedPiglinAlwaysDropExp); + } + + public float hungerStarvationDamage = 1.0F; + private void hungerSettings() { + hungerStarvationDamage = (float) getDouble("hunger.starvation-damage", hungerStarvationDamage); + } + + public int conduitDistance = 16; + public double conduitDamageDistance = 8; + public float conduitDamageAmount = 4; + public Block[] conduitBlocks; + private void conduitSettings() { + conduitDistance = getInt("blocks.conduit.effect-distance", conduitDistance); + conduitDamageDistance = getDouble("blocks.conduit.mob-damage.distance", conduitDamageDistance); + conduitDamageAmount = (float) getDouble("blocks.conduit.mob-damage.damage-amount", conduitDamageAmount); + List conduitBlockList = new ArrayList<>(); + getList("blocks.conduit.valid-ring-blocks", new ArrayList(){{ + add("minecraft:prismarine"); + add("minecraft:prismarine_bricks"); + add("minecraft:sea_lantern"); + add("minecraft:dark_prismarine"); + }}).forEach(key -> { + Block block = BuiltInRegistries.BLOCK.getValue(ResourceLocation.parse(key.toString())); + if (!block.defaultBlockState().isAir()) { + conduitBlockList.add(block); + } + }); + conduitBlocks = conduitBlockList.toArray(Block[]::new); + } + + public float cauldronRainChance = 0.05F; + public float cauldronPowderSnowChance = 0.1F; + public float cauldronDripstoneWaterFillChance = 0.17578125F; + public float cauldronDripstoneLavaFillChance = 0.05859375F; + private void cauldronSettings() { + cauldronRainChance = (float) getDouble("blocks.cauldron.fill-chances.rain", cauldronRainChance); + cauldronPowderSnowChance = (float) getDouble("blocks.cauldron.fill-chances.powder-snow", cauldronPowderSnowChance); + cauldronDripstoneWaterFillChance = (float) getDouble("blocks.cauldron.fill-chances.dripstone-water", cauldronDripstoneWaterFillChance); + cauldronDripstoneLavaFillChance = (float) getDouble("blocks.cauldron.fill-chances.dripstone-lava", cauldronDripstoneLavaFillChance); + } + + public float shearsCanDefuseTntChance = 0.00F; + public boolean shearsCanDefuseTnt = false; + private void shearsCanDefuseTntSettings() { + shearsCanDefuseTntChance = (float) getDouble("gameplay-mechanics.item.shears.defuse-tnt-chance", 0.00D); + shearsCanDefuseTnt = shearsCanDefuseTntChance > 0.00F; + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/CompassCommand.java b/src/main/java/org/purpurmc/purpur/command/CompassCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..79b8490832d2a0cc7846ddcb091cb6bcac74ea45 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/CompassCommand.java @@ -0,0 +1,27 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.task.CompassTask; + +public class CompassCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("compass") + .requires(listener -> listener.hasPermission(2, "bukkit.command.compass")) + .executes(context -> { + ServerPlayer player = context.getSource().getPlayerOrException(); + CompassTask task = CompassTask.instance(); + if (player.compassBar()) { + task.removePlayer(player.getBukkitEntity()); + player.compassBar(false); + } else { + task.addPlayer(player.getBukkitEntity()); + player.compassBar(true); + } + return 1; + }) + ); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/CreditsCommand.java b/src/main/java/org/purpurmc/purpur/command/CreditsCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..40d2fab4a9728ac90c36e30c130f3116b7025d11 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/CreditsCommand.java @@ -0,0 +1,35 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.network.protocol.game.ClientboundGameEventPacket; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; + +import java.util.Collection; +import java.util.Collections; + +public class CreditsCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("credits") + .requires((listener) -> listener.hasPermission(2, "bukkit.command.credits")) + .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.credits.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + ClientboundGameEventPacket packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1F); + player.connection.send(packet); + String output = String.format(PurpurConfig.creditsCommandOutput, player.getGameProfile().getName()); + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/DemoCommand.java b/src/main/java/org/purpurmc/purpur/command/DemoCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..235f3cd89f675b70a6152a00534608c0902f19fd --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/DemoCommand.java @@ -0,0 +1,35 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.network.protocol.game.ClientboundGameEventPacket; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; + +import java.util.Collection; +import java.util.Collections; + +public class DemoCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("demo") + .requires((listener) -> listener.hasPermission(2, "bukkit.command.demo")) + .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.demo.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + ClientboundGameEventPacket packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.DEMO_EVENT, 0); + player.connection.send(packet); + String output = String.format(PurpurConfig.demoCommandOutput, player.getGameProfile().getName()); + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/PingCommand.java b/src/main/java/org/purpurmc/purpur/command/PingCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..f202b98a194604e39798fdb8e417c6d2835f71c8 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/PingCommand.java @@ -0,0 +1,33 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +import java.util.Collection; +import java.util.Collections; + +public class PingCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("ping") + .requires((listener) -> listener.hasPermission(2, "bukkit.command.ping")) + .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.ping.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + String output = String.format(PurpurConfig.pingCommandOutput, player.getGameProfile().getName(), player.connection.latency()); + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..2621e54879e9ab0029a875f1d09eee67878b90d5 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java @@ -0,0 +1,66 @@ +package org.purpurmc.purpur.command; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import org.purpurmc.purpur.PurpurConfig; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class PurpurCommand extends Command { + public PurpurCommand(String name) { + super(name); + this.description = "Purpur related commands"; + this.usageMessage = "/purpur [reload | version]"; + this.setPermission("bukkit.command.purpur"); + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { + if (args.length == 1) { + return Stream.of("reload", "version") + .filter(arg -> arg.startsWith(args[0].toLowerCase())) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if (!testPermission(sender)) return true; + + if (args.length != 1) { + sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); + return false; + } + + if (args[0].equalsIgnoreCase("reload")) { + Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues."); + Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server."); + + MinecraftServer console = MinecraftServer.getServer(); + PurpurConfig.init((File) console.options.valueOf("purpur-settings")); + for (ServerLevel level : console.getAllLevels()) { + level.purpurConfig.init(); + level.resetBreedingCooldowns(); + } + console.server.reloadCount++; + + Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Purpur config reload complete."); + } else if (args[0].equalsIgnoreCase("version")) { + Command verCmd = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version"); + if (verCmd != null) { + return verCmd.execute(sender, commandLabel, new String[0]); + } + } + + return true; + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java b/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..2852c07adb080c34905f5d1b19efed8ea47eecc6 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java @@ -0,0 +1,44 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; +import org.purpurmc.purpur.task.RamBarTask; + +import java.util.Collection; +import java.util.Collections; + +public class RamBarCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("rambar") + .requires(listener -> listener.hasPermission(2, "bukkit.command.rambar")) + .executes(context -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.rambar.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + boolean result = RamBarTask.instance().togglePlayer(player.getBukkitEntity()); + player.ramBar(result); + + Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.rambarCommandOutput, + Placeholder.component("onoff", Component.translatable(result ? "options.on" : "options.off") + .color(result ? NamedTextColor.GREEN : NamedTextColor.RED)), + Placeholder.parsed("target", player.getGameProfile().getName())); + + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/RamCommand.java b/src/main/java/org/purpurmc/purpur/command/RamCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..992f8dfc628c7485e335191e1308cdfd4eedfbe8 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/RamCommand.java @@ -0,0 +1,30 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import org.purpurmc.purpur.PurpurConfig; +import org.purpurmc.purpur.task.RamBarTask; + +public class RamCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("ram") + .requires(listener -> listener.hasPermission(2, "bukkit.command.ram")) + .executes(context -> { + CommandSourceStack sender = context.getSource(); + RamBarTask ramBar = RamBarTask.instance(); + sender.sendSuccess(() -> PaperAdventure.asVanilla(MiniMessage.miniMessage().deserialize(PurpurConfig.ramCommandOutput, + Placeholder.component("allocated", ramBar.format(ramBar.getAllocated())), + Placeholder.component("used", ramBar.format(ramBar.getUsed())), + Placeholder.component("xmx", ramBar.format(ramBar.getXmx())), + Placeholder.component("xms", ramBar.format(ramBar.getXms())), + Placeholder.unparsed("percent", ((int) (ramBar.getPercent() * 100)) + "%") + )), false); + return 1; + }) + ); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/TPSBarCommand.java b/src/main/java/org/purpurmc/purpur/command/TPSBarCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..d8f9b044107ff7c29a83eb5378aa9f5465ba1995 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/TPSBarCommand.java @@ -0,0 +1,44 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; +import org.purpurmc.purpur.task.TPSBarTask; + +import java.util.Collection; +import java.util.Collections; + +public class TPSBarCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("tpsbar") + .requires(listener -> listener.hasPermission(2, "bukkit.command.tpsbar")) + .executes(context -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.tpsbar.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + boolean result = TPSBarTask.instance().togglePlayer(player.getBukkitEntity()); + player.tpsBar(result); + + Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.tpsbarCommandOutput, + Placeholder.component("onoff", Component.translatable(result ? "options.on" : "options.off") + .color(result ? NamedTextColor.GREEN : NamedTextColor.RED)), + Placeholder.parsed("target", player.getGameProfile().getName())); + + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/UptimeCommand.java b/src/main/java/org/purpurmc/purpur/command/UptimeCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..4bb475099bcf8f05d5f1474e7fbf29c57c2c40cd --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/UptimeCommand.java @@ -0,0 +1,55 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.server.MinecraftServer; +import org.purpurmc.purpur.PurpurConfig; + +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +public class UptimeCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("uptime") + .requires((listener) -> listener.hasPermission(2, "bukkit.command.uptime")) + .executes((context) -> execute(context.getSource())) + ); + } + + private static int execute(CommandSourceStack sender) { + Data data = new Data(); + + data.format = PurpurConfig.uptimeFormat; + data.hide = true; + data.millis = System.currentTimeMillis() - MinecraftServer.startTimeMillis; + + process(data, "", PurpurConfig.uptimeDay, PurpurConfig.uptimeDays, TimeUnit.DAYS, TimeUnit.MILLISECONDS::toDays); + process(data, "", PurpurConfig.uptimeHour, PurpurConfig.uptimeHours, TimeUnit.HOURS, TimeUnit.MILLISECONDS::toHours); + process(data, "", PurpurConfig.uptimeMinute, PurpurConfig.uptimeMinutes, TimeUnit.MINUTES, TimeUnit.MILLISECONDS::toMinutes); + data.hide = false; // never hide seconds + process(data, "", PurpurConfig.uptimeSecond, PurpurConfig.uptimeSeconds, TimeUnit.SECONDS, TimeUnit.MILLISECONDS::toSeconds); + + Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.uptimeCommandOutput, Placeholder.unparsed("uptime", data.format)); + sender.sendSuccess(output, false); + return 1; + } + + private static void process(Data data, String replace, String singular, String plural, TimeUnit unit, Function func) { + if (data.format.contains(replace)) { + long val = func.apply(data.millis); + if (data.hide) data.hide = val == 0; + if (!data.hide) data.millis -= unit.toMillis(val); + data.format = data.format.replace(replace, data.hide ? "" : String.format(val == 1 ? singular : plural, val)); + } + } + + private static class Data { + String format; + boolean hide; + long millis; + } +} diff --git a/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java b/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java new file mode 100644 index 0000000000000000000000000000000000000000..a04d23bd98075cd65a24d4de8d18281d1668480f --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java @@ -0,0 +1,67 @@ +package org.purpurmc.purpur.configuration.transformation; + +import io.papermc.paper.configuration.Configurations; +import io.papermc.paper.configuration.PaperConfigurations; +import io.papermc.paper.configuration.type.number.DoubleOr; +import java.util.OptionalDouble; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.purpurmc.purpur.PurpurConfig; +import org.spongepowered.configurate.ConfigurateException; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.NodePath; +import org.spongepowered.configurate.transformation.ConfigurationTransformation; +import org.spongepowered.configurate.transformation.TransformAction; + +import static org.spongepowered.configurate.NodePath.path; + +public class VoidDamageHeightMigration implements TransformAction { + + public static boolean HAS_BEEN_REGISTERED = false; + + public static final String ENVIRONMENT_KEY = "environment"; + public static final String VOID_DAMAGE_KEY = "void-damage-amount"; + public static final String VOID_DAMAGE_MIN_HEIGHT_OFFSET_KEY = "void-damage-min-build-height-offset"; + public static final double DEFAULT_VOID_DAMAGE_HEIGHT = -64.0D; + public static final double DEFAULT_VOID_DAMAGE = 4.0D; + + private final String worldName; + + private VoidDamageHeightMigration(String worldName) { + this.worldName = PaperConfigurations.WORLD_DEFAULTS.equals(worldName) ? "default" : worldName; + } + + @Override + public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException { + String purpurVoidDamageHeightPath = "world-settings." + this.worldName + ".gameplay-mechanics.void-damage-height"; + ConfigurationNode voidDamageMinHeightOffsetNode = value.node(ENVIRONMENT_KEY, VOID_DAMAGE_MIN_HEIGHT_OFFSET_KEY); + if (PurpurConfig.config.contains(purpurVoidDamageHeightPath)) { + double purpurVoidDamageHeight = PurpurConfig.config.getDouble(purpurVoidDamageHeightPath); + if (purpurVoidDamageHeight != DEFAULT_VOID_DAMAGE_HEIGHT && (voidDamageMinHeightOffsetNode.empty() || voidDamageMinHeightOffsetNode.getDouble() == DEFAULT_VOID_DAMAGE_HEIGHT)) { + voidDamageMinHeightOffsetNode.raw(null); + voidDamageMinHeightOffsetNode.set(purpurVoidDamageHeight); + } + PurpurConfig.config.set(purpurVoidDamageHeightPath, null); + } + + String purpurVoidDamagePath = "world-settings." + this.worldName + ".gameplay-mechanics.void-damage-dealt"; + ConfigurationNode voidDamageNode = value.node(ENVIRONMENT_KEY, VOID_DAMAGE_KEY); + if (PurpurConfig.config.contains(purpurVoidDamagePath)) { + double purpurVoidDamage = PurpurConfig.config.getDouble(purpurVoidDamagePath); + if (purpurVoidDamage != DEFAULT_VOID_DAMAGE && (voidDamageNode.empty() || voidDamageNode.getDouble() == DEFAULT_VOID_DAMAGE)) { + voidDamageNode.raw(null); + voidDamageNode.set(new DoubleOr.Disabled(OptionalDouble.of(purpurVoidDamage))); + } + PurpurConfig.config.set(purpurVoidDamagePath, null); + } + + return null; + } + + public static void apply(final ConfigurationTransformation.Builder builder, final Configurations.ContextMap contextMap) { + if (PurpurConfig.version < 36) { + HAS_BEEN_REGISTERED = true; + builder.addAction(path(), new VoidDamageHeightMigration(contextMap.require(Configurations.WORLD_NAME))); + } + } + +} diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java new file mode 100644 index 0000000000000000000000000000000000000000..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..dd219518150ca90f89ad238904fd4095efe032d8 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java @@ -0,0 +1,79 @@ +package org.purpurmc.purpur.controller; + + +import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.control.LookControl; +import net.minecraft.world.entity.player.Player; + +public class LookControllerWASD extends LookControl { + protected final Mob entity; + private float yOffset = 0; + private float xOffset = 0; + + public LookControllerWASD(Mob entity) { + super(entity); + this.entity = entity; + } + + // tick + @Override + public void tick() { + if (entity.getRider() != null && entity.isControllable()) { + purpurTick(entity.getRider()); + } else { + vanillaTick(); + } + } + + protected void purpurTick(Player rider) { + setYawPitch(rider.getYRot(), rider.getXRot()); + } + + public void vanillaTick() { + super.tick(); + } + + public void setYawPitch(float yRot, float xRot) { + entity.setXRot(normalizePitch(xRot + xOffset)); + entity.setYRot(normalizeYaw(yRot + yOffset)); + entity.setYHeadRot(entity.getYRot()); + entity.xRotO = entity.getXRot(); + entity.yRotO = entity.getYRot(); + + ClientboundMoveEntityPacket.PosRot entityPacket = new ClientboundMoveEntityPacket.PosRot( + entity.getId(), + (short) 0, (short) 0, (short) 0, + (byte) Mth.floor(entity.getYRot() * 256.0F / 360.0F), + (byte) Mth.floor(entity.getXRot() * 256.0F / 360.0F), + entity.onGround + ); + ((ServerLevel) entity.level()).getChunkSource().broadcast(entity, entityPacket); + } + + public void setOffsets(float yaw, float pitch) { + yOffset = yaw; + xOffset = pitch; + } + + public float normalizeYaw(float yaw) { + yaw %= 360.0f; + if (yaw >= 180.0f) { + yaw -= 360.0f; + } else if (yaw < -180.0f) { + yaw += 360.0f; + } + return yaw; + } + + public float normalizePitch(float pitch) { + if (pitch > 90.0f) { + pitch = 90.0f; + } else if (pitch < -90.0f) { + pitch = -90.0f; + } + return pitch; + } +} diff --git a/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java new file mode 100644 index 0000000000000000000000000000000000000000..ad85c1ff6cd5d5ce2262bdb367ce9c8a5b707170 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java @@ -0,0 +1,92 @@ +package org.purpurmc.purpur.controller; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.ai.control.MoveControl; +import net.minecraft.world.entity.player.Input; +import net.minecraft.world.entity.player.Player; +import org.purpurmc.purpur.event.entity.RidableSpacebarEvent; + +public class MoveControllerWASD extends MoveControl { + protected final Mob entity; + private final double speedModifier; + + public MoveControllerWASD(Mob entity) { + this(entity, 1.0D); + } + + public MoveControllerWASD(Mob entity, double speedModifier) { + super(entity); + this.entity = entity; + this.speedModifier = speedModifier; + } + + @Override + public boolean hasWanted() { + return entity.getRider() != null ? strafeForwards != 0 || strafeRight != 0 : super.hasWanted(); + } + + @Override + public void tick() { + if (entity.getRider() != null && entity.isControllable()) { + purpurTick(entity.getRider()); + } else { + vanillaTick(); + } + } + + public void vanillaTick() { + super.tick(); + } + + public void purpurTick(Player rider) { + Input lastClientInput = ((ServerPlayer) rider).getLastClientInput(); + float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F) * 0.5F; + float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F) * 0.25F; + + if (forward <= 0.0F) { + forward *= 0.5F; + } + + float yawOffset = 0; + if (strafe != 0) { + if (forward == 0) { + yawOffset += strafe > 0 ? -90 : 90; + forward = Math.abs(strafe * 2); + } else { + yawOffset += strafe > 0 ? -30 : 30; + strafe /= 2; + if (forward < 0) { + yawOffset += strafe > 0 ? -110 : 110; + forward *= -1; + } + } + } else if (forward < 0) { + yawOffset -= 180; + forward *= -1; + } + + ((LookControllerWASD) entity.getLookControl()).setOffsets(yawOffset, 0); + + if (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..f25abee6dbf99c8d08f8e09db02b41df86115faa --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java @@ -0,0 +1,107 @@ +package org.purpurmc.purpur.entity; + +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.animal.Dolphin; +import net.minecraft.world.entity.projectile.LlamaSpit; +import net.minecraft.world.entity.projectile.ProjectileUtil; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import org.bukkit.event.entity.EntityRemoveEvent; + +public class DolphinSpit extends LlamaSpit { + public LivingEntity dolphin; + public int ticksLived; + + public DolphinSpit(EntityType type, Level world) { + super(type, world); + } + + public DolphinSpit(Level world, Dolphin dolphin) { + this(EntityType.LLAMA_SPIT, world); + setOwner(dolphin.getRider() != null ? dolphin.getRider() : dolphin); + this.dolphin = dolphin; + this.setPos( + dolphin.getX() - (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(dolphin.yBodyRot * 0.017453292F), + dolphin.getEyeY() - 0.10000000149011612D, + dolphin.getZ() + (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(dolphin.yBodyRot * 0.017453292F)); + } + + // Purpur start + @Override + public boolean canSaveToDisk() { + return false; + } + // Purpur end + + public void tick() { + super_tick(); + + Vec3 mot = this.getDeltaMovement(); + HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); + + this.preHitTargetOrDeflectSelf(hitResult); + + double x = this.getX() + mot.x; + double y = this.getY() + mot.y; + double z = this.getZ() + mot.z; + + this.updateRotation(); + + Vec3 motDouble = mot.scale(2.0); + for (int i = 0; i < 5; i++) { + ((ServerLevel) level()).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(EntityRemoveEvent.Cause.DISCARD); + } else { + this.setDeltaMovement(mot.scale(0.99D)); + if (!this.isNoGravity()) { + this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); + } + + this.setPos(x, y, z); + } + } + + @Override + public void shoot(double x, double y, double z, float speed, float inaccuracy) { + setDeltaMovement(new Vec3(x, y, z).normalize().add( + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) + .scale(speed)); + } + + @Override + protected void onHitEntity(EntityHitResult entityHitResult) { + Entity shooter = this.getOwner(); + if (shooter instanceof LivingEntity) { + entityHitResult.getEntity().hurt(entityHitResult.getEntity().damageSources().mobProjectile(this, (LivingEntity) shooter), level().purpurConfig.dolphinSpitDamage); + } + } + + @Override + protected void onHitBlock(BlockHitResult blockHitResult) { + if (this.hitCancelled) { + return; + } + BlockState state = this.level().getBlockState(blockHitResult.getBlockPos()); + state.onProjectileHit(this.level(), state, blockHitResult, this); + this.discard(EntityRemoveEvent.Cause.DISCARD); + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java new file mode 100644 index 0000000000000000000000000000000000000000..58957b0bd3cd2c37fd4a6766a02e2506d9f51010 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java @@ -0,0 +1,129 @@ +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.getHitResultOnMoveVector(this, this::canHitEntity); + + this.preHitTargetOrDeflectSelf(hitResult); + + double x = this.getX() + mot.x; + double y = this.getY() + mot.y; + double z = this.getZ() + mot.z; + + this.updateRotation(); + + Vec3 motDouble = mot.scale(2.0); + for (int i = 0; i < 5; i++) { + ((ServerLevel) level()).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(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + } else if (this.level().getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + } else if (this.isInWaterOrBubble()) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + } else { + this.setDeltaMovement(mot.scale(0.99D)); + if (!this.isNoGravity()) { + this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); + } + + this.setPos(x, y, z); + } + } + + @Override + public void shoot(double x, double y, double z, float speed, float inaccuracy) { + setDeltaMovement(new Vec3(x, y, z).normalize().add( + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) + .scale(speed)); + } + + @Override + protected void onHitEntity(EntityHitResult entityHitResult) { + Level world = this.level(); + + if (world instanceof ServerLevel worldserver) { + Entity shooter = this.getOwner(); + if (shooter instanceof LivingEntity) { + Entity target = entityHitResult.getEntity(); + if (canGrief || (target instanceof LivingEntity && !(target instanceof ArmorStand))) { + boolean hurt = target.hurtServer(worldserver, target.damageSources().mobProjectile(this, (LivingEntity) shooter), worldserver.purpurConfig.phantomFlameDamage); + if (hurt && worldserver.purpurConfig.phantomFlameFireTime > 0) { + target.igniteForSeconds(worldserver.purpurConfig.phantomFlameFireTime); + } + } + } + } + } + + @Override + protected void onHitBlock(BlockHitResult blockHitResult) { + Level world = this.level(); + + if (world instanceof ServerLevel worldserver) { + if (this.hitCancelled) { + return; + } + if (this.canGrief) { + BlockState state = worldserver.getBlockState(blockHitResult.getBlockPos()); + state.onProjectileHit(worldserver, state, blockHitResult, this); + } + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java b/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java new file mode 100644 index 0000000000000000000000000000000000000000..7608bf0981fa0d37031e51e57e4086cb5ec4c88b --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java @@ -0,0 +1,106 @@ +package org.purpurmc.purpur.entity; + +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.Component; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.level.block.entity.BeehiveBlockEntity; +import org.bukkit.block.EntityBlockStorage; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry; +import org.bukkit.entity.Bee; +import org.bukkit.entity.EntityType; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Locale; + +public class PurpurStoredBee implements StoredEntity { + private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); + + private final EntityBlockStorage blockStorage; + private final BeehiveBlockEntity.BeeData handle; + private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(PurpurStoredBee.DATA_TYPE_REGISTRY); + + private Component customName; + + public PurpurStoredBee(BeehiveBlockEntity.BeeData data, EntityBlockStorage blockStorage) { + this.handle = data; + this.blockStorage = blockStorage; + + CompoundTag customData = handle.occupant.entityData().copyTag(); + this.customName = customData.contains("CustomName") + ? PaperAdventure.asAdventure(net.minecraft.network.chat.Component.Serializer.fromJson(customData.getString("CustomName"), MinecraftServer.getDefaultRegistryAccess())) + : null; + + if(customData.contains("BukkitValues", Tag.TAG_COMPOUND)) { + this.persistentDataContainer.putAll(customData.getCompound("BukkitValues")); + } + } + + public BeehiveBlockEntity.BeeData getHandle() { + return handle; + } + + @Override + public @Nullable Component customName() { + return customName; + } + + @Override + public void customName(@Nullable Component customName) { + this.customName = customName; + } + + @Override + public @Nullable String getCustomName() { + return PaperAdventure.asPlain(customName, Locale.US); + } + + @Override + public void setCustomName(@Nullable String name) { + customName(name != null ? Component.text(name) : null); + } + + @Override + public @NotNull PersistentDataContainer getPersistentDataContainer() { + return persistentDataContainer; + } + + @Override + public boolean hasBeenReleased() { + return !blockStorage.getEntities().contains(this); + } + + @Override + public @Nullable Bee release() { + return blockStorage.releaseEntity(this); + } + + @Override + public @Nullable EntityBlockStorage getBlockStorage() { + if(hasBeenReleased()) { + return null; + } + + return blockStorage; + } + + @Override + public @NotNull EntityType getType() { + return EntityType.BEE; + } + + @Override + public void update() { + handle.occupant.entityData().copyTag().put("BukkitValues", this.persistentDataContainer.toTagCompound()); + if(customName == null) { + handle.occupant.entityData().copyTag().remove("CustomName"); + } else { + handle.occupant.entityData().copyTag().putString("CustomName", net.minecraft.network.chat.Component.Serializer.toJson(PaperAdventure.asVanilla(customName), MinecraftServer.getDefaultRegistryAccess())); + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java new file mode 100644 index 0000000000000000000000000000000000000000..8babdaddd8b33278aea0369dbbeeb445abe45016 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java @@ -0,0 +1,20 @@ +package org.purpurmc.purpur.entity.ai; + +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.goal.Goal; + +import java.util.EnumSet; + +public class HasRider extends Goal { + public final Mob entity; + + public HasRider(Mob entity) { + this.entity = entity; + setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.TARGET, Flag.UNKNOWN_BEHAVIOR)); + } + + @Override + public boolean canUse() { + return entity.getRider() != null && entity.isControllable(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java new file mode 100644 index 0000000000000000000000000000000000000000..432f4f3d82af2f19820890b68d33189a9f2c69f9 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java @@ -0,0 +1,17 @@ +package org.purpurmc.purpur.entity.ai; + +import net.minecraft.world.entity.animal.horse.AbstractHorse; + +public class HorseHasRider extends HasRider { + public final AbstractHorse horse; + + public HorseHasRider(AbstractHorse entity) { + super(entity); + this.horse = entity; + } + + @Override + public boolean canUse() { + return super.canUse() && horse.isSaddled(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java new file mode 100644 index 0000000000000000000000000000000000000000..18a95e043cbffa65eeaaf65ff7695e5dc939820c --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java @@ -0,0 +1,17 @@ +package org.purpurmc.purpur.entity.ai; + +import net.minecraft.world.entity.animal.horse.Llama; + +public class LlamaHasRider extends HasRider { + public final Llama llama; + + public LlamaHasRider(Llama entity) { + super(entity); + this.llama = entity; + } + + @Override + public boolean canUse() { + return super.canUse() && llama.isSaddled() && llama.isControllable(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java b/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java new file mode 100644 index 0000000000000000000000000000000000000000..9660716f4162a4441c6e1b0baddef8f5086566c5 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java @@ -0,0 +1,91 @@ +package org.purpurmc.purpur.entity.ai; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.ai.goal.Goal; +import net.minecraft.world.entity.animal.IronGolem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Blocks; + +import java.util.EnumSet; +import java.util.UUID; + +public class ReceiveFlower extends Goal { + private final IronGolem irongolem; + private ServerPlayer target; + private int cooldown; + + public ReceiveFlower(IronGolem entity) { + this.irongolem = entity; + setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK)); + } + + @Override + public boolean canUse() { + if (this.irongolem.getOfferFlowerTick() > 0) { + return false; + } + if (!this.irongolem.isAngry()) { + return false; + } + UUID uuid = this.irongolem.getPersistentAngerTarget(); + if (uuid == null) { + return false; + } + Entity target = ((ServerLevel) this.irongolem.level()).getEntity(uuid); + if (!(target instanceof ServerPlayer player)) { + return false; + } + InteractionHand hand = getPoppyHand(player); + if (hand == null) { + return false; + } + removeFlower(player, hand); + this.target = player; + return true; + } + + @Override + public boolean canContinueToUse() { + return this.cooldown > 0; + } + + @Override + public void start() { + this.cooldown = 100; + this.irongolem.stopBeingAngry(); + this.irongolem.offerFlower(true); + } + + @Override + public void stop() { + this.irongolem.offerFlower(false); + this.target = null; + } + + @Override + public void tick() { + this.irongolem.getLookControl().setLookAt(this.target, 30.0F, 30.0F); + --this.cooldown; + } + + private InteractionHand getPoppyHand(ServerPlayer player) { + if (isPoppy(player.getMainHandItem())) { + return InteractionHand.MAIN_HAND; + } + if (isPoppy(player.getOffhandItem())) { + return InteractionHand.OFF_HAND; + } + return null; + } + + private void removeFlower(ServerPlayer player, InteractionHand hand) { + player.setItemInHand(hand, ItemStack.EMPTY); + } + + private boolean isPoppy(ItemStack item) { + return item.getItem() == Blocks.POPPY.asItem(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/gui/GUIColor.java b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java new file mode 100644 index 0000000000000000000000000000000000000000..550222758bf0e7deff26a6e813a860b7be365e87 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java @@ -0,0 +1,58 @@ +package org.purpurmc.purpur.gui; + +import net.md_5.bungee.api.ChatColor; + +import java.awt.Color; +import java.util.HashMap; +import java.util.Map; + +public enum GUIColor { + BLACK(ChatColor.BLACK, new Color(0x000000)), + DARK_BLUE(ChatColor.DARK_BLUE, new Color(0x0000AA)), + DARK_GREEN(ChatColor.DARK_GREEN, new Color(0x00AA00)), + DARK_AQUA(ChatColor.DARK_AQUA, new Color(0x009999)), + DARK_RED(ChatColor.DARK_RED, new Color(0xAA0000)), + DARK_PURPLE(ChatColor.DARK_PURPLE, new Color(0xAA00AA)), + GOLD(ChatColor.GOLD, new Color(0xBB8800)), + GRAY(ChatColor.GRAY, new Color(0x888888)), + DARK_GRAY(ChatColor.DARK_GRAY, new Color(0x444444)), + BLUE(ChatColor.BLUE, new Color(0x5555FF)), + GREEN(ChatColor.GREEN, new Color(0x55FF55)), + AQUA(ChatColor.AQUA, new Color(0x55DDDD)), + RED(ChatColor.RED, new Color(0xFF5555)), + LIGHT_PURPLE(ChatColor.LIGHT_PURPLE, new Color(0xFF55FF)), + YELLOW(ChatColor.YELLOW, new Color(0xFFBB00)), + WHITE(ChatColor.WHITE, new Color(0xBBBBBB)); + + private final ChatColor chat; + private final Color color; + + private static final Map BY_CHAT = new HashMap<>(); + + GUIColor(ChatColor chat, Color color) { + this.chat = chat; + this.color = color; + } + + public Color getColor() { + return color; + } + + public ChatColor getChatColor() { + return chat; + } + + public String getCode() { + return chat.toString(); + } + + public static GUIColor getColor(ChatColor chat) { + return BY_CHAT.get(chat); + } + + static { + for (GUIColor color : values()) { + BY_CHAT.put(color.chat, color); + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java new file mode 100644 index 0000000000000000000000000000000000000000..d75fb5e77eff27d86135ed7d605dbc250b660f7d --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java @@ -0,0 +1,83 @@ +package org.purpurmc.purpur.gui; + +import com.google.common.collect.Sets; +import javax.swing.UIManager; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; + +import javax.swing.JTextPane; +import javax.swing.Timer; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyleContext; +import java.util.Set; + +public class JColorTextPane extends JTextPane { + private static final GUIColor DEFAULT_COLOR; + static { + DEFAULT_COLOR = UIManager.getSystemLookAndFeelClassName().equals("com.sun.java.swing.plaf.gtk.GTKLookAndFeel") + ? GUIColor.WHITE : GUIColor.BLACK; + } + + + public void append(String msg) { + // TODO: update to use adventure instead + BaseComponent[] components = TextComponent.fromLegacyText(DEFAULT_COLOR.getCode() + msg, DEFAULT_COLOR.getChatColor()); + for (BaseComponent component : components) { + String text = component.toPlainText(); + if (text == null || text.isEmpty()) { + continue; + } + + GUIColor guiColor = GUIColor.getColor(component.getColor()); + + StyleContext context = StyleContext.getDefaultStyleContext(); + AttributeSet attr = context.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, guiColor.getColor()); + attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Bold, component.isBold() || guiColor != DEFAULT_COLOR); + attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Italic, component.isItalic()); + attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Underline, component.isUnderlined()); + attr = context.addAttribute(attr, StyleConstants.CharacterConstants.StrikeThrough, component.isStrikethrough()); + //attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Blink, component.isObfuscated()); // no such thing as Blink, sadly + + try { + int pos = getDocument().getLength(); + getDocument().insertString(pos, text, attr); + + if (component.isObfuscated()) { + // dirty hack to blink some text + Blink blink = new Blink(pos, text.length(), attr, context.addAttribute(attr, StyleConstants.Foreground, getBackground())); + BLINKS.add(blink); + } + } catch (BadLocationException ignore) { + } + } + } + + private static final Set BLINKS = Sets.newHashSet(); + private static boolean SYNC_BLINK; + + static { + new Timer(500, e -> { + SYNC_BLINK = !SYNC_BLINK; + BLINKS.forEach(Blink::blink); + }).start(); + } + + public class Blink { + private final int start, length; + private final AttributeSet attr1, attr2; + + private Blink(int start, int length, AttributeSet attr1, AttributeSet attr2) { + this.start = start; + this.length = length; + this.attr1 = attr1; + this.attr2 = attr2; + } + + private void blink() { + getStyledDocument().setCharacterAttributes(start, length, SYNC_BLINK ? attr1 : attr2, true); + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java new file mode 100644 index 0000000000000000000000000000000000000000..b257f35caa13b660854cf17f41fd8fba1d56c458 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java @@ -0,0 +1,26 @@ +package org.purpurmc.purpur.item; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import org.bukkit.event.entity.EntityPotionEffectEvent; + +public class GlowBerryItem extends BlockItem { + public GlowBerryItem(Block block, Properties settings) { + super(block, settings); + } + + @Override + public ItemStack finishUsingItem(ItemStack stack, Level world, LivingEntity user) { + ItemStack result = super.finishUsingItem(stack, world, user); + if (world.purpurConfig.glowBerriesEatGlowDuration > 0 && user instanceof ServerPlayer player) { + player.addEffect(new MobEffectInstance(MobEffects.GLOWING, world.purpurConfig.glowBerriesEatGlowDuration), EntityPotionEffectEvent.Cause.FOOD); + } + return result; + } +} diff --git a/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java new file mode 100644 index 0000000000000000000000000000000000000000..ed50cb2115401c9039df4136caf5a087a5f5991c --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java @@ -0,0 +1,40 @@ +package org.purpurmc.purpur.item; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.CustomData; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.SpawnerBlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class SpawnerItem extends BlockItem { + + public SpawnerItem(Block block, Properties settings) { + super(block, settings); + } + + @Override + protected boolean updateCustomBlockEntityTag(BlockPos pos, Level level, Player player, ItemStack stack, BlockState state) { + boolean handled = super.updateCustomBlockEntityTag(pos, level, player, stack, state); + if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.place.spawners")) { + BlockEntity blockEntity = level.getBlockEntity(pos); + if (blockEntity instanceof SpawnerBlockEntity spawner) { + CompoundTag customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag(); + if (customData.contains("Purpur.mob_type")) { + EntityType.byString(customData.getString("Purpur.mob_type")).ifPresent(type -> spawner.getSpawner().setEntityId(type, level, level.random, pos)); + } else if (customData.contains("Purpur.SpawnData")) { + net.minecraft.world.level.SpawnData.CODEC.parse(net.minecraft.nbt.NbtOps.INSTANCE, customData.getCompound("Purpur.SpawnData")).result() + .ifPresent(spawnData -> spawner.getSpawner().nextSpawnData = spawnData); + } + } + } + return handled; + } +} diff --git a/src/main/java/org/purpurmc/purpur/network/ClientboundBeehivePayload.java b/src/main/java/org/purpurmc/purpur/network/ClientboundBeehivePayload.java new file mode 100644 index 0000000000000000000000000000000000000000..793a3ea45fe04e84725926f17615c26e008b0ce4 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/network/ClientboundBeehivePayload.java @@ -0,0 +1,27 @@ +package org.purpurmc.purpur.network; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; + +public record ClientboundBeehivePayload(BlockPos pos, int numOfBees) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ClientboundBeehivePayload::write, ClientboundBeehivePayload::new); + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath("purpur", "beehive_s2c")); + + public ClientboundBeehivePayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readBlockPos(), friendlyByteBuf.readInt()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeBlockPos(this.pos); + friendlyByteBuf.writeInt(this.numOfBees); + } + + @Override + public @NotNull Type type() { + return TYPE; + } +} diff --git a/src/main/java/org/purpurmc/purpur/network/ServerboundBeehivePayload.java b/src/main/java/org/purpurmc/purpur/network/ServerboundBeehivePayload.java new file mode 100644 index 0000000000000000000000000000000000000000..fa72769e06061609e1e658a0250e99c8cb026c0e --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/network/ServerboundBeehivePayload.java @@ -0,0 +1,26 @@ +package org.purpurmc.purpur.network; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; + +public record ServerboundBeehivePayload(BlockPos pos) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec(ServerboundBeehivePayload::write, ServerboundBeehivePayload::new); + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath("purpur", "beehive_c2s")); + + public ServerboundBeehivePayload(FriendlyByteBuf friendlyByteBuf) { + this(friendlyByteBuf.readBlockPos()); + } + + private void write(FriendlyByteBuf friendlyByteBuf) { + friendlyByteBuf.writeBlockPos(this.pos); + } + + @Override + public @NotNull Type type() { + return TYPE; + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java b/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java new file mode 100644 index 0000000000000000000000000000000000000000..664f9d5e1ce5e2787bf699bd11758b9e3aa8ed3a --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java @@ -0,0 +1,67 @@ +package org.purpurmc.purpur.task; + +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.block.entity.BeehiveBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.plugin.PluginBase; +import org.bukkit.plugin.messaging.PluginMessageListener; +import org.jetbrains.annotations.NotNull; +import org.purpurmc.purpur.network.ClientboundBeehivePayload; +import org.purpurmc.purpur.network.ServerboundBeehivePayload; +import org.purpurmc.purpur.util.MinecraftInternalPlugin; + +public class BeehiveTask implements PluginMessageListener { + + private static BeehiveTask instance; + + public static BeehiveTask instance() { + if (instance == null) { + instance = new BeehiveTask(); + } + return instance; + } + + private final PluginBase plugin = new MinecraftInternalPlugin(); + + private BeehiveTask() { + } + + public void register() { + Bukkit.getMessenger().registerOutgoingPluginChannel(this.plugin, ClientboundBeehivePayload.TYPE.id().toString()); + Bukkit.getMessenger().registerIncomingPluginChannel(this.plugin, ServerboundBeehivePayload.TYPE.id().toString(), this); + } + + public void unregister() { + Bukkit.getMessenger().unregisterOutgoingPluginChannel(this.plugin, ClientboundBeehivePayload.TYPE.id().toString()); + Bukkit.getMessenger().unregisterIncomingPluginChannel(this.plugin, ServerboundBeehivePayload.TYPE.id().toString()); + } + + @Override + public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte[] bytes) { + FriendlyByteBuf byteBuf = new FriendlyByteBuf(Unpooled.copiedBuffer(bytes)); + ServerboundBeehivePayload payload = ServerboundBeehivePayload.STREAM_CODEC.decode(byteBuf); + + ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + + // targeted block info max range specified in client at net.minecraft.client.gui.hud.DebugHud#render + if (!payload.pos().getCenter().closerThan(serverPlayer.position(), 20)) return; // Targeted Block info max range is 20 + if (serverPlayer.level().getChunkIfLoaded(payload.pos()) == null) return; + + BlockEntity blockEntity = serverPlayer.level().getBlockEntity(payload.pos()); + if (!(blockEntity instanceof BeehiveBlockEntity beehive)) { + return; + } + + ClientboundBeehivePayload customPacketPayload = new ClientboundBeehivePayload(payload.pos(), beehive.getOccupantCount()); + FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); + ClientboundBeehivePayload.STREAM_CODEC.encode(friendlyByteBuf, customPacketPayload); + byte[] byteArray = new byte[friendlyByteBuf.readableBytes()]; + friendlyByteBuf.readBytes(byteArray); + player.sendPluginMessage(this.plugin, customPacketPayload.type().id().toString(), byteArray); + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/BossBarTask.java b/src/main/java/org/purpurmc/purpur/task/BossBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..3c3d4cd52db93b97a40321030a70ebc282c9636b --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/BossBarTask.java @@ -0,0 +1,121 @@ +package org.purpurmc.purpur.task; + +import net.kyori.adventure.bossbar.BossBar; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; +import org.purpurmc.purpur.util.MinecraftInternalPlugin; + +public abstract class BossBarTask extends BukkitRunnable { + private final Map bossbars = new HashMap<>(); + private boolean started; + + abstract BossBar createBossBar(); + + abstract void updateBossBar(BossBar bossbar, Player player); + + @Override + public void run() { + Iterator> iter = bossbars.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + Player player = Bukkit.getPlayer(entry.getKey()); + if (player == null) { + iter.remove(); + continue; + } + updateBossBar(entry.getValue(), player); + } + } + + @Override + public void cancel() { + super.cancel(); + new HashSet<>(this.bossbars.keySet()).forEach(uuid -> { + Player player = Bukkit.getPlayer(uuid); + if (player != null) { + removePlayer(player); + } + }); + this.bossbars.clear(); + } + + public boolean removePlayer(Player player) { + BossBar bossbar = this.bossbars.remove(player.getUniqueId()); + if (bossbar != null) { + player.hideBossBar(bossbar); + return true; + } + return false; + } + + public void addPlayer(Player player) { + removePlayer(player); + BossBar bossbar = createBossBar(); + this.bossbars.put(player.getUniqueId(), bossbar); + this.updateBossBar(bossbar, player); + player.showBossBar(bossbar); + } + + public boolean hasPlayer(UUID uuid) { + return this.bossbars.containsKey(uuid); + } + + public boolean togglePlayer(Player player) { + if (removePlayer(player)) { + return false; + } + addPlayer(player); + return true; + } + + public void start() { + stop(); + this.runTaskTimerAsynchronously(new MinecraftInternalPlugin(), 1, 1); + started = true; + } + + public void stop() { + if (started) { + cancel(); + } + } + + public static void startAll() { + RamBarTask.instance().start(); + TPSBarTask.instance().start(); + CompassTask.instance().start(); + } + + public static void stopAll() { + RamBarTask.instance().stop(); + TPSBarTask.instance().stop(); + CompassTask.instance().stop(); + } + + public static void addToAll(ServerPlayer player) { + Player bukkit = player.getBukkitEntity(); + if (player.ramBar()) { + RamBarTask.instance().addPlayer(bukkit); + } + if (player.tpsBar()) { + TPSBarTask.instance().addPlayer(bukkit); + } + if (player.compassBar()) { + CompassTask.instance().addPlayer(bukkit); + } + } + + public static void removeFromAll(Player player) { + RamBarTask.instance().removePlayer(player); + TPSBarTask.instance().removePlayer(player); + CompassTask.instance().removePlayer(player); + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/CompassTask.java b/src/main/java/org/purpurmc/purpur/task/CompassTask.java new file mode 100644 index 0000000000000000000000000000000000000000..bece7eefc8ba8822b433835526251d2fb916c025 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/CompassTask.java @@ -0,0 +1,68 @@ +package org.purpurmc.purpur.task; + +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.item.Items; +import org.bukkit.entity.Player; +import org.purpurmc.purpur.PurpurConfig; + +public class CompassTask extends BossBarTask { + private static CompassTask instance; + + private int tick = 0; + + public static CompassTask instance() { + if (instance == null) { + instance = new CompassTask(); + } + return instance; + } + + @Override + public void run() { + if (++tick < PurpurConfig.commandCompassBarTickInterval) { + return; + } + tick = 0; + + MinecraftServer.getServer().getAllLevels().forEach((level) -> { + if (level.purpurConfig.compassItemShowsBossBar) { + level.players().forEach(player -> { + if (!player.compassBar()) { + if (player.getMainHandItem().getItem() != Items.COMPASS && player.getOffhandItem().getItem() != Items.COMPASS) { + removePlayer(player.getBukkitEntity()); + } else if (!hasPlayer(player.getUUID())) { + addPlayer(player.getBukkitEntity()); + } + } + }); + } + }); + + super.run(); + } + + @Override + BossBar createBossBar() { + return BossBar.bossBar(Component.text(""), PurpurConfig.commandCompassBarProgressPercent, PurpurConfig.commandCompassBarProgressColor, PurpurConfig.commandCompassBarProgressOverlay); + } + + @Override + void updateBossBar(BossBar bossbar, Player player) { + float yaw = player.getLocation().getYaw(); + int length = PurpurConfig.commandCompassBarTitle.length(); + int pos = (int) ((normalize(yaw) * (length / 720F)) + (length / 2F)); + bossbar.name(Component.text(PurpurConfig.commandCompassBarTitle.substring(pos - 25, pos + 25))); + } + + private float normalize(float yaw) { + while (yaw < -180.0F) { + yaw += 360.0F; + } + while (yaw > 180.0F) { + yaw -= 360.0F; + } + return yaw; + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/RamBarTask.java b/src/main/java/org/purpurmc/purpur/task/RamBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..8e98c0ae73e2c40002a72b5d0d246ffa0c3ab38f --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/RamBarTask.java @@ -0,0 +1,117 @@ +package org.purpurmc.purpur.task; + +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import org.bukkit.entity.Player; +import org.purpurmc.purpur.PurpurConfig; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryUsage; + +public class RamBarTask extends BossBarTask { + private static RamBarTask instance; + private long allocated = 0L; + private long used = 0L; + private long xmx = 0L; + private long xms = 0L; + private float percent = 0F; + private int tick = 0; + + public static RamBarTask instance() { + if (instance == null) { + instance = new RamBarTask(); + } + return instance; + } + + @Override + BossBar createBossBar() { + return BossBar.bossBar(Component.text(""), 0.0F, instance().getBossBarColor(), PurpurConfig.commandRamBarProgressOverlay); + } + + @Override + void updateBossBar(BossBar bossbar, Player player) { + bossbar.progress(getBossBarProgress()); + bossbar.color(getBossBarColor()); + bossbar.name(MiniMessage.miniMessage().deserialize(PurpurConfig.commandRamBarTitle, + Placeholder.component("allocated", format(this.allocated)), + Placeholder.component("used", format(this.used)), + Placeholder.component("xmx", format(this.xmx)), + Placeholder.component("xms", format(this.xms)), + Placeholder.unparsed("percent", ((int) (this.percent * 100)) + "%") + )); + } + + @Override + public void run() { + if (++this.tick < PurpurConfig.commandRamBarTickInterval) { + return; + } + this.tick = 0; + + MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); + + this.allocated = heap.getCommitted(); + this.used = heap.getUsed(); + this.xmx = heap.getMax(); + this.xms = heap.getInit(); + this.percent = Math.max(Math.min((float) this.used / this.xmx, 1.0F), 0.0F); + + super.run(); + } + + private float getBossBarProgress() { + return this.percent; + } + + private BossBar.Color getBossBarColor() { + if (this.percent < 0.5F) { + return PurpurConfig.commandRamBarProgressColorGood; + } else if (this.percent < 0.75F) { + return PurpurConfig.commandRamBarProgressColorMedium; + } else { + return PurpurConfig.commandRamBarProgressColorLow; + } + } + + public Component format(long v) { + String color; + if (this.percent < 0.60F) { + color = PurpurConfig.commandRamBarTextColorGood; + } else if (this.percent < 0.85F) { + color = PurpurConfig.commandRamBarTextColorMedium; + } else { + color = PurpurConfig.commandRamBarTextColorLow; + } + String value; + if (v < 1024) { + value = v + "B"; + } else { + int z = (63 - Long.numberOfLeadingZeros(v)) / 10; + value = String.format("%.1f%s", (double) v / (1L << (z * 10)), "BKMGTPE".charAt(z)); + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.unparsed("text", value)); + } + + public long getAllocated() { + return this.allocated; + } + + public long getUsed() { + return this.used; + } + + public long getXmx() { + return this.xmx; + } + + public long getXms() { + return this.xms; + } + + public float getPercent() { + return this.percent; + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java b/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..8769993e7ca59da309087051a3cd38fc562c15d1 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java @@ -0,0 +1,142 @@ +package org.purpurmc.purpur.task; + +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import org.purpurmc.purpur.PurpurConfig; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class TPSBarTask extends BossBarTask { + private static TPSBarTask instance; + private double tps = 20.0D; + private double mspt = 0.0D; + private int tick = 0; + + public static TPSBarTask instance() { + if (instance == null) { + instance = new TPSBarTask(); + } + return instance; + } + + @Override + BossBar createBossBar() { + return BossBar.bossBar(Component.text(""), 0.0F, instance().getBossBarColor(), PurpurConfig.commandTPSBarProgressOverlay); + } + + @Override + void updateBossBar(BossBar bossbar, Player player) { + bossbar.progress(getBossBarProgress()); + bossbar.color(getBossBarColor()); + bossbar.name(MiniMessage.miniMessage().deserialize(PurpurConfig.commandTPSBarTitle, + Placeholder.component("tps", getTPSColor()), + Placeholder.component("mspt", getMSPTColor()), + Placeholder.component("ping", getPingColor(player.getPing())) + )); + } + + @Override + public void run() { + if (++tick < PurpurConfig.commandTPSBarTickInterval) { + return; + } + tick = 0; + + this.tps = Math.max(Math.min(Bukkit.getTPS()[0], 20.0D), 0.0D); + this.mspt = Bukkit.getAverageTickTime(); + + super.run(); + } + + private float getBossBarProgress() { + if (PurpurConfig.commandTPSBarProgressFillMode == FillMode.MSPT) { + return Math.max(Math.min((float) mspt / 50.0F, 1.0F), 0.0F); + } else { + return Math.max(Math.min((float) tps / 20.0F, 1.0F), 0.0F); + } + } + + private BossBar.Color getBossBarColor() { + if (isGood(PurpurConfig.commandTPSBarProgressFillMode)) { + return PurpurConfig.commandTPSBarProgressColorGood; + } else if (isMedium(PurpurConfig.commandTPSBarProgressFillMode)) { + return PurpurConfig.commandTPSBarProgressColorMedium; + } else { + return PurpurConfig.commandTPSBarProgressColorLow; + } + } + + private boolean isGood(FillMode mode) { + return isGood(mode, 0); + } + + private boolean isGood(FillMode mode, int ping) { + if (mode == FillMode.MSPT) { + return mspt < 40; + } else if (mode == FillMode.TPS) { + return tps >= 19; + } else if (mode == FillMode.PING) { + return ping < 100; + } else { + return false; + } + } + + private boolean isMedium(FillMode mode) { + return isMedium(mode, 0); + } + + private boolean isMedium(FillMode mode, int ping) { + if (mode == FillMode.MSPT) { + return mspt < 50; + } else if (mode == FillMode.TPS) { + return tps >= 15; + } else if (mode == FillMode.PING) { + return ping < 200; + } else { + return false; + } + } + + private Component getTPSColor() { + String color; + if (isGood(FillMode.TPS)) { + color = PurpurConfig.commandTPSBarTextColorGood; + } else if (isMedium(FillMode.TPS)) { + color = PurpurConfig.commandTPSBarTextColorMedium; + } else { + color = PurpurConfig.commandTPSBarTextColorLow; + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%.2f", tps))); + } + + private Component getMSPTColor() { + String color; + if (isGood(FillMode.MSPT)) { + color = PurpurConfig.commandTPSBarTextColorGood; + } else if (isMedium(FillMode.MSPT)) { + color = PurpurConfig.commandTPSBarTextColorMedium; + } else { + color = PurpurConfig.commandTPSBarTextColorLow; + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%.2f", mspt))); + } + + private Component getPingColor(int ping) { + String color; + if (isGood(FillMode.PING, ping)) { + color = PurpurConfig.commandTPSBarTextColorGood; + } else if (isMedium(FillMode.PING, ping)) { + color = PurpurConfig.commandTPSBarTextColorMedium; + } else { + color = PurpurConfig.commandTPSBarTextColorLow; + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%s", ping))); + } + + public enum FillMode { + TPS, MSPT, PING + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Actionable.java b/src/main/java/org/purpurmc/purpur/tool/Actionable.java new file mode 100644 index 0000000000000000000000000000000000000000..e18c37f06730da9d3055d5215e813b1477c1e70e --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Actionable.java @@ -0,0 +1,24 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Map; + +public abstract class Actionable { + private final Block into; + private final Map drops; + + public Actionable(Block into, Map drops) { + this.into = into; + this.drops = drops; + } + + public Block into() { + return into; + } + + public Map drops() { + return drops; + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Flattenable.java b/src/main/java/org/purpurmc/purpur/tool/Flattenable.java new file mode 100644 index 0000000000000000000000000000000000000000..345d4ee4ff0b78bd1050959711a4f5d16a5e8aee --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Flattenable.java @@ -0,0 +1,12 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Map; + +public class Flattenable extends Actionable { + public Flattenable(Block into, Map drops) { + super(into, drops); + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Strippable.java b/src/main/java/org/purpurmc/purpur/tool/Strippable.java new file mode 100644 index 0000000000000000000000000000000000000000..bf5402214f41af9c09bd6c5c4f45d330516d742e --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Strippable.java @@ -0,0 +1,12 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Map; + +public class Strippable extends Actionable { + public Strippable(Block into, Map drops) { + super(into, drops); + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Tillable.java b/src/main/java/org/purpurmc/purpur/tool/Tillable.java new file mode 100644 index 0000000000000000000000000000000000000000..715f6dd44480347eebced43c11bc364e05727498 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Tillable.java @@ -0,0 +1,50 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.HoeItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.block.Block; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; + +public class Tillable extends Actionable { + private final Condition condition; + + public Tillable(Condition condition, Block into, Map drops) { + super(into, drops); + this.condition = condition; + } + + public Condition condition() { + return condition; + } + + public enum Condition { + AIR_ABOVE(HoeItem::onlyIfAirAbove), + ALWAYS((useOnContext) -> true); + + private final Predicate predicate; + + Condition(Predicate predicate) { + this.predicate = predicate; + } + + public Predicate predicate() { + return predicate; + } + + private static final Map BY_NAME = new HashMap<>(); + + static { + for (Condition condition : values()) { + BY_NAME.put(condition.name(), condition); + } + } + + public static Condition get(String name) { + return BY_NAME.get(name.toUpperCase(java.util.Locale.ROOT)); + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Waxable.java b/src/main/java/org/purpurmc/purpur/tool/Waxable.java new file mode 100644 index 0000000000000000000000000000000000000000..64adb13b29b6757dcf227a55588da70ecabe083f --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Waxable.java @@ -0,0 +1,12 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Map; + +public class Waxable extends Actionable { + public Waxable(Block into, Map drops) { + super(into, drops); + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Weatherable.java b/src/main/java/org/purpurmc/purpur/tool/Weatherable.java new file mode 100644 index 0000000000000000000000000000000000000000..b7586f494528f30eb0da82420d3bcf5b83a1a902 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Weatherable.java @@ -0,0 +1,12 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Map; + +public class Weatherable extends Actionable { + public Weatherable(Block into, Map drops) { + super(into, drops); + } +} diff --git a/src/main/java/org/purpurmc/purpur/util/MinecraftInternalPlugin.java b/src/main/java/org/purpurmc/purpur/util/MinecraftInternalPlugin.java new file mode 100644 index 0000000000000000000000000000000000000000..129acb8ad139decc6b1c023cb10bc32dc91d64d1 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/util/MinecraftInternalPlugin.java @@ -0,0 +1,152 @@ +package org.purpurmc.purpur.util; + +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.generator.BiomeProvider; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.plugin.PluginBase; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginLoader; +import org.bukkit.plugin.PluginLogger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.InputStream; +import java.util.List; + +public class MinecraftInternalPlugin extends PluginBase { + private boolean enabled = true; + + private final String pluginName; + private PluginDescriptionFile pdf; + + public MinecraftInternalPlugin() { + this.pluginName = "Minecraft"; + pdf = new PluginDescriptionFile(pluginName, "1.0", "nms"); + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public File getDataFolder() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public PluginDescriptionFile getDescription() { + return pdf; + } + // Paper start + @Override + public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() { + return pdf; + } + // Paper end + + @Override + public FileConfiguration getConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public InputStream getResource(String filename) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void saveConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void saveDefaultConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void saveResource(String resourcePath, boolean replace) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void reloadConfig() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public PluginLogger getLogger() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public PluginLoader getPluginLoader() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public Server getServer() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void onDisable() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void onLoad() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void onEnable() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean isNaggable() { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public void setNaggable(boolean canNag) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public @Nullable BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + throw new UnsupportedOperationException("Not supported."); + } + + // Paper start - lifecycle events + @Override + public @NotNull io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager getLifecycleManager() { + throw new UnsupportedOperationException("Not supported."); + } + // Paper end - lifecycle events +} diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java index 1d438ef44cbe4d1eedfba36d8fe5d2ad53464921..9b1a16747aa23b18e4cff986efaac6ce64b6ddb9 100644 --- a/src/main/java/org/spigotmc/ActivationRange.java +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -37,6 +37,10 @@ import net.minecraft.world.entity.projectile.ThrownTrident; import net.minecraft.world.entity.raid.Raider; import net.minecraft.world.level.Level; import net.minecraft.world.phys.AABB; +// Pufferfish start +import net.minecraft.world.phys.Vec3; +import java.util.List; +// Pufferfish end public class ActivationRange { @@ -199,6 +203,8 @@ public class ActivationRange continue; } + if (!player.level().purpurConfig.idleTimeoutTickNearbyEntities && player.isAfk()) continue; // Purpur + // Paper start int worldHeight = world.getHeight(); ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, worldHeight, maxRange ); @@ -221,6 +227,25 @@ public class ActivationRange } // Paper end - Configurable marker ticking ActivationRange.activateEntity(entity); + + // Pufferfish start + if (gg.pufferfish.pufferfish.PufferfishConfig.dearEnabled && entity.getType().dabEnabled) { + if (!entity.activatedPriorityReset) { + entity.activatedPriorityReset = true; + entity.activatedPriority = gg.pufferfish.pufferfish.PufferfishConfig.maximumActivationPrio; + } + Vec3 playerVec = player.position(); + Vec3 entityVec = entity.position(); + double diffX = playerVec.x - entityVec.x, diffY = playerVec.y - entityVec.y, diffZ = playerVec.z - entityVec.z; + int squaredDistance = (int) (diffX * diffX + diffY * diffY + diffZ * diffZ); + entity.activatedPriority = squaredDistance > gg.pufferfish.pufferfish.PufferfishConfig.startDistanceSquared ? + Math.max(1, Math.min(squaredDistance >> gg.pufferfish.pufferfish.PufferfishConfig.activationDistanceMod, entity.activatedPriority)) : + 1; + } else { + entity.activatedPriority = 1; + } + // Pufferfish end + } // Paper end } @@ -236,12 +261,12 @@ public class ActivationRange if ( MinecraftServer.currentTick > entity.activatedTick ) { if ( entity.defaultActivationState ) - { + { // Pufferfish - diff on change entity.activatedTick = MinecraftServer.currentTick; return; } if ( entity.activationType.boundingBox.intersects( entity.getBoundingBox() ) ) - { + { // Pufferfish - diff on change entity.activatedTick = MinecraftServer.currentTick; } } @@ -295,7 +320,7 @@ public class ActivationRange if ( entity instanceof LivingEntity ) { LivingEntity living = (LivingEntity) entity; - if ( living.onClimbable() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 || living.isFreezing()) // Paper + if ( living.onClimableCached() || living.jumping || living.hurtTime > 0 || living.activeEffects.size() > 0 || living.isFreezing() ) // Paper // Pufferfish - use cached { return 1; // Paper } @@ -376,6 +401,7 @@ public class ActivationRange */ public static boolean checkIfActive(Entity entity) { + if (entity.level().purpurConfig.squidImmuneToEAR && entity instanceof net.minecraft.world.entity.animal.Squid) return true; // Purpur // Never safe to skip fireworks or item gravity if (entity instanceof FireworkRocketEntity || (entity instanceof ItemEntity && (entity.tickCount + entity.getId()) % 4 == 0)) { // Paper - Needed for item gravity, see ItemEntity tick return true; diff --git a/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/src/main/java/org/spigotmc/TicksPerSecondCommand.java index 9eb2823cc8f83bad2626fc77578b0162d9ed5782..f144a08e88f8268b84eb188a36bf470457f59958 100644 --- a/src/main/java/org/spigotmc/TicksPerSecondCommand.java +++ b/src/main/java/org/spigotmc/TicksPerSecondCommand.java @@ -39,7 +39,7 @@ public class TicksPerSecondCommand extends Command } net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text(); - builder.append(net.kyori.adventure.text.Component.text("TPS from last 1m, 5m, 15m: ", net.kyori.adventure.text.format.NamedTextColor.GOLD)); + builder.append(net.kyori.adventure.text.Component.text("TPS from last 5s, 1m, 5m, 15m: ", net.kyori.adventure.text.format.NamedTextColor.GOLD)); // Purpur builder.append(net.kyori.adventure.text.Component.join(net.kyori.adventure.text.JoinConfiguration.commas(true), tpsAvg)); sender.sendMessage(builder.asComponent()); if (args.length > 0 && args[0].equals("mem") && sender.hasPermission("bukkit.command.tpsmemory")) { diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java index f7a4fee9bb25ff256dc2e5ea26bfbceca6a49167..f51cc3de3ca935ef90f7f0e9dd0506b856fc55f3 100644 --- a/src/main/java/org/spigotmc/WatchdogThread.java +++ b/src/main/java/org/spigotmc/WatchdogThread.java @@ -96,7 +96,7 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre private WatchdogThread(long timeoutTime, boolean restart) { - super( "Paper Watchdog Thread" ); + super( "Watchdog Thread" ); // Purpur - use a generic name this.timeoutTime = timeoutTime; this.restart = restart; earlyWarningEvery = Math.min(io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.earlyWarningEvery, timeoutTime); // Paper @@ -155,14 +155,14 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre if (isLongTimeout) { // Paper end log.log( Level.SEVERE, "------------------------------" ); - log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper + log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Purpur bug." ); // Paper // Purpur 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 ) { @@ -184,12 +184,12 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre // Paper end } else { - log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); + log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PURPUR - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); // Purpur 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 ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(MinecraftServer.getServer(), isLongTimeout); // Paper - rewrite chunk system this.dumpTickingInfo(); // Paper - log detailed tick information WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); @@ -205,7 +205,7 @@ public class WatchdogThread extends ca.spottedleaf.moonrise.common.util.TickThre WatchdogThread.dumpThread( thread, log ); } } else { - log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---"); + log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PURPUR - THIS IS NOT A BUG OR A CRASH ---"); // Purpur } log.log( Level.SEVERE, "------------------------------" ); diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index d2a75850af9c6ad2aca66a5f994f1b587d73eac4..a056aa167887abef9e6d531a9edd2cda433567d2 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -2,7 +2,16 @@ - + + + + + + + + + diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png index 8b924977b7886df9ab8790b1e4ff9b1c04a2af45..518591dd83289e041a16e2c2e7d7e7640d4b2e1b 100644 GIT binary patch literal 9260 zcmWksbyU<{5M5yDS~|W3b}1=IK|*5bk`9p$r5ov78WvbWX=y?AQ0}Yp0<$+ z2n70|0E9aM1Hym>3UG)5o+kstyg;EopqB~EvfpJ`0IyyF`Z>TV(_M%s;1&uXoq-)H z;F}=ODg(TT1R4>5S0IpY2)Km+#_2%4GEk`jbcq9_JOJ7f=obdWYJprMphypx-~uK& zfGKuhnfb0%2k;C6T*B}FJWm2#LxEc5`?y0ASYiPxHGw7sut5i8nE^^g_qFCpK(#86 zU=R5D0q-;c>o{N$dUr?$K&}+{{t)O91>PFm^B>^>UPJ-mZueCg=74SV z-76o!H4G@%0&L>$C3pt{V-J8Y;`e|##z2YAwaY<&?5%eA%O$(`;HN=zzH!R+z9BrzEdm$hWPHMEnNJY_6!DIn*qN5 z_nap=0k432lC>)L25=4s0)2p&k@rF>wC=4^sR6w51u*=8Z46)^eeaD_OQ2KYesLu_ z_bnO}fnYCS27b@YJPAcFIq_k-G5l_q3J8e2Q;oLp0f9h`eg-;*AYux9f;(z{1P3K; zAc-1FB9Ge>Zs~zfp~6f|1AZ>jJ+E!xyI=H$h0b;xyj={nw3zKSrn7|FQR-*=+fyUr z=Su_V?H60K!yReo|fvWvfRwHX!FDE%^}p;&O(2o^WU?* zrjt*BXMa}vSHgZ%pMyX!K@DYuQQ*RKzN>boDdTtRg-Ly9JO}n{XVhl_+MZ?HDs@~A z5j=z*z2Hk^FS|?cBMa?*B1y~c$eczr6DJ<~8sS%-R$89I?xCD+Tq0C5SucGjXE7xnV*8Gc?TRT3SfozC^6$@Qj6@!n%l| z#bi{is#y61XyM%?*IpJew5-hAwU2LJMPXt&j8*u}Z=i(xqCeej&2w>uP~qD*iWsjN z!^cTPWqL1jBqhDw+#HnmHv8-3f_{;eCN)dJ4f@ki&gLx-FPHc-{eE0r?C;w+3p_At zj%=5ztJvcEFqBgI$80>PjARKtETWa7n%NSZx{*B42;KZ(L&33iNW$szF`A{7nN#aO z!V)P|HjhBCPMyimc<&?YyQwLsG>62*2y&d?S65du|u$!}x%`h`aUbq!#y zFJooT^6+RQZLQ=5o5>q$YfX$7eU6?=eW=*nR52euv~h1X;xm4AE`BJhVSpkv$QsV& z5Anq6bv=fRwwKeUSOrdWo?{Hzm@>0e5gE{qRIP3&NzQr(q7Etqj1H49H#Y2Wj%OfEqb+NY+_Q)$A9W% ziZ4N&q~k<@NrBE1YnNCqW)_#6WvSET^sZ9>^_ynuGtnQ3^(sekadGnT z^>b!Fik{$Ps1dI64bk`JaF@vx6&2zKkQqSu&X3*}8yQ`s=c_OT2MRCRkz=TQQ{*Uc zF{j%dt9izpO|6l3{-2cp@WtX4QR-xg)hrbh?7uA#@TKwh+MeNv*Gy%bwT=1XICtaO zdAW4DJ$8Mq8$R@f+>+13la0|ysO?O3AywkY!KB^qO9(Y5C*4P5>hH!6{E|8X#C$sS zhuNeT(~_1xZ7?=JDrbDQgsXXr1J2q*mKZdSVndXGEOhL3MuXbGVy8lQq^8z{6%n2&KBdn+3_ z&fJ28J|^Y%x{?)O&DR)OKmIZC|}&|+yY8pDZ;rYZco-tVpA`GB25dU;`C@mgTO zPNlccf4c73T?xggH}``wr#<}dwp}6eKZ7)6H>Bek1w6cls#R0HR?L?c${5sxgP#oR z9QQ!09_5zgQxiz-P`-Rk2HySc6XH7+EoEcPyUca*kH*9@1?CfNtj+0VWLzleVfEVl zm{CNN%(n4itXxD?t4u{{72YA~cRe{QS7SpjXRRb0RimgmqN@v1R~ZO+x60R?@xAyj zZplHM`TR5wPhYOwhVUwjMoX@4_%C6h!Cp(QdA52*GM*rOjs10VoV?4S1!dnS-?jof zRoHLj+oh5|e>F_H=8piE&n#$mL*?jwwCWrIL~oATnM4@|z>K&(=qf7vWc5-p*05@V5yp)ru>~B&CjM_ zp@e}3@nub7uR<3^VDH}(%W}}0q(RgYrQYXfQlgzcCc>#L2o`N_;>pX&=H}w&y|}g9 zUN0CK8u|u|362&|5^;<`$%0we%4S^5ONkLbqig^w!N+1j3`cW;4AW<^J+3L#Faie@3G|MeLH5~(uqg+ciGU2r|lYeHHXShGO{ZMWnGgOMu zsi;e+SMqcrDyb1wvA&pD!cR?+->7KbLy?XRk{-4pRQ!EpDiX&RnEAJVJjXWrmG!cx z2`lhfMp3H=4HiXxD{FJBMWjw!1}5ILmDC^pcy*Oe$>;x}LLf1pAmzXRV!o+y5H1=U zwAU~zWSLqHZkz3!bSgGEq0&t*dA`&UAVw2*%j)aP%IyD>CwiryV1VMp!;epqs-Zxk zaMVDz1U(uq*ltddXYYIOqPY&Q?c-qdVLk&1CKjRZ$a!5R<$D{#LLgXYkP6p!?>#>*GrvVaAktyZH~PWR6(;*(A)Si4vXe8hKd5xsE^FC_X?C5>8pvdE z3cq}tKvjQb^O;y?;JjL(#1h>iP;9#&Tk5JXrF571-#Ki~OtYWJUNB9Yam5ozZO#7} zaiy@4m&`~GM;B;L>&ZKfWgZrU#U%Nare)SK9=~mQ{*F~c(&uR9%cnfB^dhxlVxOyQ zCEn$=ryRuVaw9%WIFr48kM{UwuwdA1JaicPw|)BO=eHXTIkPnB}DIayQbG1ZXcXn z9>Pdlk_eG4f8Qlok2QRCjhS&;>_|?vXyC@qodyqfXh;&tgw5xO<&TtGbw(Ps2!w~L z@%!Tv%yB4b%G$PYsb@Y|yDHD6UD(8WODIH!wpLg3?HJ4Y)TT@RZA#oX)p&0mvRu^rySUXHpw#8Zm+?FRf0;0A0Ak3wMz~p z71!ai+@;rh{M=93{pyE0M2C6iskBfTX$T44%ES+nQMt^MntnsKz$xUS<^`TgGfDgA zqkIkg!usCGe>sIa7b~g>Vaz>HqqL=g^J6@+O@((j4owc9i|k9LOoKDuv3V!PP;IN$ z%HZJ5a;(l{{Dr88ag^}XNN?sSpmbegRO%CL|0MSa^BZx9Z=*KfAfJR@M1`Yww-k2b z<-O3pg=SpYCzjwry~c2~FfPhNWasR_{a|-qD>lhk8Na`y?R~1GU5#xa2vQ98U~&(3 z0y$V%kLygOS?CJ5MK{n8Tp~*M2x*>%zM|@1LR2=){lSoZ<_OCde4&6&5iDRu%g^S6 zdJ{{BoQ4N=sxI2DrG26JB$x*6hzzF#{n(RNE#D}=C&)F7)JnD|FW^0{U`5NbGE*vM z6c*(vmVH1b73#>8tl*QiNez$xYUpM$rVD9JS~r+ijre@N>+U|T9{ZazOyc$r;cAjX z*nsKAWR5Q%4V+%G<&xT0c9q!pS{8y7y1Inp7xm->JW+xTQ4 zw=Rn$XY-83dWSFy+(#{kj<}nk+@LfrEh#P>tts2*=VU&m*7A*T`hAg@WUH5yV7XT^ zM|s7R@c!}bY0WN-b4>YJhH~m4FNnel-ND4@Z)J>GmytL5P~Fpm6LuVZGlYjbBj~6n zCtMe1%pMPR5-7R#B9{=i{8t4czvk{v?*J)sZxde5gra{~+PATL$o&yjCiWkQzY_Z3 zz4hYpnygr5za*1m2wD2QYPz$degfc*(^yH5SElj+n*S8nMMg- z;K6}$aFF#=ga0;GJmfN+?EYJwGHy8&4Wlwy3urP1iyMHNsTA_SQ8$jDazma3G5XG+ zhaiKap-ClkI@w_&8XLN7bpg5>9dP2S56@9dod&Lcu#bug;nq=f6B|k0K49OT`me|LMUc?#wYlk zEdi#ai%S>$Hho=2H1I#AUpsi>aojup7QdIdao^K`Px9wsIN%L3H6bK=KXZr0tj&X4 zO^DHOl*NLOzcvT`peJNhYs2QLwn3phMRYLh&p(jt$V{waoz%{9VHmA9j4%B9`vxMlF; zkyzxtl%jG#gk6!za`YPPLc{ayX5f;!G&>!-yO&_dh3ZRzEUSzBWgC{9CTGNkpzLrc{p58?BKd{ftze%bJ})v*q7@nFCJ-$|8i9 zKQ1_Z_nJA|o0+2yD}3`&Xt&qsZJ@&(bu;f9XsTU)@2_u!fEUYlG${&}k`k~MvFJ#g zep=B zEenK};%y~yqKax9SNTs6wV8mq@#jKW_hR0oG#RT?=gg*;^ zrZoFk7<``g{Sm&pyi#b_s~#Cb_J?o3uht$O9%^oe`_zP@_f>;l??|$7c6j%ouQ@rV znI}Donku9gd(;PKoo`ZnB|lSfL(CcmcK9h{4K2yfdveiX*F?w{S=8l;c_!AY_HaJ* z0oiU2PE+{7^8nA-0?K-qZg~hyU}@*EIx{)+mOG!6$h4 z6F#K#O21)wZNhZ9b@y{kq2@|7=0~envCA>0*#=!k0tuBTLY~L#MV{d{kN#XAgk+}U z5Wt2Zwq_K{0d5|_|K{og?TJK@O?!fe+q%nRmRs{|kh!%&5@&miK0apQ-51|j2S4pt z&VI=fC!wW&d0H7BKK@5d!ESD*%_88DBubno!~bRugU2H@82>E4qSRJcVDvE}ZZ=i4 z5X8r%T8*m5$HSX_B@44s4P;WjPQ2c)nsl}mmyn?M#7`%zeqZ(@ugCSL?i z$~Ug1S`kg9`+<$RurHPIKg#y^=lz&ozJ6xp*qZXC$s2MndW9}aq@>qntRZa9QE!Li zSrJXSG5JOrD)7n3(!7}A;^MmcNS2|M@NbqUQns5pi|&Sz$2-?FY#%!HySVWPk)MaQ z+af41N6D0bs5twkv>_pXxdGUoXwOeo zQC$|98>a_F%Te2+j3@t0@G;H|2x&SPdnF!37VmuFM$t~veq)fKhm==s)&>5HoXXVB zQtO3|U_-3jWL)<@CB-|Fr~-ct^=#_?Xq3aFG8-+5c_}wff-j2Wl14dG=*Ir9` z;vz2(O;5Unr5LHNTB*VR&471*8|-qBnLX}C_xgEw_{cs><2@q7jH8*rt35VpFCY2j zo{CNJWmlFb!fB z+VoPrGl(o^jqOM?eXroBfTsHID*ndD;oV>?t@L*bcmz1sE7!M3!1u)Tg_tsY?&*yt z{-g%_mIRW%x&)2xcX+vo4UJ)XO{WNsMdc`kKl(hxy=qIm^e!9ykOZys0+Uqm>(@qN zZb-1Lj8wG=5eFuL7^|j46t_`%IbWswhBS4nhHL_=6N} zg#88*wIk^{8^Vsyzmz;?T4HZC#VV;-$K)tUbCnN}WVDwtrQ%2pCXEG1<7o7lhTTE> zJ~wyXM6LZ`OAuO9z`-gRs+(rVKt*F|`vWoQ8G}y+;(qGCesq_I=;THh4U4k78ic03 z_yCm4t=c1=4uP8^yGG0;ab zwAc{o@me2=dAu+9sU(&HWXSXObGX&CD=$ZhdG;RmbJTB_D)n6gG`*O*1MzIMSJvKi ze{aZF2T{fr)a?Fi2&+vT73&Pu*HcesDu2=t4HLmX z!$E0!OY_r{p4>*vGR-674`asP7qMmjE3Xe~PfSf;ZVr_I6P}a9nWQpoH=T{EY0~4~ z#U6eC9jnyEzz1>Q%!0>TcL~KDX?RPKsEnGSmB@N3+{1pwhgLM;rDu=G)7qD+?>(;_ z!Oz_2=8El2)Ef>-?%HT*gKaZ52~5aN;nwVE7a)vslJ@1QkAa}tuz&* z{}ialFW|fv&c}3_0)s!=?^0~-489@6VeM=7|Aqi5P!PZt&PFG+F45(Ws(78Sz*3L2+Z&?ga)D?d0Fr8GSgtNgP{e(x)4TO9Efqd-1b z^^JZ31`-ob|M zLa;{8};>3e1J*hWDD{Oc^N}f6Pq4vbrb}w-WJ$t;JoFlzdKQmA`yLEOm`=H=CHW~?J zK?0qp%gJ-+g0LOde;XF7RK)Y)=ESrK1|9oQbwZPv3tQZcF-)lf#XikxG|WUcdS#;B z+Dhu{3m0&4t2svA?8N`pSfV6UN6#vHZD`Bebk_#_f zomix$3ThRJ9kUvAF{6-o7-lvI8G<(JGEr00_6E3Jn&AO^l8Jpm_!j|HKUFV&MkoA& zvidl_aG*eY@vG+9o6|BS91B8-oh z{*HvN+rWmjS8XHl7EAM$hGDYd>O~?k+09-yEu|E;*?T>yK2@j#8b~V>XV7CYe{OT= z>LUB&z}7rO9^<4V1;Snvd$h(ZKs1js z@$uQ*bA^~Ug$YN5&!;pcKb=+%JmIhhd;h0&CMpaOCsyR@%J}z8j#wPI&qXAt^9+He z9j1Ov+{Ci?JipP-F~v;b(}BbHLx=MRS|G%XjaC&?e+4KpCuZ)UmM0Cmq!=9-vE3bE zl(uDl!TRICJOw`YvN6tq0LJQ?@TrFkX(FBwO7>>{HC0sRw8dSZ=Ybn$MNYxbw1&A~ z4U*;<|8AirF7Bn$*+Sty&*Z?8JwK2eN7J?_E^D>Jkr4MPOL6Mtk_aiCs;No_w|y0j ztG;ThhU2Suqk+D5nR0zmvibT zRpibrQ#xeY)wgm?O_F7(uo6SZbj$kr=PBOsJ7?4 z9G04pG&Mz?Y6l0W1i01Fr|wXPET~N10Q+lLgS^HWxZsd_kk*^;~xkae4sW9p)Hr-Rq9-khPzow4)vQQ zWLpI?ynx6^s2&+dNg9il9{dSTr~2X)ko&aY%Q%Itxe?BWpMfefGcD}Z%Ae(pS~bpt z^+c*S=m)cQQPeBsXQR+GjKR6(P z$aaour7~Lxl5JGp8P4|f+;D8h>&9*bn<+IBuE{c2yhv zRJ;;TW>1S(^yFg6RT~Lc&ew0yV1}z1vaK#Yby?7*^JB*MM|;Y{8h?G`raA zqna1<#l7B=aj1hP!|gRcx5mU%u2TNXVD|!gN_9b{3=>ZKRu;6ZR;RCaT8tf1l27DN z6ioSFzet(1MS&w#`tLO&g>Or=Dq5~`rS$EuZD3Vt>fho{(|e9^`>7p13RWjHIVNh) zZmVdlHYQItN;{ic9A1iG2`FVtKJi%MRn@xWbNhJ{Y(`MWo6`SbN#O^9Kp+hj9p!pO G8|43b__*%? literal 16900 zcmaf)RZtymu&!}kXmDLfg1h^|-QC?ixG&s2I0Sch5AG1$U4m2?7EFMOsQs1p)$M^xuU52LS<5tyS|A0z!B~T1;5Y)8HZp zUh9YE!*I`C!>au1!lt}?^7%)ymXa9F0E8%U6cA@Hs@r2|t2YjT?MMEK&sF!xR;P(1 zJxFf8OgT_&`%_^18f74-j~9B>_v*F_4QG7P$=~I&{g0k-!dKZ;dhG_Yv84aKQ7`JU zJ^ehid=1+b=_P#o97{5v??~H!^zyIS&U_=f-+Z&XS28Q#IuJUNE}ApzE+z8$!_(s%I3_!)=jTdGmXzz2p&3&czvSwVkj_PR|SM`xDjT-m<)@wFKtJ!fY z+A9f&c$RQF&Z%Ui9@S9nRjlxMs@)Z5_OxNu^|5JS^tNFPeEv!Mp+fj^Yc}Scf482J z_jv2_UYgabd?1AMePOH(|ApkUIjM`|sON7?4||4r>}#l#)Nj}LPNV67U-a5cAqgk9 z4hA)b1i?G`_{?Is2NgH3=G*Y_oV4G*#y>w?4I7fSpx2h|vD&hsqdFVmofnVkNpM8o zEDOkF7WVse0CrXXeH^X&Y+X5Ugeg(@8XVq_7ngH%kQ4q8to@(w`VD%+t{VjBlZzMA{89 z;%$e2aiD==VT$}%!%lBbY3xicyog$jB!Djxd7vpR6bXArR{Oqv(5MfWsJg3Yy zcUpf*M1f-z9ik)^?H|-}` zxbJl0Xc<(adaW`;Xc^eA&$kJ4EZWH)dOO+mFzw;MBfNjA5<1ZP>E3RWzD|&L1WdK! z2k&T-AdM3|);yD$reQ{x9G{_#6R5f}9%tdjf-W#_wS$qa(*X;ot*Gkja`g1Q_eN^= z`0%;Ho3r-6zU-m(+)f%v8KxzXfn20UBXua$j&hd^L+a{0lv^F@IS92IL#!_sffCl2&zHVp_~j(J1np!W5n69+~xPAJ6}_zBa%4jtFt9W z{@f*=wRJ|ZitBopGm@A{J`xa&M z)PY`TF0^X2?f!}827nOWNuI-}Ne-gU_A_rT89Qjihq3d_{Ugx}ge|kRq}v@?<-}sM1htR5<=} zI1L1)$lG(bP|&c#@>`Np6h0xGHe-S%SWq_O*_rH`M&)M5xj9Un#*HS!PqE5 zISo-XF(NX8c$<8iK|uH&>qt?Q&-b}D+Tgr7t>MFp&WJTZFnPZ1>|RTVqu7iauEwTX zVJi3CHpH3>2eq__Ox+k#@Bzl=K|7STdhX7MT{c8Ce71~q9Y&PXH}*iaRuCUgMZj4H z)QyHub z_qnc(rzc$MCNk878`Sofx_>n{BwDNL?TS=$RO_S6!R*Ey=`(aG@LbB{HGQ+@MqP=h zu&0VvO0ab!36xlai&*>Xc+6_xPmdSo9TasQ3?*TY!)%lYzD(AZ0HWie+au=#fiLo& zU+O6Y`-6UchQAZ*C2TI_f~f(2hrMt6KE)jP36+(ZZfle23Dx>Inkk_7xY0&pkp)+N z%^^0b-mA7bkD<)a8%J{cvSRJ2S;}#v9g(doR}TQ3QGy%7T$YWkQuW{|T0eu$!D%Gg zhIpru$xwR_h!F-%c~|@zigH-C2m=8{D8VNnCdFPc6Rfz(8f#dDmuUW@`u=TQn?l6ex-ha;(`` zrS1uS-(@|j8cS+#fW*WdM9k{Fbp6f|!@JL%Gh}@yEWnTjE-DYfVpx0s5?hF9Qzi@Lf>~6Pm?DX{;HP^Q242(r1D1_=jrbppWF;PQk_!Ls zS?3Zy6SOYNhA^`C9Gr`$aM+kF+PqIpNc~b)YOTag^;@K{!LHyR#-D?kKh>QZn&JHs z(S}LQ;l-T8IWrlT$vDeig`Pf3fs);`cyZgTesw;vUk*#=1ZlB5zS``R@)U;`I^|DW z?`Wu5^KI6hZo2(M-a~zF#>3kiX?zjyY=f@)xk3s24jF8WN!RqnV5qMC{5IS-?p~l` z*Od<2Atam`NRWyKlq2%T>WdXRFci|p)_QD!{us*BG6#&@1J>-ygf`d(+Yt%AR?$|m zG2&h}ZNhe;3iL&t-&Bo~bSQvwc_uqFF*q*u<%r&3Io&Jc z8X3Bs8jXqH@NHmV7BRmCYCHHs=Nrep*-}>qojz9eD&96O%Es8n$%gaSnOL~VE%6i@ z&N;!@pfy%G7dw?+2y1|uMDE?45uzNTNB_7>aX);UvtG>N2^CK4jXJOIypMJdF8LKU zTYqIdp7&|wl19M2-A~xsFLDE9e-nocdK3)_YdtcQ)W%k7bx|ihJbIc=Z5ZyZ^yh9L zz(%H87tSJzNkw!4yq5hajBkYU#kO&cksLk7!K-`GO(iyvT=U{|HBlNQU1VB|)w$-~ z!`vE~Br`P8J<1%ly9{1OIZc%XlCTOPAdcit!jhpR;%=Zn+J^5sT)?#vtC4a+pY5iB zJDz5Ru-Z>~+fH$VWPdd~FVQ(AT}O25HPC_wANYArttZij2ISLx>m75xSQO6+R*;0g zmeuq!90F_}HX%kFZpuj4@q)SDa3k?+Bb2PrSZjTt%acFjLT3$4HPduPZ4Sfv?#~)_ z*x>rvxpNnXh2P;_1YzBnVcqa9VK{mn1MhEaK>}|FhPXm?dB28(cqh2Ag&XIAnbGh%w38mufD688Vg0{`stk3i+PA1e~X7W%o(N09G z(V+dK5Ra`6>fQc$6V4g$Mc;jTrbmt|ZcfPDi&luFxnBGk{2GGnMACo~C5VWy9A^BK z%9O|VK>O{=o7e@%H==p}Gh9?4J3)S(^K@|@-bpGMlMM#a6u}N>;hDZ{$m0w+?{P+i zv!bb`WN0Gnx5bB0s;!iJeK(?O@&xo_Yr==8dbs9N^gw0u(XKa5#%g4gLt%5d9^x&bUp+ zI*CuQXb^F)LGcsTq00ke&-aZbA7b?Ow}kNZFJJuWYsoo#JJ zd^|iHd;0^2Lk8)L=de&2-C9OWIvMMW>WH|w6peAk$qJ4MH%Wu;|h=~A6+4h{@J3knK0*pJ@vag9^60=vvWcI&Lb_(VX2 zy)N7VOA=(g{REg_f)&_ekDo9i1vl8j0R0zl47}1}4kDqz)m%np1-97YCtxX^_8Eb1U&2>fjdHvFw8)9n=PT=mS{*wNJdpIN5Au>lfU5v4<160teocH-d>QHxOk-7@IW}47m1u$uA~w=(B0jA`kk+l2DCPaOxmP~ndvI$ zYkm8H%IFn;s^>pUrvz6NLyr<`Ro3Korg8A+&kfO!G6vn2h>XJTf5yvnnk!b`Vn06= zO|u}x(#U)>eRZq|c{Ep6$&^P+2{n)IUvm+$hJWpRp@dc$Pc5;};;;?#x;>0!Q&lV! z`h5GsX89Y7O)`a6U8!1!!`XBAGrQC|6pr$y?Yi~{n@H;dTYvsSV}Guzrbl=`^4UoI z8~S7M#L3iCl4D&LZY7p{pxhZgK`flMwzluNP~zogXL!BoNYnrwRFOn1!FLoBg%hgK zT2%$)cYHjmbW$l?<>3q586J5ELJKn1OZfwK6zZEGypC8YxWSi_nBA+Z_&{j*y_tMb z6C6(s<>8a1YQkTymwXrrI?Xm2Z(XHsp-_~6s;*Hc@MZxKw?mh=jIMvB--jM9zQDT5 z_##%J(qN!>z*rOmA{Oc8*IOL7NzRt42R1uBo;?F>^ndx{qY!eko1xoqPknBbx`jeg zBK1!If?!CHwgxmCjWr7V)0^wAxV{-lm1HGp@U)MCwN_MeX3LZ*jEL+Um3h1ahneA%41;uV#JudJYWnF4<o}yV;v9^YzeZ9DJPbxV zCaJz8JMuzS|;y@^GISocc73^ZoFw_q)lcpJX%zS z?3#&5BtAW>(BMlU0{VA<|F{5pf0gcm5ueT^9u0&(YN^<63?O&=!S{pn(` zLg_%W?ebF_1IK2E8}fXKJRN7Sd1NEd3=zE}{Ff-55EeRtg*n1;E66aMQp_*vt;2W-BHy(2b;Flg4sLL8j`MDJ zAbfu?@{0+Il12eRII46kiNKmt05>iU=h z$)Irsw!hHw5wf7*gjxln_O`c8!(m4}pSsbqKLIVrd=!}5jW}+WPlzQ;+_e-& z?Dy<48J&+h3*@LUmFxqzh_g>rb^`iEl)hiDf5($dZZJpaL!%i&d@Buf3+M~(|w0IKfQ za3X0Srk%nLvE~Ab9|gBtt2_H<(fw_Zha@}t^K>=dbE+8{uYX2|#N=bmI)Wc;T*rwV zwd5A@i2kamPB6hHF1AG?W!pUo_~vz+3wdlN<%QSGe!5}^qJ59h?#udS@qUf7 zv-9ZWcl%ZgYEV62Ov?klP4Ypq+COVB zzbpQbKJ4p#FTFlCeU?M~M)FWg!L^__)A~q8ym6&0c0f4_^d1Qsf;q;YQPHwFTKQaY z@}^_vfdLrw7oSN5$O~22BEUPFgd#kF2FBsIH_Toz2Nw=v^=tZBu!NT|Lp7qp(fZ&&7q@7C0rFJD6; z(%|4PztN>6GF#&@{I1tbNIIaALQ8ulFL8_Y1vGk-QMPKSZe0HpMtxgqkoct%kuq`w z#x-}Cb*!ytPr?%+STtAMUu{{K-N%@g062$UWI7UOQm3=mc9wknbhD2qEj-!b^P z%oYhuwx~lumz_3B^a7bYyyq-71@Fw*7ULPhNJocwr5CvLRsE<~sh>maF=R1p!hO** zh+7MfH?17kb@`xEls`270@5OICG>$(UstdYt%lJ^wwiJK8I1@$5SE2?UF-^CtL8@; zs4{#zGZBM@8f^QW&S9I{2Bl9>kdJkdQs6??R6c{5q%l*?6D-aNSM(>Zc4);q<1&7n zVSb1AZyvYG&77Xtb`dpP1hGZw+U_-uc%-;be&gSUcbi*hJ9V!?LnI5O4d&1TOrlrE z11&b|=uC!5&O5GB^zm!T#ncJ-bmy8|`YuXV_zy3)jPFmR0m zFLy&N`z42~p5XU|+fn|GAIE2AfPi3JbxB>QXQ+7$3m_ug7v}~qfMAh#5*_)0mSKO^ z)R>_thix1PNC=^T>X5@o5Ik^s!>_0nb%0+Qu?l@fMu||fRMI8(eq@a06~$a6goXp4 zTc(!CW&GU`Z?7*~C%0!|`Po;Y-B>bq8(=^Pt0w>CW3cOKf|^OmN3o|I)zb~mlpR!VZRWgf3r$DjB6U@% zJ!v9xOZ<+LBarT*ahaknq^miC#W^ANPQ%<$&RHDpEBCU_M(sbvsugC-mYh-fO{Sw9 z2eEARzci;On#5;xRA{kHL-zc9^rxh(B6&XXZ*i0bo|+5(tR}B*i$>CjH@i(J`<5N< zm*!QawcKB`2qVVWN|!2bmCj+qMz_>lyQe41Uc6GYo8|ZmgRouOWH<`fPtitAzEwsVe{gYe@!;OmfY1hA^J^GP2Zh7jc0#tW zV;K{f-a2?ll{FjAo&kmu**_ByBXvrN+H7%pUgwrk*v>}T<%nfg$(O1#f`vAf;$Wwj zK4OU>ekZ7*cXG`zK^{1Jk?6U1Z!$nXMaDUqNo}Oc<%5yn3pWZ=j1+|nlh9DXMmgJp zw$>=#X^n__>Lz7RpGg`FbOM{jMF-I&Mx~Gtq{nwcJ*VwE0OFOdSNksknPO9!AjUy9a^u}; zl{GfA#HVPd@8C*|vf;gcdLXrJL?MukrGr%c^ z`dR^O=T^5*G@CU0fpX=d2?dv}l#Z}rvrURI+yrK9#ndWZg69>4-LW#tEa5!`s{Zgq z8R@zhQOojaXAAXjJW6}a5>uV1LhgG$u5JQ_EBF0C=A-S5S2BuoH^CBy68!ST^VMKp z5t!x0xnCI*Lk$t%?=aM?bAC5Sk&8&Qiu@hZj7DiJ;6#WZd1Z764c#+#;>O(U9%lfW z>suxqZ)SVz&lYoFmEAcgM7u2vPU$2e-Hjzv>AJy1PeOk$DMk`K`~^i^seLl#HX2s@ z&vS?_kECyji(-+eKdk1750r)$2U(RhTgkZT@l<$kC`GSck-TzG(h{pKG1aJhxkqgZ zItykNw;mTU?xiP8Q;PAKW4yNPGkd;&0<^_8y4rHh6AzZ1@@Og1z$t3+RoVK`LOEWpvj)dqZ+bn-ZI_R@g2TDm zUOXS$8{AioF8c*Kd%YqEKoqkyqA= z;h>9H=F|lLAffO3sj^3_YLHV~t7o60Afgf+&g?fx9El~tAP}$YS=MFe#gI{HMPF+3A4XgD2y6V7pZ8*{ zm8;APEKL9wC2F|aO=CXGJo^TSmQpb}X_X3Im%nsfn-Yr)Ip(;&N*#Ay_m3?ila&Xh zA6V?kP!$WD1kP``H7hg@QY|w7?54~1UuB*oXqD_ePJg`i3GPV0EM`;%joWPh;8C{7 zYdmIemNAl|da??P+nTE06i%eXK303w@_~!CLz4QEZFdnUm~0^2U*Dh4GePdBsTQhV zsihVr6*e(LETK(_Y=c5vXJenfn3=4BLe-LG|E6?ccR#tlx)pG=|6cC;SaBt^!li5R zcPgX&c2MsDL}~N-O+3=a0$|oiwZm$c)<&SyI4_0A@|JEcP=7FY3^?#Of0zNSfD^&A$%$p{mSW|9&i*6 zj(_qDpxvBQ=^ptttH-vj$9~Va*80<33lpe5w3*6)d5BABGb>2&T7!J8KM%t$O}n*W zJo+7yk8gR<_bN{XJ|u{lon5UfZc>HMFjulERk&KL*jqG{qadfz)xhuQcg|aymb_Y? zVYhel3JJX|M+K*)DQMX1IZ`*_*vfscZkpLiT)9gL=cKs}uA(KzRP<4d8#RxOLcE#D zJP9@OB>kt#JaeOXaqm4Kc^GYiehxcy4(-(f*^`-a9<3OAl0lXjMU1hK=(Co4O{$8%UM|E#&*;l(B?QsiT24bqlr*B{Z7V`VuFjMMHlGAysOT^==1z=5qZQ_2R z-1qLb)#p6A6j}B#jg`CWg~=F5uJg4=mk4 zMbEFlbNEc>OXUCT5piF*6@<3E+@D1YQ`=LOmdxBa$alJ^s;X9Vnwl%91RCi4CyD~~ zEfY~;r~^+zF4y_)m=yu>0s3+B%|pI?8RS^ct^$kP#XRzE>S#R+#~GhIc~p)Cii4cW z9H*m(D~n23;e5HIw0*7&$Qv-cSkS?#GB%E4^9a4Zdg>n0VB=s|P>wkUFR@1Py;++p zX;6LW6tT+67ZSct6f1(Z{;9<&8!$Q%dsr@?heJCLyu$kE{QNwMcpba!S7M2R5q^_F z1m`x@%z~KAD3f^-kF`7BW3BU>kYEeLw+hLBNDUxD$O}Z7ySX3c zb)wd!i4k24wW1_C2@tbyw%f-8qhJmP`&caiZ`w$^LAjZS5Sn#m^9yX>OCkC~g$Cc7>RooBAs^cU zIlTk#)OXQ82-Til1rliQ85sNCA>X3OzZ4b&8XPSua_&2S?i?lbG}vKCBGdB|nXS>g zJ0*+o--w^syM8BpvQ@ycNTP2WG0U^*#8-MC0N=cB;m&`}!LXiv%vI8XM1O%D865l( z{g6XRuw=jeOMjz9WK|@yzj!yA9i}KA0|SHG1bi12L)S{x7e=_kAN~FN)m7xbSP^arS9Rd{|t-bdQUEl`8{54zNMvQ zmVu~1GPeH>P7JxwZV*CX5cIQzmo3E{siDMziZ%E7Tl9Q4KN4`#}D9_*vX?k}pO!=)gn7_4Bb4bJT zqDaOnV(7U1_j;to@cwADU9mBc-@BdBUmAHSzyI{7YGVPi_y~b*r-e;$%CQnDe?9;8 zfw~{4mSb>(|FgeRQE<@@i1>JZxfuACaFBgBIQO3(xsqo~Se?tnEhWOPgi|!6k69%H zBXMEw6q@;gX1q%5b}P&*(QhwjwHm7%kJPg>aV1XSsKm6t<)rE6*j;x$hUQ|hu`kT) zV+}ADC0AEh_W-HRr1Y}-%^FExK~@Y^t(ANZuuEJ`p#^k<`-MWnN77L2@X=9jV+>R; zXOQ`#-WMm65hugihkOgXY4OID(WpNU{=B$ZDs8X^hCKKCdranviTkKK>$2J_;-Ga6 z>WBEX7GD$0K(CoP7J96eYCwj_U5&HrOXJSWm=N0MQ^#7X5>(8zV6XiWLH3_ZhnV9@qF1Eb95#jw+CTK zcnC_X6?w!ouwb8!t?ZeXbU*`_*tn=L1`tKaPq~o#XH-LT(pdaeEr(+5o7_BF^YP^9 z=s=xqrRelm)Z^rj$VCV;RnXkG!NaMn=)gAL=kN77LMYwzIqFtY;-;Q!9U{UKkl*Z; zxmwdAck=k)YYlsT2UM!0spVa*x7IFL)Qt{!?hIJJcZxQPc`eiw~~Oj@Tz_oM0xtx3Lb{5kxu zyBD?uz>WN#g_E*U&crG80;MCX-DnFuJuz_nIeOw6$6c?&s+F|L2zU?5G!ekeS!llo zFPgW-3Pcj`}O?5W?ab_h%Gy97f=v~(o zy&qFFhNcAIGR5-l!~O!ti+&6tBv?y$VCZ!G*COZC^Rd=v3DD{VK&YZV`0rM0q-=5@nOTtcx@ z-`GfyVTF_)=xoTY-xG)BHAl-#;@k>0Kap5G)B~X77JGh`U;(W#+Xleny2|+?3X~v9 z@j4(Oa(GxV=hv@n1U4Y(PY6pg$c&Ot;)efq)~zTw>;uHy`pS!hYaNUHxEYhbgRg4R z+}+}7o`g)4OPEQ|;tiYeawTA$%HmQyClOH{QqjoI$3uxnpv;6|Hoy*8NC^3e-^$N* zqqby_w*0S5T>t%`@v?z_`@m;FByBE`COSJ7m_~uq^-Bim*HTzq_chCA9jeHpXN(2n zwRqW7h)`1w=SY~Q#F+#wWc43wU)ql>D-{W#MMi*+Rc<(sqj$1IsI?*Vo~~JX4iGFY zSjVn{Ia}(<$;mhGkK6li&$laGUX5+PgyS=U#yks+rN3QUeb1{R0P)Mr;duDNP0Yns zOl80yG--mz(9cLJmrW%6skc}}J*KYlL*%B2MMfm>8W3{uoeA1tCC=;U0l+}4z>%rz1`1Gu3qlk(DUqGWSub-M#qTbUB+d9M069OLgJ6ct8Id?;aM)g-r9s^V6BrQ}Q;SCiP`udh7DC zQX$nG;n1i3pom{#4@R?{E?z&>^3sL?I2rH<%HigVl9la73e4N^TR>PE}F zsi3VDlCxI}2NOm!ndIQSbW~gNZ4rN(jki^a>Fbq! zqTN5 zzb`nx8&_h%Jrt7lQxR^o;6yE0jUGfj6BHagGKnEIbC?*Yeh-mN_p6 zlPomN>R(3=k&0Ki-xElR=54S ziifTvyozV0-H|T?}miG^F_wtBpw#IDTI~O&zZ=pp6zI7~U;(eX9v~ z%_Rrklp$gbO-9{o@iq>QY$8+WLWjtqUprlw=!9l&&i<-B;;B?gDuUYF04x={Q|PYo z11qyPuIW6^msVN_PE8KdAMXa}bHL6LC^fQ9sh369#H1cfF?JZ}v`b#V$&6F1HA?9- z8rMp!9QAw;KUupJE(75s%Q_j;=twh?gcLwR?pti!=J%3LhEmj*cmxEL#xOjNHpVeK zJkF%}PF#r=gweO>TUjCt`~eJ7()chG!YE-`x^-8vG;ltjSQ*{>Exm{gthe@Wqr_;) z0wt5sLc;HhZgRcM=_rjYuGPk6qTcdMHcs}#u#-NnrJ>ijEn2POpi%bVAyH$%NC@JW z!9x#~LZ0#)=w{X8oW39GR&eJl^`<7%yQQ1IMRYe1(f#2jGXHCzX6=QT%WeN8>DptC zHdSdtJVzrAI(JAmUV3k0>(|f-Xp$15@*N%7K>n%=8xkhRkB3QAUtf=ah{(e3zoSSq z_gFfN{zLz`jCqlr&;1O+r(+F_Z0oUu;MXftO1`Y z9;O;>OCXbj;jbt_S7jVfllzmVYhq*#nMM~j1j#8VFg%#?vdErxSYKI2XR#z#^jrF| z60VzCe$K!`P7W(fGZ`zDbu=Gj|Fluc!xb3b3?KS{Jm5T)ZILV)F5q8zrZN3x1!?Fl zj24#65txQAH>pypq52gcF^Lw8LkW1LoMwVHld&c-soCEOJ`7#g5|?z#rkMgkK7BD? zv5)5fIFMR6Y+7b6;Ou);_P~PlRc2e$)>HPum(WG>M&1%61LbYx=>T1OuOHP=A_2Ml zUJa0_6*NB&eSM@;e}$dm67YWg_RVCo!)>o6Rkzh4GG20Rk8#RK+5)kj zy-EvI3s#yE&SmNou7&*UrnmOiQ_-c!M98x?rSX}WW}0I7mNW&~u)Vo_w^FUmMKUp> z8k)=i)!=z!;!K+sl(Lhz*Vd$PEuCsaMon#-vS>REEy6K+i5a(<$=x(75fa)xG@=%o zR1GQOG=s!5g(EG{JieFH*|kCh7=CEGG~xLSIY@MDuIiAnc`G2Ge{P@B(m6G z!ibqfOA9p9!7d%@C@Z6Oj!J3yF%OjokW8F{Y}gut?pbEsRm(4LL??RqN~(!1fsK#O zx=08kW?ie)L(K_4s$=P~flB)?RT~0m>*m4)K}Eg&^ysOF8lN zUHK0Q90oI&{$mRN4;~O7eZ2)RYWiOSNUxax118K@axIASH?!$hWNTeE-m< z9|d2NgO0N~GYHc0yR$}HApgS_*(Si(CRarIKe5r*$mNd^46r(X7=_aLFxEIYsbgvH znrzh|J#4IY*379ZTcw$}tG6{QE7PnE4Rz`3%NtHKG_U~aaSk@Sx>k@6ej8+~c|Yc1 zO&By$u_ANpr;D`5D&%W#Fdl9`BhvdZ5`UZwLv{1Hpy9lA>ut_To&@^KV8eBInUrO5 zh52}zCbS}L24&AucCm_(e?;?E_ef|R)nd$k+BOlW94v|UU~blRLjQ0ZP-4r2%|3Mn z>o)c|#p9v2Mo^cLhvg|xC^b2rL@3y2qYKOs?@M}`_`2q==9(4=kTk_PZBIhX-PBrZ zr~4n>by9I>XJ-@K+>o8|G;Pd^cW)?r)m~#|TJhd}QmeX{jlF$FjtCHM4zkU<60>Zs z%Hys(I^Fbs!^Li)<*(mdMDF~twQ_bC9DMnUdoO<&F-3^A3(9GW4BH?t7v#5rXa(*RMsaFXpk@rOtgnql#(8*wa~=>H&U=;wN0Q<% z=~0Qf+>{j>5oC?u{)Ajpd<^k1GgPzB2_X=HLz81#(ra*Wz}OO-za_H(wqrv+ zz0y`v?a{P}lFJz=@BUy(A)pd~b{ghG$JJ7r@AlNxR+>kt6nYMil7}v99Ja?<@UZ3T zeP1&kMRSM{5+R84Gc9j#ct$;ly+^es_n)lXhG>_EcB%`8iYCWLpCv8)3~8MaxHrGc zWH36`!Flt6p(JJsBEPqrE&gHZ?iZtxyx?@fgPx*GS)go(Nf7>mW01|;SB);nF)3s-o4wF7D!)tc}@L$K1~NQb z#Yr2Tunx+&wm$nlh2CtbCg8*b5`pf^Y+pukoQ>UqK#>9GQek~Po6Bh899xq^a4oaN z9sDuzXNM=T^ExFYu?oyxI-}8y%D;slDQ*$`e5+$KKq!pF-*Qc6*L_WdXk^K$IEown zc`-GIjJQlcjeh%cL}$EWcnajL%v-BuW@70M(BzzY-<$NJlWQg2rNIpwvZO(rx6c;G z_UBD)7Cj+GP^Rw?)N_Nikx*qcIPWa%MBy?TL-6TWG;|5d3z|f%7il*;Ac9f={o-sOWXfH2ow& z+F%t%kw65bbTBi=kaCFWiK~y^%2iyKohma@H`Ql2SmxhqN|;zqx3*C&8_FG(8wPqn z80VlP%Gr)DvZLLlMf_OsXDo*4wm909tv4{~9yF8NS%e&sJ#dPsVWzk;(a?)XK|KjU zmKml2h#A~>%-bFxM<7`wSX>`I?z8$Ca++fy%HqqV=8QYYD~bl^Hz_tBVxFF`f!?T6 z!OC4>a={OOkbTFV#ew8k2GrG${kNo(rcXXA_NWUqI*@?8f!7jnu3$bUg%u}bhY5Q));;cCAc0gS zHeTk{K!dAfJ6}@_zTLUn#qe2z2>ZdYkePg(!A+9v>R>`9$hU~PuED*xe7hANwC~xc zzMc7YMmZr7Iy0P~w;nbL>dml8nV{FL;+xs}qjWkGV5~K1I6DPEC&#K9z5E?@9vBT9 zvo#L&#*9687;Wd0D#-M4&3F8(7sFq)qrj$;8%`og3#@6+hSB^|^UtXtP#SOBvdY^G znH{rdVJq%P=QxzF3KPx3etK`XD%|1rVH#OZ35YdYtyAh8`vg5MWd_yI4)jPAmo4j_!3e<(RzCocf`Y+$BAs988hq->eMY}S?z>|#t_c*3a#|wBAi{r! z{dW0rO)!J3t~1!Um(@E{ATxo*eW!!k$}$1~;aQ@PPH8`A%_SDxVmA)U!Hw)$KXA7$ zqdYbaY15D(b-OI$URl0+-TYf+!wm}Fwiu|ApRr6t288`7`y!ZbjuZgH`OF0cwYAez zqSFt2_caUMO6L4Xk>1Be-C zNHX4bgVd8eU&h^$7@$FnL%%Qp$e+fZ(4dAotr#y7!RrZ>u)-F!F&*jOE8kA5rqU|| zkg!kLmDxaol3FWgZ=YGJwHYE#cf7j}8z24RotEJKC)?r64}@x}^89m-1nf0~ z*_bz@JP+Ml3hd*HWuOR@sNVh74L-Sw?LaYKwKo+rvy}#|aFc!~jT1o|KFR)cI+)Bn z7Gg?S>|0zfR!%gT?WBBV*!!8j6W>|lJse7F&5>m#;ooWbRQhP@izf8nK?3n;AClTI z6#M340iQgXluMzeY19}<(0FQ=O$wq@ql-?Y9&<2A-S>4Nw#z1Z-(eOaL`iCMI&1sAHP4i1wfU%}{TH10ti=qYU zDz0-eM;$$6p`VP-jrsMCM}MB&A!(m3WE#f9F_{oZS)?8^0-?^g11w23S(X*2v9^k* zKU)$N`g+?WLff<~UDFNP<{vgF`ivNrY!#|!D3|%nvN;axyiKQdEH}n|UAvW!JH2@P z6TzqIA6}zL<{b4pMQp)4ffz(REKD*y3@}3^xF7ui{>6upo0Tm&dOIrt&>JXx_VQrn z?-nhZdK!u;m!%$urT+S%i$tA>aj?l1`%j#jH%{|kdYyAos^NAM25jH<6TePW$nes= z1!HeZ;$v}XEv4u4(RnINU~U}A*?wwCqU9m|k--nxsx^vf5AeRn$pVh?0Mnuc`C!uLu2;Vg{7)wfw`U zX;*Wa;BHUq-TwcZ=lb=LAJ!W;9=uX-aQwln)6svvPf%SQ<1JdhQIPpzpQJ_KDid+% z!(xv_|7G`-+M9p4uk#~(Mx)cG<|di_EPs|%wf~Qb+;nQM%q9N4&z7FNB6Z+G+^+MR zj)|=QD8BER{dSeRdtd$LH*1N?VC!qI+*y}-;)|d!tvt|0N zm5Vlct%>!zH1~C#{PXnRXO%R17-p8ep7o3QUvcWms)tYH$`9TvKIF>&@BVzZ?VI=h zpErH+ydwvGOs@R*Hs)&kpXVngrCvTXb;0-d@xj~oPM7_l|L;HY`3dlY6p$IBU;poR XXyugpZ1#qMfq}u()z4*}Q$iB}^Bg|U diff --git a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java index 75ed5050f72c001d6eab117a2c0b352a413548bd..180c0a532bbac10a8280b63eb7aa783a1bfbb237 100644 --- a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java +++ b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java @@ -46,6 +46,7 @@ public class MinecraftCommandPermissionsTest { Set foundPerms = new HashSet<>(); for (CommandNode child : root.getChildren()) { final String vanillaPerm = VanillaCommandWrapper.getPermission(child); + if (TO_SKIP.contains(vanillaPerm)) continue; // Purpur if (!perms.contains(vanillaPerm)) { missing.add("Missing permission for " + child.getName() + " (" + vanillaPerm + ") command"); } else { @@ -58,6 +59,25 @@ public class MinecraftCommandPermissionsTest { } private static final List TO_SKIP = List.of( + // Purpur start + "minecraft.command.compass", + "minecraft.command.credits", + "minecraft.command.demo", + "minecraft.command.ping", + "minecraft.command.ram", + "minecraft.command.rambar", + "minecraft.command.tpsbar", + "minecraft.command.uptime", + "minecraft.command.debug", + "minecraft.command.gamemode.adventure", + "minecraft.command.gamemode.adventure.other", + "minecraft.command.gamemode.creative", + "minecraft.command.gamemode.creative.other", + "minecraft.command.gamemode.spectator", + "minecraft.command.gamemode.spectator.other", + "minecraft.command.gamemode.survival", + "minecraft.command.gamemode.survival.other", + // Purpur end "minecraft.command.selector" );