Files
PlazmaBukkitMC/patches/server/0002-Purpur-Server-Changes.patch
github-actions[bot] 3ba3fe9814 Updated Upstream (Purpur)
Upstream has released updates that appear to apply and compile correctly

Purpur Changes:
PurpurMC/Purpur@7927f53 fix fire and lava immunity options, closes #1431
2023-09-13 23:19:35 +00:00

28509 lines
1.5 MiB

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: AlphaKR93 <dev@alpha93.kr>
Date: Wed, 13 Sep 2023 23:17:56 +0000
Subject: [PATCH] Purpur Server Changes
Original: PurpurMC
Copyright (C) 2023 PurpurMC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
diff --git a/build.gradle.kts b/build.gradle.kts
index f6cd7b910ce41a254e71bf0fcfe93c38abbb1445..139e2b17b899da6f0147bb8b4412e2e54e817be4 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -13,12 +13,12 @@ configurations.named(log4jPlugins.compileClasspathConfigurationName) {
val alsoShade: Configuration by configurations.creating
dependencies {
- implementation(project(":pufferfish-api")) // Pufferfish // Paper
- // Pufferfish start
- implementation("io.papermc.paper:paper-mojangapi:1.19.2-R0.1-SNAPSHOT") {
+ // Purpur start
+ implementation(project(":purpur-api"))
+ implementation("io.papermc.paper:paper-mojangapi:${project.version}") {
exclude("io.papermc.paper", "paper-api")
}
- // Pufferfish end
+ // Purpur end
// Paper start
implementation("org.jline:jline-terminal-jansi:3.21.0")
implementation("net.minecrell:terminalconsoleappender:1.3.0")
@@ -56,6 +56,10 @@ dependencies {
runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.7.3")
runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.7.3")
+ implementation("org.mozilla:rhino-runtime:1.7.14") // Purpur
+ implementation("org.mozilla:rhino-engine:1.7.14") // Purpur
+ implementation("dev.omega24:upnp4j:1.0") // Purpur
+
// Pufferfish start
implementation("org.yaml:snakeyaml:1.32")
implementation ("com.github.carleslc.Simple-YAML:Simple-Yaml:1.8.4") {
@@ -91,7 +95,7 @@ tasks.jar {
attributes(
"Main-Class" to "org.bukkit.craftbukkit.Main",
"Implementation-Title" to "CraftBukkit",
- "Implementation-Version" to "git-Pufferfish-$implementationVersion", // Pufferfish
+ "Implementation-Version" to "git-Purpur-$implementationVersion", // Pufferfish // Purpur
"Implementation-Vendor" to date, // Paper
"Specification-Title" to "Bukkit",
"Specification-Version" to project.version,
@@ -168,7 +172,7 @@ fun TaskContainer.registerRunTask(
name: String,
block: JavaExec.() -> Unit
): TaskProvider<JavaExec> = register<JavaExec>(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<PatternFormatter> formatters;
+
+ private HighlightErrorConverter(List<PatternFormatter> 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<PatternFormatter> formatters = parser.parse(options[0]);
+ return new HighlightErrorConverter(formatters);
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java
index 692c962193cf9fcc6801fc93f3220bdc673d527b..8cde30544e14f8fc2dac32966ae3c21f8cf3a551 100644
--- a/src/main/java/com/destroystokyo/paper/Metrics.java
+++ b/src/main/java/com/destroystokyo/paper/Metrics.java
@@ -593,7 +593,7 @@ public class Metrics {
boolean logFailedRequests = config.getBoolean("logFailedRequests", false);
// Only start Metrics, if it's enabled in the config
if (config.getBoolean("enabled", true)) {
- Metrics metrics = new Metrics("Pufferfish", serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish
+ Metrics metrics = new Metrics("Purpur", serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish // Purpur
metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> {
String minecraftVersion = Bukkit.getVersion();
@@ -602,16 +602,8 @@ public class Metrics {
}));
metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size()));
- metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline"));
- final String paperVersion;
- final String implVersion = org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion();
- if (implVersion != null) {
- final String buildOrHash = implVersion.substring(implVersion.lastIndexOf('-') + 1);
- paperVersion = "git-Pufferfish-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash); // Pufferfish
- } else {
- paperVersion = "unknown";
- }
- metrics.addCustomChart(new Metrics.SimplePie("pufferfish_version", () -> paperVersion)); // Pufferfish
+ metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ? "bungee" : "offline"))); // Purpur
+ metrics.addCustomChart(new Metrics.SimplePie("purpur_version", () -> (org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() != null) ? org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() : "unknown")); // Purpur
metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
Map<String, Map<String, Integer>> map = new HashMap<>();
diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
index 9d687da5bdf398bb3f6c84cdf1249a7213d09f2e..462a6eed350fd660ddaf25d567bb6e97b77d0b2b 100644
--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
@@ -19,8 +19,10 @@ import java.util.stream.StreamSupport;
public class PaperVersionFetcher implements VersionFetcher {
private static final java.util.regex.Pattern VER_PATTERN = java.util.regex.Pattern.compile("^([0-9\\.]*)\\-.*R"); // R is an anchor, will always give '-R' at end
- private static final String GITHUB_BRANCH_NAME = "master";
- private static final String DOWNLOAD_PAGE = "https://papermc.io/downloads/paper";
+ // Purpur start
+ private static final String DOWNLOAD_PAGE = "https://purpurmc.org/downloads";
+ private static int distance = -2; public int distance() { return distance; }
+ // Purpur end
private static @Nullable String mcVer;
@Override
@@ -31,11 +33,11 @@ public class PaperVersionFetcher implements VersionFetcher {
@Nonnull
@Override
public Component getVersionMessage(@Nonnull String serverVersion) {
- String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]");
- final Component updateMessage = getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]);
+ String[] parts = serverVersion.substring("git-Purpur-".length()).split("[-\\s]"); // Purpur
+ final Component updateMessage = getUpdateStatusMessage("PurpurMC/Purpur", "ver/" + getMinecraftVersion(), parts[0]); // Purpur
final Component history = getHistory();
- return history != null ? TextComponent.ofChildren(updateMessage, Component.newline(), history) : updateMessage;
+ return history != null ? Component.join(net.kyori.adventure.text.JoinConfiguration.separator(Component.newline()), history, updateMessage) : updateMessage; // Purpur
}
private static @Nullable String getMinecraftVersion() {
@@ -45,7 +47,7 @@ public class PaperVersionFetcher implements VersionFetcher {
String result = matcher.group();
mcVer = result.substring(0, result.length() - 2); // strip 'R' anchor and trailing '-'
} else {
- org.bukkit.Bukkit.getLogger().warning("Unable to match version to pattern! Report to PaperMC!");
+ org.bukkit.Bukkit.getLogger().warning("Unable to match version to pattern! Report to Purpur!"); // Purpur
org.bukkit.Bukkit.getLogger().warning("Pattern: " + VER_PATTERN.toString());
org.bukkit.Bukkit.getLogger().warning("Version: " + org.bukkit.Bukkit.getBukkitVersion());
}
@@ -55,7 +57,7 @@ public class PaperVersionFetcher implements VersionFetcher {
}
private static Component getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) {
- int distance;
+ //int distance; // Purpur - use field
try {
int jenkinsBuild = Integer.parseInt(versionInfo);
distance = fetchDistanceFromSiteApi(jenkinsBuild, getMinecraftVersion());
@@ -66,13 +68,13 @@ public class PaperVersionFetcher implements VersionFetcher {
switch (distance) {
case -1:
- return Component.text("Error obtaining version information", NamedTextColor.YELLOW);
+ return Component.text("* Error obtaining version information", NamedTextColor.RED); // Purpur
case 0:
- return Component.text("You are running the latest version", NamedTextColor.GREEN);
+ return Component.text("* You are running the latest version", NamedTextColor.GREEN); // Purpur
case -2:
- return Component.text("Unknown version", NamedTextColor.YELLOW);
+ return Component.text("* Unknown version", NamedTextColor.YELLOW); // Purpur
default:
- return Component.text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW)
+ return Component.text("* You are " + distance + " version(s) behind", NamedTextColor.YELLOW) // Purpur
.append(Component.newline())
.append(Component.text("Download the new version at: ")
.append(Component.text(DOWNLOAD_PAGE, NamedTextColor.GOLD)
@@ -85,15 +87,11 @@ public class PaperVersionFetcher implements VersionFetcher {
if (siteApiVersion == null) { return -1; }
try {
try (BufferedReader reader = Resources.asCharSource(
- new URL("https://api.papermc.io/v2/projects/paper/versions/" + siteApiVersion),
+ new URL("https://api.purpurmc.org/v2/purpur/" + siteApiVersion), // Purpur
Charsets.UTF_8
).openBufferedStream()) {
JsonObject json = new Gson().fromJson(reader, JsonObject.class);
- JsonArray builds = json.getAsJsonArray("builds");
- int latest = StreamSupport.stream(builds.spliterator(), false)
- .mapToInt(e -> e.getAsInt())
- .max()
- .getAsInt();
+ int latest = json.getAsJsonObject("builds").getAsJsonPrimitive("latest").getAsInt(); // Purpur
return latest - jenkinsBuild;
} catch (JsonSyntaxException ex) {
ex.printStackTrace();
@@ -144,6 +142,6 @@ public class PaperVersionFetcher implements VersionFetcher {
return null;
}
- return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC);
+ return org.bukkit.ChatColor.parseMM("<grey>Previous: %s", oldVersion); // Purpur
}
}
diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
index c5d5648f4ca603ef2b1df723b58f9caf4dd3c722..3cb56595822799926a8141e60a42f5d1edfc6de5 100644
--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
+++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
@@ -17,7 +17,7 @@ public final class PaperConsole extends SimpleTerminalConsole {
@Override
protected LineReader buildReader(LineReaderBuilder builder) {
builder
- .appName("Paper")
+ .appName("Purpur") // Purpur
.variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history"))
.completer(new ConsoleCommandCompleter(this.server))
.option(LineReader.Option.COMPLETE_IN_WORD, true);
diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
index a08c00b8c0488d18be5e182f7892e5ab71d12247..338f693d098b6ab507c30f6411c9a952c34ba8e3 100644
--- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
+++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
@@ -136,6 +136,10 @@ public class MobGoalHelper {
static {
// TODO these kinda should be checked on each release, in case obfuscation changes
deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee");
+ // Purpur start
+ deobfuscationMap.put("zombie_1", "zombie_attack_villager");
+ deobfuscationMap.put("drowned_1", "drowned_attack_villager");
+ // Purpur end
ignored.add("goal_selector_1");
ignored.add("goal_selector_2");
diff --git a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java
index fa56cd09102a89692b42f1d14257990508c5c720..f9251183df72ddc56662fd3f02acf21641a2200c 100644
--- a/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java
+++ b/src/main/java/com/destroystokyo/paper/gui/RAMDetails.java
@@ -58,7 +58,7 @@ public class RAMDetails extends JList<String> {
GraphData data = RAMGraph.DATA.peekLast();
Vector<String> vector = new Vector<>();
- double[] tps = new double[] {server.tps1.getAverage(), server.tps5.getAverage(), server.tps15.getAverage()};
+ double[] tps = new double[] {server.tps5s.getAverage(), server.tps1.getAverage(), server.tps5.getAverage(), server.tps15.getAverage()}; // Purpur
String[] tpsAvg = new String[tps.length];
for ( int g = 0; g < tps.length; g++) {
@@ -67,7 +67,7 @@ public class RAMDetails extends JList<String> {
vector.add("Memory use: " + (data.getUsedMem() / 1024L / 1024L) + " mb (" + (data.getFree() * 100L / data.getMax()) + "% free)");
vector.add("Heap: " + (data.getTotal() / 1024L / 1024L) + " / " + (data.getMax() / 1024L / 1024L) + " mb");
vector.add("Avg tick: " + DECIMAL_FORMAT.format(getAverage(server.tickTimes)) + " ms");
- vector.add("TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg));
+ vector.add("TPS from last 5s, 1m, 5m, 15m: " + String.join(", ", tpsAvg)); // Purpur
setListData(vector);
}
diff --git a/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
index 95d1a8a5b349f7849c040026bfa3469d03d92bfd..3e36958365bc136516bafbaad0c168f7956406f1 100644
--- a/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
+++ b/src/main/java/gg/pufferfish/pufferfish/PufferfishConfig.java
@@ -28,6 +28,7 @@ public class PufferfishConfig {
private static final YamlFile config = new YamlFile();
private static int updates = 0;
+ public static File pufferfishFile; // Purpur
private static ConfigurationSection convertToBukkit(org.simpleyaml.configuration.ConfigurationSection section) {
ConfigurationSection newSection = new MemoryConfiguration();
@@ -50,7 +51,7 @@ public class PufferfishConfig {
}
public static void load() throws IOException {
- File configFile = new File("pufferfish.yml");
+ File configFile = pufferfishFile; // Purpur
if (configFile.exists()) {
try {
@@ -224,7 +225,7 @@ public class PufferfishConfig {
public static int activationDistanceMod;
private static void dynamicActivationOfBrains() throws IOException {
- dearEnabled = getBoolean("dab.enabled", "activation-range.enabled", true);
+ dearEnabled = getBoolean("dab.enabled", "activation-range.enabled", false); // Purpur
startDistance = getInt("dab.start-distance", "activation-range.start-distance", 12,
"This value determines how far away an entity has to be",
"from the player to start being effected by DEAR.");
@@ -268,7 +269,7 @@ public class PufferfishConfig {
public static boolean throttleInactiveGoalSelectorTick;
private static void inactiveGoalSelectorThrottle() {
- throttleInactiveGoalSelectorTick = getBoolean("inactive-goal-selector-throttle", "inactive-goal-selector-disable", true,
+ throttleInactiveGoalSelectorTick = getBoolean("inactive-goal-selector-throttle", "inactive-goal-selector-disable", false, // Purpur
"Throttles the AI goal selector in entity inactive ticks.",
"This can improve performance by a few percent, but has minor gameplay implications.");
}
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
index abd0217cf0bff183c8e262edc173a53403797c1a..2519ad2884b6c09b312432b933c31476b369e599 100644
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
@@ -1315,9 +1315,9 @@ public final class ChunkHolderManager {
}
public boolean processTicketUpdates() {
- co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager
+ //co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager // Purpur
return this.processTicketUpdates(true, true, null);
- } finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Paper - add timings for distance manager
+ //} finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Paper - add timings for distance manager // Purpur
}
private static final ThreadLocal<List<ChunkProgressionTask>> CURRENT_TICKET_UPDATE_SCHEDULING = new ThreadLocal<>();
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java
index 51304c5cf4b0ac7646693ef97ef4a3847d3342b5..535ab99585cd4463d051334681bc80b5d20df7c0 100644
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/NewChunkHolder.java
@@ -1779,7 +1779,7 @@ public final class NewChunkHolder {
boolean canSavePOI = !(chunk instanceof LevelChunk levelChunk && levelChunk.mustNotSave) && (poi != null && poi.isDirty());
boolean canSaveEntities = entities != null;
- try (co.aikar.timings.Timing ignored = this.world.timings.chunkSave.startTiming()) { // Paper
+ //try (co.aikar.timings.Timing ignored = this.world.timings.chunkSave.startTiming()) { // Paper // Purpur
if (canSaveChunk) {
canSaveChunk = this.saveChunk(chunk, unloading);
}
@@ -1793,7 +1793,7 @@ public final class NewChunkHolder {
this.lastEntityUnload = null;
}
}
- }
+ //} // Purpur
return executedUnloadTask | canSaveChunk | canSaveEntities | canSavePOI ? new SaveStat(executedUnloadTask || canSaveChunk, canSaveEntities, canSavePOI): null;
}
diff --git a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java
index f0fce4113fb07c64adbec029d177c236cbdcbae8..e94224ed280247ee69dfdff8dc960f2b8729be33 100644
--- a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java
+++ b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java
@@ -78,10 +78,10 @@ public class PaperPluginsCommand extends BukkitCommand {
this.setAliases(Arrays.asList("pl"));
}
- private static <T> List<Component> formatProviders(TreeMap<String, PluginProvider<T>> plugins) {
+ private static <T> List<Component> formatProviders(TreeMap<String, PluginProvider<T>> plugins, @NotNull CommandSender sender) { // Purpur
List<Component> components = new ArrayList<>(plugins.size());
for (PluginProvider<T> entry : plugins.values()) {
- components.add(formatProvider(entry));
+ components.add(formatProvider(entry, sender)); // Purpur
}
boolean isFirst = true;
@@ -109,7 +109,7 @@ public class PaperPluginsCommand extends BukkitCommand {
return formattedSublists;
}
- private static Component formatProvider(PluginProvider<?> provider) {
+ private static Component formatProvider(PluginProvider<?> provider, @NotNull CommandSender sender) { // Purpur
TextComponent.Builder builder = Component.text();
if (provider instanceof SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) {
builder.append(LEGACY_PLUGIN_STAR);
@@ -117,12 +117,64 @@ public class PaperPluginsCommand extends BukkitCommand {
String name = provider.getMeta().getName();
Component pluginName = Component.text(name, fromStatus(provider))
- .clickEvent(ClickEvent.runCommand("/version " + name));
+ // Purpur start
+ .clickEvent(ClickEvent.suggestCommand("/version " + name));
+
+ if (sender instanceof org.bukkit.entity.Player && sender.hasPermission("bukkit.command.version")) {
+ // Event components
+ String description = provider.getMeta().getDescription();
+ TextComponent.Builder hover = Component.text();
+ hover.append(Component.text("Version: ", NamedTextColor.WHITE)).append(Component.text(provider.getMeta().getVersion(), NamedTextColor.GREEN));
+
+ if (description != null) {
+ hover.append(Component.newline())
+ .append(Component.text("Description: ", NamedTextColor.WHITE))
+ .append(Component.text(description, NamedTextColor.GREEN));
+ }
+
+ if (provider.getMeta().getWebsite() != null) {
+ hover.append(Component.newline())
+ .append(Component.text("Website: ", NamedTextColor.WHITE))
+ .append(Component.text(provider.getMeta().getWebsite(), NamedTextColor.GREEN));
+ }
+
+ if (!provider.getMeta().getAuthors().isEmpty()) {
+ hover.append(Component.newline());
+ if (provider.getMeta().getAuthors().size() == 1) {
+ hover.append(Component.text("Author: "));
+ } else {
+ hover.append(Component.text("Authors: "));
+ }
+
+ hover.append(getAuthors(provider.getMeta()));
+ }
+
+ pluginName.hoverEvent(hover.build());
+ }
builder.append(pluginName);
+ // Purpur end
+
+ return builder.build();
+ }
+
+ // Purpur start
+ @NotNull
+ private static TextComponent getAuthors(@NotNull final PluginMeta pluginMeta) {
+ TextComponent.Builder builder = Component.text();
+ List<String> authors = pluginMeta.getAuthors();
+
+ for (int i = 0; i < authors.size(); i++) {
+ if (i > 0) {
+ builder.append(Component.text(i < authors.size() - 1 ? ", " : " and ", NamedTextColor.WHITE));
+ }
+
+ builder.append(Component.text(authors.get(i), NamedTextColor.GREEN));
+ }
return builder.build();
}
+ // Purpur end
private static Component asPlainComponents(String strings) {
net.kyori.adventure.text.TextComponent.Builder builder = Component.text();
@@ -182,24 +234,24 @@ public class PaperPluginsCommand extends BukkitCommand {
}
}
- Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE);
+ //Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE);
//.append(INFO_ICON_START.hoverEvent(SERVER_PLUGIN_INFO)); TODO: Add docs
- sender.sendMessage(infoMessage);
+ //sender.sendMessage(infoMessage); // Purpur
if (!paperPlugins.isEmpty()) {
- sender.sendMessage(PAPER_HEADER);
+ sender.sendMessage(PAPER_HEADER.append(Component.text(" (%s):".formatted(paperPlugins.size())))); // Purpur
}
- for (Component component : formatProviders(paperPlugins)) {
+ for (Component component : formatProviders(paperPlugins, sender)) { // Purpur
sender.sendMessage(component);
}
if (!spigotPlugins.isEmpty()) {
- sender.sendMessage(BUKKIT_HEADER);
+ sender.sendMessage(BUKKIT_HEADER.append(Component.text(" (%s):".formatted(spigotPlugins.size())))); // Purpur
}
- for (Component component : formatProviders(spigotPlugins)) {
+ for (Component component : formatProviders(spigotPlugins, sender)) { // Purpur
sender.sendMessage(component);
}
diff --git a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java
index a8e813ca89b033f061e695288b3383bdcf128531..1ab65af9359d19530bba7f985a604d2a430ee234 100644
--- a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java
+++ b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java
@@ -54,9 +54,9 @@ public final class SysoutCatcher {
final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(clazz);
// Instead of just printing the message, send it to the plugin's logger
- plugin.getLogger().log(this.level, this.prefix + line);
+ plugin.getLogger().log(this.level, /*this.prefix +*/ line); // Purpur - prefix not needed
- if (SysoutCatcher.SUPPRESS_NAGS) {
+ if (true || SysoutCatcher.SUPPRESS_NAGS) { // Purpur - nagging is annoying
return;
}
if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) {
diff --git a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java
index 89bf48fd581ee6580b91e2eb31dd532cb622df5e..e35da199be67e04c34df6bc09afd8d8122cb0487 100644
--- a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java
+++ b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java
@@ -102,6 +102,7 @@ public class PluginInitializerManager {
java.util.List<File> files = (java.util.List<File>) optionSet.valuesOf("add-plugin");
// Register plugins from the flag
io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.PluginFlagProviderSource.INSTANCE, files);
+ io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.SparkProviderSource.INSTANCE, new File("cache", "spark.jar").toPath()); // Purpur
}
// This will be the end of me...
diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/SparkProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/SparkProviderSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7d1ae53eac94bc2dcf8bc78ef1da0d3b8554736
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/provider/source/SparkProviderSource.java
@@ -0,0 +1,102 @@
+package io.papermc.paper.plugin.provider.source;
+
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.plugin.entrypoint.Entrypoint;
+import io.papermc.paper.plugin.entrypoint.EntrypointHandler;
+import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler;
+import io.papermc.paper.plugin.provider.PluginProvider;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.math.BigInteger;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.security.MessageDigest;
+import java.util.stream.Collectors;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.slf4j.Logger;
+
+public class SparkProviderSource extends FileProviderSource {
+ public static final SparkProviderSource INSTANCE = new SparkProviderSource();
+
+ private static final Logger LOGGER = LogUtils.getClassLogger();
+
+ public SparkProviderSource() {
+ super("File '%s' specified by Purpur"::formatted);
+ }
+
+ @Override
+ public void registerProviders(EntrypointHandler entrypointHandler, Path context) throws Exception {
+ // first, check if user doesn't want spark at all
+ if (Boolean.getBoolean("Purpur.IReallyDontWantSpark")) {
+ return; // boo!
+ }
+
+ // second, check if user has their own spark
+ if (hasSpark()) {
+ LOGGER.info("Purpur: Using user-provided spark plugin instead of our own.");
+ return; // let's hope it's at least the modern version :3
+ }
+
+ // you can't have errors in your code if you wrap the entire codebase in a try/catch block
+ try {
+
+ // make sure the directory exists where we want to keep spark
+ File file = context.toFile();
+ file.getParentFile().mkdirs();
+
+ boolean shouldDownload;
+
+ // check if our spark exists
+ if (!file.exists()) {
+ // it does not, so let's download it
+ shouldDownload = true;
+ } else {
+ // we have a spark file, let's see if it's up-to-date by comparing shas
+ String fileSha1 = String.format("%040x", new BigInteger(1, MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(file.toPath()))));
+ String sparkSha1;
+
+ // luck has a nifty endpoint containing the sha of the newest version
+ URLConnection urlConnection = new URL("https://sparkapi.lucko.me/download/bukkit/sha1").openConnection();
+
+ // set a reasonable timeout to prevent servers without internet from hanging for 60+ seconds on startup
+ urlConnection.setReadTimeout(5000);
+ urlConnection.setConnectTimeout(5000);
+
+ // read it
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()))) {
+ sparkSha1 = reader.lines().collect(Collectors.joining(""));
+ }
+
+ // compare; we only download a new spark if the shas don't match
+ shouldDownload = !fileSha1.equals(sparkSha1);
+ }
+
+ // ok, finally we can download spark if we need it
+ if (shouldDownload) {
+ URLConnection urlConnection = new URL("https://sparkapi.lucko.me/download/bukkit").openConnection();
+ urlConnection.setReadTimeout(5000);
+ urlConnection.setConnectTimeout(5000);
+ Files.copy(urlConnection.getInputStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ // register the spark, newly downloaded or existing
+ super.registerProviders(entrypointHandler, context);
+
+ } catch (Throwable e) {
+ LOGGER.error("Purpur: Failed to download and install spark plugin", e);
+ }
+ }
+
+ private static boolean hasSpark() {
+ for (PluginProvider<JavaPlugin> provider : LaunchEntryPointHandler.INSTANCE.get(Entrypoint.PLUGIN).getRegisteredProviders()) {
+ if (provider.getMeta().getName().equalsIgnoreCase("spark")) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java
index abe37c7c3c6f5ab73afd738ec78f06d7e4d2ed96..b5b6657e52e4f7a630229bd3ba433438af293e22 100644
--- a/src/main/java/net/minecraft/CrashReport.java
+++ b/src/main/java/net/minecraft/CrashReport.java
@@ -123,6 +123,10 @@ public class CrashReport {
StringBuilder stringbuilder = new StringBuilder();
stringbuilder.append("---- Minecraft Crash Report ----\n");
+ // Purpur start
+ stringbuilder.append("// ");
+ stringbuilder.append("// DO NOT REPORT THIS TO PAPER! REPORT TO PURPUR INSTEAD!");
+ // Purpur end
stringbuilder.append("// ");
stringbuilder.append(CrashReport.getErrorComment());
stringbuilder.append("\n\n");
diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java
index 0de2eae2d448ac9e269a4edf48406d5ea8af8059..873659dcf8c8431f21bd2b23b8d7bdb4ea7e03e8 100644
--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java
+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java
@@ -227,6 +227,19 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy
}
// CraftBukkit end
+ // Purpur start
+ public boolean testPermission(int i, String bukkitPermission) {
+ if (hasPermission(i, bukkitPermission)) {
+ return true;
+ }
+ net.kyori.adventure.text.Component permissionMessage = getLevel().getServer().server.permissionMessage();
+ if (!permissionMessage.equals(net.kyori.adventure.text.Component.empty())) {
+ sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(permissionMessage.replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("<permission>").replacement(bukkitPermission).build())));
+ }
+ return false;
+ }
+ // Purpur end
+
public Vec3 getPosition() {
return this.worldPosition;
}
@@ -336,6 +349,30 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy
}
}
+ // Purpur start
+ public void sendSuccess(@Nullable String message) {
+ sendSuccess(message, false);
+ }
+
+ public void sendSuccess(@Nullable String message, boolean broadcastToOps) {
+ if (message == null) {
+ return;
+ }
+ sendSuccess(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), broadcastToOps);
+ }
+
+ public void sendSuccess(@Nullable net.kyori.adventure.text.Component message) {
+ sendSuccess(message, false);
+ }
+
+ public void sendSuccess(@Nullable net.kyori.adventure.text.Component message, boolean broadcastToOps) {
+ if (message == null) {
+ return;
+ }
+ sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(message), broadcastToOps);
+ }
+ // Purpur end
+
public void sendSuccess(Supplier<Component> feedbackSupplier, boolean broadcastToOps) {
boolean flag1 = this.source.acceptsSuccess() && !this.silent;
boolean flag2 = broadcastToOps && this.source.shouldInformAdmins() && !this.silent;
diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java
index 80c2c8d565f03ae0ea24fbdecdbe2bc5b9aa4b82..5c0085589b08f199c75ceeab8d0cf25e970a0533 100644
--- a/src/main/java/net/minecraft/commands/Commands.java
+++ b/src/main/java/net/minecraft/commands/Commands.java
@@ -150,7 +150,7 @@ public class Commands {
DamageCommand.register(this.dispatcher, commandRegistryAccess);
DataCommands.register(this.dispatcher);
DataPackCommand.register(this.dispatcher);
- DebugCommand.register(this.dispatcher);
+ //DebugCommand.register(this.dispatcher); // Purpur
DefaultGameModeCommands.register(this.dispatcher);
DifficultyCommand.register(this.dispatcher);
EffectCommands.register(this.dispatcher, commandRegistryAccess);
@@ -224,6 +224,14 @@ public class Commands {
SetPlayerIdleTimeoutCommand.register(this.dispatcher);
StopCommand.register(this.dispatcher);
WhitelistCommand.register(this.dispatcher);
+ org.purpurmc.purpur.command.CreditsCommand.register(this.dispatcher); // Purpur
+ org.purpurmc.purpur.command.DemoCommand.register(this.dispatcher); // Purpur
+ org.purpurmc.purpur.command.PingCommand.register(this.dispatcher); // Purpur
+ org.purpurmc.purpur.command.UptimeCommand.register(this.dispatcher); // Purpur
+ org.purpurmc.purpur.command.TPSBarCommand.register(this.dispatcher); // Purpur
+ org.purpurmc.purpur.command.CompassCommand.register(this.dispatcher); // Purpur
+ org.purpurmc.purpur.command.RamBarCommand.register(this.dispatcher); // Purpur
+ org.purpurmc.purpur.command.RamCommand.register(this.dispatcher); // Purpur
}
if (environment.includeIntegrated) {
@@ -311,9 +319,9 @@ public class Commands {
public int performCommand(ParseResults<CommandSourceStack> parseresults, String s, String label) { // CraftBukkit
CommandSourceStack commandlistenerwrapper = (CommandSourceStack) parseresults.getContext().getSource();
- commandlistenerwrapper.getServer().getProfiler().push(() -> {
+ /*commandlistenerwrapper.getServer().getProfiler().push(() -> { // Purpur
return "/" + s;
- });
+ });*/ // Purpur
byte b0;
@@ -396,7 +404,7 @@ public class Commands {
b0 = 0;
}
} finally {
- commandlistenerwrapper.getServer().getProfiler().pop();
+ //commandlistenerwrapper.getServer().getProfiler().pop(); // Purpur
}
return b0;
@@ -456,6 +464,7 @@ public class Commands {
private void runSync(ServerPlayer player, Collection<String> bukkit, RootCommandNode<SharedSuggestionProvider> rootcommandnode) {
// Paper end - Async command map building
new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent<CommandSourceStack>(player.getBukkitEntity(), (RootCommandNode) rootcommandnode, false).callEvent(); // Paper
+ if (PlayerCommandSendEvent.getHandlerList().getRegisteredListeners().length > 0) { // Purpur - skip all this crap if there's nothing listening
PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit));
event.getPlayer().getServer().getPluginManager().callEvent(event);
@@ -466,6 +475,7 @@ public class Commands {
}
}
// CraftBukkit end
+ } // Purpur - skip event
player.connection.send(new ClientboundCommandsPacket(rootcommandnode));
}
diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java
index f25b9330e068c7d9e12cb57a7761cfef9ebaf7bc..7e66aaa960ce7b6dda7c064d4c6856cc4b368b58 100644
--- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java
+++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java
@@ -200,10 +200,10 @@ public class EntitySelector {
if (this.playerName != null) {
entityplayer = source.getServer().getPlayerList().getPlayerByName(this.playerName);
- return (List) (entityplayer == null ? Collections.emptyList() : Lists.newArrayList(new ServerPlayer[]{entityplayer}));
+ return entityplayer == null || !canSee(source, entityplayer) ? Collections.emptyList() : Lists.newArrayList(entityplayer); // Purpur
} else if (this.entityUUID != null) {
entityplayer = source.getServer().getPlayerList().getPlayer(this.entityUUID);
- return (List) (entityplayer == null ? Collections.emptyList() : Lists.newArrayList(new ServerPlayer[]{entityplayer}));
+ return entityplayer == null || !canSee(source, entityplayer) ? Collections.emptyList() : Lists.newArrayList(entityplayer); // Purpur
} else {
Vec3 vec3d = (Vec3) this.position.apply(source.getPosition());
Predicate<Entity> predicate = this.getPredicate(vec3d);
@@ -213,7 +213,7 @@ public class EntitySelector {
ServerPlayer entityplayer1 = (ServerPlayer) source.getEntity();
if (predicate.test(entityplayer1)) {
- return Lists.newArrayList(new ServerPlayer[]{entityplayer1});
+ return !canSee(source, entityplayer1) ? Collections.emptyList() : Lists.newArrayList(entityplayer1); // Purpur
}
}
@@ -224,6 +224,7 @@ public class EntitySelector {
if (this.isWorldLimited()) {
object = source.getLevel().getPlayers(predicate, i);
+ ((List) object).removeIf(entityplayer3 -> !canSee(source, (ServerPlayer) entityplayer3)); // Purpur
} else {
object = Lists.newArrayList();
Iterator iterator = source.getServer().getPlayerList().getPlayers().iterator();
@@ -231,7 +232,7 @@ public class EntitySelector {
while (iterator.hasNext()) {
ServerPlayer entityplayer2 = (ServerPlayer) iterator.next();
- if (predicate.test(entityplayer2)) {
+ if (predicate.test(entityplayer2) && canSee(source, entityplayer2)) { // Purpur
((List) object).add(entityplayer2);
if (((List) object).size() >= i) {
return (List) object;
@@ -276,4 +277,10 @@ public class EntitySelector {
public static Component joinNames(List<? extends Entity> entities) {
return ComponentUtils.formatList(entities, Entity::getDisplayName);
}
+
+ // Purpur start
+ private boolean canSee(CommandSourceStack sender, ServerPlayer target) {
+ return !org.purpurmc.purpur.PurpurConfig.hideHiddenPlayersFromEntitySelector || !(sender.getEntity() instanceof ServerPlayer player) || player.getBukkitEntity().canSee(target.getBukkitEntity());
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
index 83cab746d1d6fe25c043c8aee28c39412b90c127..ec6b58dae525c81bbb1c0e2d96fbded6f00a45b5 100644
--- a/src/main/java/net/minecraft/core/BlockPos.java
+++ b/src/main/java/net/minecraft/core/BlockPos.java
@@ -48,6 +48,12 @@ public class BlockPos extends Vec3i {
private static final int X_OFFSET = 38;
// Paper end
+ // Purpur start
+ public BlockPos(net.minecraft.world.entity.Entity entity) {
+ super(entity.getBlockX(), entity.getBlockY(), entity.getBlockZ());
+ }
+ // Purpur end
+
public BlockPos(int x, int y, int z) {
super(x, y, z);
}
diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java
index d0a8092bf57a29ab7c00ec0ddf52a9fdb2a33267..defe5951938ce3a7b7f83017d4af36bb49ff5be0 100644
--- a/src/main/java/net/minecraft/core/Direction.java
+++ b/src/main/java/net/minecraft/core/Direction.java
@@ -238,6 +238,12 @@ public enum Direction implements StringRepresentable {
case EAST:
var10000 = SOUTH;
break;
+ // Purpur start
+ case UP:
+ return UP;
+ case DOWN:
+ return DOWN;
+ // Purpur end
default:
throw new IllegalStateException("Unable to get Y-rotated facing of " + this);
}
@@ -350,6 +356,12 @@ public enum Direction implements StringRepresentable {
case EAST:
var10000 = NORTH;
break;
+ // Purpur start
+ case UP:
+ return UP;
+ case DOWN:
+ return DOWN;
+ // Purpur end
default:
throw new IllegalStateException("Unable to get CCW facing of " + this);
}
diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
index 70aade6a8d36f8376cc567800258ea6fabb0607f..d44fde7e44cc862253fe577eb0753524ab7a39dc 100644
--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
@@ -1186,6 +1186,23 @@ public interface DispenseItemBehavior {
}
}
});
+ // Purpur start
+ DispenserBlock.registerBehavior(Items.ANVIL, (new OptionalDispenseItemBehavior() {
+ @Override
+ public ItemStack execute(BlockSource dispenser, ItemStack stack) {
+ Level level = dispenser.getLevel();
+ if (!level.purpurConfig.dispenserPlaceAnvils) return super.execute(dispenser, stack);
+ Direction facing = dispenser.getBlockState().getValue(DispenserBlock.FACING);
+ BlockPos pos = dispenser.getPos().relative(facing);
+ BlockState state = level.getBlockState(pos);
+ if (state.isAir()) {
+ level.setBlockAndUpdate(pos, Blocks.ANVIL.defaultBlockState().setValue(net.minecraft.world.level.block.AnvilBlock.FACING, facing.getAxis() == Direction.Axis.Y ? Direction.NORTH : facing.getClockWise()));
+ stack.shrink(1);
+ }
+ return stack;
+ }
+ }));
+ // Purpur end
}
static void setEntityPokingOutOfBlock(BlockSource pointer, Entity entity, Direction direction) {
diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
index 9b0049dfeaec9b688bf276f2ac2b18943b5696b2..d7563904232353cbf3b9255cedfb75920e35220c 100644
--- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
+++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
@@ -107,7 +107,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior {
continue;
}
// CraftBukkit end
- ishearable.shear(SoundSource.BLOCKS);
+ ishearable.shear(SoundSource.BLOCKS, net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MOB_LOOTING, CraftItemStack.asNMSCopy(craftItem))); // Purpur
worldserver.gameEvent((Entity) null, GameEvent.SHEAR, blockposition);
return true;
}
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
index cf20f0983fc25b26cf92b9d3a28746b1909fc56b..89c1b69ddeb420c2fbda5f588e7c9a467a76089d 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
@@ -579,11 +579,20 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper
private static int joinAttemptsThisTick; // Paper
private static int currTick; // Paper
+ private static int tickSecond; // Purpur
public void tick() {
this.flushQueue();
// Paper start
if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) {
Connection.currTick = net.minecraft.server.MinecraftServer.currentTick;
+ // Purpur start
+ if (org.purpurmc.purpur.PurpurConfig.maxJoinsPerSecond) {
+ if (++Connection.tickSecond > 20) {
+ Connection.tickSecond = 0;
+ Connection.joinAttemptsThisTick = 0;
+ }
+ } else
+ // Purpur end
Connection.joinAttemptsThisTick = 0;
}
// Paper end
diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java
index 9938bb90bef84cf784f9a1ceb02a1a45aa8b48a1..1f4b64a5f812376c499c98cb4be62469bd0b7dbe 100644
--- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java
+++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java
@@ -98,6 +98,8 @@ public class FriendlyByteBuf extends ByteBuf {
private static final int MAX_PUBLIC_KEY_LENGTH = 512;
private static final Gson GSON = new Gson();
+ public static boolean hasItemSerializeEvent = false; // Purpur
+
public FriendlyByteBuf(ByteBuf parent) {
this.source = parent;
}
@@ -679,6 +681,17 @@ public class FriendlyByteBuf extends ByteBuf {
this.writeBoolean(false);
} else {
this.writeBoolean(true);
+ // Purpur start
+ if (hasItemSerializeEvent) {
+ var event = new org.purpurmc.purpur.event.packet.NetworkItemSerializeEvent(stack.asBukkitCopy());
+ event.callEvent();
+ ItemStack newStack = ItemStack.fromBukkitCopy(event.getItemStack());
+ if (org.purpurmc.purpur.PurpurConfig.fixNetworkSerializedItemsInCreative && !ItemStack.matches(stack, newStack)) {
+ stack.save(newStack.getOrCreateTagElement("Purpur.OriginalItem"));
+ }
+ stack = newStack;
+ }
+ // Purpur end
Item item = stack.getItem();
this.writeId(BuiltInRegistries.ITEM, item);
diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
index d2f0a0755317f5fa9a1ccf7db346aa77fd287d80..03852e7d21d9470a4469676367463fefb38acdc6 100644
--- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java
+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
@@ -47,7 +47,7 @@ public class PacketUtils {
if (MinecraftServer.getServer().hasStopped() || (listener instanceof ServerGamePacketListenerImpl && ((ServerGamePacketListenerImpl) listener).processedDisconnect)) return; // CraftBukkit, MC-142590
if (listener.isAcceptingMessages()) {
co.aikar.timings.Timing timing = co.aikar.timings.MinecraftTimings.getPacketTiming(packet); // Paper - timings
- try (co.aikar.timings.Timing ignored = timing.startTiming()) { // Paper - timings
+ try { // Paper - timings // Purpur
packet.handle(listener);
} catch (Exception exception) {
if (listener.shouldPropagateHandlingExceptions()) {
diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java
index 9ec6145fe04ec64bbee8ec6a837719caebdbc6f5..358d610ad020cada1bb83e393deeeaaec05a2791 100644
--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java
+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java
@@ -5,7 +5,7 @@ import net.minecraft.network.protocol.Packet;
public class ClientboundSetTimePacket implements Packet<ClientGamePacketListener> {
private final long gameTime;
- private final long dayTime;
+ private long dayTime; public void setDayTime(long dayTime) { this.dayTime = dayTime; } // Purpur
public ClientboundSetTimePacket(long time, long timeOfDay, boolean doDaylightCycle) {
this.gameTime = time;
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 67cba5cf510e4a602121e10c015a491650127752..e6ddf69ade7ae51640569150f7db10d1afa63691 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -293,6 +293,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public org.bukkit.command.ConsoleCommandSender console;
//public ConsoleReader reader; // Paper
public static int currentTick = 0; // Paper - Further improve tick loop
+ public static final long startTimeMillis = System.currentTimeMillis();
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
public int autosavePeriod;
public Commands vanillaCommandDispatcher;
@@ -302,15 +303,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public static final int TPS = 20;
public static final int TICK_TIME = 1000000000 / MinecraftServer.TPS;
private static final int SAMPLE_INTERVAL = 20; // Paper
- public final double[] recentTps = new double[ 3 ];
+ public final double[] recentTps = new double[ 4 ]; // Purpur
// Spigot end
public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations;
public static long currentTickLong = 0L; // Paper
+ public boolean lagging = false; // Purpur
+ protected boolean upnp = false; // Purpur
public volatile Thread shutdownThread; // Paper
public volatile boolean abnormalExit = false; // Paper
public boolean isIteratingOverLevels = false; // Paper
-
+
public gg.pufferfish.pufferfish.util.AsyncExecutor mobSpawnExecutor = new gg.pufferfish.pufferfish.util.AsyncExecutor("MobSpawning"); // Pufferfish - optimize mob spawning
public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
@@ -337,13 +340,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public MinecraftServer(OptionSet options, WorldLoader.DataLoadContext worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, Proxy proxy, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
super("Server");
SERVER = this; // Paper - better singleton
- this.metricsRecorder = InactiveMetricsRecorder.INSTANCE;
- this.profiler = this.metricsRecorder.getProfiler();
- this.onMetricsRecordingStopped = (methodprofilerresults) -> {
+ //this.metricsRecorder = InactiveMetricsRecorder.INSTANCE; // Purpur
+ //this.profiler = this.metricsRecorder.getProfiler(); // Purpur
+ /*this.onMetricsRecordingStopped = (methodprofilerresults) -> { // Purpur
this.stopRecordingMetrics();
- };
- this.onMetricsRecordingFinished = (path) -> {
- };
+ };*/ // Purpur
+ //this.onMetricsRecordingFinished = (path) -> { // Purpur
+ //}; // Purpur
this.random = RandomSource.create();
this.port = -1;
this.levels = Maps.newLinkedHashMap();
@@ -922,7 +925,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
shutdownThread = Thread.currentThread();
org.spigotmc.WatchdogThread.doStop(); // Paper
if (!isSameThread()) {
- MinecraftServer.LOGGER.info("Stopping main thread (Ignore any thread death message you see! - DO NOT REPORT THREAD DEATH TO PAPER)");
+ MinecraftServer.LOGGER.info("Stopping main thread (Ignore any thread death message you see! - DO NOT REPORT THREAD DEATH TO PURPUR)"); // Purpur
while (this.getRunningThread().isAlive()) {
this.getRunningThread().stop();
try {
@@ -932,13 +935,21 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
// Paper end
// CraftBukkit end
- if (this.metricsRecorder.isRecording()) {
+ /*if (this.metricsRecorder.isRecording()) { // Purpur
this.cancelRecordingMetrics();
- }
+ }*/ // Purpur
MinecraftServer.LOGGER.info("Stopping server");
Commands.COMMAND_SENDING_POOL.shutdownNow(); // Paper - Shutdown and don't bother finishing
MinecraftTimings.stopServer(); // Paper
+ // Purpur start
+ if (upnp) {
+ if (dev.omega24.upnp4j.UPnP4J.close(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) {
+ LOGGER.info("[UPnP] Port {} closed", this.getPort());
+ } else {
+ LOGGER.error("[UPnP] Failed to close port {}", this.getPort());
+ }
+ }
// CraftBukkit start
if (this.server != null) {
this.server.disablePlugins();
@@ -1021,6 +1032,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.safeShutdown(waitForShutdown, false);
}
public void safeShutdown(boolean waitForShutdown, boolean isRestarting) {
+ org.purpurmc.purpur.task.BossBarTask.stopAll(); // Purpur
+ org.purpurmc.purpur.task.BeehiveTask.instance().unregister(); // Purpur
this.isRestarting = isRestarting;
this.hasLoggedStop = true; // Paper
if (isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper
@@ -1047,6 +1060,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
private static final long MAX_CATCHUP_BUFFER = TICK_TIME * TPS * 60L;
private long lastTick = 0;
private long catchupTime = 0;
+ public final RollingAverage tps5s = new RollingAverage(5); // Purpur
public final RollingAverage tps1 = new RollingAverage(60);
public final RollingAverage tps5 = new RollingAverage(60 * 5);
public final RollingAverage tps15 = new RollingAverage(60 * 15);
@@ -1147,35 +1161,46 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
{
final long diff = curTime - tickSection;
java.math.BigDecimal currentTps = TPS_BASE.divide(new java.math.BigDecimal(diff), 30, java.math.RoundingMode.HALF_UP);
+ tps5s.add(currentTps, diff); // Purpur
tps1.add(currentTps, diff);
tps5.add(currentTps, diff);
tps15.add(currentTps, diff);
// Backwards compat with bad plugins
- this.recentTps[0] = tps1.getAverage();
- this.recentTps[1] = tps5.getAverage();
- this.recentTps[2] = tps15.getAverage();
+ // Purpur start
+ this.recentTps[0] = tps5s.getAverage();
+ this.recentTps[1] = tps1.getAverage();
+ this.recentTps[2] = tps5.getAverage();
+ this.recentTps[3] = tps15.getAverage();
+ // Purpur end
// Paper end
+ lagging = recentTps[0] < org.purpurmc.purpur.PurpurConfig.laggingThreshold; // Purpur
tickSection = curTime;
}
// Spigot end
- if (this.debugCommandProfilerDelayStart) {
+ /*if (this.debugCommandProfilerDelayStart) { // Purpur
this.debugCommandProfilerDelayStart = false;
this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount);
- }
+ }*/ // Purpur
//MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time
lastTick = curTime;
this.nextTickTime += 50L;
- this.startMetricsRecordingTick();
- this.profiler.push("tick");
+ //this.startMetricsRecordingTick(); // Purpur
+ //this.profiler.push("tick"); // Purpur
this.tickServer(this::haveTime);
- this.profiler.popPush("nextTickWait");
+ //this.profiler.popPush("nextTickWait"); // Purpur
this.mayHaveDelayedTasks = true;
- this.delayedTasksMaxNextTickTime = Math.max(Util.getMillis() + 50L, this.nextTickTime);
+ // Purpur start - tps catchup
+ if (org.purpurmc.purpur.PurpurConfig.tpsCatchup) {
+ this.delayedTasksMaxNextTickTime = Math.max(Util.getMillis() + 50L, this.nextTickTime);
+ } else {
+ this.delayedTasksMaxNextTickTime = this.nextTickTime = curTime / 1000000L + 50L;
+ }
+ // Purpur end - tps catchup
this.waitUntilNextTick();
- this.profiler.pop();
- this.endMetricsRecordingTick();
+ //this.profiler.pop(); // Purpur
+ //this.endMetricsRecordingTick(); // Purpur
this.isReady = true;
JvmProfiler.INSTANCE.onServerTick(this.averageTickTime);
}
@@ -1336,7 +1361,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
public void doRunTask(TickTask ticktask) { // CraftBukkit - decompile error
- this.getProfiler().incrementCounter("runTask");
+ //this.getProfiler().incrementCounter("runTask"); // Purpur
super.doRunTask(ticktask);
}
@@ -1379,15 +1404,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public void onServerExit() {}
public void tickServer(BooleanSupplier shouldKeepTicking) {
- co.aikar.timings.TimingsManager.FULL_SERVER_TICK.startTiming(); // Paper
+ //co.aikar.timings.TimingsManager.FULL_SERVER_TICK.startTiming(); // Paper // Purpur
long i = Util.getNanos();
// Paper start - move oversleep into full server tick
- isOversleep = true;MinecraftTimings.serverOversleep.startTiming();
+ //isOversleep = true;MinecraftTimings.serverOversleep.startTiming(); // Purpur
this.managedBlock(() -> {
return !this.canOversleep();
});
- isOversleep = false;MinecraftTimings.serverOversleep.stopTiming();
+ //isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); // Purpur
// Paper end
new com.destroystokyo.paper.event.server.ServerTickStartEvent(this.tickCount+1).callEvent(); // Paper
@@ -1403,7 +1428,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
if (playerSaveInterval < 0) {
playerSaveInterval = autosavePeriod;
}
- this.profiler.push("save");
+ //this.profiler.push("save"); // Purpur
final boolean fullSave = autosavePeriod > 0 && this.tickCount % autosavePeriod == 0;
try {
this.isSaving = true;
@@ -1418,20 +1443,20 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
} finally {
this.isSaving = false;
}
- this.profiler.pop();
+ //this.profiler.pop(); // Purpur
// Paper end
io.papermc.paper.util.CachedLists.reset(); // Paper
// Paper start - move executeAll() into full server tick timing
- try (co.aikar.timings.Timing ignored = MinecraftTimings.processTasksTimer.startTiming()) {
+ //try (co.aikar.timings.Timing ignored = MinecraftTimings.processTasksTimer.startTiming()) { // Purpur
this.runAllTasks();
- }
+ //} // Purpur
// Paper end
// Paper start
long endTime = System.nanoTime();
long remaining = (TICK_TIME - (endTime - lastTick)) - catchupTime;
new com.destroystokyo.paper.event.server.ServerTickEndEvent(this.tickCount, ((double)(endTime - lastTick) / 1000000D), remaining).callEvent();
// Paper end
- this.profiler.push("tallying");
+ //this.profiler.push("tallying"); // Purpur
long j = this.tickTimes[this.tickCount % 100] = Util.getNanos() - i;
this.averageTickTime = this.averageTickTime * 0.8F + (float) j / 1000000.0F * 0.19999999F;
@@ -1443,9 +1468,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
tickTimes60s.add(this.tickCount, j);
// Paper end
this.frameTimer.logFrameDuration(k - i);
- this.profiler.pop();
+ //this.profiler.pop(); // Purpur
org.spigotmc.WatchdogThread.tick(); // Spigot
- co.aikar.timings.TimingsManager.FULL_SERVER_TICK.stopTiming(); // Paper
+ //co.aikar.timings.TimingsManager.FULL_SERVER_TICK.stopTiming(); // Paper // Purpur
}
private ServerStatus buildServerStatus() {
@@ -1477,9 +1502,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
public void tickChildren(BooleanSupplier shouldKeepTicking) {
- MinecraftTimings.bukkitSchedulerTimer.startTiming(); // Spigot // Paper
+ //MinecraftTimings.bukkitSchedulerTimer.startTiming(); // Spigot // Paper // Purpur
this.server.getScheduler().mainThreadHeartbeat(this.tickCount); // CraftBukkit
- MinecraftTimings.bukkitSchedulerTimer.stopTiming(); // Spigot // Paper
+ //MinecraftTimings.bukkitSchedulerTimer.stopTiming(); // Spigot // Paper // Purpur
// Paper start - Folia scheduler API
((io.papermc.paper.threadedregions.scheduler.FoliaGlobalRegionScheduler) Bukkit.getGlobalRegionScheduler()).tick();
getAllLevels().forEach(level -> {
@@ -1495,22 +1520,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
});
// Paper end - Folia scheduler API
io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper
- this.profiler.push("commandFunctions");
- MinecraftTimings.commandFunctionsTimer.startTiming(); // Spigot // Paper
+ //this.profiler.push("commandFunctions"); // Purpur
+ //MinecraftTimings.commandFunctionsTimer.startTiming(); // Spigot // Paper // Purpur
this.getFunctions().tick();
- MinecraftTimings.commandFunctionsTimer.stopTiming(); // Spigot // Paper
- this.profiler.popPush("levels");
+ //MinecraftTimings.commandFunctionsTimer.stopTiming(); // Spigot // Paper // Purpur
+ //this.profiler.popPush("levels"); // Purpur
//Iterator iterator = this.getAllLevels().iterator(); // Paper - moved down
// CraftBukkit start
// Run tasks that are waiting on processing
- MinecraftTimings.processQueueTimer.startTiming(); // Spigot
+ //MinecraftTimings.processQueueTimer.startTiming(); // Spigot // Purpur
while (!this.processQueue.isEmpty()) {
this.processQueue.remove().run();
}
- MinecraftTimings.processQueueTimer.stopTiming(); // Spigot
+ //MinecraftTimings.processQueueTimer.stopTiming(); // Spigot // Purpur
- MinecraftTimings.timeUpdateTimer.startTiming(); // Spigot // Paper
+ //MinecraftTimings.timeUpdateTimer.startTiming(); // Spigot // Paper // Purpur
// Send time updates to everyone, it will get the right time from the world the player is in.
// Paper start - optimize time updates
for (final ServerLevel level : this.getAllLevels()) {
@@ -1519,7 +1544,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
long worldTime = level.getGameTime();
final ClientboundSetTimePacket worldPacket = new ClientboundSetTimePacket(worldTime, dayTime, doDaylight);
for (Player entityhuman : level.players()) {
- if (!(entityhuman instanceof ServerPlayer) || (tickCount + entityhuman.getId()) % 20 != 0) {
+ if (!(entityhuman instanceof ServerPlayer) || (!level.isForceTime() && (tickCount + entityhuman.getId()) % 20 != 0)) { // Purpur
continue;
}
ServerPlayer entityplayer = (ServerPlayer) entityhuman;
@@ -1530,38 +1555,40 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
// Paper end
- MinecraftTimings.timeUpdateTimer.stopTiming(); // Spigot // Paper
+ //MinecraftTimings.timeUpdateTimer.stopTiming(); // Spigot // Paper // Purpur
this.isIteratingOverLevels = true; // Paper
+ net.minecraft.network.FriendlyByteBuf.hasItemSerializeEvent = org.purpurmc.purpur.event.packet.NetworkItemSerializeEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur
Iterator iterator = this.getAllLevels().iterator(); // Paper - move down
while (iterator.hasNext()) {
ServerLevel worldserver = (ServerLevel) iterator.next();
worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
+ worldserver.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur
- this.profiler.push(() -> {
+ /*this.profiler.push(() -> { // Purpur
return worldserver + " " + worldserver.dimension().location();
- });
+ });*/ // Purpur
/* Drop global time updates
if (this.tickCount % 20 == 0) {
- this.profiler.push("timeSync");
+ //this.profiler.push("timeSync"); // Purpur
this.synchronizeTime(worldserver);
- this.profiler.pop();
+ //this.profiler.pop(); // Purpur
}
// CraftBukkit end */
- this.profiler.push("tick");
+ //this.profiler.push("tick"); // Purpur
try {
- worldserver.timings.doTick.startTiming(); // Spigot
+ //worldserver.timings.doTick.startTiming(); // Spigot // Purpur
worldserver.tick(shouldKeepTicking);
// Paper start
for (final io.papermc.paper.chunk.SingleThreadChunkRegionManager regionManager : worldserver.getChunkSource().chunkMap.regionManagers) {
regionManager.recalculateRegions();
}
// Paper end
- worldserver.timings.doTick.stopTiming(); // Spigot
+ //worldserver.timings.doTick.stopTiming(); // Spigot // Purpur
} catch (Throwable throwable) {
// Spigot Start
CrashReport crashreport;
@@ -1577,33 +1604,33 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
throw new ReportedException(crashreport);
}
- this.profiler.pop();
- this.profiler.pop();
+ //this.profiler.pop(); // Purpur
+ //this.profiler.pop(); // Purpur
worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions
}
this.isIteratingOverLevels = false; // Paper
- this.profiler.popPush("connection");
- MinecraftTimings.connectionTimer.startTiming(); // Spigot
+ //this.profiler.popPush("connection"); // Purpur
+ //MinecraftTimings.connectionTimer.startTiming(); // Spigot // Purpur
this.getConnection().tick();
- MinecraftTimings.connectionTimer.stopTiming(); // Spigot
- this.profiler.popPush("players");
- MinecraftTimings.playerListTimer.startTiming(); // Spigot // Paper
+ //MinecraftTimings.connectionTimer.stopTiming(); // Spigot // Purpur
+ //this.profiler.popPush("players"); // Purpur
+ //MinecraftTimings.playerListTimer.startTiming(); // Spigot // Paper // Purpur
this.playerList.tick();
- MinecraftTimings.playerListTimer.stopTiming(); // Spigot // Paper
+ //MinecraftTimings.playerListTimer.stopTiming(); // Spigot // Paper // Purpur
if (SharedConstants.IS_RUNNING_IN_IDE) {
GameTestTicker.SINGLETON.tick();
}
- this.profiler.popPush("server gui refresh");
+ //this.profiler.popPush("server gui refresh"); // Purpur
- MinecraftTimings.tickablesTimer.startTiming(); // Spigot // Paper
+ //MinecraftTimings.tickablesTimer.startTiming(); // Spigot // Paper // Purpur
for (int i = 0; i < this.tickables.size(); ++i) {
((Runnable) this.tickables.get(i)).run();
}
- MinecraftTimings.tickablesTimer.stopTiming(); // Spigot // Paper
+ //MinecraftTimings.tickablesTimer.stopTiming(); // Spigot // Paper // Purpur
- this.profiler.pop();
+ //this.profiler.pop(); // Purpur
}
private void synchronizeTime(ServerLevel world) {
@@ -1611,7 +1638,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
public void forceTimeSynchronization() {
- this.profiler.push("timeSync");
+ //this.profiler.push("timeSync"); // Purpur
Iterator iterator = this.getAllLevels().iterator();
while (iterator.hasNext()) {
@@ -1620,7 +1647,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.synchronizeTime(worldserver);
}
- this.profiler.pop();
+ //this.profiler.pop(); // Purpur
}
public boolean isNetherEnabled() {
@@ -1694,7 +1721,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@DontObfuscate
public String getServerModName() {
- return "Pufferfish"; // Pufferfish - Pufferfish > // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
+ return org.purpurmc.purpur.PurpurConfig.serverModName; // Purpur - Purpur > // Pufferfish - Pufferfish > // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
}
public SystemReport fillSystemReport(SystemReport details) {
@@ -2273,7 +2300,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
public ProfilerFiller getProfiler() {
- if (gg.pufferfish.pufferfish.PufferfishConfig.disableMethodProfiler) return net.minecraft.util.profiling.InactiveProfiler.INSTANCE;
+ if (true || gg.pufferfish.pufferfish.PufferfishConfig.disableMethodProfiler) return net.minecraft.util.profiling.InactiveProfiler.INSTANCE; // Pufferfish // Purpur
return this.profiler;
}
@@ -2513,7 +2540,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// CraftBukkit end
private void startMetricsRecordingTick() {
- if (this.willStartRecordingMetrics) {
+ if (false && this.willStartRecordingMetrics) { // Purpur
this.metricsRecorder = ActiveMetricsRecorder.createStarted(new ServerMetricsSamplersProvider(Util.timeSource, this.isDedicatedServer()), Util.timeSource, Util.ioPool(), new MetricsPersister("server"), this.onMetricsRecordingStopped, (path) -> {
this.executeBlocking(() -> {
this.saveDebugReport(path.resolve("server"));
@@ -2523,40 +2550,40 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.willStartRecordingMetrics = false;
}
- this.profiler = SingleTickProfiler.decorateFiller(this.metricsRecorder.getProfiler(), SingleTickProfiler.createTickProfiler("Server"));
- this.metricsRecorder.startTick();
- this.profiler.startTick();
+ //this.profiler = SingleTickProfiler.decorateFiller(this.metricsRecorder.getProfiler(), SingleTickProfiler.createTickProfiler("Server")); // Purpur
+ //this.metricsRecorder.startTick(); // Purpur
+ //this.profiler.startTick(); // Purpur
}
private void endMetricsRecordingTick() {
- this.profiler.endTick();
- this.metricsRecorder.endTick();
+ //this.profiler.endTick(); // Purpur
+ //this.metricsRecorder.endTick(); // Purpur
}
public boolean isRecordingMetrics() {
- return this.metricsRecorder.isRecording();
+ return false; //this.metricsRecorder.isRecording(); // Purpur
}
public void startRecordingMetrics(Consumer<ProfileResults> resultConsumer, Consumer<Path> dumpConsumer) {
- this.onMetricsRecordingStopped = (methodprofilerresults) -> {
+ /*this.onMetricsRecordingStopped = (methodprofilerresults) -> { // Purpur
this.stopRecordingMetrics();
resultConsumer.accept(methodprofilerresults);
};
this.onMetricsRecordingFinished = dumpConsumer;
- this.willStartRecordingMetrics = true;
+ this.willStartRecordingMetrics = true;*/ // Purpur
}
public void stopRecordingMetrics() {
- this.metricsRecorder = InactiveMetricsRecorder.INSTANCE;
+ //this.metricsRecorder = InactiveMetricsRecorder.INSTANCE; // Purpur
}
public void finishRecordingMetrics() {
- this.metricsRecorder.end();
+ //this.metricsRecorder.end(); // Purpur
}
public void cancelRecordingMetrics() {
- this.metricsRecorder.cancel();
- this.profiler = this.metricsRecorder.getProfiler();
+ //this.metricsRecorder.cancel(); // Purpur
+ //this.profiler = this.metricsRecorder.getProfiler(); // Purpur
}
public Path getWorldPath(LevelResource worldSavePath) {
@@ -2605,15 +2632,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
public boolean isTimeProfilerRunning() {
- return this.debugCommandProfilerDelayStart || this.debugCommandProfiler != null;
+ return false; //this.debugCommandProfilerDelayStart || this.debugCommandProfiler != null; // Purpur
}
public void startTimeProfiler() {
- this.debugCommandProfilerDelayStart = true;
+ //this.debugCommandProfilerDelayStart = true; // Purpur
}
public ProfileResults stopTimeProfiler() {
- if (this.debugCommandProfiler == null) {
+ if (true || this.debugCommandProfiler == null) { // Purpur
return EmptyProfileResults.EMPTY;
} else {
ProfileResults methodprofilerresults = this.debugCommandProfiler.stop(Util.getNanos(), this.tickCount);
@@ -2645,6 +2672,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Chat Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper
public ChatDecorator getChatDecorator() {
+ // Purpur start
+ return this.chatDecorator;
+ }
+ public void setChatDecorator(ChatDecorator chatDecorator) {
+ this.chatDecorator = chatDecorator;
+ }
+ private ChatDecorator chatDecorator = getPaperHardcodedChatDecorator();
+ public ChatDecorator getPaperHardcodedChatDecorator() {
+ // Purpur end
// Paper start - moved to ChatPreviewProcessor
return ChatDecorator.create((sender, commandSourceStack, message) -> {
final io.papermc.paper.adventure.ChatDecorationProcessor processor = new io.papermc.paper.adventure.ChatDecorationProcessor(this, sender, commandSourceStack, message);
@@ -2779,7 +2815,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
return;
}
- co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming();
+ //co.aikar.timings.MinecraftTimings.midTickChunkTasks.startTiming(); // Purpur
try {
for (;;) {
boolean moreTasks = this.tickMidTickTasks();
@@ -2806,7 +2842,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
} finally {
- co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming();
+ //co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming(); // Purpur
}
}
// Paper end - execute chunk tasks mid tick
diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java
index acc49f66bf34e2507d0ee6fec0a56b11bfc68f46..69d349613f01ab0ac1890734657da52984704f29 100644
--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java
+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java
@@ -147,6 +147,7 @@ public class PlayerAdvancements {
if (advancement == null) {
// CraftBukkit start
if (entry.getKey().getNamespace().equals("minecraft")) {
+ if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressIgnoredAdvancementWarnings) // Purpur
PlayerAdvancements.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", entry.getKey(), this.playerSavePath);
}
// CraftBukkit end
@@ -249,6 +250,7 @@ public class PlayerAdvancements {
advancement.getRewards().grant(this.player);
// Paper start - Add Adventure message to PlayerAdvancementDoneEvent
if (message != null && this.player.level().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
+ if (org.purpurmc.purpur.PurpurConfig.advancementOnlyBroadcastToAffectedPlayer) this.player.sendMessage(message); else // Purpur
this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), false);
// Paper end
}
diff --git a/src/main/java/net/minecraft/server/ServerFunctionManager.java b/src/main/java/net/minecraft/server/ServerFunctionManager.java
index 4a610b712acfe0b1b4dcf4988bc22c6a71616050..426ac5faa9344472560fb20bdea8bdc314a97f97 100644
--- a/src/main/java/net/minecraft/server/ServerFunctionManager.java
+++ b/src/main/java/net/minecraft/server/ServerFunctionManager.java
@@ -58,10 +58,10 @@ public class ServerFunctionManager {
}
private void executeTagFunctions(Collection<CommandFunction> functions, ResourceLocation label) {
- ProfilerFiller gameprofilerfiller = this.server.getProfiler();
+ //ProfilerFiller gameprofilerfiller = this.server.getProfiler(); // Purpur
Objects.requireNonNull(label);
- gameprofilerfiller.push(label::toString);
+ //gameprofilerfiller.push(label::toString); // Purpur
Iterator iterator = functions.iterator();
while (iterator.hasNext()) {
@@ -70,7 +70,7 @@ public class ServerFunctionManager {
this.execute(customfunction, this.getGameLoopSender());
}
- this.server.getProfiler().pop();
+ //this.server.getProfiler().pop(); // Purpur
}
public int execute(CommandFunction function, CommandSourceStack source) {
@@ -89,7 +89,7 @@ public class ServerFunctionManager {
} else {
int i;
- try (co.aikar.timings.Timing timing = function.getTiming().startTiming()) { // Paper
+ try /*(co.aikar.timings.Timing timing = function.getTiming().startTiming())*/ { // Paper // Purpur
this.context = new ServerFunctionManager.ExecutionContext(tracer);
i = this.context.runTopCommand(function, source);
} finally {
@@ -187,10 +187,10 @@ public class ServerFunctionManager {
try {
ServerFunctionManager.QueuedCommand customfunctiondata_queuedcommand = (ServerFunctionManager.QueuedCommand) this.commandQueue.removeFirst();
- ProfilerFiller gameprofilerfiller = ServerFunctionManager.this.server.getProfiler();
+ //ProfilerFiller gameprofilerfiller = ServerFunctionManager.this.server.getProfiler(); // Purpur
Objects.requireNonNull(customfunctiondata_queuedcommand);
- gameprofilerfiller.push(customfunctiondata_queuedcommand::toString);
+ //gameprofilerfiller.push(customfunctiondata_queuedcommand::toString); // Purpur
this.depth = customfunctiondata_queuedcommand.depth;
customfunctiondata_queuedcommand.execute(ServerFunctionManager.this, this.commandQueue, i, this.tracer);
if (this.abortCurrentDepth) {
@@ -209,7 +209,7 @@ public class ServerFunctionManager {
this.nestedCalls.clear();
} finally {
- ServerFunctionManager.this.server.getProfiler().pop();
+ //ServerFunctionManager.this.server.getProfiler().pop(); // Purpur
}
++j;
diff --git a/src/main/java/net/minecraft/server/commands/EnchantCommand.java b/src/main/java/net/minecraft/server/commands/EnchantCommand.java
index 664cbce2e06fcb95d3d3d6c5302fc9119f938925..bc9778c705d23acd84fa1cdeff6b403b4cda3686 100644
--- a/src/main/java/net/minecraft/server/commands/EnchantCommand.java
+++ b/src/main/java/net/minecraft/server/commands/EnchantCommand.java
@@ -48,7 +48,7 @@ public class EnchantCommand {
private static int enchant(CommandSourceStack source, Collection<? extends Entity> targets, Holder<Enchantment> enchantment, int level) throws CommandSyntaxException {
Enchantment enchantment2 = enchantment.value();
- if (level > enchantment2.getMaxLevel()) {
+ if (!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && level > enchantment2.getMaxLevel()) { // Purpur
throw ERROR_LEVEL_TOO_HIGH.create(level, enchantment2.getMaxLevel());
} else {
int i = 0;
@@ -58,7 +58,7 @@ public class EnchantCommand {
LivingEntity livingEntity = (LivingEntity)entity;
ItemStack itemStack = livingEntity.getMainHandItem();
if (!itemStack.isEmpty()) {
- if (enchantment2.canEnchant(itemStack) && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantments(itemStack).keySet(), enchantment2)) {
+ if ((enchantment2.canEnchant(itemStack) && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantments(itemStack).keySet(), enchantment2)) || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && !itemStack.hasEnchantment(enchantment2))) { // Purpur
itemStack.enchant(enchantment2, level);
++i;
} else if (targets.size() == 1) {
diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java
index 5cb15e2209d7b315904a1fc6d650ce1e75584271..7e21db60f3ace2a19686d6ea04b994ec3a793ec8 100644
--- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java
+++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java
@@ -45,6 +45,18 @@ public class GameModeCommand {
}
private static int setMode(CommandContext<CommandSourceStack> context, Collection<ServerPlayer> targets, GameType gameMode) {
+ // Purpur start
+ if (org.purpurmc.purpur.PurpurConfig.commandGamemodeRequiresPermission) {
+ String gamemode = gameMode.getName();
+ CommandSourceStack sender = context.getSource();
+ if (!sender.testPermission(2, "minecraft.command.gamemode." + gamemode)) {
+ return 0;
+ }
+ if (sender.getEntity() instanceof ServerPlayer player && (targets.size() > 1 || !targets.contains(player)) && !sender.testPermission(2, "minecraft.command.gamemode." + gamemode + ".other")) {
+ return 0;
+ }
+ }
+ // Purpur end
int i = 0;
for(ServerPlayer serverPlayer : targets) {
diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java
index d601d287e94a59ff93b8a83a44dac02544d211df..0ff3b06a98b2f4514b2d861b92dd70fe678ae86c 100644
--- a/src/main/java/net/minecraft/server/commands/GiveCommand.java
+++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java
@@ -59,6 +59,7 @@ public class GiveCommand {
boolean flag = entityplayer.getInventory().add(itemstack1);
ItemEntity entityitem;
+ if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) continue; // Purpur - add config option for toggling give command dropping
if (flag && itemstack1.isEmpty()) {
itemstack1.setCount(1);
entityitem = entityplayer.drop(itemstack1, false, false, false); // SPIGOT-2942: Add boolean to call event
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index 10501842887c1ffdb6bb667480682b7174fd89c6..2a70abb9e0af502885593df1e732887cd9d2ce4d 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -99,6 +99,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
return;
}
// Paper start - Use TerminalConsoleAppender
+ if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - has no GUI or has console (did not double-click)
new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start();
/*
jline.console.ConsoleReader bufferedreader = reader;
@@ -218,9 +219,19 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
thread.start(); // Paper - start console thread after MinecraftServer.console & PaperConfig are initialized
io.papermc.paper.command.PaperCommands.registerCommands(this);
com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics();
+ // Purpur start
+ try {
+ org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings"));
+ } catch (Exception e) {
+ DedicatedServer.LOGGER.error("Unable to load server configuration", e);
+ return false;
+ }
+ org.purpurmc.purpur.PurpurConfig.registerCommands();
+ // Purpur end
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now
io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider
// Paper end
+ gg.pufferfish.pufferfish.PufferfishConfig.pufferfishFile = (java.io.File) options.valueOf("pufferfish-settings"); // Purpur
gg.pufferfish.pufferfish.PufferfishConfig.load(); // Pufferfish
gg.pufferfish.pufferfish.PufferfishCommand.init(); // Pufferfish
@@ -269,6 +280,30 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
DedicatedServer.LOGGER.warn("Perhaps a server is already running on that port?");
return false;
}
+ // Purpur start
+ if (org.purpurmc.purpur.PurpurConfig.useUPnP) {
+ LOGGER.info("[UPnP] Attempting to start UPnP port forwarding service...");
+ if (dev.omega24.upnp4j.UPnP4J.isUPnPAvailable()) {
+ if (dev.omega24.upnp4j.UPnP4J.isOpen(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) {
+ this.upnp = false;
+ LOGGER.info("[UPnP] Port {} is already open", this.getPort());
+ } else if (dev.omega24.upnp4j.UPnP4J.open(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) {
+ this.upnp = true;
+ LOGGER.info("[UPnP] Successfully opened port {}", this.getPort());
+ } else {
+ this.upnp = false;
+ LOGGER.info("[UPnP] Failed to open port {}", this.getPort());
+ }
+
+ if (upnp) {
+ LOGGER.info("[UPnP] {}:{}", dev.omega24.upnp4j.UPnP4J.getExternalIP(), this.getPort());
+ }
+ } else {
+ this.upnp = false;
+ LOGGER.error("[UPnP] Service is unavailable");
+ }
+ }
+ // Purpur end
// CraftBukkit start
// this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up
@@ -341,6 +376,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
}
if (gg.pufferfish.pufferfish.PufferfishConfig.enableAsyncMobSpawning) mobSpawnExecutor.start(); // Pufferfish
+ org.purpurmc.purpur.task.BossBarTask.startAll(); // Purpur
+ org.purpurmc.purpur.task.BeehiveTask.instance().register(); // Purpur
return true;
}
}
@@ -487,7 +524,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
}
public void handleConsoleInputs() {
- MinecraftTimings.serverCommandTimer.startTiming(); // Spigot
+ //MinecraftTimings.serverCommandTimer.startTiming(); // Spigot // Purpur
// Paper start - use proper queue
ConsoleInput servercommand;
while ((servercommand = this.serverCommandQueue.poll()) != null) {
@@ -504,7 +541,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
// CraftBukkit end
}
- MinecraftTimings.serverCommandTimer.stopTiming(); // Spigot
+ //MinecraftTimings.serverCommandTimer.stopTiming(); // Spigot // Purpur
}
@Override
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java
index 818289e831e3dad29345c43265e2efd7689bc500..1ea3012995c738c67b31e997c138f824f9e69ba1 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java
@@ -58,6 +58,7 @@ public class DedicatedServerProperties extends Settings<DedicatedServerPropertie
public final boolean onlineMode = this.get("online-mode", true);
public final boolean preventProxyConnections = this.get("prevent-proxy-connections", false);
public final String serverIp = this.get("server-ip", "");
+ public final String serverName = this.get("server-name", "Unknown Server"); // Purpur
public final boolean spawnAnimals = this.get("spawn-animals", true);
public final boolean spawnNpcs = this.get("spawn-npcs", true);
public final boolean pvp = this.get("pvp", true);
diff --git a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java
index dd9f611efc95f7d06fd3011fedd5d0317b1d0a85..be7b3fe2dc84493dcde9e185717b0b7c7c2e9822 100644
--- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java
+++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java
@@ -43,6 +43,11 @@ public class MinecraftServerGui extends JComponent {
private Thread logAppenderThread;
private final Collection<Runnable> finalizers = Lists.newArrayList();
final AtomicBoolean isClosing = new AtomicBoolean();
+ // Purpur start
+ private final CommandHistory history = new CommandHistory();
+ private String currentCommand = "";
+ private int historyIndex = 0;
+ // Purpur end
public static MinecraftServerGui showFrameFor(final DedicatedServer server) {
try {
@@ -51,7 +56,7 @@ public class MinecraftServerGui extends JComponent {
;
}
- final JFrame jframe = new JFrame("Minecraft server");
+ final JFrame jframe = new JFrame("Purpur Minecraft server"); // Purpur
final MinecraftServerGui servergui = new MinecraftServerGui(server);
jframe.setDefaultCloseOperation(2);
@@ -59,7 +64,7 @@ public class MinecraftServerGui extends JComponent {
jframe.pack();
jframe.setLocationRelativeTo((Component) null);
jframe.setVisible(true);
- jframe.setName("Minecraft server"); // Paper
+ jframe.setName("Purpur Minecraft server"); // Paper // Purpur
// Paper start - Add logo as frame image
try {
@@ -71,7 +76,7 @@ public class MinecraftServerGui extends JComponent {
jframe.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent windowevent) {
if (!servergui.isClosing.getAndSet(true)) {
- jframe.setTitle("Minecraft server - shutting down!");
+ jframe.setTitle("Purpur Minecraft server - shutting down!"); // Purpur
server.halt(true);
servergui.runFinalizers();
}
@@ -125,7 +130,7 @@ public class MinecraftServerGui extends JComponent {
private JComponent buildChatPanel() {
JPanel jpanel = new JPanel(new BorderLayout());
- JTextArea jtextarea = new JTextArea();
+ org.purpurmc.purpur.gui.JColorTextPane jtextarea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur
JScrollPane jscrollpane = new JScrollPane(jtextarea, 22, 30);
jtextarea.setEditable(false);
@@ -137,10 +142,43 @@ public class MinecraftServerGui extends JComponent {
if (!s.isEmpty()) {
this.server.handleConsoleInput(s, this.server.createCommandSourceStack());
+ // Purpur start
+ history.add(s);
+ historyIndex = -1;
+ // Purpur end
}
jtextfield.setText("");
});
+ // Purpur start
+ jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up");
+ jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down");
+ jtextfield.getActionMap().put("up", new javax.swing.AbstractAction() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
+ if (historyIndex < 0) {
+ currentCommand = jtextfield.getText();
+ }
+ if (historyIndex < history.size() - 1) {
+ jtextfield.setText(history.get(historyIndex));
+ }
+ }
+ });
+ jtextfield.getActionMap().put("down", new javax.swing.AbstractAction() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
+ if (historyIndex >= 0) {
+ if (historyIndex == 0) {
+ --historyIndex;
+ jtextfield.setText(currentCommand);
+ } else {
+ --historyIndex;
+ jtextfield.setText(history.get(historyIndex));
+ }
+ }
+ }
+ });
+ // Purpur end
jtextarea.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent focusevent) {}
});
@@ -176,7 +214,7 @@ public class MinecraftServerGui extends JComponent {
}
private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper
- public void print(JTextArea textArea, JScrollPane scrollPane, String message) {
+ public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String message) { // Purpur
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(() -> {
this.print(textArea, scrollPane, message);
@@ -190,11 +228,14 @@ public class MinecraftServerGui extends JComponent {
flag = (double) jscrollbar.getValue() + jscrollbar.getSize().getHeight() + (double) (MinecraftServerGui.MONOSPACED.getSize() * 4) > (double) jscrollbar.getMaximum();
}
+ /* // Purpur
try {
document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(message).replaceAll(""), (AttributeSet) null); // CraftBukkit
} catch (BadLocationException badlocationexception) {
;
}
+ */ // Purpur
+ textArea.append(message); // Purpur
if (flag) {
jscrollbar.setValue(Integer.MAX_VALUE);
@@ -202,4 +243,16 @@ public class MinecraftServerGui extends JComponent {
}
}
+
+ // Purpur start
+ public static class CommandHistory extends java.util.LinkedList<String> {
+ @Override
+ public boolean add(String command) {
+ if (size() > 1000) {
+ remove();
+ }
+ return super.offerFirst(command);
+ }
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
index 3770ced0b227f7eea1f04d6afd5c14331f6a1480..319c469c20f3cb4f6d61425bfa6d98b55b55c4d6 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -637,20 +637,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
protected void tick(BooleanSupplier shouldKeepTicking) {
- ProfilerFiller gameprofilerfiller = this.level.getProfiler();
+ //ProfilerFiller gameprofilerfiller = this.level.getProfiler(); // Purpur
- try (Timing ignored = this.level.timings.poiUnload.startTiming()) { // Paper
- gameprofilerfiller.push("poi");
+ //try (Timing ignored = this.level.timings.poiUnload.startTiming()) { // Paper // Purpur
+ //gameprofilerfiller.push("poi"); // Purpur
this.poiManager.tick(shouldKeepTicking);
- } // Paper
- gameprofilerfiller.popPush("chunk_unload");
+ //} // Paper // Purpur
+ //gameprofilerfiller.popPush("chunk_unload"); // Purpur
if (!this.level.noSave()) {
- try (Timing ignored = this.level.timings.chunkUnload.startTiming()) { // Paper
+ //try (Timing ignored = this.level.timings.chunkUnload.startTiming()) { // Paper // Purpur
this.processUnloads(shouldKeepTicking);
- } // Paper
+ //} // Paper // Purpur
}
- gameprofilerfiller.pop();
+ //gameprofilerfiller.pop(); // Purpur
}
public boolean hasWork() {
@@ -1269,24 +1269,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper start - optimised tracker
private final void processTrackQueue() {
- this.level.timings.tracker1.startTiming();
+ //this.level.timings.tracker1.startTiming(); // Purpur
try {
for (TrackedEntity tracker : this.entityMap.values()) {
// update tracker entry
tracker.updatePlayers(tracker.entity.getPlayersInTrackRange());
}
} finally {
- this.level.timings.tracker1.stopTiming();
+ //this.level.timings.tracker1.stopTiming(); // Purpur
}
- this.level.timings.tracker2.startTiming();
+ //this.level.timings.tracker2.startTiming(); // Purpur
try {
for (TrackedEntity tracker : this.entityMap.values()) {
tracker.serverEntity.sendChanges();
}
} finally {
- this.level.timings.tracker2.stopTiming();
+ //this.level.timings.tracker2.stopTiming(); Purpur
}
}
// Paper end - optimised tracker
@@ -1301,7 +1301,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
List<ServerPlayer> list = Lists.newArrayList();
List<ServerPlayer> list1 = this.level.players();
ObjectIterator objectiterator = this.entityMap.values().iterator();
- level.timings.tracker1.startTiming(); // Paper
+ //level.timings.tracker1.startTiming(); // Paper // Purpur
ChunkMap.TrackedEntity playerchunkmap_entitytracker;
@@ -1326,17 +1326,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
playerchunkmap_entitytracker.serverEntity.sendChanges();
}
}
- level.timings.tracker1.stopTiming(); // Paper
+ //level.timings.tracker1.stopTiming(); // Paper // Purpur
if (!list.isEmpty()) {
objectiterator = this.entityMap.values().iterator();
- level.timings.tracker2.startTiming(); // Paper
+ //level.timings.tracker2.startTiming(); // Paper // Purpur
while (objectiterator.hasNext()) {
playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next();
playerchunkmap_entitytracker.updatePlayers(list);
}
- level.timings.tracker2.stopTiming(); // Paper
+ //level.timings.tracker2.stopTiming(); // Paper // Purpur
}
}
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
index fe1a9c646b09d11e7fa2186afbeb70b680ad2b57..2d5b0f293bb30243e774bfb62b8100c01e7eb383 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -280,16 +280,16 @@ public class ServerChunkCache extends ChunkSource {
return ifLoaded;
}
// Paper end
- ProfilerFiller gameprofilerfiller = this.level.getProfiler();
+ //ProfilerFiller gameprofilerfiller = this.level.getProfiler(); // Purpur
- gameprofilerfiller.incrementCounter("getChunk");
+ //gameprofilerfiller.incrementCounter("getChunk"); // Purpur
long k = ChunkPos.asLong(x, z);
ChunkAccess ichunkaccess;
// Paper - rewrite chunk system - there are no correct callbacks to remove items from cache in the new chunk system
- gameprofilerfiller.incrementCounter("getChunkCacheMiss");
+ //gameprofilerfiller.incrementCounter("getChunkCacheMiss"); // Purpur
CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create, true); // Paper
ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor;
@@ -299,10 +299,10 @@ public class ServerChunkCache extends ChunkSource {
io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.pushChunkWait(this.level, x1, z1); // Paper - rewrite chunk system
// Paper end
com.destroystokyo.paper.io.SyncLoadFinder.logSyncLoad(this.level, x1, z1); // Paper - sync load info
- this.level.timings.syncChunkLoad.startTiming(); // Paper
+ //this.level.timings.syncChunkLoad.startTiming(); // Paper // Purpur
chunkproviderserver_b.managedBlock(completablefuture::isDone);
io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.popChunkWait(); // Paper - async chunk debug // Paper - rewrite chunk system
- this.level.timings.syncChunkLoad.stopTiming(); // Paper
+ //this.level.timings.syncChunkLoad.stopTiming(); // Paper // Purpur
} // Paper
ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> {
return ichunkaccess1;
@@ -451,17 +451,17 @@ public class ServerChunkCache extends ChunkSource {
public void save(boolean flush) {
this.runDistanceManagerUpdates();
- try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings
+ //try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings // Purpur
this.chunkMap.saveAllChunks(flush);
- } // Paper - Timings
+ //} // Paper - Timings // Purpur
}
// Paper start - duplicate save, but call incremental
public void saveIncrementally() {
this.runDistanceManagerUpdates();
- try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings
+ //try (co.aikar.timings.Timing timed = level.timings.chunkSaveData.startTiming()) { // Paper - Timings // Purpur
this.chunkMap.saveIncrementally();
- } // Paper - Timings
+ //} // Paper - Timings // Purpur
}
// Paper end
@@ -478,36 +478,36 @@ public class ServerChunkCache extends ChunkSource {
// CraftBukkit start - modelled on below
public void purgeUnload() {
if (true) return; // Paper - tickets will be removed later, this behavior isn't really well accounted for by the chunk system
- this.level.getProfiler().push("purge");
+ //this.level.getProfiler().push("purge"); // Purpur
this.distanceManager.purgeStaleTickets();
this.runDistanceManagerUpdates();
- this.level.getProfiler().popPush("unload");
+ //this.level.getProfiler().popPush("unload"); // Purpur
this.chunkMap.tick(() -> true);
- this.level.getProfiler().pop();
+ //this.level.getProfiler().pop(); // Purpur
this.clearCache();
}
// CraftBukkit end
@Override
public void tick(BooleanSupplier shouldKeepTicking, boolean tickChunks) {
- this.level.getProfiler().push("purge");
- this.level.timings.doChunkMap.startTiming(); // Spigot
+ //this.level.getProfiler().push("purge"); // Purpur
+ //this.level.timings.doChunkMap.startTiming(); // Spigot // Purpur
this.distanceManager.purgeStaleTickets();
this.runDistanceManagerUpdates();
- this.level.timings.doChunkMap.stopTiming(); // Spigot
- this.level.getProfiler().popPush("chunks");
+ //this.level.timings.doChunkMap.stopTiming(); // Spigot // Purpur
+ //this.level.getProfiler().popPush("chunks"); // Purpur
if (tickChunks) {
- this.level.timings.chunks.startTiming(); // Paper - timings
+ //this.level.timings.chunks.startTiming(); // Paper - timings // Purpur
this.chunkMap.level.playerChunkLoader.tick(); // Paper - replace player chunk loader - this is mostly required to account for view distance changes
this.tickChunks();
- this.level.timings.chunks.stopTiming(); // Paper - timings
+ //this.level.timings.chunks.stopTiming(); // Paper - timings // Purpur
}
- this.level.timings.doChunkUnload.startTiming(); // Spigot
- this.level.getProfiler().popPush("unload");
+ //this.level.timings.doChunkUnload.startTiming(); // Spigot // Purpur
+ //this.level.getProfiler().popPush("unload"); // Purpur
this.chunkMap.tick(shouldKeepTicking);
- this.level.timings.doChunkUnload.stopTiming(); // Spigot
- this.level.getProfiler().pop();
+ //this.level.timings.doChunkUnload.stopTiming(); // Spigot // Purpur
+ //this.level.getProfiler().pop(); // Purpur
this.clearCache();
}
@@ -553,15 +553,15 @@ public class ServerChunkCache extends ChunkSource {
}
// Paper end - optimize isOutisdeRange
LevelData worlddata = this.level.getLevelData();
- ProfilerFiller gameprofilerfiller = this.level.getProfiler();
+ //ProfilerFiller gameprofilerfiller = this.level.getProfiler(); // Purpur
- gameprofilerfiller.push("pollingChunks");
+ //gameprofilerfiller.push("pollingChunks"); // Purpur
this.level.resetIceAndSnowTick(); // Pufferfish - reset ice & snow tick random
int k = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
boolean flag1 = level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && worlddata.getGameTime() % level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
- gameprofilerfiller.push("naturalSpawnCount");
- this.level.timings.countNaturalMobs.startTiming(); // Paper - timings
+ //gameprofilerfiller.push("naturalSpawnCount"); // Purpur
+ //this.level.timings.countNaturalMobs.startTiming(); // Paper - timings // Purpur
int l = this.distanceManager.getNaturalSpawnChunkCount();
// Paper start - per player mob spawning
NaturalSpawner.SpawnState spawnercreature_d; // moved down
@@ -592,16 +592,16 @@ public class ServerChunkCache extends ChunkSource {
// Pufferfish end
}
// Paper end
- this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings
+ //this.level.timings.countNaturalMobs.stopTiming(); // Paper - timings // Purpur
//this.lastSpawnState = spawnercreature_d; // Pufferfish - this is managed asynchronously
- gameprofilerfiller.popPush("filteringLoadedChunks");
+ //gameprofilerfiller.popPush("filteringLoadedChunks"); // Purpur
// Paper - moved down
- this.level.timings.chunkTicks.startTiming(); // Paper
+ //this.level.timings.chunkTicks.startTiming(); // Paper // Purpur
// Paper - moved down
- gameprofilerfiller.popPush("spawnAndTick");
+ //gameprofilerfiller.popPush("spawnAndTick"); // Purpur
boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
// Paper - only shuffle if per-player mob spawning is disabled
@@ -652,17 +652,17 @@ public class ServerChunkCache extends ChunkSource {
}
}
// Paper end - optimise chunk tick iteration
- this.level.timings.chunkTicks.stopTiming(); // Paper
- gameprofilerfiller.popPush("customSpawners");
+ //this.level.timings.chunkTicks.stopTiming(); // Paper // Purpur
+ //gameprofilerfiller.popPush("customSpawners"); // Purpur
if (flag2) {
- try (co.aikar.timings.Timing ignored = this.level.timings.miscMobSpawning.startTiming()) { // Paper - timings
+ //try (co.aikar.timings.Timing ignored = this.level.timings.miscMobSpawning.startTiming()) { // Paper - timings // Purpur
this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
- } // Paper - timings
+ //} // Paper - timings // Purpur
}
- gameprofilerfiller.pop();
+ //gameprofilerfiller.pop(); // Purpur
// Paper start - use set of chunks requiring updates, rather than iterating every single one loaded
- gameprofilerfiller.popPush("broadcast");
- this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
+ //gameprofilerfiller.popPush("broadcast"); // Purpur
+ //this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing // Purpur
if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) {
ReferenceOpenHashSet<ChunkHolder> copy = this.chunkMap.needsChangeBroadcasting.clone();
this.chunkMap.needsChangeBroadcasting.clear();
@@ -674,8 +674,8 @@ public class ServerChunkCache extends ChunkSource {
}
}
}
- this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
- gameprofilerfiller.pop();
+ //this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing // Purpur
+ //gameprofilerfiller.pop(); // Purpur
// Paper end - use set of chunks requiring updates, rather than iterating every single one loaded
// Paper start - controlled flush for entity tracker packets
List<net.minecraft.network.Connection> disabledFlushes = new java.util.ArrayList<>(this.level.players.size());
@@ -900,7 +900,7 @@ public class ServerChunkCache extends ChunkSource {
@Override
protected void doRunTask(Runnable task) {
- ServerChunkCache.this.level.getProfiler().incrementCounter("runTask");
+ //ServerChunkCache.this.level.getProfiler().incrementCounter("runTask"); // Purpur
super.doRunTask(task);
}
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
index 8ab959dd588b5154b63e133b2e937fa2d0ab8e52..fb11b020d52988360562db23d8568e5ef37e21c8 100644
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
@@ -70,7 +70,7 @@ public class ServerEntity {
@Nullable
private List<SynchedEntityData.DataValue<?>> trackedDataValues;
// CraftBukkit start
- final Set<ServerPlayerConnection> trackedPlayers; // Paper - private -> package
+ public final Set<ServerPlayerConnection> trackedPlayers; // Paper - private -> package // Purpur - package -> public
public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer<Packet<?>> consumer, Set<ServerPlayerConnection> trackedPlayers) {
this.trackedPlayers = trackedPlayers;
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index eac31c3fcc9161711328588ac852fcae1116d8ef..221d1d0e1b4b46de6ebca5faac09bbda875fae17 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -214,6 +214,8 @@ public class ServerLevel extends Level implements WorldGenLevel {
private final StructureManager structureManager;
private final StructureCheck structureCheck;
private final boolean tickTime;
+ private double preciseTime; // Purpur
+ private boolean forceTime; // Purpur
private final RandomSequences randomSequences;
public long lastMidTickExecuteFailure; // Paper - execute chunk tasks mid tick
@@ -223,6 +225,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
public boolean hasPhysicsEvent = true; // Paper
public boolean hasEntityMoveEvent = false; // Paper
private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current)
+ public boolean hasRidableMoveEvent = false; // Purpur
public static Throwable getAddToWorldStackTrace(Entity entity) {
final Throwable thr = new Throwable(entity + " Added to world at " + new java.util.Date());
io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(thr);
@@ -662,7 +665,24 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.dragonParts = new Int2ObjectOpenHashMap();
this.tickTime = flag1;
this.server = minecraftserver;
- this.customSpawners = list;
+ // Purpur start - enable/disable MobSpawners per world
+ this.customSpawners = Lists.newArrayList();
+ if (purpurConfig.phantomSpawning) {
+ customSpawners.add(new net.minecraft.world.level.levelgen.PhantomSpawner());
+ }
+ if (purpurConfig.patrolSpawning) {
+ customSpawners.add(new net.minecraft.world.level.levelgen.PatrolSpawner());
+ }
+ if (purpurConfig.catSpawning) {
+ customSpawners.add(new net.minecraft.world.entity.npc.CatSpawner());
+ }
+ if (purpurConfig.villageSiegeSpawning) {
+ customSpawners.add(new net.minecraft.world.entity.ai.village.VillageSiege());
+ }
+ if (purpurConfig.villagerTraderSpawning) {
+ customSpawners.add(new net.minecraft.world.entity.npc.WanderingTraderSpawner(iworlddataserver));
+ }
+ // Purpur end
this.serverLevelData = iworlddataserver;
ChunkGenerator chunkgenerator = worlddimension.generator();
// CraftBukkit start
@@ -732,6 +752,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.chunkTaskScheduler = new io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler(this, io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.workerThreads); // Paper - rewrite chunk system
this.entityLookup = new io.papermc.paper.chunk.system.entity.EntityLookup(this, new EntityCallbacks()); // Paper - rewrite chunk system
+ this.preciseTime = this.serverLevelData.getDayTime(); // Purpur
}
// Paper start
@@ -774,17 +795,17 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
}
// Paper end - optimise checkDespawn
- ProfilerFiller gameprofilerfiller = this.getProfiler();
+ //ProfilerFiller gameprofilerfiller = this.getProfiler(); // Purpur
this.handlingTick = true;
- gameprofilerfiller.push("world border");
+ //gameprofilerfiller.push("world border"); // Purpur
this.getWorldBorder().tick();
- gameprofilerfiller.popPush("weather");
+ //gameprofilerfiller.popPush("weather"); // Purpur
this.advanceWeatherCycle();
int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
long j;
- if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) {
+ if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) {
// CraftBukkit start
j = this.levelData.getDayTime() + 24000L;
TimeSkipEvent event = new TimeSkipEvent(this.getWorld(), TimeSkipEvent.SkipReason.NIGHT_SKIP, (j - j % 24000L) - this.getDayTime());
@@ -806,32 +827,32 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.updateSkyBrightness();
this.tickTime();
- gameprofilerfiller.popPush("tickPending");
- timings.scheduledBlocks.startTiming(); // Paper
+ //gameprofilerfiller.popPush("tickPending"); // Purpur
+ //timings.scheduledBlocks.startTiming(); // Paper // Purpur
if (!this.isDebug()) {
j = this.getGameTime();
- gameprofilerfiller.push("blockTicks");
+ //gameprofilerfiller.push("blockTicks"); // Purpur
this.blockTicks.tick(j, 65536, this::tickBlock);
- gameprofilerfiller.popPush("fluidTicks");
+ //gameprofilerfiller.popPush("fluidTicks"); // Purpur
this.fluidTicks.tick(j, 65536, this::tickFluid);
- gameprofilerfiller.pop();
+ //gameprofilerfiller.pop(); // Purpur
}
- timings.scheduledBlocks.stopTiming(); // Paper
+ //timings.scheduledBlocks.stopTiming(); // Paper // Purpur
- gameprofilerfiller.popPush("raid");
- this.timings.raids.startTiming(); // Paper - timings
+ //gameprofilerfiller.popPush("raid"); // Purpur
+ //this.timings.raids.startTiming(); // Paper - timings // Purpur
this.raids.tick();
- this.timings.raids.stopTiming(); // Paper - timings
- gameprofilerfiller.popPush("chunkSource");
- this.timings.chunkProviderTick.startTiming(); // Paper - timings
+ //this.timings.raids.stopTiming(); // Paper - timings // Purpur
+ //gameprofilerfiller.popPush("chunkSource"); // Purpur
+ //this.timings.chunkProviderTick.startTiming(); // Paper - timings // Purpur
this.getChunkSource().tick(shouldKeepTicking, true);
- this.timings.chunkProviderTick.stopTiming(); // Paper - timings
- gameprofilerfiller.popPush("blockEvents");
- timings.doSounds.startTiming(); // Spigot
+ //this.timings.chunkProviderTick.stopTiming(); // Paper - timings // Purpur
+ //gameprofilerfiller.popPush("blockEvents"); // Purpur
+ //timings.doSounds.startTiming(); // Spigot // Purpur
this.runBlockEvents();
- timings.doSounds.stopTiming(); // Spigot
+ //timings.doSounds.stopTiming(); // Spigot // Purpur
this.handlingTick = false;
- gameprofilerfiller.pop();
+ //gameprofilerfiller.pop(); // Purpur
boolean flag = true || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players
if (flag) {
@@ -839,25 +860,25 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
if (flag || this.emptyTime++ < 300) {
- gameprofilerfiller.push("entities");
- timings.tickEntities.startTiming(); // Spigot
+ //gameprofilerfiller.push("entities"); // Purpur
+ //timings.tickEntities.startTiming(); // Spigot // Purpur
if (this.dragonFight != null) {
- gameprofilerfiller.push("dragonFight");
+ //gameprofilerfiller.push("dragonFight"); // Purpur
this.dragonFight.tick();
- gameprofilerfiller.pop();
+ //gameprofilerfiller.pop(); // Purpur
}
org.spigotmc.ActivationRange.activateEntities(this); // Spigot
- timings.entityTick.startTiming(); // Spigot
+ //timings.entityTick.startTiming(); // Spigot // Purpur
this.entityTickList.forEach((entity) -> {
entity.activatedPriorityReset = false; // Pufferfish - DAB
if (!entity.isRemoved()) {
if (false && this.shouldDiscardEntity(entity)) { // CraftBukkit - We prevent spawning in general, so this butchering is not needed
entity.discard();
} else {
- gameprofilerfiller.push("checkDespawn");
+ //gameprofilerfiller.push("checkDespawn"); // Purpur
entity.checkDespawn();
- gameprofilerfiller.pop();
+ //gameprofilerfiller.pop(); // Purpur
if (true || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { // Paper - now always true if in the ticking list
Entity entity1 = entity.getVehicle();
@@ -869,7 +890,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
entity.stopRiding();
}
- gameprofilerfiller.push("tick");
+ //gameprofilerfiller.push("tick"); // Purpur
// Pufferfish start - copied from this.guardEntityTick
try {
this.tickNonPassenger(entity); // Pufferfish - changed
@@ -884,20 +905,19 @@ public class ServerLevel extends Level implements WorldGenLevel {
// Paper end
}
// Pufferfish end
- gameprofilerfiller.pop();
+ //gameprofilerfiller.pop(); // Purpur
}
}
}
});
- timings.entityTick.stopTiming(); // Spigot
- timings.tickEntities.stopTiming(); // Spigot
- gameprofilerfiller.pop();
+ //timings.entityTick.stopTiming(); // Spigot // Purpur
+ //timings.tickEntities.stopTiming(); // Spigot // Purpur
+ //gameprofilerfiller.pop(); // Purpur
this.tickBlockEntities();
}
- gameprofilerfiller.push("entityManagement");
+ //gameprofilerfiller.push("entityManagement"); // Purpur
//this.entityManager.tick(); // Paper - rewrite chunk system
- gameprofilerfiller.pop();
}
@Override
@@ -915,6 +935,13 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.serverLevelData.setGameTime(i);
this.serverLevelData.getScheduledEvents().tick(this.server, i);
if (this.levelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
+ // Purpur start
+ int incrementTicks = isDay() ? this.purpurConfig.daytimeTicks : this.purpurConfig.nighttimeTicks;
+ if (incrementTicks != 12000) {
+ this.preciseTime += 12000 / (double) incrementTicks;
+ this.setDayTime(this.preciseTime);
+ } else
+ // Purpur end
this.setDayTime(this.levelData.getDayTime() + 1L);
}
@@ -923,7 +950,21 @@ public class ServerLevel extends Level implements WorldGenLevel {
public void setDayTime(long timeOfDay) {
this.serverLevelData.setDayTime(timeOfDay);
+ // Purpur start
+ this.preciseTime = timeOfDay;
+ this.forceTime = false;
}
+ public void setDayTime(double i) {
+ this.serverLevelData.setDayTime((long) i);
+ this.forceTime = true;
+ // Purpur end
+ }
+
+ // Purpur start
+ public boolean isForceTime() {
+ return this.forceTime;
+ }
+ // Purpur end
public void tickCustomSpawners(boolean spawnMonsters, boolean spawnAnimals) {
Iterator iterator = this.customSpawners.iterator();
@@ -948,7 +989,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
// Paper start - optimise random block ticking
private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos();
- // private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(); // Pufferfish - moved to super
+ private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(this.random.nextLong()); public net.minecraft.util.RandomSource getThreadUnsafeRandom() { return this.randomTickRandom; } // Pufferfish - moved to super // Purpur - dont break ABI
// Paper end
private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.randomTickRandom.nextInt(16); } // Pufferfish
@@ -958,9 +999,9 @@ public class ServerLevel extends Level implements WorldGenLevel {
boolean flag = this.isRaining();
int j = chunkcoordintpair.getMinBlockX();
int k = chunkcoordintpair.getMinBlockZ();
- ProfilerFiller gameprofilerfiller = this.getProfiler();
+ //ProfilerFiller gameprofilerfiller = this.getProfiler(); // Purpur
- gameprofilerfiller.push("thunder");
+ //gameprofilerfiller.push("thunder"); // Purpur
final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change
if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && /*this.random.nextInt(this.spigotConfig.thunderChance) == 0 &&*/ chunk.shouldDoLightning(this.random)) { // Spigot // Paper - disable thunder // Pufferfish - replace random with shouldDoLightning
@@ -970,10 +1011,18 @@ public class ServerLevel extends Level implements WorldGenLevel {
boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper
if (flag1) {
- SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this);
+ // Purpur start
+ net.minecraft.world.entity.animal.horse.AbstractHorse entityhorseskeleton;
+ if (purpurConfig.zombieHorseSpawnChance > 0D && random.nextDouble() <= purpurConfig.zombieHorseSpawnChance) {
+ entityhorseskeleton = EntityType.ZOMBIE_HORSE.create(this);
+ } else {
+ entityhorseskeleton = EntityType.SKELETON_HORSE.create(this);
+ if (entityhorseskeleton != null) ((SkeletonHorse) entityhorseskeleton).setTrap(true);
+ }
+ // Purpur end
if (entityhorseskeleton != null) {
- entityhorseskeleton.setTrap(true);
+ //entityhorseskeleton.setTrap(true); // Purpur - moved up
entityhorseskeleton.setAge(0);
entityhorseskeleton.setPos((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ());
this.addFreshEntity(entityhorseskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
@@ -990,7 +1039,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
}
- gameprofilerfiller.popPush("iceandsnow");
+ //gameprofilerfiller.popPush("iceandsnow"); // Purpur
int l;
int i1;
@@ -1042,8 +1091,8 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
// Paper start - optimise random block ticking
- gameprofilerfiller.popPush("randomTick");
- timings.chunkTicksBlocks.startTiming(); // Paper
+ //gameprofilerfiller.popPush("randomTick"); // Purpur
+ //timings.chunkTicksBlocks.startTiming(); // Paper // Purpur
if (randomTickSpeed > 0) {
LevelChunkSection[] sections = chunk.getSections();
int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this);
@@ -1077,8 +1126,8 @@ public class ServerLevel extends Level implements WorldGenLevel {
}
}
// Paper end - optimise random block ticking
- timings.chunkTicksBlocks.stopTiming(); // Paper
- gameprofilerfiller.pop();
+ //timings.chunkTicksBlocks.stopTiming(); // Paper // Purpur
+ //gameprofilerfiller.pop(); // Purpur
}
public Optional<BlockPos> findLightningRod(BlockPos pos) {
@@ -1086,7 +1135,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
return holder.is(PoiTypes.LIGHTNING_ROD);
}, (blockposition1) -> {
return blockposition1.getY() == this.getHeight(Heightmap.Types.WORLD_SURFACE, blockposition1.getX(), blockposition1.getZ()) - 1;
- }, pos, 128, PoiManager.Occupancy.ANY);
+ }, pos, org.purpurmc.purpur.PurpurConfig.lightningRodRange, PoiManager.Occupancy.ANY);
return optional.map((blockposition1) -> {
return blockposition1.above(1);
@@ -1135,11 +1184,27 @@ public class ServerLevel extends Level implements WorldGenLevel {
if (this.canSleepThroughNights()) {
if (!this.getServer().isSingleplayer() || this.getServer().isPublished()) {
int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
- MutableComponent ichatmutablecomponent;
+ Component ichatmutablecomponent;
if (this.sleepStatus.areEnoughSleeping(i)) {
+ // Purpur start
+ if (org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.isBlank()) {
+ return;
+ }
+ if (!org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.equalsIgnoreCase("default")) {
+ ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepSkippingNight));
+ } else
ichatmutablecomponent = Component.translatable("sleep.skipping_night");
} else {
+ if (org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.isBlank()) {
+ return;
+ }
+ if (!org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.equalsIgnoreCase("default")) {
+ ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent,
+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("count", Integer.toString(this.sleepStatus.amountSleeping())),
+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("total", Integer.toString(this.sleepStatus.sleepersNeeded(i)))));
+ } else
+ // Purpur end
ichatmutablecomponent = Component.translatable("sleep.players_sleeping", this.sleepStatus.amountSleeping(), this.sleepStatus.sleepersNeeded(i));
}
@@ -1278,6 +1343,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
private void resetWeatherCycle() {
// CraftBukkit start
+ if (this.purpurConfig.rainStopsAfterSleep) // Purpur
this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - when passing the night
// If we stop due to everyone sleeping we should reset the weather duration to some other random value.
// Not that everyone ever manages to get the whole server to sleep at the same time....
@@ -1285,6 +1351,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.serverLevelData.setRainTime(0);
}
// CraftBukkit end
+ if (this.purpurConfig.thunderStopsAfterSleep) // Purpur
this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - when passing the night
// CraftBukkit start
// If we stop due to everyone sleeping we should reset the weather duration to some other random value.
@@ -1352,24 +1419,24 @@ public class ServerLevel extends Level implements WorldGenLevel {
// Spigot end
// Paper start- timings
final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(entity);
- timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper
- try {
+ //timer = isActive ? entity.getType().tickTimer.startTiming() : entity.getType().inactiveTickTimer.startTiming(); // Paper // Purpur
+ //try { // Purpur
// Paper end - timings
entity.setOldPosAndRot();
- ProfilerFiller gameprofilerfiller = this.getProfiler();
+ //ProfilerFiller gameprofilerfiller = this.getProfiler(); // Purpur
++entity.tickCount;
- this.getProfiler().push(() -> {
+ /*this.getProfiler().push(() -> { // Purpur
return BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString();
- });
- gameprofilerfiller.incrementCounter("tickNonPassenger");
+ });*/ // Purpur
+ //gameprofilerfiller.incrementCounter("tickNonPassenger"); // Purpur
if (isActive) { // Paper - EAR 2
TimingHistory.activatedEntityTicks++;
entity.tick();
entity.postTick(); // CraftBukkit
} else { entity.inactiveTick(); } // Paper - EAR 2
- this.getProfiler().pop();
- } finally { timer.stopTiming(); } // Paper - timings
+ //this.getProfiler().pop(); // Purpur
+ //} finally { timer.stopTiming(); } // Paper - timings // Purpur
Iterator iterator = entity.getPassengers().iterator();
while (iterator.hasNext()) {
@@ -1392,17 +1459,17 @@ public class ServerLevel extends Level implements WorldGenLevel {
if (passenger instanceof Player || this.entityTickList.contains(passenger)) {
// Paper - EAR 2
final boolean isActive = org.spigotmc.ActivationRange.checkIfActive(passenger);
- co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper
- try {
+ //co.aikar.timings.Timing timer = isActive ? passenger.getType().passengerTickTimer.startTiming() : passenger.getType().passengerInactiveTickTimer.startTiming(); // Paper // Purpur
+ //try { // Purpur
// Paper end
passenger.setOldPosAndRot();
++passenger.tickCount;
- ProfilerFiller gameprofilerfiller = this.getProfiler();
+ //ProfilerFiller gameprofilerfiller = this.getProfiler(); // Purpur
- gameprofilerfiller.push(() -> {
+ /*gameprofilerfiller.push(() -> { // Purpur
return BuiltInRegistries.ENTITY_TYPE.getKey(passenger.getType()).toString();
- });
- gameprofilerfiller.incrementCounter("tickPassenger");
+ });*/ // Purpur
+ //gameprofilerfiller.incrementCounter("tickPassenger"); // Purpur
// Paper start - EAR 2
if (isActive) {
passenger.rideTick();
@@ -1414,7 +1481,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
vehicle.positionRider(passenger);
}
// Paper end - EAR 2
- gameprofilerfiller.pop();
+ //gameprofilerfiller.pop(); // Purpur
Iterator iterator = passenger.getPassengers().iterator();
while (iterator.hasNext()) {
@@ -1423,7 +1490,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.tickPassenger(passenger, entity2);
}
- } finally { timer.stopTiming(); }// Paper - EAR2 timings
+ //} finally { timer.stopTiming(); }// Paper - EAR2 timings // Purpur
}
} else {
passenger.stopRiding();
@@ -1443,14 +1510,14 @@ public class ServerLevel extends Level implements WorldGenLevel {
org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld()));
}
- try (co.aikar.timings.Timing ignored = this.timings.worldSave.startTiming()) {
+ //try (co.aikar.timings.Timing ignored = this.timings.worldSave.startTiming()) { // Purpur
if (doFull) {
this.saveLevelData();
}
- this.timings.worldSaveChunks.startTiming(); // Paper
+ //this.timings.worldSaveChunks.startTiming(); // Paper // Purpur
if (!this.noSave()) chunkproviderserver.saveIncrementally();
- this.timings.worldSaveChunks.stopTiming(); // Paper
+ //this.timings.worldSaveChunks.stopTiming(); // Paper // Purpur
// Copied from save()
// CraftBukkit start - moved from MinecraftServer.saveChunks
@@ -1462,7 +1529,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
this.convertable.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData());
}
// CraftBukkit end
- }
+ //} // Purpur
}
// Paper end
@@ -1476,7 +1543,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
if (!savingDisabled) {
org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit
- try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper
+ //try (co.aikar.timings.Timing ignored = timings.worldSave.startTiming()) { // Paper // Purpur // Purpur
if (progressListener != null) {
progressListener.progressStartNoAbort(Component.translatable("menu.savingLevel"));
}
@@ -1486,11 +1553,11 @@ public class ServerLevel extends Level implements WorldGenLevel {
progressListener.progressStage(Component.translatable("menu.savingChunks"));
}
- timings.worldSaveChunks.startTiming(); // Paper
+ //timings.worldSaveChunks.startTiming(); // Paper // Purpur
if (!close) chunkproviderserver.save(flush); // Paper - rewrite chunk system
if (close) chunkproviderserver.close(true); // Paper - rewrite chunk system
- timings.worldSaveChunks.stopTiming(); // Paper
- }// Paper
+ //timings.worldSaveChunks.stopTiming(); // Paper // Purpur
+ //}// Paper // Purpur
// Paper - rewrite chunk system - entity saving moved into ChunkHolder
} else if (close) { chunkproviderserver.close(false); } // Paper - rewrite chunk system
@@ -2769,7 +2836,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
// Spigot Start
if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message
// Paper start
- if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) {
+ if (!entity.level().purpurConfig.playerVoidTrading && entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { // Purpur
merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED);
}
// Paper end
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index b382da838acc04a1c5d89064b4fa43bcdd38ae71..0249eeb3937cf48cea13846a7e39b248947e21a4 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -278,6 +278,11 @@ public class ServerPlayer extends Player {
public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleHashSet; // Paper
public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper
public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - there are a lot of changes to do if we change all methods leading to the event
+ public boolean purpurClient = false; // Purpur
+ public boolean acceptingResourcePack = false; // Purpur
+ private boolean ramBar = false; // Purpur
+ private boolean tpsBar = false; // Purpur
+ private boolean compassBar = false; // Purpur
private final java.util.concurrent.atomic.AtomicReference<io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances> viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1));
public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader;
@@ -418,6 +423,7 @@ public class ServerPlayer extends Player {
this.bukkitPickUpLoot = true;
this.maxHealthCache = this.getMaxHealth();
this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
+ this.spawnInvulnerableTime = world.purpurConfig.playerSpawnInvulnerableTicks; // Purpur
}
// Yes, this doesn't match Vanilla, but it's the best we can do for now.
@@ -557,6 +563,9 @@ public class ServerPlayer extends Player {
}
}
+ if (nbt.contains("Purpur.RamBar")) { this.ramBar = nbt.getBoolean("Purpur.RamBar"); } // Purpur
+ if (nbt.contains("Purpur.TPSBar")) { this.tpsBar = nbt.getBoolean("Purpur.TPSBar"); } // Purpur
+ if (nbt.contains("Purpur.CompassBar")) { this.compassBar = nbt.getBoolean("Purpur.CompassBar"); } // Purpur
}
@Override
@@ -623,6 +632,9 @@ public class ServerPlayer extends Player {
}
this.getBukkitEntity().setExtraData(nbt); // CraftBukkit
+ nbt.putBoolean("Purpur.RamBar", this.ramBar); // Purpur
+ nbt.putBoolean("Purpur.TPSBar", this.tpsBar); // Purpur
+ nbt.putBoolean("Purpur.CompassBar", this.compassBar); // Purpur
}
// CraftBukkit start - World fallback code, either respawn location or global spawn
@@ -751,6 +763,15 @@ public class ServerPlayer extends Player {
this.trackStartFallingPosition();
this.trackEnteredOrExitedLavaOnVehicle();
this.advancements.flushDirty(this);
+
+ // Purpur start
+ if (this.level().purpurConfig.useNightVisionWhenRiding && this.getVehicle() != null && this.getVehicle().getRider() == this && this.level().getGameTime() % 100 == 0) { // 5 seconds
+ MobEffectInstance nightVision = this.getEffect(MobEffects.NIGHT_VISION);
+ if (nightVision == null || nightVision.getDuration() <= 300) { // 15 seconds
+ this.addEffect(new MobEffectInstance(MobEffects.NIGHT_VISION, 400, 0)); // 20 seconds
+ }
+ }
+ // Purpur end
}
public void doTick() {
@@ -989,6 +1010,7 @@ public class ServerPlayer extends Player {
}));
Team scoreboardteambase = this.getTeam();
+ if (org.purpurmc.purpur.PurpurConfig.deathMessageOnlyBroadcastToAffectedPlayer) this.sendSystemMessage(ichatbasecomponent); else // Purpur
if (scoreboardteambase != null && scoreboardteambase.getDeathMessageVisibility() != Team.Visibility.ALWAYS) {
if (scoreboardteambase.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) {
this.server.getPlayerList().broadcastSystemToTeam(this, ichatbasecomponent);
@@ -1090,14 +1112,30 @@ public class ServerPlayer extends Player {
}
+ // Purpur start
+ public boolean isSpawnInvulnerable() {
+ return spawnInvulnerableTime > 0 || frozen;
+ }
+ // Purpur end
+
@Override
public boolean hurt(DamageSource source, float amount) {
if (this.isInvulnerableTo(source)) {
return false;
} else {
+ // Purpur start
+ if (source.is(DamageTypeTags.IS_FALL)) { // Purpur
+ if (getRootVehicle() instanceof net.minecraft.world.entity.vehicle.AbstractMinecart && level().purpurConfig.minecartControllable && !level().purpurConfig.minecartControllableFallDamage) {
+ return false;
+ }
+ if (getRootVehicle() instanceof net.minecraft.world.entity.vehicle.Boat && !level().purpurConfig.boatsDoFallDamage) {
+ return false;
+ }
+ }
+ // Purpur end
boolean flag = this.server.isDedicatedServer() && this.isPvpAllowed() && source.is(DamageTypeTags.IS_FALL);
- if (!flag && this.spawnInvulnerableTime > 0 && !source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) {
+ if (!flag && isSpawnInvulnerable() && !source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) { // Purpur
return false;
} else {
Entity entity = source.getEntity();
@@ -1206,7 +1244,7 @@ public class ServerPlayer extends Player {
PortalInfo shapedetectorshape = this.findDimensionEntryPoint(worldserver);
if (shapedetectorshape != null) {
- worldserver1.getProfiler().push("moving");
+ //worldserver1.getProfiler().push("moving"); // Purpur
worldserver = shapedetectorshape.world; // CraftBukkit
if (worldserver == null) { } else // CraftBukkit - empty to fall through to null to event
if (resourcekey == LevelStem.OVERWORLD && worldserver.getTypeKey() == LevelStem.NETHER) { // CraftBukkit
@@ -1229,8 +1267,8 @@ public class ServerPlayer extends Player {
worldserver = ((CraftWorld) exit.getWorld()).getHandle();
// CraftBukkit end
- worldserver1.getProfiler().pop();
- worldserver1.getProfiler().push("placing");
+ //worldserver1.getProfiler().pop(); // Purpur
+ //worldserver1.getProfiler().push("placing"); // Purpur
if (true) { // CraftBukkit
this.isChangingDimension = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds
@@ -1241,13 +1279,14 @@ public class ServerPlayer extends Player {
playerlist.sendPlayerPermissionLevel(this);
worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
this.unsetRemoved();
+ this.portalPos = io.papermc.paper.util.MCUtil.toBlockPosition(exit); // Purpur
// CraftBukkit end
this.setServerLevel(worldserver);
this.connection.teleport(exit); // CraftBukkit - use internal teleport without event
this.connection.resetPosition();
worldserver.addDuringPortalTeleport(this);
- worldserver1.getProfiler().pop();
+ //worldserver1.getProfiler().pop(); // Purpur
this.triggerDimensionChangeTriggers(worldserver1);
this.connection.send(new ClientboundPlayerAbilitiesPacket(this.getAbilities()));
playerlist.sendLevelInfo(this, worldserver);
@@ -1276,6 +1315,7 @@ public class ServerPlayer extends Player {
}
// Paper end
+ this.spawnInvulnerableTime = worldserver.purpurConfig.playerSpawnInvulnerableTicks; // Purpur
return this;
}
}
@@ -1397,7 +1437,7 @@ public class ServerPlayer extends Player {
return entitymonster.isPreventingPlayerRest(this);
});
- if (!list.isEmpty()) {
+ if (!this.level().purpurConfig.playerSleepNearMonsters && !list.isEmpty()) { // Purpur
return Either.left(Player.BedSleepingProblem.NOT_SAFE);
}
}
@@ -1437,7 +1477,19 @@ public class ServerPlayer extends Player {
});
if (!this.serverLevel().canSleepThroughNights()) {
- this.displayClientMessage(Component.translatable("sleep.not_possible"), true);
+ // Purpur start
+ Component clientMessage;
+ if (org.purpurmc.purpur.PurpurConfig.sleepNotPossible.isBlank()) {
+ clientMessage = null;
+ } else if (!org.purpurmc.purpur.PurpurConfig.sleepNotPossible.equalsIgnoreCase("default")) {
+ clientMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepNotPossible));
+ } else {
+ clientMessage = Component.translatable("sleep.not_possible");
+ }
+ if (clientMessage != null) {
+ this.displayClientMessage(clientMessage, true);
+ }
+ // Purpur end
}
((ServerLevel) this.level()).updateSleepingPlayerList();
@@ -1534,6 +1586,7 @@ public class ServerPlayer extends Player {
@Override
public void openTextEdit(SignBlockEntity sign, boolean front) {
+ if (level().purpurConfig.signAllowColors) this.connection.send(sign.getTranslatedUpdatePacket(textFilteringEnabled, front)); // Purpur
this.connection.send(new ClientboundBlockUpdatePacket(this.level(), sign.getBlockPos()));
this.connection.send(new ClientboundOpenSignEditorPacket(sign.getBlockPos(), front));
}
@@ -1779,6 +1832,26 @@ public class ServerPlayer extends Player {
this.lastSentExp = -1; // CraftBukkit - Added to reset
}
+ // Purpur start
+ public void sendActionBarMessage(@Nullable String message) {
+ if (message != null && !message.isEmpty()) {
+ sendActionBarMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message));
+ }
+ }
+
+ public void sendActionBarMessage(@Nullable net.kyori.adventure.text.Component message) {
+ if (message != null) {
+ sendActionBarMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message));
+ }
+ }
+
+ public void sendActionBarMessage(@Nullable Component message) {
+ if (message != null) {
+ displayClientMessage(message, true);
+ }
+ }
+ // Purpur end
+
@Override
public void displayClientMessage(Component message, boolean overlay) {
this.sendSystemMessage(message, overlay);
@@ -2078,6 +2151,7 @@ public class ServerPlayer extends Player {
}
public void sendTexturePack(String url, String hash, boolean required, @Nullable Component resourcePackPrompt) {
+ this.acceptingResourcePack = true; // Purpur
this.connection.send(new ClientboundResourcePackPacket(url, hash, required, resourcePackPrompt));
}
@@ -2092,8 +2166,68 @@ public class ServerPlayer extends Player {
public void resetLastActionTime() {
this.lastActionTime = Util.getMillis();
+ this.setAfk(false); // Purpur
+ }
+
+ // Purpur Start
+ private boolean isAfk = false;
+
+ @Override
+ public void setAfk(boolean afk) {
+ if (this.isAfk == afk) {
+ return;
+ }
+
+ String msg = afk ? org.purpurmc.purpur.PurpurConfig.afkBroadcastAway : org.purpurmc.purpur.PurpurConfig.afkBroadcastBack;
+
+ org.purpurmc.purpur.event.PlayerAFKEvent event = new org.purpurmc.purpur.event.PlayerAFKEvent(this.getBukkitEntity(), afk, this.level().purpurConfig.idleTimeoutKick, msg, !Bukkit.isPrimaryThread());
+ if (!event.callEvent() || event.shouldKick()) {
+ return;
+ }
+
+ this.isAfk = afk;
+
+ if (!afk) {
+ resetLastActionTime();
+ }
+
+ msg = event.getBroadcastMsg();
+ if (msg != null && !msg.isEmpty()) {
+ String playerName = this.getGameProfile().getName();
+ if (org.purpurmc.purpur.PurpurConfig.afkBroadcastUseDisplayName) {
+ net.kyori.adventure.text.Component playerDisplayNameComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(this.getBukkitEntity().getDisplayName());
+ playerName = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText().serialize(playerDisplayNameComponent);
+ }
+ server.getPlayerList().broadcastMiniMessage(String.format(msg, playerName), false);
+ }
+
+ if (this.level().purpurConfig.idleTimeoutUpdateTabList) {
+ String scoreboardName = getScoreboardName();
+ String playerListName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().serialize(getBukkitEntity().playerListName());
+ String[] split = playerListName.split(scoreboardName);
+ String prefix = (split.length > 0 ? split[0] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix, "");
+ String suffix = (split.length > 1 ? split[1] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, "");
+ if (afk) {
+ getBukkitEntity().setPlayerListName(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix + prefix + scoreboardName + suffix + org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, true);
+ } else {
+ getBukkitEntity().setPlayerListName(prefix + scoreboardName + suffix);
+ }
+ }
+
+ ((ServerLevel) this.level()).updateSleepingPlayerList();
+ }
+
+ @Override
+ public boolean isAfk() {
+ return this.isAfk;
}
+ @Override
+ public boolean canBeCollidedWith() {
+ return !this.isAfk() && super.canBeCollidedWith();
+ }
+ // Purpur End
+
public ServerStatsCounter getStats() {
return this.stats;
}
@@ -2605,9 +2739,17 @@ public class ServerPlayer extends Player {
@Override
public boolean isImmobile() {
- return super.isImmobile() || (this.connection != null && this.connection.isDisconnected()); // Paper
+ return super.isImmobile() || frozen || (this.connection != null && this.connection.isDisconnected()); // Paper // Purpur
}
+ // Purpur start
+ private boolean frozen = false;
+
+ public void setFrozen(boolean frozen) {
+ this.frozen = frozen;
+ }
+ // Purpur end
+
@Override
public Scoreboard getScoreboard() {
return this.getBukkitEntity().getScoreboard().getHandle();
@@ -2655,4 +2797,50 @@ public class ServerPlayer extends Player {
return (CraftPlayer) super.getBukkitEntity();
}
// CraftBukkit end
+
+ // Purpur start
+ public void teleport(Location to) {
+ this.ejectPassengers();
+ this.stopRiding(true);
+
+ if (this.isSleeping()) {
+ this.stopSleepInBed(true, false);
+ }
+
+ if (this.containerMenu != this.inventoryMenu) {
+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT);
+ }
+
+ ServerLevel toLevel = ((CraftWorld) to.getWorld()).getHandle();
+ if (this.level() == toLevel) {
+ this.connection.internalTeleport(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch(), java.util.EnumSet.noneOf(net.minecraft.world.entity.RelativeMovement.class));
+ } else {
+ this.server.getPlayerList().respawn(this, toLevel, true, to, !toLevel.paperConfig().environment.disableTeleportationSuffocationCheck, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH);
+ }
+ }
+
+ public boolean ramBar() {
+ return this.ramBar;
+ }
+
+ public void ramBar(boolean ramBar) {
+ this.ramBar = ramBar;
+ }
+
+ public boolean tpsBar() {
+ return this.tpsBar;
+ }
+
+ public void tpsBar(boolean tpsBar) {
+ this.tpsBar = tpsBar;
+ }
+
+ public boolean compassBar() {
+ return this.compassBar;
+ }
+
+ public void compassBar(boolean compassBar) {
+ this.compassBar = compassBar;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
index 618ab9a2903f6d4139acd4aaa2e6db0a26e88ba9..f3bac087856f420aa6c14707e90288f4962967bf 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
@@ -398,6 +398,7 @@ public class ServerPlayerGameMode {
} else {capturedBlockEntity = true;} // Paper end
return false;
}
+ if (this.player.level().purpurConfig.slabHalfBreak && this.player.isShiftKeyDown() && iblockdata.getBlock() instanceof net.minecraft.world.level.block.SlabBlock && ((net.minecraft.world.level.block.SlabBlock) iblockdata.getBlock()).halfBreak(iblockdata, pos, this.player)) return true; // Purpur
}
// CraftBukkit end
@@ -428,7 +429,7 @@ public class ServerPlayerGameMode {
ItemStack mainHandStack = null; // Paper
boolean isCorrectTool = false; // Paper
- if (this.isCreative()) {
+ if (this.isCreative() || (this.level.purpurConfig.shulkerBoxAllowOversizedStacks && block instanceof net.minecraft.world.level.block.ShulkerBoxBlock)) { // Purpur
// return true; // CraftBukkit
} else {
ItemStack itemstack = this.player.getMainHandItem();
@@ -517,6 +518,7 @@ public class ServerPlayerGameMode {
public InteractionHand interactHand;
public ItemStack interactItemStack;
public InteractionResult useItemOn(ServerPlayer player, Level world, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) {
+ if (shiftClickMended(stack)) return InteractionResult.SUCCESS; // Purpur
BlockPos blockposition = hitResult.getBlockPos();
BlockState iblockdata = world.getBlockState(blockposition);
InteractionResult enuminteractionresult = InteractionResult.PASS;
@@ -578,7 +580,7 @@ public class ServerPlayerGameMode {
boolean flag1 = player.isSecondaryUseActive() && flag;
ItemStack itemstack1 = stack.copy();
- if (!flag1) {
+ if (!flag1 || (player.level().purpurConfig.composterBulkProcess && iblockdata.is(Blocks.COMPOSTER))) { // Purpur
enuminteractionresult = iblockdata.use(world, player, hand, hitResult);
if (enuminteractionresult.consumesAction()) {
@@ -619,4 +621,18 @@ public class ServerPlayerGameMode {
public void setLevel(ServerLevel world) {
this.level = world;
}
+
+ // Purpur start
+ public boolean shiftClickMended(ItemStack itemstack) {
+ if (this.player.level().purpurConfig.shiftRightClickRepairsMendingPoints > 0 && this.player.isShiftKeyDown() && this.player.getBukkitEntity().hasPermission("purpur.mending_shift_click")) {
+ int points = Math.min(this.player.totalExperience, this.player.level().purpurConfig.shiftRightClickRepairsMendingPoints);
+ if (points > 0 && itemstack.isDamaged() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MENDING, itemstack) > 0) {
+ this.player.giveExperiencePoints(-points);
+ this.player.level().addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(this.player.level(), this.player.getX(), this.player.getY(), this.player.getZ(), points, org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN, this.player, this.player));
+ return true;
+ }
+ }
+ return false;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
index 877498729c66de9aa6a27c9148f7494d7895615c..acd7468ee3c86d3456e96e4ec3d7e6a4c612e89d 100644
--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java
+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
@@ -297,6 +297,7 @@ public class WorldGenRegion implements WorldGenLevel {
return true;
} else {
// Paper start
+ if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressSetBlockFarChunk) // Purpur
if (!hasSetFarWarned) {
Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + pos + ", status: " + this.generatingStatus + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get()));
hasSetFarWarned = true;
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index 776c7df81d2b71a5610fe90475f4e8044850beab..9cc3ab8cd3f7ab7f8fbf4d9d14f25ea0bd757eec 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -265,6 +265,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
private long keepAliveTime = Util.getMillis();
private boolean keepAlivePending;
private long keepAliveChallenge;
+ private it.unimi.dsi.fastutil.longs.LongList keepAlives = new it.unimi.dsi.fastutil.longs.LongArrayList(); // Purpur
// CraftBukkit start - multithreaded fields
private final AtomicInteger chatSpamTickCount = new AtomicInteger();
private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits
@@ -342,6 +343,20 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
private boolean justTeleported = false;
private boolean hasMoved; // Spigot
+ // Purpur start
+ private final com.google.common.cache.LoadingCache<CraftPlayer, Boolean> kickPermissionCache = com.google.common.cache.CacheBuilder.newBuilder()
+ .maximumSize(1000)
+ .expireAfterWrite(1, java.util.concurrent.TimeUnit.MINUTES)
+ .build(
+ new com.google.common.cache.CacheLoader<>() {
+ @Override
+ public Boolean load(CraftPlayer player) {
+ return player.hasPermission("purpur.bypassIdleKick");
+ }
+ }
+ );
+ // Purpur end
+
public CraftPlayer getCraftPlayer() {
return (this.player == null) ? null : (CraftPlayer) this.player.getBukkitEntity();
}
@@ -397,12 +412,27 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
this.aboveGroundVehicleTickCount = 0;
}
- this.server.getProfiler().push("keepAlive");
+ //this.server.getProfiler().push("keepAlive"); // Purpur
// Paper Start - give clients a longer time to respond to pings as per pre 1.12.2 timings
// This should effectively place the keepalive handling back to "as it was" before 1.12.2
long currentTime = Util.getMillis();
long elapsedTime = currentTime - this.keepAliveTime;
+ // Purpur start
+ if (org.purpurmc.purpur.PurpurConfig.useAlternateKeepAlive) {
+ if (elapsedTime >= 1000L) { // 1 second
+ if (!processedDisconnect && keepAlives.size() * 1000L >= KEEPALIVE_LIMIT) {
+ LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName());
+ disconnect(Component.translatable("disconnect.timeout"), org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT);
+ } else {
+ keepAliveTime = currentTime; // hijack this field for 1 second intervals
+ keepAlives.add(currentTime); // currentTime is ID
+ send(new ClientboundKeepAlivePacket(currentTime));
+ }
+ }
+ } else
+ // Purpur end
+
if (this.keepAlivePending) {
if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected
ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName()); // more info
@@ -418,7 +448,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
}
// Paper end
- this.server.getProfiler().pop();
+ //this.server.getProfiler().pop(); // Purpur
// CraftBukkit start
for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !this.chatSpamTickCount.compareAndSet(spam, spam - 1); ) ;
if (tabSpamLimiter.get() > 0) tabSpamLimiter.getAndDecrement(); // Paper - split to seperate variable
@@ -435,6 +465,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
}
if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits.
+ // Purpur start
+ this.player.setAfk(true);
+ if (!this.player.level().purpurConfig.idleTimeoutKick || (!Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")) && kickPermissionCache.getUnchecked(this.player.getBukkitEntity()))) {
+ return;
+ }
+ // Purpur end
this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854
this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause
}
@@ -753,6 +789,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
this.lastYaw = to.getYaw();
this.lastPitch = to.getPitch();
+ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur
+
// Skip the first time we do this
if (true) { // Spigot - don't skip any move events
Location oldTo = to.clone();
@@ -829,6 +867,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
if (packet.getId() == this.awaitingTeleport) {
if (this.awaitingPositionFromClient == null) {
this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
+ ServerGamePacketListenerImpl.LOGGER.warn("Disconnected on accept teleport packet. Was not expecting position data from client at this time"); // Purpur
return;
}
@@ -1241,10 +1280,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
int maxBookPageSize = io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.pageMax;
double multiplier = Math.max(0.3D, Math.min(1D, io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier));
long byteAllowed = maxBookPageSize;
+ ItemStack itemstack = this.player.getInventory().getItem(packet.getSlot()); // Purpur
for (String testString : pageList) {
int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
if (byteLength > 256 * 4) {
ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!");
+ org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur
server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause
return;
}
@@ -1268,6 +1309,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
if (byteTotal > byteAllowed) {
ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size());
+ org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur
server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause
return;
}
@@ -1321,13 +1363,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
itemstack1.setTag(nbttagcompound.copy());
}
+ // Purpur start
+ boolean hasPerm = getCraftPlayer().hasPermission("purpur.book.color.edit") || getCraftPlayer().hasPermission("purpur.book.color.sign");
itemstack1.addTagElement("author", StringTag.valueOf(this.player.getName().getString()));
if (this.player.isTextFilteringEnabled()) {
- itemstack1.addTagElement("title", StringTag.valueOf(title.filteredOrEmpty()));
+ itemstack1.addTagElement("title", StringTag.valueOf(color(title.filteredOrEmpty(), hasPerm)));
} else {
- itemstack1.addTagElement("filtered_title", StringTag.valueOf(title.filteredOrEmpty()));
- itemstack1.addTagElement("title", StringTag.valueOf(title.raw()));
+ itemstack1.addTagElement("filtered_title", StringTag.valueOf(color(title.filteredOrEmpty(), hasPerm)));
+ itemstack1.addTagElement("title", StringTag.valueOf(color(title.raw(), hasPerm)));
}
+ // Purpur end
this.updateBookPages(pages, (s) -> {
return Component.Serializer.toJson(Component.literal(s));
@@ -1339,10 +1384,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
private void updateBookPages(List<FilteredText> list, UnaryOperator<String> unaryoperator, ItemStack itemstack, int slot, ItemStack handItem) { // CraftBukkit
ListTag nbttaglist = new ListTag();
+ // Purpur start
+ boolean hasPerm = getCraftPlayer().hasPermission("purpur.book.color.edit");
if (this.player.isTextFilteringEnabled()) {
- Stream<StringTag> stream = list.stream().map((filteredtext) -> { // CraftBukkit - decompile error
- return StringTag.valueOf((String) unaryoperator.apply(filteredtext.filteredOrEmpty()));
+ Stream<StringTag> stream = list.stream().map(s -> color(s.filteredOrEmpty(), hasPerm, false)).map((s) -> { // CraftBukkit - decompile error
+ return StringTag.valueOf((String) unaryoperator.apply(s));
});
+ // Purpur end
Objects.requireNonNull(nbttaglist);
stream.forEach(nbttaglist::add);
@@ -1352,11 +1400,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
for (int j = list.size(); i < j; ++i) {
FilteredText filteredtext = (FilteredText) list.get(i);
- String s = filteredtext.raw();
+ String s = color(filteredtext.raw(), hasPerm, false); // Purpur
nbttaglist.add(StringTag.valueOf((String) unaryoperator.apply(s)));
if (filteredtext.isFiltered()) {
- nbttagcompound.putString(String.valueOf(i), (String) unaryoperator.apply(filteredtext.filteredOrEmpty()));
+ nbttagcompound.putString(String.valueOf(i), (String) unaryoperator.apply((String) color(filteredtext.filteredOrEmpty(), hasPerm, false))); // Purpur
}
}
@@ -1369,6 +1417,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
this.player.getInventory().setItem(slot, CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent)
}
+ // Purpur start
+ private String color(String str, boolean hasPerm) {
+ return color(str, hasPerm, true);
+ }
+
+ private String color(String str, boolean hasPerm, boolean parseHex) {
+ return hasPerm ? org.bukkit.ChatColor.color(str, parseHex) : str;
+ }
+ // Purpur end
+
@Override
public void handleEntityTagQuery(ServerboundEntityTagQuery packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -1398,8 +1456,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
@Override
public void handleMovePlayer(ServerboundMovePlayerPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
- if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) {
+ // Purpur start
+ boolean invalidX = Double.isNaN(packet.getX(0.0D));
+ boolean invalidY = Double.isNaN(packet.getY(0.0D));
+ boolean invalidZ = Double.isNaN(packet.getZ(0.0D));
+ boolean invalidYaw = !Floats.isFinite(packet.getYRot(0.0F));
+ boolean invalidPitch = !Floats.isFinite(packet.getXRot(0.0F));
+ if (invalidX || invalidY || invalidZ || invalidYaw || invalidPitch) {
this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
+ ServerGamePacketListenerImpl.LOGGER.warn(String.format("Disconnected on move player packet. Invalid data: x=%b, y=%b, z=%b, yaw=%b, pitch=%b", invalidX, invalidY, invalidZ, invalidYaw, invalidPitch));
+ // Purpur end
} else {
ServerLevel worldserver = this.player.serverLevel();
@@ -1582,7 +1648,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
if (!event.isAllowed()) {
flag2 = true; // Paper - diff on change, this should be moved wrongly
if (event.getLogWarning())
- ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
+ ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!, ({})", this.player.getName().getString(), d11); // Purpur
}
// Paper end
}
@@ -1644,6 +1710,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
this.lastYaw = to.getYaw();
this.lastPitch = to.getPitch();
+ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur
+
// Skip the first time we do this
if (from.getX() != Double.MAX_VALUE) {
Location oldTo = to.clone();
@@ -1682,6 +1750,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
this.player.resetFallDistance();
}
+ // Purpur Start
+ if (this.player.level().purpurConfig.dontRunWithScissors && this.player.isSprinting() && !(this.player.level().purpurConfig.ignoreScissorsInWater && this.player.isInWater()) && !(this.player.level().purpurConfig.ignoreScissorsInLava && this.player.isInLava()) && (isScissor(this.player.getItemInHand(InteractionHand.MAIN_HAND)) || isScissor(this.player.getItemInHand(InteractionHand.OFF_HAND))) && (int) (Math.random() * 10) == 0) {
+ this.player.hurt(this.player.damageSources().scissors(), (float) this.player.level().purpurConfig.scissorsRunningDamage);
+ if (!org.purpurmc.purpur.PurpurConfig.dontRunWithScissors.isBlank()) this.player.sendActionBarMessage(org.purpurmc.purpur.PurpurConfig.dontRunWithScissors);
+ }
+ // Purpur End
+
this.player.checkMovementStatistics(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5);
this.lastGoodX = this.player.getX();
this.lastGoodY = this.player.getY();
@@ -1726,6 +1801,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
}
}
// Paper end - optimise out extra getCubes
+
+ // Purpur start
+ public boolean isScissor(ItemStack stack) {
+ return stack.is(Items.SHEARS) && (stack.getTag() == null || stack.getTag().getInt("CustomModelData") == 0);
+ }
+ // Purpur end
+
private boolean isPlayerCollidingWithAnythingNew(LevelReader world, AABB box, double newX, double newY, double newZ) {
AABB axisalignedbb1 = this.player.getBoundingBox().move(newX - this.player.getX(), newY - this.player.getY(), newZ - this.player.getZ());
Iterable<VoxelShape> iterable = world.getCollisions(this.player, axisalignedbb1.deflate(9.999999747378752E-6D));
@@ -2071,6 +2153,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
boolean cancelled;
if (movingobjectposition == null || movingobjectposition.getType() != HitResult.Type.BLOCK) {
+ if (this.player.gameMode.shiftClickMended(itemstack)) return; // Purpur
org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack, enumhand);
cancelled = event.useItemInHand() == Event.Result.DENY;
} else {
@@ -2125,12 +2208,21 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
@Override
public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+ // Purpur start
+ if (player.level().purpurConfig.playerInvulnerableWhileAcceptingResourcePack && !this.player.acceptingResourcePack) {
+ ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack packet exploitation attempt", this.player.getName());
+ this.disconnect(Component.translatable("multiplayer.texturePrompt.failure.line1"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // "Server resource pack couldn't be applied"
+ return;
+ }
+ // Purpur end
if (packet.getAction() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) {
ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack rejection", this.player.getGameProfile().getName()); // Paper - Don't print component in resource pack rejection message
this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - add cause
}
// Paper start
PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action.ordinal()];
+ if (player.level().purpurConfig.playerInvulnerableWhileAcceptingResourcePack) player.setFrozen(packStatus == PlayerResourcePackStatusEvent.Status.ACCEPTED); // Purpur
+ this.player.acceptingResourcePack = packStatus == PlayerResourcePackStatusEvent.Status.ACCEPTED; // Purpur
player.getBukkitEntity().setResourcePackStatus(packStatus);
this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packStatus)); // CraftBukkit
// Paper end
@@ -2427,7 +2519,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
do {
instant1 = (Instant) this.lastChatTimeStamp.get();
if (timestamp.isBefore(instant1)) {
- return false;
+ return !org.purpurmc.purpur.PurpurConfig.kickForOutOfOrderChat; // Purpur
}
} while (!this.lastChatTimeStamp.compareAndSet(instant1, timestamp));
@@ -2558,7 +2650,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
}
}
// Paper End
- co.aikar.timings.MinecraftTimings.playerCommandTimer.startTiming(); // Paper
+ //co.aikar.timings.MinecraftTimings.playerCommandTimer.startTiming(); // Paper // Purpur
if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot
this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s);
@@ -2568,7 +2660,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
this.cserver.getPluginManager().callEvent(event);
if (event.isCancelled()) {
- co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper
+ //co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper // Purpur
return;
}
@@ -2581,7 +2673,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
java.util.logging.Logger.getLogger(ServerGamePacketListenerImpl.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
return;
} finally {
- co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper
+ //co.aikar.timings.MinecraftTimings.playerCommandTimer.stopTiming(); // Paper // Purpur
}
}
// CraftBukkit end
@@ -2857,6 +2949,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
AABB axisalignedbb = entity.getBoundingBox();
if (axisalignedbb.distanceToSqr(this.player.getEyePosition()) < ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE) {
+ if (entity instanceof Mob mob) mob.ticksSinceLastInteraction = 0; // Purpur
packet.dispatch(new ServerboundInteractPacket.Handler() {
private void performInteraction(InteractionHand enumhand, ServerGamePacketListenerImpl.EntityInteraction playerconnection_a, PlayerInteractEntityEvent event) { // CraftBukkit
ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand);
@@ -2870,6 +2963,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event);
+ player.processClick(enumhand); // Purpur
+
// Entity in bucket - SPIGOT-4048 and SPIGOT-6859a
if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) {
entity.getEntityData().resendPossiblyDesyncedEntity(player); // Paper - The entire mob gets deleted, so resend it.
@@ -3450,6 +3545,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
}
}
}
+ // Purpur start
+ if (org.purpurmc.purpur.PurpurConfig.fixNetworkSerializedItemsInCreative) {
+ var tag = itemstack.getTagElement("Purpur.OriginalItem");
+ if (tag != null) itemstack = ItemStack.of(tag);
+ }
+ // Purpur end
boolean flag1 = packet.getSlotNum() >= 1 && packet.getSlotNum() <= 45;
boolean flag2 = itemstack.isEmpty() || itemstack.getDamageValue() >= 0 && itemstack.getCount() <= 64 && !itemstack.isEmpty();
@@ -3545,6 +3646,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
@Override
public void handleKeepAlive(ServerboundKeepAlivePacket packet) {
+ // Purpur start
+ if (org.purpurmc.purpur.PurpurConfig.useAlternateKeepAlive) {
+ long id = packet.getId();
+ if (keepAlives.size() > 0 && keepAlives.contains(id)) {
+ int ping = (int) (Util.getMillis() - id);
+ player.latency = (player.latency * 3 + ping) / 4;
+ keepAlives.clear(); // we got a valid response, lets roll with it and forget the rest
+ }
+ } else
+ // Purpur end
//PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // CraftBukkit // Paper - This shouldn't be on the main thread
if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) {
int i = (int) (Util.getMillis() - this.keepAliveTime);
@@ -3595,6 +3706,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
private static final ResourceLocation CUSTOM_UNREGISTER = new ResourceLocation("unregister");
private static final ResourceLocation MINECRAFT_BRAND = new ResourceLocation("brand"); // Paper - Brand support
+ private static final ResourceLocation PURPUR_CLIENT = new ResourceLocation("purpur", "client"); // Purpur
@Override
public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
@@ -3619,6 +3731,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex);
this.disconnect("Invalid payload UNREGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
}
+ // Purpur start
+ } else if (packet.identifier.equals(PURPUR_CLIENT)) {
+ try {
+ player.purpurClient = true;
+ } catch (Exception ignore) {
+ }
+ // Purpur end
} else {
try {
byte[] data = new byte[packet.data.readableBytes()];
diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index 878001928327d92423d5f7f6d5ce8772d6fa477f..01d5fa265fb2818465b5a71a2e2efeec751a7a05 100644
--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -221,6 +221,8 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
return false;
}
+ if (true) return org.purpurmc.purpur.PurpurConfig.usernameValidCharactersPattern.matcher(in).matches(); // Purpur
+
for (int i = 0, len = in.length(); i < len; ++i) {
char c = in.charAt(i);
@@ -340,7 +342,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
ServerLoginPacketListenerImpl.this.gameProfile = gameprofile;
ServerLoginPacketListenerImpl.this.state = ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT;
} else {
- ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username"));
+ ServerLoginPacketListenerImpl.this.disconnect(org.purpurmc.purpur.PurpurConfig.unverifiedUsername.equals("default") ? Component.translatable("multiplayer.disconnect.unverified_username") : io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.unverifiedUsername))); // Purpur
ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", gameprofile.getName());
}
} catch (AuthenticationUnavailableException authenticationunavailableexception) {
diff --git a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
index 2c13147bc063a09bb7907d6f90c3a1e811a09eb1..3edb0c392cec7fdabebad81da1a8b06a700fbfca 100644
--- a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
@@ -153,6 +153,7 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene
this.connection.send(new ClientboundStatusResponsePacket(ping));
// CraftBukkit end
*/
+ if (MinecraftServer.getServer().getStatus().version().isEmpty()) return; // Purpur - do not respond to pings before we know the protocol version
com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(MinecraftServer.getServer(), this.connection);
// Paper end
}
diff --git a/src/main/java/net/minecraft/server/packs/resources/ResourceManagerReloadListener.java b/src/main/java/net/minecraft/server/packs/resources/ResourceManagerReloadListener.java
index 9ddbfcf80d9a381dace78a62880f85a4d767e0eb..7383c7d3820dce06108eaafd236a7c6c06a10a42 100644
--- a/src/main/java/net/minecraft/server/packs/resources/ResourceManagerReloadListener.java
+++ b/src/main/java/net/minecraft/server/packs/resources/ResourceManagerReloadListener.java
@@ -9,11 +9,11 @@ public interface ResourceManagerReloadListener extends PreparableReloadListener
@Override
default CompletableFuture<Void> reload(PreparableReloadListener.PreparationBarrier synchronizer, ResourceManager manager, ProfilerFiller prepareProfiler, ProfilerFiller applyProfiler, Executor prepareExecutor, Executor applyExecutor) {
return synchronizer.wait(Unit.INSTANCE).thenRunAsync(() -> {
- applyProfiler.startTick();
- applyProfiler.push("listener");
+ //applyProfiler.startTick(); // Purpur
+ //applyProfiler.push("listener"); // Purpur
this.onResourceManagerReload(manager);
- applyProfiler.pop();
- applyProfiler.endTick();
+ //applyProfiler.pop(); // Purpur
+ //applyProfiler.endTick(); // Purpur
}, applyExecutor);
}
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index f097ec5b4e3ad6b1a7c464a8cff4f8b2568fcf4f..824f31b68b38f2f8642fb9d59a123cfdaffbb7b2 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -484,6 +484,7 @@ public abstract class PlayerList {
scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam);
}
// Paper end
+ org.purpurmc.purpur.task.BossBarTask.addToAll(player); // Purpur
// CraftBukkit - Moved from above, added world
PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ());
}
@@ -593,6 +594,7 @@ public abstract class PlayerList {
}
public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) {
// Paper end
+ org.purpurmc.purpur.task.BossBarTask.removeFromAll(entityplayer.getBukkitEntity()); // Purpur
ServerLevel worldserver = entityplayer.serverLevel();
entityplayer.awardStat(Stats.LEAVE_GAME);
@@ -747,7 +749,7 @@ public abstract class PlayerList {
event.disallow(PlayerLoginEvent.Result.KICK_BANNED, PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure
} else {
// return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null;
- if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile)) {
+ if (this.players.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameprofile))) { // Purpur
event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure
}
}
@@ -985,6 +987,8 @@ public abstract class PlayerList {
}
// Paper end
+ entityplayer1.spawnInvulnerableTime = entityplayer1.level().purpurConfig.playerSpawnInvulnerableTicks; // Purpur
+
// CraftBukkit end
return entityplayer1;
}
@@ -1045,6 +1049,20 @@ public abstract class PlayerList {
}
// CraftBukkit end
+ // Purpur Start
+ public void broadcastMiniMessage(@Nullable String message, boolean overlay) {
+ if (message != null && !message.isEmpty()) {
+ this.broadcastMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), overlay);
+ }
+ }
+
+ public void broadcastMessage(@Nullable net.kyori.adventure.text.Component message, boolean overlay) {
+ if (message != null) {
+ this.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), overlay);
+ }
+ }
+ // Purpur end
+
public void broadcastAll(Packet<?> packet, ResourceKey<Level> dimension) {
Iterator iterator = this.players.iterator();
@@ -1148,6 +1166,7 @@ public abstract class PlayerList {
} else {
b0 = (byte) (24 + permissionLevel);
}
+ if (b0 < 28 && player.getBukkitEntity().hasPermission("purpur.debug.f3n")) b0 = 28; // Purpur
player.connection.send(new ClientboundEntityEventPacket(player, b0));
}
@@ -1156,6 +1175,27 @@ public abstract class PlayerList {
player.getBukkitEntity().recalculatePermissions(); // CraftBukkit
this.server.getCommands().sendCommands(player);
} // Paper
+
+ // Purpur start
+ if (org.purpurmc.purpur.PurpurConfig.enderChestSixRows && org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) {
+ org.bukkit.craftbukkit.entity.CraftHumanEntity bukkit = player.getBukkitEntity();
+ if (bukkit.hasPermission("purpur.enderchest.rows.six")) {
+ player.sixRowEnderchestSlotCount = 54;
+ } else if (bukkit.hasPermission("purpur.enderchest.rows.five")) {
+ player.sixRowEnderchestSlotCount = 45;
+ } else if (bukkit.hasPermission("purpur.enderchest.rows.four")) {
+ player.sixRowEnderchestSlotCount = 36;
+ } else if (bukkit.hasPermission("purpur.enderchest.rows.three")) {
+ player.sixRowEnderchestSlotCount = 27;
+ } else if (bukkit.hasPermission("purpur.enderchest.rows.two")) {
+ player.sixRowEnderchestSlotCount = 18;
+ } else if (bukkit.hasPermission("purpur.enderchest.rows.one")) {
+ player.sixRowEnderchestSlotCount = 9;
+ }
+ } else {
+ player.sixRowEnderchestSlotCount = -1;
+ }
+ //Purpur end
}
public boolean isWhiteListed(GameProfile profile) {
@@ -1217,7 +1257,7 @@ public abstract class PlayerList {
public void saveAll(int interval) {
io.papermc.paper.util.MCUtil.ensureMain("Save Players" , () -> { // Paper - Ensure main
- MinecraftTimings.savePlayers.startTiming(); // Paper
+ //MinecraftTimings.savePlayers.startTiming(); // Paper // Purpur
int numSaved = 0;
long now = MinecraftServer.currentTick;
for (int i = 0; i < this.players.size(); ++i) {
@@ -1228,7 +1268,7 @@ public abstract class PlayerList {
}
// Paper end
}
- MinecraftTimings.savePlayers.stopTiming(); // Paper
+ //MinecraftTimings.savePlayers.stopTiming(); // Paper // Purpur
return null; }); // Paper - ensure main
}
diff --git a/src/main/java/net/minecraft/server/players/SleepStatus.java b/src/main/java/net/minecraft/server/players/SleepStatus.java
index 823efad652d8ff9e96b99375b102fef6f017716e..caa8a69bde0c212c36dd990a67836ac2f95548c0 100644
--- a/src/main/java/net/minecraft/server/players/SleepStatus.java
+++ b/src/main/java/net/minecraft/server/players/SleepStatus.java
@@ -19,7 +19,7 @@ public class SleepStatus {
public boolean areEnoughDeepSleeping(int percentage, List<ServerPlayer> players) {
// CraftBukkit start
- int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping; }).count();
+ int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping || (eh.level().purpurConfig.idleTimeoutCountAsSleeping && eh.isAfk()); }).count(); // Purpur
boolean anyDeepSleep = players.stream().anyMatch(Player::isSleepingLongEnough);
return anyDeepSleep && j >= this.sleepersNeeded(percentage);
@@ -52,7 +52,7 @@ public class SleepStatus {
if (!entityplayer.isSpectator()) {
++this.activePlayers;
- if (entityplayer.isSleeping() || entityplayer.fauxSleeping) { // CraftBukkit
+ if ((entityplayer.isSleeping() || entityplayer.fauxSleeping) || (entityplayer.level().purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk())) { // CraftBukkit // Purpur
++this.sleepingPlayers;
}
// CraftBukkit start
diff --git a/src/main/java/net/minecraft/stats/ServerRecipeBook.java b/src/main/java/net/minecraft/stats/ServerRecipeBook.java
index ea29e07a105f3ba6a878bdccf36e7eaf66280280..d5dce6c8d85938d61a57a78f82381d26daf8f87a 100644
--- a/src/main/java/net/minecraft/stats/ServerRecipeBook.java
+++ b/src/main/java/net/minecraft/stats/ServerRecipeBook.java
@@ -125,6 +125,7 @@ public class ServerRecipeBook extends RecipeBook {
Optional<? extends Recipe<?>> optional = recipeManager.byKey(minecraftkey);
if (!optional.isPresent()) {
+ if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressUnrecognizedRecipeErrors) // Purpur
ServerRecipeBook.LOGGER.error("Tried to load unrecognized recipe: {} removed now.", minecraftkey);
} else {
handler.accept((Recipe) optional.get());
diff --git a/src/main/java/net/minecraft/util/profiling/ActiveProfiler.java b/src/main/java/net/minecraft/util/profiling/ActiveProfiler.java
index 196c7331138fee2822c76aacd136f9da040e0049..c6c30d99399c5cde2b0ec2f320d81d952b422d78 100644
--- a/src/main/java/net/minecraft/util/profiling/ActiveProfiler.java
+++ b/src/main/java/net/minecraft/util/profiling/ActiveProfiler.java
@@ -55,7 +55,7 @@ public class ActiveProfiler implements ProfileCollector {
this.started = true;
this.path = "";
this.paths.clear();
- this.push("root");
+ //this.push("root"); // Purpur
}
}
@@ -64,7 +64,7 @@ public class ActiveProfiler implements ProfileCollector {
if (!this.started) {
LOGGER.error("Profiler tick already ended - missing startTick()?");
} else {
- this.pop();
+ //this.pop(); // Purpur
this.started = false;
if (!this.path.isEmpty()) {
LOGGER.error("Profiler tick ended before path was fully popped (remainder: '{}'). Mismatched push/pop?", LogUtils.defer(() -> {
@@ -93,7 +93,7 @@ public class ActiveProfiler implements ProfileCollector {
@Override
public void push(Supplier<String> locationGetter) {
- this.push(locationGetter.get());
+ //this.push(locationGetter.get()); // Purpur
}
@Override
@@ -132,14 +132,14 @@ public class ActiveProfiler implements ProfileCollector {
@Override
public void popPush(String location) {
- this.pop();
- this.push(location);
+ //this.pop(); // Purpur
+ //this.push(location); // Purpur
}
@Override
public void popPush(Supplier<String> locationGetter) {
- this.pop();
- this.push(locationGetter);
+ //this.pop(); // Purpur
+ //this.push(locationGetter); // Purpur
}
private ActiveProfiler.PathEntry getCurrentEntry() {
diff --git a/src/main/java/net/minecraft/util/profiling/ProfilerFiller.java b/src/main/java/net/minecraft/util/profiling/ProfilerFiller.java
index 2e6e8eac987c4ef6b2dcd3de592d8a51d2b29792..863343a87fe34d72f04af89d75268b477b2adc7a 100644
--- a/src/main/java/net/minecraft/util/profiling/ProfilerFiller.java
+++ b/src/main/java/net/minecraft/util/profiling/ProfilerFiller.java
@@ -6,32 +6,44 @@ import net.minecraft.util.profiling.metrics.MetricCategory;
public interface ProfilerFiller {
String ROOT = "root";
+ @io.papermc.paper.annotation.DoNotUse // Purpur
void startTick();
+ @io.papermc.paper.annotation.DoNotUse // Purpur
void endTick();
+ @io.papermc.paper.annotation.DoNotUse // Purpur
void push(String location);
+ @io.papermc.paper.annotation.DoNotUse // Purpur
void push(Supplier<String> locationGetter);
+ @io.papermc.paper.annotation.DoNotUse // Purpur
void pop();
+ @io.papermc.paper.annotation.DoNotUse // Purpur
void popPush(String location);
+ @io.papermc.paper.annotation.DoNotUse // Purpur
void popPush(Supplier<String> locationGetter);
+ @io.papermc.paper.annotation.DoNotUse // Purpur
void markForCharting(MetricCategory type);
+ @io.papermc.paper.annotation.DoNotUse // Purpur
default void incrementCounter(String marker) {
- this.incrementCounter(marker, 1);
+ //this.incrementCounter(marker, 1); // Purpur
}
+ @io.papermc.paper.annotation.DoNotUse // Purpur
void incrementCounter(String marker, int num);
+ @io.papermc.paper.annotation.DoNotUse // Purpur
default void incrementCounter(Supplier<String> markerGetter) {
- this.incrementCounter(markerGetter, 1);
+ //this.incrementCounter(markerGetter, 1); // Purpur
}
+ @io.papermc.paper.annotation.DoNotUse // Purpur
void incrementCounter(Supplier<String> markerGetter, int num);
static ProfilerFiller tee(final ProfilerFiller a, final ProfilerFiller b) {
@@ -41,62 +53,62 @@ public interface ProfilerFiller {
return b == InactiveProfiler.INSTANCE ? a : new ProfilerFiller() {
@Override
public void startTick() {
- a.startTick();
- b.startTick();
+ //a.startTick(); // Purpur
+ //b.startTick(); // Purpur
}
@Override
public void endTick() {
- a.endTick();
- b.endTick();
+ //a.endTick(); // Purpur
+ //b.endTick(); // Purpur
}
@Override
public void push(String location) {
- a.push(location);
- b.push(location);
+ //a.push(location); // Purpur
+ //b.push(location); // Purpur
}
@Override
public void push(Supplier<String> locationGetter) {
- a.push(locationGetter);
- b.push(locationGetter);
+ //a.push(locationGetter); // Purpur
+ //b.push(locationGetter); // Purpur
}
@Override
public void markForCharting(MetricCategory type) {
- a.markForCharting(type);
- b.markForCharting(type);
+ //a.markForCharting(type); // Purpur
+ //b.markForCharting(type); // Purpur
}
@Override
public void pop() {
- a.pop();
- b.pop();
+ //a.pop(); // Purpur
+ //b.pop(); // Purpur
}
@Override
public void popPush(String location) {
- a.popPush(location);
- b.popPush(location);
+ //a.popPush(location); // Purpur
+ //b.popPush(location); // Purpur
}
@Override
public void popPush(Supplier<String> locationGetter) {
- a.popPush(locationGetter);
- b.popPush(locationGetter);
+ //a.popPush(locationGetter); // Purpur
+ //b.popPush(locationGetter); // Purpur
}
@Override
public void incrementCounter(String marker, int num) {
- a.incrementCounter(marker, num);
- b.incrementCounter(marker, num);
+ //a.incrementCounter(marker, num); // Purpur
+ //b.incrementCounter(marker, num); // Purpur
}
@Override
public void incrementCounter(Supplier<String> markerGetter, int num) {
- a.incrementCounter(markerGetter, num);
- b.incrementCounter(markerGetter, num);
+ //a.incrementCounter(markerGetter, num); // Purpur
+ //b.incrementCounter(markerGetter, num); // Purpur
}
};
}
diff --git a/src/main/java/net/minecraft/world/damagesource/CombatRules.java b/src/main/java/net/minecraft/world/damagesource/CombatRules.java
index ccbfcef3e83b1bef364447657bfd08a92d615cf6..aa2331c6df4e79d4bb0add071a0b11d2a3a08b88 100644
--- a/src/main/java/net/minecraft/world/damagesource/CombatRules.java
+++ b/src/main/java/net/minecraft/world/damagesource/CombatRules.java
@@ -11,12 +11,12 @@ public class CombatRules {
public static float getDamageAfterAbsorb(float damage, float armor, float armorToughness) {
float f = 2.0F + armorToughness / 4.0F;
- float g = Mth.clamp(armor - damage / f, armor * 0.2F, 20.0F);
+ float g = Mth.clamp(armor - damage / f, armor * 0.2F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur
return damage * (1.0F - g / 25.0F);
}
public static float getDamageAfterMagicAbsorb(float damageDealt, float protection) {
- float f = Mth.clamp(protection, 0.0F, 20.0F);
+ float f = Mth.clamp(protection, 0.0F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur
return damageDealt * (1.0F - f / 25.0F);
}
}
diff --git a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java
index c12d7bacf2c54f268b1bc5e46250a083ca041415..45a1cbe958c1a626df97022afe22484f2b387233 100644
--- a/src/main/java/net/minecraft/world/damagesource/CombatTracker.java
+++ b/src/main/java/net/minecraft/world/damagesource/CombatTracker.java
@@ -59,7 +59,7 @@ public class CombatTracker {
}
ItemStack itemStack = var10000;
- return !itemStack.isEmpty() && itemStack.hasCustomHoverName() ? Component.translatable(itemDeathTranslationKey, this.mob.getDisplayName(), attackerDisplayName, itemStack.getDisplayName()) : Component.translatable(deathTranslationKey, this.mob.getDisplayName(), attackerDisplayName);
+ return !itemStack.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemStack.hasCustomHoverName()) ? Component.translatable(itemDeathTranslationKey, this.mob.getDisplayName(), attackerDisplayName, itemStack.getDisplayName()) : Component.translatable(deathTranslationKey, this.mob.getDisplayName(), attackerDisplayName);
}
private Component getFallMessage(CombatEntry damageRecord, @Nullable Entity attacker) {
@@ -99,6 +99,13 @@ public class CombatTracker {
Component component = ComponentUtils.wrapInSquareBrackets(Component.translatable(string + ".link")).withStyle(INTENTIONAL_GAME_DESIGN_STYLE);
return Component.translatable(string + ".message", this.mob.getDisplayName(), component);
} else {
+ // Purpur start
+ if (damageSource.isScissors) {
+ return damageSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgRunWithScissors, this.mob);
+ } else if (damageSource.isStoneCutter) {
+ return damageSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgStonecutter, this.mob);
+ }
+ // Purpur end
return damageSource.getLocalizedDeathMessage(this.mob);
}
}
diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java
index 25a5a3b949a0eb632611355e74ccd4865be108ca..c8860f20956a2819da001e93938249452bf7cb49 100644
--- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java
+++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java
@@ -54,6 +54,10 @@ public class DamageSource {
// CraftBukkit end
public @Nullable org.bukkit.block.BlockState explodedBlockState; // Paper - add exploded state
+ public boolean isScissors; // Purpur
+
+ public boolean isStoneCutter; // Purpur
+
public String toString() {
return "DamageSource (" + this.type().msgId() + ")";
}
@@ -122,10 +126,19 @@ public class DamageSource {
ItemStack itemstack1 = itemstack;
- return !itemstack1.isEmpty() && itemstack1.hasCustomHoverName() ? Component.translatable(s + ".item", killed.getDisplayName(), ichatbasecomponent, itemstack1.getDisplayName()) : Component.translatable(s, killed.getDisplayName(), ichatbasecomponent);
+ return !itemstack1.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemstack1.hasCustomHoverName()) ? Component.translatable(s + ".item", killed.getDisplayName(), ichatbasecomponent, itemstack1.getDisplayName()) : Component.translatable(s, killed.getDisplayName(), ichatbasecomponent);
}
}
+ // Purpur start
+ public Component getLocalizedDeathMessage(String str, LivingEntity entity) {
+ net.kyori.adventure.text.Component name = io.papermc.paper.adventure.PaperAdventure.asAdventure(entity.getDisplayName());
+ net.kyori.adventure.text.minimessage.tag.resolver.TagResolver template = net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("player", name);
+ net.kyori.adventure.text.Component component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(str, template);
+ return io.papermc.paper.adventure.PaperAdventure.asVanilla(component);
+ }
+ // Purpur end
+
public String getMsgId() {
return this.type().msgId();
}
diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSources.java b/src/main/java/net/minecraft/world/damagesource/DamageSources.java
index 4604f8b38460e9113e966889a679d4547f24aff6..4a5d2e263d2bbee96bde7012d3385fa33860bc1b 100644
--- a/src/main/java/net/minecraft/world/damagesource/DamageSources.java
+++ b/src/main/java/net/minecraft/world/damagesource/DamageSources.java
@@ -267,4 +267,17 @@ public class DamageSources {
public DamageSource genericKill() {
return this.genericKill;
}
+ // Purpur start
+ public DamageSource scissors() {
+ DamageSource source = new DamageSource(this.damageTypes.getHolderOrThrow(DamageTypes.MAGIC));
+ source.isScissors = true;
+ return source;
+ }
+
+ public DamageSource stonecutter() {
+ DamageSource source = new DamageSource(this.damageTypes.getHolderOrThrow(DamageTypes.MAGIC));
+ source.isStoneCutter = true;
+ return source;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/effect/MobEffect.java b/src/main/java/net/minecraft/world/effect/MobEffect.java
index 53cc6befb752affcfec65e18365f6d369448d407..01850fc596a85974287ff6750427186d21acac41 100644
--- a/src/main/java/net/minecraft/world/effect/MobEffect.java
+++ b/src/main/java/net/minecraft/world/effect/MobEffect.java
@@ -59,16 +59,16 @@ public class MobEffect {
public void applyEffectTick(LivingEntity entity, int amplifier) {
if (this == MobEffects.REGENERATION) {
if (entity.getHealth() < entity.getMaxHealth()) {
- entity.heal(1.0F, RegainReason.MAGIC_REGEN); // CraftBukkit
+ entity.heal(entity.level().purpurConfig.entityHealthRegenAmount, RegainReason.MAGIC_REGEN); // CraftBukkit // Purpur
}
} else if (this == MobEffects.POISON) {
- if (entity.getHealth() > 1.0F) {
- entity.hurt(entity.damageSources().poison, 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON
+ if (entity.getHealth() > entity.level().purpurConfig.entityMinimalHealthPoison) { // Purpur
+ entity.hurt(entity.damageSources().poison, entity.level().purpurConfig.entityPoisonDegenerationAmount); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON // Purpur
}
} else if (this == MobEffects.WITHER) {
- entity.hurt(entity.damageSources().wither(), 1.0F);
+ entity.hurt(entity.damageSources().wither(), entity.level().purpurConfig.entityWitherDegenerationAmount); // Purpur
} else if (this == MobEffects.HUNGER && entity instanceof Player) {
- ((Player) entity).causeFoodExhaustion(0.005F * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent
+ ((Player) entity).causeFoodExhaustion(entity.level().purpurConfig.humanHungerExhaustionAmount * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent // Purpur
} else if (this == MobEffects.SATURATION && entity instanceof Player) {
if (!entity.level().isClientSide) {
// CraftBukkit start
@@ -76,7 +76,7 @@ public class MobEffect {
int oldFoodLevel = entityhuman.getFoodData().foodLevel;
org.bukkit.event.entity.FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(entityhuman, amplifier + 1 + oldFoodLevel);
if (!event.isCancelled()) {
- entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F);
+ entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, entity.level().purpurConfig.humanSaturationRegenAmount); // Purpur
}
((CraftPlayer) entityhuman.getBukkitEntity()).sendHealthUpdate();
diff --git a/src/main/java/net/minecraft/world/effect/MobEffectInstance.java b/src/main/java/net/minecraft/world/effect/MobEffectInstance.java
index 14fab63346d56c72cd7534a04760efd10eef4295..745e792482f61c571e2efbd4200dd1bdaef6e474 100644
--- a/src/main/java/net/minecraft/world/effect/MobEffectInstance.java
+++ b/src/main/java/net/minecraft/world/effect/MobEffectInstance.java
@@ -14,6 +14,7 @@ import net.minecraft.util.ExtraCodecs;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.LivingEntity;
import org.slf4j.Logger;
+import org.bukkit.NamespacedKey;
public class MobEffectInstance implements Comparable<MobEffectInstance> {
private static final Logger LOGGER = LogUtils.getLogger();
@@ -25,6 +26,7 @@ public class MobEffectInstance implements Comparable<MobEffectInstance> {
private boolean visible;
private boolean showIcon;
@Nullable
+ private NamespacedKey key; // Purpur - add key
private MobEffectInstance hiddenEffect;
private final Optional<MobEffectInstance.FactorData> factorData;
@@ -44,17 +46,36 @@ public class MobEffectInstance implements Comparable<MobEffectInstance> {
this(type, duration, amplifier, ambient, visible, visible);
}
+ // Purpur start
+ public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean visible, @Nullable NamespacedKey key) {
+ this(type, duration, amplifier, ambient, visible, visible, key);
+ }
+ // Purpur end
+
public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon) {
- this(type, duration, amplifier, ambient, showParticles, showIcon, (MobEffectInstance)null, type.createFactorData());
+ // Purpur start
+ this(type, duration, amplifier, ambient, showParticles, showIcon, (MobEffectInstance)null, type.createFactorData(), (NamespacedKey)null);
+ }
+
+ public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon, @Nullable NamespacedKey key) {
+ this(type, duration, amplifier, ambient, showParticles, showIcon, (MobEffectInstance)null, type.createFactorData(), key);
+ // Purpur end
}
public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon, @Nullable MobEffectInstance hiddenEffect, Optional<MobEffectInstance.FactorData> factorCalculationData) {
+ // Purpur start
+ this(type, duration, amplifier, ambient, showParticles, showIcon, hiddenEffect, factorCalculationData, (NamespacedKey) null);
+ }
+
+ public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon, @Nullable MobEffectInstance hiddenEffect, Optional<MobEffectInstance.FactorData> factorCalculationData, @Nullable NamespacedKey key) {
+ // Purpur end
this.effect = type;
this.duration = duration;
this.amplifier = amplifier;
this.ambient = ambient;
this.visible = showParticles;
this.showIcon = showIcon;
+ this.key = key; // Purpur - add key
this.hiddenEffect = hiddenEffect;
this.factorData = factorCalculationData;
}
@@ -75,6 +96,7 @@ public class MobEffectInstance implements Comparable<MobEffectInstance> {
this.ambient = that.ambient;
this.visible = that.visible;
this.showIcon = that.showIcon;
+ this.key = that.key; // Purpur - add key
}
public boolean update(MobEffectInstance that) {
@@ -120,6 +142,13 @@ public class MobEffectInstance implements Comparable<MobEffectInstance> {
bl = true;
}
+ // Purpur start
+ if (that.key != this.key) {
+ this.key = that.key;
+ bl = true;
+ }
+ // Purpur end
+
return bl;
}
@@ -163,6 +192,17 @@ public class MobEffectInstance implements Comparable<MobEffectInstance> {
return this.showIcon;
}
+ // Purpur start
+ public boolean hasKey() {
+ return this.key != null;
+ }
+
+ @Nullable
+ public NamespacedKey getKey() {
+ return this.key;
+ }
+ // Purpur end
+
public boolean tick(LivingEntity entity, Runnable overwriteCallback) {
if (this.hasRemainingDuration()) {
int i = this.isInfiniteDuration() ? entity.tickCount : this.duration;
@@ -226,6 +266,12 @@ public class MobEffectInstance implements Comparable<MobEffectInstance> {
string = string + ", Show Icon: false";
}
+ // Purpur start
+ if (this.hasKey()) {
+ string = string + ", Key: " + this.key;
+ }
+ // Purpur end
+
return string;
}
@@ -241,7 +287,7 @@ public class MobEffectInstance implements Comparable<MobEffectInstance> {
return false;
} else {
MobEffectInstance mobEffectInstance = (MobEffectInstance)object;
- return this.duration == mobEffectInstance.duration && this.amplifier == mobEffectInstance.amplifier && this.ambient == mobEffectInstance.ambient && this.effect.equals(mobEffectInstance.effect);
+ return this.duration == mobEffectInstance.duration && this.amplifier == mobEffectInstance.amplifier && this.ambient == mobEffectInstance.ambient && this.effect.equals(mobEffectInstance.effect) && this.key == mobEffectInstance.key; // Purpur - add key
}
}
@@ -265,6 +311,11 @@ public class MobEffectInstance implements Comparable<MobEffectInstance> {
nbt.putBoolean("Ambient", this.isAmbient());
nbt.putBoolean("ShowParticles", this.isVisible());
nbt.putBoolean("ShowIcon", this.showIcon());
+ // Purpur start
+ if (this.key != null) {
+ nbt.putString("Key", this.key.toString());
+ }
+ // Purpur end
if (this.hiddenEffect != null) {
CompoundTag compoundTag = new CompoundTag();
this.hiddenEffect.save(compoundTag);
@@ -299,6 +350,13 @@ public class MobEffectInstance implements Comparable<MobEffectInstance> {
bl3 = nbt.getBoolean("ShowIcon");
}
+ // Purpur start
+ NamespacedKey key = null;
+ if (nbt.contains("Key")) {
+ key = NamespacedKey.fromString(nbt.getString("Key"));
+ }
+ // Purpur end
+
MobEffectInstance mobEffectInstance = null;
if (nbt.contains("HiddenEffect", 10)) {
mobEffectInstance = loadSpecifiedEffect(type, nbt.getCompound("HiddenEffect"));
@@ -311,7 +369,7 @@ public class MobEffectInstance implements Comparable<MobEffectInstance> {
optional = Optional.empty();
}
- return new MobEffectInstance(type, j, Math.max(i, 0), bl, bl2, bl3, mobEffectInstance, optional);
+ return new MobEffectInstance(type, j, Math.max(i, 0), bl, bl2, bl3, mobEffectInstance, optional, key); // Purpur - add key
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 36795968f4c296f680f79cc5a795391ae13c64e4..cf0d8da4c4b5f4aa4e4ef15897ca252a2b52ec8d 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -156,7 +156,7 @@ import org.bukkit.plugin.PluginManager;
// CraftBukkit end
public abstract class Entity implements Nameable, EntityAccess, CommandSource {
-
+ public static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur
// CraftBukkit start
private static final int CURRENT_LEVEL = 2;
public boolean preserveMotion = true; // Paper - keep initial motion on first setPositionRotation
@@ -334,7 +334,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
public double xOld;
public double yOld;
public double zOld;
- private float maxUpStep;
+ public float maxUpStep; // Purpur - private -> public
public boolean noPhysics;
protected final RandomSource random;
public int tickCount;
@@ -376,7 +376,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
private final Set<String> tags;
private final double[] pistonDeltas;
private long pistonDeltasGameTime;
- private EntityDimensions dimensions;
+ protected EntityDimensions dimensions; // Purpur - private -> protected
private float eyeHeight;
public boolean isInPowderSnow;
public boolean wasInPowderSnow;
@@ -418,6 +418,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
public boolean freezeLocked = false; // Paper - Freeze Tick Lock API
public boolean collidingWithWorldBorder; // Paper
public boolean fixedPose = false; // Paper
+ public @Nullable Boolean immuneToFire = null; // Purpur - Fire immune API
public void setOrigin(@javax.annotation.Nonnull Location location) {
this.origin = location.toVector();
@@ -439,7 +440,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
public int activatedPriority = gg.pufferfish.pufferfish.PufferfishConfig.maximumActivationPrio; // golf score
public final BlockPos.MutableBlockPos cachedBlockPos = new BlockPos.MutableBlockPos(); // used where needed
// Pufferfish end
-
+
public float getBukkitYaw() {
return this.yRot;
}
@@ -493,6 +494,25 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
return false;
}
+ public boolean canSaveToDisk() {
+ return true;
+ }
+
+ // Purpur start - copied from Mob
+ public boolean isSunBurnTick() {
+ if (this.level().isDay() && !this.level().isClientSide) {
+ float f = this.getLightLevelDependentMagicValue();
+ BlockPos blockposition = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ());
+ boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow;
+
+ if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level().canSeeSky(blockposition)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
public final boolean hardCollides() {
return this.hardCollides;
}
@@ -597,7 +617,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
this.bb = Entity.INITIAL_AABB;
this.stuckSpeedMultiplier = Vec3.ZERO;
this.nextStep = 1.0F;
- this.random = SHARED_RANDOM; // Paper
+ this.random = world == null || world.purpurConfig.entitySharedRandom ? SHARED_RANDOM : RandomSource.create(); // Paper // Purpur
this.remainingFireTicks = -this.getFireImmuneTicks();
this.fluidHeight = new Object2DoubleArrayMap(2);
this.fluidOnEyes = new HashSet();
@@ -845,7 +865,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
// CraftBukkit end
public void baseTick() {
- this.level().getProfiler().push("entityBaseTick");
+ //this.level().getProfiler().push("entityBaseTick"); // Purpur
if (firstTick && this instanceof net.minecraft.world.entity.NeutralMob neutralMob) neutralMob.tickInitialPersistentAnger(level); // Paper - Update last hurt when ticking
this.feetBlockState = null;
if (this.isPassenger() && this.getVehicle().isRemoved()) {
@@ -906,7 +926,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
this.firstTick = false;
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
}
public void setSharedFlagOnFire(boolean onFire) {
@@ -915,10 +935,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
public void checkBelowWorld() {
// Paper start - Configurable nether ceiling damage
- if (this.getY() < (double) (this.level.getMinBuildHeight() - 64) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER
+ if (this.getY() < (double) (this.level.getMinBuildHeight() + level().purpurConfig.voidDamageHeight) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER // Purpur
&& this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v)
&& (!(this instanceof Player player) || !player.getAbilities().invulnerable))) {
// Paper end
+ if (this.level().purpurConfig.teleportOnNetherCeilingDamage && this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER && this instanceof ServerPlayer player) player.teleport(io.papermc.paper.util.MCUtil.toLocation(this.level, this.level.getSharedSpawnPos())); else // Purpur
this.onBelowWorld();
}
@@ -1125,7 +1146,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
}
- this.level().getProfiler().push("move");
+ //this.level().getProfiler().push("move"); // Purpur
if (this.stuckSpeedMultiplier.lengthSqr() > 1.0E-7D) {
movement = movement.multiply(this.stuckSpeedMultiplier);
this.stuckSpeedMultiplier = Vec3.ZERO;
@@ -1134,7 +1155,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
// Paper start - ignore movement changes while inactive.
if (isTemporarilyActive && !(this instanceof ItemEntity || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) && movement == getDeltaMovement() && movementType == MoverType.SELF) {
setDeltaMovement(Vec3.ZERO);
- this.level.getProfiler().pop();
+ //this.level.getProfiler().pop(); // Purpur
return;
}
// Paper end
@@ -1155,8 +1176,8 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
this.setPos(this.getX() + vec3d1.x, this.getY() + vec3d1.y, this.getZ() + vec3d1.z);
}
- this.level().getProfiler().pop();
- this.level().getProfiler().push("rest");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("rest"); // Purpur
boolean flag = !Mth.equal(movement.x, vec3d1.x);
boolean flag1 = !Mth.equal(movement.z, vec3d1.z);
@@ -1175,7 +1196,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
this.checkFallDamage(vec3d1.y, this.onGround(), iblockdata, blockposition);
if (this.isRemoved()) {
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
} else {
if (this.horizontalCollision) {
Vec3 vec3d2 = this.getDeltaMovement();
@@ -1313,7 +1334,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
this.setRemainingFireTicks(-this.getFireImmuneTicks());
}
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
}
}
// Paper start - detailed watchdog information
@@ -1803,7 +1824,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
public boolean fireImmune() {
- return this.getType().fireImmune();
+ return this.immuneToFire != null ? immuneToFire : this.getType().fireImmune(); // Purpur - add fire immune API
}
public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) {
@@ -1872,7 +1893,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
return this.isInWater() || flag;
}
- void updateInWaterStateAndDoWaterCurrentPushing() {
+ public void updateInWaterStateAndDoWaterCurrentPushing() { // Purpur - package-private -> public
Entity entity = this.getVehicle();
if (entity instanceof Boat) {
@@ -2473,6 +2494,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
nbt.putBoolean("Paper.FreezeLock", true);
}
// Paper end
+ // Purpur start
+ if (immuneToFire != null) {
+ nbt.putBoolean("Purpur.FireImmune", immuneToFire);
+ }
+ // Purpur end
return nbt;
} catch (Throwable throwable) {
CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT");
@@ -2641,6 +2667,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
freezeLocked = nbt.getBoolean("Paper.FreezeLock");
}
// Paper end
+ // Purpur start
+ if (nbt.contains("Purpur.FireImmune")) {
+ immuneToFire = nbt.getBoolean("Purpur.FireImmune");
+ }
+ // Purpur end
} catch (Throwable throwable) {
CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT");
@@ -2942,6 +2973,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
this.passengers = ImmutableList.copyOf(list);
}
+ // Purpur start
+ if (isRidable() && this.passengers.get(0) == passenger && passenger instanceof Player player) {
+ onMount(player);
+ this.rider = player;
+ }
+ // Purpur end
+
this.gameEvent(GameEvent.ENTITY_MOUNT, passenger);
}
}
@@ -2982,6 +3020,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
return false;
}
// Spigot end
+
+ // Purpur start
+ if (this.rider != null && this.passengers.get(0) == this.rider) {
+ onDismount(this.rider);
+ this.rider = null;
+ }
+ // Purpur end
+
if (this.passengers.size() == 1 && this.passengers.get(0) == entity) {
this.passengers = ImmutableList.of();
} else {
@@ -3041,12 +3087,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
return Vec3.directionFromRotation(this.getRotationVector());
}
+ public BlockPos portalPos = BlockPos.ZERO; // Purpur
public void handleInsidePortal(BlockPos pos) {
if (this.isOnPortalCooldown()) {
+ if (!(level().purpurConfig.playerFixStuckPortal && this instanceof Player && !pos.equals(portalPos))) // Purpur
this.setPortalCooldown();
- } else {
+ } else if (level().purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer) { // Purpur
if (!this.level().isClientSide && !pos.equals(this.portalEntrancePos)) {
this.portalEntrancePos = pos.immutable();
+ portalPos = BlockPos.ZERO; // Purpur
}
this.isInsidePortal = true;
@@ -3064,7 +3113,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
ServerLevel worldserver1 = minecraftserver.getLevel(resourcekey);
if (true && !this.isPassenger() && this.portalTime++ >= i) { // CraftBukkit
- this.level().getProfiler().push("portal");
+ //this.level().getProfiler().push("portal"); // Purpur
this.portalTime = i;
// Paper start
io.papermc.paper.event.entity.EntityPortalReadyEvent event = new io.papermc.paper.event.entity.EntityPortalReadyEvent(this.getBukkitEntity(), worldserver1 == null ? null : worldserver1.getWorld(), org.bukkit.PortalType.NETHER);
@@ -3082,7 +3131,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
} // Paper
// CraftBukkit end
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
}
this.isInsidePortal = false;
@@ -3097,7 +3146,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
this.processPortalCooldown();
- this.tickEndPortal(); // Paper - make end portalling safe
+ if (this.level().purpurConfig.endPortalSafeTeleporting) this.tickEndPortal(); // Paper - make end portalling safe // Purpur
}
}
@@ -3283,7 +3332,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
public int getMaxAirSupply() {
- return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+ return this.level == null? this.maxAirTicks : this.level().purpurConfig.drowningAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() // Purpur
}
public int getAirSupply() {
@@ -3553,14 +3602,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
// Paper end
if (this.level() instanceof ServerLevel && !this.isRemoved()) {
- this.level().getProfiler().push("changeDimension");
+ //this.level().getProfiler().push("changeDimension"); // Purpur
// CraftBukkit start
// this.unRide();
if (worldserver == null) {
return null;
}
// CraftBukkit end
- this.level().getProfiler().push("reposition");
+ //this.level().getProfiler().push("reposition"); // Purpur
PortalInfo shapedetectorshape = (location == null) ? this.findDimensionEntryPoint(worldserver) : new PortalInfo(new Vec3(location.x(), location.y(), location.z()), Vec3.ZERO, this.yRot, this.xRot, worldserver, null); // CraftBukkit
if (shapedetectorshape == null) {
@@ -3599,7 +3648,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
this.unRide();
// CraftBukkit end
- this.level().getProfiler().popPush("reloading");
+ //this.level().getProfiler().popPush("reloading"); // Purpur
// Paper start - Change lead drop timing to prevent dupe
if (this instanceof Mob) {
((Mob) this).dropLeash(true, true); // Paper drop lead
@@ -3622,10 +3671,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
this.removeAfterChangingDimensions();
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
((ServerLevel) this.level()).resetEmptyTime();
worldserver.resetEmptyTime();
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
return entity;
}
} else {
@@ -3745,7 +3794,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
public boolean canChangeDimensions() {
- return !this.isPassenger() && !this.isVehicle() && isAlive() && valid; // Paper
+ return !this.isPassenger() && !this.isVehicle() && isAlive() && valid && (level().purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer); // Paper // Purpur
}
public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) {
@@ -4047,6 +4096,20 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
return SlotAccess.NULL;
}
+ // Purpur Start
+ public void sendMiniMessage(@Nullable String message) {
+ if (message != null && !message.isEmpty()) {
+ this.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message));
+ }
+ }
+
+ public void sendMessage(@Nullable net.kyori.adventure.text.Component message) {
+ if (message != null) {
+ this.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message));
+ }
+ }
+ // Purpur end
+
@Override
public void sendSystemMessage(Component message) {}
@@ -4316,6 +4379,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
this.yRotO = this.getYRot();
}
+ // Purpur start
+ public AABB getAxisForFluidCheck() {
+ return this.getBoundingBox().deflate(0.001D);
+ }
+ // Purpur end
+
public boolean updateFluidHeightAndDoFluidPushing(TagKey<Fluid> tag, double speed) {
if (false && this.touchingUnloadedChunk()) { // Pufferfish - cost of a lookup here is the same cost as below, so skip
return false;
@@ -4869,4 +4938,45 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
return ((net.minecraft.server.level.ServerChunkCache) level.getChunkSource()).isPositionTicking(this);
}
// Paper end
+
+ // Purpur start
+ @Nullable
+ private Player rider = null;
+
+ @Nullable
+ public Player getRider() {
+ return rider;
+ }
+
+ public boolean isRidable() {
+ return false;
+ }
+
+ public boolean isControllable() {
+ return true;
+ }
+
+ public void onMount(Player rider) {
+ if (this instanceof Mob) {
+ ((Mob) this).setTarget(null, null, false);
+ ((Mob) this).getNavigation().stop();
+ }
+ rider.setJumping(false); // fixes jump on mount
+ }
+
+ public void onDismount(Player player) {
+ }
+
+ public boolean onSpacebar() {
+ return false;
+ }
+
+ public boolean onClick(InteractionHand hand) {
+ return false;
+ }
+
+ public boolean processClick(InteractionHand hand) {
+ return false;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java
index 3ff999734d14e2b6e7828e117f5ee32a60c26bc1..cfa9607241c3e69777ffc317206996c2f783437a 100644
--- a/src/main/java/net/minecraft/world/entity/EntitySelector.java
+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java
@@ -39,6 +39,7 @@ public final class EntitySelector {
return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks;
};
// Paper end
+ public static Predicate<Player> notAfk = (player) -> !player.isAfk(); // Purpur
private EntitySelector() {}
// Paper start
diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
index aa5cec6d56d7a8e80861aa4c9b4a74ca3e64be8c..a8ffa403ac48041e17aa4ce057be0539e5468fd4 100644
--- a/src/main/java/net/minecraft/world/entity/EntityType.java
+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
@@ -308,13 +308,24 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
private Component description;
@Nullable
private ResourceLocation lootTable;
- private final EntityDimensions dimensions;
+ private EntityDimensions dimensions; // Purpur - remove final
+ public void setDimensions(EntityDimensions dimensions) { this.dimensions = dimensions; } // Purpur
private final FeatureFlagSet requiredFeatures;
private static <T extends Entity> EntityType<T> register(String id, EntityType.Builder type) { // CraftBukkit - decompile error
return (EntityType) Registry.register(BuiltInRegistries.ENTITY_TYPE, id, (EntityType<T>) type.build(id)); // CraftBukkit - decompile error
}
+ // Purpur start
+ public static EntityType<?> getFromBukkitType(org.bukkit.entity.EntityType bukkitType) {
+ return getFromKey(new ResourceLocation(bukkitType.getKey().toString()));
+ }
+
+ public static EntityType<?> getFromKey(ResourceLocation location) {
+ return BuiltInRegistries.ENTITY_TYPE.get(location);
+ }
+ // Purpur end
+
public static ResourceLocation getKey(EntityType<?> type) {
return BuiltInRegistries.ENTITY_TYPE.getKey(type);
}
@@ -530,6 +541,16 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
return this.category;
}
+ // Purpur start
+ public String getName() {
+ return BuiltInRegistries.ENTITY_TYPE.getKey(this).getPath();
+ }
+
+ public String getTranslatedName() {
+ return getDescription().getString();
+ }
+ // Purpur end
+
public String getDescriptionId() {
if (this.descriptionId == null) {
this.descriptionId = Util.makeDescriptionId("entity", BuiltInRegistries.ENTITY_TYPE.getKey(this));
@@ -591,6 +612,12 @@ public class EntityType<T extends Entity> implements FeatureElement, EntityTypeT
entity.load(nbt);
}, () -> {
EntityType.LOGGER.warn("Skipping Entity with id {}", nbt.getString("id"));
+ // Purpur start - log skipped entity's position
+ try {
+ ListTag pos = nbt.getList("Pos", 6);
+ EntityType.LOGGER.warn("Location: {} {},{},{}", world.getWorld().getName(), pos.getDouble(0), pos.getDouble(1), pos.getDouble(2));
+ } catch (Throwable ignore) {}
+ // Purpur end
});
}
diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java
index eca634792d2a7cc649675e3394e84dbaf1453905..724bf857bf1b89cb0947b8a82e0ce09a0bec0335 100644
--- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java
+++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java
@@ -313,7 +313,7 @@ public class ExperienceOrb extends Entity {
public void playerTouch(Player player) {
if (!this.level().isClientSide) {
if (player.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper
- player.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(player, 2, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2;
+ player.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(player, this.level().purpurConfig.playerExpPickupDelay, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; // Purpur
player.take(this, 1);
int i = this.repairPlayerItems(player, this.value);
@@ -331,7 +331,7 @@ public class ExperienceOrb extends Entity {
}
private int repairPlayerItems(Player player, int amount) {
- Entry<EquipmentSlot, ItemStack> entry = EnchantmentHelper.getRandomItemWith(Enchantments.MENDING, player, ItemStack::isDamaged);
+ Entry<EquipmentSlot, ItemStack> entry = level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedEquipment(Enchantments.MENDING, player) : EnchantmentHelper.getRandomItemWith(Enchantments.MENDING, player, ItemStack::isDamaged); // Purpur
if (entry != null) {
ItemStack itemstack = (ItemStack) entry.getValue();
@@ -359,13 +359,15 @@ public class ExperienceOrb extends Entity {
}
}
+ // Purpur start
public int durabilityToXp(int repairAmount) {
- return repairAmount / 2;
+ return (int) (repairAmount / (2 * level().purpurConfig.mendingMultiplier));
}
public int xpToDurability(int experienceAmount) {
- return experienceAmount * 2;
+ return (int) ((experienceAmount * 2) * level().purpurConfig.mendingMultiplier);
}
+ // Purpur end
public int getValue() {
return this.value;
diff --git a/src/main/java/net/minecraft/world/entity/GlowSquid.java b/src/main/java/net/minecraft/world/entity/GlowSquid.java
index 759713f7c646aaf1a918c87a2834a1d405385dad..43cdda0cb26c5d5cc9025199eb71673d71c2abea 100644
--- a/src/main/java/net/minecraft/world/entity/GlowSquid.java
+++ b/src/main/java/net/minecraft/world/entity/GlowSquid.java
@@ -23,6 +23,39 @@ public class GlowSquid extends Squid {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.glowSquidRidable;
+ }
+
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.glowSquidControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.glowSquidMaxHealth);
+ }
+
+ @Override
+ public boolean canFly() {
+ return this.level().purpurConfig.glowSquidsCanFly;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.glowSquidTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.glowSquidAlwaysDropExp;
+ }
+
@Override
protected ParticleOptions getInkParticle() {
return ParticleTypes.GLOW_SQUID_INK;
diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
index d265a32bd273beed0e65e224e1c5f9c0bca7b3fb..49906b4dc8392eee592215d0b4cba85267a875fc 100644
--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
@@ -218,9 +218,9 @@ public abstract class LivingEntity extends Entity implements Attackable {
protected int deathScore;
public float lastHurt;
public boolean jumping;
- public float xxa;
- public float yya;
- public float zza;
+ public float xxa; public float getStrafeMot() { return xxa; } public void setStrafeMot(float strafe) { xxa = strafe; } // Purpur - OBFHELPER
+ public float yya; public float getVerticalMot() { return yya; } public void setVerticalMot(float vertical) { yya = vertical; } // Purpur - OBFHELPER
+ public float zza; public float getForwardMot() { return zza; } public void setForwardMot(float forward) { zza = forward; } // Purpur - OBFHELPER
protected int lerpSteps;
protected double lerpX;
protected double lerpY;
@@ -253,6 +253,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
private boolean skipDropExperience;
// CraftBukkit start
public int expToDrop;
+ public float safeFallDistance = 3.0F; // Purpur
public boolean forceDrops;
public ArrayList<org.bukkit.inventory.ItemStack> drops = new ArrayList<org.bukkit.inventory.ItemStack>();
public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes;
@@ -262,6 +263,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper
public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event
public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper
+ protected boolean shouldBurnInDay = false; public boolean shouldBurnInDay() { return this.shouldBurnInDay; } public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Purpur
@Override
public float getBukkitYaw() {
@@ -286,7 +288,8 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.effectsDirty = true;
this.useItem = ItemStack.EMPTY;
this.lastClimbablePos = Optional.empty();
- this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type));
+ this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type), this); // Purpur
+ this.initAttributes(); // Purpur
this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit
// CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor
this.entityData.set(LivingEntity.DATA_HEALTH_ID, (float) this.getAttribute(Attributes.MAX_HEALTH).getValue());
@@ -302,6 +305,8 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.brain = this.makeBrain(new Dynamic(dynamicopsnbt, (Tag) dynamicopsnbt.createMap((Map) ImmutableMap.of(dynamicopsnbt.createString("memories"), (Tag) dynamicopsnbt.emptyMap()))));
}
+ protected void initAttributes() {}// Purpur
+
public Brain<?> getBrain() {
return this.brain;
}
@@ -337,6 +342,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
public static AttributeSupplier.Builder createLivingAttributes() {
return AttributeSupplier.builder().add(Attributes.MAX_HEALTH).add(Attributes.KNOCKBACK_RESISTANCE).add(Attributes.MOVEMENT_SPEED).add(Attributes.ARMOR).add(Attributes.ARMOR_TOUGHNESS);
}
+ public boolean shouldSendAttribute(Attribute attribute) { return true; } // Purpur
@Override
protected void checkFallDamage(double heightDifference, boolean onGround, BlockState state, BlockPos landedPosition) {
@@ -349,7 +355,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.tryAddSoulSpeed();
}
- if (!this.level().isClientSide && this.fallDistance > 3.0F && onGround && !state.isAir()) {
+ if (!this.level().isClientSide && this.fallDistance > this.safeFallDistance && onGround && !state.isAir()) { // Purpur
double d1 = this.getX();
double d2 = this.getY();
double d3 = this.getZ();
@@ -364,7 +370,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
d3 = (double) landedPosition.getZ() + 0.5D + d5 / d6 * 0.5D;
}
- float f = (float) Mth.ceil(this.fallDistance - 3.0F);
+ float f = (float) Mth.ceil(this.fallDistance - this.safeFallDistance); // Purpur
double d7 = Math.min((double) (0.2F + f / 15.0F), 2.5D);
int i = (int) (150.0D * d7);
@@ -404,7 +410,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
super.baseTick();
- this.level().getProfiler().push("livingEntityBaseTick");
+ //this.level().getProfiler().push("livingEntityBaseTick"); // Purpur
if (this.fireImmune() || this.level().isClientSide) {
this.clearFire();
}
@@ -422,6 +428,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
double d1 = this.level().getWorldBorder().getDamagePerBlock();
if (d1 > 0.0D) {
+ if (level().purpurConfig.teleportIfOutsideBorder && this instanceof ServerPlayer serverPlayer) { serverPlayer.teleport(io.papermc.paper.util.MCUtil.toLocation(level(), ((ServerLevel) level()).getSharedSpawnPos())); return; } // Purpur
this.hurt(this.damageSources().outOfBorder(), (float) Math.max(1, Mth.floor(-d0 * d1)));
}
}
@@ -433,7 +440,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
if (flag1) {
this.setAirSupply(this.decreaseAirSupply(this.getAirSupply()));
- if (this.getAirSupply() == -20) {
+ if (this.getAirSupply() == -this.level().purpurConfig.drowningDamageInterval) { // Purpur
this.setAirSupply(0);
Vec3 vec3d = this.getDeltaMovement();
@@ -445,7 +452,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.level().addParticle(ParticleTypes.BUBBLE, this.getX() + d2, this.getY() + d3, this.getZ() + d4, vec3d.x, vec3d.y, vec3d.z);
}
- this.hurt(this.damageSources().drown(), 2.0F);
+ this.hurt(this.damageSources().drown(), (float) this.level().purpurConfig.damageFromDrowning); // Purpur
}
}
@@ -506,7 +513,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.yHeadRotO = this.yHeadRot;
this.yRotO = this.getYRot();
this.xRotO = this.getXRot();
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
}
public boolean canSpawnSoulSpeedParticle() {
@@ -798,6 +805,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> {
nbt.put("Brain", nbtbase);
});
+ nbt.putBoolean("Purpur.ShouldBurnInDay", shouldBurnInDay); // Purpur
}
@Override
@@ -882,6 +890,11 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.brain = this.makeBrain(new Dynamic(NbtOps.INSTANCE, nbt.get("Brain")));
}
+ // Purpur start
+ if (nbt.contains("Purpur.ShouldBurnInDay")) {
+ shouldBurnInDay = nbt.getBoolean("Purpur.ShouldBurnInDay");
+ }
+ // Purpur end
}
// CraftBukkit start
@@ -1027,9 +1040,31 @@ public abstract class LivingEntity extends Entity implements Attackable {
ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
EntityType<?> entitytypes = entity.getType();
- if (entitytypes == EntityType.SKELETON && itemstack.is(Items.SKELETON_SKULL) || entitytypes == EntityType.ZOMBIE && itemstack.is(Items.ZOMBIE_HEAD) || entitytypes == EntityType.PIGLIN && itemstack.is(Items.PIGLIN_HEAD) || entitytypes == EntityType.PIGLIN_BRUTE && itemstack.is(Items.PIGLIN_HEAD) || entitytypes == EntityType.CREEPER && itemstack.is(Items.CREEPER_HEAD)) {
- d0 *= 0.5D;
+ // Purpur start
+ if (entitytypes == EntityType.SKELETON && itemstack.is(Items.SKELETON_SKULL)) {
+ d0 *= entity.level().purpurConfig.skeletonHeadVisibilityPercent;
+ }
+ else if (entitytypes == EntityType.ZOMBIE && itemstack.is(Items.ZOMBIE_HEAD)) {
+ d0 *= entity.level().purpurConfig.zombieHeadVisibilityPercent;
+ }
+ else if (entitytypes == EntityType.CREEPER && itemstack.is(Items.CREEPER_HEAD)) {
+ d0 *= entity.level().purpurConfig.creeperHeadVisibilityPercent;
+ }
+ else if ((entitytypes == EntityType.PIGLIN || entitytypes == EntityType.PIGLIN_BRUTE) && itemstack.is(Items.PIGLIN_HEAD)) {
+ d0 *= entity.level().purpurConfig.piglinHeadVisibilityPercent;
+ }
+ // Purpur end
+
+ // Purpur start
+ if (entity instanceof LivingEntity entityliving) {
+ if (entityliving.hasEffect(MobEffects.BLINDNESS)) {
+ int amplifier = entityliving.getEffect(MobEffects.BLINDNESS).getAmplifier();
+ for (int i = 0; i < amplifier; i++) {
+ d0 *= this.level().purpurConfig.mobsBlindnessMultiplier;
+ }
+ }
}
+ // Purpur end
}
return d0;
@@ -1089,6 +1124,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
for (flag = false; iterator.hasNext(); flag = true) {
// CraftBukkit start
MobEffectInstance effect = (MobEffectInstance) iterator.next();
+ if (cause == EntityPotionEffectEvent.Cause.MILK && !this.level().purpurConfig.milkClearsBeneficialEffects && effect.getEffect().isBeneficial()) continue; // Purpur
EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED);
if (event.isCancelled()) {
continue;
@@ -1471,13 +1507,13 @@ public abstract class LivingEntity extends Entity implements Attackable {
if (entity1 instanceof net.minecraft.world.entity.player.Player) {
net.minecraft.world.entity.player.Player entityhuman = (net.minecraft.world.entity.player.Player) entity1;
- this.lastHurtByPlayerTime = 100;
+ this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur
this.lastHurtByPlayer = entityhuman;
} else if (entity1 instanceof Wolf) {
Wolf entitywolf = (Wolf) entity1;
if (entitywolf.isTame()) {
- this.lastHurtByPlayerTime = 100;
+ this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur
LivingEntity entityliving2 = entitywolf.getOwner();
if (entityliving2 instanceof net.minecraft.world.entity.player.Player) {
@@ -1588,6 +1624,18 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
}
+ // Purpur start
+ if (level().purpurConfig.totemOfUndyingWorksInInventory && this instanceof ServerPlayer player && (itemstack == null || itemstack.getItem() != Items.TOTEM_OF_UNDYING) && player.getBukkitEntity().hasPermission("purpur.inventory_totem")) {
+ for (ItemStack item : player.getInventory().items) {
+ if (item.getItem() == Items.TOTEM_OF_UNDYING) {
+ itemstack1 = item;
+ itemstack = item.copy();
+ break;
+ }
+ }
+ }
+ // Purpur end
+
org.bukkit.inventory.EquipmentSlot handSlot = (hand != null) ? org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand) : null;
EntityResurrectEvent event = new EntityResurrectEvent((org.bukkit.entity.LivingEntity) this.getBukkitEntity(), handSlot);
event.setCancelled(itemstack == null);
@@ -1754,7 +1802,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
boolean flag = false;
if (this.dead && adversary instanceof WitherBoss) { // Paper
- if (this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (this.level().purpurConfig.witherBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
BlockPos blockposition = this.blockPosition();
BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState();
@@ -1800,6 +1848,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.dropEquipment(); // CraftBukkit - from below
if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+ if (!(source.is(net.minecraft.world.damagesource.DamageTypes.CRAMMING) && level().purpurConfig.disableDropsOnCrammingDeath)) { // Purpur
this.dropFromLootTable(source, flag);
// Paper start
final boolean prev = this.clearEquipmentSlots;
@@ -1808,6 +1857,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
// Paper end
this.dropCustomDeathLoot(source, i, flag);
this.clearEquipmentSlots = prev; // Paper
+ } // Purpur
}
// CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment
org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops, () -> {
@@ -2054,7 +2104,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
MobEffectInstance mobeffect = this.getEffect(MobEffects.JUMP);
float f2 = mobeffect == null ? 0.0F : (float) (mobeffect.getAmplifier() + 1);
- return Mth.ceil((fallDistance - 3.0F - f2) * damageMultiplier);
+ return Mth.ceil((fallDistance - this.safeFallDistance - f2) * damageMultiplier); // Purpur
}
}
@@ -2277,6 +2327,20 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
}
+ // Purpur start
+ if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player player && damagesource.getEntity().level().purpurConfig.creativeOnePunch) {
+ if (player.isCreative()) {
+ double attackDamage = 0;
+ for (AttributeModifier modifier : player.getMainHandItem().getAttributeModifiers(EquipmentSlot.MAINHAND).get(Attributes.ATTACK_DAMAGE)) {
+ attackDamage += modifier.getAmount();
+ }
+ if (attackDamage == 0) {
+ this.setHealth(0);
+ }
+ }
+ }
+ // Purpur end
+
if (f > 0 || !human) {
if (human) {
// PAIL: Be sure to drag all this code from the EntityHuman subclass each update.
@@ -2493,7 +2557,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
@Override
protected void onBelowWorld() {
- this.hurt(this.damageSources().fellOutOfWorld(), 4.0F);
+ this.hurt(this.damageSources().fellOutOfWorld(), (float) level().purpurConfig.voidDamageDealt); // Purpur
}
protected void updateSwingTime() {
@@ -2690,7 +2754,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
protected long lastJumpTime = 0L; // Paper
- protected void jumpFromGround() {
+ public void jumpFromGround() { // Purpur - protected -> public
Vec3 vec3d = this.getDeltaMovement();
// Paper start
long time = System.nanoTime();
@@ -2842,6 +2906,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
if (f3 > 0.0F) {
this.playSound(this.getFallDamageSound((int) f3), 1.0F, 1.0F);
+ if (level().purpurConfig.elytraKineticDamage) // Purpur
this.hurt(this.damageSources().flyIntoWall(), f3);
}
}
@@ -3063,10 +3128,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
this.run += (f3 - this.run) * 0.3F;
- this.level().getProfiler().push("headTurn");
+ //this.level().getProfiler().push("headTurn"); // Purpur
f2 = this.tickHeadTurn(f1, f2);
- this.level().getProfiler().pop();
- this.level().getProfiler().push("rangeChecks");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("rangeChecks"); // Purpur
// Paper start - stop large pitch and yaw changes from crashing the server
this.yRotO += Math.round((this.getYRot() - this.yRotO) / 360.0F) * 360.0F;
@@ -3078,7 +3143,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.yHeadRotO += Math.round((this.yHeadRot - this.yHeadRotO) / 360.0F) * 360.0F;
// Paper end
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
this.animStep += f2;
if (this.isFallFlying()) {
++this.fallFlyTicks;
@@ -3367,19 +3432,19 @@ public abstract class LivingEntity extends Entity implements Attackable {
}
this.setDeltaMovement(d4, d5, d6);
- this.level().getProfiler().push("ai");
+ //this.level().getProfiler().push("ai"); // Purpur
if (this.isImmobile()) {
this.jumping = false;
this.xxa = 0.0F;
this.zza = 0.0F;
} else if (this.isEffectiveAi()) {
- this.level().getProfiler().push("newAi");
+ //this.level().getProfiler().push("newAi"); // Purpur
this.serverAiStep();
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
}
- this.level().getProfiler().pop();
- this.level().getProfiler().push("jump");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("jump"); // Purpur
if (this.jumping && this.isAffectedByFluids()) {
double d7;
@@ -3406,8 +3471,8 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.noJumpDelay = 0;
}
- this.level().getProfiler().pop();
- this.level().getProfiler().push("travel");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("travel"); // Purpur
this.xxa *= 0.98F;
this.zza *= 0.98F;
this.updateFallFlying();
@@ -3434,8 +3499,8 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.travel(vec3d1);
}
- this.level().getProfiler().pop();
- this.level().getProfiler().push("freezing");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("freezing"); // Purpur
if (!this.level().isClientSide && !this.isDeadOrDying() && !freezeLocked) { // Paper - Freeze Tick Lock API
int i = this.getTicksFrozen();
@@ -3452,18 +3517,20 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.hurt(this.damageSources().freeze(), 1.0F);
}
- this.level().getProfiler().pop();
- this.level().getProfiler().push("push");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("push"); // Purpur
if (this.autoSpinAttackTicks > 0) {
--this.autoSpinAttackTicks;
this.checkAutoSpinAttack(axisalignedbb, this.getBoundingBox());
}
this.pushEntities();
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
// Paper start
- if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) {
- if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) {
+ // Purpur start
+ if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) {
+ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) {
+ // Purpur end
Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO);
Location to = new Location (this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone());
@@ -3473,12 +3540,48 @@ public abstract class LivingEntity extends Entity implements Attackable {
this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch());
}
}
+ // Purpur start
+ if (getRider() != null) {
+ getRider().resetLastActionTime();
+ if (((ServerLevel) level()).hasRidableMoveEvent && this instanceof Mob) {
+ Location from = new Location(level().getWorld(), xo, yo, zo, this.yRotO, this.xRotO);
+ Location to = new Location(level().getWorld(), getX(), getY(), getZ(), this.getYRot(), this.getXRot());
+ org.purpurmc.purpur.event.entity.RidableMoveEvent event = new org.purpurmc.purpur.event.entity.RidableMoveEvent((org.bukkit.entity.Mob) getBukkitLivingEntity(), (Player) getRider().getBukkitEntity(), from, to.clone());
+ if (!event.callEvent()) {
+ absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch());
+ } else if (!to.equals(event.getTo())) {
+ absMoveTo(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
+ }
+ }
+ }
+ // Purpur end
}
// Paper end
if (!this.level().isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) {
this.hurt(this.damageSources().drown(), 1.0F);
}
+ // Purpur start - copied from Zombie
+ if (this.isAlive()) {
+ boolean flag = this.shouldBurnInDay() && this.isSunBurnTick();
+ if (flag) {
+ ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
+ if (!itemstack.isEmpty()) {
+ if (itemstack.isDamageableItem()) {
+ itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2));
+ if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) {
+ this.broadcastBreakEvent(EquipmentSlot.HEAD);
+ this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY);
+ }
+ }
+ flag = false;
+ }
+ if (flag) {
+ this.setSecondsOnFire(8);
+ }
+ }
+ }
+ // Purpur end
}
public boolean isSensitiveToWater() {
@@ -3499,7 +3602,16 @@ public abstract class LivingEntity extends Entity implements Attackable {
int j = i / 10;
if (j % 2 == 0) {
- itemstack.hurtAndBreak(1, this, (entityliving) -> {
+ // Purpur start
+ int damage = level().purpurConfig.elytraDamagePerSecond;
+ if (level().purpurConfig.elytraDamageMultiplyBySpeed > 0) {
+ double speed = getDeltaMovement().lengthSqr();
+ if (speed > level().purpurConfig.elytraDamageMultiplyBySpeed) {
+ damage *= (int) speed;
+ }
+ }
+ itemstack.hurtAndBreak(damage, this, (entityliving) -> {
+ // Purpur end
entityliving.broadcastBreakEvent(EquipmentSlot.CHEST);
});
}
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
index f6eb032897c6d5d16ab5c8c287e49e189c24571c..19d9d3a9f634631e2ec5a6fa7f2cd9dd18f62f89 100644
--- a/src/main/java/net/minecraft/world/entity/Mob.java
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
@@ -65,6 +65,7 @@ import net.minecraft.world.item.ProjectileWeaponItem;
import net.minecraft.world.item.SpawnEggItem;
import net.minecraft.world.item.SwordItem;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
+import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.Level;
@@ -132,6 +133,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
private BlockPos restrictCenter;
private float restrictRadius;
+ public int ticksSinceLastInteraction; // Purpur
public boolean aware = true; // CraftBukkit
protected Mob(EntityType<? extends Mob> type, Level world) {
@@ -145,8 +147,8 @@ public abstract class Mob extends LivingEntity implements Targeting {
this.restrictRadius = -1.0F;
this.goalSelector = new GoalSelector(world.getProfilerSupplier());
this.targetSelector = new GoalSelector(world.getProfilerSupplier());
- this.lookControl = new LookControl(this);
- this.moveControl = new MoveControl(this);
+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this); // Purpur
+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this); // Purpur
this.jumpControl = new JumpControl(this);
this.bodyRotationControl = this.createBodyControl();
this.navigation = this.createNavigation(world);
@@ -322,6 +324,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
entityliving = null;
}
}
+ if (entityliving instanceof ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur
this.target = entityliving;
return true;
// CraftBukkit end
@@ -362,15 +365,35 @@ public abstract class Mob extends LivingEntity implements Targeting {
@Override
public void baseTick() {
super.baseTick();
- this.level().getProfiler().push("mobBaseTick");
+ //this.level().getProfiler().push("mobBaseTick"); // Purpur
if (this.isAlive() && this.random.nextInt(1000) < this.ambientSoundTime++) {
this.resetAmbientSoundTime();
this.playAmbientSound();
}
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
+ incrementTicksSinceLastInteraction(); // Purpur
}
+ // Purpur start
+ private void incrementTicksSinceLastInteraction() {
+ ++this.ticksSinceLastInteraction;
+ if (getRider() != null) {
+ this.ticksSinceLastInteraction = 0;
+ return;
+ }
+ if (this.level().purpurConfig.entityLifeSpan <= 0) {
+ return; // feature disabled
+ }
+ if (!this.removeWhenFarAway(0) || isPersistenceRequired() || requiresCustomPersistence() || hasCustomName()) {
+ return; // mob persistent
+ }
+ if (this.ticksSinceLastInteraction > this.level().purpurConfig.entityLifeSpan) {
+ this.discard();
+ }
+ }
+ // Purpur end
+
@Override
protected void playHurtSound(DamageSource source) {
this.resetAmbientSoundTime();
@@ -560,6 +583,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
}
nbt.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit
+ nbt.putInt("Purpur.ticksSinceLastInteraction", this.ticksSinceLastInteraction); // Purpur
}
@Override
@@ -630,6 +654,11 @@ public abstract class Mob extends LivingEntity implements Targeting {
this.aware = nbt.getBoolean("Bukkit.Aware");
}
// CraftBukkit end
+ // Purpur start
+ if (nbt.contains("Purpur.ticksSinceLastInteraction")) {
+ this.ticksSinceLastInteraction = nbt.getInt("Purpur.ticksSinceLastInteraction");
+ }
+ // Purpur end
}
@Override
@@ -673,8 +702,8 @@ public abstract class Mob extends LivingEntity implements Targeting {
@Override
public void aiStep() {
super.aiStep();
- this.level().getProfiler().push("looting");
- if (!this.level().isClientSide && this.canPickUpLoot() && this.isAlive() && !this.dead && this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ //this.level().getProfiler().push("looting"); // Purpur
+ if (!this.level().isClientSide && this.canPickUpLoot() && this.isAlive() && !this.dead && (this.level().purpurConfig.entitiesPickUpLootBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) {
Vec3i baseblockposition = this.getPickupReach();
List<ItemEntity> list = this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ()));
Iterator iterator = list.iterator();
@@ -693,7 +722,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
}
}
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
}
protected Vec3i getPickupReach() {
@@ -905,46 +934,46 @@ public abstract class Mob extends LivingEntity implements Targeting {
return;
}
// Paper end
- this.level().getProfiler().push("sensing");
+ //this.level().getProfiler().push("sensing"); // Purpur
this.sensing.tick();
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
int i = this.level().getServer().getTickCount() + this.getId();
if (i % 2 != 0 && this.tickCount > 1) {
- this.level().getProfiler().push("targetSelector");
+ //this.level().getProfiler().push("targetSelector"); // Purpur
if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
this.targetSelector.tickRunningGoals(false);
- this.level().getProfiler().pop();
- this.level().getProfiler().push("goalSelector");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("goalSelector"); // Purpur
if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
this.goalSelector.tickRunningGoals(false);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
} else {
- this.level().getProfiler().push("targetSelector");
+ //this.level().getProfiler().push("targetSelector");
if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
this.targetSelector.tick();
- this.level().getProfiler().pop();
- this.level().getProfiler().push("goalSelector");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("goalSelector"); // Purpur
if (this.goalSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
this.goalSelector.tick();
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
}
- this.level().getProfiler().push("navigation");
+ //this.level().getProfiler().push("navigation"); // Purpur
this.navigation.tick();
- this.level().getProfiler().pop();
- this.level().getProfiler().push("mob tick");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("mob tick"); // Purpur
this.customServerAiStep();
- this.level().getProfiler().pop();
- this.level().getProfiler().push("controls");
- this.level().getProfiler().push("move");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("controls"); // Purpur
+ //this.level().getProfiler().push("move"); // Purpur
this.moveControl.tick();
- this.level().getProfiler().popPush("look");
+ //this.level().getProfiler().popPush("look"); // Purpur
this.lookControl.tick();
- this.level().getProfiler().popPush("jump");
+ //this.level().getProfiler().popPush("jump"); // Purpur
this.jumpControl.tick();
- this.level().getProfiler().pop();
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().pop(); // Purpur
this.sendDebugPackets();
}
@@ -1166,6 +1195,12 @@ public abstract class Mob extends LivingEntity implements Targeting {
}
+ // Purpur start
+ public static @Nullable EquipmentSlot getSlotForDispenser(ItemStack itemstack) {
+ return EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BINDING_CURSE, itemstack) > 0 ? null : getEquipmentSlotForItem(itemstack);
+ }
+ // Purpur end
+
@Nullable
public static Item getEquipmentForSlot(EquipmentSlot equipmentSlot, int equipmentLevel) {
switch (equipmentSlot) {
@@ -1260,7 +1295,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
RandomSource randomsource = world.getRandom();
this.getAttribute(Attributes.FOLLOW_RANGE).addPermanentModifier(new AttributeModifier("Random spawn bonus", randomsource.triangle(0.0D, 0.11485000000000001D), AttributeModifier.Operation.MULTIPLY_BASE));
- if (randomsource.nextFloat() < 0.05F) {
+ if (randomsource.nextFloat() < world.getLevel().purpurConfig.entityLeftHandedChance) { // Purpur
this.setLeftHanded(true);
} else {
this.setLeftHanded(false);
@@ -1308,6 +1343,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
if (!this.isAlive()) {
return InteractionResult.PASS;
} else if (this.getLeashHolder() == player) {
+ if (hand == InteractionHand.OFF_HAND && (level().purpurConfig.villagerCanBeLeashed || level().purpurConfig.wanderingTraderCanBeLeashed) && this instanceof net.minecraft.world.entity.npc.AbstractVillager) return InteractionResult.CONSUME; // Purpur
// CraftBukkit start - fire PlayerUnleashEntityEvent
// Paper start - drop leash variable
org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.getAbilities().instabuild);
@@ -1381,7 +1417,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
protected void onOffspringSpawnedFromEgg(Player player, Mob child) {}
protected InteractionResult mobInteract(Player player, InteractionHand hand) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur
}
public boolean isWithinRestriction() {
@@ -1686,6 +1722,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
this.setLastHurtMob(target);
}
+ if (target instanceof ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur
return flag;
}
@@ -1702,17 +1739,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
}
public boolean isSunBurnTick() {
- if (this.level().isDay() && !this.level().isClientSide) {
- float f = this.getLightLevelDependentMagicValue();
- BlockPos blockposition = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ());
- boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow;
-
- if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level().canSeeSky(blockposition)) {
- return true;
- }
- }
-
- return false;
+ return super.isSunBurnTick();
}
@Override
@@ -1759,4 +1786,56 @@ public abstract class Mob extends LivingEntity implements Targeting {
return itemmonsteregg == null ? null : new ItemStack(itemmonsteregg);
}
+
+ // Purpur start
+ public double getMaxY() {
+ return level().getHeight();
+ }
+
+ public InteractionResult tryRide(Player player, InteractionHand hand) {
+ return tryRide(player, hand, InteractionResult.PASS);
+ }
+
+ public InteractionResult tryRide(Player player, InteractionHand hand, InteractionResult result) {
+ if (!isRidable()) {
+ return result;
+ }
+ if (hand != InteractionHand.MAIN_HAND) {
+ return InteractionResult.PASS;
+ }
+ if (player.isShiftKeyDown()) {
+ return InteractionResult.PASS;
+ }
+ if (!player.getItemInHand(hand).isEmpty()) {
+ return InteractionResult.PASS;
+ }
+ if (!passengers.isEmpty() || player.isPassenger()) {
+ return InteractionResult.PASS;
+ }
+ if (this instanceof TamableAnimal tamable) {
+ if (tamable.isTame() && !tamable.isOwnedBy(player)) {
+ return InteractionResult.PASS;
+ }
+ if (!tamable.isTame() && !level().purpurConfig.untamedTamablesAreRidable) {
+ return InteractionResult.PASS;
+ }
+ }
+ if (this instanceof AgeableMob ageable) {
+ if (ageable.isBaby() && !level().purpurConfig.babiesAreRidable) {
+ return InteractionResult.PASS;
+ }
+ }
+ if (!player.getBukkitEntity().hasPermission("allow.ride." + net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getKey(getType()).getPath())) {
+ player.sendMiniMessage(org.purpurmc.purpur.PurpurConfig.cannotRideMob);
+ return InteractionResult.PASS;
+ }
+ player.setYRot(this.getYRot());
+ player.setXRot(this.getXRot());
+ if (player.startRiding(this)) {
+ return InteractionResult.SUCCESS;
+ } else {
+ return InteractionResult.PASS;
+ }
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/entity/Shearable.java b/src/main/java/net/minecraft/world/entity/Shearable.java
index 5e8cc5cfac8888628c6d513148f41be09ca65a2c..a089fc61ec09be6b7490375489178dc6ba5a644b 100644
--- a/src/main/java/net/minecraft/world/entity/Shearable.java
+++ b/src/main/java/net/minecraft/world/entity/Shearable.java
@@ -3,7 +3,13 @@ package net.minecraft.world.entity;
import net.minecraft.sounds.SoundSource;
public interface Shearable {
- void shear(SoundSource shearedSoundCategory);
+ // Purpur start
+ default void shear(SoundSource shearedSoundCategory) {
+ shear(shearedSoundCategory, 0);
+ }
+
+ void shear(SoundSource shearedSoundCategory, int looting);
+ // Purpur end
boolean readyForShearing();
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
index e283eb57c25f7de222f9d09dca851169f5f6e488..210a0bee1227e4671909dd553ab22027cfc868fb 100644
--- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
@@ -24,14 +24,21 @@ public class AttributeMap {
private final Set<AttributeInstance> dirtyAttributes = Sets.newHashSet();
private final AttributeSupplier supplier;
private final java.util.function.Function<Attribute, 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 = attribute -> this.supplier.createInstance(this::onAttributeModified, attribute); // Pufferfish
}
private void onAttributeModified(AttributeInstance instance) {
- if (instance.getAttribute().isClientSyncable()) {
+ if (instance.getAttribute().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute()))) { // Purpur
this.dirtyAttributes.add(instance);
}
@@ -43,7 +50,7 @@ public class AttributeMap {
public Collection<AttributeInstance> getSyncableAttributes() {
return this.attributes.values().stream().filter((attribute) -> {
- return attribute.getAttribute().isClientSyncable();
+ return attribute.getAttribute().isClientSyncable() && (entity == null || entity.shouldSendAttribute(attribute.getAttribute())); // Purpur
}).collect(Collectors.toList());
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java
index 8a720f9ae81d7ea856e28cb27a66adcf04bcb0eb..e0b70d9732a2b7d96999b7e4a497ffa1d8cf86a7 100644
--- a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java
+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java
@@ -80,7 +80,88 @@ import org.slf4j.Logger;
public class DefaultAttributes {
private static final Logger LOGGER = LogUtils.getLogger();
- private static final Map<EntityType<? extends LivingEntity>, AttributeSupplier> SUPPLIERS = ImmutableMap.<EntityType<? extends LivingEntity>, AttributeSupplier>builder().put(EntityType.ALLAY, Allay.createAttributes().build()).put(EntityType.ARMOR_STAND, LivingEntity.createLivingAttributes().build()).put(EntityType.AXOLOTL, Axolotl.createAttributes().build()).put(EntityType.BAT, Bat.createAttributes().build()).put(EntityType.BEE, Bee.createAttributes().build()).put(EntityType.BLAZE, Blaze.createAttributes().build()).put(EntityType.CAT, Cat.createAttributes().build()).put(EntityType.CAMEL, Camel.createAttributes().build()).put(EntityType.CAVE_SPIDER, CaveSpider.createCaveSpider().build()).put(EntityType.CHICKEN, Chicken.createAttributes().build()).put(EntityType.COD, AbstractFish.createAttributes().build()).put(EntityType.COW, Cow.createAttributes().build()).put(EntityType.CREEPER, Creeper.createAttributes().build()).put(EntityType.DOLPHIN, Dolphin.createAttributes().build()).put(EntityType.DONKEY, AbstractChestedHorse.createBaseChestedHorseAttributes().build()).put(EntityType.DROWNED, Zombie.createAttributes().build()).put(EntityType.ELDER_GUARDIAN, ElderGuardian.createAttributes().build()).put(EntityType.ENDERMAN, EnderMan.createAttributes().build()).put(EntityType.ENDERMITE, Endermite.createAttributes().build()).put(EntityType.ENDER_DRAGON, EnderDragon.createAttributes().build()).put(EntityType.EVOKER, Evoker.createAttributes().build()).put(EntityType.FOX, Fox.createAttributes().build()).put(EntityType.FROG, Frog.createAttributes().build()).put(EntityType.GHAST, Ghast.createAttributes().build()).put(EntityType.GIANT, Giant.createAttributes().build()).put(EntityType.GLOW_SQUID, GlowSquid.createAttributes().build()).put(EntityType.GOAT, Goat.createAttributes().build()).put(EntityType.GUARDIAN, Guardian.createAttributes().build()).put(EntityType.HOGLIN, Hoglin.createAttributes().build()).put(EntityType.HORSE, AbstractHorse.createBaseHorseAttributes().build()).put(EntityType.HUSK, Zombie.createAttributes().build()).put(EntityType.ILLUSIONER, Illusioner.createAttributes().build()).put(EntityType.IRON_GOLEM, IronGolem.createAttributes().build()).put(EntityType.LLAMA, Llama.createAttributes().build()).put(EntityType.MAGMA_CUBE, MagmaCube.createAttributes().build()).put(EntityType.MOOSHROOM, Cow.createAttributes().build()).put(EntityType.MULE, AbstractChestedHorse.createBaseChestedHorseAttributes().build()).put(EntityType.OCELOT, Ocelot.createAttributes().build()).put(EntityType.PANDA, Panda.createAttributes().build()).put(EntityType.PARROT, Parrot.createAttributes().build()).put(EntityType.PHANTOM, Monster.createMonsterAttributes().build()).put(EntityType.PIG, Pig.createAttributes().build()).put(EntityType.PIGLIN, Piglin.createAttributes().build()).put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build()).put(EntityType.PILLAGER, Pillager.createAttributes().build()).put(EntityType.PLAYER, Player.createAttributes().build()).put(EntityType.POLAR_BEAR, PolarBear.createAttributes().build()).put(EntityType.PUFFERFISH, AbstractFish.createAttributes().build()).put(EntityType.RABBIT, Rabbit.createAttributes().build()).put(EntityType.RAVAGER, Ravager.createAttributes().build()).put(EntityType.SALMON, AbstractFish.createAttributes().build()).put(EntityType.SHEEP, Sheep.createAttributes().build()).put(EntityType.SHULKER, Shulker.createAttributes().build()).put(EntityType.SILVERFISH, Silverfish.createAttributes().build()).put(EntityType.SKELETON, AbstractSkeleton.createAttributes().build()).put(EntityType.SKELETON_HORSE, SkeletonHorse.createAttributes().build()).put(EntityType.SLIME, Monster.createMonsterAttributes().build()).put(EntityType.SNIFFER, Sniffer.createAttributes().build()).put(EntityType.SNOW_GOLEM, SnowGolem.createAttributes().build()).put(EntityType.SPIDER, Spider.createAttributes().build()).put(EntityType.SQUID, Squid.createAttributes().build()).put(EntityType.STRAY, AbstractSkeleton.createAttributes().build()).put(EntityType.STRIDER, Strider.createAttributes().build()).put(EntityType.TADPOLE, Tadpole.createAttributes().build()).put(EntityType.TRADER_LLAMA, Llama.createAttributes().build()).put(EntityType.TROPICAL_FISH, AbstractFish.createAttributes().build()).put(EntityType.TURTLE, Turtle.createAttributes().build()).put(EntityType.VEX, Vex.createAttributes().build()).put(EntityType.VILLAGER, Villager.createAttributes().build()).put(EntityType.VINDICATOR, Vindicator.createAttributes().build()).put(EntityType.WARDEN, Warden.createAttributes().build()).put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build()).put(EntityType.WITCH, Witch.createAttributes().build()).put(EntityType.WITHER, WitherBoss.createAttributes().build()).put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build()).put(EntityType.WOLF, Wolf.createAttributes().build()).put(EntityType.ZOGLIN, Zoglin.createAttributes().build()).put(EntityType.ZOMBIE, Zombie.createAttributes().build()).put(EntityType.ZOMBIE_HORSE, ZombieHorse.createAttributes().build()).put(EntityType.ZOMBIE_VILLAGER, Zombie.createAttributes().build()).put(EntityType.ZOMBIFIED_PIGLIN, ZombifiedPiglin.createAttributes().build()).build();
+ private static final Map<EntityType<? extends LivingEntity>, AttributeSupplier> SUPPLIERS = ImmutableMap.<EntityType<? extends LivingEntity>, AttributeSupplier>builder()
+ .put(EntityType.ALLAY, Allay.createAttributes().build())
+ .put(EntityType.ARMOR_STAND, LivingEntity.createLivingAttributes().build())
+ .put(EntityType.AXOLOTL, Axolotl.createAttributes().build())
+ .put(EntityType.BAT, Bat.createAttributes().build())
+ .put(EntityType.BEE, Bee.createAttributes().build())
+ .put(EntityType.BLAZE, Blaze.createAttributes().build())
+ .put(EntityType.CAT, Cat.createAttributes().build())
+ .put(EntityType.CAMEL, Camel.createAttributes().build())
+ .put(EntityType.CAVE_SPIDER, CaveSpider.createCaveSpider().build())
+ .put(EntityType.CHICKEN, Chicken.createAttributes().build())
+ .put(EntityType.COD, AbstractFish.createAttributes().build())
+ .put(EntityType.COW, Cow.createAttributes().build())
+ .put(EntityType.CREEPER, Creeper.createAttributes().build())
+ .put(EntityType.DOLPHIN, Dolphin.createAttributes().build())
+ .put(EntityType.DONKEY, AbstractChestedHorse.createBaseChestedHorseAttributes().build())
+ .put(EntityType.DROWNED, Zombie.createAttributes().build())
+ .put(EntityType.ELDER_GUARDIAN, ElderGuardian.createAttributes().build())
+ .put(EntityType.ENDERMAN, EnderMan.createAttributes().build())
+ .put(EntityType.ENDERMITE, Endermite.createAttributes().build())
+ .put(EntityType.ENDER_DRAGON, EnderDragon.createAttributes().build())
+ .put(EntityType.EVOKER, Evoker.createAttributes().build())
+ .put(EntityType.FOX, Fox.createAttributes().build())
+ .put(EntityType.FROG, Frog.createAttributes().build())
+ .put(EntityType.GHAST, Ghast.createAttributes().build())
+ .put(EntityType.GIANT, Giant.createAttributes().build())
+ .put(EntityType.GLOW_SQUID, GlowSquid.createAttributes().build())
+ .put(EntityType.GOAT, Goat.createAttributes().build())
+ .put(EntityType.GUARDIAN, Guardian.createAttributes().build())
+ .put(EntityType.HOGLIN, Hoglin.createAttributes().build())
+ .put(EntityType.HORSE, AbstractHorse.createBaseHorseAttributes().build())
+ .put(EntityType.HUSK, Zombie.createAttributes().build())
+ .put(EntityType.ILLUSIONER, Illusioner.createAttributes().build())
+ .put(EntityType.IRON_GOLEM, IronGolem.createAttributes().build())
+ .put(EntityType.LLAMA, Llama.createAttributes().build())
+ .put(EntityType.MAGMA_CUBE, MagmaCube.createAttributes().build())
+ .put(EntityType.MOOSHROOM, Cow.createAttributes().build())
+ .put(EntityType.MULE, AbstractChestedHorse.createBaseChestedHorseAttributes().build())
+ .put(EntityType.OCELOT, Ocelot.createAttributes().build())
+ .put(EntityType.PANDA, Panda.createAttributes().build())
+ .put(EntityType.PARROT, Parrot.createAttributes().build())
+ .put(EntityType.PHANTOM, net.minecraft.world.entity.monster.Phantom.createAttributes().build()) // Purpur
+ .put(EntityType.PIG, Pig.createAttributes().build())
+ .put(EntityType.PIGLIN, Piglin.createAttributes().build())
+ .put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build())
+ .put(EntityType.PILLAGER, Pillager.createAttributes().build())
+ .put(EntityType.PLAYER, Player.createAttributes().build())
+ .put(EntityType.POLAR_BEAR, PolarBear.createAttributes().build())
+ .put(EntityType.PUFFERFISH, AbstractFish.createAttributes().build())
+ .put(EntityType.RABBIT, Rabbit.createAttributes().build())
+ .put(EntityType.RAVAGER, Ravager.createAttributes().build())
+ .put(EntityType.SALMON, AbstractFish.createAttributes().build())
+ .put(EntityType.SHEEP, Sheep.createAttributes().build())
+ .put(EntityType.SHULKER, Shulker.createAttributes().build())
+ .put(EntityType.SILVERFISH, Silverfish.createAttributes().build())
+ .put(EntityType.SKELETON, AbstractSkeleton.createAttributes().build())
+ .put(EntityType.SKELETON_HORSE, SkeletonHorse.createAttributes().build())
+ .put(EntityType.SLIME, Monster.createMonsterAttributes().build())
+ .put(EntityType.SNIFFER, Sniffer.createAttributes().build())
+ .put(EntityType.SNOW_GOLEM, SnowGolem.createAttributes().build())
+ .put(EntityType.SPIDER, Spider.createAttributes().build())
+ .put(EntityType.SQUID, Squid.createAttributes().build())
+ .put(EntityType.STRAY, AbstractSkeleton.createAttributes().build())
+ .put(EntityType.STRIDER, Strider.createAttributes().build())
+ .put(EntityType.TADPOLE, Tadpole.createAttributes().build())
+ .put(EntityType.TRADER_LLAMA, Llama.createAttributes().build())
+ .put(EntityType.TROPICAL_FISH, AbstractFish.createAttributes().build())
+ .put(EntityType.TURTLE, Turtle.createAttributes().build())
+ .put(EntityType.VEX, Vex.createAttributes().build())
+ .put(EntityType.VILLAGER, Villager.createAttributes().build())
+ .put(EntityType.VINDICATOR, Vindicator.createAttributes().build())
+ .put(EntityType.WARDEN, Warden.createAttributes().build())
+ .put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build())
+ .put(EntityType.WITCH, Witch.createAttributes().build())
+ .put(EntityType.WITHER, WitherBoss.createAttributes().build())
+ .put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build())
+ .put(EntityType.WOLF, Wolf.createAttributes().build())
+ .put(EntityType.ZOGLIN, Zoglin.createAttributes().build())
+ .put(EntityType.ZOMBIE, Zombie.createAttributes().build())
+ .put(EntityType.ZOMBIE_HORSE, ZombieHorse.createAttributes().build())
+ .put(EntityType.ZOMBIE_VILLAGER, Zombie.createAttributes().build())
+ .put(EntityType.ZOMBIFIED_PIGLIN, ZombifiedPiglin.createAttributes().build()).build();
public static AttributeSupplier getSupplier(EntityType<? extends LivingEntity> type) {
return SUPPLIERS.get(type);
diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java b/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
index f0703302e7dbbda88de8c648d20d87c55ed9b1e0..a913ebabaa5f443afa987b972355a8f8d1723c78 100644
--- a/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
@@ -29,6 +29,7 @@ public class RangedAttribute extends Attribute {
@Override
public double sanitizeValue(double value) {
+ if (!org.purpurmc.purpur.PurpurConfig.clampAttributes) return Double.isNaN(value) ? this.minValue : value; // Purpur
return Double.isNaN(value) ? this.minValue : Mth.clamp(value, this.minValue, this.maxValue);
}
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java
index 57ef7fbba3028c28231abf7b7ae78aa019323536..651c156dc8a5aad04d461add02e22147af657d07 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java
@@ -58,9 +58,9 @@ public abstract class Behavior<E extends LivingEntity> implements BehaviorContro
this.status = Behavior.Status.RUNNING;
int i = this.minDuration + world.getRandom().nextInt(this.maxDuration + 1 - this.minDuration);
this.endTimestamp = time + (long)i;
- this.timing.startTiming(); // Paper - behavior timings
+ //this.timing.startTiming(); // Paper - behavior timings // Purpur
this.start(world, entity, time);
- this.timing.stopTiming(); // Paper - behavior timings
+ //this.timing.stopTiming(); // Paper - behavior timings // Purpur
return true;
} else {
return false;
@@ -72,13 +72,13 @@ public abstract class Behavior<E extends LivingEntity> implements BehaviorContro
@Override
public final void tickOrStop(ServerLevel world, E entity, long time) {
- this.timing.startTiming(); // Paper - behavior timings
+ //this.timing.startTiming(); // Paper - behavior timings // Purpur
if (!this.timedOut(time) && this.canStillUse(world, entity, time)) {
this.tick(world, entity, time);
} else {
this.doStop(world, entity, time);
}
- this.timing.stopTiming(); // Paper - behavior timings
+ //this.timing.stopTiming(); // Paper - behavior timings // Purpur
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
index d3a2a6dee2d83b3df0ddc521c080f7d72b709461..a1acc479c9d6a838e3180da3ac8a3a02d22db5c1 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
@@ -40,17 +40,19 @@ public class HarvestFarmland extends Behavior<Villager> {
private long nextOkStartTime;
private int timeWorkedSoFar;
private final List<BlockPos> validFarmlandAroundVillager = Lists.newArrayList();
+ private boolean clericWartFarmer = false; // Purpur
public HarvestFarmland() {
super(ImmutableMap.of(MemoryModuleType.LOOK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.SECONDARY_JOB_SITE, MemoryStatus.VALUE_PRESENT));
}
protected boolean checkExtraStartConditions(ServerLevel world, Villager entity) {
- if (!world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (!world.purpurConfig.villagerBypassMobGriefing && !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
return false;
- } else if (entity.getVillagerData().getProfession() != VillagerProfession.FARMER) {
+ } else if (entity.getVillagerData().getProfession() != VillagerProfession.FARMER && !(world.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC)) { // Purpur
return false;
} else {
+ if (!this.clericWartFarmer && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC) this.clericWartFarmer = true; // Purpur
BlockPos.MutableBlockPos blockposition_mutableblockposition = entity.blockPosition().mutable();
this.validFarmlandAroundVillager.clear();
@@ -81,6 +83,7 @@ public class HarvestFarmland extends Behavior<Villager> {
Block block = iblockdata.getBlock();
Block block1 = world.getBlockState(pos.below()).getBlock();
+ if (this.clericWartFarmer) return block == Blocks.NETHER_WART && iblockdata.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3 || iblockdata.isAir() && block1 == Blocks.SOUL_SAND; // Purpur
return block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata) || iblockdata.isAir() && block1 instanceof FarmBlock;
}
@@ -106,20 +109,20 @@ public class HarvestFarmland extends Behavior<Villager> {
Block block = iblockdata.getBlock();
Block block1 = world.getBlockState(this.aboveFarmlandPos.below()).getBlock();
- if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) {
+ if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata) && !this.clericWartFarmer || this.clericWartFarmer && block == Blocks.NETHER_WART && iblockdata.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3) { // Purpur
if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state
world.destroyBlock(this.aboveFarmlandPos, true, entity);
} // CraftBukkit
}
- if (iblockdata.isAir() && block1 instanceof FarmBlock && entity.hasFarmSeeds()) {
+ if (iblockdata.isAir() && (block1 instanceof FarmBlock && !this.clericWartFarmer || this.clericWartFarmer && block1 == Blocks.SOUL_SAND) && entity.hasFarmSeeds()) { // Purpur
SimpleContainer inventorysubcontainer = entity.getInventory();
for (int j = 0; j < inventorysubcontainer.getContainerSize(); ++j) {
ItemStack itemstack = inventorysubcontainer.getItem(j);
boolean flag = false;
- if (!itemstack.isEmpty() && itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS)) {
+ if (!itemstack.isEmpty() && (itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS) || this.clericWartFarmer && itemstack.getItem() == net.minecraft.world.item.Items.NETHER_WART)) {
Item item = itemstack.getItem();
if (item instanceof BlockItem) {
@@ -135,7 +138,7 @@ public class HarvestFarmland extends Behavior<Villager> {
}
if (flag) {
- world.playSound((Player) null, (double) this.aboveFarmlandPos.getX(), (double) this.aboveFarmlandPos.getY(), (double) this.aboveFarmlandPos.getZ(), SoundEvents.CROP_PLANTED, SoundSource.BLOCKS, 1.0F, 1.0F);
+ world.playSound((Player) null, (double) this.aboveFarmlandPos.getX(), (double) this.aboveFarmlandPos.getY(), (double) this.aboveFarmlandPos.getZ(), this.clericWartFarmer ? SoundEvents.NETHER_WART_PLANTED : SoundEvents.CROP_PLANTED, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur
itemstack.shrink(1);
if (itemstack.isEmpty()) {
inventorysubcontainer.setItem(j, ItemStack.EMPTY);
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java b/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
index 42ae4d293a420f0b8eb476df6389b2e7a693895f..97c20c5b89e6d7e4ed844eff39ee55dfa8988d37 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
@@ -57,7 +57,7 @@ public class InteractWithDoor {
if (iblockdata.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> {
return blockbase_blockdata.getBlock() instanceof DoorBlock;
- })) {
+ }) && !DoorBlock.requiresRedstone(entityliving.level(), iblockdata, blockposition)) { // Purpur
DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock();
if (!blockdoor.isOpen(iblockdata)) {
@@ -79,7 +79,7 @@ public class InteractWithDoor {
if (iblockdata1.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> {
return blockbase_blockdata.getBlock() instanceof DoorBlock;
- })) {
+ }) && !DoorBlock.requiresRedstone(entityliving.level(), iblockdata, blockposition1)) { // Purpur
DoorBlock blockdoor1 = (DoorBlock) iblockdata1.getBlock();
if (!blockdoor1.isOpen(iblockdata1)) {
@@ -122,7 +122,7 @@ public class InteractWithDoor {
if (!iblockdata.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> {
return blockbase_blockdata.getBlock() instanceof DoorBlock;
- })) {
+ }) || DoorBlock.requiresRedstone(entity.level(), iblockdata, blockposition)) { // Purpur
iterator.remove();
} else {
DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock();
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
index 050be72c815010bf3f4b72427e2052b00420e8ee..8a213205f57c8dcd2226d7194d74b1b13dec5a78 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
@@ -42,6 +42,7 @@ public class ShowTradesToPlayer extends Behavior<Villager> {
@Override
public boolean canStillUse(ServerLevel world, Villager entity, long time) {
+ if (!entity.level().purpurConfig.villagerDisplayTradeItem) return false; // Purpur
return this.checkExtraStartConditions(world, entity) && this.lookTime > 0 && entity.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).isPresent();
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java b/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java
index 3af715e2f3f3949af614a8fcebbc4a835d48ca49..ade1e411ea1f3b4c9a417265e77b0d6861b222f9 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java
@@ -56,6 +56,12 @@ public class TradeWithVillager extends Behavior<Villager> {
throwHalfStack(entity, ImmutableSet.of(Items.WHEAT), villager);
}
+ // Purpur start
+ if (world.purpurConfig.villagerClericsFarmWarts && world.purpurConfig.villagerClericFarmersThrowWarts && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC && entity.getInventory().countItem(Items.NETHER_WART) > Items.NETHER_WART.getMaxStackSize() / 2) {
+ throwHalfStack(entity, ImmutableSet.of(Items.NETHER_WART), villager);
+ }
+ // Purpur end
+
if (!this.trades.isEmpty() && entity.getInventory().hasAnyOf(this.trades)) {
throwHalfStack(entity, this.trades, villager);
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
index cd7a90ec1073b2b452ca70decefe6a594445003b..47672e48c1cae73cffe532d622b296343fc12ef0 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
@@ -30,8 +30,13 @@ public class VillagerGoalPackages {
}
public static ImmutableList<Pair<Integer, ? extends BehaviorControl<? super Villager>>> getWorkPackage(VillagerProfession profession, float speed) {
+ // Purpur start
+ return getWorkPackage(profession, speed, false);
+ }
+ public static ImmutableList<Pair<Integer, ? extends BehaviorControl<? super Villager>>> getWorkPackage(VillagerProfession profession, float speed, boolean clericsFarmWarts) {
+ // Purpur end
WorkAtPoi workAtPoi;
- if (profession == VillagerProfession.FARMER) {
+ if (profession == VillagerProfession.FARMER || (clericsFarmWarts && profession == VillagerProfession.CLERIC)) { // Purpur
workAtPoi = new WorkAtComposter();
} else {
workAtPoi = new WorkAtPoi();
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
index 645d204fe2c727637a6a547aad7d4db1279628ae..8be2f612c9e8a64b0aad6bb54450cdfb12b3c19e 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
@@ -127,8 +127,10 @@ public class VillagerMakeLove extends Behavior<Villager> {
return Optional.empty();
}
// Move age setting down
- parent.setAge(6000);
- partner.setAge(6000);
+ // Purpur start
+ parent.setAge(world.purpurConfig.villagerBreedingTicks);
+ partner.setAge(world.purpurConfig.villagerBreedingTicks);
+ // Purpur end
world.addFreshEntityWithPassengers(entityvillager2, CreatureSpawnEvent.SpawnReason.BREEDING);
// CraftBukkit end
world.broadcastEntityEvent(entityvillager2, (byte) 12);
diff --git a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java
index 03d77c6d4697d4934ca65e3329982f0efe089820..ce7e3e90993b76225dc820a21e04267e488f5f7b 100644
--- a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java
+++ b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java
@@ -29,6 +29,20 @@ public class MoveControl implements Control {
this.mob = entity;
}
+ // Purpur start
+ public void setSpeedModifier(double speed) {
+ this.speedModifier = speed;
+ }
+
+ public void setForward(float forward) {
+ this.strafeForwards = forward;
+ }
+
+ public void setStrafe(float strafe) {
+ this.strafeRight = strafe;
+ }
+ // Purpur end
+
public boolean hasWanted() {
return this.operation == MoveControl.Operation.MOVE_TO;
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java
index 7df56705a4a0de2dc4ff7ab133fc26612c219162..384bed4505b6cabb1ae151cd2c4eb5e56f24563f 100644
--- a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java
+++ b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java
@@ -3,7 +3,7 @@ package net.minecraft.world.entity.ai.control;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Mob;
-public class SmoothSwimmingLookControl extends LookControl {
+public class SmoothSwimmingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur
private final int maxYRotFromCenter;
private static final int HEAD_TILT_X = 10;
private static final int HEAD_TILT_Y = 20;
@@ -14,7 +14,7 @@ public class SmoothSwimmingLookControl extends LookControl {
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur
if (this.lookAtCooldown > 0) {
--this.lookAtCooldown;
this.getYRotD().ifPresent((yaw) -> {
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
index a85885ee51df585fa11ae9f8fcd67ff2a71c5a18..d81509e08e70ec5b2f837c9dc66b1254c86854e4 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
@@ -32,7 +32,7 @@ public class BreakDoorGoal extends DoorInteractGoal {
@Override
public boolean canUse() {
- return !super.canUse() ? false : (!this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.isValidDifficulty(this.mob.level().getDifficulty()) && !this.isOpen());
+ return !super.canUse() ? false : ((!this.mob.level().purpurConfig.zombieBypassMobGriefing && !this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) ? false : this.isValidDifficulty(this.mob.level().getDifficulty()) && !this.isOpen()); // Purpur
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
index 93bbda61f0eb2dd52573602b1f9cc7b031d1fc5a..63f9e5e2490e5b2fec6f2395077e21e601804ca5 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
@@ -74,7 +74,7 @@ public class EatBlockGoal extends Goal {
final BlockState blockState = this.level.getBlockState(blockposition); // Paper - fix wrong block state
if (EatBlockGoal.IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state
- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state
+ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !this.level.purpurConfig.sheepBypassMobGriefing && !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state // Purpur
this.level.destroyBlock(blockposition, false);
}
@@ -83,7 +83,7 @@ public class EatBlockGoal extends Goal {
BlockPos blockposition1 = blockposition.below();
if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) {
- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state
+ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !this.level.purpurConfig.sheepBypassMobGriefing && !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state // Purpur
this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState()));
this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2);
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
index 1635818fc4b1788c0d397085239df6dd75b210ab..02978315bc2b828cc603ce7478408f3f82c249c2 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/GoalSelector.java
@@ -105,8 +105,8 @@ public class GoalSelector {
}
public void tick() {
- ProfilerFiller profilerFiller = this.profiler.get();
- profilerFiller.push("goalCleanup");
+ //ProfilerFiller profilerFiller = this.profiler.get(); // Purpur
+ //profilerFiller.push("goalCleanup"); // Purpur
for(WrappedGoal wrappedGoal : this.availableGoals) {
if (wrappedGoal.isRunning() && (goalContainsAnyFlags(wrappedGoal, this.goalTypes) || !wrappedGoal.canContinueToUse())) {
@@ -123,8 +123,8 @@ public class GoalSelector {
}
}
- profilerFiller.pop();
- profilerFiller.push("goalUpdate");
+ //profilerFiller.pop(); // Purpur
+ //profilerFiller.push("goalUpdate"); // Purpur
for(WrappedGoal wrappedGoal2 : this.availableGoals) {
// Paper start
@@ -144,13 +144,13 @@ public class GoalSelector {
}
}
- profilerFiller.pop();
+ //profilerFiller.pop(); // Purpur
this.tickRunningGoals(true);
}
public void tickRunningGoals(boolean tickAll) {
- ProfilerFiller profilerFiller = this.profiler.get();
- profilerFiller.push("goalTick");
+ //ProfilerFiller profilerFiller = this.profiler.get(); // Purpur
+ //profilerFiller.push("goalTick"); // Purpur
for(WrappedGoal wrappedGoal : this.availableGoals) {
if (wrappedGoal.isRunning() && (tickAll || wrappedGoal.requiresUpdateEveryTick())) {
@@ -158,7 +158,7 @@ public class GoalSelector {
}
}
- profilerFiller.pop();
+ //profilerFiller.pop(); // Purpur
}
public Set<WrappedGoal> getAvailableGoals() {
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
index 21725aee29e9120d1c7e1e19f91c21a73a28844f..3fc9528201fb96d6a0f905afe0b6a82ec88a7235 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
@@ -22,6 +22,7 @@ public class LlamaFollowCaravanGoal extends Goal {
@Override
public boolean canUse() {
+ if (!this.llama.level().purpurConfig.llamaJoinCaravans || !this.llama.shouldJoinCaravan) return false; // Purpur
if (!this.llama.isLeashed() && !this.llama.inCaravan()) {
List<Entity> list = this.llama.level().getEntities(this.llama, this.llama.getBoundingBox().inflate(9.0D, 4.0D, 9.0D), (entity) -> {
EntityType<?> entityType = entity.getType();
@@ -71,6 +72,7 @@ public class LlamaFollowCaravanGoal extends Goal {
@Override
public boolean canContinueToUse() {
+ if (!this.llama.shouldJoinCaravan) return false; // Purpur
if (this.llama.inCaravan() && this.llama.getCaravanHead().isAlive() && this.firstIsLeashed(this.llama, 0)) {
double d = this.llama.distanceToSqr(this.llama.getCaravanHead());
if (d > 676.0D) {
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java
index 87fb10096fc9dade33c663234b1cecc34d3d77bb..874c7b29a261b1b5ad6e86ca219ff935870aecb0 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java
@@ -119,9 +119,9 @@ public class RangedBowAttackGoal<T extends Monster & RangedAttackMob> extends Go
}
this.mob.lookAt(livingEntity, 30.0F, 30.0F);
- } else {
+ } //else { // Purpur - fix MC-121706
this.mob.getLookControl().setLookAt(livingEntity, 30.0F, 30.0F);
- }
+ //} // Purpur
if (this.mob.isUsingItem()) {
if (!bl && this.seeTime < -60) {
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
index 509317a26c79f453335df1c19dc4c9ec570046af..8e4d673e4f2d7f50ea5ed13794da08b1d20e6665 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
@@ -40,7 +40,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal {
@Override
public boolean canUse() {
- if (!this.removerMob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (!this.removerMob.level().purpurConfig.zombieBypassMobGriefing && !this.removerMob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
return false;
} else if (this.nextStartTick > 0) {
--this.nextStartTick;
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
index 7fc5b5d624b61a3de9c71e975b51d20c5800be00..8fb070b1ae11d34b2fa420ded58097d421263cab 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
@@ -66,7 +66,7 @@ public class RunAroundLikeCrazyGoal extends Goal {
int i = this.horse.getTemper();
int j = this.horse.getMaxTemper();
- if (j > 0 && this.horse.getRandom().nextInt(j) < i && !CraftEventFactory.callEntityTameEvent(this.horse, ((CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent
+ if ((this.horse.level().purpurConfig.alwaysTameInCreative && ((Player) entity).getAbilities().instabuild) || (j > 0 && this.horse.getRandom().nextInt(j) < i && !CraftEventFactory.callEntityTameEvent(this.horse, ((CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled())) { // CraftBukkit - fire EntityTameEvent // Purpur
this.horse.tameWithName((Player) entity);
return;
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java
index e241ae250f4f04a17ef2c583d00b065a4ca56a4c..7b99c3446b50939241d3e220d93e05649f72a6df 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java
@@ -54,6 +54,14 @@ public class SwellGoal extends Goal {
this.creeper.setSwellDir(-1);
} else {
this.creeper.setSwellDir(1);
+ // Purpur start
+ if (this.creeper.level().purpurConfig.creeperEncircleTarget) {
+ net.minecraft.world.phys.Vec3 relative = this.creeper.position().subtract(this.target.position());
+ relative = relative.yRot((float) Math.PI / 3).normalize().multiply(2, 2, 2);
+ net.minecraft.world.phys.Vec3 destination = this.target.position().add(relative);
+ this.creeper.getNavigation().moveTo(destination.x, destination.y, destination.z, 1);
+ }
+ // Purpur end
}
}
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java
index 0d9b194781d152e842c9a4b8d6f23d307b2e4452..00cf59524477ec79d4354cc403fc3e75a63b81a0 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java
@@ -62,7 +62,7 @@ public class TemptGoal extends Goal {
}
private boolean shouldFollow(LivingEntity entity) {
- return this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem());
+ return (this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem())) && (!(this.mob instanceof net.minecraft.world.entity.npc.Villager villager) || !villager.isSleeping()); // Purpur Fix #512
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
index b376670d11088e524ce246f667e580e90cd119a3..fdb08ecc19bc4a1bc93cf6b18adcea2bc5573965 100644
--- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java
@@ -172,12 +172,12 @@ public abstract class PathNavigation {
}
}
// Paper end
- this.level.getProfiler().push("pathfind");
+ //this.level.getProfiler().push("pathfind"); // Purpur
BlockPos blockPos = useHeadPos ? this.mob.blockPosition().above() : this.mob.blockPosition();
int i = (int)(followRange + (float)range);
PathNavigationRegion pathNavigationRegion = new PathNavigationRegion(this.level, blockPos.offset(-i, -i, -i), blockPos.offset(i, i, i));
Path path = this.pathFinder.findPath(pathNavigationRegion, this.mob, positions, followRange, distance, this.maxVisitedNodesMultiplier);
- this.level.getProfiler().pop();
+ //this.level.getProfiler().pop(); // Purpur
if (path != null && path.getTarget() != null) {
this.targetPos = path.getTarget();
this.reachRange = distance;
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java
index cb1d91f9fe98f21c2afbe3894dfd9bca3bdd3ba6..d2703432af207c74ea8d298a784329c3219d2f13 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java
@@ -22,6 +22,13 @@ public class SecondaryPoiSensor extends Sensor<Villager> {
@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<Level> resourceKey = world.dimension();
BlockPos blockPos = entity.blockPosition();
List<GlobalPos> list = Lists.newArrayList();
@@ -38,7 +45,7 @@ public class SecondaryPoiSensor extends Sensor<Villager> {
}
}
- Brain<?> brain = entity.getBrain();
+ //Brain<?> brain = entity.getBrain(); // Purpur - moved up
if (!list.isEmpty()) {
brain.setMemory(MemoryModuleType.SECONDARY_JOB_SITE, list);
} else {
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensing.java b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensing.java
index 51772f03a3469b11e7166ec6f3a1b9c64a606221..02f2f46ccc48bb4d9bd08555818b0489f60d9f13 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensing.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensing.java
@@ -26,9 +26,9 @@ public class Sensing {
} else if (this.unseen.contains(i)) {
return false;
} else {
- this.mob.level().getProfiler().push("hasLineOfSight");
+ //this.mob.level().getProfiler().push("hasLineOfSight"); // Purpur
boolean bl = this.mob.hasLineOfSight(entity);
- this.mob.level().getProfiler().pop();
+ //this.mob.level().getProfiler().pop(); // Purpur
if (bl) {
this.seen.add(i);
} else {
diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java
index fcdb9bde8e1605e30dde3e580491522d4b62cdc0..7094701d213c73ba47ace806962244c10fdf4dda 100644
--- a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java
+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java
@@ -46,10 +46,10 @@ public abstract class Sensor<E extends LivingEntity> {
if (--this.timeToTick <= 0L) {
// Paper start - configurable sensor tick rate and timings
this.timeToTick = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.sensor.get(entity.getType(), this.configKey), this.scanRate);
- this.timing.startTiming();
+ //this.timing.startTiming(); // Purpur
// Paper end
this.doTick(world, entity);
- this.timing.stopTiming(); // Paper - sensor timings
+ //this.timing.stopTiming(); // Paper - sensor timings // Purpur
}
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
index d25307ae8bbdf10ae067ec70fc2cb957b852a0eb..54bdb81785b617e13e67530752395f2a0c6d703a 100644
--- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
+++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
@@ -64,6 +64,10 @@ public class TargetingConditions {
return false;
} else if (this.selector != null && !this.selector.test(targetEntity)) {
return false;
+ // Purpur start
+ } else if (!targetEntity.level().purpurConfig.idleTimeoutTargetPlayer && targetEntity instanceof net.minecraft.server.level.ServerPlayer player && player.isAfk()) {
+ return false;
+ // Purpur end
} else {
if (baseEntity == null) {
if (this.isCombat && (!targetEntity.canBeSeenAsEnemy() || targetEntity.level().getDifficulty() == Difficulty.PEACEFUL)) {
diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
index 2c91fe46355c9a201507de5577f693ed4f5fb974..1eab1393a2636c4a247f25dae317cea60cc7021c 100644
--- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java
+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
@@ -18,6 +18,7 @@ import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobSpawnType;
+import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
@@ -43,12 +44,59 @@ public class Bat extends AmbientCreature {
public Bat(EntityType<? extends Bat> type, Level world) {
super(type, world);
+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.075F); // Purpur
if (!world.isClientSide) {
this.setResting(true);
}
}
+ // Purpur start
+ @Override
+ public boolean shouldSendAttribute(net.minecraft.world.entity.ai.attributes.Attribute attribute) { return attribute != Attributes.FLYING_SPEED; } // Fixes log spam on clients
+
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.batRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.batRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.batControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.batMaxY;
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ if (isResting()) {
+ setResting(false);
+ level().levelEvent(null, 1025, new BlockPos(this).above(), 0);
+ }
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2;
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(MoverType.SELF, mot.multiply(speed, 0.25, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+ // Purpur end
+
@Override
public boolean isFlapping() {
return !this.isResting() && this.tickCount % Bat.TICKS_PER_FLAP == 0;
@@ -98,7 +146,7 @@ public class Bat extends AmbientCreature {
protected void pushEntities() {}
public static AttributeSupplier.Builder createAttributes() {
- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D);
+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur
}
public boolean isResting() {
@@ -130,6 +178,14 @@ public class Bat extends AmbientCreature {
@Override
protected void customServerAiStep() {
+ // Purpur start
+ if (getRider() != null && this.isControllable()) {
+ Vec3 mot = getDeltaMovement();
+ setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z());
+ return;
+ }
+ // Purpur end
+
super.customServerAiStep();
BlockPos blockposition = this.blockPosition();
BlockPos blockposition1 = blockposition.above();
@@ -208,6 +264,28 @@ public class Bat extends AmbientCreature {
}
}
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.batMaxHealth);
+ this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(this.level().purpurConfig.batFollowRange);
+ this.getAttribute(Attributes.KNOCKBACK_RESISTANCE).setBaseValue(this.level().purpurConfig.batKnockbackResistance);
+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.batMovementSpeed);
+ this.getAttribute(Attributes.FLYING_SPEED).setBaseValue(this.level().purpurConfig.batFlyingSpeed);
+ this.getAttribute(Attributes.ARMOR).setBaseValue(this.level().purpurConfig.batArmor);
+ this.getAttribute(Attributes.ARMOR_TOUGHNESS).setBaseValue(this.level().purpurConfig.batArmorToughness);
+ this.getAttribute(Attributes.ATTACK_KNOCKBACK).setBaseValue(this.level().purpurConfig.batAttackKnockback);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.batTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.batAlwaysDropExp;
+ }
+
@Override
public void readAdditionalSaveData(CompoundTag nbt) {
super.readAdditionalSaveData(nbt);
@@ -227,7 +305,7 @@ public class Bat extends AmbientCreature {
int i = world.getMaxLocalRawBrightness(pos);
byte b0 = 4;
- if (Bat.isHalloween()) {
+ if (Bat.isHalloweenSeason(world.getMinecraftWorld())) { // Purpur
b0 = 7;
} else if (random.nextBoolean()) {
return false;
@@ -241,6 +319,7 @@ public class Bat extends AmbientCreature {
private static boolean isSpookySeason = false;
private static final int ONE_HOUR = 20 * 60 * 60;
private static int lastSpookyCheck = -ONE_HOUR;
+ public static boolean isHalloweenSeason(Level level) { return level.purpurConfig.forceHalloweenSeason || isHalloween(); } // Purpur
private static boolean isHalloween() {
if (net.minecraft.server.MinecraftServer.currentTick - lastSpookyCheck > ONE_HOUR) {
LocalDate localdate = LocalDate.now();
diff --git a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java
index 2249fc6dd98afb8d52623b5864955fdd3b3fc042..2ccfaab0a02cf5ff9779e250fb79a75a9852e10d 100644
--- a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java
+++ b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java
@@ -94,7 +94,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
@Override
protected void registerGoals() {
super.registerGoals();
- this.goalSelector.addGoal(0, new PanicGoal(this, 1.25D));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 1.6D, 1.4D, EntitySelector.NO_SPECTATORS::test));
this.goalSelector.addGoal(4, new AbstractFish.FishSwimGoal(this));
}
@@ -107,7 +107,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
@Override
public void travel(Vec3 movementInput) {
if (this.isEffectiveAi() && this.isInWater()) {
- this.moveRelative(0.01F, movementInput);
+ this.moveRelative(getRider() != null ? getSpeed() : 0.01F, movementInput); // Purpur
this.move(MoverType.SELF, this.getDeltaMovement());
this.setDeltaMovement(this.getDeltaMovement().scale(0.9D));
if (this.getTarget() == null) {
@@ -166,7 +166,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
protected void playStepSound(BlockPos pos, BlockState state) {
}
- static class FishMoveControl extends MoveControl {
+ static class FishMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur
private final AbstractFish fish;
FishMoveControl(AbstractFish owner) {
@@ -174,14 +174,22 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
this.fish = owner;
}
+ // Purpur start
@Override
- public void tick() {
+ public void purpurTick(Player rider) {
+ super.purpurTick(rider);
+ fish.setDeltaMovement(fish.getDeltaMovement().add(0.0D, 0.005D, 0.0D));
+ }
+ // Purpur end
+
+ @Override
+ public void vanillaTick() { // Purpur
if (this.fish.isEyeInFluid(FluidTags.WATER)) {
this.fish.setDeltaMovement(this.fish.getDeltaMovement().add(0.0D, 0.005D, 0.0D));
}
if (this.operation == MoveControl.Operation.MOVE_TO && !this.fish.getNavigation().isDone()) {
- float f = (float)(this.speedModifier * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED));
+ float f = (float)(this.getSpeedModifier() * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur
this.fish.setSpeed(Mth.lerp(0.125F, this.fish.getSpeed(), f));
double d = this.wantedX - this.fish.getX();
double e = this.wantedY - this.fish.getY();
diff --git a/src/main/java/net/minecraft/world/entity/animal/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java
index c2f61ed153260692c96af4f20bc5b7d55cbbc380..8773b1072016a3bbf025959e9ab827704ec17fc6 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Animal.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java
@@ -42,6 +42,7 @@ public abstract class Animal extends AgeableMob {
@Nullable
public UUID loveCause;
public ItemStack breedItem; // CraftBukkit - Add breedItem variable
+ public abstract int getPurpurBreedTime(); // Purpur
protected Animal(EntityType<? extends Animal> type, Level world) {
super(type, world);
@@ -154,7 +155,7 @@ public abstract class Animal extends AgeableMob {
if (this.isFood(itemstack)) {
int i = this.getAge();
- if (!this.level().isClientSide && i == 0 && this.canFallInLove()) {
+ if (!this.level().isClientSide && i == 0 && this.canFallInLove() && (this.level().purpurConfig.animalBreedingCooldownSeconds <= 0 || !this.level().hasBreedingCooldown(player.getUUID(), this.getClass()))) { // Purpur
this.usePlayerItem(player, hand, itemstack);
this.setInLove(player);
return InteractionResult.SUCCESS;
@@ -236,12 +237,20 @@ public abstract class Animal extends AgeableMob {
AgeableMob entityageable = this.getBreedOffspring(world, other);
if (entityageable != null) {
- entityageable.setBaby(true);
- entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
- // CraftBukkit start - call EntityBreedEvent
+ // Purpur start
ServerPlayer breeder = Optional.ofNullable(this.getLoveCause()).or(() -> {
return Optional.ofNullable(other.getLoveCause());
}).orElse(null);
+ if (breeder != null && world.purpurConfig.animalBreedingCooldownSeconds > 0) {
+ if (world.hasBreedingCooldown(breeder.getUUID(), this.getClass())) {
+ return;
+ }
+ world.addBreedingCooldown(breeder.getUUID(), this.getClass());
+ }
+ entityageable.setBaby(true);
+ entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
+ // CraftBukkit start - call EntityBreedEvent
+ // Purpur end
int experience = this.getRandom().nextInt(7) + 1;
EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityageable, this, other, breeder, this.breedItem, experience);
if (entityBreedEvent.isCancelled()) {
@@ -269,8 +278,10 @@ public abstract class Animal extends AgeableMob {
entityplayer.awardStat(Stats.ANIMALS_BRED);
CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, entityanimal, entityageable);
} // Paper
- this.setAge(6000);
- entityanimal.setAge(6000);
+ // Purpur start
+ this.setAge(this.getPurpurBreedTime());
+ entityanimal.setAge(entityanimal.getPurpurBreedTime());
+ // Purpur end
this.resetLove();
entityanimal.resetLove();
worldserver.broadcastEntityEvent(this, (byte) 18);
diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java
index 55026e1731e41b4e3e4c6a8fef5d96a32051a556..6c04c8e7776b2830ac368229da834532e8ce163e 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java
@@ -43,6 +43,7 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobType;
+import net.minecraft.world.entity.MoverType;
import net.minecraft.world.entity.NeutralMob;
import net.minecraft.world.entity.PathfinderMob;
import net.minecraft.world.entity.Pose;
@@ -147,6 +148,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
public Bee(EntityType<? extends Bee> type, Level world) {
super(type, world);
this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60);
+ final org.purpurmc.purpur.controller.FlyingMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.25F, 1.0F, false); // Purpur
// Paper start - apply gravity to bees when they get stuck in the void, fixes MC-167279
class BeeFlyingMoveControl extends FlyingMoveControl {
public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) {
@@ -155,22 +157,69 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
@Override
public void tick() {
+ // Purpur start
+ if (mob.getRider() != null && mob.isControllable()) {
+ flyingController.purpurTick(mob.getRider());
+ return;
+ }
+ // Purpur end
if (this.mob.getY() <= Bee.this.level().getMinBuildHeight()) {
this.mob.setNoGravity(false);
}
super.tick();
}
+
+ // Purpur start
+ @Override
+ public boolean hasWanted() {
+ return mob.getRider() != null || !mob.isControllable() || super.hasWanted();
+ }
+ // Purpur end
}
this.moveControl = new BeeFlyingMoveControl(this, 20, true);
// Paper end
this.lookControl = new Bee.BeeLookControl(this);
this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F);
- this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F);
+ if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur
this.setPathfindingMalus(BlockPathTypes.WATER_BORDER, 16.0F);
this.setPathfindingMalus(BlockPathTypes.COCOA, -1.0F);
this.setPathfindingMalus(BlockPathTypes.FENCE, -1.0F);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.beeRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.beeRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.beeControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.beeMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2;
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(MoverType.SELF, mot.multiply(speed, speed, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+ // Purpur end
+
@Override
protected void defineSynchedData() {
super.defineSynchedData();
@@ -185,6 +234,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(0, new Bee.BeeAttackGoal(this, 1.399999976158142D, true));
this.goalSelector.addGoal(1, new Bee.BeeEnterHiveGoal());
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
@@ -200,6 +250,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
this.goalSelector.addGoal(7, new Bee.BeeGrowCropGoal());
this.goalSelector.addGoal(8, new Bee.BeeWanderGoal());
this.goalSelector.addGoal(9, new FloatGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, (new Bee.BeeHurtByOtherGoal(this)).setAlertOthers(new Class[0]));
this.targetSelector.addGoal(2, new Bee.BeeBecomeAngryTargetGoal(this));
this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true));
@@ -348,7 +399,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
boolean wantsToEnterHive() {
if (this.stayOutOfHiveCountdown <= 0 && !this.beePollinateGoal.isPollinating() && !this.hasStung() && this.getTarget() == null) {
- boolean flag = this.isTiredOfLookingForNectar() || this.level().isRaining() || this.level().isNight() || this.hasNectar();
+ boolean flag = this.isTiredOfLookingForNectar() || (this.level().isRaining() && !this.level().purpurConfig.beeCanWorkInRain) || (this.level().isNight() && !this.level().purpurConfig.beeCanWorkAtNight) || this.hasNectar(); // Purpur
return flag && !this.isHiveNearFire();
} else {
@@ -388,6 +439,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
this.hurt(this.damageSources().drown(), 1.0F);
}
+ if (flag && !this.level().purpurConfig.beeDiesAfterSting) setHasStung(false); else // Purpur
if (flag) {
++this.timeSinceSting;
if (this.timeSinceSting % 5 == 0 && this.random.nextInt(Mth.clamp(1200 - this.timeSinceSting, 1, 1200)) == 0) {
@@ -420,6 +472,26 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
}
}
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.beeMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.beeBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.beeTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.beeAlwaysDropExp;
+ }
+
@Override
public int getRemainingPersistentAngerTime() {
return (Integer) this.entityData.get(Bee.DATA_REMAINING_ANGER_TIME);
@@ -735,6 +807,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
if (optional.isPresent()) {
Bee.this.savedFlowerPos = (BlockPos) optional.get();
Bee.this.navigation.moveTo((double) Bee.this.savedFlowerPos.getX() + 0.5D, (double) Bee.this.savedFlowerPos.getY() + 0.5D, (double) Bee.this.savedFlowerPos.getZ() + 0.5D, 1.2000000476837158D);
+ new org.purpurmc.purpur.event.entity.BeeFoundFlowerEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos)).callEvent(); // Purpur
return true;
} else {
Bee.this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(Bee.this.random, 20, 60);
@@ -791,6 +864,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
this.pollinating = false;
Bee.this.navigation.stop();
Bee.this.remainingCooldownBeforeLocatingNewFlower = 200;
+ new org.purpurmc.purpur.event.entity.BeeStopPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), Bee.this.savedFlowerPos == null ? null : io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos), Bee.this.hasNectar()).callEvent(); // Purpur
}
@Override
@@ -837,6 +911,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
this.setWantedPos();
}
+ if (this.successfulPollinatingTicks == 0) new org.purpurmc.purpur.event.entity.BeeStartedPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos)).callEvent(); // Purpur
++this.successfulPollinatingTicks;
if (Bee.this.random.nextFloat() < 0.05F && this.successfulPollinatingTicks > this.lastSoundPlayedTick + 60) {
this.lastSoundPlayedTick = this.successfulPollinatingTicks;
@@ -881,16 +956,16 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
}
}
- private class BeeLookControl extends LookControl {
+ private class BeeLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur
BeeLookControl(Mob entity) {
super(entity);
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur
if (!Bee.this.isAngry()) {
- super.tick();
+ super.vanillaTick(); // Purpur
}
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java
index 90ce201bc7c47cef9bc59d7b535a7453854bac75..3347b39fa1bc3308aa3b70b4169523885b91a047 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Cat.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java
@@ -97,6 +97,51 @@ public class Cat extends TamableAnimal implements VariantHolder<CatVariant> {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.catRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.catRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.catControllable;
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ setInSittingPose(false);
+ setLying(false);
+ setRelaxStateOne(false);
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.catMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.catBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.catTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.catAlwaysDropExp;
+ }
+
public ResourceLocation getResourceLocation() {
return this.getVariant().texture();
}
@@ -105,6 +150,7 @@ public class Cat extends TamableAnimal implements VariantHolder<CatVariant> {
protected void registerGoals() {
this.temptGoal = new Cat.CatTemptGoal(this, 0.6D, Cat.TEMPT_INGREDIENT, true);
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new PanicGoal(this, 1.5D));
this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
this.goalSelector.addGoal(3, new Cat.CatRelaxOnOwnerGoal(this));
@@ -117,6 +163,7 @@ public class Cat extends TamableAnimal implements VariantHolder<CatVariant> {
this.goalSelector.addGoal(10, new BreedGoal(this, 0.8D));
this.goalSelector.addGoal(11, new WaterAvoidingRandomStrollGoal(this, 0.8D, 1.0000001E-5F));
this.goalSelector.addGoal(12, new LookAtPlayerGoal(this, Player.class, 10.0F));
+ this.targetSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Rabbit.class, false, (Predicate) null));
this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR));
}
@@ -308,6 +355,14 @@ public class Cat extends TamableAnimal implements VariantHolder<CatVariant> {
return Mth.lerp(tickDelta, this.relaxStateOneAmountO, this.relaxStateOneAmount);
}
+ // Purpur start
+ @Override
+ public void tame(Player player) {
+ setCollarColor(level().purpurConfig.catDefaultCollarColor);
+ super.tame(player);
+ }
+ // Purpur end
+
@Nullable
@Override
public Cat getBreedOffspring(ServerLevel world, AgeableMob entity) {
@@ -373,6 +428,7 @@ public class Cat extends TamableAnimal implements VariantHolder<CatVariant> {
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
+ if (getRider() != null) return InteractionResult.PASS; // Purpur
ItemStack itemstack = player.getItemInHand(hand);
Item item = itemstack.getItem();
@@ -419,7 +475,7 @@ public class Cat extends TamableAnimal implements VariantHolder<CatVariant> {
}
} else if (this.isFood(itemstack)) {
this.usePlayerItem(player, hand, itemstack);
- if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit
+ if ((this.level().purpurConfig.alwaysTameInCreative && player.getAbilities().instabuild) || (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled())) { // CraftBukkit // Purpur
this.tame(player);
this.setOrderedToSit(true);
this.level().broadcastEntityEvent(this, (byte) 7);
diff --git a/src/main/java/net/minecraft/world/entity/animal/Chicken.java b/src/main/java/net/minecraft/world/entity/animal/Chicken.java
index 84cefbc339ca368ac306d521012ce2f826a660b5..00bdb2b75221349cb40da2b5fc1563393fd17ef6 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Chicken.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Chicken.java
@@ -54,16 +54,65 @@ public class Chicken extends Animal {
this.setPathfindingMalus(BlockPathTypes.WATER, 0.0F);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.chickenRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.chickenRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.chickenControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.chickenMaxHealth);
+ if (level().purpurConfig.chickenRetaliate) {
+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(2.0D);
+ }
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.chickenBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.chickenTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.chickenAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
- this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
+ // this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D)); // Purpur - moved down
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
this.goalSelector.addGoal(3, new TemptGoal(this, 1.0D, Chicken.FOOD_ITEMS, false));
this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.1D));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
+ // Purpur start
+ if (level().purpurConfig.chickenRetaliate) {
+ this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.0D, false));
+ this.targetSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal(this));
+ } else {
+ this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D));
+ }
+ // Purpur end
}
@Override
@@ -72,7 +121,7 @@ public class Chicken extends Animal {
}
public static AttributeSupplier.Builder createAttributes() {
- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 4.0D).add(Attributes.MOVEMENT_SPEED, 0.25D);
+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 4.0D).add(Attributes.MOVEMENT_SPEED, 0.25D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/animal/Cod.java b/src/main/java/net/minecraft/world/entity/animal/Cod.java
index 824e5e4fe7619ae46061c3c978c9a044db8c84ab..f0b6118a9995bb41836685bbf94d2e7fb15761eb 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Cod.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Cod.java
@@ -13,6 +13,33 @@ public class Cod extends AbstractSchoolingFish {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.codRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.codControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.codMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.codTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.codAlwaysDropExp;
+ }
+
@Override
public ItemStack getBucketItemStack() {
return new ItemStack(Items.COD_BUCKET);
diff --git a/src/main/java/net/minecraft/world/entity/animal/Cow.java b/src/main/java/net/minecraft/world/entity/animal/Cow.java
index 0263acf81330ada1bb8ae7d52a3b04e08d0c4dcb..3cee7d5656b2df2997ceaee3489c02ce881e6875 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Cow.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Cow.java
@@ -2,6 +2,7 @@ package net.minecraft.world.entity.animal;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
+import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
@@ -29,6 +30,7 @@ import net.minecraft.world.item.ItemUtils;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
// CraftBukkit start
import org.bukkit.craftbukkit.event.CraftEventFactory;
@@ -37,25 +39,74 @@ import org.bukkit.event.player.PlayerBucketFillEvent;
// CraftBukkit end
public class Cow extends Animal {
+ private boolean isNaturallyAggressiveToPlayers; // Purpur
public Cow(EntityType<? extends Cow> type, Level world) {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.cowRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.cowRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.cowControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.cowMaxHealth);
+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.cowNaturallyAggressiveToPlayersDamage); // Purpur
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.cowBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.cowTakeDamageFromWater;
+ }
+
+ @Override
+ public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.MobSpawnType spawnReason, net.minecraft.world.entity.SpawnGroupData entityData, net.minecraft.nbt.CompoundTag entityNbt) {
+ this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance;
+ return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt);
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.cowAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new PanicGoal(this, 2.0D));
+ this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
+ if (level().purpurConfig.cowFeedMushrooms > 0) this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, Ingredient.of(Items.WHEAT, Blocks.RED_MUSHROOM.asItem(), Blocks.BROWN_MUSHROOM.asItem()), false)); else // Purpur
this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, Ingredient.of(Items.WHEAT), false));
this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25D));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, target -> isNaturallyAggressiveToPlayers)); // Purpur
}
public static AttributeSupplier.Builder createAttributes() {
- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D);
+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur
}
@Override
@@ -85,6 +136,7 @@ public class Cow extends Animal {
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
+ if (getRider() != null) return InteractionResult.PASS; // Purpur
ItemStack itemstack = player.getItemInHand(hand);
if (itemstack.is(Items.BUCKET) && !this.isBaby()) {
@@ -92,7 +144,7 @@ public class Cow extends Animal {
PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand);
if (event.isCancelled()) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur
}
// CraftBukkit end
@@ -101,6 +153,10 @@ public class Cow extends Animal {
player.setItemInHand(hand, itemstack1);
return InteractionResult.sidedSuccess(this.level().isClientSide);
+ // Purpur start - feed mushroom to change to mooshroom
+ } else if (level().purpurConfig.cowFeedMushrooms > 0 && this.getType() != EntityType.MOOSHROOM && isMushroom(itemstack)) {
+ return this.feedMushroom(player, itemstack);
+ // Purpur end
} else {
return super.mobInteract(player, hand);
}
@@ -116,4 +172,69 @@ public class Cow extends Animal {
protected float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) {
return this.isBaby() ? dimensions.height * 0.95F : 1.3F;
}
+
+ // Purpur start - feed mushroom to change to mooshroom
+ private int redMushroomsFed = 0;
+ private int brownMushroomsFed = 0;
+
+ private boolean isMushroom(ItemStack stack) {
+ return stack.getItem() == Blocks.RED_MUSHROOM.asItem() || stack.getItem() == Blocks.BROWN_MUSHROOM.asItem();
+ }
+
+ private int incrementFeedCount(ItemStack stack) {
+ if (stack.getItem() == Blocks.RED_MUSHROOM.asItem()) {
+ return ++redMushroomsFed;
+ } else {
+ return ++brownMushroomsFed;
+ }
+ }
+
+ private InteractionResult feedMushroom(Player player, ItemStack stack) {
+ level().broadcastEntityEvent(this, (byte) 18); // hearts
+ playSound(SoundEvents.COW_MILK, 1.0F, 1.0F);
+ if (incrementFeedCount(stack) < level().purpurConfig.cowFeedMushrooms) {
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(1);
+ }
+ return InteractionResult.CONSUME; // require 5 mushrooms to transform (prevents mushroom duping)
+ }
+ MushroomCow mooshroom = EntityType.MOOSHROOM.create(level());
+ if (mooshroom == null) {
+ return InteractionResult.PASS;
+ }
+ if (stack.getItem() == Blocks.BROWN_MUSHROOM.asItem()) {
+ mooshroom.setVariant(MushroomCow.MushroomType.BROWN);
+ } else {
+ mooshroom.setVariant(MushroomCow.MushroomType.RED);
+ }
+ mooshroom.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
+ mooshroom.setHealth(this.getHealth());
+ mooshroom.setAge(getAge());
+ mooshroom.copyPosition(this);
+ mooshroom.setYBodyRot(this.yBodyRot);
+ mooshroom.setYHeadRot(this.getYHeadRot());
+ mooshroom.yRotO = this.yRotO;
+ mooshroom.xRotO = this.xRotO;
+ if (this.hasCustomName()) {
+ mooshroom.setCustomName(this.getCustomName());
+ }
+ if (CraftEventFactory.callEntityTransformEvent(this, mooshroom, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) {
+ return InteractionResult.PASS;
+ }
+ if (!new com.destroystokyo.paper.event.entity.EntityTransformedEvent(this.getBukkitEntity(), mooshroom.getBukkitEntity(), com.destroystokyo.paper.event.entity.EntityTransformedEvent.TransformedReason.INFECTED).callEvent()) {
+ return InteractionResult.PASS;
+ }
+ this.level().addFreshEntity(mooshroom);
+ this.remove(RemovalReason.DISCARDED);
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(1);
+ }
+ for (int i = 0; i < 15; ++i) {
+ ((ServerLevel) level()).sendParticles(((ServerLevel) level()).players(), null, ParticleTypes.HAPPY_VILLAGER,
+ getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1,
+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true);
+ }
+ return InteractionResult.SUCCESS;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java
index 8448c5d778998390cf2b683f36e4e18ca7ffdc34..4c9c07d470357c35bd5ba3c21437a2c44c52811e 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java
@@ -82,19 +82,104 @@ public class Dolphin extends WaterAnimal {
public static final Predicate<ItemEntity> ALLOWED_ITEMS = (entityitem) -> {
return !entityitem.hasPickUpDelay() && entityitem.isAlive() && entityitem.isInWater();
};
+ private boolean isNaturallyAggressiveToPlayers; // Purpur
+ private int spitCooldown; // Purpur
public Dolphin(EntityType<? extends Dolphin> type, Level world) {
super(type, world);
- this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
+ // Purpur start
+ class DolphinMoveControl extends SmoothSwimmingMoveControl {
+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterMoveControllerWASD;
+ private final Dolphin dolphin;
+
+ public DolphinMoveControl(Dolphin dolphin, int pitchChange, int yawChange, float speedInWater, float speedInAir, boolean buoyant) {
+ super(dolphin, pitchChange, yawChange, speedInWater, speedInAir, buoyant);
+ this.dolphin = dolphin;
+ this.waterMoveControllerWASD = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(dolphin);
+ }
+
+ @Override
+ public void tick() {
+ if (dolphin.getRider() != null && dolphin.isControllable()) {
+ purpurTick(dolphin.getRider());
+ } else {
+ super.tick();
+ }
+ }
+
+ public void purpurTick(Player rider) {
+ if (dolphin.getAirSupply() < 150) {
+ // if drowning override player WASD controls to find air
+ super.tick();
+ } else {
+ waterMoveControllerWASD.purpurTick(rider);
+ dolphin.setDeltaMovement(dolphin.getDeltaMovement().add(0.0D, 0.005D, 0.0D));
+ }
+ }
+ };
+ this.moveControl = new DolphinMoveControl(this, 85, 10, 0.02F, 0.1F, true);
+ // Purpur end
this.lookControl = new SmoothSwimmingLookControl(this, 10);
this.setCanPickUpLoot(true);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.dolphinRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.dolphinControllable;
+ }
+
+ @Override
+ public boolean onSpacebar() {
+ if (spitCooldown == 0 && getRider() != null) {
+ spitCooldown = level().purpurConfig.dolphinSpitCooldown;
+
+ org.bukkit.craftbukkit.entity.CraftPlayer player = (org.bukkit.craftbukkit.entity.CraftPlayer) getRider().getBukkitEntity();
+ if (!player.hasPermission("allow.special.dolphin")) {
+ return false;
+ }
+
+ org.bukkit.Location loc = player.getEyeLocation();
+ loc.setPitch(loc.getPitch() - 10);
+ org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(10).add(loc.toVector());
+
+ org.purpurmc.purpur.entity.DolphinSpit spit = new org.purpurmc.purpur.entity.DolphinSpit(level(), this);
+ spit.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), level().purpurConfig.dolphinSpitSpeed, 5.0F);
+
+ level().addFreshEntity(spit);
+ playSound(SoundEvents.DOLPHIN_ATTACK, 1.0F, 1.0F + (random.nextFloat() - random.nextFloat()) * 0.2F);
+ return true;
+ }
+ return false;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.dolphinMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.dolphinTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.dolphinAlwaysDropExp;
+ }
+
@Nullable
@Override
public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) {
this.setAirSupply(this.getMaxAirSupply());
this.setXRot(0.0F);
+ this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance; // Purpur
return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt);
}
@@ -164,17 +249,21 @@ public class Dolphin extends WaterAnimal {
protected void registerGoals() {
this.goalSelector.addGoal(0, new BreathAirGoal(this));
this.goalSelector.addGoal(0, new TryFindWaterGoal(this));
+ this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new Dolphin.DolphinSwimToTreasureGoal(this));
this.goalSelector.addGoal(2, new Dolphin.DolphinSwimWithPlayerGoal(this, 4.0D));
this.goalSelector.addGoal(4, new RandomSwimmingGoal(this, 1.0D, 10));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(5, new DolphinJumpGoal(this, 10));
- this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2000000476837158D, true));
+ //this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur - moved up
this.goalSelector.addGoal(8, new Dolphin.PlayWithItemsGoal());
this.goalSelector.addGoal(8, new FollowBoatGoal(this));
this.goalSelector.addGoal(9, new AvoidEntityGoal<>(this, Guardian.class, 8.0F, 1.0D, 1.0D));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Guardian.class})).setAlertOthers());
+ this.targetSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, target -> isNaturallyAggressiveToPlayers)); // Purpur
}
public static AttributeSupplier.Builder createAttributes() {
@@ -225,7 +314,7 @@ public class Dolphin extends WaterAnimal {
@Override
protected boolean canRide(Entity entity) {
- return true;
+ return boardingCooldown <= 0; // Purpur - make dolphin honor ride cooldown like all other non-boss mobs;
}
@Override
@@ -260,6 +349,11 @@ public class Dolphin extends WaterAnimal {
@Override
public void tick() {
super.tick();
+ // Purpur start
+ if (spitCooldown > 0) {
+ spitCooldown--;
+ }
+ // Purpur end
if (this.isNoAi()) {
this.setAirSupply(this.getMaxAirSupply());
} else {
@@ -405,6 +499,7 @@ public class Dolphin extends WaterAnimal {
@Override
public boolean canUse() {
+ if (this.dolphin.level().purpurConfig.dolphinDisableTreasureSearching) return false; // Purpur
return this.dolphin.gotFish() && this.dolphin.getAirSupply() >= 100;
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java
index 62604ed2e82afd3603ccac5d9c6e285c431542b1..ca3096abbd54745af591101c7b306ed87f212954 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Fox.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java
@@ -35,6 +35,7 @@ import net.minecraft.util.RandomSource;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.Entity;
@@ -88,6 +89,7 @@ import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.CaveVines;
import net.minecraft.world.level.block.SweetBerryBushBlock;
import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.pathfinder.BlockPathTypes;
import net.minecraft.world.phys.Vec3;
@@ -141,6 +143,64 @@ public class Fox extends Animal implements VariantHolder<Fox.Type> {
this.setCanPickUpLoot(true);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.foxRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.foxRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.foxControllable;
+ }
+
+ @Override
+ public float getJumpPower() {
+ return getRider() != null && this.isControllable() ? 0.5F : super.getJumpPower();
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ setCanPickUpLoot(false);
+ clearStates();
+ setIsPouncing(false);
+ spitOutItem(getItemBySlot(EquipmentSlot.MAINHAND));
+ setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
+ }
+
+ @Override
+ public void onDismount(Player rider) {
+ super.onDismount(rider);
+ setCanPickUpLoot(true);
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.foxMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.foxBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.foxTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.foxAlwaysDropExp;
+ }
+
@Override
protected void defineSynchedData() {
super.defineSynchedData();
@@ -160,6 +220,7 @@ public class Fox extends Animal implements VariantHolder<Fox.Type> {
return entityliving instanceof AbstractSchoolingFish;
});
this.goalSelector.addGoal(0, new Fox.FoxFloatGoal());
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(0, new ClimbOnTopOfPowderSnowGoal(this, this.level()));
this.goalSelector.addGoal(1, new Fox.FaceplantGoal());
this.goalSelector.addGoal(2, new Fox.FoxPanicGoal(2.2D));
@@ -186,6 +247,7 @@ public class Fox extends Animal implements VariantHolder<Fox.Type> {
this.goalSelector.addGoal(11, new Fox.FoxSearchForItemsGoal());
this.goalSelector.addGoal(12, new Fox.FoxLookAtPlayerGoal(this, Player.class, 24.0F));
this.goalSelector.addGoal(13, new Fox.PerchAndSearchGoal());
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(3, new Fox.DefendTrustedTargetGoal(LivingEntity.class, false, false, (entityliving) -> {
return Fox.TRUSTED_TARGET_SELECTOR.test(entityliving) && !this.trusts(entityliving.getUUID());
}));
@@ -342,6 +404,11 @@ public class Fox extends Animal implements VariantHolder<Fox.Type> {
}
private void setTargetGoals() {
+ // Purpur start - do not add duplicate goals
+ this.targetSelector.removeGoal(this.landTargetGoal);
+ this.targetSelector.removeGoal(this.turtleEggTargetGoal);
+ this.targetSelector.removeGoal(this.fishTargetGoal);
+ // Purpur end
if (this.getVariant() == Fox.Type.RED) {
this.targetSelector.addGoal(4, this.landTargetGoal);
this.targetSelector.addGoal(4, this.turtleEggTargetGoal);
@@ -375,6 +442,7 @@ public class Fox extends Animal implements VariantHolder<Fox.Type> {
public void setVariant(Fox.Type variant) {
this.entityData.set(Fox.DATA_TYPE_ID, variant.getId());
+ this.setTargetGoals(); // Purpur - fix API bug not updating pathfinders on type change
}
List<UUID> getTrustedUUIDs() {
@@ -711,6 +779,29 @@ public class Fox extends Animal implements VariantHolder<Fox.Type> {
return this.getTrustedUUIDs().contains(uuid);
}
+ // Purpur start
+ @Override
+ public InteractionResult mobInteract(Player player, InteractionHand hand) {
+ if (level().purpurConfig.foxTypeChangesWithTulips) {
+ ItemStack itemstack = player.getItemInHand(hand);
+ if (getVariant() == Type.RED && itemstack.getItem() == Items.WHITE_TULIP) {
+ setVariant(Type.SNOW);
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
+ return InteractionResult.SUCCESS;
+ } else if (getVariant() == Type.SNOW && itemstack.getItem() == Items.ORANGE_TULIP) {
+ setVariant(Type.RED);
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
+ return InteractionResult.SUCCESS;
+ }
+ }
+ return super.mobInteract(player, hand);
+ }
+ // Purpur end
+
@Override
// Paper start - Cancellable death event
protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource source) {
@@ -758,16 +849,16 @@ public class Fox extends Animal implements VariantHolder<Fox.Type> {
return new Vec3(0.0D, (double) (0.55F * this.getEyeHeight()), (double) (this.getBbWidth() * 0.4F));
}
- public class FoxLookControl extends LookControl {
+ public class FoxLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur
public FoxLookControl() {
super(Fox.this);
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur
if (!Fox.this.isSleeping()) {
- super.tick();
+ super.vanillaTick(); // Purpur
}
}
@@ -778,16 +869,16 @@ public class Fox extends Animal implements VariantHolder<Fox.Type> {
}
}
- private class FoxMoveControl extends MoveControl {
+ private class FoxMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur
public FoxMoveControl() {
super(Fox.this);
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur
if (Fox.this.canMove()) {
- super.tick();
+ super.vanillaTick(); // Purpur
}
}
@@ -905,8 +996,10 @@ public class Fox extends Animal implements VariantHolder<Fox.Type> {
CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer2, this.animal, this.partner, entityfox);
}
- this.animal.setAge(6000);
- this.partner.setAge(6000);
+ // Purpur start
+ this.animal.setAge(this.animal.getPurpurBreedTime());
+ this.partner.setAge(this.partner.getPurpurBreedTime());
+ // Purpur end
this.animal.resetLove();
this.partner.resetLove();
worldserver.addFreshEntityWithPassengers(entityfox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason
@@ -1294,7 +1387,7 @@ public class Fox extends Animal implements VariantHolder<Fox.Type> {
}
protected void onReachedTarget() {
- if (Fox.this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (Fox.this.level().purpurConfig.foxBypassMobGriefing || Fox.this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
BlockState iblockdata = Fox.this.level().getBlockState(this.blockPos);
if (iblockdata.is(Blocks.SWEET_BERRY_BUSH)) {
diff --git a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
index f383928fc5b331ddf128bdcb6a23010d8fe088d3..64aba511e615983988cdb6a0fd45b7d9d4f2f16d 100644
--- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
+++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
@@ -60,14 +60,59 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
private int remainingPersistentAngerTime;
@Nullable
private UUID persistentAngerTarget;
+ @Nullable private UUID summoner; // Purpur
public IronGolem(EntityType<? extends IronGolem> type, Level world) {
super(type, world);
this.setMaxUpStep(1.0F);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.ironGolemRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ironGolemRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.ironGolemControllable;
+ }
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ironGolemMaxHealth);
+ }
+ // Purpur end
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.ironGolemTakeDamageFromWater;
+ }
+
+ @Nullable
+ public UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(@Nullable UUID summoner) {
+ this.summoner = summoner;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.ironGolemAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
+ if (level().purpurConfig.ironGolemCanSwim) this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur
+ if (this.level().purpurConfig.ironGolemPoppyCalm) this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.ReceiveFlower(this)); // Purpur
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0D, true));
this.goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9D, 32.0F));
this.goalSelector.addGoal(2, new MoveBackToVillageGoal(this, 0.6D, false));
@@ -75,6 +120,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
this.goalSelector.addGoal(5, new OfferFlowerGoal(this));
this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new DefendVillageTargetGoal(this));
this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0]));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
@@ -139,6 +185,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
public void addAdditionalSaveData(CompoundTag nbt) {
super.addAdditionalSaveData(nbt);
nbt.putBoolean("PlayerCreated", this.isPlayerCreated());
+ if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur
this.addPersistentAngerSaveData(nbt);
}
@@ -146,6 +193,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
public void readAdditionalSaveData(CompoundTag nbt) {
super.readAdditionalSaveData(nbt);
this.setPlayerCreated(nbt.getBoolean("PlayerCreated"));
+ if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur
this.readPersistentAngerSaveData(this.level(), nbt);
}
@@ -270,13 +318,13 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
ItemStack itemstack = player.getItemInHand(hand);
if (!itemstack.is(Items.IRON_INGOT)) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur
} else {
float f = this.getHealth();
this.heal(25.0F);
if (this.getHealth() == f) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur
} else {
float f1 = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F;
@@ -285,6 +333,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
itemstack.shrink(1);
}
+ if (this.level().purpurConfig.ironGolemHealCalm && isAngry() && getHealth() == getMaxHealth()) stopBeingAngry(); // Purpur
return InteractionResult.sidedSuccess(this.level().isClientSide);
}
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
index a04374f91f2fbb31219d86b6ae63bcf8fdf7318c..e462b81aa1276a8f1222b74459359f6c6d57f739 100644
--- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
+++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
@@ -63,6 +63,43 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder<Mushroo
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.mooshroomRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.mooshroomRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.mooshroomControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.mooshroomMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.mooshroomBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.mooshroomTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.mooshroomAlwaysDropExp;
+ }
+
@Override
public float getWalkTargetValue(BlockPos pos, LevelReader world) {
return world.getBlockState(pos.below()).is(Blocks.MYCELIUM) ? 10.0F : world.getPathfindingCostFromLightLevels(pos);
@@ -124,10 +161,10 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder<Mushroo
} else if (itemstack.is(Items.SHEARS) && this.readyForShearing()) {
// CraftBukkit start
if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur
}
// CraftBukkit end
- this.shear(SoundSource.PLAYERS);
+ this.shear(SoundSource.PLAYERS, net.minecraft.world.item.enchantment.EnchantmentHelper.getMobLooting(player)); // Purpur
this.gameEvent(GameEvent.SHEAR, player);
if (!this.level().isClientSide) {
itemstack.hurtAndBreak(1, player, (entityhuman1) -> {
@@ -145,7 +182,7 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder<Mushroo
Optional<Pair<MobEffect, Integer>> optional = this.getEffectFromItemStack(itemstack);
if (!optional.isPresent()) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur
}
Pair<MobEffect, Integer> pair = (Pair) optional.get();
@@ -170,7 +207,7 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder<Mushroo
}
@Override
- public void shear(SoundSource shearedSoundCategory) {
+ public void shear(SoundSource shearedSoundCategory, int looting) { // Purpur
this.level().playSound((Player) null, (Entity) this, SoundEvents.MOOSHROOM_SHEAR, shearedSoundCategory, 1.0F, 1.0F);
if (!this.level().isClientSide()) {
Cow entitycow = (Cow) EntityType.COW.create(this.level());
@@ -180,7 +217,13 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder<Mushroo
// this.discard(); // CraftBukkit - moved down
entitycow.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
entitycow.setHealth(this.getHealth());
+ // Purpur start
+ entitycow.copyPosition(this);
entitycow.yBodyRot = this.yBodyRot;
+ entitycow.setYHeadRot(this.getYHeadRot());
+ entitycow.yRotO = this.yRotO;
+ entitycow.xRotO = this.xRotO;
+ // Purpur end
if (this.hasCustomName()) {
entitycow.setCustomName(this.getCustomName());
entitycow.setCustomNameVisible(this.isCustomNameVisible());
@@ -200,7 +243,7 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder<Mushroo
this.discard(); // CraftBukkit - from above
// CraftBukkit end
- for (int i = 0; i < 5; ++i) {
+ for (int i = 0; i < 5 + (org.purpurmc.purpur.PurpurConfig.allowShearsLooting ? looting : 0); ++i) {
// CraftBukkit start
ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), new ItemStack(this.getVariant().blockState.getBlock()));
EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
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 924ea172cab5178e5754bfe09cc7b83c1a66faa6..45fb438065b1db2a9baa648f4d397472847ed638 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java
@@ -68,6 +68,43 @@ 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);
+ }
+
+ @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);
}
@@ -99,12 +136,14 @@ public class Ocelot extends Animal {
protected void registerGoals() {
this.temptGoal = new Ocelot.OcelotTemptGoal(this, 0.6D, Ocelot.TEMPT_INGREDIENT, true);
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(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));
}
@@ -254,7 +293,7 @@ public class Ocelot extends Animal {
if (world.isUnobstructed(this) && !world.containsAnyLiquid(this.getBoundingBox())) {
BlockPos blockposition = this.blockPosition();
- if (blockposition.getY() < world.getSeaLevel()) {
+ if (!level().purpurConfig.ocelotSpawnUnderSeaLevel && blockposition.getY() < world.getSeaLevel()) {
return false;
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java
index f43c4fed59c4c75540008284ddb197d9a6b5487b..bd7c5f6768a54a3d8ffd585d91414e65936991da 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java
@@ -111,6 +111,53 @@ public class Panda extends Animal {
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.pandaRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.pandaRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.pandaControllable;
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ setForwardMot(0.0F);
+ sit(false);
+ eat(false);
+ setOnBack(false);
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pandaMaxHealth);
+ setAttributes();
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.pandaBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.pandaTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.pandaAlwaysDropExp;
+ }
+
@Override
public boolean canTakeItem(ItemStack stack) {
EquipmentSlot enumitemslot = Mob.getEquipmentSlotForItem(stack);
@@ -272,6 +319,7 @@ public class Panda extends Animal {
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(2, new Panda.PandaPanicGoal(this, 2.0D));
this.goalSelector.addGoal(2, new Panda.PandaBreedGoal(this, 1.0D));
this.goalSelector.addGoal(3, new Panda.PandaAttackGoal(this, 1.2000000476837158D, true));
@@ -287,6 +335,7 @@ public class Panda extends Animal {
this.goalSelector.addGoal(12, new Panda.PandaRollGoal(this));
this.goalSelector.addGoal(13, new FollowParentGoal(this, 1.25D));
this.goalSelector.addGoal(14, new WaterAvoidingRandomStrollGoal(this, 1.0D));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, (new Panda.PandaHurtByTargetGoal(this, new Class[0])).setAlertOthers(new Class[0]));
}
@@ -610,7 +659,10 @@ public class Panda extends Animal {
public void setAttributes() {
if (this.isWeak()) {
- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(10.0D);
+ // Purpur start
+ net.minecraft.world.entity.ai.attributes.AttributeInstance maxHealth = this.getAttribute(Attributes.MAX_HEALTH);
+ maxHealth.setBaseValue(maxHealth.getValue() / 2);
+ // Purpur end
}
if (this.isLazy()) {
@@ -633,7 +685,7 @@ public class Panda extends Animal {
ItemStack itemstack = player.getItemInHand(hand);
if (this.isScared()) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur
} else if (this.isOnBack()) {
this.setOnBack(false);
return InteractionResult.sidedSuccess(this.level().isClientSide);
@@ -650,7 +702,7 @@ public class Panda extends Animal {
this.setInLove(player);
} else {
if (this.level().isClientSide || this.isSitting() || this.isInWater()) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur
}
this.tryToSit();
@@ -669,7 +721,7 @@ public class Panda extends Animal {
return InteractionResult.SUCCESS;
} else {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur
}
}
@@ -709,7 +761,7 @@ public class Panda extends Animal {
return !this.isOnBack() && !this.isScared() && !this.isEating() && !this.isRolling() && !this.isSitting();
}
- private static class PandaMoveControl extends MoveControl {
+ private static class PandaMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur
private final Panda panda;
@@ -719,9 +771,9 @@ public class Panda extends Animal {
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur
if (this.panda.canPerformAction()) {
- super.tick();
+ super.vanillaTick(); // Purpur
}
}
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/Parrot.java b/src/main/java/net/minecraft/world/entity/animal/Parrot.java
index e1874aaa0065a6e8e6ae043713c22f08b58b5e21..0e20d9ed5b25f5aaab68dc3a4fa4e63ced280969 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java
@@ -129,12 +129,88 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder<Parrot
public Parrot(EntityType<? extends Parrot> type, Level world) {
super(type, world);
- this.moveControl = new FlyingMoveControl(this, 10, false);
+ // Purpur start
+ final org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F);
+ class ParrotMoveControl extends FlyingMoveControl {
+ public ParrotMoveControl(Mob entity, int maxPitchChange, boolean noGravity) {
+ super(entity, maxPitchChange, noGravity);
+ }
+
+ @Override
+ public void tick() {
+ if (mob.getRider() != null && mob.isControllable()) {
+ flyingController.purpurTick(mob.getRider());
+ } else {
+ super.tick();
+ }
+ }
+
+ @Override
+ public boolean hasWanted() {
+ return mob.getRider() != null && mob.isControllable() ? getForwardMot() != 0 || getStrafeMot() != 0 : super.hasWanted();
+ }
+ }
+ this.moveControl = new ParrotMoveControl(this, 10, false);
+ // Purpur end
this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F);
this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, -1.0F);
this.setPathfindingMalus(BlockPathTypes.COCOA, -1.0F);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.parrotRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.parrotRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.parrotControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.parrotMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2;
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.25, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.parrotMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return 6000;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.parrotTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.parrotAlwaysDropExp;
+ }
+
@Nullable
@Override
public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) {
@@ -153,8 +229,11 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder<Parrot
@Override
protected void registerGoals() {
- this.goalSelector.addGoal(0, new PanicGoal(this, 1.25D));
+ //this.goalSelector.addGoal(0, new PanicGoal(this, 1.25D)); // Purpur - move down
this.goalSelector.addGoal(0, new FloatGoal(this));
+ if (this.level().purpurConfig.parrotBreedable) this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.BreedGoal(this, 1.0D)); // Purpur
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
+ this.goalSelector.addGoal(1, new PanicGoal(this, 1.25D)); // Purpur
this.goalSelector.addGoal(1, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
this.goalSelector.addGoal(2, new FollowOwnerGoal(this, 1.0D, 5.0F, 1.0F, true));
@@ -261,7 +340,7 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder<Parrot
}
if (!this.level().isClientSide) {
- if (this.random.nextInt(10) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit
+ if ((this.level().purpurConfig.alwaysTameInCreative && player.getAbilities().instabuild) || (this.random.nextInt(10) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled())) { // CraftBukkit // Purpur
this.tame(player);
this.level().broadcastEntityEvent(this, (byte) 7);
} else {
@@ -269,6 +348,7 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder<Parrot
}
}
+ if (this.level().purpurConfig.parrotBreedable) return super.mobInteract(player, hand); // Purpur
return InteractionResult.sidedSuccess(this.level().isClientSide);
} else if (itemstack.is(Parrot.POISONOUS_FOOD)) {
if (!player.getAbilities().instabuild) {
@@ -294,7 +374,7 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder<Parrot
@Override
public boolean isFood(ItemStack stack) {
- return false;
+ return this.level().purpurConfig.parrotBreedable && Parrot.TAME_FOOD.contains(stack.getItem()); // Purpur
}
public static boolean checkParrotSpawnRules(EntityType<Parrot> type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
@@ -306,13 +386,13 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder<Parrot
@Override
public boolean canMate(Animal other) {
- return false;
+ return super.canMate(other); // Purpur
}
@Nullable
@Override
public AgeableMob getBreedOffspring(ServerLevel world, AgeableMob entity) {
- return null;
+ return world.purpurConfig.parrotBreedable ? EntityType.PARROT.create(world) : null; // Purpur
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/animal/Pig.java b/src/main/java/net/minecraft/world/entity/animal/Pig.java
index 4175767f248b1389c5c2a896edde5dc036eb4345..23baba7cdf010afc1c0a1ed264020949bd622fde 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Pig.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Pig.java
@@ -64,9 +64,47 @@ public class Pig extends Animal implements ItemSteerable, Saddleable {
this.steering = new ItemBasedSteering(this.entityData, Pig.DATA_BOOST_TIME, Pig.DATA_SADDLE_ID);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.pigRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.pigRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.pigControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pigMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.pigBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.pigTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.pigAlwaysDropExp;
+ }
+
@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, 1.25D));
this.goalSelector.addGoal(3, new BreedGoal(this, 1.0D));
this.goalSelector.addGoal(4, new TemptGoal(this, 1.2D, Ingredient.of(Items.CARROT_ON_A_STICK), false));
@@ -151,6 +189,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 04f07ecb74d53b6ecf6a9a6055488d5a3023b535..5dd1fa0824dc153c52bf75e397bfc40422c710d0 100644
--- a/src/main/java/net/minecraft/world/entity/animal/PolarBear.java
+++ b/src/main/java/net/minecraft/world/entity/animal/PolarBear.java
@@ -60,11 +60,81 @@ public class PolarBear extends Animal implements NeutralMob {
private int remainingPersistentAngerTime;
@Nullable
private UUID persistentAngerTarget;
+ private int standTimer = 0; // Purpur
public PolarBear(EntityType<? extends PolarBear> type, Level world) {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.polarBearRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.polarBearRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.polarBearControllable;
+ }
+
+ @Override
+ public boolean onSpacebar() {
+ if (!isStanding()) {
+ if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0) {
+ setStanding(true);
+ playSound(SoundEvents.POLAR_BEAR_WARNING, 1.0F, 1.0F);
+ }
+ }
+ return false;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.polarBearMaxHealth);
+ }
+
+ public boolean canMate(Animal other) {
+ if (other == this) {
+ return false;
+ } else if (this.isStanding()) {
+ return false;
+ } else if (this.getTarget() != null) {
+ return false;
+ } else if (!(other instanceof PolarBear)) {
+ return false;
+ } else {
+ PolarBear bear = (PolarBear) other;
+ if (bear.isStanding()) {
+ return false;
+ }
+ if (bear.getTarget() != null) {
+ return false;
+ }
+ return this.isInLove() && bear.isInLove();
+ }
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.polarBearBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.polarBearTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.polarBearAlwaysDropExp;
+ }
+
@Nullable
@Override
public AgeableMob getBreedOffspring(ServerLevel world, AgeableMob entity) {
@@ -73,19 +143,27 @@ public class PolarBear extends Animal implements NeutralMob {
@Override
public boolean isFood(ItemStack stack) {
- return false;
+ return level().purpurConfig.polarBearBreedableItem != null && stack.getItem() == level().purpurConfig.polarBearBreedableItem; // Purpur
}
@Override
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new PolarBear.PolarBearMeleeAttackGoal());
this.goalSelector.addGoal(1, new PolarBear.PolarBearPanicGoal());
+ // Purpur start
+ if (level().purpurConfig.polarBearBreedableItem != null) {
+ this.goalSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.BreedGoal(this, 1.0D));
+ this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, net.minecraft.world.item.crafting.Ingredient.of(level().purpurConfig.polarBearBreedableItem), false));
+ }
+ // Purpur end
this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25D));
this.goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0D));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new PolarBear.PolarBearHurtByTargetGoal());
this.targetSelector.addGoal(2, new PolarBear.PolarBearAttackPlayersGoal());
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
@@ -202,6 +280,11 @@ public class PolarBear extends Animal implements NeutralMob {
this.updatePersistentAnger((ServerLevel)this.level(), true);
}
+ // Purpur start
+ if (isStanding() && --standTimer <= 0) {
+ setStanding(false);
+ }
+ // Purpur end
}
@Override
@@ -231,6 +314,7 @@ public class PolarBear extends Animal implements NeutralMob {
public void setStanding(boolean warning) {
this.entityData.set(DATA_STANDING_ID, warning);
+ standTimer = warning ? 20 : -1; // Purpur
}
public float getStandingAnimationScale(float tickDelta) {
diff --git a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java
index a197337f2e09f53cf382022569c8836745d78769..54f5206b686c3cf4d2e5b470c07047a518f5dd00 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java
@@ -45,6 +45,33 @@ public class Pufferfish extends AbstractFish {
this.refreshDimensions();
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.pufferfishRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.pufferfishControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pufferfishMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.pufferfishTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.pufferfishAlwaysDropExp;
+ }
+
@Override
protected void defineSynchedData() {
super.defineSynchedData();
diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java
index 082351bd5f98d8738334b9164375f63fdc890455..81a33954aedffd18fcfa96babeaf6388715ab3f3 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java
@@ -86,6 +86,7 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
private boolean wasOnGround;
private int jumpDelayTicks;
public int moreCarrotTicks;
+ private boolean actualJump; // Purpur
public Rabbit(EntityType<? extends Rabbit> type, Level world) {
super(type, world);
@@ -93,9 +94,75 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
this.moveControl = new Rabbit.RabbitMoveControl(this);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.rabbitRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.rabbitRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.rabbitControllable;
+ }
+
+ @Override
+ public boolean onSpacebar() {
+ if (onGround) {
+ actualJump = true;
+ jumpFromGround();
+ actualJump = false;
+ }
+ return true;
+ }
+
+ private void handleJumping() {
+ if (onGround) {
+ RabbitJumpControl jumpController = (RabbitJumpControl) jumpControl;
+ if (!wasOnGround) {
+ setJumping(false);
+ jumpController.setCanJump(false);
+ }
+ if (!jumpController.wantJump()) {
+ if (moveControl.hasWanted()) {
+ startJumping();
+ }
+ } else if (!jumpController.canJump()) {
+ jumpController.setCanJump(true);
+ }
+ }
+ wasOnGround = onGround;
+ }
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.rabbitMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.rabbitBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.rabbitTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.rabbitAlwaysDropExp;
+ }
+ // Purpur end
+
@Override
public void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level()));
this.goalSelector.addGoal(1, new Rabbit.RabbitPanicGoal(this, 2.2D));
this.goalSelector.addGoal(2, new BreedGoal(this, 0.8D));
@@ -110,6 +177,14 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
@Override
protected float getJumpPower() {
+ // Purpur start
+ if (getRider() != null && this.isControllable()) {
+ if (getForwardMot() < 0) {
+ setSpeed(getForwardMot() * 2F);
+ }
+ return actualJump ? 0.5F : 0.3F;
+ }
+ // Purpur end
float f = 0.3F;
if (this.horizontalCollision || this.moveControl.hasWanted() && this.moveControl.getWantedY() > this.getY() + 0.5D) {
@@ -134,7 +209,7 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
}
@Override
- protected void jumpFromGround() {
+ public void jumpFromGround() { // Purpur - protected -> public
super.jumpFromGround();
double d0 = this.moveControl.getSpeedModifier();
@@ -184,6 +259,13 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
@Override
public void customServerAiStep() {
+ // Purpur start
+ if (getRider() != null && this.isControllable()) {
+ handleJumping();
+ return;
+ }
+ // Purpur end
+
if (this.jumpDelayTicks > 0) {
--this.jumpDelayTicks;
}
@@ -401,10 +483,23 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
}
this.setVariant(entityrabbit_variant);
+
+ // Purpur start
+ if (entityrabbit_variant != Variant.EVIL && world.getLevel().purpurConfig.rabbitNaturalToast > 0D && random.nextDouble() <= world.getLevel().purpurConfig.rabbitNaturalToast) {
+ setCustomName(Component.translatable("Toast"));
+ }
+ // Purpur end
+
return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData, entityNbt);
}
private static Rabbit.Variant getRandomRabbitVariant(LevelAccessor world, BlockPos pos) {
+ // Purpur start
+ Level level = world.getMinecraftWorld();
+ if (level.purpurConfig.rabbitNaturalKiller > 0D && world.getRandom().nextDouble() <= level.purpurConfig.rabbitNaturalKiller) {
+ return Rabbit.Variant.EVIL;
+ }
+ // Purpur end
Holder<Biome> holder = world.getBiome(pos);
int i = world.getRandom().nextInt(100);
@@ -468,7 +563,7 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
}
}
- private static class RabbitMoveControl extends MoveControl {
+ private static class RabbitMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur
private final Rabbit rabbit;
private double nextJumpSpeed;
@@ -479,14 +574,14 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur
if (this.rabbit.onGround() && !this.rabbit.jumping && !((Rabbit.RabbitJumpControl) this.rabbit.jumpControl).wantJump()) {
this.rabbit.setSpeedModifier(0.0D);
} else if (this.hasWanted()) {
this.rabbit.setSpeedModifier(this.nextJumpSpeed);
}
- super.tick();
+ super.vanillaTick(); // Purpur
}
@Override
@@ -548,7 +643,7 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
@Override
public boolean canUse() {
if (this.nextStartTick <= 0) {
- if (!this.rabbit.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (!this.rabbit.level().purpurConfig.rabbitBypassMobGriefing && !this.rabbit.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
return false;
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/Salmon.java b/src/main/java/net/minecraft/world/entity/animal/Salmon.java
index 0af79daa357f53a8871e293b57e16c099e5d3f64..382e47f26ee94506cb76463a677351b9bdcf8040 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Salmon.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Salmon.java
@@ -13,6 +13,33 @@ public class Salmon extends AbstractSchoolingFish {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.salmonRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.salmonControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.salmonMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.salmonTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.salmonAlwaysDropExp;
+ }
+
@Override
public int getMaxSchoolSize() {
return 5;
diff --git a/src/main/java/net/minecraft/world/entity/animal/Sheep.java b/src/main/java/net/minecraft/world/entity/animal/Sheep.java
index c0e89262c596fbdd0bb3c3f76baccb17a1bb5fcd..54de1f782e45cb3e3b1442aeb56b6e8547b065c0 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Sheep.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Sheep.java
@@ -117,10 +117,48 @@ public class Sheep extends Animal implements Shearable {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.sheepRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.sheepRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.sheepControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.sheepMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.sheepBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.sheepTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.sheepAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
this.eatBlockGoal = new EatBlockGoal(this);
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new PanicGoal(this, 1.25D));
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
this.goalSelector.addGoal(3, new TemptGoal(this, 1.1D, Ingredient.of(Items.WHEAT), false));
@@ -255,7 +293,7 @@ public class Sheep extends Animal implements Shearable {
return InteractionResult.PASS;
}
// CraftBukkit end
- this.shear(SoundSource.PLAYERS);
+ this.shear(SoundSource.PLAYERS, net.minecraft.world.item.enchantment.EnchantmentHelper.getMobLooting(player)); // Purpur
this.gameEvent(GameEvent.SHEAR, player);
itemstack.hurtAndBreak(1, player, (entityhuman1) -> {
entityhuman1.broadcastBreakEvent(hand);
@@ -270,14 +308,15 @@ public class Sheep extends Animal implements Shearable {
}
@Override
- public void shear(SoundSource shearedSoundCategory) {
+ public void shear(SoundSource shearedSoundCategory, int looting) { // Purpur
this.level().playSound((Player) null, (Entity) this, SoundEvents.SHEEP_SHEAR, shearedSoundCategory, 1.0F, 1.0F);
this.setSheared(true);
int i = 1 + this.random.nextInt(3);
+ if (org.purpurmc.purpur.PurpurConfig.allowShearsLooting) i += looting; // Purpur
for (int j = 0; j < i; ++j) {
this.forceDrops = true; // CraftBukkit
- ItemEntity entityitem = this.spawnAtLocation((ItemLike) Sheep.ITEM_BY_DYE.get(this.getColor()), 1);
+ ItemEntity entityitem = this.spawnAtLocation((ItemLike) Sheep.ITEM_BY_DYE.get(this.level().purpurConfig.sheepShearJebRandomColor && hasCustomName() && getCustomName().getString().equals("jeb_") ? DyeColor.random(this.level().random) : this.getColor()), 1); // Purpur
this.forceDrops = false; // CraftBukkit
if (entityitem != null) {
diff --git a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java
index 8adcfc8f6772a32b5915e4a07100e8eb735f907a..8b364fe9f3a3d47ae6daa331b8f16941ca17432a 100644
--- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java
+++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java
@@ -49,17 +49,56 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
private static final EntityDataAccessor<Byte> DATA_PUMPKIN_ID = SynchedEntityData.defineId(SnowGolem.class, EntityDataSerializers.BYTE);
private static final byte PUMPKIN_FLAG = 16;
private static final float EYE_HEIGHT = 1.7F;
+ @Nullable private java.util.UUID summoner; // Purpur
public SnowGolem(EntityType<? extends SnowGolem> type, Level world) {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.snowGolemRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.snowGolemRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.snowGolemControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.snowGolemMaxHealth);
+ }
+
+ @Nullable
+ public java.util.UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(@Nullable java.util.UUID summoner) {
+ this.summoner = summoner;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.snowGolemAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
- this.goalSelector.addGoal(1, new RangedAttackGoal(this, 1.25D, 20, 10.0F));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
+ this.goalSelector.addGoal(1, new RangedAttackGoal(this, level().purpurConfig.snowGolemAttackDistance, level().purpurConfig.snowGolemSnowBallMin, level().purpurConfig.snowGolemSnowBallMax, level().purpurConfig.snowGolemSnowBallModifier)); // Purpur
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D, 1.0000001E-5F));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Mob.class, 10, true, false, (entityliving) -> {
return entityliving instanceof Enemy;
}));
@@ -79,6 +118,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
public void addAdditionalSaveData(CompoundTag nbt) {
super.addAdditionalSaveData(nbt);
nbt.putBoolean("Pumpkin", this.hasPumpkin());
+ if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur
}
@Override
@@ -87,12 +127,13 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
if (nbt.contains("Pumpkin")) {
this.setPumpkin(nbt.getBoolean("Pumpkin"));
}
+ if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur
}
@Override
public boolean isSensitiveToWater() {
- return true;
+ return this.level().purpurConfig.snowGolemTakeDamageFromWater; // Purpur
}
@Override
@@ -103,10 +144,11 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
this.hurt(this.damageSources().melting, 1.0F); // CraftBukkit - DamageSource.BURN -> CraftEventFactory.MELTING
}
- if (!this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (!this.level().purpurConfig.snowGolemBypassMobGriefing && !this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
return;
}
+ if (getRider() != null && this.isControllable() && !level().purpurConfig.snowGolemLeaveTrailWhenRidden) return; // Purpur - don't leave snow trail when being ridden
BlockState iblockdata = Blocks.SNOW.defaultBlockState();
for (int i = 0; i < 4; ++i) {
@@ -154,10 +196,10 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
if (itemstack.is(Items.SHEARS) && this.readyForShearing()) {
// CraftBukkit start
if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur
}
// CraftBukkit end
- this.shear(SoundSource.PLAYERS);
+ this.shear(SoundSource.PLAYERS, net.minecraft.world.item.enchantment.EnchantmentHelper.getMobLooting(player)); // Purpur
this.gameEvent(GameEvent.SHEAR, player);
if (!this.level().isClientSide) {
itemstack.hurtAndBreak(1, player, (entityhuman1) -> {
@@ -166,17 +208,27 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
}
return InteractionResult.sidedSuccess(this.level().isClientSide);
+ // Purpur start
+ } else if (level().purpurConfig.snowGolemPutPumpkinBack && !hasPumpkin() && itemstack.getItem() == Blocks.CARVED_PUMPKIN.asItem()) {
+ setPumpkin(true);
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
+ return InteractionResult.SUCCESS;
+ // Purpur end
} else {
- return InteractionResult.PASS;
+ return tryRide(player, hand); // Purpur
}
}
@Override
- public void shear(SoundSource shearedSoundCategory) {
+ public void shear(SoundSource shearedSoundCategory, int looting) { // Purpur
this.level().playSound((Player) null, (Entity) this, SoundEvents.SNOW_GOLEM_SHEAR, shearedSoundCategory, 1.0F, 1.0F);
if (!this.level().isClientSide()) {
this.setPumpkin(false);
this.forceDrops = true; // CraftBukkit
+ if (level().purpurConfig.snowGolemDropsPumpkin) // Purpur
+ for (int i = 0; i < 1 + (org.purpurmc.purpur.PurpurConfig.allowShearsLooting ? looting : 0); i++) // Purpur
this.spawnAtLocation(new ItemStack(Items.CARVED_PUMPKIN), 1.7F);
this.forceDrops = false; // CraftBukkit
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/Squid.java b/src/main/java/net/minecraft/world/entity/animal/Squid.java
index f60c4cd0543fd5d50fa7e2c1a9e8381227adb540..dd7f2beabf0edad4143ac2365ac04a22edf1f75e 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Squid.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java
@@ -44,13 +44,66 @@ public class Squid extends WaterAnimal {
public Squid(EntityType<? extends Squid> type, Level world) {
super(type, world);
- //this.random.setSeed((long)this.getId()); // Paper - we set the random to shared, do not clobber the seed
+ if (!world.purpurConfig.entitySharedRandom) this.random.setSeed((long) this.getId()); // Paper - we set the random to shared, do not clobber the seed // Purpur
this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F;
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.squidRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.squidControllable;
+ }
+
+ protected void rotateVectorAroundY(org.bukkit.util.Vector vector, double degrees) {
+ double rad = Math.toRadians(degrees);
+ double cos = Math.cos(rad);
+ double sine = Math.sin(rad);
+ double x = vector.getX();
+ double z = vector.getZ();
+ vector.setX(cos * x - sine * z);
+ vector.setZ(sine * x + cos * z);
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.squidMaxHealth);
+ }
+
+ @Override
+ public net.minecraft.world.phys.AABB getAxisForFluidCheck() {
+ // Stops squids from floating just over the water
+ return super.getAxisForFluidCheck().offsetY(level().purpurConfig.squidOffsetWaterCheck);
+ }
+
+ public boolean canFly() {
+ return this.level().purpurConfig.squidsCanFly;
+ }
+
+ @Override
+ public boolean isInWater() {
+ return this.wasTouchingWater || canFly();
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.squidTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.squidAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new Squid.SquidRandomMovementGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new Squid.SquidFleeGoal());
}
@@ -119,6 +172,7 @@ public class Squid extends WaterAnimal {
}
if (this.isInWaterOrBubble()) {
+ if (canFly()) setNoGravity(!wasTouchingWater); // Purpur
if (this.tentacleMovement < (float)Math.PI) {
float f = this.tentacleMovement / (float)Math.PI;
this.tentacleAngle = Mth.sin(f * f * (float)Math.PI) * (float)Math.PI * 0.25F;
@@ -298,10 +352,41 @@ public class Squid extends WaterAnimal {
@Override
public void tick() {
+ // Purpur start
+ Player rider = squid.getRider();
+ if (rider != null && squid.isControllable()) {
+ if (rider.jumping) {
+ squid.onSpacebar();
+ }
+ float forward = rider.getForwardMot();
+ float strafe = rider.getStrafeMot();
+ float speed = (float) squid.getAttributeValue(Attributes.MOVEMENT_SPEED) * 10F;
+ if (forward < 0.0F) {
+ speed *= -0.5;
+ }
+ org.bukkit.util.Vector dir = rider.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(speed / 20.0F);
+ if (strafe != 0.0F) {
+ if (forward == 0.0F) {
+ dir.setY(0);
+ rotateVectorAroundY(dir, strafe > 0.0F ? -90 : 90);
+ } else if (forward < 0.0F) {
+ rotateVectorAroundY(dir, strafe > 0.0F ? 45 : -45);
+ } else {
+ rotateVectorAroundY(dir, strafe > 0.0F ? -45 : 45);
+ }
+ }
+ if (forward != 0.0F || strafe != 0.0F) {
+ squid.setMovementVector((float) dir.getX(), (float) dir.getY(), (float) dir.getZ());
+ } else {
+ squid.setMovementVector(0.0F, 0.0F, 0.0F);
+ }
+ return;
+ }
+ // Purpur end
int i = this.squid.getNoActionTime();
if (i > 100) {
this.squid.setMovementVector(0.0F, 0.0F, 0.0F);
- } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.wasTouchingWater || !this.squid.hasMovementVector()) {
+ } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.isInWater() || !this.squid.hasMovementVector()) { // Purpur
float f = this.squid.getRandom().nextFloat() * ((float)Math.PI * 2F);
float g = Mth.cos(f) * 0.2F;
float h = -0.1F + this.squid.getRandom().nextFloat() * 0.2F;
diff --git a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java
index b05b560b7570e97bc234b75f26233909fcf575b3..87b6f6b10ba6e3d9c6a42298a2019a526a183d90 100644
--- a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java
+++ b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java
@@ -42,6 +42,33 @@ public class TropicalFish extends AbstractSchoolingFish implements VariantHolder
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.tropicalFishRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.tropicalFishControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.tropicalFishMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.tropicalFishTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.tropicalFishAlwaysDropExp;
+ }
+
public static String getPredefinedName(int variant) {
return "entity.minecraft.tropical_fish.predefined." + variant;
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
index d3c15d029d5f003cba3c89f7ea1f3ed4f943f2bd..1502f13acd0a104efe470e605826213ea92af191 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
@@ -83,6 +83,43 @@ public class Turtle extends Animal {
this.setMaxUpStep(1.0F);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.turtleRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.turtleRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.turtleControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.turtleMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.turtleBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.turtleTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.turtleAlwaysDropExp;
+ }
+
public void setHomePos(BlockPos pos) {
this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos...
}
@@ -185,6 +222,7 @@ public class Turtle extends Animal {
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(0, new Turtle.TurtlePanicGoal(this, 1.2D));
this.goalSelector.addGoal(1, new Turtle.TurtleBreedGoal(this, 1.0D));
this.goalSelector.addGoal(1, new Turtle.TurtleLayEggGoal(this, 1.0D));
@@ -342,13 +380,15 @@ public class Turtle extends Animal {
org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = null; // CraftBukkit
}
- private static class TurtleMoveControl extends MoveControl {
+ private static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur
private final Turtle turtle;
+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur
TurtleMoveControl(Turtle turtle) {
super(turtle);
this.turtle = turtle;
+ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(turtle, 0.25D); // Purpur
}
private void updateSpeed() {
@@ -367,8 +407,18 @@ public class Turtle extends Animal {
}
+ // Purpur start
+ public void purpurTick(Player rider) {
+ if (turtle.isInWater()) {
+ waterController.purpurTick(rider);
+ } else {
+ super.purpurTick(rider);
+ }
+ }
+ // Purpur end
+
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur
this.updateSpeed();
if (this.operation == MoveControl.Operation.MOVE_TO && !this.turtle.getNavigation().isDone()) {
double d0 = this.wantedX - this.turtle.getX();
@@ -384,7 +434,7 @@ public class Turtle extends Animal {
this.turtle.setYRot(this.rotlerp(this.turtle.getYRot(), f, 90.0F));
this.turtle.yBodyRot = this.turtle.getYRot();
- float f1 = (float) (this.speedModifier * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED));
+ float f1 = (float) (this.getSpeedModifier() * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED));
this.turtle.setSpeed(Mth.lerp(0.125F, this.turtle.getSpeed(), f1));
this.turtle.setDeltaMovement(this.turtle.getDeltaMovement().add(0.0D, (double) this.turtle.getSpeed() * d1 * 0.1D, 0.0D));
diff --git a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java
index cd2ce5bcb8c30e4657cd0e340d80544c7e805905..c8c6fed3f93903bb5c6145930538d415f6f59738 100644
--- a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java
+++ b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java
@@ -82,6 +82,6 @@ public abstract class WaterAnimal extends PathfinderMob {
i = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(i);
j = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(j);
// Paper end
- return pos.getY() >= j && pos.getY() <= i && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER);
+ return ((reason == MobSpawnType.SPAWNER && world.getMinecraftWorld().purpurConfig.spawnerFixMC238526) || (pos.getY() >= j && pos.getY() <= i)) && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER); // Purpur
}
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/Wolf.java b/src/main/java/net/minecraft/world/entity/animal/Wolf.java
index 57ceec70bb150afaa66962090b142048d5b50c2f..64bceae4d06b35fcbecb0daca2496ba30e39d995 100644
--- a/src/main/java/net/minecraft/world/entity/animal/Wolf.java
+++ b/src/main/java/net/minecraft/world/entity/animal/Wolf.java
@@ -10,6 +10,7 @@ import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.tags.BlockTags;
@@ -17,9 +18,12 @@ import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.util.TimeUtil;
import net.minecraft.util.valueproviders.UniformInt;
+import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.effect.MobEffectInstance;
+import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.AgeableMob;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
@@ -29,6 +33,7 @@ import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.NeutralMob;
import net.minecraft.world.entity.Pose;
+import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.TamableAnimal;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
@@ -37,6 +42,7 @@ import net.minecraft.world.entity.ai.goal.BegGoal;
import net.minecraft.world.entity.ai.goal.BreedGoal;
import net.minecraft.world.entity.ai.goal.FloatGoal;
import net.minecraft.world.entity.ai.goal.FollowOwnerGoal;
+import net.minecraft.world.entity.ai.goal.Goal;
import net.minecraft.world.entity.ai.goal.LeapAtTargetGoal;
import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
@@ -64,6 +70,7 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
+import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.level.pathfinder.BlockPathTypes;
@@ -84,6 +91,37 @@ public class Wolf extends TamableAnimal implements NeutralMob {
return entitytypes == EntityType.SHEEP || entitytypes == EntityType.RABBIT || entitytypes == EntityType.FOX;
};
+ // Purpur start - rabid wolf spawn chance
+ private boolean isRabid = false;
+ private static final Predicate<LivingEntity> RABID_PREDICATE = entity -> entity instanceof ServerPlayer || entity instanceof Mob;
+ private final Goal PATHFINDER_VANILLA = new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR);
+ private final Goal PATHFINDER_RABID = new NonTameRandomTargetGoal<>(this, LivingEntity.class, false, RABID_PREDICATE);
+ private static final class AvoidRabidWolfGoal extends AvoidEntityGoal<Wolf> {
+ private final Wolf wolf;
+
+ public AvoidRabidWolfGoal(Wolf wolf, float distance, double minSpeed, double maxSpeed) {
+ super(wolf, Wolf.class, distance, minSpeed, maxSpeed);
+ this.wolf = wolf;
+ }
+
+ @Override
+ public boolean canUse() {
+ return super.canUse() && !this.wolf.isRabid() && this.toAvoid != null && this.toAvoid.isRabid(); // wolves which are not rabid run away from rabid wolves
+ }
+
+ @Override
+ public void start() {
+ this.wolf.setTarget(null);
+ super.start();
+ }
+
+ @Override
+ public void tick() {
+ this.wolf.setTarget(null);
+ super.tick();
+ }
+ }
+ // Purpur end
private static final float START_HEALTH = 8.0F;
private static final float TAME_HEALTH = 20.0F;
private float interestedAngle;
@@ -103,12 +141,93 @@ public class Wolf extends TamableAnimal implements NeutralMob {
this.setPathfindingMalus(BlockPathTypes.DANGER_POWDER_SNOW, -1.0F);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.wolfRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wolfRidableInWater;
+ }
+
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ setInSittingPose(false);
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.wolfControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.wolfMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.wolfBreedingTicks;
+ }
+
+ public boolean isRabid() {
+ return this.isRabid;
+ }
+
+ public void setRabid(boolean isRabid) {
+ this.isRabid = isRabid;
+ updatePathfinders(true);
+ }
+
+ public void updatePathfinders(boolean modifyEffects) {
+ this.targetSelector.removeGoal(PATHFINDER_VANILLA);
+ this.targetSelector.removeGoal(PATHFINDER_RABID);
+ if (this.isRabid) {
+ setTame(false);
+ setOwnerUUID(null);
+ this.targetSelector.addGoal(5, PATHFINDER_RABID);
+ if (modifyEffects) this.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 1200));
+ } else {
+ this.targetSelector.addGoal(5, PATHFINDER_VANILLA);
+ this.stopBeingAngry();
+ if (modifyEffects) this.removeEffect(MobEffects.CONFUSION);
+ }
+ }
+
+ @Override
+ public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType type, @Nullable SpawnGroupData data, @Nullable CompoundTag nbt) {
+ this.isRabid = world.getLevel().purpurConfig.wolfNaturalRabid > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.wolfNaturalRabid;
+ this.updatePathfinders(false);
+ return super.finalizeSpawn(world, difficulty, type, data, nbt);
+ }
+
+ @Override
+ public void tame(Player player) {
+ setCollarColor(level().purpurConfig.wolfDefaultCollarColor);
+ super.tame(player);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.wolfTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.wolfAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new Wolf.WolfPanicGoal(1.5D));
this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
this.goalSelector.addGoal(3, new Wolf.WolfAvoidEntityGoal<>(this, Llama.class, 24.0F, 1.5D, 1.5D));
+ this.goalSelector.addGoal(3, new AvoidRabidWolfGoal(this, 24.0F, 1.5D, 1.5D)); // Purpur
this.goalSelector.addGoal(4, new LeapAtTargetGoal(this, 0.4F));
this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0D, true));
this.goalSelector.addGoal(6, new FollowOwnerGoal(this, 1.0D, 10.0F, 2.0F, false));
@@ -117,11 +236,12 @@ public class Wolf extends TamableAnimal implements NeutralMob {
this.goalSelector.addGoal(9, new BegGoal(this, 8.0F));
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(10, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new OwnerHurtByTargetGoal(this));
this.targetSelector.addGoal(2, new OwnerHurtTargetGoal(this));
this.targetSelector.addGoal(3, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers());
this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
- this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, Wolf.PREY_SELECTOR));
+ // this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, Wolf.PREY_SELECTOR)); // Purpur - moved to updatePathfinders()
this.targetSelector.addGoal(6, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR));
this.targetSelector.addGoal(7, new NearestAttackableTargetGoal<>(this, AbstractSkeleton.class, false));
this.targetSelector.addGoal(8, new ResetUniversalAngerTargetGoal<>(this, true));
@@ -148,6 +268,7 @@ public class Wolf extends TamableAnimal implements NeutralMob {
public void addAdditionalSaveData(CompoundTag nbt) {
super.addAdditionalSaveData(nbt);
nbt.putByte("CollarColor", (byte) this.getCollarColor().getId());
+ nbt.putBoolean("Purpur.IsRabid", this.isRabid); // Purpur
this.addPersistentAngerSaveData(nbt);
}
@@ -157,6 +278,10 @@ public class Wolf extends TamableAnimal implements NeutralMob {
if (nbt.contains("CollarColor", 99)) {
this.setCollarColor(DyeColor.byId(nbt.getInt("CollarColor")));
}
+ // Purpur start
+ this.isRabid = nbt.getBoolean("Purpur.IsRabid");
+ this.updatePathfinders(false);
+ // Purpur end
this.readPersistentAngerSaveData(this.level(), nbt);
}
@@ -201,6 +326,11 @@ public class Wolf extends TamableAnimal implements NeutralMob {
public void tick() {
super.tick();
if (this.isAlive()) {
+ // Purpur start
+ if (this.age % 300 == 0 && this.isRabid()) {
+ this.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 400));
+ }
+ // Purpur end
this.interestedAngleO = this.interestedAngle;
if (this.isInterested()) {
this.interestedAngle += (1.0F - this.interestedAngle) * 0.4F;
@@ -405,7 +535,7 @@ public class Wolf extends TamableAnimal implements NeutralMob {
}
// CraftBukkit - added event call and isCancelled check.
- if (this.random.nextInt(3) == 0 && !CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) {
+ if ((this.level().purpurConfig.alwaysTameInCreative && player.getAbilities().instabuild) || (this.random.nextInt(3) == 0 && !CraftEventFactory.callEntityTameEvent(this, player).isCancelled())) { // Purpur
this.tame(player);
this.navigation.stop();
this.setTarget((LivingEntity) null);
@@ -416,6 +546,19 @@ public class Wolf extends TamableAnimal implements NeutralMob {
}
return InteractionResult.SUCCESS;
+ // Purpur start
+ } else if (this.level().purpurConfig.wolfMilkCuresRabies && itemstack.getItem() == Items.MILK_BUCKET && this.isRabid()) {
+ if (!player.isCreative()) {
+ player.setItemInHand(hand, new ItemStack(Items.BUCKET));
+ }
+ this.setRabid(false);
+ for (int i = 0; i < 10; ++i) {
+ ((ServerLevel) level()).sendParticles(((ServerLevel) level()).players(), null, ParticleTypes.HAPPY_VILLAGER,
+ getX() + random.nextFloat(), getY() + (random.nextFloat() * 1.5), getZ() + random.nextFloat(), 1,
+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true);
+ }
+ return InteractionResult.SUCCESS;
+ // Purpur end
} else {
return super.mobInteract(player, hand);
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
index 1a0eee3b766a5ce5623c32ed9c023a0f80db1d1a..7166f4a39fd615e10d7b1f53c57363832a41f365 100644
--- a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
+++ b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
@@ -101,10 +101,23 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS
private float spinningAnimationTicks;
private float spinningAnimationTicks0;
public boolean forceDancing = false; // CraftBukkit
+ private org.purpurmc.purpur.controller.FlyingMoveControllerWASD purpurController; // Purpur
public Allay(EntityType<? extends Allay> type, Level world) {
super(type, world);
- this.moveControl = new FlyingMoveControl(this, 20, true);
+ // Purpur start
+ this.purpurController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.1F, 0.5F);
+ this.moveControl = new FlyingMoveControl(this, 20, true) {
+ @Override
+ public void tick() {
+ if (mob.getRider() != null && mob.isControllable()) {
+ purpurController.purpurTick(mob.getRider());
+ } else {
+ super.tick();
+ }
+ }
+ };
+ // Purpur end
this.setCanPickUpLoot(this.canPickUpLoot());
this.vibrationUser = new Allay.VibrationUser();
this.vibrationData = new VibrationSystem.Data();
@@ -118,6 +131,28 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS
}
// CraftBukkit end
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.allayRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.allayRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.allayControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
+ }
+ // Purpur end
+
@Override
protected Brain.Provider<Allay> brainProvider() {
return Brain.provider(Allay.MEMORY_TYPES, Allay.SENSOR_TYPES);
@@ -225,13 +260,13 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS
private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep() {
- this.level().getProfiler().push("allayBrain");
- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ //this.level().getProfiler().push("allayBrain"); // Purpur
+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
this.getBrain().tick((ServerLevel) this.level(), this);
- this.level().getProfiler().pop();
- this.level().getProfiler().push("allayActivityUpdate");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("allayActivityUpdate"); // Purpur
AllayAi.updateActivity(this);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
super.customServerAiStep();
}
@@ -372,9 +407,31 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS
@Override
public boolean wantsToPickUp(ItemStack stack) {
- ItemStack itemstack1 = this.getItemInHand(InteractionHand.MAIN_HAND);
-
- return !itemstack1.isEmpty() && this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.inventory.canAddItem(stack) && this.allayConsidersItemEqual(itemstack1, stack);
+ // Purpur start
+ if (!this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ return false;
+ }
+ ItemStack itemStack = this.getItemInHand(InteractionHand.MAIN_HAND);
+ if (itemStack.isEmpty()) {
+ return false;
+ }
+ if (!allayConsidersItemEqual(itemStack, stack)) {
+ return false;
+ }
+ if (!this.inventory.canAddItem(stack)) {
+ return false;
+ }
+ for (String tag : this.level().purpurConfig.allayRespectNBT) {
+ if (stack.hasTag() && itemStack.hasTag()) {
+ Tag tag1 = stack.getTag().get(tag);
+ Tag tag2 = itemStack.getTag().get(tag);
+ if (!Objects.equals(tag1, tag2)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ // Purpur end
}
private boolean allayConsidersItemEqual(ItemStack stack, ItemStack stack2) {
diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
index 42e22a4b9cb6841de04862cc81454da3232aa65a..07abf72b7aed98652ac6639b26ade358fcd9135a 100644
--- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
+++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
@@ -98,6 +98,43 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder<Axolo
this.setMaxUpStep(1.0F);
}
+ // 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
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.axolotlMaxHealth);
+ }
+
+ @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;
+ }
+
@Override
public Map<String, Vector3f> getModelRotationValues() {
return this.modelRotationValues;
@@ -288,13 +325,13 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder<Axolo
private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep() {
- this.level().getProfiler().push("axolotlBrain");
- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ //this.level().getProfiler().push("axolotlBrain"); // Purpur
+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
this.getBrain().tick((ServerLevel) this.level(), this);
- this.level().getProfiler().pop();
- this.level().getProfiler().push("axolotlActivityUpdate");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("axolotlActivityUpdate"); // Purpur
AxolotlAi.updateActivity(this);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
if (!this.isNoAi()) {
Optional<Integer> optional = this.getBrain().getMemory(MemoryModuleType.PLAY_DEAD_TICKS);
@@ -521,14 +558,22 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder<Axolo
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();
}
@@ -543,9 +588,9 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder<Axolo
}
@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 4efa7e331cc974008c653a04687a336e97626445..9f19ebfa6392a080672c472e08f755379e9776b4 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
@@ -84,6 +84,17 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Rider
groundPathNavigation.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);
@@ -150,13 +161,13 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Rider
@Override
protected void customServerAiStep() {
- this.level().getProfiler().push("camelBrain");
+ //this.level().getProfiler().push("camelBrain"); // Purpur
Brain<Camel> brain = (Brain<Camel>) this.getBrain(); // Paper - decompile fix
brain.tick((ServerLevel)this.level(), this);
- this.level().getProfiler().pop();
- this.level().getProfiler().push("camelActivityUpdate");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("camelActivityUpdate"); // Purpur
CamelAi.updateActivity(this);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
super.customServerAiStep();
}
@@ -319,6 +330,23 @@ public class Camel extends AbstractHorse implements PlayerRideableJumping, Rider
return this.dashCooldown;
}
+ // Purpur start
+ @Override
+ public float generateMaxHealth(net.minecraft.util.RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.camelMaxHealthMin, this.level().purpurConfig.camelMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(net.minecraft.util.RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.camelJumpStrengthMin, this.level().purpurConfig.camelJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(net.minecraft.util.RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.camelMovementSpeedMin, this.level().purpurConfig.camelMovementSpeedMax);
+ }
+ // Purpur end
+
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.CAMEL_AMBIENT;
diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
index 80ddb3059d6484c2b90c55ef601043798f1a4b50..dc1e8bcd8049d79c0e383ccd6a5697f79a3a2ebd 100644
--- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
@@ -77,16 +77,69 @@ public class Frog extends Animal implements VariantHolder<FrogVariant> {
public final AnimationState croakAnimationState = new AnimationState();
public final AnimationState tongueAnimationState = new AnimationState();
public final AnimationState swimIdleAnimationState = new AnimationState();
+ private org.purpurmc.purpur.controller.MoveControllerWASD purpurLandController; // Purpur
+ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurWaterController; // Purpur
public Frog(EntityType<? extends Animal> type, Level world) {
super(type, world);
this.lookControl = new Frog.FrogLookControl(this);
this.setPathfindingMalus(BlockPathTypes.WATER, 4.0F);
this.setPathfindingMalus(BlockPathTypes.TRAPDOOR, -1.0F);
- this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
+ // Purpur start
+ this.purpurLandController = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.2F);
+ this.purpurWaterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F);
+ this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) {
+ @Override
+ public void tick() {
+ net.minecraft.world.entity.player.Player rider = mob.getRider();
+ if (rider != null && mob.isControllable()) {
+ if (mob.isInWater()) {
+ purpurWaterController.purpurTick(rider);
+ mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, -0.005D, 0.0D));
+ } else {
+ purpurLandController.purpurTick(rider);
+ }
+ } else {
+ super.tick();
+ }
+ }
+ };
+ // Purpur end
this.setMaxUpStep(1.0F);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.frogRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.frogRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.frogControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
+ }
+
+ @Override
+ public float getJumpPower() {
+ return (getRider() != null && isControllable()) ? level().purpurConfig.frogRidableJumpHeight * this.getBlockJumpFactor() : super.getJumpPower();
+ }
+ // Purpur end
+
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.frogBreedingTicks;
+ }
+
@Override
protected Brain.Provider<Frog> brainProvider() {
return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
@@ -165,13 +218,13 @@ public class Frog extends Animal implements VariantHolder<FrogVariant> {
private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep() {
- this.level().getProfiler().push("frogBrain");
- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ //this.level().getProfiler().push("frogBrain"); // Purpur
+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
this.getBrain().tick((ServerLevel)this.level(), this);
- this.level().getProfiler().pop();
- this.level().getProfiler().push("frogActivityUpdate");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("frogActivityUpdate"); // Purpur
FrogAi.updateActivity(this);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
super.customServerAiStep();
}
@@ -347,7 +400,7 @@ public class Frog extends Animal implements VariantHolder<FrogVariant> {
return world.getBlockState(pos.below()).is(BlockTags.FROGS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos);
}
- class FrogLookControl extends LookControl {
+ class FrogLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur
FrogLookControl(Mob entity) {
super(entity);
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
index 6ed4ac06c76b8d0d6e8db778cade15dbd1e3e5f5..6b012bea26e8ef0c04571f43da67f6e108188830 100644
--- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
@@ -45,13 +45,50 @@ public class Tadpole extends AbstractFish {
protected static final ImmutableList<SensorType<? extends Sensor<? super Tadpole>>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS);
protected static final ImmutableList<MemoryModuleType<?>> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING);
public boolean ageLocked; // Paper
+ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurController; // Purpur
public Tadpole(EntityType<? extends AbstractFish> type, Level world) {
super(type, world);
- this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
+ // Purpur start
+ this.purpurController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F);
+ this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) {
+ @Override
+ public void tick() {
+ Player rider = mob.getRider();
+ if (rider != null && mob.isControllable()) {
+ purpurController.purpurTick(rider);
+ mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, 0.002D, 0.0D));
+ } else {
+ super.tick();
+ }
+ }
+ };
+ // Purpur end
this.lookControl = new SmoothSwimmingLookControl(this, 10);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.tadpoleRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.tadpoleRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.tadpoleControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
+ }
+ // Purpur end
+
@Override
protected PathNavigation createNavigation(Level world) {
return new WaterBoundPathNavigation(this, world);
@@ -80,13 +117,13 @@ public class Tadpole extends AbstractFish {
private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep() {
- this.level().getProfiler().push("tadpoleBrain");
- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ //this.level().getProfiler().push("tadpoleBrain"); // Purpur
+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
this.getBrain().tick((ServerLevel) this.level(), this);
- this.level().getProfiler().pop();
- this.level().getProfiler().push("tadpoleActivityUpdate");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("tadpoleActivityUpdate"); // Purpur
TadpoleAi.updateActivity(this);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
super.customServerAiStep();
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
index 7f21d1d400c8a5615ed1a787dcb0680338f8c28d..b04af8b72357b455597d80592c8b8b0c9da744cd 100644
--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
@@ -90,6 +90,38 @@ public class Goat extends Animal {
return InstrumentItem.create(Items.GOAT_HORN, (Holder) holderset.getRandomElement(randomsource).get());
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.goatRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.goatRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.goatControllable;
+ }
+ // Purpur end
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.goatBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.goatTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.goatAlwaysDropExp;
+ }
+
@Override
protected Brain.Provider<Goat> brainProvider() {
return Brain.provider(Goat.MEMORY_TYPES, Goat.SENSOR_TYPES);
@@ -192,13 +224,14 @@ public class Goat extends Animal {
private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep() {
- this.level().getProfiler().push("goatBrain");
- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ //this.level().getProfiler().push("goatBrain"); // Purpur
+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider // Purpur - TODO: Uncomment when pufferfish patch
+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider // Purpur - TODO: Uncomment when pufferfish patch
this.getBrain().tick((ServerLevel) this.level(), this);
- this.level().getProfiler().pop();
- this.level().getProfiler().push("goatActivityUpdate");
+ //this.level().getProfiler().pop(); // Purpur
+ //this.level().getProfiler().push("goatActivityUpdate"); // Purpur
GoatAi.updateActivity(this);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
super.customServerAiStep();
}
@@ -390,6 +423,7 @@ public class Goat extends Animal {
// Paper start - Goat ram API
public void ram(net.minecraft.world.entity.LivingEntity entity) {
+ if(!new org.purpurmc.purpur.event.entity.GoatRamEntityEvent((org.bukkit.entity.Goat) getBukkitEntity(), (org.bukkit.entity.LivingEntity) entity.getBukkitLivingEntity()).callEvent()) return; // Purpur
Brain<Goat> brain = this.getBrain();
brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position());
brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS);
diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java
index 49d7109b6ca63c8073db777549a65b2fcb70967e..fdf41f4990923aa4309febb9c0ca8009de8e16f1 100644
--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java
+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java
@@ -147,12 +147,60 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
protected AbstractHorse(EntityType<? extends AbstractHorse> type, Level world) {
super(type, world);
+ this.moveControl = new net.minecraft.world.entity.ai.control.MoveControl(this); // Purpur - use vanilla controller
+ this.lookControl = new net.minecraft.world.entity.ai.control.LookControl(this); // Purpur - use vanilla controller
this.setMaxUpStep(1.0F);
this.createInventory();
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return false; // vanilla handles
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.generateMaxHealth(random));
+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.generateSpeed(random));
+ this.getAttribute(Attributes.JUMP_STRENGTH).setBaseValue(this.generateJumpStrength(random));
+ }
+
+ protected double generateMaxHealth(double min, double max) {
+ if (min == max) return min;
+ int diff = Mth.floor(max - min);
+ double base = max - diff;
+ int first = Mth.floor((double) diff / 2);
+ int rest = diff - first;
+ return base + random.nextInt(first + 1) + random.nextInt(rest + 1);
+ }
+
+ protected double generateJumpStrength(double min, double max) {
+ if (min == max) return min;
+ return min + (max - min) * this.random.nextDouble();
+ }
+
+ protected double generateSpeed(double min, double max) {
+ if (min == max) return min;
+ return min + (max - min) * this.random.nextDouble();
+ }
+
+ protected float generateMaxHealth(RandomSource random) {
+ return 15.0F + (float) random.nextInt(8) + (float) random.nextInt(9);
+ }
+
+ protected double generateJumpStrength(RandomSource random) {
+ return 0.4000000059604645D + random.nextDouble() * 0.2D + random.nextDouble() * 0.2D + random.nextDouble() * 0.2D;
+ }
+
+ protected double generateSpeed(RandomSource random) {
+ return (0.44999998807907104D + random.nextDouble() * 0.3D + random.nextDouble() * 0.3D + random.nextDouble() * 0.3D) * 0.25D;
+ }
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur
this.goalSelector.addGoal(1, new PanicGoal(this, 1.2D));
this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2D));
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D, AbstractHorse.class));
@@ -163,6 +211,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
if (this.canPerformRearing()) {
this.goalSelector.addGoal(9, new RandomStandGoal(this));
}
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur
this.addBehaviourGoals();
}
@@ -335,7 +384,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
@Override
protected int calculateFallDamage(float fallDistance, float damageMultiplier) {
- return Mth.ceil((fallDistance * 0.5F - 3.0F) * damageMultiplier);
+ return Mth.ceil((fallDistance * 0.5F - this.safeFallDistance) * damageMultiplier);
}
protected int getInventorySize() {
@@ -1247,7 +1296,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
entityData = new AgeableMob.AgeableMobGroupData(0.2F);
}
- this.randomizeAttributes(world.getRandom());
+ // this.randomizeAttributes(world.getRandom()); // Purpur - replaced by initAttributes()
return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData, entityNbt);
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java
index e0dfee0e0ce091d5ae0ec740e939c2c50915c104..72ad12175325091397459e06743875cce6df8d94 100644
--- a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java
+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java
@@ -15,6 +15,43 @@ public class Donkey extends AbstractChestedHorse {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.donkeyRidableInWater;
+ }
+ // Purpur end
+
+ @Override
+ public float generateMaxHealth(net.minecraft.util.RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.donkeyMaxHealthMin, this.level().purpurConfig.donkeyMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(net.minecraft.util.RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.donkeyJumpStrengthMin, this.level().purpurConfig.donkeyJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(net.minecraft.util.RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.donkeyMovementSpeedMin, this.level().purpurConfig.donkeyMovementSpeedMax);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.donkeyBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.donkeyTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.donkeyAlwaysDropExp;
+ }
+
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.DONKEY_AMBIENT;
diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java
index 5f5dc651d570989ec1294c31a14dcfede466b80a..3b1faa63e46a48e83ea672cf6da444a1d7e13270 100644
--- a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java
+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java
@@ -40,6 +40,43 @@ public class Horse extends AbstractHorse implements VariantHolder<Variant> {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.horseRidableInWater;
+ }
+ // Purpur end
+
+ @Override
+ public float generateMaxHealth(RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.horseMaxHealthMin, this.level().purpurConfig.horseMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.horseJumpStrengthMin, this.level().purpurConfig.horseJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.horseMovementSpeedMin, this.level().purpurConfig.horseMovementSpeedMax);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.horseBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.horseTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.horseAlwaysDropExp;
+ }
+
@Override
protected void randomizeAttributes(RandomSource random) {
this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double)generateMaxHealth(random::nextInt));
diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
index d28089c37707f323f73e60cb0ebed4e3cdf8c0fd..a9f36af3e07f74b3d11697d5fad9f0a469844779 100644
--- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
@@ -73,9 +73,84 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
private Llama caravanHead;
@Nullable
public Llama caravanTail; // Paper
+ public boolean shouldJoinCaravan = true; // Purpur
public Llama(EntityType<? extends Llama> type, Level world) {
super(type, world);
+ // Purpur start
+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this) {
+ @Override
+ public void tick() {
+ if (entity.getRider() != null && entity.isControllable() && isSaddled()) {
+ purpurTick(entity.getRider());
+ } else {
+ vanillaTick();
+ }
+ }
+ };
+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) {
+ @Override
+ public void tick() {
+ if (entity.getRider() != null && entity.isControllable() && isSaddled()) {
+ purpurTick(entity.getRider());
+ } else {
+ vanillaTick();
+ }
+ }
+ };
+ // Purpur end
+ }
+
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.llamaRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.llamaRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.llamaControllable;
+ }
+
+ @Override
+ public boolean isSaddled() {
+ return super.isSaddled() || (isTamed() && getSwag() != null);
+ }
+ // Purpur end
+
+ @Override
+ public float generateMaxHealth(RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.llamaMaxHealthMin, this.level().purpurConfig.llamaMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.llamaJumpStrengthMin, this.level().purpurConfig.llamaJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.llamaMovementSpeedMin, this.level().purpurConfig.llamaMovementSpeedMax);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.llamaBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.llamaTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.llamaAlwaysDropExp;
}
public boolean isTraderLlama() {
@@ -109,7 +184,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
if (!this.inventory.getItem(1).isEmpty()) {
nbt.put("DecorItem", this.inventory.getItem(1).save(new CompoundTag()));
}
-
+ nbt.putBoolean("Purpur.ShouldJoinCaravan", shouldJoinCaravan); // Purpur
}
@Override
@@ -120,13 +195,14 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
if (nbt.contains("DecorItem", 10)) {
this.inventory.setItem(1, ItemStack.of(nbt.getCompound("DecorItem")));
}
-
+ if (nbt.contains("Purpur.ShouldJoinCaravan")) this.shouldJoinCaravan = nbt.getBoolean("Purpur.ShouldJoinCaravan"); // Purpur
this.updateContainerEquipment();
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.LlamaHasRider(this)); // Purpur
this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2D));
this.goalSelector.addGoal(2, new LlamaFollowCaravanGoal(this, 2.0999999046325684D));
this.goalSelector.addGoal(3, new RangedAttackGoal(this, 1.25D, 40, 20.0F));
@@ -137,6 +213,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 0.7D));
this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(9, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.LlamaHasRider(this)); // Purpur
this.targetSelector.addGoal(1, new Llama.LlamaHurtByTargetGoal(this));
this.targetSelector.addGoal(2, new Llama.LlamaAttackWolfGoal(this));
}
@@ -459,6 +536,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
public void leaveCaravan() {
if (this.caravanHead != null) {
+ new org.purpurmc.purpur.event.entity.LlamaLeaveCaravanEvent((org.bukkit.entity.Llama) getBukkitEntity()).callEvent(); // Purpur
this.caravanHead.caravanTail = null;
}
@@ -466,6 +544,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
}
public void joinCaravan(Llama llama) {
+ if (!this.level().purpurConfig.llamaJoinCaravans || !shouldJoinCaravan || !new org.purpurmc.purpur.event.entity.LlamaJoinCaravanEvent((org.bukkit.entity.Llama) getBukkitEntity(), (org.bukkit.entity.Llama) llama.getBukkitEntity()).callEvent()) return; // Purpur
this.caravanHead = llama;
this.caravanHead.caravanTail = this;
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Mule.java b/src/main/java/net/minecraft/world/entity/animal/horse/Mule.java
index a6601f70890f90691923c0e6a9f10ea597ccabc2..42dcdbec69b052679e590a1ff932c6192c68b20d 100644
--- a/src/main/java/net/minecraft/world/entity/animal/horse/Mule.java
+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Mule.java
@@ -14,6 +14,43 @@ public class Mule extends AbstractChestedHorse {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.muleRidableInWater;
+ }
+ // Purpur end
+
+ @Override
+ public float generateMaxHealth(net.minecraft.util.RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.muleMaxHealthMin, this.level().purpurConfig.muleMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(net.minecraft.util.RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.muleJumpStrengthMin, this.level().purpurConfig.muleJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(net.minecraft.util.RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.muleMovementSpeedMin, this.level().purpurConfig.muleMovementSpeedMax);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.muleBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.muleTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.muleAlwaysDropExp;
+ }
+
@Override
protected SoundEvent getAmbientSound() {
return SoundEvents.MULE_AMBIENT;
diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonHorse.java
index d2007d4461967551c873b7c2020613c5c6d49b1d..984aaa96742d9559eaf2f922d865c39e0f7caeb5 100644
--- a/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonHorse.java
+++ b/src/main/java/net/minecraft/world/entity/animal/horse/SkeletonHorse.java
@@ -28,6 +28,43 @@ public class SkeletonHorse extends AbstractHorse {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isTamed() {
+ return true;
+ }
+ // Purpur end
+
+ @Override
+ public float generateMaxHealth(RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.skeletonHorseMaxHealthMin, this.level().purpurConfig.skeletonHorseMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.skeletonHorseJumpStrengthMin, this.level().purpurConfig.skeletonHorseJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.skeletonHorseMovementSpeedMin, this.level().purpurConfig.skeletonHorseMovementSpeedMax);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return 6000;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.skeletonHorseTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.skeletonHorseAlwaysDropExp;
+ }
+
public static AttributeSupplier.Builder createAttributes() {
return createBaseHorseAttributes().add(Attributes.MAX_HEALTH, 15.0D).add(Attributes.MOVEMENT_SPEED, (double)0.2F);
}
@@ -39,6 +76,7 @@ public class SkeletonHorse extends AbstractHorse {
@Override
protected void addBehaviourGoals() {
+ if (level().purpurConfig.skeletonHorseCanSwim) goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this));
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/TraderLlama.java b/src/main/java/net/minecraft/world/entity/animal/horse/TraderLlama.java
index b016f53c6644c6411b3a91e09049892131187179..02e3adc9a55fa9cd2a418094eb206970faaf471b 100644
--- a/src/main/java/net/minecraft/world/entity/animal/horse/TraderLlama.java
+++ b/src/main/java/net/minecraft/world/entity/animal/horse/TraderLlama.java
@@ -27,6 +27,58 @@ public class TraderLlama extends Llama {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.traderLlamaRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.traderLlamaRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.traderLlamaControllable;
+ }
+
+ @Override
+ public boolean isSaddled() {
+ return super.isSaddled() || isTamed();
+ }
+ // Purpur end
+
+ @Override
+ public float generateMaxHealth(net.minecraft.util.RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.traderLlamaMaxHealthMin, this.level().purpurConfig.traderLlamaMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(net.minecraft.util.RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.traderLlamaJumpStrengthMin, this.level().purpurConfig.traderLlamaJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(net.minecraft.util.RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.traderLlamaMovementSpeedMin, this.level().purpurConfig.traderLlamaMovementSpeedMax);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.traderLlamaBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.traderLlamaTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.traderLlamaAlwaysDropExp;
+ }
+
@Override
public boolean isTraderLlama() {
return true;
diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/ZombieHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/ZombieHorse.java
index e2935115c8d41af1d623da4f0d4f73de80386129..1dbe73dda3fb3f32b354c416908d37ae514dfc27 100644
--- a/src/main/java/net/minecraft/world/entity/animal/horse/ZombieHorse.java
+++ b/src/main/java/net/minecraft/world/entity/animal/horse/ZombieHorse.java
@@ -21,6 +21,48 @@ public class ZombieHorse extends AbstractHorse {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombieHorseRidableInWater;
+ }
+
+ @Override
+ public boolean isTamed() {
+ return true;
+ }
+ // Purpur end
+
+ @Override
+ public float generateMaxHealth(RandomSource random) {
+ return (float) generateMaxHealth(this.level().purpurConfig.zombieHorseMaxHealthMin, this.level().purpurConfig.zombieHorseMaxHealthMax);
+ }
+
+ @Override
+ public double generateJumpStrength(RandomSource random) {
+ return generateJumpStrength(this.level().purpurConfig.zombieHorseJumpStrengthMin, this.level().purpurConfig.zombieHorseJumpStrengthMax);
+ }
+
+ @Override
+ public double generateSpeed(RandomSource random) {
+ return generateSpeed(this.level().purpurConfig.zombieHorseMovementSpeedMin, this.level().purpurConfig.zombieHorseMovementSpeedMax);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return 6000;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.zombieHorseTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.zombieHorseAlwaysDropExp;
+ }
+
public static AttributeSupplier.Builder createAttributes() {
return createBaseHorseAttributes().add(Attributes.MAX_HEALTH, 15.0D).add(Attributes.MOVEMENT_SPEED, (double)0.2F);
}
@@ -63,5 +105,6 @@ public class ZombieHorse extends AbstractHorse {
@Override
protected void addBehaviourGoals() {
+ if (level().purpurConfig.zombieHorseCanSwim) goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur
}
}
diff --git a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java
index 94965855233509cbe48cebd690a83ef29cd0c27a..552777d7ef6a1190a3b84bdf2f130f735c61d275 100644
--- a/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java
+++ b/src/main/java/net/minecraft/world/entity/animal/sniffer/Sniffer.java
@@ -90,6 +90,33 @@ public class Sniffer extends Animal {
this.setPathfindingMalus(BlockPathTypes.DAMAGE_CAUTIOUS, -1.0F);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.snifferRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.snifferRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.snifferControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.snifferMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.snifferBreedingTicks;
+ }
+
// CraftBukkit start - SPIGOT-7295: moved from constructor to appropriate location
@Override
protected void defineSynchedData() {
@@ -334,7 +361,7 @@ public class Sniffer extends Animal {
}
@Override
- protected void jumpFromGround() {
+ public void jumpFromGround() { // Purpur - protected -> public
super.jumpFromGround();
double d0 = this.moveControl.getSpeedModifier();
@@ -493,11 +520,11 @@ public class Sniffer extends Animal {
@Override
protected void customServerAiStep() {
- this.level().getProfiler().push("snifferBrain");
+ //this.level().getProfiler().push("snifferBrain"); // Purpur
this.getBrain().tick((ServerLevel) this.level(), this);
- this.level().getProfiler().popPush("snifferActivityUpdate");
+ //this.level().getProfiler().popPush("snifferActivityUpdate"); // Purpur
SnifferAi.updateActivity(this);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
super.customServerAiStep();
}
diff --git a/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java b/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java
index a6f3fba3b02b0b4d2a4e9e5205301c6f52d0188a..4eebfc27ded55e4d764d04f35d3e9c9e0791c89f 100644
--- a/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java
+++ b/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java
@@ -24,6 +24,13 @@ public class EnderDragonPart extends Entity {
this.name = name;
}
+ // Purpur start
+ @Override
+ public net.minecraft.world.InteractionResult interact(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) {
+ return parentMob.isAlive() ? parentMob.tryRide(player, hand) : net.minecraft.world.InteractionResult.PASS;
+ }
+ // Purpur end
+
@Override
protected void defineSynchedData() {
}
diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
index ffc5b68c4246a7111845230a75552bb15875a209..440add62fcfa62d483409e1aecfc91591ba476c3 100644
--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
@@ -30,6 +30,12 @@ public class EndCrystal extends Entity {
private static final EntityDataAccessor<Boolean> DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN);
public int time;
public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals
+ // Purpur start
+ private net.minecraft.world.entity.monster.Phantom targetPhantom;
+ private int phantomBeamTicks = 0;
+ private int phantomDamageCooldown = 0;
+ private int idleCooldown = 0;
+ // Purpur end
public EndCrystal(EntityType<? extends EndCrystal> type, Level world) {
super(type, world);
@@ -42,6 +48,22 @@ public class EndCrystal extends Entity {
this.setPos(x, y, z);
}
+ public boolean shouldExplode() {
+ return showsBottom() ? level().purpurConfig.basedEndCrystalExplode : level().purpurConfig.baselessEndCrystalExplode;
+ }
+
+ public float getExplosionPower() {
+ return (float) (showsBottom() ? level().purpurConfig.basedEndCrystalExplosionPower : level().purpurConfig.baselessEndCrystalExplosionPower);
+ }
+
+ public boolean hasExplosionFire() {
+ return showsBottom() ? level().purpurConfig.basedEndCrystalExplosionFire : level().purpurConfig.baselessEndCrystalExplosionFire;
+ }
+
+ public Level.ExplosionInteraction getExplosionEffect() {
+ return showsBottom() ? level().purpurConfig.basedEndCrystalExplosionEffect : level().purpurConfig.baselessEndCrystalExplosionEffect;
+ }
+
@Override
protected Entity.MovementEmission getMovementEmission() {
return Entity.MovementEmission.NONE;
@@ -77,9 +99,53 @@ public class EndCrystal extends Entity {
}
}
// Paper end
+ if (this.level().purpurConfig.endCrystalCramming > 0 && this.level().getEntitiesOfClass(EndCrystal.class, getBoundingBox()).size() > this.level().purpurConfig.endCrystalCramming) this.hurt(this.damageSources().cramming(), 6.0F); // Purpur
}
+ // Purpur start
+ if (level().purpurConfig.phantomAttackedByCrystalRadius <= 0 || --idleCooldown > 0) {
+ return; // on cooldown
+ }
+
+ if (targetPhantom == null) {
+ for (net.minecraft.world.entity.monster.Phantom phantom : level().getEntitiesOfClass(net.minecraft.world.entity.monster.Phantom.class, getBoundingBox().inflate(level().purpurConfig.phantomAttackedByCrystalRadius))) {
+ if (phantom.hasLineOfSight(this)) {
+ attackPhantom(phantom);
+ break;
+ }
+ }
+ } else {
+ setBeamTarget(new BlockPos(targetPhantom).offset(0, -2, 0));
+ if (--phantomBeamTicks > 0 && targetPhantom.isAlive()) {
+ phantomDamageCooldown--;
+ if (targetPhantom.hasLineOfSight(this)) {
+ if (phantomDamageCooldown <= 0) {
+ phantomDamageCooldown = 20;
+ targetPhantom.hurt(targetPhantom.damageSources().indirectMagic(this, this), level().purpurConfig.phantomAttackedByCrystalDamage);
+ }
+ } else {
+ forgetPhantom(); // no longer in sight
+ }
+ } else {
+ forgetPhantom(); // attacked long enough
+ }
+ }
+ }
+
+ private void attackPhantom(net.minecraft.world.entity.monster.Phantom phantom) {
+ phantomDamageCooldown = 0;
+ phantomBeamTicks = 60;
+ targetPhantom = phantom;
+ }
+
+ private void forgetPhantom() {
+ targetPhantom = null;
+ setBeamTarget(null);
+ phantomBeamTicks = 0;
+ phantomDamageCooldown = 0;
+ idleCooldown = 60;
}
+ // Purpur end
@Override
protected void addAdditionalSaveData(CompoundTag nbt) {
@@ -124,16 +190,18 @@ public class EndCrystal extends Entity {
// CraftBukkit end
this.remove(Entity.RemovalReason.KILLED);
if (!source.is(DamageTypeTags.IS_EXPLOSION)) {
+ if (shouldExplode()) {// Purpur
DamageSource damagesource1 = source.getEntity() != null ? this.damageSources().explosion(this, source.getEntity()) : null;
// CraftBukkit start
- ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false);
+ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, getExplosionPower(), hasExplosionFire()); // Purpur
if (event.isCancelled()) {
this.unsetRemoved();
return false;
}
- this.level().explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK);
+ this.level().explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), getExplosionEffect()); // Purpur
// CraftBukkit end
+ } else this.unsetRemoved(); // Purpur
}
this.onDestroyedBy(source);
diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
index a24ae93efcdb2da5782d342c7697a1bb253400c7..ec595b74a6376adb65840035cdaa7502c9a3fe50 100644
--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
@@ -105,9 +105,11 @@ public class EnderDragon extends Mob implements Enemy {
@Nullable
private BlockPos podium;
// Paper end
+ private boolean hadRider; // Purpur
public EnderDragon(EntityType<? extends EnderDragon> entitytypes, Level world) {
super(EntityType.ENDER_DRAGON, world);
+ this.explosionSource = new Explosion(world, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.BlockInteraction.DESTROY); // Purpur - moved instantiation from field
this.fightOrigin = BlockPos.ZERO;
this.growlTime = 100;
this.nodes = new Node[24];
@@ -126,7 +128,37 @@ public class EnderDragon extends Mob implements Enemy {
this.noPhysics = true;
this.noCulling = true;
this.phaseManager = new EnderDragonPhaseManager(this);
- this.explosionSource = new Explosion(world, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit
+
+ // Purpur start
+ this.moveControl = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this) {
+ @Override
+ public void vanillaTick() {
+ // dragon doesn't use the controller. do nothing
+ }
+ };
+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) {
+ @Override
+ public void vanillaTick() {
+ // dragon doesn't use the controller. do nothing
+ }
+
+ @Override
+ public void purpurTick(Player rider) {
+ setYawPitch(rider.getYRot() - 180F, rider.xRotO * 0.5F);
+ }
+ };
+ // Purpur end
+ }
+
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.enderDragonRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.enderDragonRidableInWater;
}
public void setDragonFight(EndDragonFight fight) {
@@ -141,6 +173,27 @@ public class EnderDragon extends Mob implements Enemy {
return this.fightOrigin;
}
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.enderDragonControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.enderDragonMaxY;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.enderDragonMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.enderDragonTakeDamageFromWater;
+ }
+
public static AttributeSupplier.Builder createAttributes() {
return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D);
}
@@ -202,6 +255,37 @@ public class EnderDragon extends Mob implements Enemy {
@Override
public void aiStep() {
+ // Purpur start
+ boolean hasRider = getRider() != null && this.isControllable();
+ if (hasRider) {
+ if (!hadRider) {
+ hadRider = true;
+ noPhysics = false;
+ this.dimensions = net.minecraft.world.entity.EntityDimensions.scalable(4.0F, 2.0F);
+ }
+
+ // dragon doesn't use controllers, so must tick manually
+ moveControl.tick();
+ lookControl.tick();
+
+ moveRelative((float) getAttributeValue(Attributes.MOVEMENT_SPEED) * 0.1F, new Vec3(-getStrafeMot(), getVerticalMot(), -getForwardMot()));
+ Vec3 mot = getDeltaMovement();
+ setDeltaMovement(mot);
+ move(MoverType.PLAYER, mot);
+
+ mot = mot.multiply(0.9F, 0.9F, 0.9F);
+ setDeltaMovement(mot);
+
+ // control wing flap speed on client
+ phaseManager.setPhase(mot.x() * mot.x() + mot.z() * mot.z() < 0.005F ? EnderDragonPhase.HOVERING : EnderDragonPhase.HOLDING_PATTERN);
+ } else if (hadRider) {
+ hadRider = false;
+ noPhysics = true;
+ this.dimensions = net.minecraft.world.entity.EntityDimensions.scalable(16.0F, 8.0F);
+ phaseManager.setPhase(EnderDragonPhase.HOLDING_PATTERN); // HoldingPattern
+ }
+ // Purpur end
+
this.processFlappingMovement();
if (this.level().isClientSide) {
this.setHealth(this.getHealth());
@@ -228,6 +312,8 @@ public class EnderDragon extends Mob implements Enemy {
float f;
if (this.isDeadOrDying()) {
+ if (hasRider) ejectPassengers(); // Purpur
+
float f1 = (this.random.nextFloat() - 0.5F) * 8.0F;
f = (this.random.nextFloat() - 0.5F) * 4.0F;
@@ -240,9 +326,9 @@ public class EnderDragon extends Mob implements Enemy {
f = 0.2F / ((float) vec3d.horizontalDistance() * 10.0F + 1.0F);
f *= (float) Math.pow(2.0D, vec3d.y);
- if (this.phaseManager.getCurrentPhase().isSitting()) {
+ if (!hasRider && this.phaseManager.getCurrentPhase().isSitting()) { // Purpur
this.flapTime += 0.1F;
- } else if (this.inWall) {
+ } else if (!hasRider && this.inWall) { // Purpur
this.flapTime += f * 0.5F;
} else {
this.flapTime += f;
@@ -287,7 +373,7 @@ public class EnderDragon extends Mob implements Enemy {
}
this.phaseManager.getCurrentPhase().doClientTick();
- } else {
+ } else if (!hasRider) { // Purpur
DragonPhaseInstance idragoncontroller = this.phaseManager.getCurrentPhase();
idragoncontroller.doServerTick();
@@ -356,7 +442,7 @@ public class EnderDragon extends Mob implements Enemy {
this.tickPart(this.body, (double) (f11 * 0.5F), 0.0D, (double) (-f12 * 0.5F));
this.tickPart(this.wing1, (double) (f12 * 4.5F), 2.0D, (double) (f11 * 4.5F));
this.tickPart(this.wing2, (double) (f12 * -4.5F), 2.0D, (double) (f11 * -4.5F));
- if (!this.level().isClientSide && this.hurtTime == 0) {
+ if (!hasRider && !this.level().isClientSide && this.hurtTime == 0) { // Purpur
this.knockBack(this.level().getEntities((Entity) this, this.wing1.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
this.knockBack(this.level().getEntities((Entity) this, this.wing2.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
this.hurt(this.level().getEntities((Entity) this, this.head.getBoundingBox().inflate(1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
@@ -400,7 +486,7 @@ public class EnderDragon extends Mob implements Enemy {
}
if (!this.level().isClientSide) {
- this.inWall = this.checkWalls(this.head.getBoundingBox()) | this.checkWalls(this.neck.getBoundingBox()) | this.checkWalls(this.body.getBoundingBox());
+ this.inWall = !hasRider && this.checkWalls(this.head.getBoundingBox()) | this.checkWalls(this.neck.getBoundingBox()) | this.checkWalls(this.body.getBoundingBox()); // Purpur
if (this.dragonFight != null) {
this.dragonFight.updateDragon(this);
}
@@ -532,7 +618,7 @@ public class EnderDragon extends Mob implements Enemy {
BlockState iblockdata = this.level().getBlockState(blockposition);
if (!iblockdata.isAir() && !iblockdata.is(BlockTags.DRAGON_TRANSPARENT)) {
- if (this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) {
+ if ((this.level().purpurConfig.enderDragonBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) { // Purpur
// CraftBukkit start - Add blocks to list rather than destroying them
// flag1 = this.level().removeBlock(blockposition, false) || flag1;
flag1 = true;
@@ -676,7 +762,7 @@ public class EnderDragon extends Mob implements Enemy {
boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
short short0 = 500;
- if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
+ if (this.dragonFight != null && (level().purpurConfig.enderDragonAlwaysDropsFullExp || !this.dragonFight.hasPreviouslyKilledDragon())) {
short0 = 12000;
}
@@ -1111,6 +1197,7 @@ public class EnderDragon extends Mob implements Enemy {
@Override
protected boolean canRide(Entity entity) {
+ if (this.level().purpurConfig.enderDragonCanRideVehicles) return this.boardingCooldown <= 0; // Purpur
return false;
}
diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
index c65ab566c6241dd6a44bd11a449ef0c4b2f6dc65..c94a1b75593ad01e7f79fdc84818ea8ec269e124 100644
--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
@@ -84,20 +84,59 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
return entityliving.getMobType() != MobType.UNDEAD && entityliving.attackable();
};
private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR);
+ @Nullable private java.util.UUID summoner; // Purpur
+ private int shootCooldown = 0; // Purpur
// Paper start
private boolean canPortal = false;
public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; }
// Paper end
+ private org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD purpurController; // Purpur
public WitherBoss(EntityType<? extends WitherBoss> type, Level world) {
super(type, world);
this.bossEvent = (ServerBossEvent) (new ServerBossEvent(this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS)).setDarkenScreen(true);
- this.moveControl = new FlyingMoveControl(this, 10, false);
+ // Purpur start
+ this.purpurController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.1F);
+ this.moveControl = new FlyingMoveControl(this, 10, false) {
+ @Override
+ public void tick() {
+ if (mob.getRider() != null && mob.isControllable()) {
+ purpurController.purpurTick(mob.getRider());
+ } else {
+ super.tick();
+ }
+ }
+ };
+ // Purpur end
this.setHealth(this.getMaxHealth());
this.xpReward = 50;
}
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.witherTakeDamageFromWater;
+ }
+
+ @Nullable
+ public java.util.UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(@Nullable java.util.UUID summoner) {
+ this.summoner = summoner;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.witherAlwaysDropExp;
+ }
+
@Override
protected PathNavigation createNavigation(Level world) {
FlyingPathNavigation navigationflying = new FlyingPathNavigation(this, world);
@@ -108,13 +147,113 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
return navigationflying;
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.witherRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witherRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.witherControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.witherMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 5F;
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.5, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ this.entityData.set(DATA_TARGETS.get(0), 0);
+ this.entityData.set(DATA_TARGETS.get(1), 0);
+ this.entityData.set(DATA_TARGETS.get(2), 0);
+ getNavigation().stop();
+ shootCooldown = 20;
+ }
+
+ @Override
+ public boolean onClick(net.minecraft.world.InteractionHand hand) {
+ return shoot(getRider(), hand == net.minecraft.world.InteractionHand.MAIN_HAND ? new int[]{1} : new int[]{2});
+ }
+
+ public boolean shoot(@Nullable Player rider, int[] heads) {
+ if (shootCooldown > 0) {
+ return false;
+ }
+
+ shootCooldown = 20;
+ if (rider == null) {
+ return false;
+ }
+
+ org.bukkit.craftbukkit.entity.CraftHumanEntity player = rider.getBukkitEntity();
+ if (!player.hasPermission("allow.special.wither")) {
+ return false;
+ }
+
+ net.minecraft.world.phys.HitResult rayTrace = getRayTrace(120, net.minecraft.world.level.ClipContext.Fluid.NONE);
+ if (rayTrace == null) {
+ return false;
+ }
+
+ Vec3 loc;
+ if (rayTrace.getType() == net.minecraft.world.phys.HitResult.Type.BLOCK) {
+ BlockPos pos = ((net.minecraft.world.phys.BlockHitResult) rayTrace).getBlockPos();
+ loc = new Vec3(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D);
+ } else if (rayTrace.getType() == net.minecraft.world.phys.HitResult.Type.ENTITY) {
+ Entity target = ((net.minecraft.world.phys.EntityHitResult) rayTrace).getEntity();
+ loc = new Vec3(target.getX(), target.getY() + (target.getEyeHeight() / 2), target.getZ());
+ } else {
+ org.bukkit.block.Block block = player.getTargetBlock(null, 120);
+ loc = new Vec3(block.getX() + 0.5D, block.getY() + 0.5D, block.getZ() + 0.5D);
+ }
+
+ for (int head : heads) {
+ shoot(head, loc.x(), loc.y(), loc.z(), rider);
+ }
+
+ return true; // handled
+ }
+
+ public void shoot(int head, double x, double y, double z, Player rider) {
+ level().levelEvent(null, 1024, blockPosition(), 0);
+ double headX = getHeadX(head);
+ double headY = getHeadY(head);
+ double headZ = getHeadZ(head);
+ WitherSkull skull = new WitherSkull(level(), this, x - headX, y - headY, z - headZ);
+ skull.setPosRaw(headX, headY, headZ);
+ level().addFreshEntity(skull);
+ }
+ // Purpur end
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(0, new WitherBoss.WitherDoNothingGoal());
this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 40, 20.0F));
this.goalSelector.addGoal(5, new WaterAvoidingRandomFlyingGoal(this, 1.0D));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0]));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 0, false, false, WitherBoss.LIVING_ENTITY_SELECTOR));
}
@@ -132,6 +271,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
public void addAdditionalSaveData(CompoundTag nbt) {
super.addAdditionalSaveData(nbt);
nbt.putInt("Invul", this.getInvulnerableTicks());
+ if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur
}
@Override
@@ -141,6 +281,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
if (this.hasCustomName()) {
this.bossEvent.setName(this.getDisplayName());
}
+ if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur
}
@@ -263,6 +404,16 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
@Override
protected void customServerAiStep() {
+ // Purpur start
+ if (getRider() != null && this.isControllable()) {
+ Vec3 mot = getDeltaMovement();
+ setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z());
+ }
+ if (shootCooldown > 0) {
+ shootCooldown--;
+ }
+ // Purpur end
+
int i;
if (this.getInvulnerableTicks() > 0) {
@@ -279,7 +430,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
}
// CraftBukkit end
- if (!this.isSilent()) {
+ if (!this.isSilent() && level().purpurConfig.witherPlaySpawnSound) {
// CraftBukkit start - Use relative location for far away sounds
// this.level().globalLevelEvent(1023, new BlockPosition(this), 0);
int viewDistance = ((ServerLevel) this.level()).getCraftServer().getViewDistance() * 16;
@@ -303,7 +454,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
this.setInvulnerableTicks(i);
if (this.tickCount % 10 == 0) {
- this.heal(10.0F, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit
+ this.heal(this.getMaxHealth() / 33, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit // Purpur
}
} else {
@@ -363,7 +514,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
if (this.destroyBlocksTick > 0) {
--this.destroyBlocksTick;
- if (this.destroyBlocksTick == 0 && this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (this.destroyBlocksTick == 0 && (this.level().purpurConfig.witherBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur
i = Mth.floor(this.getY());
j = Mth.floor(this.getX());
int i1 = Mth.floor(this.getZ());
@@ -396,8 +547,10 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
}
}
- if (this.tickCount % 20 == 0) {
- this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
+ // Purpur start - customizable heal rate and amount
+ if (this.tickCount % level().purpurConfig.witherHealthRegenDelay == 0) {
+ this.heal(level().purpurConfig.witherHealthRegenAmount, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
+ // Purpur end
}
this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
@@ -583,11 +736,11 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
}
public int getAlternativeTarget(int headIndex) {
- return (Integer) this.entityData.get((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex));
+ return getRider() != null && this.isControllable() ? 0 : this.entityData.get(WitherBoss.DATA_TARGETS.get(headIndex)); // Purpur
}
public void setAlternativeTarget(int headIndex, int id) {
- this.entityData.set((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex), id);
+ if (getRider() == null || !this.isControllable()) this.entityData.set(WitherBoss.DATA_TARGETS.get(headIndex), id); // Purpur
}
@Override
@@ -602,6 +755,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
@Override
protected boolean canRide(Entity entity) {
+ if (this.level().purpurConfig.witherCanRideVehicles) return this.boardingCooldown <= 0; // Purpur
return false;
}
diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
index 5c6e060a54ffb13c37ff5711992e964bdd59643d..4c5f20bafe589fbd4a034408edb721ac79bbc666 100644
--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
@@ -99,10 +99,12 @@ public class ArmorStand extends LivingEntity {
private boolean noTickPoseDirty = false;
private boolean noTickEquipmentDirty = false;
// Paper end
+ public boolean canMovementTick = true; // Purpur
public ArmorStand(EntityType<? extends ArmorStand> type, Level world) {
super(type, world);
if (world != null) this.canTick = world.paperConfig().entities.armorStands.tick; // Paper - armour stand ticking
+ if (world != null) this.canMovementTick = world.purpurConfig.armorstandMovement; // Purpur
this.handItems = NonNullList.withSize(2, ItemStack.EMPTY);
this.armorItems = NonNullList.withSize(4, ItemStack.EMPTY);
this.headPose = ArmorStand.DEFAULT_HEAD_POSE;
@@ -112,6 +114,7 @@ public class ArmorStand extends LivingEntity {
this.leftLegPose = ArmorStand.DEFAULT_LEFT_LEG_POSE;
this.rightLegPose = ArmorStand.DEFAULT_RIGHT_LEG_POSE;
this.setMaxUpStep(0.0F);
+ this.setShowArms(world != null && world.purpurConfig.armorstandPlaceWithArms); // Purpur
}
public ArmorStand(Level world, double x, double y, double z) {
@@ -606,7 +609,7 @@ public class ArmorStand extends LivingEntity {
private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(DamageSource damageSource) { // Paper
ItemStack itemstack = new ItemStack(Items.ARMOR_STAND);
- if (this.hasCustomName()) {
+ if (this.level().purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { // Purpur
itemstack.setHoverName(this.getCustomName());
}
@@ -682,6 +685,7 @@ public class ArmorStand extends LivingEntity {
@Override
public void tick() {
+ maxUpStep = level().purpurConfig.armorstandStepHeight;
// Paper start
if (!this.canTick) {
if (this.noTickPoseDirty) {
@@ -1016,4 +1020,18 @@ public class ArmorStand extends LivingEntity {
}
// Paper end
// Paper end
+
+ // Purpur start
+ @Override
+ public void updateInWaterStateAndDoWaterCurrentPushing() {
+ if (this.level().purpurConfig.armorstandWaterMovement &&
+ (this.level().purpurConfig.armorstandWaterFence || !(level().getBlockState(blockPosition().below()).getBlock() instanceof net.minecraft.world.level.block.FenceBlock)))
+ super.updateInWaterStateAndDoWaterCurrentPushing();
+ }
+
+ @Override
+ public void aiStep() {
+ if (this.canMovementTick && this.canMove) super.aiStep();
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java
index 759ecd79534a7706f7d4a63eb9dacbefcfe54674..182faba889dc15a3500c5919cad8a5483a53033a 100644
--- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java
+++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java
@@ -273,7 +273,13 @@ public class ItemFrame extends HangingEntity {
}
if (alwaysDrop) {
- this.spawnAtLocation(this.getFrameItemStack());
+ // Purpur start
+ final ItemStack itemFrame = this.getFrameItemStack();
+ if (this.level().purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) {
+ itemFrame.setHoverName(this.getCustomName());
+ }
+ this.spawnAtLocation(itemFrame);
+ // Purpur end
}
if (!itemstack.isEmpty()) {
diff --git a/src/main/java/net/minecraft/world/entity/decoration/Painting.java b/src/main/java/net/minecraft/world/entity/decoration/Painting.java
index 0f4ef103afcbabc04880c8fc3547b861341c15fc..4b3a8e4a084585d56dd10a08405463b1a677368b 100644
--- a/src/main/java/net/minecraft/world/entity/decoration/Painting.java
+++ b/src/main/java/net/minecraft/world/entity/decoration/Painting.java
@@ -121,7 +121,7 @@ public class Painting extends HangingEntity implements VariantHolder<Holder<Pain
@Override
public void readAdditionalSaveData(CompoundTag nbt) {
- Holder<PaintingVariant> holder = loadVariant(nbt).orElseGet(Painting::getDefaultVariant);
+ Holder<PaintingVariant> holder = loadVariant(nbt).orElseGet(() -> (Holder.Reference<PaintingVariant>) getDefaultVariant()); // Purpur - decompile error TODO: still needed?
this.setVariant(holder);
this.direction = Direction.from2DDataValue(nbt.getByte("facing"));
super.readAdditionalSaveData(nbt);
@@ -159,7 +159,13 @@ public class Painting extends HangingEntity implements VariantHolder<Holder<Pain
}
}
- this.spawnAtLocation(Items.PAINTING);
+ // Purpur start
+ final ItemStack painting = new ItemStack(Items.PAINTING);
+ if (this.level().purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) {
+ painting.setHoverName(this.getCustomName());
+ }
+ this.spawnAtLocation(painting);
+ // Purpur end
}
}
diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
index 18eaccb39a4c81338a8cbebe3de03934913ac2a4..7ad5583f05b608d5a34a33fb77c5bf91f25de9a1 100644
--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
@@ -133,7 +133,7 @@ public class FallingBlockEntity extends Entity {
@Override
public void tick() {
// Paper start - fix sand duping
- if (this.isRemoved()) {
+ if (this.level().purpurConfig.fixSandDuping && this.isRemoved()) { // Purpur
return;
}
// Paper end - fix sand duping
@@ -150,7 +150,7 @@ public class FallingBlockEntity extends Entity {
this.move(MoverType.SELF, this.getDeltaMovement());
// Paper start - fix sand duping
- if (this.isRemoved()) {
+ if (this.level().purpurConfig.fixSandDuping && this.isRemoved()) { // Purpur
return;
}
// Paper end - fix sand duping
diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
index d39f31e7cf315c7cdc1daab28164380e44dd8341..028b83b8e835cfdad19ae9edad76c524b98ead7f 100644
--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
@@ -56,6 +56,12 @@ public class ItemEntity extends Entity implements TraceableEntity {
public boolean canMobPickup = true; // Paper
private int despawnRate = -1; // Paper
public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper
+ // Purpur start
+ public boolean immuneToCactus = false;
+ public boolean immuneToExplosion = false;
+ public boolean immuneToFire = false;
+ public boolean immuneToLightning = false;
+ // Purpur end
public ItemEntity(EntityType<? extends ItemEntity> type, Level world) {
super(type, world);
@@ -346,7 +352,16 @@ public class ItemEntity extends Entity implements TraceableEntity {
@Override
public boolean hurt(DamageSource source, float amount) {
- if (this.isInvulnerableTo(source)) {
+ // Purpur start
+ if (
+ (immuneToCactus && source.is(net.minecraft.world.damagesource.DamageTypes.CACTUS)) ||
+ (immuneToFire && (source.is(DamageTypeTags.IS_FIRE) || source.is(net.minecraft.world.damagesource.DamageTypes.ON_FIRE) || source.is(net.minecraft.world.damagesource.DamageTypes.IN_FIRE))) ||
+ (immuneToLightning && source.is(net.minecraft.world.damagesource.DamageTypes.LIGHTNING_BOLT)) ||
+ (immuneToExplosion && source.is(DamageTypeTags.IS_EXPLOSION))
+ ) {
+ return false;
+ } else if (this.isInvulnerableTo(source)) {
+ // Purpur end
return false;
} else if (!this.getItem().isEmpty() && this.getItem().is(Items.NETHER_STAR) && source.is(DamageTypeTags.IS_EXPLOSION)) {
return false;
@@ -549,6 +564,12 @@ public class ItemEntity extends Entity implements TraceableEntity {
// com.google.common.base.Preconditions.checkArgument(!stack.isEmpty(), "Cannot drop air"); // CraftBukkit // Paper - Remove check
this.getEntityData().set(ItemEntity.DATA_ITEM, stack);
this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper
+ // Purpur start
+ if (level().purpurConfig.itemImmuneToCactus.contains(stack.getItem())) immuneToCactus = true;
+ if (level().purpurConfig.itemImmuneToExplosion.contains(stack.getItem())) immuneToExplosion = true;
+ if (level().purpurConfig.itemImmuneToFire.contains(stack.getItem())) immuneToFire = true;
+ if (level().purpurConfig.itemImmuneToLightning.contains(stack.getItem())) immuneToLightning = true;
+ // level end
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
index 4ce3e69970dd9eb251d0538a2d233ca30e9e5e47..afc65b8bb7e7f7f70a25f2d869412ed325b658da 100644
--- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java
@@ -175,4 +175,29 @@ public class PrimedTnt extends Entity implements TraceableEntity {
return !level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid();
}
// Paper end
+ // Purpur start - Shears can defuse TNT
+ @Override
+ public net.minecraft.world.InteractionResult interact(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) {
+ if (!level().isClientSide && level().purpurConfig.shearsCanDefuseTnt) {
+ final net.minecraft.world.item.ItemStack inHand = player.getItemInHand(hand);
+
+ if (!inHand.is(net.minecraft.world.item.Items.SHEARS) || !player.getBukkitEntity().hasPermission("purpur.tnt.defuse") ||
+ level().random.nextFloat() > level().purpurConfig.shearsCanDefuseTntChance) return net.minecraft.world.InteractionResult.PASS;
+
+ net.minecraft.world.entity.item.ItemEntity tntItem = new net.minecraft.world.entity.item.ItemEntity(level(), getX(), getY(), getZ(),
+ new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.TNT));
+ tntItem.setPickUpDelay(10);
+
+ inHand.hurtAndBreak(1, player, entity -> entity.broadcastBreakEvent(hand));
+ level().addFreshEntity(tntItem, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CUSTOM);
+
+ this.playSound(net.minecraft.sounds.SoundEvents.SHEEP_SHEAR);
+
+ this.kill();
+ return net.minecraft.world.InteractionResult.SUCCESS;
+ }
+
+ return super.interact(player, hand);
+ }
+ // Purpur end - Shears can defuse TNT
}
diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
index 9ca1e9d95e62929c0015d5ca2c2f9c70e421842e..dd84433a988712da9d799cbda2487a902315fb92 100644
--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
@@ -66,16 +66,19 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
protected AbstractSkeleton(EntityType<? extends AbstractSkeleton> type, Level world) {
super(type, world);
this.reassessWeaponGoal();
+ this.setShouldBurnInDay(true); // Purpur
}
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(2, new RestrictSunGoal(this));
this.goalSelector.addGoal(3, new FleeSunGoal(this, 1.0D));
this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Wolf.class, 6.0F, 1.0D, 1.2D));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0]));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
@@ -99,35 +102,14 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
}
// Paper start
- private boolean shouldBurnInDay = true;
+ // private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity - keep methods for ABI compatibility
public boolean shouldBurnInDay() { return shouldBurnInDay; }
public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
// Paper end
@Override
public void aiStep() {
- boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - Configurable Burning
-
- if (flag) {
- ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
-
- if (!itemstack.isEmpty()) {
- if (itemstack.isDamageableItem()) {
- itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2));
- if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) {
- this.broadcastBreakEvent(EquipmentSlot.HEAD);
- this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY);
- }
- }
-
- flag = false;
- }
-
- if (flag) {
- this.setSecondsOnFire(8);
- }
- }
-
+ // Purpur start - implemented in LivingEntity
super.aiStep();
}
@@ -161,11 +143,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
this.reassessWeaponGoal();
this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper
if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
- LocalDate localdate = LocalDate.now();
- int i = localdate.get(ChronoField.DAY_OF_MONTH);
- int j = localdate.get(ChronoField.MONTH_OF_YEAR);
-
- if (j == 10 && i == 31 && randomsource.nextFloat() < 0.25F) {
+ if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(world.getMinecraftWorld()) && this.random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur
this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(randomsource.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN));
this.armorDropChances[EquipmentSlot.HEAD.getIndex()] = 0.0F;
}
@@ -192,7 +170,6 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
} else {
this.goalSelector.addGoal(4, this.meleeGoal);
}
-
}
}
@@ -205,7 +182,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
double d2 = target.getZ() - this.getZ();
double d3 = Math.sqrt(d0 * d0 + d2 * d2);
- entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.level().getDifficulty().getId() * 4));
+ entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, this.level().purpurConfig.skeletonBowAccuracyMap.getOrDefault(this.level().getDifficulty().getId(), (float) (14 - this.level().getDifficulty().getId() * 4))); // Purpur
// CraftBukkit start
org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getPickupItem(), entityarrow, net.minecraft.world.InteractionHand.MAIN_HAND, 0.8F, true); // Paper
if (event.isCancelled()) {
@@ -236,7 +213,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
this.reassessWeaponGoal();
// Paper start
if (nbt.contains("Paper.ShouldBurnInDay")) {
- this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay");
+ // this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); // Purpur - implemented in LivingEntity
}
// Paper end
}
@@ -245,7 +222,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
@Override
public void addAdditionalSaveData(CompoundTag nbt) {
super.addAdditionalSaveData(nbt);
- nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay);
+ // nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - implemented in LivingEntity
}
// Paper end
diff --git a/src/main/java/net/minecraft/world/entity/monster/Blaze.java b/src/main/java/net/minecraft/world/entity/monster/Blaze.java
index 17aa7676ab624440651850bbe5689f8a6c9dbeed..a8b58469fd8a1ed4ec0ce443cf05557903527bd7 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Blaze.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Blaze.java
@@ -32,26 +32,73 @@ public class Blaze extends Monster {
public Blaze(EntityType<? extends Blaze> type, Level world) {
super(type, world);
- this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F);
+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); // Purpur
+ if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur
this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F);
this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F);
this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F);
this.xpReward = 10;
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.blazeRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.blazeRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.blazeControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.blazeMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED);
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.blazeMaxHealth);
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.blazeAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(4, new Blaze.BlazeAttackGoal(this));
this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0D));
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D, 0.0F));
this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, (new HurtByTargetGoal(this)).setAlertOthers());
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
public static AttributeSupplier.Builder createAttributes() {
- return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0D).add(Attributes.MOVEMENT_SPEED, (double)0.23F).add(Attributes.FOLLOW_RANGE, 48.0D);
+ return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0D).add(Attributes.MOVEMENT_SPEED, (double)0.23F).add(Attributes.FOLLOW_RANGE, 48.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur
}
@Override
@@ -101,11 +148,19 @@ public class Blaze extends Monster {
@Override
public boolean isSensitiveToWater() {
- return true;
+ return this.level().purpurConfig.blazeTakeDamageFromWater; // Purpur
}
@Override
protected void customServerAiStep() {
+ // Purpur start
+ if (getRider() != null && this.isControllable()) {
+ Vec3 mot = getDeltaMovement();
+ setDeltaMovement(mot.x(), getVerticalMot() > 0 ? 0.07D : -0.07D, mot.z());
+ return;
+ }
+ // Purpur end
+
--this.nextHeightOffsetChangeTick;
if (this.nextHeightOffsetChangeTick <= 0) {
this.nextHeightOffsetChangeTick = 100;
diff --git a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java
index da48bd8e15463be8170262ae90a8e95575959bf2..bf218586a79ea5f4288a64ccf6c245ee8491ce9a 100644
--- a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java
+++ b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java
@@ -28,6 +28,38 @@ public class CaveSpider extends Spider {
return Spider.createAttributes().add(Attributes.MAX_HEALTH, 12.0D);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.caveSpiderRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.caveSpiderRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.caveSpiderControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.caveSpiderMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.caveSpiderTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.caveSpiderAlwaysDropExp;
+ }
+
@Override
public boolean doHurtTarget(Entity target) {
if (super.doHurtTarget(target)) {
diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java
index fd1b5a1beea7594fa65decfdcccfa15781fc005b..304ea7fdcd410a7c88ec61143364e14de8db0b0c 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java
@@ -59,21 +59,99 @@ public class Creeper extends Monster implements PowerableMob {
public int maxSwell = 30;
public int explosionRadius = 3;
private int droppedSkulls;
+ // Purpur start
+ private int spacebarCharge = 0;
+ private int prevSpacebarCharge = 0;
+ private int powerToggleDelay = 0;
+ private boolean exploding = false;
+ // Purpur end
public Creeper(EntityType<? extends Creeper> type, Level world) {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.creeperRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.creeperRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.creeperControllable;
+ }
+
+ @Override
+ protected void customServerAiStep() {
+ if (powerToggleDelay > 0) {
+ powerToggleDelay--;
+ }
+ if (getRider() != null && this.isControllable()) {
+ if (getRider().getForwardMot() != 0 || getRider().getStrafeMot() != 0) {
+ spacebarCharge = 0;
+ setIgnited(false);
+ setSwellDir(-1);
+ }
+ if (spacebarCharge == prevSpacebarCharge) {
+ spacebarCharge = 0;
+ }
+ prevSpacebarCharge = spacebarCharge;
+ }
+ super.customServerAiStep();
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ setIgnited(false);
+ setSwellDir(-1);
+ }
+
+ @Override
+ public boolean onSpacebar() {
+ if (powerToggleDelay > 0) {
+ return true; // just toggled power, do not jump or ignite
+ }
+ spacebarCharge++;
+ if (spacebarCharge > maxSwell - 2) {
+ spacebarCharge = 0;
+ if (getRider() != null && getRider().getBukkitEntity().hasPermission("allow.powered.creeper")) {
+ powerToggleDelay = 20;
+ setPowered(!isPowered());
+ setIgnited(false);
+ setSwellDir(-1);
+ return true;
+ }
+ }
+ if (!isIgnited()) {
+ if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0 &&
+ getRider().getBukkitEntity().hasPermission("allow.special.creeper")) {
+ setIgnited(true);
+ setSwellDir(1);
+ return true;
+ }
+ }
+ return getForwardMot() == 0 && getStrafeMot() == 0; // do not jump if standing still
+ }
+ // Purpur end
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
this.goalSelector.addGoal(2, new SwellGoal(this));
+ this.goalSelector.addGoal(3, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Ocelot.class, 6.0F, 1.0D, 1.2D));
this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Cat.class, 6.0F, 1.0D, 1.2D));
this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, false));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true));
this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0]));
}
@@ -173,6 +251,37 @@ public class Creeper extends Monster implements PowerableMob {
}
}
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.creeperMaxHealth);
+ }
+
+ public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.MobSpawnType spawnReason, @Nullable net.minecraft.world.entity.SpawnGroupData entityData, @Nullable CompoundTag entityNbt) {
+ double chance = world.getLevel().purpurConfig.creeperChargedChance;
+ if (chance > 0D && random.nextDouble() <= chance) {
+ setPowered(true);
+ }
+ return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.creeperTakeDamageFromWater;
+ }
+
+ @Override
+ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource damagesource) {
+ if (!exploding && this.level().purpurConfig.creeperExplodeWhenKilled && damagesource.getEntity() instanceof net.minecraft.server.level.ServerPlayer) {
+ this.explodeCreeper();
+ }
+ return super.dropAllDeathLoot(damagesource);
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.creeperAlwaysDropExp;
+ }
+
@Override
protected SoundEvent getHurtSound(DamageSource source) {
return SoundEvents.CREEPER_HURT;
@@ -264,15 +373,17 @@ public class Creeper extends Monster implements PowerableMob {
}
public void explodeCreeper() {
+ this.exploding = true; // Purpur
if (!this.level().isClientSide) {
float f = this.isPowered() ? 2.0F : 1.0F;
+ float multiplier = this.level().purpurConfig.creeperHealthRadius ? this.getHealth() / this.getMaxHealth() : 1; // Purpur
// CraftBukkit start
- ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, this.explosionRadius * f, false);
+ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, (this.explosionRadius * f) * multiplier, false); // Purpur
if (!event.isCancelled()) {
// CraftBukkit end
this.dead = true;
- this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit
+ this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), this.level().getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) && level().purpurConfig.creeperAllowGriefing ? Level.ExplosionInteraction.MOB : Level.ExplosionInteraction.NONE); // CraftBukkit // Purpur
this.discard();
this.spawnLingeringCloud();
// CraftBukkit start
@@ -282,7 +393,7 @@ public class Creeper extends Monster implements PowerableMob {
}
// CraftBukkit end
}
-
+ this.exploding = false; // Purpur
}
private void spawnLingeringCloud() {
@@ -324,6 +435,7 @@ public class Creeper extends Monster implements PowerableMob {
com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited);
if (event.callEvent()) {
this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited());
+ if (!event.isIgnited()) setSwellDir(-1); // Purpur
}
}
// Paper end
diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java
index 991d728db2a3b64316fc2102cf3aee470327a62e..63a1cf5604c14025171d7be7434e2d6b64c98107 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java
@@ -29,6 +29,7 @@ import net.minecraft.world.entity.ai.goal.MoveToBlockGoal;
import net.minecraft.world.entity.ai.goal.RandomStrollGoal;
import net.minecraft.world.entity.ai.goal.RangedAttackGoal;
import net.minecraft.world.entity.ai.goal.ZombieAttackGoal;
+import net.minecraft.world.entity.ai.goal.MoveThroughVillageGoal;
import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
@@ -68,6 +69,58 @@ public class Drowned extends Zombie implements RangedAttackMob {
this.groundNavigation = new GroundPathNavigation(this, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.drownedRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.drownedRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.drownedControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.drownedMaxHealth);
+ }
+
+ @Override
+ protected void randomizeReinforcementsChance() {
+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.drownedSpawnReinforcements);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.drownedTakeDamageFromWater;
+ }
+
+ @Override
+ public boolean jockeyOnlyBaby() {
+ return level().purpurConfig.drownedJockeyOnlyBaby;
+ }
+
+ @Override
+ public double jockeyChance() {
+ return level().purpurConfig.drownedJockeyChance;
+ }
+
+ @Override
+ public boolean jockeyTryExistingChickens() {
+ return level().purpurConfig.drownedJockeyTryExistingChickens;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.drownedAlwaysDropExp;
+ }
+
@Override
protected void addBehaviourGoals() {
this.goalSelector.addGoal(1, new Drowned.DrownedGoToWaterGoal(this, 1.0D));
@@ -75,10 +128,23 @@ public class Drowned extends Zombie implements RangedAttackMob {
this.goalSelector.addGoal(2, new Drowned.DrownedAttackGoal(this, 1.0D, false));
this.goalSelector.addGoal(5, new Drowned.DrownedGoToBeachGoal(this, 1.0D));
this.goalSelector.addGoal(6, new Drowned.DrownedSwimUpGoal(this, 1.0D, this.level().getSeaLevel()));
+ if (level().purpurConfig.drownedBreakDoors) this.goalSelector.addGoal(6, new MoveThroughVillageGoal(this, 1.0D, true, 4, this::canBreakDoors));
this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0D));
this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Drowned.class})).setAlertOthers(ZombifiedPiglin.class));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::okTarget));
- if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper
+ // Purpur start
+ if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false) { // Spigot
+ @Override
+ public boolean canUse() {
+ return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canUse();
+ }
+
+ @Override
+ public boolean canContinueToUse() {
+ return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canContinueToUse();
+ }
+ });
+ // Purpur end
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false));
this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR));
@@ -112,7 +178,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
@Override
public boolean supportsBreakDoorGoal() {
- return false;
+ return level().purpurConfig.drownedBreakDoors ? true : false;
}
@Override
@@ -259,8 +325,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
this.searchingForLand = targetingUnderwater;
}
- private static class DrownedMoveControl extends MoveControl {
-
+ private static class DrownedMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur
private final Drowned drowned;
public DrownedMoveControl(Drowned drowned) {
@@ -269,7 +334,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur
LivingEntity entityliving = this.drowned.getTarget();
if (this.drowned.wantsToSwim() && this.drowned.isInWater()) {
@@ -292,7 +357,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
this.drowned.setYRot(this.rotlerp(this.drowned.getYRot(), f, 90.0F));
this.drowned.yBodyRot = this.drowned.getYRot();
- float f1 = (float) (this.speedModifier * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED));
+ float f1 = (float) (this.getSpeedModifier() * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur
float f2 = Mth.lerp(0.125F, this.drowned.getSpeed(), f1);
this.drowned.setSpeed(f2);
@@ -302,7 +367,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(0.0D, -0.008D, 0.0D));
}
- super.tick();
+ super.vanillaTick(); // Purpur
}
}
diff --git a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java
index 8f481e11815d7162dd62a2b850b3d2af6d904519..b30f13d2a7198f568bc36c0d974fd6dc6be292c2 100644
--- a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java
+++ b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java
@@ -33,6 +33,33 @@ public class ElderGuardian extends Guardian {
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.elderGuardianRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.elderGuardianControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.elderGuardianMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.elderGuardianTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.elderGuardianAlwaysDropExp;
+ }
+
public static AttributeSupplier.Builder createAttributes() {
return Guardian.createAttributes().add(Attributes.MOVEMENT_SPEED, 0.30000001192092896D).add(Attributes.ATTACK_DAMAGE, 8.0D).add(Attributes.MAX_HEALTH, 80.0D);
}
diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
index 410f10ad93935d1c078447a4596023f367a8e9b7..44331be61edffd5afadf3e637dd181bff1133d4f 100644
--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
@@ -93,12 +93,40 @@ public class EnderMan extends Monster implements NeutralMob {
public EnderMan(EntityType<? extends EnderMan> type, Level world) {
super(type, world);
this.setMaxUpStep(1.0F);
- this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F);
+ if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur
+ }
+
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.endermanRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.endermanRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.endermanControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.endermanMaxHealth);
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.endermanAlwaysDropExp;
}
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new EnderMan.EndermanFreezeWhenLookedAt(this));
this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false));
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D, 0.0F));
@@ -106,9 +134,10 @@ public class EnderMan extends Monster implements NeutralMob {
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
this.goalSelector.addGoal(10, new EnderMan.EndermanLeaveBlockGoal(this));
this.goalSelector.addGoal(11, new EnderMan.EndermanTakeBlockGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new EnderMan.EndermanLookForPlayerGoal(this, this::isAngryAt));
this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0]));
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, true, false));
+ this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, 10, true, false, (entityliving) -> entityliving.level().purpurConfig.endermanAggroEndermites && entityliving instanceof Endermite endermite && (!entityliving.level().purpurConfig.endermanAggroEndermitesOnlyIfPlayerSpawned || endermite.isPlayerSpawned()))); // Purpur
this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal<>(this, false));
}
@@ -245,7 +274,7 @@ public class EnderMan extends Monster implements NeutralMob {
// Paper end
ItemStack itemstack = (ItemStack) player.getInventory().armor.get(3);
- if (itemstack.is(Blocks.CARVED_PUMPKIN.asItem())) {
+ if (this.level().purpurConfig.endermanDisableStareAggro || itemstack.is(Blocks.CARVED_PUMPKIN.asItem()) || (this.level().purpurConfig.endermanIgnorePlayerDragonHead && itemstack.is(net.minecraft.world.item.Items.DRAGON_HEAD))) { // Purpur
return false;
} else {
Vec3 vec3d = player.getViewVector(1.0F).normalize();
@@ -282,12 +311,12 @@ public class EnderMan extends Monster implements NeutralMob {
@Override
public boolean isSensitiveToWater() {
- return true;
+ return this.level().purpurConfig.endermanTakeDamageFromWater; // Purpur
}
@Override
protected void customServerAiStep() {
- if (this.level().isDay() && this.tickCount >= this.targetChangeTime + 600) {
+ if ((getRider() == null || !this.isControllable()) && this.level().isDay() && this.tickCount >= this.targetChangeTime + 600) { // Purpur - no random teleporting
float f = this.getLightLevelDependentMagicValue();
if (f > 0.5F && this.level().canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper
@@ -408,6 +437,8 @@ public class EnderMan extends Monster implements NeutralMob {
public boolean hurt(DamageSource source, float amount) {
if (this.isInvulnerableTo(source)) {
return false;
+ } else if (getRider() != null && this.isControllable()) { return super.hurt(source, amount); // Purpur - no teleporting on damage
+ } else if (org.purpurmc.purpur.PurpurConfig.endermanShortHeight && source.is(net.minecraft.world.damagesource.DamageTypes.IN_WALL)) { return false; // Purpur - no suffocation damage if short height
} else {
boolean flag = source.getDirectEntity() instanceof ThrownPotion;
boolean flag1;
@@ -422,6 +453,7 @@ public class EnderMan extends Monster implements NeutralMob {
} else {
flag1 = flag && this.hurtWithCleanWater(source, (ThrownPotion) source.getDirectEntity(), amount);
+ if (!flag1 && this.level().purpurConfig.endermanIgnoreProjectiles) return super.hurt(source, amount); // Purpur
if (this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.INDIRECT)) { // Paper start
for (int i = 0; i < 64; ++i) {
if (this.teleport()) {
@@ -468,7 +500,7 @@ public class EnderMan extends Monster implements NeutralMob {
@Override
public boolean requiresCustomPersistence() {
- return super.requiresCustomPersistence() || this.getCarriedBlock() != null;
+ return super.requiresCustomPersistence() || (!this.level().purpurConfig.endermanDespawnEvenWithBlock && this.getCarriedBlock() != null); // Purpur
}
private static class EndermanFreezeWhenLookedAt extends Goal {
@@ -515,7 +547,16 @@ public class EnderMan extends Monster implements NeutralMob {
@Override
public boolean canUse() {
- return this.enderman.getCarriedBlock() == null ? false : (!this.enderman.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0);
+ if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur
+ // Purpur start
+ if (this.enderman.getCarriedBlock() == null) {
+ return false;
+ }
+ if (!this.enderman.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !this.enderman.level().purpurConfig.endermanBypassMobGriefing) {
+ return false;
+ }
+ return this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0;
+ // Purpur end
}
@Override
@@ -560,7 +601,16 @@ public class EnderMan extends Monster implements NeutralMob {
@Override
public boolean canUse() {
- return this.enderman.getCarriedBlock() != null ? false : (!this.enderman.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0);
+ if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur
+ // Purpur start
+ if (this.enderman.getCarriedBlock() != null) {
+ return false;
+ }
+ if (!this.enderman.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !this.enderman.level().purpurConfig.endermanBypassMobGriefing) {
+ return false;
+ }
+ return this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0;
+ // Purpur end
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/monster/Endermite.java b/src/main/java/net/minecraft/world/entity/monster/Endermite.java
index 29b8771ec85f07fcd9d9ba041c9a25a212009b16..e131949bd6aab4c8ff5cd4ea57280ad945ceef67 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Endermite.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Endermite.java
@@ -31,20 +31,63 @@ import net.minecraft.world.level.block.state.BlockState;
public class Endermite extends Monster {
private static final int MAX_LIFE = 2400;
public int life;
+ private boolean isPlayerSpawned; // Purpur
public Endermite(EntityType<? extends Endermite> type, Level world) {
super(type, world);
this.xpReward = 3;
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.endermiteRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.endermiteRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.endermiteControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.endermiteMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.endermiteTakeDamageFromWater;
+ }
+
+ public boolean isPlayerSpawned() {
+ return this.isPlayerSpawned;
+ }
+
+ public void setPlayerSpawned(boolean playerSpawned) {
+ this.isPlayerSpawned = playerSpawned;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.endermiteAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level()));
this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false));
this.goalSelector.addGoal(3, new WaterAvoidingRandomStrollGoal(this, 1.0D));
this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, (new HurtByTargetGoal(this)).setAlertOthers());
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
@@ -87,12 +130,14 @@ public class Endermite extends Monster {
public void readAdditionalSaveData(CompoundTag nbt) {
super.readAdditionalSaveData(nbt);
this.life = nbt.getInt("Lifetime");
+ this.isPlayerSpawned = nbt.getBoolean("PlayerSpawned"); // Purpur
}
@Override
public void addAdditionalSaveData(CompoundTag nbt) {
super.addAdditionalSaveData(nbt);
nbt.putInt("Lifetime", this.life);
+ nbt.putBoolean("PlayerSpawned", this.isPlayerSpawned); // Purpur
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/monster/Evoker.java b/src/main/java/net/minecraft/world/entity/monster/Evoker.java
index a6dde4e31790eaecff27135a4036627192c85702..4a87b76cc92220f6569b976871a86c8eab3c218d 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Evoker.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Evoker.java
@@ -48,10 +48,43 @@ public class Evoker extends SpellcasterIllager {
this.xpReward = 10;
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.evokerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.evokerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.evokerControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.evokerMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.evokerTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.evokerAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new Evoker.EvokerCastingSpellGoal());
this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 0.6D, 1.0D));
this.goalSelector.addGoal(4, new Evoker.EvokerSummonSpellGoal());
@@ -60,6 +93,7 @@ public class Evoker extends SpellcasterIllager {
this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D));
this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F));
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers());
this.targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(this, Player.class, true)).setUnseenMemoryTicks(300));
this.targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)).setUnseenMemoryTicks(300));
@@ -317,7 +351,7 @@ public class Evoker extends SpellcasterIllager {
return false;
} else if (Evoker.this.tickCount < this.nextAttackTickCount) {
return false;
- } else if (!Evoker.this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ } else if (!Evoker.this.level().purpurConfig.evokerBypassMobGriefing && !Evoker.this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
return false;
} else {
List<Sheep> list = Evoker.this.level().getNearbyEntities(Sheep.class, this.wololoTargeting, Evoker.this, Evoker.this.getBoundingBox().inflate(16.0D, 4.0D, 16.0D));
diff --git a/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java
index 592b0dae251800552a0771ec46b4b8532b63075d..b0aad5778a12d3f0f2ef806f856064c4da842fb3 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java
@@ -44,11 +44,47 @@ public class Ghast extends FlyingMob implements Enemy {
this.moveControl = new Ghast.GhastMoveControl(this);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.ghastRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ghastRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.ghastControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.ghastMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED);
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+ // Purpur end
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(5, new Ghast.RandomFloatAroundGoal(this));
this.goalSelector.addGoal(7, new Ghast.GhastLookGoal(this));
this.goalSelector.addGoal(7, new Ghast.GhastShootFireballGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving) -> {
return Math.abs(entityliving.getY() - this.getY()) <= 4.0D;
}));
@@ -96,6 +132,21 @@ public class Ghast extends FlyingMob implements Enemy {
}
}
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ghastMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.ghastTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.ghastAlwaysDropExp;
+ }
+
@Override
protected void defineSynchedData() {
super.defineSynchedData();
@@ -103,7 +154,7 @@ public class Ghast extends FlyingMob implements Enemy {
}
public static AttributeSupplier.Builder createAttributes() {
- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D);
+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur
}
@Override
@@ -160,7 +211,7 @@ public class Ghast extends FlyingMob implements Enemy {
return 2.6F;
}
- private static class GhastMoveControl extends MoveControl {
+ private static class GhastMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur
private final Ghast ghast;
private int floatDuration;
@@ -171,7 +222,7 @@ public class Ghast extends FlyingMob implements Enemy {
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur
if (this.operation == MoveControl.Operation.MOVE_TO) {
if (this.floatDuration-- <= 0) {
this.floatDuration += this.ghast.getRandom().nextInt(5) + 2;
diff --git a/src/main/java/net/minecraft/world/entity/monster/Giant.java b/src/main/java/net/minecraft/world/entity/monster/Giant.java
index 41004c28edb748e12c4f868aa07b4672891197c1..88b0dffe2c2b4da1406f218186d28f018ee879a3 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Giant.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Giant.java
@@ -1,17 +1,122 @@
package net.minecraft.world.entity.monster;
import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.Difficulty;
+import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.Pose;
+import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.ai.goal.FloatGoal;
+import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
+import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
+import net.minecraft.world.entity.ai.goal.MoveTowardsRestrictionGoal;
+import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
+import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
+import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
+import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
+import net.minecraft.world.entity.animal.IronGolem;
+import net.minecraft.world.entity.animal.Turtle;
+import net.minecraft.world.entity.npc.Villager;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
+import net.minecraft.world.level.ServerLevelAccessor;
+
+import javax.annotation.Nullable;
public class Giant extends Monster {
public Giant(EntityType<? extends Giant> type, Level world) {
super(type, world);
+ this.safeFallDistance = 10.0F; // Purpur
+ }
+
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.giantRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.giantRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.giantControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ if (level().purpurConfig.giantHaveAI) {
+ this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
+ this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D));
+ this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 16.0F));
+ this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
+ this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0D));
+ if (level().purpurConfig.giantHaveHostileAI) {
+ this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
+ this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class));
+ this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
+ this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Villager.class, false));
+ this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
+ this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, true));
+ }
+ }
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.giantTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.giantAlwaysDropExp;
+ }
+ // Purpur end
+
+ @Override
+ protected void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.giantMaxHealth);
+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.giantMovementSpeed);
+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.giantAttackDamage);
+ }
+
+ @Override
+ public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) {
+ SpawnGroupData groupData = super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt);
+ if (groupData == null) {
+ populateDefaultEquipmentSlots(this.random, difficulty);
+ populateDefaultEquipmentEnchantments(this.random, difficulty);
+ }
+ return groupData;
+ }
+
+ @Override
+ protected void populateDefaultEquipmentSlots(net.minecraft.util.RandomSource random, DifficultyInstance difficulty) {
+ super.populateDefaultEquipmentSlots(this.random, difficulty);
+ // TODO make configurable
+ if (random.nextFloat() < (level().getDifficulty() == Difficulty.HARD ? 0.1F : 0.05F)) {
+ this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.IRON_SWORD));
+ }
+ }
+
+ @Override
+ public float getJumpPower() {
+ // make giants jump as high as everything else relative to their size
+ // 1.0 makes bottom of feet about as high as their waist when they jump
+ return level().purpurConfig.giantJumpHeight;
}
@Override
@@ -25,6 +130,6 @@ public class Giant extends Monster {
@Override
public float getWalkTargetValue(BlockPos pos, LevelReader world) {
- return world.getPathfindingCostFromLightLevels(pos);
+ return super.getWalkTargetValue(pos, world); // Purpur - fix light requirements for natural spawns
}
}
diff --git a/src/main/java/net/minecraft/world/entity/monster/Guardian.java b/src/main/java/net/minecraft/world/entity/monster/Guardian.java
index 5e9b9e9f124ec6103b2e832ae94bc5b334a2c533..96338ed7952683f56d9a406d0956ce230581f146 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Guardian.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Guardian.java
@@ -69,15 +69,51 @@ public class Guardian extends Monster {
this.xpReward = 10;
this.setPathfindingMalus(BlockPathTypes.WATER, 0.0F);
this.moveControl = new Guardian.GuardianMoveControl(this);
+ // Purpur start
+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) {
+ @Override
+ public void setYawPitch(float yaw, float pitch) {
+ super.setYawPitch(yaw, pitch * 0.35F);
+ }
+ };
+ // Purpur end
this.clientSideTailAnimation = this.random.nextFloat();
this.clientSideTailAnimationO = this.clientSideTailAnimation;
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.guardianRidable;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.guardianControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.guardianMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.guardianTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.guardianAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
MoveTowardsRestrictionGoal pathfindergoalmovetowardsrestriction = new MoveTowardsRestrictionGoal(this, 1.0D);
this.randomStrollGoal = new RandomStrollGoal(this, 1.0D, 80);
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(4, this.guardianAttackGoal = new Guardian.GuardianAttackGoal(this)); // CraftBukkit - assign field
this.goalSelector.addGoal(5, pathfindergoalmovetowardsrestriction);
this.goalSelector.addGoal(7, this.randomStrollGoal);
@@ -86,6 +122,7 @@ public class Guardian extends Monster {
this.goalSelector.addGoal(9, new RandomLookAroundGoal(this));
this.randomStrollGoal.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK));
pathfindergoalmovetowardsrestriction.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 10, true, false, new Guardian.GuardianAttackSelector(this)));
}
@@ -351,7 +388,7 @@ public class Guardian extends Monster {
@Override
public void travel(Vec3 movementInput) {
if (this.isControlledByLocalInstance() && this.isInWater()) {
- this.moveRelative(0.1F, movementInput);
+ this.moveRelative(getRider() != null && this.isControllable() ? getSpeed() : 0.1F, movementInput); // Purpur
this.move(MoverType.SELF, this.getDeltaMovement());
this.setDeltaMovement(this.getDeltaMovement().scale(0.9D));
if (!this.isMoving() && this.getTarget() == null) {
@@ -363,7 +400,7 @@ public class Guardian extends Monster {
}
- private static class GuardianMoveControl extends MoveControl {
+ private static class GuardianMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur
private final Guardian guardian;
@@ -372,8 +409,17 @@ public class Guardian extends Monster {
this.guardian = guardian;
}
+ // Purpur start
@Override
- public void tick() {
+ public void purpurTick(Player rider) {
+ super.purpurTick(rider);
+ guardian.setDeltaMovement(guardian.getDeltaMovement().add(0.0D, 0.005D, 0.0D));
+ guardian.setMoving(guardian.getForwardMot() > 0.0F); // control tail speed
+ }
+ // Purpur end
+
+ @Override
+ public void vanillaTick() { // Purpur
if (this.operation == MoveControl.Operation.MOVE_TO && !this.guardian.getNavigation().isDone()) {
Vec3 vec3d = new Vec3(this.wantedX - this.guardian.getX(), this.wantedY - this.guardian.getY(), this.wantedZ - this.guardian.getZ());
double d0 = vec3d.length();
@@ -384,7 +430,7 @@ public class Guardian extends Monster {
this.guardian.setYRot(this.rotlerp(this.guardian.getYRot(), f, 90.0F));
this.guardian.yBodyRot = this.guardian.getYRot();
- float f1 = (float) (this.speedModifier * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED));
+ float f1 = (float) (this.getSpeedModifier() * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur
float f2 = Mth.lerp(0.125F, this.guardian.getSpeed(), f1);
this.guardian.setSpeed(f2);
diff --git a/src/main/java/net/minecraft/world/entity/monster/Husk.java b/src/main/java/net/minecraft/world/entity/monster/Husk.java
index efe8e6b895da1a81e5de42697fda58443cf15a59..cd78fbca21112a2354bc40747531e5202bbd5f20 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Husk.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Husk.java
@@ -20,6 +20,59 @@ public class Husk extends Zombie {
public Husk(EntityType<? extends Husk> type, Level world) {
super(type, world);
+ this.setShouldBurnInDay(false); // Purpur
+ }
+
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.huskRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.huskRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.huskControllable;
+ }
+ // 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<Husk> type, ServerLevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
@@ -28,7 +81,7 @@ public class Husk extends Zombie {
@Override
public boolean isSunSensitive() {
- return false;
+ return this.shouldBurnInDay; // Purpur - moved to LivingEntity - keep methods for ABI compatibility
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java
index 63fce7e3d9f59f36e29bc827a46396d73143bb8b..f9cddf46d1dbcabc738842ba039daa76bb6f3eb5 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java
@@ -59,10 +59,45 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob {
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.illusionerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.illusionerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.illusionerControllable;
+ }
+ // Purpur end
+
+ @Override
+ protected void initAttributes() {
+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.illusionerMovementSpeed);
+ this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(this.level().purpurConfig.illusionerFollowRange);
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.illusionerMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.illusionerTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.illusionerAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new SpellcasterIllager.SpellcasterCastingSpellGoal());
this.goalSelector.addGoal(4, new Illusioner.IllusionerMirrorSpellGoal());
this.goalSelector.addGoal(5, new Illusioner.IllusionerBlindnessSpellGoal());
@@ -70,6 +105,7 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob {
this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D));
this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F));
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers());
this.targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(this, Player.class, true)).setUnseenMemoryTicks(300));
this.targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)).setUnseenMemoryTicks(300));
diff --git a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java
index 2858ea5562d06c11e5c7337a2b123f9be7a3f62e..1ad97267394d3717b1871336193cdc91f3ffb276 100644
--- a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java
+++ b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java
@@ -25,6 +25,58 @@ public class MagmaCube extends Slime {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.magmaCubeRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.magmaCubeRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.magmaCubeControllable;
+ }
+
+ @Override
+ public float getJumpPower() {
+ return 0.42F * this.getBlockJumpFactor(); // from EntityLiving
+ }
+ // Purpur end
+
+ @Override
+ protected String getMaxHealthEquation() {
+ return level().purpurConfig.magmaCubeMaxHealth;
+ }
+
+ @Override
+ protected String getAttackDamageEquation() {
+ return level().purpurConfig.magmaCubeAttackDamage;
+ }
+
+ @Override
+ protected java.util.Map<Integer, Double> getMaxHealthCache() {
+ return level().purpurConfig.magmaCubeMaxHealthCache;
+ }
+
+ @Override
+ protected java.util.Map<Integer, Double> getAttackDamageCache() {
+ return level().purpurConfig.magmaCubeAttackDamageCache;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.magmaCubeTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.magmaCubeAlwaysDropExp;
+ }
+
public static AttributeSupplier.Builder createAttributes() {
return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, (double)0.2F);
}
@@ -70,11 +122,12 @@ public class MagmaCube extends Slime {
}
@Override
- protected void jumpFromGround() {
+ public void jumpFromGround() { // Purpur - protected -> public
Vec3 vec3 = this.getDeltaMovement();
float f = (float)this.getSize() * 0.1F;
this.setDeltaMovement(vec3.x, (double)(this.getJumpPower() + f), vec3.z);
this.hasImpulse = true;
+ this.actualJump = false; // Purpur
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java
index 55c245d0dfa369dc6de2197ae37335fba4fae4ae..c9b40515f4c2ff1eedfc9510930c3baebc078ebd 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Monster.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java
@@ -89,6 +89,14 @@ public abstract class Monster extends PathfinderMob implements Enemy {
}
public static boolean isDarkEnoughToSpawn(ServerLevelAccessor world, BlockPos pos, RandomSource random) {
+ // Purpur start
+ if (!world.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce || !world.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce) {
+ net.minecraft.world.level.block.state.BlockState spawnBlock = world.getBlockState(pos.below());
+ if ((!world.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.PACKED_ICE)) || (!world.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.BLUE_ICE))) {
+ return false;
+ }
+ }
+ // Purpur end
if (world.getBrightness(LightLayer.SKY, pos) > random.nextInt(32)) {
return false;
} else {
diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java
index 4a132c3eff6978e927bcd4df56b9ce0306af6d19..17638b9d3340c86528a8ae597712c7590b98dba6 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java
@@ -49,6 +49,8 @@ public class Phantom extends FlyingMob implements Enemy {
Vec3 moveTargetPoint;
public BlockPos anchorPoint;
Phantom.AttackPhase attackPhase;
+ private static final net.minecraft.world.item.crafting.Ingredient TORCH = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.item.Items.TORCH, net.minecraft.world.item.Items.SOUL_TORCH); // Purpur
+ Vec3 crystalPosition; // Purpur
public Phantom(EntityType<? extends Phantom> type, Level world) {
super(type, world);
@@ -58,6 +60,92 @@ public class Phantom extends FlyingMob implements Enemy {
this.xpReward = 5;
this.moveControl = new Phantom.PhantomMoveControl(this);
this.lookControl = new Phantom.PhantomLookControl(this);
+ this.setShouldBurnInDay(true); // Purpur
+ }
+
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.phantomRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.phantomRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.phantomControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.phantomMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable() && !onGround) {
+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED);
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, speed, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+
+ public static net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder createAttributes() {
+ return Monster.createMonsterAttributes().add(Attributes.FLYING_SPEED, 3.0D);
+ }
+
+ @Override
+ public boolean onSpacebar() {
+ if (getRider() != null && getRider().getBukkitEntity().hasPermission("allow.special.phantom")) {
+ shoot();
+ }
+ return false;
+ }
+
+ public boolean shoot() {
+ org.bukkit.Location loc = ((org.bukkit.entity.LivingEntity) getBukkitEntity()).getEyeLocation();
+ loc.setPitch(-loc.getPitch());
+ org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(100).add(loc.toVector());
+
+ org.purpurmc.purpur.entity.PhantomFlames flames = new org.purpurmc.purpur.entity.PhantomFlames(level(), this);
+ flames.canGrief = level().purpurConfig.phantomAllowGriefing;
+ flames.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), 1.0F, 5.0F);
+ level().addFreshEntity(flames);
+ return true;
+ }
+
+ @Override
+ protected void dropFromLootTable(DamageSource damageSource, boolean causedByPlayer) {
+ boolean dropped = false;
+ if (lastHurtByPlayer == null && damageSource.getEntity() instanceof net.minecraft.world.entity.boss.enderdragon.EndCrystal) {
+ if (random.nextInt(5) < 1) {
+ dropped = spawnAtLocation(new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.PHANTOM_MEMBRANE)) != null;
+ }
+ }
+ if (!dropped) {
+ super.dropFromLootTable(damageSource, causedByPlayer);
+ }
+ }
+
+ public boolean isCirclingCrystal() {
+ return crystalPosition != null;
+ }
+ // Purpur end
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.phantomTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.phantomAlwaysDropExp;
}
@Override
@@ -72,9 +160,17 @@ public class Phantom extends FlyingMob implements Enemy {
@Override
protected void registerGoals() {
- this.goalSelector.addGoal(1, new Phantom.PhantomAttackStrategyGoal());
- this.goalSelector.addGoal(2, new Phantom.PhantomSweepAttackGoal());
- this.goalSelector.addGoal(3, new Phantom.PhantomCircleAroundAnchorGoal());
+ // Purpur start
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
+ if (level().purpurConfig.phantomOrbitCrystalRadius > 0) {
+ this.goalSelector.addGoal(1, new FindCrystalGoal(this));
+ this.goalSelector.addGoal(2, new OrbitCrystalGoal(this));
+ }
+ this.goalSelector.addGoal(3, new Phantom.PhantomAttackStrategyGoal());
+ this.goalSelector.addGoal(4, new Phantom.PhantomSweepAttackGoal());
+ this.goalSelector.addGoal(5, new Phantom.PhantomCircleAroundAnchorGoal());
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
+ // Purpur end
this.targetSelector.addGoal(1, new Phantom.PhantomAttackPlayerTargetGoal());
}
@@ -90,7 +186,10 @@ public class Phantom extends FlyingMob implements Enemy {
private void updatePhantomSizeInfo() {
this.refreshDimensions();
- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) (6 + this.getPhantomSize()));
+ // Purpur start
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomMaxHealth, () -> this.level().purpurConfig.phantomMaxHealthCache, () -> 20.0D));
+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomAttackDamage, () -> this.level().purpurConfig.phantomAttackDamageCache, () -> (double) 6 + this.getPhantomSize()));
+ // Purpur end
}
public int getPhantomSize() {
@@ -120,6 +219,21 @@ public class Phantom extends FlyingMob implements Enemy {
return true;
}
+ private double getFromCache(java.util.function.Supplier<String> equation, java.util.function.Supplier<java.util.Map<Integer, Double>> cache, java.util.function.Supplier<Double> defaultValue) {
+ int size = getPhantomSize();
+ Double value = cache.get().get(size);
+ if (value == null) {
+ try {
+ value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue();
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ value = defaultValue.get();
+ }
+ cache.get().put(size, value);
+ }
+ return value;
+ }
+
@Override
public void tick() {
super.tick();
@@ -140,14 +254,12 @@ public class Phantom extends FlyingMob implements Enemy {
this.level().addParticle(ParticleTypes.MYCELIUM, this.getX() - (double) f2, this.getY() + (double) f4, this.getZ() - (double) f3, 0.0D, 0.0D, 0.0D);
}
+ if (level().purpurConfig.phantomFlamesOnSwoop && attackPhase == AttackPhase.SWOOP) shoot(); // Purpur
}
@Override
public void aiStep() {
- if (this.isAlive() && shouldBurnInDay && this.isSunBurnTick()) { // Paper - Configurable Burning
- this.setSecondsOnFire(8);
- }
-
+ // Purpur - moved down to shouldBurnInDay()
super.aiStep();
}
@@ -159,7 +271,11 @@ public class Phantom extends FlyingMob implements Enemy {
@Override
public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) {
this.anchorPoint = this.blockPosition().above(5);
- this.setPhantomSize(0);
+ // Purpur start
+ int min = world.getLevel().purpurConfig.phantomMinSize;
+ int max = world.getLevel().purpurConfig.phantomMaxSize;
+ this.setPhantomSize(min == max ? min : world.getRandom().nextInt(max + 1 - min) + min);
+ // Purpur end
return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt);
}
@@ -175,7 +291,7 @@ public class Phantom extends FlyingMob implements Enemy {
if (nbt.hasUUID("Paper.SpawningEntity")) {
this.spawningEntity = nbt.getUUID("Paper.SpawningEntity");
}
- if (nbt.contains("Paper.ShouldBurnInDay")) {
+ if (false && nbt.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity
this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay");
}
// Paper end
@@ -192,7 +308,7 @@ public class Phantom extends FlyingMob implements Enemy {
if (this.spawningEntity != null) {
nbt.putUUID("Paper.SpawningEntity", this.spawningEntity);
}
- nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay);
+ // nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); // Purpur - implemented in LivingEntity
// Paper end
}
@@ -258,8 +374,14 @@ public class Phantom extends FlyingMob implements Enemy {
}
public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; }
- private boolean shouldBurnInDay = true;
- public boolean shouldBurnInDay() { return shouldBurnInDay; }
+ // private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity - keep methods for ABI compatibility
+ // Purpur start
+ public boolean shouldBurnInDay() {
+ boolean burnFromDaylight = this.shouldBurnInDay && this.level().purpurConfig.phantomBurnInDaylight;
+ boolean burnFromLightSource = this.level().purpurConfig.phantomBurnInLight > 0 && this.level().getMaxLocalRawBrightness(blockPosition()) >= this.level().purpurConfig.phantomBurnInLight;
+ return burnFromDaylight || burnFromLightSource;
+ }
+ // Purpur End
public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
// Paper end
private static enum AttackPhase {
@@ -269,7 +391,125 @@ public class Phantom extends FlyingMob implements Enemy {
private AttackPhase() {}
}
- private class PhantomMoveControl extends MoveControl {
+ // Purpur start
+ class FindCrystalGoal extends Goal {
+ private final Phantom phantom;
+ private net.minecraft.world.entity.boss.enderdragon.EndCrystal crystal;
+ private Comparator<net.minecraft.world.entity.boss.enderdragon.EndCrystal> 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<net.minecraft.world.entity.boss.enderdragon.EndCrystal> crystals = level().getEntitiesOfClass(net.minecraft.world.entity.boss.enderdragon.EndCrystal.class, phantom.getBoundingBox().inflate(range));
+ if (crystals.isEmpty()) {
+ return false;
+ }
+ crystals.sort(comparator);
+ crystal = crystals.get(0);
+ if (phantom.distanceToSqr(crystal) > range * range) {
+ crystal = null;
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean canContinueToUse() {
+ if (crystal == null || !crystal.isAlive()) {
+ return false;
+ }
+ double range = maxTargetRange();
+ return phantom.distanceToSqr(crystal) <= (range * range) * 2;
+ }
+
+ @Override
+ public void start() {
+ phantom.crystalPosition = new Vec3(crystal.getX(), crystal.getY() + (phantom.random.nextInt(10) + 10), crystal.getZ());
+ }
+
+ @Override
+ public void stop() {
+ crystal = null;
+ phantom.crystalPosition = null;
+ super.stop();
+ }
+
+ private double maxTargetRange() {
+ return phantom.level().purpurConfig.phantomOrbitCrystalRadius;
+ }
+ }
+
+ class OrbitCrystalGoal extends Goal {
+ private final Phantom phantom;
+ private float offset;
+ private float radius;
+ private float verticalChange;
+ private float direction;
+
+ OrbitCrystalGoal(Phantom phantom) {
+ this.phantom = phantom;
+ this.setFlags(EnumSet.of(Flag.MOVE));
+ }
+
+ @Override
+ public boolean canUse() {
+ return phantom.isCirclingCrystal();
+ }
+
+ @Override
+ public void start() {
+ this.radius = 5.0F + phantom.random.nextFloat() * 10.0F;
+ this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F;
+ this.direction = phantom.random.nextBoolean() ? 1.0F : -1.0F;
+ updateOffset();
+ }
+
+ @Override
+ public void tick() {
+ if (phantom.random.nextInt(350) == 0) {
+ this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F;
+ }
+ if (phantom.random.nextInt(250) == 0) {
+ ++this.radius;
+ if (this.radius > 15.0F) {
+ this.radius = 5.0F;
+ this.direction = -this.direction;
+ }
+ }
+ if (phantom.random.nextInt(450) == 0) {
+ this.offset = phantom.random.nextFloat() * 2.0F * 3.1415927F;
+ updateOffset();
+ }
+ if (phantom.moveTargetPoint.distanceToSqr(phantom.getX(), phantom.getY(), phantom.getZ()) < 4.0D) {
+ updateOffset();
+ }
+ if (phantom.moveTargetPoint.y < phantom.getY() && !phantom.level().isEmptyBlock(new BlockPos(phantom).below(1))) {
+ this.verticalChange = Math.max(1.0F, this.verticalChange);
+ updateOffset();
+ }
+ if (phantom.moveTargetPoint.y > phantom.getY() && !phantom.level().isEmptyBlock(new BlockPos(phantom).above(1))) {
+ this.verticalChange = Math.min(-1.0F, this.verticalChange);
+ updateOffset();
+ }
+ }
+
+ private void updateOffset() {
+ this.offset += this.direction * 15.0F * 0.017453292F;
+ phantom.moveTargetPoint = phantom.crystalPosition.add(
+ this.radius * Mth.cos(this.offset),
+ -4.0F + this.verticalChange,
+ this.radius * Mth.sin(this.offset));
+ }
+ }
+ // Purpur end
+
+ private class PhantomMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur
private float speed = 0.1F;
@@ -277,8 +517,19 @@ public class Phantom extends FlyingMob implements Enemy {
super(entity);
}
+ // Purpur start
+ public void purpurTick(Player rider) {
+ if (!Phantom.this.onGround) {
+ // phantom is always in motion when flying
+ // TODO - FIX THIS
+ // rider.setForward(1.0F);
+ }
+ super.purpurTick(rider);
+ }
+ // Purpur end
+
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur
if (Phantom.this.horizontalCollision) {
Phantom.this.setYRot(Phantom.this.getYRot() + 180.0F);
this.speed = 0.1F;
@@ -324,14 +575,20 @@ public class Phantom extends FlyingMob implements Enemy {
}
}
- private class PhantomLookControl extends LookControl {
+ private class PhantomLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur
public PhantomLookControl(Mob entity) {
super(entity);
}
+ // Purpur start
+ public void purpurTick(Player rider) {
+ setYawPitch(rider.getYRot(), -rider.xRotO * 0.75F);
+ }
+ // Purpur end
+
@Override
- public void tick() {}
+ public void vanillaTick() {} // Purpur
}
private class PhantomBodyRotationControl extends BodyRotationControl {
@@ -418,6 +675,12 @@ public class Phantom extends FlyingMob implements Enemy {
return false;
} else if (!entityliving.isAlive()) {
return false;
+ // Purpur start
+ } else if (level().purpurConfig.phantomBurnInLight > 0 && level().getLightEmission(new BlockPos(Phantom.this)) >= level().purpurConfig.phantomBurnInLight) {
+ return false;
+ } else if (level().purpurConfig.phantomIgnorePlayersWithTorch && (TORCH.test(entityliving.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(entityliving.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND)))) {
+ return false;
+ // Purpur end
} else {
if (entityliving instanceof Player) {
Player entityhuman = (Player) entityliving;
@@ -563,6 +826,7 @@ public class Phantom extends FlyingMob implements Enemy {
this.nextScanTick = reducedTickDelay(60);
List<Player> list = Phantom.this.level().getNearbyPlayers(this.attackTargeting, Phantom.this, Phantom.this.getBoundingBox().inflate(16.0D, 64.0D, 16.0D));
+ if (level().purpurConfig.phantomIgnorePlayersWithTorch) list.removeIf(human -> TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND)));// Purpur
if (!list.isEmpty()) {
list.sort(Comparator.comparing((Entity e) -> { return e.getY(); }).reversed()); // CraftBukkit - decompile error
Iterator iterator = list.iterator();
diff --git a/src/main/java/net/minecraft/world/entity/monster/Pillager.java b/src/main/java/net/minecraft/world/entity/monster/Pillager.java
index cec545c3baa6599d47b9cf1a4b97de8771062a22..06d52d8b61abc4dbbdc953bfed2e688be377b3cc 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Pillager.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Pillager.java
@@ -62,15 +62,49 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.pillagerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.pillagerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.pillagerControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pillagerMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.pillagerTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.pillagerAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, 10.0F));
this.goalSelector.addGoal(3, new RangedCrossbowAttackGoal<>(this, 1.0D, 8.0F));
this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D));
this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 15.0F, 1.0F));
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 15.0F));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, Raider.class)).setAlertOthers());
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false));
diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java
index e87c6c8ab585d6b0b38bbb8e42c0082e38f03250..0211b50ce4b48a26698f1ece8b593747d170178e 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java
@@ -68,14 +68,54 @@ public class Ravager extends Raider {
this.setPathfindingMalus(BlockPathTypes.LEAVES, 0.0F);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.ravagerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ravagerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.ravagerControllable;
+ }
+
+ @Override
+ public void onMount(Player rider) {
+ super.onMount(rider);
+ getNavigation().stop();
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ravagerMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.ravagerTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.ravagerAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(4, new Ravager.RavagerMeleeAttackGoal());
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.4D));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(2, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers());
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true));
this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true, (entityliving) -> {
@@ -153,7 +193,7 @@ public class Ravager extends Raider {
@Override
public void aiStep() {
super.aiStep();
- if (this.isAlive()) {
+ if (this.isAlive() && (getRider() == null || !this.isControllable())) { // Purpur
if (this.isImmobile()) {
this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.0D);
} else {
@@ -163,7 +203,7 @@ public class Ravager extends Raider {
this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(Mth.lerp(0.1D, d1, d0));
}
- if (this.horizontalCollision && this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (this.horizontalCollision && (this.level().purpurConfig.ravagerBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur
boolean flag = false;
AABB axisalignedbb = this.getBoundingBox().inflate(0.2D);
Iterator iterator = BlockPos.betweenClosed(Mth.floor(axisalignedbb.minX), Mth.floor(axisalignedbb.minY), Mth.floor(axisalignedbb.minZ), Mth.floor(axisalignedbb.maxX), Mth.floor(axisalignedbb.maxY), Mth.floor(axisalignedbb.maxZ)).iterator();
@@ -173,7 +213,7 @@ public class Ravager extends Raider {
BlockState iblockdata = this.level().getBlockState(blockposition);
Block block = iblockdata.getBlock();
- if (block instanceof LeavesBlock) {
+ if (this.level().purpurConfig.ravagerGriefableBlocks.contains(block)) { // Purpur
// CraftBukkit start
if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
continue;
diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java
index d2073de07bdad71b20e05046577b16649123967b..3e2c60a301d4c66c85cd8ff812331005a667dc4e 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java
@@ -22,6 +22,8 @@ import net.minecraft.tags.DamageTypeTags;
import net.minecraft.util.Mth;
import net.minecraft.world.Difficulty;
import net.minecraft.world.DifficultyInstance;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityDimensions;
@@ -50,6 +52,8 @@ import net.minecraft.world.entity.projectile.AbstractArrow;
import net.minecraft.world.entity.projectile.ShulkerBullet;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.item.DyeColor;
+import net.minecraft.world.item.DyeItem;
+import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.Blocks;
@@ -98,12 +102,59 @@ public class Shulker extends AbstractGolem implements VariantHolder<Optional<Dye
this.lookControl = new Shulker.ShulkerLookControl(this);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.shulkerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.shulkerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.shulkerControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.shulkerMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.shulkerTakeDamageFromWater;
+ }
+
+ @Override
+ protected InteractionResult mobInteract(Player player, InteractionHand hand) {
+ ItemStack itemstack = player.getItemInHand(hand);
+ if (player.level().purpurConfig.shulkerChangeColorWithDye && itemstack.getItem() instanceof DyeItem dye && dye.getDyeColor() != this.getColor()) {
+ this.setVariant(Optional.of(dye.getDyeColor()));
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
+ return InteractionResult.SUCCESS;
+ }
+ return super.mobInteract(player, hand);
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.shulkerAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new LookAtPlayerGoal(this, Player.class, 8.0F, 0.02F, true));
this.goalSelector.addGoal(4, new Shulker.ShulkerAttackGoal());
this.goalSelector.addGoal(7, new Shulker.ShulkerPeekGoal());
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[]{this.getClass()})).setAlertOthers());
this.targetSelector.addGoal(2, new Shulker.ShulkerNearestAttackGoal(this));
this.targetSelector.addGoal(3, new Shulker.ShulkerDefenseAttackGoal(this));
@@ -482,12 +533,21 @@ public class Shulker extends AbstractGolem implements VariantHolder<Optional<Dye
Vec3 vec3d = this.position();
AABB axisalignedbb = this.getBoundingBox();
- if (!this.isClosed() && this.teleportSomewhere()) {
- int i = this.level().getEntities((EntityTypeTest) EntityType.SHULKER, axisalignedbb.inflate(8.0D), Entity::isAlive).size();
- float f = (float) (i - 1) / 5.0F;
-
- if (this.level().random.nextFloat() >= f) {
+ if ((!this.level().purpurConfig.shulkerSpawnFromBulletRequireOpenLid || !this.isClosed()) && this.teleportSomewhere()) {
+ // Purpur start
+ float chance = this.level().purpurConfig.shulkerSpawnFromBulletBaseChance;
+ if (!this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation.isBlank()) {
+ int nearby = this.level().getEntities((EntityTypeTest) EntityType.SHULKER, axisalignedbb.inflate(this.level().purpurConfig.shulkerSpawnFromBulletNearbyRange), Entity::isAlive).size();
+ try {
+ chance -= ((Number) scriptEngine.eval("let nearby = " + nearby + "; " + this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation)).floatValue();
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ chance -= (nearby - 1) / 5.0F;
+ }
+ }
+ if (this.level().random.nextFloat() <= chance) {
Shulker entityshulker = (Shulker) EntityType.SHULKER.create(this.level());
+ // Purpur end
if (entityshulker != null) {
entityshulker.setVariant(this.getVariant());
@@ -599,7 +659,7 @@ public class Shulker extends AbstractGolem implements VariantHolder<Optional<Dye
@Override
public Optional<DyeColor> getVariant() {
- return Optional.ofNullable(this.getColor());
+ return Optional.ofNullable(this.level().purpurConfig.shulkerSpawnFromBulletRandomColor ? DyeColor.random(this.level().random) : this.getColor()); // Purpur
}
@Nullable
@@ -609,7 +669,7 @@ public class Shulker extends AbstractGolem implements VariantHolder<Optional<Dye
return b0 != 16 && b0 <= 15 ? DyeColor.byId(b0) : null;
}
- private class ShulkerLookControl extends LookControl {
+ private class ShulkerLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur
public ShulkerLookControl(Mob entity) {
super(entity);
diff --git a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java
index 8d1f99f95a08eac98c6a03c6e534fc1997f8fe71..83641c7cf51453e98809900e23f44a68018555ff 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java
@@ -45,14 +45,48 @@ public class Silverfish extends Monster {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.silverfishRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.silverfishRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.silverfishControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.silverfishMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.silverfishTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.silverfishAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
this.friendsGoal = new Silverfish.SilverfishWakeUpFriendsGoal(this);
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(3, this.friendsGoal);
this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, false));
this.goalSelector.addGoal(5, new Silverfish.SilverfishMergeWithStoneGoal(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));
}
@@ -184,7 +218,7 @@ public class Silverfish extends Monster {
continue;
}
// CraftBukkit end
- if (world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ if (world.purpurConfig.silverfishBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
world.destroyBlock(blockposition1, true, this.silverfish);
} else {
world.setBlock(blockposition1, ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)), 3);
@@ -222,7 +256,7 @@ public class Silverfish extends Monster {
} else {
RandomSource randomsource = this.mob.getRandom();
- if (this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && randomsource.nextInt(reducedTickDelay(10)) == 0) {
+ if ((this.mob.level().purpurConfig.silverfishBypassMobGriefing || this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && randomsource.nextInt(reducedTickDelay(10)) == 0) { // Purpur
this.selectedDirection = Direction.getRandom(randomsource);
BlockPos blockposition = BlockPos.containing(this.mob.getX(), this.mob.getY() + 0.5D, this.mob.getZ()).relative(this.selectedDirection);
BlockState iblockdata = this.mob.level().getBlockState(blockposition);
diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java
index 8b818a7cb835512c4bd2ea9641d4bfd904150332..f4a47ac2c86cc95178922cce7320ba1ef5121b57 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java
@@ -14,6 +14,16 @@ import net.minecraft.world.item.Items;
import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.Level;
+// Purpur start
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.block.Blocks;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.InteractionResult;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.core.particles.ParticleTypes;
+// Purpur end
+
public class Skeleton extends AbstractSkeleton {
private static final int TOTAL_CONVERSION_TIME = 300;
@@ -26,6 +36,38 @@ public class Skeleton extends AbstractSkeleton {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.skeletonRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.skeletonRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.skeletonControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.skeletonMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.skeletonTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.skeletonAlwaysDropExp;
+ }
+
@Override
protected void defineSynchedData() {
super.defineSynchedData();
@@ -142,4 +184,67 @@ public class Skeleton extends AbstractSkeleton {
}
}
+
+ // Purpur start
+ private int witherRosesFed = 0;
+
+ @Override
+ public InteractionResult mobInteract(Player player, InteractionHand hand) {
+ ItemStack stack = player.getItemInHand(hand);
+
+ if (level().purpurConfig.skeletonFeedWitherRoses > 0 && this.getType() != EntityType.WITHER_SKELETON && stack.getItem() == Blocks.WITHER_ROSE.asItem()) {
+ return this.feedWitherRose(player, stack);
+ }
+
+ return super.mobInteract(player, hand);
+ }
+
+ private InteractionResult feedWitherRose(Player player, ItemStack stack) {
+ if (++witherRosesFed < level().purpurConfig.skeletonFeedWitherRoses) {
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(1);
+ }
+ return InteractionResult.CONSUME;
+ }
+
+ WitherSkeleton skeleton = EntityType.WITHER_SKELETON.create(level());
+ if (skeleton == null) {
+ return InteractionResult.PASS;
+ }
+
+ skeleton.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
+ skeleton.setHealth(this.getHealth());
+ skeleton.setAggressive(this.isAggressive());
+ skeleton.copyPosition(this);
+ skeleton.setYBodyRot(this.yBodyRot);
+ skeleton.setYHeadRot(this.getYHeadRot());
+ skeleton.yRotO = this.yRotO;
+ skeleton.xRotO = this.xRotO;
+
+ if (this.hasCustomName()) {
+ skeleton.setCustomName(this.getCustomName());
+ }
+
+ if (CraftEventFactory.callEntityTransformEvent(this, skeleton, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) {
+ return InteractionResult.PASS;
+ }
+
+ if (!new com.destroystokyo.paper.event.entity.EntityTransformedEvent(this.getBukkitEntity(), skeleton.getBukkitEntity(), com.destroystokyo.paper.event.entity.EntityTransformedEvent.TransformedReason.INFECTED).callEvent()) {
+ return InteractionResult.PASS;
+ }
+
+ this.level().addFreshEntity(skeleton);
+ this.remove(RemovalReason.DISCARDED);
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(1);
+ }
+
+ for (int i = 0; i < 15; ++i) {
+ ((ServerLevel) level()).sendParticles(((ServerLevel) level()).players(), null, ParticleTypes.HAPPY_VILLAGER,
+ getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1,
+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true);
+ }
+ return InteractionResult.SUCCESS;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java
index 343433158507451152e5b2fc5e5fd0b0e6b229db..467337542551dedc05d922bb3a37b811aeef4d7b 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java
@@ -64,6 +64,7 @@ public class Slime extends Mob implements Enemy {
public float squish;
public float oSquish;
private boolean wasOnGround;
+ protected boolean actualJump; // Purpur
public Slime(EntityType<? extends Slime> type, Level world) {
super(type, world);
@@ -71,12 +72,89 @@ public class Slime extends Mob implements Enemy {
this.moveControl = new Slime.SlimeMoveControl(this);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.slimeRidable;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.slimeTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.slimeAlwaysDropExp;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.slimeRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.slimeControllable;
+ }
+
+ @Override
+ public float getJumpPower() {
+ float height = super.getJumpPower();
+ return getRider() != null && this.isControllable() && actualJump ? height * 1.5F : height;
+ }
+
+ @Override
+ public boolean onSpacebar() {
+ if (onGround && getRider() != null && this.isControllable()) {
+ actualJump = true;
+ if (getRider().getForwardMot() == 0 || getRider().getStrafeMot() == 0) {
+ jumpFromGround(); // jump() here if not moving
+ }
+ }
+ return true; // do not jump() in wasd controller, let vanilla controller handle
+ }
+
+ protected String getMaxHealthEquation() {
+ return level().purpurConfig.slimeMaxHealth;
+ }
+
+ protected String getAttackDamageEquation() {
+ return level().purpurConfig.slimeAttackDamage;
+ }
+
+ protected java.util.Map<Integer, Double> getMaxHealthCache() {
+ return level().purpurConfig.slimeMaxHealthCache;
+ }
+
+ protected java.util.Map<Integer, Double> getAttackDamageCache() {
+ return level().purpurConfig.slimeAttackDamageCache;
+ }
+
+ protected double getFromCache(java.util.function.Supplier<String> equation, java.util.function.Supplier<java.util.Map<Integer, Double>> cache, java.util.function.Supplier<Double> defaultValue) {
+ int size = getSize();
+ Double value = cache.get().get(size);
+ if (value == null) {
+ try {
+ value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue();
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ value = defaultValue.get();
+ }
+ cache.get().put(size, value);
+ }
+ return value;
+ }
+ // Purpur end
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new Slime.SlimeFloatGoal(this));
this.goalSelector.addGoal(2, new Slime.SlimeAttackGoal(this));
this.goalSelector.addGoal(3, new Slime.SlimeRandomDirectionGoal(this));
this.goalSelector.addGoal(5, new Slime.SlimeKeepOnJumpingGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving) -> {
return Math.abs(entityliving.getY() - this.getY()) <= 4.0D;
}));
@@ -96,9 +174,9 @@ public class Slime extends Mob implements Enemy {
this.entityData.set(Slime.ID_SIZE, j);
this.reapplyPosition();
this.refreshDimensions();
- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double) (j * j));
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(this::getMaxHealthEquation, this::getMaxHealthCache, () -> (double) size * size)); // Purpur
this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue((double) (0.2F + 0.1F * (float) j));
- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) j);
+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(this::getAttackDamageEquation, this::getAttackDamageCache, () -> (double) j)); // Purpur
if (heal) {
this.setHealth(this.getMaxHealth());
}
@@ -368,11 +446,12 @@ public class Slime extends Mob implements Enemy {
}
@Override
- protected void jumpFromGround() {
+ public void jumpFromGround() { // Purpur - protected -> public
Vec3 vec3d = this.getDeltaMovement();
this.setDeltaMovement(vec3d.x, (double) this.getJumpPower(), vec3d.z);
this.hasImpulse = true;
+ this.actualJump = false; // Purpur
}
@Nullable
@@ -406,7 +485,7 @@ public class Slime extends Mob implements Enemy {
return super.getDimensions(pose).scale(0.255F * (float) this.getSize());
}
- private static class SlimeMoveControl extends MoveControl {
+ private static class SlimeMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur
private float yRot;
private int jumpDelay;
@@ -425,21 +504,33 @@ public class Slime extends Mob implements Enemy {
}
public void setWantedMovement(double speed) {
- this.speedModifier = speed;
+ this.setSpeedModifier(speed); // Purpur
this.operation = MoveControl.Operation.MOVE_TO;
}
@Override
public void tick() {
+ // Purpur start
+ if (slime.getRider() != null && slime.isControllable()) {
+ purpurTick(slime.getRider());
+ if (slime.getForwardMot() != 0 || slime.getStrafeMot() != 0) {
+ if (jumpDelay > 10) {
+ jumpDelay = 6;
+ }
+ } else {
+ jumpDelay = 20;
+ }
+ } else {
+ // Purpur end
this.mob.setYRot(this.rotlerp(this.mob.getYRot(), this.yRot, 90.0F));
this.mob.yHeadRot = this.mob.getYRot();
this.mob.yBodyRot = this.mob.getYRot();
- if (this.operation != MoveControl.Operation.MOVE_TO) {
+ } if ((slime.getRider() == null || !slime.isControllable()) && this.operation != MoveControl.Operation.MOVE_TO) { // Purpur
this.mob.setZza(0.0F);
} else {
this.operation = MoveControl.Operation.WAIT;
if (this.mob.onGround()) {
- this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED)));
+ this.mob.setSpeed((float) (this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur
if (this.jumpDelay-- <= 0) {
this.jumpDelay = this.slime.getJumpDelay();
if (this.isAggressive) {
@@ -456,7 +547,7 @@ public class Slime extends Mob implements Enemy {
this.mob.setSpeed(0.0F);
}
} else {
- this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED)));
+ this.mob.setSpeed((float) (this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur
}
}
diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java
index 05432184077752b1d0cb764a5e39ed875748b2d6..b3ab0f8cdbd678970e39e89c2062772374466b67 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Spider.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java
@@ -51,14 +51,48 @@ public class Spider extends Monster {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.spiderRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.spiderRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.spiderControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.spiderMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.spiderTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.spiderAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(3, new LeapAtTargetGoal(this, 0.4F));
this.goalSelector.addGoal(4, new Spider.SpiderAttackGoal(this));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0]));
this.targetSelector.addGoal(2, new Spider.SpiderTargetGoal<>(this, Player.class));
this.targetSelector.addGoal(3, new Spider.SpiderTargetGoal<>(this, IronGolem.class));
diff --git a/src/main/java/net/minecraft/world/entity/monster/Stray.java b/src/main/java/net/minecraft/world/entity/monster/Stray.java
index 118b636a44e4b062e812e433f603b039276337da..a95e983032b3d3d125a39a46700b7db9dc69f307 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Stray.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Stray.java
@@ -21,6 +21,38 @@ public class Stray extends AbstractSkeleton {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.strayRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.strayRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.strayControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.strayMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.strayTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.strayAlwaysDropExp;
+ }
+
public static boolean checkStraySpawnRules(EntityType<Stray> type, ServerLevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
BlockPos blockPos = pos;
diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java
index ebf54d6e36fdee2833250816fae14195ac45eb67..cfa603c7ccaaf1940aa89fa7cd8fafba29529075 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Strider.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java
@@ -94,12 +94,44 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
super(type, world);
this.steering = new ItemBasedSteering(this.entityData, Strider.DATA_BOOST_TIME, Strider.DATA_SADDLE_ID);
this.blocksBuilding = true;
- this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F);
+ if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur
this.setPathfindingMalus(BlockPathTypes.LAVA, 0.0F);
this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F);
this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.striderRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.striderRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.striderControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.striderMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.striderBreedingTicks;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.striderAlwaysDropExp;
+ }
+
public static boolean checkStriderSpawnRules(EntityType<Strider> type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable();
@@ -161,6 +193,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
@Override
protected void registerGoals() {
this.panicGoal = new PanicGoal(this, 1.65D);
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, this.panicGoal);
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
this.temptGoal = new TemptGoal(this, 1.4D, Strider.TEMPT_ITEMS, false);
@@ -416,7 +449,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
@Override
public boolean isSensitiveToWater() {
- return true;
+ return this.level().purpurConfig.striderTakeDamageFromWater; // Purpur
}
@Override
@@ -458,6 +491,19 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
public InteractionResult mobInteract(Player player, InteractionHand hand) {
boolean flag = this.isFood(player.getItemInHand(hand));
+ // Purpur start
+ if (level().purpurConfig.striderGiveSaddleBack && player.isSecondaryUseActive() && !flag && isSaddled() && !isVehicle()) {
+ this.steering.setSaddle(false);
+ if (!player.getAbilities().instabuild) {
+ ItemStack saddle = new ItemStack(Items.SADDLE);
+ if (!player.getInventory().add(saddle)) {
+ player.drop(saddle, false);
+ }
+ }
+ return InteractionResult.SUCCESS;
+ }
+ // Purpur end
+
if (!flag && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) {
if (!this.level().isClientSide) {
player.startRiding(this);
@@ -470,7 +516,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
if (!enuminteractionresult.consumesAction()) {
ItemStack itemstack = player.getItemInHand(hand);
- return itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : InteractionResult.PASS;
+ return itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : tryRide(player, hand); // Purpur
} else {
if (flag && !this.isSilent()) {
this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.STRIDER_EAT, this.getSoundSource(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F);
diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java
index 65cb385ab294e362d666a6d03c4496cdc3b64890..5db70031c8c9ba901a360c758d51222efe911fcc 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Vex.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java
@@ -63,6 +63,65 @@ public class Vex extends Monster implements TraceableEntity {
this.xpReward = 3;
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.vexRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.vexRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.vexControllable;
+ }
+
+ @Override
+ public double getMaxY() {
+ return level().purpurConfig.vexMaxY;
+ }
+
+ @Override
+ public void travel(Vec3 vec3) {
+ super.travel(vec3);
+ if (getRider() != null && this.isControllable()) {
+ float speed;
+ if (onGround) {
+ speed = (float) getAttributeValue(Attributes.MOVEMENT_SPEED) * 0.1F;
+ } else {
+ speed = (float) getAttributeValue(Attributes.FLYING_SPEED);
+ }
+ setSpeed(speed);
+ Vec3 mot = getDeltaMovement();
+ move(MoverType.SELF, mot.multiply(speed, 1.0, speed));
+ setDeltaMovement(mot.scale(0.9D));
+ }
+ }
+
+ @Override
+ public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) {
+ return false; // no fall damage please
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vexMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.vexTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.vexAlwaysDropExp;
+ }
+
@Override
protected float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) {
return dimensions.height - 0.28125F;
@@ -81,7 +140,7 @@ public class Vex extends Monster implements TraceableEntity {
@Override
public void tick() {
- this.noPhysics = true;
+ this.noPhysics = getRider() == null || !this.isControllable(); // Purpur
super.tick();
this.noPhysics = false;
this.setNoGravity(true);
@@ -96,17 +155,19 @@ public class Vex extends Monster implements TraceableEntity {
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(4, new Vex.VexChargeAttackGoal());
this.goalSelector.addGoal(8, new Vex.VexRandomMoveGoal());
this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F));
this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers());
this.targetSelector.addGoal(2, new Vex.VexCopyOwnerTargetGoal(this));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true));
}
public static AttributeSupplier.Builder createAttributes() {
- return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D);
+ return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur;
}
@Override
@@ -235,14 +296,14 @@ public class Vex extends Monster implements TraceableEntity {
return 0.4D;
}
- private class VexMoveControl extends MoveControl {
+ private class VexMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur
public VexMoveControl(Vex entityvex) {
super(entityvex);
}
@Override
- public void tick() {
+ public void vanillaTick() { // Purpur
if (this.operation == MoveControl.Operation.MOVE_TO) {
Vec3 vec3d = new Vec3(this.wantedX - Vex.this.getX(), this.wantedY - Vex.this.getY(), this.wantedZ - Vex.this.getZ());
double d0 = vec3d.length();
@@ -251,7 +312,7 @@ public class Vex extends Monster implements TraceableEntity {
this.operation = MoveControl.Operation.WAIT;
Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().scale(0.5D));
} else {
- Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.speedModifier * 0.05D / d0)));
+ Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.getSpeedModifier() * 0.05D / d0))); // Purpur
if (Vex.this.getTarget() == null) {
Vec3 vec3d1 = Vex.this.getDeltaMovement();
diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java
index 2acc531bd9e948251cac77d979f973678f576394..f1e410b602bf0d5520f8e9340f7b8b9e25ca4d39 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java
@@ -58,14 +58,48 @@ public class Vindicator extends AbstractIllager {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.vindicatorRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.vindicatorRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.vindicatorControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vindicatorMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.vindicatorTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.vindicatorAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(1, new Vindicator.VindicatorBreakDoorGoal(this));
this.goalSelector.addGoal(2, new AbstractIllager.RaiderOpenDoorGoal(this));
this.goalSelector.addGoal(3, new Raider.HoldGroundAttackGoal(this, 10.0F));
this.goalSelector.addGoal(4, new Vindicator.VindicatorMeleeAttackGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, Raider.class)).setAlertOthers());
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true));
@@ -130,6 +164,12 @@ public class Vindicator extends AbstractIllager {
RandomSource randomSource = world.getRandom();
this.populateDefaultEquipmentSlots(randomSource, difficulty);
this.populateDefaultEquipmentEnchantments(randomSource, difficulty);
+ // Purpur start
+ Level level = world.getMinecraftWorld();
+ if (level().purpurConfig.vindicatorJohnnySpawnChance > 0D && random.nextDouble() <= level().purpurConfig.vindicatorJohnnySpawnChance) {
+ setCustomName(Component.translatable("Johnny"));
+ }
+ // Purpur end
return spawnGroupData;
}
diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java
index 701f6bf1d558cf0ec4bc1abb9e1f66d96ea5a6c9..80e90202eae1e12d5261d0bb77ecfadcc9ac4599 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Witch.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java
@@ -57,6 +57,38 @@ public class Witch extends Raider implements RangedAttackMob {
super(type, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.witchRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witchRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.witchControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witchMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.witchTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.witchAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
super.registerGoals();
@@ -65,10 +97,12 @@ public class Witch extends Raider implements RangedAttackMob {
});
this.attackPlayersGoal = new NearestAttackableWitchTargetGoal<>(this, Player.class, 10, true, false, (Predicate) null);
this.goalSelector.addGoal(1, new FloatGoal(this));
+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 60, 10.0F));
this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(3, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[]{Raider.class}));
this.targetSelector.addGoal(2, this.healRaidersGoal);
this.targetSelector.addGoal(3, this.attackPlayersGoal);
diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java
index 9a81cb0c3a5ac40ff50dc7c81f6196a447cd03c6..2b3decb90aeb99bef42b36f3b8df06cb1172b413 100644
--- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java
+++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java
@@ -35,6 +35,38 @@ public class WitherSkeleton extends AbstractSkeleton {
this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.witherSkeletonRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witherSkeletonRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.witherSkeletonControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherSkeletonMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.witherSkeletonTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.witherSkeletonAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractPiglin.class, true));
diff --git a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java
index c1abeb62f63d2f8fb891efec8f76c6736b8f7f75..399e9baedaf37597c539aa5432a513365df83b5e 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java
@@ -67,6 +67,38 @@ public class Zoglin extends Monster implements Enemy, HoglinBase {
this.xpReward = 5;
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.zoglinRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zoglinRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.zoglinControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zoglinMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.zoglinTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.zoglinAlwaysDropExp;
+ }
+
@Override
protected Brain.Provider<Zoglin> brainProvider() {
return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
@@ -182,7 +214,7 @@ public class Zoglin extends Monster implements Enemy, HoglinBase {
@Override
public Brain<Zoglin> getBrain() {
- return super.getBrain();
+ return (Brain<Zoglin>) super.getBrain(); // Purpur - decompile error
}
protected void updateActivity() {
@@ -198,9 +230,10 @@ public class Zoglin extends Monster implements Enemy, HoglinBase {
@Override
protected void customServerAiStep() {
- this.level().getProfiler().push("zoglinBrain");
+ //this.level().getProfiler().push("zoglinBrain"); // Purpur
+ if (getRider() == null || !this.isControllable()) // Purpur - only use brain if no rider
this.getBrain().tick((ServerLevel)this.level(), this);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
this.updateActivity();
}
diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
index 3f8c1d1d3c408fc4f15c4b5680bc22c86f104a9d..7c5efb5582f43d9f333f926ad7dba1ed9920333a 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
@@ -95,22 +95,69 @@ public class Zombie extends Monster {
private int inWaterTime;
public int conversionTime;
private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field
- private boolean shouldBurnInDay = true; // Paper
+ // private boolean shouldBurnInDay = true; // Paper // Purpur - implemented in LivingEntity
public Zombie(EntityType<? extends Zombie> type, Level world) {
super(type, world);
this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(type, world.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper
+ this.setShouldBurnInDay(true); // Purpur
}
public Zombie(Level world) {
this(EntityType.ZOMBIE, world);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.zombieRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombieRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.zombieControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieMaxHealth);
+ }
+
+ public boolean jockeyOnlyBaby() {
+ return level().purpurConfig.zombieJockeyOnlyBaby;
+ }
+
+ public double jockeyChance() {
+ return level().purpurConfig.zombieJockeyChance;
+ }
+
+ public boolean jockeyTryExistingChickens() {
+ return level().purpurConfig.zombieJockeyTryExistingChickens;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.zombieTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.zombieAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper
this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
this.addBehaviourGoals();
}
@@ -120,7 +167,19 @@ public class Zombie extends Monster {
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D));
this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers(ZombifiedPiglin.class));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
- if ( this.level().spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Spigot
+ // Purpur start
+ if ( this.level().spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false) { // Spigot
+ @Override
+ public boolean canUse() {
+ return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canUse();
+ }
+
+ @Override
+ public boolean canContinueToUse() {
+ return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canContinueToUse();
+ }
+ });
+ // Purpur end
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR));
}
@@ -242,30 +301,7 @@ public class Zombie extends Monster {
@Override
public void aiStep() {
- if (this.isAlive()) {
- boolean flag = this.isSunSensitive() && this.isSunBurnTick();
-
- if (flag) {
- ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
-
- if (!itemstack.isEmpty()) {
- if (itemstack.isDamageableItem()) {
- itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2));
- if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) {
- this.broadcastBreakEvent(EquipmentSlot.HEAD);
- this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY);
- }
- }
-
- flag = false;
- }
-
- if (flag) {
- this.setSecondsOnFire(8);
- }
- }
- }
-
+ // Purpur - implemented in LivingEntity
super.aiStep();
}
@@ -303,6 +339,7 @@ public class Zombie extends Monster {
}
+ public boolean shouldBurnInDay() { return isSunSensitive(); } // Purpur - for ABI compatibility
public boolean isSunSensitive() {
return this.shouldBurnInDay; // Paper - use api value instead
}
@@ -432,7 +469,7 @@ public class Zombie extends Monster {
nbt.putBoolean("CanBreakDoors", this.canBreakDoors());
nbt.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1);
nbt.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1);
- nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper
+ // nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper // Purpur - implemented in LivingEntity
}
@Override
@@ -446,7 +483,7 @@ public class Zombie extends Monster {
}
// Paper start
if (nbt.contains("Paper.ShouldBurnInDay")) {
- this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay");
+ // this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); // Purpur - implemented in LivingEntity
}
// Paper end
@@ -527,19 +564,20 @@ public class Zombie extends Monster {
if (object instanceof Zombie.ZombieGroupData) {
Zombie.ZombieGroupData entityzombie_groupdatazombie = (Zombie.ZombieGroupData) object;
- if (entityzombie_groupdatazombie.isBaby) {
- this.setBaby(true);
+ // Purpur start
+ if (!jockeyOnlyBaby() || entityzombie_groupdatazombie.isBaby) {
+ this.setBaby(entityzombie_groupdatazombie.isBaby);
if (entityzombie_groupdatazombie.canSpawnJockey) {
- if ((double) randomsource.nextFloat() < 0.05D) {
- List<Chicken> 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<Chicken> list = jockeyTryExistingChickens() ? world.getEntitiesOfClass(Chicken.class, this.getBoundingBox().inflate(5.0D, 3.0D, 5.0D), EntitySelector.ENTITY_NOT_BEING_RIDDEN) : java.util.Collections.emptyList();
+ // Purpur end
if (!list.isEmpty()) {
Chicken entitychicken = (Chicken) list.get(0);
entitychicken.setChickenJockey(true);
this.startRiding(entitychicken);
- }
- } else if ((double) randomsource.nextFloat() < 0.05D) {
+ } else { // Purpur
Chicken entitychicken1 = (Chicken) EntityType.CHICKEN.create(this.level());
if (entitychicken1 != null) {
@@ -549,6 +587,7 @@ public class Zombie extends Monster {
this.startRiding(entitychicken1);
world.addFreshEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit
}
+ } // Purpur
}
}
}
@@ -559,11 +598,7 @@ public class Zombie extends Monster {
}
if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
- LocalDate localdate = LocalDate.now();
- int i = localdate.get(ChronoField.DAY_OF_MONTH);
- int j = localdate.get(ChronoField.MONTH_OF_YEAR);
-
- if (j == 10 && i == 31 && randomsource.nextFloat() < 0.25F) {
+ if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(world.getMinecraftWorld()) && this.random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur
this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(randomsource.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN));
this.armorDropChances[EquipmentSlot.HEAD.getIndex()] = 0.0F;
}
@@ -595,7 +630,7 @@ public class Zombie extends Monster {
}
protected void randomizeReinforcementsChance() {
- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * 0.10000000149011612D);
+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieSpawnReinforcements); // Purpur
}
@Override
diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java
index 25ed5571b24e590bc95056020d84496492b53298..be2a35f703310f699ee31bd2ec1c938af281a577 100644
--- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java
+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java
@@ -79,6 +79,58 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder {
});
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.zombieVillagerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombieVillagerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.zombieVillagerControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieVillagerMaxHealth);
+ }
+
+ @Override
+ protected void randomizeReinforcementsChance() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieVillagerSpawnReinforcements);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.zombieVillagerTakeDamageFromWater;
+ }
+
+ @Override
+ public boolean jockeyOnlyBaby() {
+ return level().purpurConfig.zombieVillagerJockeyOnlyBaby;
+ }
+
+ @Override
+ public double jockeyChance() {
+ return level().purpurConfig.zombieVillagerJockeyChance;
+ }
+
+ @Override
+ public boolean jockeyTryExistingChickens() {
+ return level().purpurConfig.zombieVillagerJockeyTryExistingChickens;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.zombieVillagerAlwaysDropExp;
+ }
+
@Override
protected void defineSynchedData() {
super.defineSynchedData();
@@ -165,13 +217,13 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder {
ItemStack itemstack = player.getItemInHand(hand);
if (itemstack.is(Items.GOLDEN_APPLE)) {
- if (this.hasEffect(MobEffects.WEAKNESS)) {
+ if (this.hasEffect(MobEffects.WEAKNESS) && level().purpurConfig.zombieVillagerCureEnabled) { // Purpur
if (!player.getAbilities().instabuild) {
itemstack.shrink(1);
}
if (!this.level().isClientSide) {
- this.startConverting(player.getUUID(), this.random.nextInt(2401) + 3600);
+ this.startConverting(player.getUUID(), this.random.nextInt(level().purpurConfig.zombieVillagerCuringTimeMax - level().purpurConfig.zombieVillagerCuringTimeMin + 1) + level().purpurConfig.zombieVillagerCuringTimeMin); // Purpur
}
return InteractionResult.SUCCESS;
diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java
index aff140cfb2bbdce8b512080cf394c84c5c4ff807..7bb99d7fd8e05805e0cac7bec0b2771990057f58 100644
--- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java
+++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java
@@ -63,6 +63,53 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F);
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.zombifiedPiglinRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombifiedPiglinRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.zombifiedPiglinControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombifiedPiglinMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.zombifiedPiglinTakeDamageFromWater;
+ }
+
+ @Override
+ public boolean jockeyOnlyBaby() {
+ return level().purpurConfig.zombifiedPiglinJockeyOnlyBaby;
+ }
+
+ @Override
+ public double jockeyChance() {
+ return level().purpurConfig.zombifiedPiglinJockeyChance;
+ }
+
+ @Override
+ public boolean jockeyTryExistingChickens() {
+ return level().purpurConfig.zombifiedPiglinJockeyTryExistingChickens;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.zombifiedPiglinAlwaysDropExp;
+ }
+
@Override
public void setPersistentAngerTarget(@Nullable UUID angryAt) {
this.persistentAngerTarget = angryAt;
@@ -115,7 +162,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
this.maybeAlertOthers();
}
- if (this.isAngry()) {
+ if (this.isAngry() && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur
this.lastHurtByPlayerTime = this.tickCount;
}
@@ -170,7 +217,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
this.ticksUntilNextAlert = ZombifiedPiglin.ALERT_INTERVAL.sample(this.random);
}
- if (entityliving instanceof Player) {
+ if (entityliving instanceof Player && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur
this.setLastHurtByPlayer((Player) entityliving);
}
@@ -250,7 +297,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
@Override
protected void randomizeReinforcementsChance() {
- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(0.0D);
+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombifiedPiglinSpawnReinforcements); // Purpur
}
@Nullable
diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
index d6d61b91096d28eea1e5af69ea1c07890820ee7f..ecc445bb3cda2d79d64c580b72544567dd304f53 100644
--- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
+++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
@@ -67,6 +67,43 @@ public class Hoglin extends Animal implements Enemy, HoglinBase {
this.xpReward = 5;
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.hoglinRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.hoglinRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.hoglinControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.hoglinMaxHealth);
+ }
+
+ @Override
+ public int getPurpurBreedTime() {
+ return this.level().purpurConfig.hoglinBreedingTicks;
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.hoglinTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.hoglinAlwaysDropExp;
+ }
+
@Override
public boolean canBeLeashed(Player player) {
return !this.isLeashed();
@@ -129,10 +166,10 @@ public class Hoglin extends Animal implements Enemy, HoglinBase {
private int behaviorTick; // Pufferfish
@Override
protected void customServerAiStep() {
- this.level().getProfiler().push("hoglinBrain");
- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ //this.level().getProfiler().push("hoglinBrain"); // Purpur
+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
this.getBrain().tick((ServerLevel)this.level(), this);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
HoglinAi.updateActivity(this);
if (this.isConverting()) {
++this.timeInOverworld;
diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
index e235cc9d1b3ce59ab662ef3cf3ce0267ca78536d..79f969ea76fc2073e10c050c2aa64b5b5e473181 100644
--- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
@@ -96,6 +96,38 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
this.xpReward = 5;
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.piglinRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.piglinRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.piglinControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.piglinMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.piglinTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.piglinAlwaysDropExp;
+ }
+
@Override
public void addAdditionalSaveData(CompoundTag nbt) {
super.addAdditionalSaveData(nbt);
@@ -308,10 +340,10 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
private int behaviorTick; // Pufferfish
@Override
protected void customServerAiStep() {
- this.level().getProfiler().push("piglinBrain");
- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
+ //this.level().getProfiler().push("piglinBrain"); // Purpur
+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
this.getBrain().tick((ServerLevel) this.level(), this);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
PiglinAi.updateActivity(this);
super.customServerAiStep();
}
@@ -407,7 +439,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
@Override
public boolean wantsToPickUp(ItemStack stack) {
- return this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack);
+ return (this.level().purpurConfig.piglinBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack);
}
protected boolean canReplaceCurrentItem(ItemStack stack) {
diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java
index d98c526676202741e628d5e317b8cdd3f4d3be0a..edf9208ed49a179dc15308dd72ee47367c0a9ded 100644
--- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java
+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java
@@ -597,20 +597,33 @@ public class PiglinAi {
Iterator iterator = iterable.iterator();
Item item;
+ ItemStack itemstack; // Purpur
do {
if (!iterator.hasNext()) {
return false;
}
- ItemStack itemstack = (ItemStack) iterator.next();
+ itemstack = (ItemStack) iterator.next(); // Purpur
item = itemstack.getItem();
- } while (!(item instanceof ArmorItem) || ((ArmorItem) item).getMaterial() != ArmorMaterials.GOLD);
+ } while (!(item instanceof ArmorItem) || ((ArmorItem) item).getMaterial() != ArmorMaterials.GOLD && (!entity.level().purpurConfig.piglinIgnoresArmorWithGoldTrim || !isWearingGoldTrim(entity, itemstack))); // Purpur
return true;
}
+ // Purpur start
+ private static boolean isWearingGoldTrim(LivingEntity entity, ItemStack itemstack) {
+ Optional<net.minecraft.world.item.armortrim.ArmorTrim> optionalArmorTrim = net.minecraft.world.item.armortrim.ArmorTrim.getTrim(entity.level().registryAccess(), itemstack);
+
+ if (optionalArmorTrim.isEmpty()) return false;
+
+ net.minecraft.world.item.armortrim.ArmorTrim armorTrim = optionalArmorTrim.get();
+
+ return armorTrim.material().is(net.minecraft.world.item.armortrim.TrimMaterials.GOLD);
+ }
+ // Purpur end
+
private static void stopWalking(Piglin piglin) {
piglin.getBrain().eraseMemory(MemoryModuleType.WALK_TARGET);
piglin.getNavigation().stop();
diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java
index d02ee11066fc4f07ccb110b09b86d895ff90d4f2..856e6e02c9424a6c06e310262cb4f5bdd34da516 100644
--- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java
+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java
@@ -41,6 +41,38 @@ public class PiglinBrute extends AbstractPiglin {
this.xpReward = 20;
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.piglinBruteRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.piglinBruteRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.piglinBruteControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.piglinBruteMaxHealth);
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.piglinBruteTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.piglinBruteAlwaysDropExp;
+ }
+
public static AttributeSupplier.Builder createAttributes() {
return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 50.0D).add(Attributes.MOVEMENT_SPEED, (double)0.35F).add(Attributes.ATTACK_DAMAGE, 7.0D);
}
@@ -70,7 +102,7 @@ public class PiglinBrute extends AbstractPiglin {
@Override
public Brain<PiglinBrute> getBrain() {
- return super.getBrain();
+ return (Brain<PiglinBrute>) super.getBrain(); // Purpur - decompile error
}
@Override
@@ -85,9 +117,10 @@ public class PiglinBrute extends AbstractPiglin {
@Override
protected void customServerAiStep() {
- this.level().getProfiler().push("piglinBruteBrain");
+ //this.level().getProfiler().push("piglinBruteBrain"); // Purpur
+ if (getRider() == null || this.isControllable()) // Purpur - only use brain if no rider
this.getBrain().tick((ServerLevel)this.level(), this);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
PiglinBruteAi.updateActivity(this);
PiglinBruteAi.maybePlayActivitySound(this);
super.customServerAiStep();
diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
index 71db8bd6885377d55cfc571fccc21df6d8a57b54..3fa46affc4d77d01909cfeaeaba6e06ba9fd5592 100644
--- a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
+++ b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
@@ -121,8 +121,32 @@ public class Warden extends Monster implements VibrationSystem {
this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F);
this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F);
this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F);
+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.5F); // Purpur
}
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.wardenRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wardenRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.wardenControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
+ }
+ // Purpur end
+
@Override
public Packet<ClientGamePacketListener> getAddEntityPacket() {
return new ClientboundAddEntityPacket(this, this.hasPose(Pose.EMERGING) ? 1 : 0);
@@ -276,10 +300,10 @@ public class Warden extends Monster implements VibrationSystem {
protected void customServerAiStep() {
ServerLevel worldserver = (ServerLevel) this.level();
- worldserver.getProfiler().push("wardenBrain");
+ //worldserver.getProfiler().push("wardenBrain"); // Purpur
if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick(worldserver, this);
- this.level().getProfiler().pop();
+ //this.level().getProfiler().pop(); // Purpur
super.customServerAiStep();
if ((this.tickCount + this.getId()) % 120 == 0) {
Warden.applyDarknessAround(worldserver, this.position(), this, 20);
@@ -396,19 +420,16 @@ public class Warden extends Monster implements VibrationSystem {
@Contract("null->false")
public boolean canTargetEntity(@Nullable Entity entity) {
- boolean flag;
-
+ if (getRider() != null && isControllable()) return false; // Purpur
if (entity instanceof LivingEntity) {
LivingEntity entityliving = (LivingEntity) entity;
if (this.level() == entity.level() && EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(entity) && !this.isAlliedTo(entity) && entityliving.getType() != EntityType.ARMOR_STAND && entityliving.getType() != EntityType.WARDEN && !entityliving.isInvulnerable() && !entityliving.isDeadOrDying() && this.level().getWorldBorder().isWithinBounds(entityliving.getBoundingBox())) {
- flag = true;
- return flag;
+ return true; // Purpur - wtf
}
}
- flag = false;
- return flag;
+ return false; // Purpur - wtf
}
public static void applyDarknessAround(ServerLevel world, Vec3 pos, @Nullable Entity entity, int range) {
diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java
index 564908ce0a560c2190fb624e77d227d3b7031024..f2a4e214744227f1df32e3782e71f8a9d5cea261 100644
--- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java
+++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java
@@ -43,6 +43,7 @@ import org.bukkit.event.entity.VillagerAcquireTradeEvent;
// CraftBukkit end
public abstract class AbstractVillager extends AgeableMob implements InventoryCarrier, Npc, Merchant {
+ static final net.minecraft.world.item.crafting.Ingredient TEMPT_ITEMS = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.level.block.Blocks.EMERALD_BLOCK.asItem()); // Purpur
// CraftBukkit start
private CraftMerchant craftMerchant;
diff --git a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java
index 5f407535298a31a34cfe114dd863fd6a9b977707..29c7e33fe961020e5a0007287fe9b6631689f1b8 100644
--- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java
+++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java
@@ -30,7 +30,7 @@ public class CatSpawner implements CustomSpawner {
if (this.nextTick > 0) {
return 0;
} else {
- this.nextTick = 1200;
+ this.nextTick = world.purpurConfig.catSpawnDelay; // Purpur
Player player = world.getRandomPlayer();
if (player == null) {
return 0;
@@ -63,11 +63,15 @@ public class CatSpawner implements CustomSpawner {
}
private int spawnInVillage(ServerLevel world, BlockPos pos) {
- int i = 48;
+ // Purpur start
+ int range = world.purpurConfig.catSpawnVillageScanRange;
+ if (range <= 0) return 0;
+
if (world.getPoiManager().getCountInRange((entry) -> {
return entry.is(PoiTypes.HOME);
- }, pos, 48, PoiManager.Occupancy.IS_OCCUPIED) > 4L) {
- List<Cat> list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(48.0D, 8.0D, 48.0D));
+ }, pos, range, PoiManager.Occupancy.IS_OCCUPIED) > 4L) {
+ List<Cat> list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(range, 8.0D, range));
+ // Purpur end
if (list.size() < 5) {
return this.spawnCat(pos, world);
}
@@ -77,8 +81,11 @@ public class CatSpawner implements CustomSpawner {
}
private int spawnInHut(ServerLevel world, BlockPos pos) {
- int i = 16;
- List<Cat> list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(16.0D, 8.0D, 16.0D));
+ // Purpur start
+ int range = world.purpurConfig.catSpawnSwampHutScanRange;
+ if (range <= 0) return 0;
+ List<Cat> list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(range, 8.0D, range));
+ // Purpur end
return list.size() < 1 ? this.spawnCat(pos, world) : 0;
}
diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java
index c4ddf2661bca728d504918171295e10e307b18b4..2ea19f72dd8e86d5821f734c13ac2dc6f53e9c30 100644
--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
@@ -140,6 +140,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
}, MemoryModuleType.MEETING_POINT, (entityvillager, holder) -> {
return holder.is(PoiTypes.MEETING);
});
+ private boolean isLobotomized = false; public boolean isLobotomized() { return this.isLobotomized; } // Purpur
+ private int notLobotomizedCount = 0; // Purpur
public long nextGolemPanic = -1; // Pufferfish
@@ -154,6 +156,90 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
this.getNavigation().setCanFloat(true);
this.setCanPickUpLoot(true);
this.setVillagerData(this.getVillagerData().setType(type).setProfession(VillagerProfession.NONE));
+ if (level().purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false));
+ }
+
+ // Purpur start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.villagerRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.villagerRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.villagerControllable;
+ }
+
+ @Override
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.villagerMaxHealth);
+ }
+
+ @Override
+ public boolean canBeLeashed(Player player) {
+ return level().purpurConfig.villagerCanBeLeashed && !this.isLeashed();
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.villagerTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.villagerAlwaysDropExp;
+ }
+
+ private boolean checkLobotomized() {
+ int interval = this.level().purpurConfig.villagerLobotomizeCheckInterval;
+ if (this.notLobotomizedCount > 3) {
+ // check half as often if not lobotomized for the last 3+ consecutive checks
+ interval *= 2;
+ }
+ if (this.level().getGameTime() % interval == 0) {
+ // offset Y for short blocks like dirt_path/farmland
+ this.isLobotomized = !canTravelFrom(BlockPos.containing(this.position().x, this.getBoundingBox().minY + 0.0625D, this.position().z));
+
+ if (this.isLobotomized) {
+ this.notLobotomizedCount = 0;
+ } else {
+ this.notLobotomizedCount++;
+ }
+ }
+ return this.isLobotomized;
+ }
+
+ private boolean canTravelFrom(BlockPos pos) {
+ return canTravelTo(pos.east()) || canTravelTo(pos.west()) || canTravelTo(pos.north()) || canTravelTo(pos.south());
+ }
+
+ private boolean canTravelTo(BlockPos pos) {
+ net.minecraft.world.level.block.state.BlockState state = this.level().getBlockStateIfLoaded(pos);
+ if (state == null) {
+ // chunk not loaded
+ return false;
+ }
+ net.minecraft.world.level.block.Block bottom = state.getBlock();
+ if (bottom instanceof net.minecraft.world.level.block.FenceBlock ||
+ bottom instanceof net.minecraft.world.level.block.FenceGateBlock ||
+ bottom instanceof net.minecraft.world.level.block.WallBlock) {
+ // bottom block is too tall to get over
+ return false;
+ }
+ net.minecraft.world.level.block.Block top = level().getBlockState(pos.above()).getBlock();
+ // only if both blocks have no collision
+ return !bottom.hasCollision && !top.hasCollision;
}
@Override
@@ -190,7 +276,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
brain.addActivity(Activity.PLAY, VillagerGoalPackages.getPlayPackage(0.5F));
} else {
brain.setSchedule(Schedule.VILLAGER_DEFAULT);
- brain.addActivityWithConditions(Activity.WORK, VillagerGoalPackages.getWorkPackage(villagerprofession, 0.5F), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT)));
+ brain.addActivityWithConditions(Activity.WORK, VillagerGoalPackages.getWorkPackage(villagerprofession, 0.5F, this.level().purpurConfig.villagerClericsFarmWarts), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))); // Purpur
}
brain.addActivity(Activity.CORE, VillagerGoalPackages.getCorePackage(villagerprofession, 0.5F));
@@ -253,15 +339,22 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
// Paper start
this.customServerAiStep(false);
}
- protected void customServerAiStep(final boolean inactive) {
+ protected void customServerAiStep(boolean inactive) { // Purpur - not final
// Paper end
- this.level().getProfiler().push("villagerBrain");
- // Pufferfish start
- if (!inactive && this.behaviorTick++ % this.activatedPriority == 0) {
- this.getBrain().tick((ServerLevel) this.level(), this); // Paper
+ //this.level().getProfiler().push("villagerBrain"); // Purpur
+ // Purpur start
+ if (this.level().purpurConfig.villagerLobotomizeEnabled) {
+ // treat as inactive if lobotomized
+ inactive = inactive || checkLobotomized();
+ } else {
+ // clean up state for API
+ this.isLobotomized = false;
}
- // Pufferfish end
- this.level().getProfiler().pop();
+ if (!inactive && (getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Purpur - only use brain if no rider
+ this.getBrain().tick((ServerLevel) this.level(), this); // Paper
+ else if (this.isLobotomized && shouldRestock()) restock();
+ // Purpur end
+ //this.level().getProfiler().pop(); // Purpur
if (this.assignProfessionWhenSpawned) {
this.assignProfessionWhenSpawned = false;
}
@@ -317,7 +410,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
if (!itemstack.is(Items.VILLAGER_SPAWN_EGG) && this.isAlive() && !this.isTrading() && !this.isSleeping()) {
if (this.isBaby()) {
this.setUnhappy();
- return InteractionResult.sidedSuccess(this.level().isClientSide);
+ return tryRide(player, hand, InteractionResult.sidedSuccess(this.level().isClientSide)); // Purpur
} else {
boolean flag = this.getOffers().isEmpty();
@@ -330,9 +423,10 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
}
if (flag) {
- return InteractionResult.sidedSuccess(this.level().isClientSide);
+ return tryRide(player, hand, InteractionResult.sidedSuccess(this.level().isClientSide)); // Purpur
} else {
- if (!this.level().isClientSide && !this.offers.isEmpty()) {
+ if (level().purpurConfig.villagerRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur
+ if (this.level().purpurConfig.villagerAllowTrading && !this.offers.isEmpty()) {
this.startTrading(player);
}
@@ -501,7 +595,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
while (iterator.hasNext()) {
MerchantOffer merchantrecipe = (MerchantOffer) iterator.next();
- merchantrecipe.updateDemand();
+ merchantrecipe.updateDemand(this.level().purpurConfig.villagerMinimumDemand); // Purpur
}
}
@@ -751,7 +845,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
@Override
public boolean canBreed() {
- return this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0;
+ return this.level().purpurConfig.villagerCanBreed && this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0; // Purpur
}
private boolean hungry() {
@@ -944,6 +1038,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
public boolean hasFarmSeeds() {
return this.getInventory().hasAnyMatching((itemstack) -> {
+ // Purpur start
+ if (this.level().purpurConfig.villagerClericsFarmWarts && this.getVillagerData().getProfession() == VillagerProfession.CLERIC) {
+ return itemstack.is(Items.NETHER_WART);
+ }
+ // Purpur end
return itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS);
});
}
@@ -993,6 +1092,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
}
public void spawnGolemIfNeeded(ServerLevel world, long time, int requiredCount) {
+ if (world.purpurConfig.villagerSpawnIronGolemRadius > 0 && world.getEntitiesOfClass(net.minecraft.world.entity.animal.IronGolem.class, getBoundingBox().inflate(world.purpurConfig.villagerSpawnIronGolemRadius)).size() > world.purpurConfig.villagerSpawnIronGolemLimit) return; // Purpur
if (this.wantsToSpawnGolem(time)) {
AABB axisalignedbb = this.getBoundingBox().inflate(10.0D, 10.0D, 10.0D);
List<Villager> list = world.getEntitiesOfClass(Villager.class, axisalignedbb);
@@ -1066,6 +1166,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
@Override
public void startSleeping(BlockPos pos) {
+ // Purpur start
+ if (level().purpurConfig.bedExplodeOnVillagerSleep && this.level().getBlockState(pos).getBlock() instanceof net.minecraft.world.level.block.BedBlock) {
+ this.level().explode(null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (float) this.level().purpurConfig.bedExplosionPower, this.level().purpurConfig.bedExplosionFire, this.level().purpurConfig.bedExplosionEffect);
+ return;
+ }
+ // Purpur end
super.startSleeping(pos);
this.brain.setMemory(MemoryModuleType.LAST_SLEPT, this.level().getGameTime()); // CraftBukkit - decompile error
this.brain.eraseMemory(MemoryModuleType.WALK_TARGET);
diff --git a/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java b/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java
index ac70c2c03241e73943bd517a8c69dd05e0873634..0318663a824d2a9515f867a075d148c3fcb1a907 100644
--- a/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java
+++ b/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java
@@ -26,7 +26,7 @@ public record VillagerProfession(String name, Predicate<Holder<PoiType>> heldJob
public static final VillagerProfession ARMORER = register("armorer", PoiTypes.ARMORER, SoundEvents.VILLAGER_WORK_ARMORER);
public static final VillagerProfession BUTCHER = register("butcher", PoiTypes.BUTCHER, SoundEvents.VILLAGER_WORK_BUTCHER);
public static final VillagerProfession CARTOGRAPHER = register("cartographer", PoiTypes.CARTOGRAPHER, SoundEvents.VILLAGER_WORK_CARTOGRAPHER);
- public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, SoundEvents.VILLAGER_WORK_CLERIC);
+ public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, ImmutableSet.of(Items.NETHER_WART), ImmutableSet.of(Blocks.SOUL_SAND), SoundEvents.VILLAGER_WORK_CLERIC); // Purpur
public static final VillagerProfession FARMER = register("farmer", PoiTypes.FARMER, ImmutableSet.of(Items.WHEAT, Items.WHEAT_SEEDS, Items.BEETROOT_SEEDS, Items.BONE_MEAL), ImmutableSet.of(Blocks.FARMLAND), SoundEvents.VILLAGER_WORK_FARMER);
public static final VillagerProfession FISHERMAN = register("fisherman", PoiTypes.FISHERMAN, SoundEvents.VILLAGER_WORK_FISHERMAN);
public static final VillagerProfession FLETCHER = register("fletcher", PoiTypes.FLETCHER, SoundEvents.VILLAGER_WORK_FLETCHER);
diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
index 0002f3a2f2e096d4b2015baf707707f6cc1a9582..0e5fae26bf89749e446050c92dc7c23d788f5edb 100644
--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
@@ -66,6 +66,43 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
//this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader // Paper - move back to MobSpawnerTrader - Vanilla behavior is that only traders spawned by it have this value set.
}
+ // Purpur - start
+ @Override
+ public boolean isRidable() {
+ return level().purpurConfig.wanderingTraderRidable;
+ }
+
+ @Override
+ public boolean dismountsUnderwater() {
+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wanderingTraderRidableInWater;
+ }
+
+ @Override
+ public boolean isControllable() {
+ return level().purpurConfig.wanderingTraderControllable;
+ }
+ // Purpur end
+
+ @Override
+ public void initAttributes() {
+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.wanderingTraderMaxHealth);
+ }
+
+ @Override
+ public boolean canBeLeashed(Player player) {
+ return level().purpurConfig.wanderingTraderCanBeLeashed && !this.isLeashed();
+ }
+
+ @Override
+ public boolean isSensitiveToWater() {
+ return this.level().purpurConfig.wanderingTraderTakeDamageFromWater;
+ }
+
+ @Override
+ protected boolean isAlwaysExperienceDropper() {
+ return this.level().purpurConfig.wanderingTraderAlwaysDropExp;
+ }
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
@@ -73,7 +110,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
return this.canDrinkPotion && this.level().isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API
}));
this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> {
- return this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API
+ return level().purpurConfig.milkClearsBeneficialEffects && this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API // Purpur
}));
this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this));
this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D));
@@ -86,6 +123,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
this.goalSelector.addGoal(1, new PanicGoal(this, 0.5D));
this.goalSelector.addGoal(1, new LookAtTradingPlayerGoal(this));
this.goalSelector.addGoal(2, new WanderingTrader.WanderToPositionGoal(this, 2.0D, 0.35D));
+ if (level().purpurConfig.wanderingTraderFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); // Purpur
this.goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, 0.35D));
this.goalSelector.addGoal(8, new WaterAvoidingRandomStrollGoal(this, 0.35D));
this.goalSelector.addGoal(9, new InteractGoal(this, Player.class, 3.0F, 1.0F));
@@ -113,9 +151,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
}
if (this.getOffers().isEmpty()) {
- return InteractionResult.sidedSuccess(this.level().isClientSide);
+ return tryRide(player, hand, InteractionResult.sidedSuccess(this.level().isClientSide)); // Purpur
} else {
- if (!this.level().isClientSide) {
+ if (level().purpurConfig.wanderingTraderRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur
+ if (this.level().purpurConfig.wanderingTraderAllowTrading) {
this.setTradingPlayer(player);
this.openTradingScreen(player, this.getDisplayName(), 1);
}
diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
index 8385eb1d60f377da94e3178ab506feefb43563fd..a5443f92786427c42092aec8350e7ab37704db7a 100644
--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
@@ -159,7 +159,17 @@ public class WanderingTraderSpawner implements CustomSpawner {
int k = pos.getX() + this.random.nextInt(range * 2) - range;
int l = pos.getZ() + this.random.nextInt(range * 2) - range;
int i1 = world.getHeight(Heightmap.Types.WORLD_SURFACE, k, l);
- BlockPos blockposition2 = new BlockPos(k, i1, l);
+ // Purpur start - allow traders to spawn below nether roof
+ BlockPos.MutableBlockPos blockposition2 = new BlockPos.MutableBlockPos(k, i1, l);
+ if (world.dimensionType().hasCeiling()) {
+ do {
+ blockposition2.relative(net.minecraft.core.Direction.DOWN);
+ } while (!world.getBlockState(blockposition2).isAir());
+ do {
+ blockposition2.relative(net.minecraft.core.Direction.DOWN);
+ } while (world.getBlockState(blockposition2).isAir() && blockposition2.getY() > 0);
+ }
+ // Purpur end
if (NaturalSpawner.isSpawnPositionOk(SpawnPlacements.Type.ON_GROUND, world, blockposition2, EntityType.WANDERING_TRADER)) {
blockposition1 = blockposition2;
diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
index 58152160d609d0e9d105153aeb166a56a7955603..d00035e31cf4773a418d1cc6a6018d08e6b558f0 100644
--- a/src/main/java/net/minecraft/world/entity/player/Player.java
+++ b/src/main/java/net/minecraft/world/entity/player/Player.java
@@ -187,17 +187,40 @@ public abstract class Player extends LivingEntity {
public boolean affectsSpawning = true;
public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET;
// Paper end
+ public int sixRowEnderchestSlotCount = -1; // Purpur
+ public int burpDelay = 0; // Purpur
+ public boolean canPortalInstant = false; // Purpur
// CraftBukkit start
public boolean fauxSleeping;
public int oldLevel = -1;
+ public void setAfk(boolean afk) {
+ }
+
+ public boolean isAfk() {
+ return false;
+ }
+
@Override
public CraftHumanEntity getBukkitEntity() {
return (CraftHumanEntity) super.getBukkitEntity();
}
// CraftBukkit end
+ // Purpur start
+ public abstract void resetLastActionTime();
+
+ @Override
+ public boolean processClick(InteractionHand hand) {
+ Entity vehicle = getRootVehicle();
+ if (vehicle != null && vehicle.getRider() == this) {
+ return vehicle.onClick(hand);
+ }
+ return false;
+ }
+ // Purpur end
+
public Player(Level world, BlockPos pos, float yaw, GameProfile gameProfile) {
super(EntityType.PLAYER, world);
this.lastItemInMainHand = ItemStack.EMPTY;
@@ -242,6 +265,12 @@ public abstract class Player extends LivingEntity {
@Override
public void tick() {
+ // Purpur start
+ if (this.burpDelay > 0 && --this.burpDelay == 0) {
+ this.level().playSound(null, getX(), getY(), getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 1.0F, this.level().random.nextFloat() * 0.1F + 0.9F);
+ }
+ // Purpur end
+
this.noPhysics = this.isSpectator();
if (this.isSpectator()) {
this.setOnGround(false);
@@ -345,6 +374,16 @@ public abstract class Player extends LivingEntity {
this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TURTLE_HELMET); // CraftBukkit
}
+ // Purpur start
+ if (this.level().purpurConfig.playerNetheriteFireResistanceDuration > 0 && this.level().getGameTime() % 20 == 0) {
+ if (itemstack.is(Items.NETHERITE_HELMET)
+ && this.getItemBySlot(EquipmentSlot.CHEST).is(Items.NETHERITE_CHESTPLATE)
+ && this.getItemBySlot(EquipmentSlot.LEGS).is(Items.NETHERITE_LEGGINGS)
+ && this.getItemBySlot(EquipmentSlot.FEET).is(Items.NETHERITE_BOOTS)) {
+ this.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, this.level().purpurConfig.playerNetheriteFireResistanceDuration, this.level().purpurConfig.playerNetheriteFireResistanceAmplifier, this.level().purpurConfig.playerNetheriteFireResistanceAmbient, this.level().purpurConfig.playerNetheriteFireResistanceShowParticles, this.level().purpurConfig.playerNetheriteFireResistanceShowIcon), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.NETHERITE_ARMOR);
+ }
+ }
+ // Purpur end
}
protected ItemCooldowns createItemCooldowns() {
@@ -431,7 +470,7 @@ public abstract class Player extends LivingEntity {
@Override
public int getPortalWaitTime() {
- return this.abilities.invulnerable ? 1 : 80;
+ return canPortalInstant ? 1 : this.abilities.invulnerable ? this.level().purpurConfig.playerCreativePortalWaitTime : this.level().purpurConfig.playerPortalWaitTime; // Purpur
}
@Override
@@ -591,7 +630,7 @@ public abstract class Player extends LivingEntity {
for (int i = 0; i < list.size(); ++i) {
Entity entity = (Entity) list.get(i);
- if (entity.getType() == EntityType.EXPERIENCE_ORB) {
+ if (entity.getType() == EntityType.EXPERIENCE_ORB && entity.level().purpurConfig.playerExpPickupDelay >= 0) { // Purpur
list1.add(entity);
} else if (!entity.isRemoved()) {
this.touch(entity);
@@ -1282,7 +1321,7 @@ public abstract class Player extends LivingEntity {
flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper
flag2 = flag2 && !this.isSprinting();
if (flag2) {
- f *= 1.5F;
+ f *= this.level().purpurConfig.playerCriticalDamageMultiplier; // Purpur
}
f += f1;
@@ -1977,9 +2016,19 @@ public abstract class Player extends LivingEntity {
@Override
public int getExperienceReward() {
if (!this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator()) {
- int i = this.experienceLevel * 7;
-
- return i > 100 ? 100 : i;
+ // Purpur start
+ int toDrop;
+ try {
+ toDrop = Math.round(((Number) scriptEngine.eval("let expLevel = " + experienceLevel + "; " +
+ "let expTotal = " + totalExperience + "; " +
+ "let exp = " + experienceProgress + "; " +
+ level().purpurConfig.playerDeathExpDropEquation)).floatValue());
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ toDrop = experienceLevel * 7;
+ }
+ return Math.min(toDrop, level().purpurConfig.playerDeathExpDropMax);
+ // Purpur end
} else {
return 0;
}
@@ -2055,6 +2104,11 @@ public abstract class Player extends LivingEntity {
return this.inventory.armor;
}
+ @Override
+ public boolean dismountsUnderwater() {
+ return !level().purpurConfig.playerRidableInWater;
+ }
+
public boolean setEntityOnShoulder(CompoundTag entityNbt) {
if (!this.isPassenger() && this.onGround() && !this.isInWater() && !this.isInPowderSnow) {
if (this.getShoulderEntityLeft().isEmpty()) {
@@ -2339,7 +2393,7 @@ public abstract class Player extends LivingEntity {
public ItemStack eat(Level world, ItemStack stack) {
this.getFoodData().eat(stack.getItem(), stack);
this.awardStat(Stats.ITEM_USED.get(stack.getItem()));
- world.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F);
+ // world.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); // Purpur - moved to tick()
if (this instanceof ServerPlayer) {
CriteriaTriggers.CONSUME_ITEM.trigger((ServerPlayer) this, stack);
}
diff --git a/src/main/java/net/minecraft/world/entity/player/StackedContents.java b/src/main/java/net/minecraft/world/entity/player/StackedContents.java
index 68d272e98f9e54c9b150c75c27a9ae545be842f6..63fc6074082b579c3f88316008cbeb2a2d7f5841 100644
--- a/src/main/java/net/minecraft/world/entity/player/StackedContents.java
+++ b/src/main/java/net/minecraft/world/entity/player/StackedContents.java
@@ -40,8 +40,62 @@ public class StackedContents {
int j = Math.min(maxCount, stack.getCount());
if (this.extrasMap != null && stack.hasTag() && this.extrasMap.accountStack(stack, j)) return; // Paper - if an exact ingredient, don't include it
this.put(i, j);
+ // PaperPR start
+ if (stack.hasTag()) {
+ this.put(getExactStackingIndex(stack), j);
+ }
+ }
+
+ }
+ private static final net.minecraft.core.IdMap<ItemStack> EXACT_MATCHES_ID_MAP = new net.minecraft.core.IdMap<>() {
+ private final java.util.concurrent.atomic.AtomicInteger idCounter = new java.util.concurrent.atomic.AtomicInteger(BuiltInRegistries.ITEM.size());
+ private final it.unimi.dsi.fastutil.objects.Object2IntMap<ItemStack> itemstackToId = new it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap<>(new it.unimi.dsi.fastutil.Hash.Strategy<>() {
+ @Override
+ public int hashCode(ItemStack o) {
+ return java.util.Objects.hash(o.getItem(), o.getTag());
+ }
+
+ @Override
+ public boolean equals(@Nullable ItemStack a, @Nullable ItemStack b) {
+ if (a == null || b == null) {
+ return false;
+ }
+ return ItemStack.matches(a, b);
+ }
+ });
+ private final it.unimi.dsi.fastutil.ints.Int2ObjectMap<net.minecraft.world.item.ItemStack> idToItemstack = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>();
+
+ @Override
+ public int getId(ItemStack value) {
+ if (!this.itemstackToId.containsKey(value)) {
+ final int id = this.idCounter.incrementAndGet();
+ final ItemStack copy = value.copy();
+ this.itemstackToId.put(copy, id);
+ this.idToItemstack.put(id, copy);
+ return id;
+ }
+ return this.itemstackToId.getInt(value);
+ }
+
+ @Override
+ public @Nullable ItemStack byId(int index) {
+ return this.idToItemstack.get(index);
+ }
+
+ @Override
+ public int size() {
+ return this.itemstackToId.size();
+ }
+
+ @Override
+ public java.util.Iterator<net.minecraft.world.item.ItemStack> iterator() {
+ return this.idToItemstack.values().iterator();
}
+ };
+ public static int getExactStackingIndex(ItemStack stack) {
+ return EXACT_MATCHES_ID_MAP.getId(stack);
+ // PaperPR end
}
public static int getStackingIndex(ItemStack stack) {
@@ -83,6 +137,12 @@ public class StackedContents {
}
public static ItemStack fromStackingIndex(int itemId) {
+ // PaperPR start
+ if (itemId > BuiltInRegistries.ITEM.size()) {
+ final ItemStack stack = EXACT_MATCHES_ID_MAP.byId(itemId);
+ return stack == null ? ItemStack.EMPTY : stack.copy();
+ }
+ // PaperPR end
return itemId == 0 ? ItemStack.EMPTY : new ItemStack(Item.byId(itemId));
}
diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java
index fa885337085348308604e50049ecc5bb52023884..6b070ad6aa1b62a66fb85d54042af9d64cdcea7f 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java
@@ -72,6 +72,7 @@ public abstract class AbstractArrow extends Projectile {
private IntOpenHashSet piercingIgnoreEntityIds;
@Nullable
private List<Entity> piercedAndKilledEntities;
+ public int lootingLevel; // Purpur
// Spigot Start
@Override
@@ -312,7 +313,7 @@ public abstract class AbstractArrow extends Projectile {
Vec3 vec3d = this.getDeltaMovement();
this.setDeltaMovement(vec3d.multiply((double) (this.random.nextFloat() * 0.2F), (double) (this.random.nextFloat() * 0.2F), (double) (this.random.nextFloat() * 0.2F)));
- this.life = 0;
+ if (this.level().purpurConfig.arrowMovementResetsDespawnCounter) this.life = 0; // Purpur - do not reset despawn counter
}
@Override
@@ -612,6 +613,12 @@ public abstract class AbstractArrow extends Projectile {
this.knockback = punch;
}
+ // Purpur start
+ public void setLootingLevel(int looting) {
+ this.lootingLevel = looting;
+ }
+ // Purpur end
+
public int getKnockback() {
return this.knockback;
}
diff --git a/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java b/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java
index 251e6bc87eb02ec4372062ef22b21249ac5164ff..6b60597a0e837054b0d1891c1e6e5e7cd3af9539 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java
@@ -16,20 +16,20 @@ public class LargeFireball extends Fireball {
public LargeFireball(EntityType<? extends LargeFireball> type, Level world) {
super(type, world);
- isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit
+ isIncendiary = this.level().purpurConfig.fireballsBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit // Purpur
}
public LargeFireball(Level world, LivingEntity owner, double velocityX, double velocityY, double velocityZ, int explosionPower) {
super(EntityType.FIREBALL, owner, velocityX, velocityY, velocityZ, world);
this.explosionPower = explosionPower;
- isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit
+ isIncendiary = this.level().purpurConfig.fireballsBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit // Purpur
}
@Override
protected void onHit(HitResult hitResult) {
super.onHit(hitResult);
if (!this.level().isClientSide) {
- boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
+ boolean flag = this.level().purpurConfig.fireballsBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur
// CraftBukkit start - fire ExplosionPrimeEvent
ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity());
diff --git a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java
index 0bbe853f7df93f9dcd2b21d762939f8b6be069aa..7db9844083703944f59e112c6dc5e1a5e062d31c 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java
@@ -26,6 +26,12 @@ public class LlamaSpit extends Projectile {
this.setPos(owner.getX() - (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(owner.yBodyRot * 0.017453292F), owner.getEyeY() - 0.10000000149011612D, owner.getZ() + (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(owner.yBodyRot * 0.017453292F));
}
+ // Purpur start
+ public void super_tick() {
+ super.tick();
+ }
+ // Purpur end
+
@Override
public void tick() {
super.tick();
diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
index 459aee61b519a40d9136546c0d9356562f5757c8..134b5758e5c6335a52ffb2703c3fa98dc792948e 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
@@ -319,6 +319,6 @@ public abstract class Projectile extends Entity implements TraceableEntity {
public boolean mayInteract(Level world, BlockPos pos) {
Entity entity = this.getOwner();
- return entity instanceof Player ? entity.mayInteract(world, pos) : entity == null || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
+ return entity instanceof Player ? entity.mayInteract(world, pos) : entity == null || world.purpurConfig.projectilesBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
}
}
diff --git a/src/main/java/net/minecraft/world/entity/projectile/ProjectileUtil.java b/src/main/java/net/minecraft/world/entity/projectile/ProjectileUtil.java
index cc0a3d9794d05b6bc6ab05f4f2ab8d83134b181d..e1f918d0bd2a70db1aba8bda8717149f58766825 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/ProjectileUtil.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/ProjectileUtil.java
@@ -33,7 +33,7 @@ public final class ProjectileUtil {
return getHitResult(vec32, entity, predicate, vec3, level);
}
- private static HitResult getHitResult(Vec3 pos, Entity entity, Predicate<Entity> predicate, Vec3 velocity, Level world) {
+ public static HitResult getHitResult(Vec3 pos, Entity entity, Predicate<Entity> predicate, Vec3 velocity, Level world) { // Purpur - private -> public
Vec3 vec3 = pos.add(velocity);
HitResult hitResult = world.clip(new ClipContext(pos, vec3, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, entity));
if (hitResult.getType() != HitResult.Type.MISS) {
diff --git a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java
index 04c2ea1ff44af72ae48e2d6b7b912b1c14285038..7839d856b3f924f0c87b298d9a1e3b15ab0693d6 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java
@@ -24,7 +24,7 @@ public class SmallFireball extends Fireball {
super(EntityType.SMALL_FIREBALL, owner, velocityX, velocityY, velocityZ, world);
// CraftBukkit start
if (this.getOwner() != null && this.getOwner() instanceof Mob) {
- isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
+ isIncendiary = this.level().purpurConfig.fireballsBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur
}
// CraftBukkit end
}
diff --git a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java
index 718e120c9768cf716b32d3d652f53f1dda925168..440d3d72d8b2dac14f83a83caa5ae9dbf3e979b6 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java
@@ -53,10 +53,40 @@ public class Snowball extends ThrowableItemProjectile {
protected void onHitEntity(EntityHitResult entityHitResult) {
super.onHitEntity(entityHitResult);
Entity entity = entityHitResult.getEntity();
- int i = entity instanceof Blaze ? 3 : 0;
+ int i = entity.level().purpurConfig.snowballDamage >= 0 ? entity.level().purpurConfig.snowballDamage : entity instanceof Blaze ? 3 : 0; // Purpur
entity.hurt(this.damageSources().thrown(this, this.getOwner()), (float)i);
}
+ // Purpur start - borrowed and modified code from ThrownPotion#onHitBlock and ThrownPotion#dowseFire
+ @Override
+ protected void onHitBlock(net.minecraft.world.phys.BlockHitResult blockHitResult) {
+ super.onHitBlock(blockHitResult);
+
+ if (!this.level().isClientSide) {
+ net.minecraft.core.BlockPos blockposition = blockHitResult.getBlockPos();
+ net.minecraft.core.BlockPos blockposition1 = blockposition.relative(blockHitResult.getDirection());
+
+ net.minecraft.world.level.block.state.BlockState iblockdata = this.level().getBlockState(blockposition);
+
+ if (this.level().purpurConfig.snowballExtinguishesFire && this.level().getBlockState(blockposition1).is(net.minecraft.world.level.block.Blocks.FIRE)) {
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition1, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) {
+ this.level().removeBlock(blockposition1, false);
+ }
+ } else if (this.level().purpurConfig.snowballExtinguishesCandles && net.minecraft.world.level.block.AbstractCandleBlock.isLit(iblockdata)) {
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.setValue(net.minecraft.world.level.block.AbstractCandleBlock.LIT, false))) {
+ net.minecraft.world.level.block.AbstractCandleBlock.extinguish(null, iblockdata, this.level(), blockposition);
+ }
+ } else if (this.level().purpurConfig.snowballExtinguishesCampfires && net.minecraft.world.level.block.CampfireBlock.isLitCampfire(iblockdata)) {
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false))) {
+ this.level().levelEvent(null, 1009, blockposition, 0);
+ net.minecraft.world.level.block.CampfireBlock.dowse(this.getOwner(), this.level(), blockposition, iblockdata);
+ this.level().setBlockAndUpdate(blockposition, iblockdata.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false));
+ }
+ }
+ }
+ }
+ // Purpur end
+
@Override
protected void onHit(HitResult hitResult) {
super.onHit(hitResult);
diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
index e8114d89a3129e56c0329410a49ded63cc77cb4c..d7359c675707eade00f9b737fd67ef8d066e813f 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
@@ -68,10 +68,11 @@ public class ThrownEnderpearl extends ThrowableItemProjectile {
Bukkit.getPluginManager().callEvent(teleEvent);
if (!teleEvent.isCancelled() && entityplayer.connection.isAcceptingMessages()) {
- if (this.random.nextFloat() < 0.05F && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) {
+ if (this.random.nextFloat() < this.level().purpurConfig.enderPearlEndermiteChance && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { // Purpur
Endermite entityendermite = (Endermite) EntityType.ENDERMITE.create(this.level());
if (entityendermite != null) {
+ entityendermite.setPlayerSpawned(true); // Purpur
entityendermite.moveTo(entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot());
this.level().addFreshEntity(entityendermite, CreatureSpawnEvent.SpawnReason.ENDER_PEARL);
}
@@ -84,7 +85,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile {
entityplayer.connection.teleport(teleEvent.getTo());
entity.resetFallDistance();
CraftEventFactory.entityDamage = this;
- entity.hurt(this.damageSources().fall(), 5.0F);
+ entity.hurt(this.damageSources().fall(), this.level().purpurConfig.enderPearlDamage); // Purpur
CraftEventFactory.entityDamage = null;
}
// CraftBukkit end
diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java
index a6bc277b6589dd7104566542733327822d6299a4..dde841cc09ba4a3575a462b03537887551d47ba5 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java
@@ -62,7 +62,7 @@ public class ThrownTrident extends AbstractArrow {
Entity entity = this.getOwner();
byte b0 = (Byte) this.entityData.get(ThrownTrident.ID_LOYALTY);
- if (b0 > 0 && (this.dealtDamage || this.isNoPhysics()) && entity != null) {
+ if (b0 > 0 && (this.dealtDamage || this.isNoPhysics() || (level().purpurConfig.tridentLoyaltyVoidReturnHeight < 0.0D && getY() < level().purpurConfig.tridentLoyaltyVoidReturnHeight)) && entity != null) { // Purpur
if (!this.isAcceptibleReturnOwner()) {
if (!this.level().isClientSide && this.pickup == AbstractArrow.Pickup.ALLOWED) {
this.spawnAtLocation(this.getPickupItem(), 0.1F);
diff --git a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java
index 06d1bdb9bd124b201c36d284c50d22bf50d3735a..b4687453256ead43cf5288994316c7bf946b86df 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java
@@ -97,7 +97,7 @@ public class WitherSkull extends AbstractHurtingProjectile {
if (!this.level().isClientSide) {
// CraftBukkit start
// this.level().explode(this, this.getX(), this.getY(), this.getZ(), 1.0F, false, World.a.MOB);
- ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 1.0F, false);
+ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), this.level().purpurConfig.witherExplosionRadius, false); // Purpur
this.level().getCraftServer().getPluginManager().callEvent(event);
if (!event.isCancelled()) {
@@ -109,6 +109,17 @@ public class WitherSkull extends AbstractHurtingProjectile {
}
+ @Override
+ public boolean canHitEntity(Entity target) {
+ // do not hit rider
+ return target != this.getRider() && super.canHitEntity(target);
+ }
+
+ @Override
+ public boolean canSaveToDisk() {
+ return false;
+ }
+
@Override
public boolean isPickable() {
return false;
diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java
index 57fdcdaf54fd1c92a6e51a3a81789029096e5abe..822dd4265ce02252afadbc652064450b7dfd7e0d 100644
--- a/src/main/java/net/minecraft/world/entity/raid/Raider.java
+++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java
@@ -319,7 +319,7 @@ public abstract class Raider extends PatrollingMonster {
@Override
public boolean canUse() {
- if (!this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items
+ if ((!this.mob.level().purpurConfig.pillagerBypassMobGriefing && !this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items // Purpur
Raid raid = this.mob.getCurrentRaid();
if (this.mob.hasActiveRaid() && !this.mob.getCurrentRaid().isOver() && this.mob.canBeLeader() && !ItemStack.matches(this.mob.getItemBySlot(EquipmentSlot.HEAD), Raid.getLeaderBannerInstance())) {
diff --git a/src/main/java/net/minecraft/world/entity/raid/Raids.java b/src/main/java/net/minecraft/world/entity/raid/Raids.java
index 41457c9f27b18fa2734a6cca297ec5186470e82f..94356e0541f8f4da68211fa533347cc97d4f3518 100644
--- a/src/main/java/net/minecraft/world/entity/raid/Raids.java
+++ b/src/main/java/net/minecraft/world/entity/raid/Raids.java
@@ -28,6 +28,7 @@ import net.minecraft.world.phys.Vec3;
public class Raids extends SavedData {
private static final String RAID_FILE_ID = "raids";
+ public final Map<java.util.UUID, Integer> playerCooldowns = Maps.newHashMap();
public final Map<Integer, Raid> raidMap = Maps.newHashMap();
private final ServerLevel level;
private int nextAvailableID;
@@ -45,6 +46,17 @@ public class Raids extends SavedData {
public void tick() {
++this.tick;
+ // Purpur start
+ if (level.purpurConfig.raidCooldownSeconds != 0 && this.tick % 20 == 0) {
+ com.google.common.collect.ImmutableMap.copyOf(playerCooldowns).forEach((uuid, i) -> {
+ if (i < 1) {
+ playerCooldowns.remove(uuid);
+ } else {
+ playerCooldowns.put(uuid, i - 1);
+ }
+ });
+ }
+ // Purpur end
Iterator iterator = this.raidMap.values().iterator();
while (iterator.hasNext()) {
@@ -129,11 +141,13 @@ public class Raids extends SavedData {
}
if (flag) {
+ if (level.purpurConfig.raidCooldownSeconds != 0 && playerCooldowns.containsKey(player.getUUID())) return null; // Purpur
// CraftBukkit start
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(raid, player)) {
player.removeEffect(MobEffects.BAD_OMEN);
return null;
}
+ if (level.purpurConfig.raidCooldownSeconds != 0) playerCooldowns.put(player.getUUID(), level.purpurConfig.raidCooldownSeconds); // Purpur
if (!this.raidMap.containsKey(raid.getId())) {
this.raidMap.put(raid.getId(), raid);
diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java
index 9948a28dae4edba877c13ef0156be5ff58df3fa2..b99b94c6ec4767aba16d82eaca8b2761d779b226 100644
--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java
+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java
@@ -108,11 +108,13 @@ public abstract class AbstractMinecart extends Entity {
private double flyingY = 0.949999988079071D; // Paper - restore vanilla precision
private double flyingZ = 0.949999988079071D; // Paper - restore vanilla precision
public double maxSpeed = 0.4D;
+ public double storedMaxSpeed; // Purpur
// CraftBukkit end
protected AbstractMinecart(EntityType<?> type, Level world) {
super(type, world);
this.blocksBuilding = true;
+ if (world != null) maxSpeed = storedMaxSpeed = world.purpurConfig.minecartMaxSpeed; // Purpur
}
protected AbstractMinecart(EntityType<?> type, Level world, double x, double y, double z) {
@@ -335,6 +337,12 @@ public abstract class AbstractMinecart extends Entity {
@Override
public void tick() {
+ // Purpur start
+ if (storedMaxSpeed != level().purpurConfig.minecartMaxSpeed) {
+ maxSpeed = storedMaxSpeed = level().purpurConfig.minecartMaxSpeed;
+ }
+ // Purpur end
+
// CraftBukkit start
double prevX = this.getX();
double prevY = this.getY();
@@ -499,16 +507,62 @@ public abstract class AbstractMinecart extends Entity {
public void activateMinecart(int x, int y, int z, boolean powered) {}
+ // Purpur start
+ private Double lastSpeed;
+
+ public double getControllableSpeed() {
+ BlockState blockState = level().getBlockState(this.blockPosition());
+ if (!blockState.isSolid()) {
+ blockState = level().getBlockState(this.blockPosition().relative(Direction.DOWN));
+ }
+ Double speed = level().purpurConfig.minecartControllableBlockSpeeds.get(blockState.getBlock());
+ if (!blockState.isSolid()) {
+ speed = lastSpeed;
+ }
+ if (speed == null) {
+ speed = level().purpurConfig.minecartControllableBaseSpeed;
+ }
+ return lastSpeed = speed;
+ }
+ // Purpur end
+
protected void comeOffTrack() {
double d0 = this.getMaxSpeed();
Vec3 vec3d = this.getDeltaMovement();
this.setDeltaMovement(Mth.clamp(vec3d.x, -d0, d0), vec3d.y, Mth.clamp(vec3d.z, -d0, d0));
+
+ // Purpur start
+ if (level().purpurConfig.minecartControllable && !isInWater() && !isInLava() && !passengers.isEmpty()) {
+ Entity passenger = passengers.get(0);
+ if (passenger instanceof Player) {
+ Player player = (Player) passenger;
+ if (player.jumping && this.onGround) {
+ setDeltaMovement(new Vec3(getDeltaMovement().x, level().purpurConfig.minecartControllableHopBoost, getDeltaMovement().z));
+ }
+ if (player.zza != 0.0F) {
+ Vector velocity = player.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(getControllableSpeed());
+ if (player.zza < 0.0) {
+ velocity.multiply(-0.5);
+ }
+ setDeltaMovement(new Vec3(velocity.getX(), getDeltaMovement().y, velocity.getZ()));
+ }
+ this.setYRot(passenger.getYRot() - 90);
+ maxUpStep = level().purpurConfig.minecartControllableStepHeight;
+ } else {
+ maxUpStep = 0.0F;
+ }
+ } else {
+ maxUpStep = 0.0F;
+ }
+ // Purpur end
+
if (this.onGround()) {
// CraftBukkit start - replace magic numbers with our variables
this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.derailedX, this.getDeltaMovement().y * this.derailedY, this.getDeltaMovement().z * this.derailedZ));
// CraftBukkit end
}
+ else if (level().purpurConfig.minecartControllable) setDeltaMovement(new Vec3(getDeltaMovement().x * derailedX, getDeltaMovement().y, getDeltaMovement().z * derailedZ)); // Purpur
this.move(MoverType.SELF, this.getDeltaMovement());
if (!this.onGround()) {
@@ -670,7 +724,7 @@ public abstract class AbstractMinecart extends Entity {
if (d18 > 0.01D) {
double d20 = 0.06D;
- this.setDeltaMovement(vec3d4.add(vec3d4.x / d18 * 0.06D, 0.0D, vec3d4.z / d18 * 0.06D));
+ this.setDeltaMovement(vec3d4.add(vec3d4.x / d18 * this.level().purpurConfig.poweredRailBoostModifier, 0.0D, vec3d4.z / d18 * this.level().purpurConfig.poweredRailBoostModifier)); // Purpur
} else {
Vec3 vec3d5 = this.getDeltaMovement();
double d21 = vec3d5.x;
diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java
index 2c5658df753ebc08f8531d4bdf22ff8f6ca77e94..a7ab9e2284d4a1ae6f5601adc3dfac534bce26fc 100644
--- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java
+++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java
@@ -223,7 +223,13 @@ public class Boat extends Entity implements VariantHolder<Boat.Type> {
}
protected void destroy(DamageSource source) {
- this.spawnAtLocation((ItemLike) this.getDropItem());
+ // Purpur start
+ final ItemStack boat = new ItemStack(this.getDropItem());
+ if (this.level().purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) {
+ boat.setHoverName(this.getCustomName());
+ }
+ this.spawnAtLocation(boat);
+ // Purpur end
}
@Override
@@ -541,6 +547,7 @@ public class Boat extends Entity implements VariantHolder<Boat.Type> {
if (f > 0.0F) {
this.landFriction = f;
+ if (level().purpurConfig.boatEjectPlayersOnLand) ejectPassengers(); // Purpur
return Boat.Status.ON_LAND;
} else {
return Boat.Status.IN_AIR;
diff --git a/src/main/java/net/minecraft/world/food/FoodData.java b/src/main/java/net/minecraft/world/food/FoodData.java
index 2038df72f8d7d33d4105de8129628daf21de6f0f..e54af9ff2a786e919b8261aa27509be942e70261 100644
--- a/src/main/java/net/minecraft/world/food/FoodData.java
+++ b/src/main/java/net/minecraft/world/food/FoodData.java
@@ -33,8 +33,10 @@ public class FoodData {
// CraftBukkit end
public void eat(int food, float saturationModifier) {
+ int oldValue = this.foodLevel; // Purpur
this.foodLevel = Math.min(food + this.foodLevel, 20);
this.saturationLevel = Math.min(this.saturationLevel + (float) food * saturationModifier * 2.0F, (float) this.foodLevel);
+ if (this.entityhuman.level().purpurConfig.playerBurpWhenFull && this.foodLevel == 20 && oldValue < 20) this.entityhuman.burpDelay = this.entityhuman.level().purpurConfig.playerBurpDelay; // Purpur
}
public void eat(Item item, ItemStack stack) {
@@ -100,7 +102,7 @@ public class FoodData {
++this.tickTimer;
if (this.tickTimer >= this.starvationRate) { // CraftBukkit - add regen rate manipulation
if (player.getHealth() > 10.0F || enumdifficulty == Difficulty.HARD || player.getHealth() > 1.0F && enumdifficulty == Difficulty.NORMAL) {
- player.hurt(player.damageSources().starve(), 1.0F);
+ player.hurt(player.damageSources().starve(), player.level().purpurConfig.hungerStarvationDamage); // Purpur
}
this.tickTimer = 0;
diff --git a/src/main/java/net/minecraft/world/food/FoodProperties.java b/src/main/java/net/minecraft/world/food/FoodProperties.java
index 9967ba762567631f2bdb1e4f8fe16a13ea927b46..6c945ae8fe8b1517e312c688f829fab41f12d9f4 100644
--- a/src/main/java/net/minecraft/world/food/FoodProperties.java
+++ b/src/main/java/net/minecraft/world/food/FoodProperties.java
@@ -2,15 +2,22 @@ package net.minecraft.world.food;
import com.google.common.collect.Lists;
import com.mojang.datafixers.util.Pair;
+
+import java.util.ArrayList;
import java.util.List;
import net.minecraft.world.effect.MobEffectInstance;
public class FoodProperties {
- private final int nutrition;
- private final float saturationModifier;
- private final boolean isMeat;
- private final boolean canAlwaysEat;
- private final boolean fastFood;
+ // Purpur start
+ private int nutrition; public void setNutrition(int nutrition) { this.nutrition = nutrition; }
+ private float saturationModifier; public void setSaturationModifier(float saturation) { this.saturationModifier = saturation; }
+ private boolean isMeat; public void setIsMeat(boolean isMeat) { this.isMeat = isMeat; }
+ private boolean canAlwaysEat; public void setCanAlwaysEat(boolean canAlwaysEat) { this.canAlwaysEat = canAlwaysEat; }
+ private boolean fastFood; public void setFastFood(boolean isFastFood) { this.fastFood = isFastFood; }
+ public FoodProperties copy() {
+ return new FoodProperties(this.nutrition, this.saturationModifier, this.isMeat, this.canAlwaysEat, this.fastFood, new ArrayList<>(this.effects));
+ }
+ // Purpur end
private final List<Pair<MobEffectInstance, Float>> effects;
FoodProperties(int hunger, float saturationModifier, boolean meat, boolean alwaysEdible, boolean snack, List<Pair<MobEffectInstance, Float>> statusEffects) {
diff --git a/src/main/java/net/minecraft/world/food/Foods.java b/src/main/java/net/minecraft/world/food/Foods.java
index b16d9e2eaa589f19c563ee70b1a56d67dbcdecb0..71beab673f04cd051c46ea37f8c847316885d38d 100644
--- a/src/main/java/net/minecraft/world/food/Foods.java
+++ b/src/main/java/net/minecraft/world/food/Foods.java
@@ -4,6 +4,9 @@ import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
public class Foods {
+ public static final java.util.Map<String, FoodProperties> ALL_PROPERTIES = new java.util.HashMap<>(); // Purpur
+ public static final java.util.Map<String, FoodProperties> DEFAULT_PROPERTIES = new java.util.HashMap<>(); // Purpur
+
public static final FoodProperties APPLE = (new FoodProperties.Builder()).nutrition(4).saturationMod(0.3F).build();
public static final FoodProperties BAKED_POTATO = (new FoodProperties.Builder()).nutrition(5).saturationMod(0.6F).build();
public static final FoodProperties BEEF = (new FoodProperties.Builder()).nutrition(3).saturationMod(0.3F).meat().build();
diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
index 71b7a091e56dd68da280d13318a393170967b042..9614a0b5b32d88a5b212e33af649ca1aa2a22dfd 100644
--- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java
@@ -76,6 +76,7 @@ public abstract class AbstractContainerMenu {
@Nullable
private ContainerSynchronizer synchronizer;
private boolean suppressRemoteUpdates;
+ @javax.annotation.Nullable protected ItemStack activeQuickItem = null; // Purpur
// CraftBukkit start
public boolean checkReachable = true;
diff --git a/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java
index 35b41bceeba72c3896c91c2605bac3b0bf9c54e9..5c028f957661089ff502109c996692856b12ee27 100644
--- a/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java
@@ -145,7 +145,13 @@ public abstract class AbstractFurnaceMenu extends RecipeBookMenu<Container> {
} else if (slot != 1 && slot != 0) {
if (this.canSmelt(itemstack1)) {
if (!this.moveItemStackTo(itemstack1, 0, 1, false)) {
- return ItemStack.EMPTY;
+ // Purpur start - fix #625
+ if (this.isFuel(itemstack1)) {
+ if (!this.moveItemStackTo(itemstack1, 1, 2, false)) {
+ return ItemStack.EMPTY;
+ }
+ }
+ // Purpur end
}
} else if (this.isFuel(itemstack1)) {
if (!this.moveItemStackTo(itemstack1, 1, 2, false)) {
diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java
index e0c3a4ba27e21c3692e601acd0af60873bcbb84c..531b911c1bcdd3735529ee18f2bb0ccdf4ad6089 100644
--- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java
@@ -23,6 +23,13 @@ import org.slf4j.Logger;
import org.bukkit.craftbukkit.inventory.CraftInventoryView;
// CraftBukkit end
+// Purpur start
+import net.minecraft.nbt.IntTag;
+import net.minecraft.network.protocol.game.ClientboundContainerSetDataPacket;
+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
+import net.minecraft.server.level.ServerPlayer;
+// Purpur end
+
public class AnvilMenu extends ItemCombinerMenu {
public static final int INPUT_SLOT = 0;
@@ -51,6 +58,8 @@ public class AnvilMenu extends ItemCombinerMenu {
public int maximumRepairCost = 40;
private CraftInventoryView bukkitEntity;
// CraftBukkit end
+ public boolean bypassCost = false; // Purpur
+ public boolean canDoUnsafeEnchants = false; // Purpur
public AnvilMenu(int syncId, Inventory inventory) {
this(syncId, inventory, ContainerLevelAccess.NULL);
@@ -78,12 +87,15 @@ public class AnvilMenu extends ItemCombinerMenu {
@Override
protected boolean mayPickup(Player player, boolean present) {
- return (player.getAbilities().instabuild || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && present; // CraftBukkit - allow cost 0 like a free item
+ return (player.getAbilities().instabuild || player.experienceLevel >= this.cost.get()) && (bypassCost || this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST) && present; // CraftBukkit - allow cost 0 like a free item // Purpur
}
@Override
protected void onTake(Player player, ItemStack stack) {
+ ItemStack itemstack = activeQuickItem == null ? stack : activeQuickItem; // Purpur
+ if (org.purpurmc.purpur.event.inventory.AnvilTakeResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)).callEvent(); // Purpur
if (!player.getAbilities().instabuild) {
+ if (bypassCost) ((ServerPlayer) player).lastSentExp = -1; else // Purpur
player.giveExperienceLevels(-this.cost.get());
}
@@ -134,6 +146,12 @@ public class AnvilMenu extends ItemCombinerMenu {
@Override
public void createResult() {
+ // Purpur start
+ bypassCost = false;
+ canDoUnsafeEnchants = false;
+ if (org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent(getBukkitView()).callEvent();
+ // Purpur end
+
ItemStack itemstack = this.inputSlots.getItem(0);
this.cost.set(1);
@@ -210,7 +228,8 @@ public class AnvilMenu extends ItemCombinerMenu {
int i2 = (Integer) map1.get(enchantment);
i2 = l1 == i2 ? i2 + 1 : Math.max(i2, l1);
- boolean flag3 = enchantment.canEnchant(itemstack);
+ boolean flag3 = canDoUnsafeEnchants || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants && org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants) || enchantment.canEnchant(itemstack); // Purpur
+ boolean flag4 = true; // Purpur
if (this.player.getAbilities().instabuild || itemstack.is(Items.ENCHANTED_BOOK)) {
flag3 = true;
@@ -222,16 +241,20 @@ public class AnvilMenu extends ItemCombinerMenu {
Enchantment enchantment1 = (Enchantment) iterator1.next();
if (enchantment1 != enchantment && !enchantment.isCompatibleWith(enchantment1)) {
- flag3 = false;
+ flag4 = canDoUnsafeEnchants || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants && org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants); // Purpur flag3 -> flag4
+ if (!flag4 && org.purpurmc.purpur.PurpurConfig.replaceIncompatibleEnchants) {
+ iterator1.remove();
+ flag4 = true;
+ }
++i;
}
}
- if (!flag3) {
+ if (!flag3 || !flag4) { // Purpur
flag2 = true;
} else {
flag1 = true;
- if (i2 > enchantment.getMaxLevel()) {
+ if ((!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants || !org.purpurmc.purpur.PurpurConfig.allowHigherEnchantsLevels) && i2 > enchantment.getMaxLevel()) { // Purpur
i2 = enchantment.getMaxLevel();
}
@@ -276,6 +299,54 @@ public class AnvilMenu extends ItemCombinerMenu {
if (!this.itemName.equals(itemstack.getHoverName().getString())) {
b1 = 1;
i += b1;
+ // Purpur start
+ if (this.player != null) {
+ org.bukkit.craftbukkit.entity.CraftHumanEntity player = this.player.getBukkitEntity();
+ String name = this.itemName;
+ boolean removeItalics = false;
+ if (player.hasPermission("purpur.anvil.remove_italics")) {
+ if (name.startsWith("&r")) {
+ name = name.substring(2);
+ removeItalics = true;
+ } else if (name.startsWith("<r>")) {
+ name = name.substring(3);
+ removeItalics = true;
+ } else if (name.startsWith("<reset>")) {
+ name = name.substring(7);
+ removeItalics = true;
+ }
+ }
+ if (this.player.level().purpurConfig.anvilAllowColors) {
+ if (player.hasPermission("purpur.anvil.color")) {
+ java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?i)&([0-9a-fr])").matcher(name);
+ while (matcher.find()) {
+ String match = matcher.group(1);
+ name = name.replace("&" + match, "\u00a7" + match.toLowerCase(java.util.Locale.ROOT));
+ }
+ //name = name.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1");
+ }
+ if (player.hasPermission("purpur.anvil.format")) {
+ java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?i)&([k-or])").matcher(name);
+ while (matcher.find()) {
+ String match = matcher.group(1);
+ name = name.replace("&" + match, "\u00a7" + match.toLowerCase(java.util.Locale.ROOT));
+ }
+ //name = name.replaceAll("(?i)&([l-or])", "\u00a7$1");
+ }
+ }
+ net.kyori.adventure.text.Component component;
+ if (this.player.level().purpurConfig.anvilColorsUseMiniMessage && player.hasPermission("purpur.anvil.minimessage")) {
+ component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.bukkit.ChatColor.stripColor(name));
+ } else {
+ component = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(name);
+ }
+ if (removeItalics) {
+ component = component.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false);
+ }
+ itemstack1.setHoverName(io.papermc.paper.adventure.PaperAdventure.asVanilla(component));
+ }
+ else
+ // Purpur end
itemstack1.setHoverName(Component.literal(this.itemName));
}
} else if (itemstack.hasCustomHoverName()) {
@@ -293,6 +364,13 @@ public class AnvilMenu extends ItemCombinerMenu {
this.cost.set(this.maximumRepairCost - 1); // CraftBukkit
}
+ // Purpur start
+ if (bypassCost && cost.get() >= maximumRepairCost) {
+ itemstack.addTagElement("Purpur.realCost", IntTag.valueOf(cost.get()));
+ cost.set(maximumRepairCost - 1);
+ }
+ // Purpur end
+
if (this.cost.get() >= this.maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit
itemstack1 = ItemStack.EMPTY;
}
@@ -315,11 +393,17 @@ public class AnvilMenu extends ItemCombinerMenu {
org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemstack1); // CraftBukkit
sendAllDataToRemote(); // CraftBukkit - SPIGOT-6686: Always send completed inventory to stay in sync with client
this.broadcastChanges();
+ // Purpur start
+ if ((canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants) && itemstack1 != ItemStack.EMPTY) {
+ ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(containerId, incrementStateId(), 2, itemstack1));
+ ((ServerPlayer) player).connection.send(new ClientboundContainerSetDataPacket(containerId, 0, cost.get()));
+ }
+ // Purpur end
}
}
public static int calculateIncreasedRepairCost(int cost) {
- return cost * 2 + 1;
+ return org.purpurmc.purpur.PurpurConfig.anvilCumulativeCost ? cost * 2 + 1 : 0;
}
public boolean setItemName(String newItemName) {
diff --git a/src/main/java/net/minecraft/world/inventory/ChestMenu.java b/src/main/java/net/minecraft/world/inventory/ChestMenu.java
index 0dbfd23bbfc6ad203f048142f8c90ef741849fe1..9a80427d2bb470b6b1638e59aba57216676dcbd2 100644
--- a/src/main/java/net/minecraft/world/inventory/ChestMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/ChestMenu.java
@@ -67,10 +67,30 @@ public class ChestMenu extends AbstractContainerMenu {
return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, 6);
}
+ // Purpur start
+ public static ChestMenu oneRow(int syncId, Inventory playerInventory, Container inventory) {
+ return new ChestMenu(MenuType.GENERIC_9x1, syncId, playerInventory, inventory, 1);
+ }
+
+ public static ChestMenu twoRows(int syncId, Inventory playerInventory, Container inventory) {
+ return new ChestMenu(MenuType.GENERIC_9x2, syncId, playerInventory, inventory, 2);
+ }
+ // Purpur end
+
public static ChestMenu threeRows(int syncId, Inventory playerInventory, Container inventory) {
return new ChestMenu(MenuType.GENERIC_9x3, syncId, playerInventory, inventory, 3);
}
+ // Purpur start
+ public static ChestMenu fourRows(int syncId, Inventory playerInventory, Container inventory) {
+ return new ChestMenu(MenuType.GENERIC_9x4, syncId, playerInventory, inventory, 4);
+ }
+
+ public static ChestMenu fiveRows(int syncId, Inventory playerInventory, Container inventory) {
+ return new ChestMenu(MenuType.GENERIC_9x5, syncId, playerInventory, inventory, 5);
+ }
+ // Purpur end
+
public static ChestMenu sixRows(int syncId, Inventory playerInventory, Container inventory) {
return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, inventory, 6);
}
diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java
index 93e8316f9d64625dcc4df0644a2187bcc884ef65..1711406996ec4a9cb7b1838bd2446e5cf802e043 100644
--- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java
@@ -38,6 +38,12 @@ import org.bukkit.event.enchantment.PrepareItemEnchantEvent;
import org.bukkit.entity.Player;
// CraftBukkit end
+// Purpur start
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.entity.EnchantmentTableBlockEntity;
+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
+// Purpur end
+
public class EnchantmentMenu extends AbstractContainerMenu {
private final Container enchantSlots;
@@ -71,6 +77,22 @@ public class EnchantmentMenu extends AbstractContainerMenu {
return context.getLocation();
}
// CraftBukkit end
+
+ // Purpur start
+ @Override
+ public void onClose(CraftHumanEntity who) {
+ super.onClose(who);
+
+ if (who.getHandle().level().purpurConfig.enchantmentTableLapisPersists) {
+ access.execute((level, pos) -> {
+ BlockEntity blockEntity = level.getBlockEntity(pos);
+ if (blockEntity instanceof EnchantmentTableBlockEntity enchantmentTable) {
+ enchantmentTable.setLapis(this.getItem(1).getCount());
+ }
+ });
+ }
+ }
+ // Purpur end
};
this.random = RandomSource.create();
this.enchantmentSeed = DataSlot.standalone();
@@ -96,6 +118,17 @@ public class EnchantmentMenu extends AbstractContainerMenu {
}
});
+ // Purpur start
+ access.execute((level, pos) -> {
+ if (level.purpurConfig.enchantmentTableLapisPersists) {
+ BlockEntity blockEntity = level.getBlockEntity(pos);
+ if (blockEntity instanceof EnchantmentTableBlockEntity enchantmentTable) {
+ this.getSlot(1).set(new ItemStack(Items.LAPIS_LAZULI, enchantmentTable.getLapis()));
+ }
+ }
+ });
+ // Purpur end
+
int j;
for (j = 0; j < 3; ++j) {
@@ -340,6 +373,7 @@ public class EnchantmentMenu extends AbstractContainerMenu {
public void removed(net.minecraft.world.entity.player.Player player) {
super.removed(player);
this.access.execute((world, blockposition) -> {
+ if (world.purpurConfig.enchantmentTableLapisPersists) this.getSlot(1).set(ItemStack.EMPTY); // Purpur
this.clearContainer(player, this.enchantSlots);
});
}
diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java
index b56766ff0e61691294b40ea8c2370940c0e8b640..23a8522b80475ad29ffb4afd2f4836acda2538e3 100644
--- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java
@@ -95,9 +95,11 @@ public class GrindstoneMenu extends AbstractContainerMenu {
@Override
public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) {
+ ItemStack itemstack = activeQuickItem == null ? stack : activeQuickItem; // Purpur
context.execute((world, blockposition) -> {
+ org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent grindstoneTakeResultEvent = new org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), this.getExperienceAmount(world)); grindstoneTakeResultEvent.callEvent(); // Purpur
if (world instanceof ServerLevel) {
- ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Paper
+ ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), grindstoneTakeResultEvent.getExperienceAmount(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Paper // Purpur
}
world.levelEvent(1042, blockposition, 0);
@@ -130,7 +132,7 @@ public class GrindstoneMenu extends AbstractContainerMenu {
Enchantment enchantment = (Enchantment) entry.getKey();
Integer integer = (Integer) entry.getValue();
- if (!enchantment.isCurse()) {
+ if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(enchantment)) { // Purpur
j += enchantment.getMinCost(integer);
}
}
@@ -230,7 +232,7 @@ public class GrindstoneMenu extends AbstractContainerMenu {
Entry<Enchantment, Integer> entry = (Entry) iterator.next();
Enchantment enchantment = (Enchantment) entry.getKey();
- if (!enchantment.isCurse() || EnchantmentHelper.getItemEnchantmentLevel(enchantment, itemstack2) == 0) {
+ if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(enchantment) || EnchantmentHelper.getItemEnchantmentLevel(enchantment, itemstack2) == 0) { // Purpur
itemstack2.enchant(enchantment, (Integer) entry.getValue());
}
}
@@ -250,7 +252,7 @@ public class GrindstoneMenu extends AbstractContainerMenu {
}
Map<Enchantment, Integer> map = (Map) EnchantmentHelper.getEnchantments(item).entrySet().stream().filter((entry) -> {
- return ((Enchantment) entry.getKey()).isCurse();
+ return org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains((Enchantment) entry.getKey()); // Purpur
}).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
EnchantmentHelper.setEnchantments(map, itemstack1);
@@ -266,6 +268,20 @@ public class GrindstoneMenu extends AbstractContainerMenu {
itemstack1.setRepairCost(AnvilMenu.calculateIncreasedRepairCost(itemstack1.getBaseRepairCost()));
}
+ // Purpur start
+ if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveAttributes && itemstack1.getTag() != null) {
+ for (String key : itemstack1.getTag().getAllKeys()) {
+ if (!key.equals("display")) {
+ itemstack1.getTag().remove(key);
+ }
+ }
+ }
+
+ if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveDisplay && itemstack1.getTag() != null) {
+ itemstack1.getTag().remove("display");
+ }
+ // Purpur end
+
return itemstack1;
}
@@ -327,7 +343,9 @@ public class GrindstoneMenu extends AbstractContainerMenu {
return ItemStack.EMPTY;
}
+ this.activeQuickItem = itemstack; // Purpur
slot1.onTake(player, itemstack1);
+ this.activeQuickItem = null; // Purpur
}
return itemstack;
diff --git a/src/main/java/net/minecraft/world/inventory/InventoryMenu.java b/src/main/java/net/minecraft/world/inventory/InventoryMenu.java
index c549618421c5d077c3d977d8d2064eca2acc438a..5972fa434847d24fa98b7895fd8386d20a636885 100644
--- a/src/main/java/net/minecraft/world/inventory/InventoryMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/InventoryMenu.java
@@ -4,6 +4,7 @@ import com.mojang.datafixers.util.Pair;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.Container;
+import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.player.Inventory;
@@ -95,7 +96,7 @@ public class InventoryMenu extends RecipeBookMenu<CraftingContainer> {
public boolean mayPickup(Player playerEntity) {
ItemStack itemstack = this.getItem();
- return !itemstack.isEmpty() && !playerEntity.isCreative() && EnchantmentHelper.hasBindingCurse(itemstack) ? false : super.mayPickup(playerEntity);
+ return !itemstack.isEmpty() && !playerEntity.isCreative() && EnchantmentHelper.hasBindingCurse(itemstack) ? playerEntity.level().purpurConfig.playerRemoveBindingWithWeakness && playerEntity.hasEffect(MobEffects.WEAKNESS) : super.mayPickup(playerEntity); // Purpur
}
@Override
diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java
index ff770b9ce68a62418de0c7ed389650626fa1dcb2..102739c0089ff3f6b3432f954304d43a3dfebc35 100644
--- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java
+++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java
@@ -177,7 +177,9 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu {
return ItemStack.EMPTY;
}
+ this.activeQuickItem = itemstack; // Purpur
slot1.onTake(player, itemstack1);
+ this.activeQuickItem = null; // Purpur
}
return itemstack;
diff --git a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java
index 4703f23316f82a1a942907b46d2d6dcb7d70ec37..162798f57a05b78121fa6c4fadf5adee80fbe221 100644
--- a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java
+++ b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java
@@ -30,11 +30,18 @@ public class PlayerEnderChestContainer extends SimpleContainer {
}
public PlayerEnderChestContainer(Player owner) {
- super(27);
+ super(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? 54 : 27); // Purpur
this.owner = owner;
// CraftBukkit end
}
+ // Purpur start
+ @Override
+ public int getContainerSize() {
+ return owner.sixRowEnderchestSlotCount < 0 ? super.getContainerSize() : owner.sixRowEnderchestSlotCount;
+ }
+ // Purpur end
+
public void setActiveChest(EnderChestBlockEntity blockEntity) {
this.activeChest = blockEntity;
}
diff --git a/src/main/java/net/minecraft/world/item/ArmorItem.java b/src/main/java/net/minecraft/world/item/ArmorItem.java
index d7a0cbde8f8c99276307502674c71463fbe7e89c..3500c56cb85d8c76b2acd77976d374eaf487b3b3 100644
--- a/src/main/java/net/minecraft/world/item/ArmorItem.java
+++ b/src/main/java/net/minecraft/world/item/ArmorItem.java
@@ -60,7 +60,7 @@ public class ArmorItem extends Item implements Equipable {
return false;
} else {
LivingEntity entityliving = (LivingEntity) list.get(0);
- EquipmentSlot enumitemslot = Mob.getEquipmentSlotForItem(armor);
+ EquipmentSlot enumitemslot = pointer.getLevel().purpurConfig.dispenserApplyCursedArmor ? Mob.getEquipmentSlotForItem(armor) : Mob.getSlotForDispenser(armor); if (enumitemslot == null) return false; // Purpur
ItemStack itemstack1 = armor.copyWithCount(1); // Paper - shrink below and single item in event
// CraftBukkit start
Level world = pointer.getLevel();
diff --git a/src/main/java/net/minecraft/world/item/ArmorStandItem.java b/src/main/java/net/minecraft/world/item/ArmorStandItem.java
index 7cffc64573008502bdd14ae4906fe51166b12fb3..1feafdbb48cf760cb6ebf95d5be2c32bdb1ad44f 100644
--- a/src/main/java/net/minecraft/world/item/ArmorStandItem.java
+++ b/src/main/java/net/minecraft/world/item/ArmorStandItem.java
@@ -58,6 +58,14 @@ public class ArmorStandItem extends Item {
return InteractionResult.FAIL;
}
// CraftBukkit end
+ // Purpur start
+ if (world.purpurConfig.persistentDroppableEntityDisplayNames && itemstack.hasCustomHoverName()) {
+ entityarmorstand.setCustomName(itemstack.getHoverName());
+ if (world.purpurConfig.armorstandSetNameVisible) {
+ entityarmorstand.setCustomNameVisible(true);
+ }
+ }
+ // Purpur end
worldserver.addFreshEntityWithPassengers(entityarmorstand);
world.playSound((Player) null, entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F);
entityarmorstand.gameEvent(GameEvent.ENTITY_PLACE, context.getPlayer());
diff --git a/src/main/java/net/minecraft/world/item/AxeItem.java b/src/main/java/net/minecraft/world/item/AxeItem.java
index 18898e16ec42f6b694b06e09d9174b60d62450d7..20f33b77b4a9494be227456bc742a029eb0af59b 100644
--- a/src/main/java/net/minecraft/world/item/AxeItem.java
+++ b/src/main/java/net/minecraft/world/item/AxeItem.java
@@ -33,29 +33,32 @@ public class AxeItem extends DiggerItem {
BlockPos blockPos = context.getClickedPos();
Player player = context.getPlayer();
BlockState blockState = level.getBlockState(blockPos);
- Optional<BlockState> optional = this.getStripped(blockState);
- Optional<BlockState> optional2 = WeatheringCopper.getPrevious(blockState);
- Optional<BlockState> optional3 = Optional.ofNullable(HoneycombItem.WAX_OFF_BY_BLOCK.get().get(blockState.getBlock())).map((block) -> {
- return block.withPropertiesOf(blockState);
- });
+ // Purpur start
+ Block clickedBlock = level.getBlockState(blockPos).getBlock();
+ Optional<org.purpurmc.purpur.tool.Actionable> optional = Optional.ofNullable(level.purpurConfig.axeStrippables.get(blockState.getBlock()));
+ Optional<org.purpurmc.purpur.tool.Actionable> optional2 = Optional.ofNullable(level.purpurConfig.axeWeatherables.get(blockState.getBlock()));
+ Optional<org.purpurmc.purpur.tool.Actionable> optional3 = Optional.ofNullable(level.purpurConfig.axeWaxables.get(blockState.getBlock()));
+ // Purpur end
ItemStack itemStack = context.getItemInHand();
- Optional<BlockState> optional4 = Optional.empty();
+ Optional<org.purpurmc.purpur.tool.Actionable> optional4 = Optional.empty(); // Purpur
if (optional.isPresent()) {
- level.playSound(player, blockPos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F);
+ if (!STRIPPABLES.containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound
optional4 = optional;
} else if (optional2.isPresent()) {
- level.playSound(player, blockPos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F);
+ if (!HoneycombItem.WAXABLES.get().containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound
level.levelEvent(player, 3005, blockPos, 0);
optional4 = optional2;
} else if (optional3.isPresent()) {
- level.playSound(player, blockPos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F);
+ if (!HoneycombItem.WAX_OFF_BY_BLOCK.get().containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound
level.levelEvent(player, 3004, blockPos, 0);
optional4 = optional3;
}
if (optional4.isPresent()) {
+ org.purpurmc.purpur.tool.Actionable actionable = optional4.get(); // Purpur
+ BlockState state = actionable.into().withPropertiesOf(blockState); // Purpur
// Paper start - EntityChangeBlockEvent
- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional4.get())) {
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state)) { // Purpur
return InteractionResult.PASS;
}
// Paper end
@@ -63,15 +66,22 @@ public class AxeItem extends DiggerItem {
CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack);
}
- level.setBlock(blockPos, optional4.get(), 11);
- level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, optional4.get()));
+ // Purpur start
+ level.setBlock(blockPos, state, 11);
+ actionable.drops().forEach((drop, chance) -> {
+ if (level.random.nextDouble() < chance) {
+ Block.popResourceFromFace(level, blockPos, context.getClickedFace(), new ItemStack(drop));
+ }
+ });
+ level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, state));
+ // Purpur end
if (player != null) {
itemStack.hurtAndBreak(1, player, (p) -> {
p.broadcastBreakEvent(context.getHand());
});
}
- return InteractionResult.sidedSuccess(level.isClientSide);
+ return InteractionResult.SUCCESS; // Purpur - force arm swing
} else {
return InteractionResult.PASS;
}
diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java
index e483186a5292b3b53bfb1af4d56f55fcc1a6106c..41728bcbd5ca3585da64f7c3289b26977f90c229 100644
--- a/src/main/java/net/minecraft/world/item/BlockItem.java
+++ b/src/main/java/net/minecraft/world/item/BlockItem.java
@@ -153,7 +153,24 @@ public class BlockItem extends Item {
}
protected boolean updateCustomBlockEntityTag(BlockPos pos, Level world, @Nullable Player player, ItemStack stack, BlockState state) {
- return BlockItem.updateCustomBlockEntityTag(world, player, pos, stack);
+ // Purpur start
+ boolean handled = updateCustomBlockEntityTag(world, player, pos, stack);
+ if (world.purpurConfig.persistentTileEntityDisplayNames && stack.hasTag()) {
+ CompoundTag display = stack.getTagElement("display");
+ if (display != null) {
+ BlockEntity blockEntity = world.getBlockEntity(pos);
+ if (blockEntity != null) {
+ if (display.contains("Name", 8)) {
+ blockEntity.setPersistentDisplayName(display.getString("Name"));
+ }
+ if (display.contains("Lore", 9)) {
+ blockEntity.setPersistentLore(display.getList("Lore", 8));
+ }
+ }
+ }
+ }
+ return handled;
+ // Purpur end
}
@Nullable
@@ -288,7 +305,7 @@ public class BlockItem extends Item {
@Override
public void onDestroyed(ItemEntity entity) {
- if (this.block instanceof ShulkerBoxBlock) {
+ if (this.block instanceof ShulkerBoxBlock && entity.level().purpurConfig.shulkerBoxItemDropContentsWhenDestroyed) {
ItemStack itemstack = entity.getItem();
CompoundTag nbttagcompound = BlockItem.getBlockEntityData(itemstack);
diff --git a/src/main/java/net/minecraft/world/item/BoatItem.java b/src/main/java/net/minecraft/world/item/BoatItem.java
index aec7ac31a35b1cc81f40b3fbeb5cf95c0f2c8a6c..cbcd35e60a2c344c83978abf0b94c2120ff53dee 100644
--- a/src/main/java/net/minecraft/world/item/BoatItem.java
+++ b/src/main/java/net/minecraft/world/item/BoatItem.java
@@ -69,6 +69,11 @@ public class BoatItem extends Item {
entityboat.setVariant(this.type);
entityboat.setYRot(user.getYRot());
+ // Purpur start
+ if (world.purpurConfig.persistentDroppableEntityDisplayNames && itemstack.hasCustomHoverName()) {
+ entityboat.setCustomName(itemstack.getHoverName());
+ }
+ // Purpur end
if (!world.noCollision(entityboat, entityboat.getBoundingBox())) {
return InteractionResultHolder.fail(itemstack);
} else {
diff --git a/src/main/java/net/minecraft/world/item/BowItem.java b/src/main/java/net/minecraft/world/item/BowItem.java
index 08d597db1a5345a343777a01427655e6bf2c926b..d45a2f49c82d00801578c34e5f5277fc5e82be87 100644
--- a/src/main/java/net/minecraft/world/item/BowItem.java
+++ b/src/main/java/net/minecraft/world/item/BowItem.java
@@ -38,13 +38,13 @@ public class BowItem extends ProjectileWeaponItem implements Vanishable {
float f = BowItem.getPowerForTime(j);
if ((double) f >= 0.1D) {
- boolean flag1 = flag && itemstack1.is(Items.ARROW);
+ boolean flag1 = flag && ((itemstack1.is(Items.ARROW) && world.purpurConfig.infinityWorksWithNormalArrows) || (itemstack1.is(Items.TIPPED_ARROW) && world.purpurConfig.infinityWorksWithTippedArrows) || (itemstack1.is(Items.SPECTRAL_ARROW) && world.purpurConfig.infinityWorksWithSpectralArrows)); // Purpur if (!world.isClientSide) {
if (!world.isClientSide) {
ArrowItem itemarrow = (ArrowItem) (itemstack1.getItem() instanceof ArrowItem ? itemstack1.getItem() : Items.ARROW);
AbstractArrow entityarrow = itemarrow.createArrow(world, itemstack1, entityhuman);
- entityarrow.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, f * 3.0F, 1.0F);
+ entityarrow.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, f * 3.0F, (float) world.purpurConfig.bowProjectileOffset); // Purpur
if (f == 1.0F) {
entityarrow.setCritArrow(true);
}
@@ -64,6 +64,13 @@ public class BowItem extends ProjectileWeaponItem implements Vanishable {
if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.FLAMING_ARROWS, stack) > 0) {
entityarrow.setSecondsOnFire(100);
}
+ // Purpur start
+ int lootingLevel = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MOB_LOOTING, stack);
+
+ if (lootingLevel > 0) {
+ entityarrow.setLootingLevel(lootingLevel);
+ }
+ // Purpur end
// CraftBukkit start
org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(entityhuman, stack, itemstack1, entityarrow, entityhuman.getUsedItemHand(), f, !flag1);
if (event.isCancelled()) {
@@ -132,7 +139,7 @@ public class BowItem extends ProjectileWeaponItem implements Vanishable {
ItemStack itemstack = user.getItemInHand(hand);
boolean flag = !user.getProjectile(itemstack).isEmpty();
- if (!user.getAbilities().instabuild && !flag) {
+ if (!(world.purpurConfig.infinityWorksWithoutArrows && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY_ARROWS, itemstack) > 0) && !user.getAbilities().instabuild && !flag) { // Purpur
return InteractionResultHolder.fail(itemstack);
} else {
user.startUsingItem(hand);
diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java
index 578c3db52dda4c169b5ea615a4ce4a79f15a4cad..0bd98b802f246a3f6061f716d470a4797b28d59d 100644
--- a/src/main/java/net/minecraft/world/item/BucketItem.java
+++ b/src/main/java/net/minecraft/world/item/BucketItem.java
@@ -164,7 +164,7 @@ public class BucketItem extends Item implements DispensibleContainerItem {
// CraftBukkit end
if (!flag1) {
return movingobjectpositionblock != null && this.emptyContents(entityhuman, world, movingobjectpositionblock.getBlockPos().relative(movingobjectpositionblock.getDirection()), (BlockHitResult) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit
- } else if (world.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) {
+ } else if ((world.dimensionType().ultraWarm() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) && this.content.is(FluidTags.WATER)) { // Purpur
int i = blockposition.getX();
int j = blockposition.getY();
int k = blockposition.getZ();
@@ -172,7 +172,7 @@ public class BucketItem extends Item implements DispensibleContainerItem {
world.playSound(entityhuman, blockposition, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F);
for (int l = 0; l < 8; ++l) {
- world.addParticle(ParticleTypes.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 0.0D, 0.0D, 0.0D);
+ ((ServerLevel) world).sendParticles(null, ParticleTypes.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 1, 0.0D, 0.0D, 0.0D, 0.0D, true); // Purpur
}
return true;
diff --git a/src/main/java/net/minecraft/world/item/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java
index eede02c3f125d230af537bb67bebed9b88f7d1b4..2c51a73ebfd05af21b0f5d731fc9f1df77fed1a1 100644
--- a/src/main/java/net/minecraft/world/item/CrossbowItem.java
+++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java
@@ -64,7 +64,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable {
ItemStack itemstack = user.getItemInHand(hand);
if (CrossbowItem.isCharged(itemstack)) {
- CrossbowItem.performShooting(world, user, hand, itemstack, CrossbowItem.getShootingPower(itemstack), 1.0F);
+ CrossbowItem.performShooting(world, user, hand, itemstack, CrossbowItem.getShootingPower(itemstack), (float) world.purpurConfig.crossbowProjectileOffset); // Purpur
CrossbowItem.setCharged(itemstack, false);
return InteractionResultHolder.consume(itemstack);
} else if (!user.getProjectile(itemstack).isEmpty()) {
@@ -114,7 +114,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable {
// Paper end
int i = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MULTISHOT, crossbow);
int j = i == 0 ? 1 : 3;
- boolean flag = !consume || shooter instanceof Player && ((Player) shooter).getAbilities().instabuild; // Paper - add consume
+ boolean flag = !consume || shooter instanceof Player && ((Player) shooter).getAbilities().instabuild || (org.purpurmc.purpur.PurpurConfig.allowCrossbowInfinity && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY_ARROWS, crossbow) > 0); // Paper - add consume // Purpur
ItemStack itemstack1 = shooter.getProjectile(crossbow);
ItemStack itemstack2 = itemstack1.copy();
@@ -291,6 +291,14 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable {
entityarrow.setPierceLevel((byte) i);
}
+ // Purpur start
+ int lootingLevel = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MOB_LOOTING, crossbow);
+
+ if (lootingLevel > 0) {
+ entityarrow.setLootingLevel(lootingLevel);
+ }
+ // Purpur end
+
return entityarrow;
}
@@ -300,7 +308,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable {
for (int i = 0; i < list.size(); ++i) {
ItemStack itemstack1 = (ItemStack) list.get(i);
- boolean flag = entity instanceof Player && ((Player) entity).getAbilities().instabuild;
+ boolean flag = entity instanceof Player && ((Player) entity).getAbilities().instabuild || (org.purpurmc.purpur.PurpurConfig.allowCrossbowInfinity && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY_ARROWS, stack) > 0); // Purpur
if (!itemstack1.isEmpty()) {
if (i == 0) {
diff --git a/src/main/java/net/minecraft/world/item/DyeColor.java b/src/main/java/net/minecraft/world/item/DyeColor.java
index 88e1c2431d51d8cdc3d555b711e506648225d289..ac8735cc9d127fc1f867b40d4000c033ef73bb83 100644
--- a/src/main/java/net/minecraft/world/item/DyeColor.java
+++ b/src/main/java/net/minecraft/world/item/DyeColor.java
@@ -103,4 +103,10 @@ public enum DyeColor implements StringRepresentable {
public String getSerializedName() {
return this.name;
}
+
+ // Purpur start
+ public static DyeColor random(net.minecraft.util.RandomSource random) {
+ return values()[random.nextInt(values().length)];
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/item/EggItem.java b/src/main/java/net/minecraft/world/item/EggItem.java
index 58cb992c5defec2f092755cbde661ff10f38bf9d..52f48681407d23f0925f4c9c072d5f0a2a6b1778 100644
--- a/src/main/java/net/minecraft/world/item/EggItem.java
+++ b/src/main/java/net/minecraft/world/item/EggItem.java
@@ -24,7 +24,7 @@ public class EggItem extends Item {
ThrownEgg entityegg = new ThrownEgg(world, user);
entityegg.setItem(itemstack);
- entityegg.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F);
+ entityegg.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, (float) world.purpurConfig.eggProjectileOffset); // Purpur
// Paper start
com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entityegg.getBukkitEntity());
if (event.callEvent() && world.addFreshEntity(entityegg)) {
diff --git a/src/main/java/net/minecraft/world/item/EndCrystalItem.java b/src/main/java/net/minecraft/world/item/EndCrystalItem.java
index 3688e9f8c6c6d1239095e3a87060ccca90386d0c..34254eec36d34ae343733fa1abbaaba60be41a3b 100644
--- a/src/main/java/net/minecraft/world/item/EndCrystalItem.java
+++ b/src/main/java/net/minecraft/world/item/EndCrystalItem.java
@@ -26,7 +26,7 @@ public class EndCrystalItem extends Item {
BlockPos blockposition = context.getClickedPos();
BlockState iblockdata = world.getBlockState(blockposition);
- if (!iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) {
+ if (!world.purpurConfig.endCrystalPlaceAnywhere && !iblockdata.is(Blocks.OBSIDIAN) && !iblockdata.is(Blocks.BEDROCK)) {
return InteractionResult.FAIL;
} else {
BlockPos blockposition1 = blockposition.above(); final BlockPos aboveBlockPosition = blockposition1; // Paper - OBFHELPER
diff --git a/src/main/java/net/minecraft/world/item/EnderpearlItem.java b/src/main/java/net/minecraft/world/item/EnderpearlItem.java
index 749ab72edc0d2e9c6f1161415ab8d59d3d6ca976..6b27d98d06b163243bb0e1bb979aad03f48d7770 100644
--- a/src/main/java/net/minecraft/world/item/EnderpearlItem.java
+++ b/src/main/java/net/minecraft/world/item/EnderpearlItem.java
@@ -24,7 +24,7 @@ public class EnderpearlItem extends Item {
ThrownEnderpearl entityenderpearl = new ThrownEnderpearl(world, user);
entityenderpearl.setItem(itemstack);
- entityenderpearl.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F);
+ entityenderpearl.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, (float) world.purpurConfig.enderPearlProjectileOffset); // Purpur
// Paper start
com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entityenderpearl.getBukkitEntity());
if (event.callEvent() && world.addFreshEntity(entityenderpearl)) {
@@ -36,7 +36,7 @@ public class EnderpearlItem extends Item {
world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (net.minecraft.world.entity.Entity.SHARED_RANDOM.nextFloat() * 0.4F + 0.8F));
user.awardStat(Stats.ITEM_USED.get(this));
- user.getCooldowns().addCooldown(this, 20);
+ user.getCooldowns().addCooldown(this, user.getAbilities().instabuild ? world.purpurConfig.enderPearlCooldownCreative : world.purpurConfig.enderPearlCooldown); // Purpur
} else {
// Paper end
if (user instanceof net.minecraft.server.level.ServerPlayer) {
diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java
index 82b0bda3e35ec2157a477e1a17b2b46baadc97d9..0fc45b1048a1c4e0dc2bd1ae0437eecbe113cf96 100644
--- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java
+++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java
@@ -15,6 +15,7 @@ import net.minecraft.util.ByIdMap;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionResultHolder;
+import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.projectile.FireworkRocketEntity;
import net.minecraft.world.item.context.UseOnContext;
@@ -69,6 +70,14 @@ public class FireworkRocketItem extends Item {
com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) fireworkRocketEntity.getBukkitEntity());
if (event.callEvent() && world.addFreshEntity(fireworkRocketEntity)) {
user.awardStat(Stats.ITEM_USED.get(this));
+ // Purpur start
+ if (world.purpurConfig.elytraDamagePerFireworkBoost > 0) {
+ ItemStack chestItem = user.getItemBySlot(EquipmentSlot.CHEST);
+ if (chestItem.getItem() == Items.ELYTRA) {
+ chestItem.hurtAndBreak(world.purpurConfig.elytraDamagePerFireworkBoost, user, (entityliving) -> entityliving.broadcastBreakEvent(EquipmentSlot.CHEST));
+ }
+ }
+ // Purpur end
if (event.shouldConsume() && !user.getAbilities().instabuild) {
itemStack.shrink(1);
} else ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory();
diff --git a/src/main/java/net/minecraft/world/item/HangingEntityItem.java b/src/main/java/net/minecraft/world/item/HangingEntityItem.java
index b2ad6d230de2c29f371178bccde1111c7532ee70..6667926519a0f1c151e53f59cce36e7417dfc1cd 100644
--- a/src/main/java/net/minecraft/world/item/HangingEntityItem.java
+++ b/src/main/java/net/minecraft/world/item/HangingEntityItem.java
@@ -48,7 +48,7 @@ public class HangingEntityItem extends Item {
return InteractionResult.FAIL;
} else {
Level world = context.getLevel();
- Object object;
+ Entity object; // Purpur
if (this.type == EntityType.PAINTING) {
Optional<Painting> optional = Painting.create(world, blockposition1, enumdirection);
@@ -72,6 +72,11 @@ public class HangingEntityItem extends Item {
if (nbttagcompound != null) {
EntityType.updateCustomEntityTag(world, entityhuman, (Entity) object, nbttagcompound);
+ // Purpur start
+ if (world.purpurConfig.persistentDroppableEntityDisplayNames && itemstack.hasCustomHoverName()) {
+ object.setCustomName(itemstack.getHoverName());
+ }
+ // Purpur end
}
if (((HangingEntity) object).survives()) {
diff --git a/src/main/java/net/minecraft/world/item/HoeItem.java b/src/main/java/net/minecraft/world/item/HoeItem.java
index 180aec596110309aade13d2080f8824d152b07cb..552c31c0f3746dd35388395036e70a925bf00bd4 100644
--- a/src/main/java/net/minecraft/world/item/HoeItem.java
+++ b/src/main/java/net/minecraft/world/item/HoeItem.java
@@ -34,15 +34,23 @@ public class HoeItem extends DiggerItem {
public InteractionResult useOn(UseOnContext context) {
Level level = context.getLevel();
BlockPos blockPos = context.getClickedPos();
- Pair<Predicate<UseOnContext>, Consumer<UseOnContext>> pair = TILLABLES.get(level.getBlockState(blockPos).getBlock());
- if (pair == null) {
- return InteractionResult.PASS;
- } else {
- Predicate<UseOnContext> predicate = pair.getFirst();
- Consumer<UseOnContext> 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<UseOnContext> predicate = tillable.condition().predicate();
+ Consumer<UseOnContext> consumer = (ctx) -> {
+ level.setBlock(blockPos, tillable.into().defaultBlockState(), 11);
+ tillable.drops().forEach((drop, chance) -> {
+ if (level.random.nextDouble() < chance) {
+ Block.popResourceFromFace(level, blockPos, ctx.getClickedFace(), new ItemStack(drop));
+ }
+ });
+ };
+ // Purpur end
if (predicate.test(context)) {
Player player = context.getPlayer();
- level.playSound(player, blockPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F);
+ if (!TILLABLES.containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound
if (!level.isClientSide) {
consumer.accept(context);
if (player != null) {
@@ -52,7 +60,7 @@ public class HoeItem extends DiggerItem {
}
}
- return InteractionResult.sidedSuccess(level.isClientSide);
+ return InteractionResult.SUCCESS; // Purpur - force arm swing
} else {
return InteractionResult.PASS;
}
diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java
index 879cc823d56625867eb73bb621db6a13f40ad81c..7e68596e28db88213e9352f798c5a4ce37cc5656 100644
--- a/src/main/java/net/minecraft/world/item/ItemStack.java
+++ b/src/main/java/net/minecraft/world/item/ItemStack.java
@@ -113,6 +113,7 @@ import org.bukkit.event.world.StructureGrowEvent;
public final class ItemStack {
+ public boolean isExactRecipeIngredient = false; // PaperPR
public static final Codec<ItemStack> CODEC = RecordCodecBuilder.create((instance) -> {
return instance.group(BuiltInRegistries.ITEM.byNameCodec().fieldOf("id").forGetter(ItemStack::getItem), Codec.INT.fieldOf("Count").forGetter(ItemStack::getCount), CompoundTag.CODEC.optionalFieldOf("tag").forGetter((itemstack) -> {
return Optional.ofNullable(itemstack.getTag());
@@ -426,6 +427,7 @@ public final class ItemStack {
world.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710
for (BlockState blockstate : blocks) {
blockstate.update(true, false);
+ ((CraftBlock) blockstate.getBlock()).getNMS().getBlock().forgetPlacer(); // Purpur
}
world.preventPoiUpdated = false;
@@ -455,6 +457,7 @@ public final class ItemStack {
if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically
block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, context); // Paper - pass context
}
+ block.getBlock().forgetPlacer(); // Purpur
world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point
}
@@ -583,6 +586,16 @@ public final class ItemStack {
return this.isDamageableItem() && this.getDamageValue() > 0;
}
+ // Purpur start
+ public float getDamagePercent() {
+ if (isDamaged()) {
+ return (float) getDamageValue() / (float) getItem().getMaxDamage();
+ } else {
+ return 0F;
+ }
+ }
+ // Purpur end
+
public int getDamageValue() {
return this.tag == null ? 0 : this.tag.getInt("Damage");
}
@@ -602,7 +615,7 @@ public final class ItemStack {
int j;
if (amount > 0) {
- j = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.UNBREAKING, this);
+ j = (getItem() == Items.ELYTRA && player != null && player.level().purpurConfig.elytraIgnoreUnbreaking) ? 0 : EnchantmentHelper.getItemEnchantmentLevel(Enchantments.UNBREAKING, this);
int k = 0;
for (int l = 0; j > 0 && l < amount; ++l) {
@@ -657,6 +670,12 @@ public final class ItemStack {
if (this.hurt(amount, entity.getRandom(), entity /*instanceof ServerPlayer ? (ServerPlayer) entity : null*/)) { // Paper - pass LivingEntity for EntityItemDamageEvent
breakCallback.accept(entity);
Item item = this.getItem();
+ // Purpur start
+ if (item == Items.ELYTRA) {
+ setDamageValue(item.getMaxDamage() - 1);
+ return;
+ }
+ // Purpur end
// CraftBukkit start - Check for item breaking
if (this.count == 1 && entity instanceof net.minecraft.world.entity.player.Player) {
org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent((net.minecraft.world.entity.player.Player) entity, this);
@@ -1183,7 +1202,7 @@ public final class ItemStack {
ListTag nbttaglist = this.tag.getList("Enchantments", 10);
- nbttaglist.add(EnchantmentHelper.storeEnchantment(EnchantmentHelper.getEnchantmentId(enchantment), (byte) level));
+ nbttaglist.add(EnchantmentHelper.storeEnchantment(EnchantmentHelper.getEnchantmentId(enchantment), (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels) ? (byte) level : (short) level)); // Purpur
processEnchantOrder(this.tag); // Paper
}
@@ -1191,6 +1210,12 @@ public final class ItemStack {
return this.tag != null && this.tag.contains("Enchantments", 9) ? !this.tag.getList("Enchantments", 10).isEmpty() : false;
}
+ // Purpur start
+ public boolean hasEnchantment(Enchantment enchantment) {
+ return isEnchanted() && EnchantmentHelper.deserializeEnchantments(getEnchantmentTags()).containsKey(enchantment);
+ }
+ // Purpur end
+
public void addTagElement(String key, Tag element) {
this.getOrCreateTag().put(key, element);
}
diff --git a/src/main/java/net/minecraft/world/item/Items.java b/src/main/java/net/minecraft/world/item/Items.java
index 5f20e075c532f0f1d413242949d1738c0c152bf7..5fbb13ebef0ca66419f3e5006d19e4a5918a038a 100644
--- a/src/main/java/net/minecraft/world/item/Items.java
+++ b/src/main/java/net/minecraft/world/item/Items.java
@@ -294,7 +294,7 @@ public class Items {
public static final Item PURPUR_BLOCK = registerBlock(Blocks.PURPUR_BLOCK);
public static final Item PURPUR_PILLAR = registerBlock(Blocks.PURPUR_PILLAR);
public static final Item PURPUR_STAIRS = registerBlock(Blocks.PURPUR_STAIRS);
- public static final Item SPAWNER = registerBlock(Blocks.SPAWNER);
+ public static final Item SPAWNER = registerBlock(new org.purpurmc.purpur.item.SpawnerItem(Blocks.SPAWNER, new Item.Properties().rarity(Rarity.EPIC))); // Purpur
public static final Item CHEST = registerBlock(Blocks.CHEST);
public static final Item CRAFTING_TABLE = registerBlock(Blocks.CRAFTING_TABLE);
public static final Item FARMLAND = registerBlock(Blocks.FARMLAND);
@@ -1184,7 +1184,7 @@ public class Items {
public static final Item LANTERN = registerBlock(Blocks.LANTERN);
public static final Item SOUL_LANTERN = registerBlock(Blocks.SOUL_LANTERN);
public static final Item SWEET_BERRIES = registerItem("sweet_berries", new ItemNameBlockItem(Blocks.SWEET_BERRY_BUSH, (new Item.Properties()).food(Foods.SWEET_BERRIES)));
- public static final Item GLOW_BERRIES = registerItem("glow_berries", new ItemNameBlockItem(Blocks.CAVE_VINES, (new Item.Properties()).food(Foods.GLOW_BERRIES)));
+ public static final Item GLOW_BERRIES = registerItem("glow_berries", new org.purpurmc.purpur.item.GlowBerryItem(Blocks.CAVE_VINES, (new Item.Properties()).food(Foods.GLOW_BERRIES))); // Purpur
public static final Item CAMPFIRE = registerBlock(Blocks.CAMPFIRE);
public static final Item SOUL_CAMPFIRE = registerBlock(Blocks.SOUL_CAMPFIRE);
public static final Item SHROOMLIGHT = registerBlock(Blocks.SHROOMLIGHT);
@@ -1309,6 +1309,13 @@ public class Items {
((BlockItem)item).registerBlocks(Item.BY_BLOCK, item);
}
+ // Purpur start
+ if (item.getFoodProperties() != null) {
+ Foods.ALL_PROPERTIES.put(key.location().getPath(), item.getFoodProperties());
+ Foods.DEFAULT_PROPERTIES.put(key.location().getPath(), item.getFoodProperties().copy());
+ }
+ // Purpur end
+
return Registry.register(BuiltInRegistries.ITEM, key, item);
}
}
diff --git a/src/main/java/net/minecraft/world/item/MapItem.java b/src/main/java/net/minecraft/world/item/MapItem.java
index d3c29e6bf8b3c2dd628809177dac50220a7de415..735486b46581bb3b91c85f57490b560ff490787e 100644
--- a/src/main/java/net/minecraft/world/item/MapItem.java
+++ b/src/main/java/net/minecraft/world/item/MapItem.java
@@ -243,6 +243,7 @@ public class MapItem extends ComplexItem {
MapItemSavedData worldmap = MapItem.getSavedData(map, world);
if (worldmap != null) {
+ worldmap.isExplorerMap = true; // Purpur
if (world.dimension() == worldmap.dimension) {
int i = 1 << worldmap.scale;
int j = worldmap.centerX;
diff --git a/src/main/java/net/minecraft/world/item/MilkBucketItem.java b/src/main/java/net/minecraft/world/item/MilkBucketItem.java
index f33977d95b6db473be4f95075ba99caf90ad0220..56dc04d8875971ee9a5d077a695509af74fe2473 100644
--- a/src/main/java/net/minecraft/world/item/MilkBucketItem.java
+++ b/src/main/java/net/minecraft/world/item/MilkBucketItem.java
@@ -5,6 +5,8 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.stats.Stats;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
+import net.minecraft.world.effect.MobEffectInstance;
+import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
@@ -31,7 +33,9 @@ public class MilkBucketItem extends Item {
}
if (!world.isClientSide) {
+ MobEffectInstance badOmen = user.getEffect(MobEffects.BAD_OMEN);
user.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.MILK); // CraftBukkit
+ if (!world.purpurConfig.milkCuresBadOmen && badOmen != null) user.addEffect(badOmen); // Purpur
}
return stack.isEmpty() ? new ItemStack(Items.BUCKET) : stack;
diff --git a/src/main/java/net/minecraft/world/item/MinecartItem.java b/src/main/java/net/minecraft/world/item/MinecartItem.java
index c6d2f764efa9b8bec730bbe757d480e365b25ccc..cef98413d25dcc2def82775bbae71f92b096d905 100644
--- a/src/main/java/net/minecraft/world/item/MinecartItem.java
+++ b/src/main/java/net/minecraft/world/item/MinecartItem.java
@@ -120,8 +120,9 @@ public class MinecartItem extends Item {
BlockState iblockdata = world.getBlockState(blockposition);
if (!iblockdata.is(BlockTags.RAILS)) {
- return InteractionResult.FAIL;
- } else {
+ if (!world.purpurConfig.minecartPlaceAnywhere) return InteractionResult.FAIL;
+ if (iblockdata.isSolid()) blockposition = blockposition.relative(context.getClickedFace());
+ } // else { // Purpur - place minecarts anywhere
ItemStack itemstack = context.getItemInHand();
if (!world.isClientSide) {
@@ -149,6 +150,6 @@ public class MinecartItem extends Item {
itemstack.shrink(1);
return InteractionResult.sidedSuccess(world.isClientSide);
- }
+ // } // Purpur - place minecarts anywhere
}
}
diff --git a/src/main/java/net/minecraft/world/item/NameTagItem.java b/src/main/java/net/minecraft/world/item/NameTagItem.java
index 2941c16ef486345b57ab2dfcd26f0272285d3b5a..7cc6812bf6f2ba015f65fd1fc1eaac02dd0f53e2 100644
--- a/src/main/java/net/minecraft/world/item/NameTagItem.java
+++ b/src/main/java/net/minecraft/world/item/NameTagItem.java
@@ -20,6 +20,7 @@ public class NameTagItem extends Item {
if (!event.callEvent()) return InteractionResult.PASS;
LivingEntity newEntityLiving = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle();
newEntityLiving.setCustomName(event.getName() != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getName()) : null);
+ if (user.level().purpurConfig.armorstandFixNametags && entity instanceof net.minecraft.world.entity.decoration.ArmorStand) entity.setCustomNameVisible(true); // Purpur
if (event.isPersistent() && newEntityLiving instanceof Mob) {
((Mob) newEntityLiving).setPersistenceRequired();
// Paper end
diff --git a/src/main/java/net/minecraft/world/item/ShovelItem.java b/src/main/java/net/minecraft/world/item/ShovelItem.java
index 21212462e6b415e96536a27b2c009d1562f18946..193bcb12152347f5f02ce18b01ba918e5e838f30 100644
--- a/src/main/java/net/minecraft/world/item/ShovelItem.java
+++ b/src/main/java/net/minecraft/world/item/ShovelItem.java
@@ -37,9 +37,12 @@ public class ShovelItem extends DiggerItem {
BlockState blockState2 = FLATTENABLES.get(blockState.getBlock());
BlockState blockState3 = null;
Runnable afterAction = null; // Paper
- if (blockState2 != null && level.getBlockState(blockPos.above()).isAir()) {
- afterAction = () -> level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); // Paper
- blockState3 = blockState2;
+ // Purpur start
+ var flattenable = level.purpurConfig.shovelFlattenables.get(blockState.getBlock());
+ if (flattenable != null && level.getBlockState(blockPos.above()).isAir()) {
+ afterAction = () -> {if (!FLATTENABLES.containsKey(blockState.getBlock())) level.playSound(null, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F);}; // Paper
+ blockState3 = flattenable.into().defaultBlockState();
+ // Purpur end
} else if (blockState.getBlock() instanceof CampfireBlock && blockState.getValue(CampfireBlock.LIT)) {
afterAction = () -> { // Paper
if (!level.isClientSide()) {
@@ -68,7 +71,7 @@ public class ShovelItem extends DiggerItem {
}
}
- return InteractionResult.sidedSuccess(level.isClientSide);
+ return InteractionResult.SUCCESS; // Purpur - force arm swing
} else {
return InteractionResult.PASS;
}
diff --git a/src/main/java/net/minecraft/world/item/SnowballItem.java b/src/main/java/net/minecraft/world/item/SnowballItem.java
index ef3f90a5bcdd7b9815a4053cff166f9d2552f55d..e7e5e1cc92f56e3daba8fa09c59188febec5e8f2 100644
--- a/src/main/java/net/minecraft/world/item/SnowballItem.java
+++ b/src/main/java/net/minecraft/world/item/SnowballItem.java
@@ -25,7 +25,7 @@ public class SnowballItem extends Item {
Snowball entitysnowball = new Snowball(world, user);
entitysnowball.setItem(itemstack);
- entitysnowball.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F);
+ entitysnowball.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, (float) world.purpurConfig.snowballProjectileOffset); // Purpur
// Paper start
com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entitysnowball.getBukkitEntity());
if (event.callEvent() && world.addFreshEntity(entitysnowball)) {
diff --git a/src/main/java/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java
index 741719301e6fc91a598e74342810c4185e6fde26..6fbff9c02fbabf03c9c649a9ea6128021081f9cd 100644
--- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java
+++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java
@@ -68,6 +68,15 @@ public class SpawnEggItem extends Item {
SpawnerBlockEntity tileentitymobspawner = (SpawnerBlockEntity) tileentity;
EntityType<?> entitytypes = this.getType(itemstack.getTag());
+ // Purpur start
+ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
+ org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent event = new org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), bukkitBlock, (org.bukkit.block.CreatureSpawner) bukkitBlock.getState(), org.bukkit.entity.EntityType.fromName(entitytypes.getName()));
+ if (!event.callEvent()) {
+ return InteractionResult.FAIL;
+ }
+ entitytypes = EntityType.getFromBukkitType(event.getEntityType());
+ // Purpur end
+
tileentitymobspawner.setEntityId(entitytypes, world.getRandom());
tileentity.setChanged();
world.sendBlockUpdated(blockposition, iblockdata, iblockdata, 3);
diff --git a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java
index de5bdceb4c8578fb972a2fd5ee0dfdae509e46dc..bcf63ccb6e679cb97d658780b2663aafa3568bcb 100644
--- a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java
+++ b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java
@@ -18,7 +18,7 @@ public class ThrowablePotionItem extends PotionItem {
if (!world.isClientSide) {
ThrownPotion thrownPotion = new ThrownPotion(world, user);
thrownPotion.setItem(itemStack);
- thrownPotion.shootFromRotation(user, user.getXRot(), user.getYRot(), -20.0F, 0.5F, 1.0F);
+ thrownPotion.shootFromRotation(user, user.getXRot(), user.getYRot(), -20.0F, 0.5F, (float) world.purpurConfig.throwablePotionProjectileOffset); // Purpur
// Paper start
com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownPotion.getBukkitEntity());
if (event.callEvent() && world.addFreshEntity(thrownPotion)) {
diff --git a/src/main/java/net/minecraft/world/item/TridentItem.java b/src/main/java/net/minecraft/world/item/TridentItem.java
index 8078f127ff4b6e0aafb5804b9c02e237f79445b5..c32cbe6065ecb6810f352b8a3598c21e42e60e1d 100644
--- a/src/main/java/net/minecraft/world/item/TridentItem.java
+++ b/src/main/java/net/minecraft/world/item/TridentItem.java
@@ -77,11 +77,19 @@ public class TridentItem extends Item implements Vanishable {
if (k == 0) {
ThrownTrident entitythrowntrident = new ThrownTrident(world, entityhuman, stack);
- entitythrowntrident.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, 2.5F + (float) k * 0.5F, 1.0F);
+ entitythrowntrident.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, 2.5F + (float) k * 0.5F, (float) world.purpurConfig.tridentProjectileOffset); // Purpur
if (entityhuman.getAbilities().instabuild) {
entitythrowntrident.pickup = AbstractArrow.Pickup.CREATIVE_ONLY;
}
+ // Purpur start
+ int lootingLevel = EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MOB_LOOTING, stack);
+
+ if (lootingLevel > 0) {
+ entitythrowntrident.setLootingLevel(lootingLevel);
+ }
+ // Purpur end
+
// CraftBukkit start
// Paper start
com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) entitythrowntrident.getBukkitEntity());
@@ -130,6 +138,14 @@ public class TridentItem extends Item implements Vanishable {
f2 *= f6 / f5;
f3 *= f6 / f5;
f4 *= f6 / f5;
+
+ // Purpur start
+ ItemStack chestItem = entityhuman.getItemBySlot(EquipmentSlot.CHEST);
+ if (chestItem.getItem() == Items.ELYTRA && world.purpurConfig.elytraDamagePerTridentBoost > 0) {
+ chestItem.hurtAndBreak(world.purpurConfig.elytraDamagePerTridentBoost, entityhuman, (entity) -> entity.broadcastBreakEvent(EquipmentSlot.CHEST));
+ }
+ // Purpur end
+
entityhuman.push((double) f2, (double) f3, (double) f4);
entityhuman.startAutoSpinAttack(20);
if (entityhuman.onGround()) {
diff --git a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java
index cd7ea0c16f9ddcb84b5d7e8a2533e6e84f3879c7..f47eab4c31925f51de4a6bc8be730281cb3388fc 100644
--- a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java
+++ b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java
@@ -39,6 +39,7 @@ public final class Ingredient implements Predicate<ItemStack> {
@Nullable
private IntList stackingIds;
public boolean exact; // CraftBukkit
+ public Predicate<org.bukkit.inventory.ItemStack> predicate;
public Ingredient(Stream<? extends Ingredient.Value> entries) {
this.values = (Ingredient.Value[]) entries.toArray((i) -> {
@@ -50,7 +51,11 @@ public final class Ingredient implements Predicate<ItemStack> {
if (this.itemStacks == null) {
this.itemStacks = (ItemStack[]) Arrays.stream(this.values).flatMap((recipeitemstack_provider) -> {
return recipeitemstack_provider.getItems().stream();
- }).distinct().toArray((i) -> {
+ // PaperPR start
+ }).distinct().peek(stack -> {
+ stack.isExactRecipeIngredient = this.exact;
+ }).toArray((i) -> {
+ // PaperPR end
return new ItemStack[i];
});
}
@@ -64,6 +69,12 @@ public final class Ingredient implements Predicate<ItemStack> {
} else if (this.isEmpty()) {
return itemstack.isEmpty();
} else {
+ // Purpur start
+ if (predicate != null) {
+ return predicate.test(itemstack.asBukkitCopy());
+ }
+ // Purpur end
+
ItemStack[] aitemstack = this.getItems();
int i = aitemstack.length;
@@ -99,7 +110,13 @@ public final class Ingredient implements Predicate<ItemStack> {
for (int j = 0; j < i; ++j) {
ItemStack itemstack = aitemstack1[j];
+ // PaperPR start
+ if (itemstack.isExactRecipeIngredient) {
+ this.stackingIds.add(StackedContents.getExactStackingIndex(itemstack));
+ } else {
+ // PaperPR end
this.stackingIds.add(StackedContents.getStackingIndex(itemstack));
+ } // PaperPR
}
this.stackingIds.sort(IntComparators.NATURAL_COMPARATOR);
diff --git a/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java b/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java
index 518d85a13c37a2f7d32ca0718323181048559986..27512787b37381a5236b1b473e9ce3f06df8e2d0 100644
--- a/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java
+++ b/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java
@@ -7,6 +7,14 @@ public class ArrowInfiniteEnchantment extends Enchantment {
super(weight, EnchantmentCategory.BOW, slotTypes);
}
+ // Purpur start
+ @Override
+ public boolean canEnchant(net.minecraft.world.item.ItemStack stack) {
+ // we have to cheat the system because this class is loaded before purpur's config is loaded
+ return (org.purpurmc.purpur.PurpurConfig.allowCrossbowInfinity ? EnchantmentCategory.BOW_AND_CROSSBOW : EnchantmentCategory.BOW).canEnchant(stack.getItem());
+ }
+ // Purpur end
+
@Override
public int getMinCost(int level) {
return 20;
@@ -19,6 +27,6 @@ public class ArrowInfiniteEnchantment extends Enchantment {
@Override
public boolean checkCompatibility(Enchantment other) {
- return other instanceof MendingEnchantment ? false : super.checkCompatibility(other);
+ return other instanceof MendingEnchantment ? org.purpurmc.purpur.PurpurConfig.allowInfinityMending : super.checkCompatibility(other);
}
}
diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java
index 246516e67db0b8b197b287c067d5a0163d8bde22..fc2c35f57436371cb0111aedfd289ac95d506d07 100644
--- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java
+++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java
@@ -121,6 +121,20 @@ public enum EnchantmentCategory {
public boolean canEnchant(Item item) {
return item instanceof Vanishable || Block.byItem(item) instanceof Vanishable || BREAKABLE.canEnchant(item);
}
+ // Purpur start
+ },
+ BOW_AND_CROSSBOW {
+ @Override
+ public boolean canEnchant(Item item) {
+ return item instanceof BowItem || item instanceof CrossbowItem;
+ }
+ },
+ WEAPON_AND_SHEARS {
+ @Override
+ public boolean canEnchant(Item item) {
+ return WEAPON.canEnchant(item) || item instanceof net.minecraft.world.item.ShearsItem;
+ }
+ // Purpur end
};
public abstract boolean canEnchant(Item item);
diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java
index ecf640b00007a386290f8dfe9935a8aa610079fd..1eec84e217f6dc929091fa7451cd235ef3623822 100644
--- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java
+++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java
@@ -46,7 +46,7 @@ public class EnchantmentHelper {
}
public static int getEnchantmentLevel(CompoundTag nbt) {
- return Mth.clamp(nbt.getInt("lvl"), 0, 255);
+ return Mth.clamp(nbt.getInt("lvl"), 0, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels) ? 255 : 32767); // Purpur
}
@Nullable
@@ -278,6 +278,29 @@ public class EnchantmentHelper {
return getItemEnchantmentLevel(Enchantments.CHANNELING, stack) > 0;
}
+ // Purpur start
+ @Nullable
+ public static Map.Entry<EquipmentSlot, ItemStack> getMostDamagedEquipment(Enchantment enchantment, LivingEntity entity) {
+ Map<EquipmentSlot, ItemStack> map = enchantment.getSlotItems(entity);
+ if (map.isEmpty()) {
+ return null;
+ }
+ Map.Entry<EquipmentSlot, ItemStack> item = null;
+ float maxPercent = 0F;
+ for (Map.Entry<EquipmentSlot, ItemStack> entry : map.entrySet()) {
+ ItemStack itemstack = entry.getValue();
+ if (!itemstack.isEmpty() && itemstack.isDamaged() && getItemEnchantmentLevel(enchantment, itemstack) > 0) {
+ float percent = itemstack.getDamagePercent();
+ if (item == null || percent > maxPercent) {
+ item = entry;
+ maxPercent = percent;
+ }
+ }
+ }
+ return item;
+ }
+ // Purpur end
+
@Nullable
public static Map.Entry<EquipmentSlot, ItemStack> getRandomItemWith(Enchantment enchantment, LivingEntity entity) {
return getRandomItemWith(enchantment, entity, (stack) -> {
diff --git a/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java b/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java
index 4007c16550683e23b396dfdff29530a82523fe05..8fe09c13643d99639fb242da4367c42ef31b38b4 100644
--- a/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java
+++ b/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java
@@ -7,6 +7,14 @@ public class LootBonusEnchantment extends Enchantment {
super(weight, target, slotTypes);
}
+ // Purpur start
+ @Override
+ public boolean canEnchant(net.minecraft.world.item.ItemStack stack) {
+ // we have to cheat the system because this class is loaded before purpur's config is loaded
+ return (org.purpurmc.purpur.PurpurConfig.allowShearsLooting && this.category == EnchantmentCategory.WEAPON ? EnchantmentCategory.WEAPON_AND_SHEARS : this.category).canEnchant(stack.getItem());
+ }
+ // Purpur end
+
@Override
public int getMinCost(int level) {
return 15 + (level - 1) * 9;
diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java
index 28bdcb14cb5b458d3c990fcf343ef97f08e4f3c6..48167334162443365bb8a6d082a51b2c626ab3d8 100644
--- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java
+++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java
@@ -134,7 +134,12 @@ public class MerchantOffer {
}
public void updateDemand() {
- this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper
+ // Purpur start
+ this.updateDemand(0);
+ }
+ public void updateDemand(int minimumDemand) {
+ this.demand = Math.max(minimumDemand, this.demand + this.uses - (this.maxUses - this.uses));
+ // Purpur end
}
public ItemStack assemble() {
diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java
index 633500aefd515df5dadda3802b94079f75a03fa0..64d911bee1607880514061c75116d8672df8bb8f 100644
--- a/src/main/java/net/minecraft/world/level/BaseSpawner.java
+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java
@@ -55,6 +55,7 @@ public abstract class BaseSpawner {
}
public boolean isNearPlayer(Level world, BlockPos pos) {
+ if (world.purpurConfig.spawnerDeactivateByRedstone && world.hasNeighborSignal(pos)) return false; // Purpur
return world.hasNearbyAlivePlayerThatAffectsSpawning((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper - Affects Spawning API
}
diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java
index 3b959f42d958bf0f426853aee56753d6c455fcdb..d17abb283ea818244df0379d6b57fc634071e0b9 100644
--- a/src/main/java/net/minecraft/world/level/EntityGetter.java
+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java
@@ -154,7 +154,7 @@ public interface EntityGetter {
default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) {
for(Player player : this.players()) {
- if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) {
+ if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player) && EntitySelector.notAfk.test(player)) {
double d = player.distanceToSqr(x, y, z);
if (range < 0.0D || d < range * range) {
return true;
diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java
index 8f97c9df726ac20cfce7bdddd5dd4f8c5aa76c35..4932374ab9a3d8582fb0ef024d817ad896dd23c4 100644
--- a/src/main/java/net/minecraft/world/level/Explosion.java
+++ b/src/main/java/net/minecraft/world/level/Explosion.java
@@ -86,7 +86,7 @@ public class Explosion {
this.hitPlayers = Maps.newHashMap();
this.level = world;
this.source = entity;
- this.radius = (float) Math.max(power, 0.0); // CraftBukkit - clamp bad values
+ this.radius = (float) (world == null || world.purpurConfig.explosionClampRadius ? Math.max(power, 0.0) : power); // CraftBukkit - clamp bad values // Purpur
this.x = x;
this.y = y;
this.z = z;
@@ -137,10 +137,27 @@ public class Explosion {
public void explode() {
// CraftBukkit start
- if (this.radius < 0.1F) {
+ if ((this.level == null || this.level.purpurConfig.explosionClampRadius) && this.radius < 0.1F) { // Purpur
return;
}
// CraftBukkit end
+
+ // Purpur start - add PreExplodeEvents
+ if(this.source != null){
+ Location location = new Location(this.level.getWorld(), this.x, this.y, this.z);
+ if(!new org.purpurmc.purpur.event.entity.PreEntityExplodeEvent(this.source.getBukkitEntity(), location, this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F).callEvent()) {
+ this.wasCanceled = true;
+ return;
+ }
+ }else {
+ Location location = new Location(this.level.getWorld(), this.x, this.y, this.z);
+ if(!new org.purpurmc.purpur.event.PreBlockExplodeEvent(location.getBlock(), this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F, this.damageSource.explodedBlockState).callEvent()) {
+ this.wasCanceled = true;
+ return;
+ }
+ }
+ //Purpur end
+
this.level.gameEvent(this.source, GameEvent.EXPLODE, new Vec3(this.x, this.y, this.z));
Set<BlockPos> set = Sets.newHashSet();
boolean flag = true;
@@ -382,7 +399,7 @@ public class Explosion {
if (!iblockdata.isAir() && iblockdata.isDestroyable()) { // Paper
BlockPos blockposition1 = blockposition.immutable();
- this.level.getProfiler().push("explosion_blocks");
+ //this.level.getProfiler().push("explosion_blocks"); // Purpur
if (block.dropFromExplosion(this)) {
Level world = this.level;
@@ -404,7 +421,7 @@ public class Explosion {
this.level.setBlock(blockposition, Blocks.AIR.defaultBlockState(), 3);
block.wasExploded(this.level, blockposition, this);
- this.level.getProfiler().pop();
+ //this.level.getProfiler().pop(); // Purpur
}
}
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
index 5c83d703211725ddc36602bb480545288d6a06d9..80ab17142c34d576409e5fb353ff31db9673b2c0 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
@@ -176,6 +176,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
// Paper end
public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
+ public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur
public final co.aikar.timings.WorldTimingsHandler timings; // Paper
public static BlockPos lastPhysicsProblem; // Spigot
private org.spigotmc.TickLimiter entityLimiter;
@@ -193,6 +194,49 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
// Paper end - fix and optimise world upgrading
+ // Purpur start
+ private com.google.common.cache.Cache<BreedingCooldownPair, Object> playerBreedingCooldowns;
+
+ private com.google.common.cache.Cache<BreedingCooldownPair, Object> 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<? extends net.minecraft.world.entity.animal.Animal> animalType) { // Purpur
+ return this.playerBreedingCooldowns.getIfPresent(new BreedingCooldownPair(player, animalType)) != null;
+ }
+
+ public void addBreedingCooldown(java.util.UUID player, Class<? extends net.minecraft.world.entity.animal.Animal> animalType) {
+ this.playerBreedingCooldowns.put(new BreedingCooldownPair(player, animalType), new Object());
+ }
+
+ private static final class BreedingCooldownPair {
+ private final java.util.UUID playerUUID;
+ private final Class<? extends net.minecraft.world.entity.animal.Animal> animalType;
+
+ public BreedingCooldownPair(java.util.UUID playerUUID, Class<? extends net.minecraft.world.entity.animal.Animal> animalType) {
+ this.playerUUID = playerUUID;
+ this.animalType = animalType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BreedingCooldownPair that = (BreedingCooldownPair) o;
+ return playerUUID.equals(that.playerUUID) && animalType.equals(that.animalType);
+ }
+
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(playerUUID, animalType);
+ }
+ }
+ // Purpur end
+
public CraftWorld getWorld() {
return this.world;
}
@@ -273,7 +317,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
public abstract ResourceKey<LevelStem> getTypeKey();
- protected final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(java.util.concurrent.ThreadLocalRandom.current().nextLong()); public net.minecraft.util.RandomSource getThreadUnsafeRandom() { return this.randomTickRandom; } // Pufferfish - move thread unsafe random initialization // Pufferfish - getter
+ //protected final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(java.util.concurrent.ThreadLocalRandom.current().nextLong()); public net.minecraft.util.RandomSource getThreadUnsafeRandom() { return this.randomTickRandom; } // Pufferfish - move thread unsafe random initialization // Pufferfish - getter // Purpur - dont break ABI
// Pufferfish start - ensure these get inlined
private final int minBuildHeight, minSection, height, maxBuildHeight, maxSection;
@@ -287,6 +331,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function<org.spigotmc.SpigotWorldConfig, io.papermc.paper.configuration.WorldConfiguration> paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - Async-Anti-Xray - Pass executor
this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper
+ this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur
+ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur
this.generator = gen;
this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env);
@@ -966,18 +1012,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
protected void tickBlockEntities() {
- ProfilerFiller gameprofilerfiller = this.getProfiler();
+ //ProfilerFiller gameprofilerfiller = this.getProfiler(); // Purpur
- gameprofilerfiller.push("blockEntities");
- timings.tileEntityPending.startTiming(); // Spigot
+ //gameprofilerfiller.push("blockEntities"); // Purpur
+ //timings.tileEntityPending.startTiming(); // Spigot // Purpur
this.tickingBlockEntities = true;
if (!this.pendingBlockEntityTickers.isEmpty()) {
this.blockEntityTickers.addAll(this.pendingBlockEntityTickers);
this.pendingBlockEntityTickers.clear();
}
- timings.tileEntityPending.stopTiming(); // Spigot
+ //timings.tileEntityPending.stopTiming(); // Spigot // Purpur
- timings.tileEntityTick.startTiming(); // Spigot
+ //timings.tileEntityTick.startTiming(); // Spigot // Purpur
// Spigot start
// Iterator iterator = this.blockEntityTickers.iterator();
int tilesThisCycle = 0;
@@ -1010,10 +1056,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
this.blockEntityTickers.removeAll(toRemove);
- timings.tileEntityTick.stopTiming(); // Spigot
+ //timings.tileEntityTick.stopTiming(); // Spigot // Purpur
this.tickingBlockEntities = false;
co.aikar.timings.TimingHistory.tileEntityTicks += this.blockEntityTickers.size(); // Paper
- gameprofilerfiller.pop();
+ //gameprofilerfiller.pop(); // Purpur
spigotConfig.currentPrimedTnt = 0; // Spigot
}
@@ -1206,7 +1252,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
@Override
public List<Entity> getEntities(@Nullable Entity except, AABB box, Predicate<? super Entity> predicate) {
- this.getProfiler().incrementCounter("getEntities");
+ //this.getProfiler().incrementCounter("getEntities"); // Purpur
List<Entity> list = Lists.newArrayList();
((ServerLevel)this).getEntityLookup().getEntities(except, box, list, predicate); // Paper - optimise this call
return list;
@@ -1225,7 +1271,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
public <T extends Entity> void getEntities(EntityTypeTest<Entity, T> filter, AABB box, Predicate<? super T> predicate, List<? super T> result, int limit) {
- this.getProfiler().incrementCounter("getEntities");
+ //this.getProfiler().incrementCounter("getEntities"); // Purpur
// Paper start - optimise this call
//TODO use limit
if (filter instanceof net.minecraft.world.entity.EntityType entityTypeTest) {
@@ -1482,7 +1528,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
}
public ProfilerFiller getProfiler() {
- if (gg.pufferfish.pufferfish.PufferfishConfig.disableMethodProfiler) return net.minecraft.util.profiling.InactiveProfiler.INSTANCE; // Pufferfish
+ if (true || gg.pufferfish.pufferfish.PufferfishConfig.disableMethodProfiler) return net.minecraft.util.profiling.InactiveProfiler.INSTANCE; // Pufferfish // Purpur
return (ProfilerFiller) this.profiler.get();
}
@@ -1573,4 +1619,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
return null;
}
// Paper end
+
+ // Purpur start
+ public boolean isNether() {
+ return getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER;
+ }
+
+ public boolean isTheEnd() {
+ return getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
index 49de1fca183b2c6a0a5399025abfc0e47f314315..1f9994f4b0b736f64a8676d9431469527c6484df 100644
--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
@@ -132,8 +132,8 @@ public final class NaturalSpawner {
}
public static void spawnForChunk(ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnState info, boolean spawnAnimals, boolean spawnMonsters, boolean rareSpawn) {
- world.getProfiler().push("spawner");
- world.timings.mobSpawn.startTiming(); // Spigot
+ //world.getProfiler().push("spawner"); // Purpur
+ //world.timings.mobSpawn.startTiming(); // Spigot // Purpur
MobCategory[] aenumcreaturetype = NaturalSpawner.SPAWNING_CATEGORIES;
int i = aenumcreaturetype.length;
@@ -188,8 +188,8 @@ public final class NaturalSpawner {
}
}
- world.timings.mobSpawn.stopTiming(); // Spigot
- world.getProfiler().pop();
+ //world.timings.mobSpawn.stopTiming(); // Spigot // Purpur
+ //world.getProfiler().pop(); // Purpur
}
// Paper start
@@ -260,7 +260,7 @@ public final class NaturalSpawner {
blockposition_mutableblockposition.set(l, i, i1);
double d0 = (double) l + 0.5D;
double d1 = (double) i1 + 0.5D;
- Player entityhuman = (chunk instanceof LevelChunk) ? ((LevelChunk)chunk).findNearestPlayer(d0, i, d1, 576.0D, net.minecraft.world.entity.EntitySelector.NO_SPECTATORS) : world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); // Paper - use chunk's player cache to optimize search in range
+ Player entityhuman = (chunk instanceof LevelChunk) ? ((LevelChunk)chunk).findNearestPlayer(d0, i, d1, 576.0D, world.purpurConfig.mobSpawningIgnoreCreativePlayers ? net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR : net.minecraft.world.entity.EntitySelector.NO_SPECTATORS) : world.getNearestPlayer(d0, (double) i, d1, -1.0D, world.purpurConfig.mobSpawningIgnoreCreativePlayers); // Paper - use chunk's player cache to optimize search in range // Purpur
if (entityhuman != null) {
double d2 = entityhuman.distanceToSqr(d0, (double) i, d1);
diff --git a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java
index 5c5a3b169795bf8a527b316c666cbc2105c66622..020afeca950d2c7fb6c7b179d424548fd90f8b0d 100644
--- a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java
@@ -55,6 +55,54 @@ public class AnvilBlock extends FallingBlock {
@Override
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
+ // Purpur start - repairable/damageable anvils
+ if (world.purpurConfig.anvilRepairIngotsAmount > 0) {
+ net.minecraft.world.item.ItemStack itemstack = player.getItemInHand(hand);
+ if (itemstack.is(net.minecraft.world.item.Items.IRON_INGOT)) {
+ if (itemstack.getCount() < world.purpurConfig.anvilRepairIngotsAmount) {
+ // not enough iron ingots, play "error" sound and consume
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return InteractionResult.CONSUME;
+ }
+ if (state.is(Blocks.DAMAGED_ANVIL)) {
+ world.setBlock(pos, Blocks.CHIPPED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3);
+ } else if (state.is(Blocks.CHIPPED_ANVIL)) {
+ world.setBlock(pos, Blocks.ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3);
+ } else if (state.is(Blocks.ANVIL)) {
+ // anvil is already fully repaired, play "error" sound and consume
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return InteractionResult.CONSUME;
+ }
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(world.purpurConfig.anvilRepairIngotsAmount);
+ }
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_PLACE, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return InteractionResult.CONSUME;
+ }
+ }
+ if (world.purpurConfig.anvilDamageObsidianAmount > 0) {
+ net.minecraft.world.item.ItemStack itemstack = player.getItemInHand(hand);
+ if (itemstack.is(net.minecraft.world.item.Items.OBSIDIAN)) {
+ if (itemstack.getCount() < world.purpurConfig.anvilDamageObsidianAmount) {
+ // not enough obsidian, play "error" sound and consume
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return InteractionResult.CONSUME;
+ }
+ if (state.is(Blocks.DAMAGED_ANVIL)) {
+ world.destroyBlock(pos, false);
+ } else if (state.is(Blocks.CHIPPED_ANVIL)) {
+ world.setBlock(pos, Blocks.DAMAGED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3);
+ } else if (state.is(Blocks.ANVIL)) {
+ world.setBlock(pos, Blocks.CHIPPED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3);
+ }
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(world.purpurConfig.anvilDamageObsidianAmount);
+ }
+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_LAND, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F);
+ return InteractionResult.CONSUME;
+ }
+ }
+ // Purpur end
if (world.isClientSide) {
return InteractionResult.SUCCESS;
} else {
diff --git a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java
index 95b53450a807fccfa55b59852da52785b8cf3e3d..c69f1d23979a0759472d22760a18d986b2d979b6 100644
--- a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java
@@ -43,6 +43,20 @@ public class AzaleaBlock extends BushBlock implements BonemealableBlock {
@Override
public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
+ // Purpur start
+ growTree(world, random, pos, state);
+ }
+
+ @Override
+ public void randomTick(net.minecraft.world.level.block.state.BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+ double chance = state.getBlock() == Blocks.FLOWERING_AZALEA ? world.purpurConfig.floweringAzaleaGrowthChance : world.purpurConfig.azaleaGrowthChance;
+ if (chance > 0.0D && world.getMaxLocalRawBrightness(pos.above()) > 9 && random.nextDouble() < chance) {
+ growTree(world, random, pos, state);
+ }
+ }
+
+ private void growTree(ServerLevel world, RandomSource random, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) {
+ // Purpur end
TREE_GROWER.growTree(world, world.getChunkSource().getGenerator(), pos, state, random);
}
diff --git a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java
index 3d2b34c5a7c9b00c1164b4f89c2cbff81fc460eb..b5505e926e5cdb447de68e8eb8e46c97eb988e27 100644
--- a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java
@@ -35,6 +35,7 @@ public class BaseCoralPlantTypeBlock extends Block implements SimpleWaterloggedB
}
protected static boolean scanForWater(BlockState state, BlockGetter world, BlockPos pos) {
+ if (!((net.minecraft.world.level.LevelAccessor) world).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur
if (state.getValue(WATERLOGGED)) {
return true;
} else {
diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java
index d40500f9a807cab0b2fb6fa9032f33f4fb74c895..2b66ddafaaca17f64d1e7502dfa4d7576e3e032f 100644
--- a/src/main/java/net/minecraft/world/level/block/BedBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java
@@ -96,7 +96,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock
Vec3 vec3d = pos.getCenter();
- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK);
+ if (world.purpurConfig.bedExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // Purpur
return InteractionResult.SUCCESS;
} else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) {
if (!BedBlock.canSetSpawn(world)) return this.explodeBed(state, world, pos); // Paper - check explode first
@@ -149,7 +149,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock
Vec3 vec3d = blockposition.getCenter();
- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK);
+ if (world.purpurConfig.bedExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // Purpur
return InteractionResult.SUCCESS;
}
}
@@ -173,7 +173,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock
@Override
public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
- super.fallOn(world, state, pos, entity, fallDistance * 0.5F);
+ super.fallOn(world, state, pos, entity, fallDistance); // Purpur
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java
index 5921f7ebb56e1d5d3004ae327271873093cff357..c7bb655983d2ee2c6461d23d6fa921def1a9a74c 100644
--- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java
@@ -236,7 +236,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone
BigDripleafBlock.playTiltSound(world, blockposition, soundeffect);
}
- int i = BigDripleafBlock.DELAY_UNTIL_NEXT_TILT_STATE.getInt(tilt);
+ int i = world.purpurConfig.bigDripleafTiltDelay.getOrDefault(tilt, -1); // Purpur
if (i != -1) {
world.scheduleTick(blockposition, (Block) this, i);
diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java
index fdd9c61b7248e92dbcbec91cd6fe4c6310bba237..dc718efe054aaf36961514287c9cc48e2d28bc4c 100644
--- a/src/main/java/net/minecraft/world/level/block/Block.java
+++ b/src/main/java/net/minecraft/world/level/block/Block.java
@@ -62,6 +62,13 @@ import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.slf4j.Logger;
+// Purpur start
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.nbt.StringTag;
+import net.minecraft.world.Nameable;
+// Purpur end
+
public class Block extends BlockBehaviour implements ItemLike {
private static final Logger LOGGER = LogUtils.getLogger();
@@ -87,6 +94,10 @@ public class Block extends BlockBehaviour implements ItemLike {
public static final int UPDATE_LIMIT = 512;
protected final StateDefinition<Block, BlockState> stateDefinition;
private BlockState defaultBlockState;
+ // Purpur start
+ public float fallDamageMultiplier = 1.0F;
+ public float fallDistanceMultiplier = 1.0F;
+ // Purpur end
// Paper start
public final boolean isDestroyable() {
return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits ||
@@ -313,7 +324,7 @@ public class Block extends BlockBehaviour implements ItemLike {
public static void dropResources(BlockState state, LevelAccessor world, BlockPos pos, @Nullable BlockEntity blockEntity) {
if (world instanceof ServerLevel) {
Block.getDrops(state, (ServerLevel) world, pos, blockEntity).forEach((itemstack) -> {
- Block.popResource((ServerLevel) world, pos, itemstack);
+ Block.popResource((ServerLevel) world, pos, applyDisplayNameAndLoreFromTile(itemstack, blockEntity)); // Purpur
});
state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true);
}
@@ -329,7 +340,7 @@ public class Block extends BlockBehaviour implements ItemLike {
io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.block.CraftBlock.at(world, source), items);
event.callEvent();
for (var drop : event.getDrops()) {
- popResource(world.getMinecraftWorld(), pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop));
+ popResource(world.getMinecraftWorld(), pos, applyDisplayNameAndLoreFromTile(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop), blockEntity)); // Purpur
}
state.spawnAfterBreak(world.getMinecraftWorld(), pos, ItemStack.EMPTY, true);
}
@@ -340,13 +351,53 @@ public class Block extends BlockBehaviour implements ItemLike {
public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) {
if (world instanceof ServerLevel) {
Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> {
- Block.popResource(world, pos, itemstack1);
+ Block.popResource(world, pos, applyDisplayNameAndLoreFromTile(itemstack1, blockEntity)); // Purpur
});
state.spawnAfterBreak((ServerLevel) world, pos, tool, true);
}
}
+ // Purpur start
+ private static ItemStack applyDisplayNameAndLoreFromTile(ItemStack stack, BlockEntity blockEntity) {
+ if (stack.getItem() instanceof BlockItem) {
+ if (blockEntity != null && blockEntity.getLevel() instanceof ServerLevel && blockEntity.getLevel().purpurConfig.persistentTileEntityDisplayNames) {
+ String name = blockEntity.getPersistentDisplayName();
+ ListTag lore = blockEntity.getPersistentLore();
+ if (blockEntity instanceof Nameable) {
+ Nameable namedTile = (Nameable) blockEntity;
+ if (namedTile.hasCustomName()) {
+ name = Component.Serializer.toJson(namedTile.getCustomName());
+ }
+ }
+
+ if (name != null || lore != null) {
+ CompoundTag display = stack.getTagElement("display");
+ if (display == null) {
+ display = new CompoundTag();
+ }
+
+ if (name != null) {
+ display.put("Name", StringTag.valueOf(name));
+ }
+ if (lore != null) {
+ display.put("Lore", lore);
+ }
+
+ CompoundTag tag = stack.getTag();
+ if (tag == null) {
+ tag = new CompoundTag();
+ }
+ tag.put("display", display);
+
+ stack.setTag(tag);
+ }
+ }
+ }
+ return stack;
+ }
+ // Purpur end
+
public static void popResource(Level world, BlockPos pos, ItemStack stack) {
double d0 = (double) EntityType.ITEM.getHeight() / 2.0D;
double d1 = (double) pos.getX() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D);
@@ -430,7 +481,17 @@ public class Block extends BlockBehaviour implements ItemLike {
} // Paper
}
- public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {}
+ // Purpur start
+ @Nullable protected LivingEntity placer = null;
+
+ public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {
+ this.placer = placer;
+ }
+
+ public void forgetPlacer() {
+ this.placer = null;
+ }
+ // Purpur end
public boolean isPossibleToRespawnInThis(BlockState state) {
return !state.isSolid() && !state.liquid();
@@ -449,7 +510,7 @@ public class Block extends BlockBehaviour implements ItemLike {
}
public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
- entity.causeFallDamage(fallDistance, 1.0F, entity.damageSources().fall());
+ entity.causeFallDamage(fallDistance * fallDistanceMultiplier, fallDamageMultiplier, entity.damageSources().fall()); // Purpur
}
public void updateEntityAfterFallOn(BlockGetter world, Entity entity) {
diff --git a/src/main/java/net/minecraft/world/level/block/Blocks.java b/src/main/java/net/minecraft/world/level/block/Blocks.java
index 877035b6c6593a28f475b9c5bcd7727e3fcdb802..ed35878fdb9dffcd46c27d26ee8379401207cef5 100644
--- a/src/main/java/net/minecraft/world/level/block/Blocks.java
+++ b/src/main/java/net/minecraft/world/level/block/Blocks.java
@@ -1093,8 +1093,8 @@ public class Blocks {
public static final Block CAVE_VINES = register("cave_vines", new CaveVinesBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).randomTicks().noCollission().lightLevel(CaveVines.emission(14)).instabreak().sound(SoundType.CAVE_VINES).pushReaction(PushReaction.DESTROY)));
public static final Block CAVE_VINES_PLANT = register("cave_vines_plant", new CaveVinesPlantBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().lightLevel(CaveVines.emission(14)).instabreak().sound(SoundType.CAVE_VINES).pushReaction(PushReaction.DESTROY)));
public static final Block SPORE_BLOSSOM = register("spore_blossom", new SporeBlossomBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).instabreak().noCollission().sound(SoundType.SPORE_BLOSSOM).pushReaction(PushReaction.DESTROY)));
- public static final Block AZALEA = register("azalea", new AzaleaBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).forceSolidOff().instabreak().sound(SoundType.AZALEA).noOcclusion().pushReaction(PushReaction.DESTROY)));
- public static final Block FLOWERING_AZALEA = register("flowering_azalea", new AzaleaBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).forceSolidOff().instabreak().sound(SoundType.FLOWERING_AZALEA).noOcclusion().pushReaction(PushReaction.DESTROY)));
+ public static final Block AZALEA = register("azalea", new AzaleaBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).forceSolidOff().randomTicks().instabreak().sound(SoundType.AZALEA).noOcclusion().pushReaction(PushReaction.DESTROY))); // Purpur
+ public static final Block FLOWERING_AZALEA = register("flowering_azalea", new AzaleaBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).forceSolidOff().randomTicks().instabreak().sound(SoundType.FLOWERING_AZALEA).noOcclusion().pushReaction(PushReaction.DESTROY))); // Purpur
public static final Block MOSS_CARPET = register("moss_carpet", new CarpetBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_GREEN).strength(0.1F).sound(SoundType.MOSS_CARPET).pushReaction(PushReaction.DESTROY)));
public static final Block PINK_PETALS = register("pink_petals", new PinkPetalsBlock(BlockBehaviour.Properties.of().mapColor(MapColor.PLANT).noCollission().sound(SoundType.PINK_PETALS).pushReaction(PushReaction.DESTROY)));
public static final Block MOSS_BLOCK = register("moss_block", new MossBlock(BlockBehaviour.Properties.of().mapColor(MapColor.COLOR_GREEN).strength(0.1F).sound(SoundType.MOSS).pushReaction(PushReaction.DESTROY)));
@@ -1159,7 +1159,7 @@ public class Blocks {
}
private static Boolean ocelotOrParrot(BlockState state, BlockGetter world, BlockPos pos, EntityType<?> type) {
- return (boolean)type == EntityType.OCELOT || type == EntityType.PARROT;
+ return type == EntityType.OCELOT || type == EntityType.PARROT; // Purpur - decompile error
}
private static BedBlock bed(DyeColor color) {
diff --git a/src/main/java/net/minecraft/world/level/block/BushBlock.java b/src/main/java/net/minecraft/world/level/block/BushBlock.java
index 03fde6e47c4a347c62fe9b4a3351769aedf874f6..ca906b0250e5332f7ececf1419ca6d2c1d385adc 100644
--- a/src/main/java/net/minecraft/world/level/block/BushBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/BushBlock.java
@@ -48,4 +48,24 @@ public class BushBlock extends Block {
public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) {
return type == PathComputationType.AIR && !this.hasCollision ? true : super.isPathfindable(state, world, pos, type);
}
+
+ // Purpur start
+ public void playerDestroyAndReplant(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, net.minecraft.world.item.ItemStack itemInHand, net.minecraft.world.level.ItemLike itemToReplant) {
+ player.awardStat(net.minecraft.stats.Stats.BLOCK_MINED.get(this));
+ player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED);
+ java.util.List<net.minecraft.world.item.ItemStack> dropList = Block.getDrops(state, (net.minecraft.server.level.ServerLevel) world, pos, blockEntity, player, itemInHand);
+
+ boolean planted = false;
+ for (net.minecraft.world.item.ItemStack itemToDrop : dropList) {
+ if (!planted && itemToDrop.getItem() == itemToReplant) {
+ world.setBlock(pos, defaultBlockState(), 3);
+ itemToDrop.setCount(itemToDrop.getCount() - 1);
+ planted = true;
+ }
+ Block.popResource(world, pos, itemToDrop);
+ }
+
+ state.spawnAfterBreak((net.minecraft.server.level.ServerLevel) world, pos, itemInHand, true);
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java
index 0003fb51ae3a6575575e10b4c86719f3061e2577..0d5f87d24231f6d2b8639825bcd62dd2f8791c8e 100644
--- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java
@@ -22,7 +22,7 @@ import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
-public class CactusBlock extends Block {
+public class CactusBlock extends Block implements BonemealableBlock { // Purpur
public static final IntegerProperty AGE = BlockStateProperties.AGE_15;
public static final int MAX_AGE = 15;
@@ -107,7 +107,7 @@ public class CactusBlock extends Block {
enumdirection = (Direction) iterator.next();
iblockdata1 = world.getBlockState(pos.relative(enumdirection));
- } while (!iblockdata1.isSolid() && !world.getFluidState(pos.relative(enumdirection)).is(FluidTags.LAVA));
+ } while ((!world.getWorldBorder().world.purpurConfig.cactusBreaksFromSolidNeighbors || !iblockdata1.isSolid()) && !world.getFluidState(pos.relative(enumdirection)).is(FluidTags.LAVA)); // Purpur
return false;
}
@@ -129,4 +129,34 @@ public class CactusBlock extends Block {
public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) {
return false;
}
+
+ // Purpur start
+ @Override
+ public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, BlockState state, boolean isClient) {
+ if (!((Level) world).purpurConfig.cactusAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false;
+
+ int cactusHeight = 0;
+ while (world.getBlockState(pos.below(cactusHeight)).is(this)) {
+ cactusHeight++;
+ }
+
+ return cactusHeight < ((Level) world).paperConfig().maxGrowthHeight.cactus;
+ }
+
+ @Override
+ public boolean isBonemealSuccess(Level world, RandomSource random, BlockPos pos, BlockState state) {
+ return true;
+ }
+
+ @Override
+ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
+ int cactusHeight = 0;
+ while (world.getBlockState(pos.below(cactusHeight)).is(this)) {
+ cactusHeight++;
+ }
+ for (int i = 0; i <= world.paperConfig().maxGrowthHeight.cactus - cactusHeight; i++) {
+ world.setBlockAndUpdate(pos.above(i), state.setValue(CactusBlock.AGE, 0));
+ }
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java
index 219c87dcf065e86512f330fbeec59e55f4675083..f8fd3b320494d1c1e8ee3d170f2feebd152230fa 100644
--- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java
@@ -122,7 +122,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB
BlockPos blockposition = ctx.getClickedPos();
boolean flag = world.getFluidState(blockposition).getType() == Fluids.WATER;
- return (BlockState) ((BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(CampfireBlock.WATERLOGGED, flag)).setValue(CampfireBlock.SIGNAL_FIRE, this.isSmokeSource(world.getBlockState(blockposition.below())))).setValue(CampfireBlock.LIT, !flag)).setValue(CampfireBlock.FACING, ctx.getHorizontalDirection());
+ return (BlockState) ((BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(CampfireBlock.WATERLOGGED, flag)).setValue(CampfireBlock.SIGNAL_FIRE, this.isSmokeSource(world.getBlockState(blockposition.below())))).setValue(CampfireBlock.LIT, world.purpurConfig.campFireLitWhenPlaced ? !flag : world.purpurConfig.campFireLitWhenPlaced)).setValue(CampfireBlock.FACING, ctx.getHorizontalDirection()); // Purpur
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java b/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java
index 23c487e295b3b736d8800f0c884324c9b18a5373..ebeb7caf7fd4f45714bab0856a48b847a544cce7 100644
--- a/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java
@@ -64,7 +64,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock {
SnowGolem entitysnowman = (SnowGolem) EntityType.SNOW_GOLEM.create(world);
if (entitysnowman != null) {
- CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection, entitysnowman, shapedetector_shapedetectorcollection.getBlock(0, 2, 0).getPos());
+ CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection, entitysnowman, shapedetector_shapedetectorcollection.getBlock(0, 2, 0).getPos(), this.placer); // Purpur
}
} else {
BlockPattern.BlockPatternMatch shapedetector_shapedetectorcollection1 = this.getOrCreateIronGolemFull().find(world, pos);
@@ -74,7 +74,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock {
if (entityirongolem != null) {
entityirongolem.setPlayerCreated(true);
- CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection1, entityirongolem, shapedetector_shapedetectorcollection1.getBlock(1, 2, 0).getPos());
+ CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection1, entityirongolem, shapedetector_shapedetectorcollection1.getBlock(1, 2, 0).getPos(), this.placer); // Purpur
}
}
}
@@ -82,6 +82,16 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock {
}
private static void spawnGolemInWorld(Level world, BlockPattern.BlockPatternMatch patternResult, Entity entity, BlockPos pos) {
+ // Purpur start
+ spawnGolemInWorld(world, patternResult, entity, pos, null);
+ }
+ private static void spawnGolemInWorld(Level world, BlockPattern.BlockPatternMatch patternResult, Entity entity, BlockPos pos, net.minecraft.world.entity.LivingEntity placer) {
+ if (entity instanceof SnowGolem snowGolem) {
+ snowGolem.setSummoner(placer == null ? null : placer.getUUID());
+ } else if (entity instanceof IronGolem ironGolem) {
+ ironGolem.setSummoner(placer == null ? null : placer.getUUID());
+ }
+ // Purpur end
// clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - moved down
entity.moveTo((double) pos.getX() + 0.5D, (double) pos.getY() + 0.05D, (double) pos.getZ() + 0.5D, 0.0F, 0.0F);
// CraftBukkit start
diff --git a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java
index 2f85b893dd0abc39fcedec65acc89e1567faf6f0..3ee012a9ef8cada0b2203e53b2f731f60f697cb1 100644
--- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java
@@ -29,7 +29,7 @@ public class CauldronBlock extends AbstractCauldronBlock {
}
protected static boolean shouldHandlePrecipitation(Level world, Biome.Precipitation precipitation) {
- return precipitation == Biome.Precipitation.RAIN ? world.getRandom().nextFloat() < 0.05F : (precipitation == Biome.Precipitation.SNOW ? world.getRandom().nextFloat() < 0.1F : false);
+ return precipitation == Biome.Precipitation.RAIN ? world.getRandom().nextFloat() < world.purpurConfig.cauldronRainChance : (precipitation == Biome.Precipitation.SNOW ? world.getRandom().nextFloat() < world.purpurConfig.cauldronPowderSnowChance : false); // Purpur
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java
index 18b5bce1138d50be32e5da013221be69dc47e21f..58b4a0d97af37f7164db86ef821f04102c6c5ddd 100644
--- a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java
@@ -88,4 +88,11 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements Bonemealabl
public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
world.setBlock(pos, state.setValue(BERRIES, Boolean.valueOf(true)), 2);
}
+
+ // Purpur start
+ @Override
+ public int getMaxGrowthAge() {
+ return org.purpurmc.purpur.PurpurConfig.caveVinesMaxGrowthAge;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/ChestBlock.java b/src/main/java/net/minecraft/world/level/block/ChestBlock.java
index 5e22d175b1048a58802cdf64ac70a8b56329e915..d81946b400f208c39941128ce823ff7709741c10 100644
--- a/src/main/java/net/minecraft/world/level/block/ChestBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/ChestBlock.java
@@ -355,6 +355,7 @@ public class ChestBlock extends AbstractChestBlock<ChestBlockEntity> implements
}
private static boolean isBlockedChestByBlock(BlockGetter world, BlockPos pos) {
+ if (world instanceof Level && ((Level) world).purpurConfig.chestOpenWithBlockOnTop) return false; // Purpur
BlockPos blockposition1 = pos.above();
return world.getBlockState(blockposition1).isRedstoneConductor(world, blockposition1);
diff --git a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java
index 10d3912ef043eefdf89105332e29b0d2bf4a5539..596b77306f690a2298835f0f0fea1abee2a7c85d 100644
--- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java
@@ -229,20 +229,28 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder {
ItemStack itemstack = player.getItemInHand(hand);
if (i < 8 && ComposterBlock.COMPOSTABLES.containsKey(itemstack.getItem())) {
- if (i < 7 && !world.isClientSide) {
- BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, itemstack);
- // Paper start - handle cancelled events
- if (iblockdata1 == null) {
- return InteractionResult.PASS;
- }
- // Paper end
+ // Purpur start
+ BlockState newState = process(i, state, world, itemstack, pos, player);
+ if (newState == null) {
+ return InteractionResult.PASS;
+ }
- world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0);
- player.awardStat(Stats.ITEM_USED.get(itemstack.getItem()));
- if (!player.getAbilities().instabuild) {
- itemstack.shrink(1);
- }
+ if (world.purpurConfig.composterBulkProcess && player.isShiftKeyDown() && newState != state) {
+ BlockState oldState;
+ int oldCount, newCount, oldLevel, newLevel;
+ do {
+ oldState = newState;
+ oldCount = itemstack.getCount();
+ oldLevel = oldState.getValue(ComposterBlock.LEVEL);
+ newState = process(oldLevel, oldState, world, itemstack, pos, player);
+ if (newState == null) {
+ return InteractionResult.PASS;
+ }
+ newCount = itemstack.getCount();
+ newLevel = newState.getValue(ComposterBlock.LEVEL);
+ } while (newCount > 0 && (newCount != oldCount || newLevel != oldLevel || newState != oldState));
}
+ // Purpur end
return InteractionResult.sidedSuccess(world.isClientSide);
} else if (i == 8) {
@@ -253,6 +261,26 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder {
}
}
+ private static BlockState process(int level, BlockState state, Level world, ItemStack itemstack, BlockPos pos, Player player) {
+ if (level < 7 && !world.isClientSide) {
+ BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, itemstack);
+ // Paper start - handle cancelled events
+ if (iblockdata1 == null) {
+ return iblockdata1;
+ }
+ // Paper end
+
+ world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0);
+ player.awardStat(Stats.ITEM_USED.get(itemstack.getItem()));
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
+ return iblockdata1;
+ }
+ return state;
+ }
+ // Purpur end
+
public static BlockState insertItem(Entity user, BlockState state, ServerLevel world, ItemStack stack, BlockPos pos) {
int i = (Integer) state.getValue(ComposterBlock.LEVEL);
diff --git a/src/main/java/net/minecraft/world/level/block/CoralBlock.java b/src/main/java/net/minecraft/world/level/block/CoralBlock.java
index 88faea00be60a519f56f975a5311df5e1eb3e6b8..cbb726ac367be81e27d3a86643baf7c4f0746edf 100644
--- a/src/main/java/net/minecraft/world/level/block/CoralBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/CoralBlock.java
@@ -45,6 +45,7 @@ public class CoralBlock extends Block {
}
protected boolean scanForWater(BlockGetter world, BlockPos pos) {
+ if (!((net.minecraft.world.level.LevelAccessor) world).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur
Direction[] aenumdirection = Direction.values();
int i = aenumdirection.length;
diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java
index decb8caa67bc5f4525e4d92fedf465a17171fceb..26b3f162f25bed3e9d8001e3d9c2f8d7d4b3e08a 100644
--- a/src/main/java/net/minecraft/world/level/block/CropBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java
@@ -168,7 +168,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock {
@Override
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper
- if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit
+ if (entity instanceof Ravager && world.purpurConfig.ravagerGriefableBlocks.contains(world.getBlockState(pos).getBlock()) && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), (!world.purpurConfig.ravagerBypassMobGriefing && !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)))) { // CraftBukkit // Purpur
world.destroyBlock(pos, true, entity);
}
@@ -203,4 +203,15 @@ public class CropBlock extends BushBlock implements BonemealableBlock {
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(CropBlock.AGE);
}
+
+ // Purpur start
+ @Override
+ public void playerDestroy(Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops) {
+ if (world.purpurConfig.hoeReplantsCrops && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) {
+ super.playerDestroyAndReplant(world, player, pos, state, blockEntity, itemInHand, getBaseSeedId());
+ } else {
+ super.playerDestroy(world, player, pos, state, blockEntity, itemInHand, includeDrops);
+ }
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/DoorBlock.java b/src/main/java/net/minecraft/world/level/block/DoorBlock.java
index c028a7158e41a0754abb8e24dcd647633fbf3fe8..cd65d32f4af016d4937e598c71386a3072f4c490 100644
--- a/src/main/java/net/minecraft/world/level/block/DoorBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/DoorBlock.java
@@ -167,6 +167,7 @@ public class DoorBlock extends Block {
public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if (!this.type.canOpenByHand()) {
return InteractionResult.PASS;
+ } else if (requiresRedstone(world, state, pos)) { return InteractionResult.CONSUME; // Purpur
} else {
state = (BlockState) state.cycle(DoorBlock.OPEN);
world.setBlock(pos, state, 10);
@@ -270,4 +271,18 @@ public class DoorBlock extends Block {
flag = false;
return flag;
}
+
+ // Purpur start
+ public static boolean requiresRedstone(Level level, BlockState state, BlockPos pos) {
+ if (level.purpurConfig.doorRequiresRedstone.contains(state.getBlock())) {
+ // force update client
+ BlockPos otherPos = pos.relative(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN);
+ BlockState otherState = level.getBlockState(otherPos);
+ level.sendBlockUpdated(pos, state, state, 3);
+ level.sendBlockUpdated(otherPos, otherState, otherState, 3);
+ return true;
+ }
+ return false;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java b/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java
index 7e1edcc7b9f170b7c649437c2f0dd78c0bab9be4..5f8ac1fdac2c334951261f2b9702f5e711743c88 100644
--- a/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java
@@ -42,8 +42,8 @@ public class DragonEggBlock extends FallingBlock {
}
private void teleport(BlockState state, Level world, BlockPos pos) {
+ if (!world.purpurConfig.dragonEggTeleport) return; // Purpur
WorldBorder worldborder = world.getWorldBorder();
-
for (int i = 0; i < 1000; ++i) {
BlockPos blockposition1 = pos.offset(world.random.nextInt(16) - world.random.nextInt(16), world.random.nextInt(8) - world.random.nextInt(8), world.random.nextInt(16) - world.random.nextInt(16));
diff --git a/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java b/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java
index 839b7bc9392906dca384003468746963631fe095..286f34eef22a85be3fe9747dc3c3f9a7d51f437c 100644
--- a/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java
@@ -29,6 +29,8 @@ import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
+import net.minecraft.world.Containers; // Purpur
+import net.minecraft.world.item.Items; // Purpur
public class EnchantmentTableBlock extends BaseEntityBlock {
protected static final VoxelShape SHAPE = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 12.0D, 16.0D);
@@ -121,4 +123,18 @@ public class EnchantmentTableBlock extends BaseEntityBlock {
public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) {
return false;
}
+
+ // Purpur start
+ @Override
+ public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean moved) {
+ BlockEntity blockEntity = level.getBlockEntity(pos);
+
+ if (level.purpurConfig.enchantmentTableLapisPersists && blockEntity instanceof EnchantmentTableBlockEntity enchantmentTable) {
+ Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), new ItemStack(Items.LAPIS_LAZULI, enchantmentTable.getLapis()));
+ level.updateNeighbourForOutputSignal(pos, this);
+ }
+
+ super.onRemove(state, level, pos, newState, moved);
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java
index 41d7cff39fc37955877668337689b4b26cd8c7cf..2deddc746e43896584bd65ba8e7971a80acb4a4d 100644
--- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java
@@ -46,6 +46,14 @@ public class EndPortalBlock extends BaseEntityBlock {
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper
if (world instanceof ServerLevel && entity.canChangeDimensions() && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-pos.getX()), (double) (-pos.getY()), (double) (-pos.getZ()))), state.getShape(world, pos), BooleanOp.AND)) {
+ // Purpur start
+ if (entity.isPassenger() || entity.isVehicle()) {
+ if (new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, PlayerTeleportEvent.TeleportCause.END_PORTAL).callEvent()) {
+ this.entityInside(state, world, pos, entity);
+ }
+ return;
+ }
+ // Purpur end
ResourceKey<Level> resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends
ServerLevel worldserver = ((ServerLevel) world).getServer().getLevel(resourcekey);
@@ -53,6 +61,22 @@ public class EndPortalBlock extends BaseEntityBlock {
// return; // CraftBukkit - always fire event in case plugins wish to change it
}
+ // Purpur start
+ if (!world.purpurConfig.endPortalSafeTeleporting) {
+ // CraftBukkit start - Entity in portal
+ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()));
+ world.getCraftServer().getPluginManager().callEvent(event);
+
+ if (entity instanceof ServerPlayer) {
+ ((ServerPlayer) entity).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL);
+ return;
+ }
+ // CraftBukkit end
+ entity.changeDimension(worldserver);
+ return;
+ }
+ // Purpur end
+
// Paper start - move all of this logic into portal tick
entity.portalWorld = ((ServerLevel)world);
entity.portalBlock = pos.immutable();
diff --git a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java
index 7385e91f32f070e86a4e0fd3d214f55d832c7979..7b73de87236a60ce7343c29ec147e1866b448ba3 100644
--- a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java
@@ -85,6 +85,34 @@ public class EnderChestBlock extends AbstractChestBlock<EnderChestBlockEntity> i
EnderChestBlockEntity enderChestBlockEntity = (EnderChestBlockEntity)blockEntity;
playerEnderChestContainer.setActiveChest(enderChestBlockEntity);
player.openMenu(new SimpleMenuProvider((syncId, inventory, playerx) -> {
+ // Purpur start
+ if (org.purpurmc.purpur.PurpurConfig.enderChestSixRows) {
+ if (org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) {
+ org.bukkit.craftbukkit.entity.CraftHumanEntity bukkitPlayer = player.getBukkitEntity();
+ if (bukkitPlayer.hasPermission("purpur.enderchest.rows.six")) {
+ player.sixRowEnderchestSlotCount = 54;
+ return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer);
+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.five")) {
+ player.sixRowEnderchestSlotCount = 45;
+ return ChestMenu.fiveRows(syncId, inventory, playerEnderChestContainer);
+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.four")) {
+ player.sixRowEnderchestSlotCount = 36;
+ return ChestMenu.fourRows(syncId, inventory, playerEnderChestContainer);
+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.three")) {
+ player.sixRowEnderchestSlotCount = 27;
+ return ChestMenu.threeRows(syncId, inventory, playerEnderChestContainer);
+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.two")) {
+ player.sixRowEnderchestSlotCount = 18;
+ return ChestMenu.twoRows(syncId, inventory, playerEnderChestContainer);
+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.one")) {
+ player.sixRowEnderchestSlotCount = 9;
+ return ChestMenu.oneRow(syncId, inventory, playerEnderChestContainer);
+ }
+ }
+ player.sixRowEnderchestSlotCount = -1;
+ return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer);
+ }
+ // Purpur end
return ChestMenu.threeRows(syncId, inventory, playerEnderChestContainer);
}, CONTAINER_TITLE));
player.awardStat(Stats.OPEN_ENDERCHEST);
diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java
index 5946f06f63b5694034bd027984a4925b0831d439..d566f67f8f6f1748023430de4f191881b79e44a1 100644
--- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java
@@ -101,7 +101,7 @@ public class FarmBlock extends Block {
@Override
public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
super.fallOn(world, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage.
- if (!world.isClientSide && world.random.nextFloat() < fallDistance - 0.5F && entity instanceof LivingEntity && (entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) {
+ if (!world.isClientSide && (world.purpurConfig.farmlandTrampleHeight >= 0D ? fallDistance >= world.purpurConfig.farmlandTrampleHeight : world.random.nextFloat() < fallDistance - 0.5F) && entity instanceof LivingEntity && (entity instanceof Player || world.purpurConfig.farmlandBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { // Purpur
// CraftBukkit start - Interact soil
org.bukkit.event.Cancellable cancellable;
if (entity instanceof Player) {
@@ -115,6 +115,22 @@ public class FarmBlock extends Block {
return;
}
+ // Purpur start
+ if (world.purpurConfig.farmlandTramplingDisabled) return;
+ if (world.purpurConfig.farmlandTramplingOnlyPlayers && !(entity instanceof Player)) return;
+ if (world.purpurConfig.farmlandAlpha) {
+ Block block = world.getBlockState(pos.below()).getBlock();
+ if (block instanceof FenceBlock || block instanceof WallBlock) {
+ return;
+ }
+ }
+ if (world.purpurConfig.farmlandTramplingFeatherFalling) {
+ Iterator<net.minecraft.world.item.ItemStack> armor = entity.getArmorSlots().iterator();
+ if (armor.hasNext() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FALL_PROTECTION, armor.next()) >= (int) entity.fallDistance) {
+ return;
+ }
+ }
+ // Purpur end
if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) {
return;
}
@@ -162,7 +178,7 @@ public class FarmBlock extends Block {
}
}
- return false;
+ return ((ServerLevel) world).purpurConfig.farmlandGetsMoistFromBelow && world.getFluidState(pos.relative(Direction.DOWN)).is(FluidTags.WATER); // Purpur;
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
index 3a1aa4e2405090ccebefb7f5944f36462929e221..f3cf9f06de40054720d1847c1869a9d82592134d 100644
--- a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
@@ -30,12 +30,12 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements
@Override
public BlockState getStateForPlacement(LevelAccessor world) {
- return (BlockState) this.defaultBlockState().setValue(GrowingPlantHeadBlock.AGE, world.getRandom().nextInt(25));
+ return (BlockState) this.defaultBlockState().setValue(GrowingPlantHeadBlock.AGE, world.getRandom().nextInt(getMaxGrowthAge())); // Purpur
}
@Override
public boolean isRandomlyTicking(BlockState state) {
- return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25;
+ return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) < getMaxGrowthAge(); // Purpur
}
@Override
@@ -51,7 +51,7 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements
} else {
modifier = world.spigotConfig.caveVinesModifier;
}
- if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution
+ if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < getMaxGrowthAge() && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution // Purpur
// Spigot end
BlockPos blockposition1 = pos.relative(this.growthDirection);
@@ -73,11 +73,11 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements
}
public BlockState getMaxAgeState(BlockState state) {
- return (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, 25);
+ return (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, getMaxGrowthAge()); // Purpur
}
public boolean isMaxAge(BlockState state) {
- return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) == 25;
+ return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) >= getMaxGrowthAge(); // Purpur
}
protected BlockState updateBodyAfterConvertedFromHead(BlockState from, BlockState to) {
@@ -119,13 +119,13 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements
@Override
public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
BlockPos blockposition1 = pos.relative(this.growthDirection);
- int i = Math.min((Integer) state.getValue(GrowingPlantHeadBlock.AGE) + 1, 25);
+ int i = Math.min((Integer) state.getValue(GrowingPlantHeadBlock.AGE) + 1, getMaxGrowthAge()); // Purpur
int j = this.getBlocksToGrowWhenBonemealed(random);
for (int k = 0; k < j && this.canGrowInto(world.getBlockState(blockposition1)); ++k) {
world.setBlockAndUpdate(blockposition1, (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, i));
blockposition1 = blockposition1.relative(this.growthDirection);
- i = Math.min(i + 1, 25);
+ i = Math.min(i + 1, getMaxGrowthAge()); // Purpur
}
}
@@ -138,4 +138,6 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements
protected GrowingPlantHeadBlock getHeadBlock() {
return this;
}
+
+ public abstract int getMaxGrowthAge(); // Purpur
}
diff --git a/src/main/java/net/minecraft/world/level/block/HayBlock.java b/src/main/java/net/minecraft/world/level/block/HayBlock.java
index cfbe1dae76db76cf54a4f5d72aca72d5e893859e..74cb10230d459ac9f300a9d59af504d233ac663e 100644
--- a/src/main/java/net/minecraft/world/level/block/HayBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/HayBlock.java
@@ -15,6 +15,6 @@ public class HayBlock extends RotatedPillarBlock {
@Override
public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
- entity.causeFallDamage(fallDistance, 0.2F, world.damageSources().fall());
+ super.fallOn(world, state, pos, entity, fallDistance); // Purpur
}
}
diff --git a/src/main/java/net/minecraft/world/level/block/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java
index 04089e6f7d6e3d532b00585870283922b6be5246..61e6d14abd54ecd5e43a5459f8daa7d86adedf44 100644
--- a/src/main/java/net/minecraft/world/level/block/IceBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java
@@ -33,7 +33,7 @@ public class IceBlock extends HalfTransparentBlock {
public void afterDestroy(Level world, BlockPos pos, ItemStack tool) {
// Paper end
if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) == 0) {
- if (world.dimensionType().ultraWarm()) {
+ if (world.isNether() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur
world.removeBlock(pos, false);
return;
}
@@ -61,7 +61,7 @@ public class IceBlock extends HalfTransparentBlock {
return;
}
// CraftBukkit end
- if (world.dimensionType().ultraWarm()) {
+ if (world.isNether() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur
world.removeBlock(pos, false);
} else {
world.setBlockAndUpdate(pos, IceBlock.meltsInto());
diff --git a/src/main/java/net/minecraft/world/level/block/KelpBlock.java b/src/main/java/net/minecraft/world/level/block/KelpBlock.java
index bc66fa91ec3e13431d5d9b6e17935cab73066be7..0f16b5ed2e249f3d8f583dc941e32066d354cf95 100644
--- a/src/main/java/net/minecraft/world/level/block/KelpBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/KelpBlock.java
@@ -64,4 +64,11 @@ public class KelpBlock extends GrowingPlantHeadBlock implements LiquidBlockConta
public FluidState getFluidState(BlockState state) {
return Fluids.WATER.getSource(false);
}
+
+ // Purpur start
+ @Override
+ public int getMaxGrowthAge() {
+ return org.purpurmc.purpur.PurpurConfig.kelpMaxGrowthAge;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
index bbabe4ad8afcc3a2069f6e9d4a9adcb643266894..55419bd653f7f5391fa13cd15a0b00fbff5e9c39 100644
--- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java
@@ -105,7 +105,7 @@ public class LiquidBlock extends Block implements BucketPickup {
@Override
public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) {
- if (this.shouldSpreadLiquid(world, pos, state)) {
+ if (world.purpurConfig.tickFluids && this.shouldSpreadLiquid(world, pos, state)) { // Purpur
world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper
}
@@ -133,7 +133,7 @@ public class LiquidBlock extends Block implements BucketPickup {
@Override
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) {
- if (state.getFluidState().isSource() || neighborState.getFluidState().isSource()) {
+ if (world.getMinecraftWorld().purpurConfig.tickFluids && state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { // Purpur
world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world));
}
@@ -142,7 +142,7 @@ public class LiquidBlock extends Block implements BucketPickup {
@Override
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) {
- if (this.shouldSpreadLiquid(world, pos, state)) {
+ if (world.purpurConfig.tickFluids && this.shouldSpreadLiquid(world, pos, state)) { // Purpur
world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper
}
diff --git a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java
index 1b766045687e4dcded5cbcc50b746c55b9a34e22..be365914856593bb3c4e1945cc990786072f2953 100644
--- a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java
@@ -22,7 +22,7 @@ public class MagmaBlock extends Block {
@Override
public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) {
- if (!entity.isSteppingCarefully() && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) {
+ if ((!entity.isSteppingCarefully() || world.purpurConfig.magmaBlockDamageWhenSneaking) && entity instanceof LivingEntity && (world.purpurConfig.magmaBlockDamageWithFrostWalker || !EnchantmentHelper.hasFrostWalker((LivingEntity) entity))) { // Purpur
org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit
entity.hurt(world.damageSources().hotFloor(), 1.0F);
org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; // CraftBukkit
diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java
index a6ab0d0defc05e56a91084c49897059670a1324b..589b437e7c97c846410f293e2f014bdcd7cb333e 100644
--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java
@@ -52,7 +52,7 @@ public class NetherPortalBlock extends Block {
@Override
public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
- if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(2000) < world.getDifficulty().getId()) { // Spigot
+ if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(world.purpurConfig.piglinPortalSpawnModifier) < world.getDifficulty().getId()) { // Spigot // Purpur
while (world.getBlockState(pos).is((Block) this)) {
pos = pos.below();
}
@@ -84,6 +84,14 @@ public class NetherPortalBlock extends Block {
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper
if (entity.canChangeDimensions()) {
+ // Purpur start
+ if (entity.isPassenger() || entity.isVehicle()) {
+ if (new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.NETHER_PORTAL).callEvent()) {
+ this.entityInside(state, world, pos, entity);
+ }
+ return;
+ }
+ // Purpur end
// CraftBukkit start - Entity in portal
EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()));
world.getCraftServer().getPluginManager().callEvent(event);
diff --git a/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java b/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java
index e55720c4d2fbdf6aae526910e87a67c29cf906fd..4310d62c45c776eea81987809309da08c242c7b0 100644
--- a/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java
@@ -14,7 +14,7 @@ import net.minecraft.world.level.block.state.properties.IntegerProperty;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
-public class NetherWartBlock extends BushBlock {
+public class NetherWartBlock extends BushBlock implements BonemealableBlock { // Purpur
public static final int MAX_AGE = 3;
public static final IntegerProperty AGE = BlockStateProperties.AGE_3;
@@ -60,4 +60,32 @@ public class NetherWartBlock extends BushBlock {
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(NetherWartBlock.AGE);
}
+
+ // Purpur start
+ @Override
+ public void playerDestroy(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops) {
+ if (world.purpurConfig.hoeReplantsNetherWarts && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) {
+ super.playerDestroyAndReplant(world, player, pos, state, blockEntity, itemInHand, Items.NETHER_WART);
+ } else {
+ super.playerDestroy(world, player, pos, state, blockEntity, itemInHand, includeDrops);
+ }
+ }
+
+ @Override
+ public boolean isValidBonemealTarget(net.minecraft.world.level.LevelReader world, BlockPos pos, BlockState state, boolean isClient) {
+ return ((net.minecraft.world.level.Level) world).purpurConfig.netherWartAffectedByBonemeal && state.getValue(NetherWartBlock.AGE) < 3;
+ }
+
+ @Override
+ public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) {
+ return true;
+ }
+
+ @Override
+ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
+ int i = Math.min(3, state.getValue(NetherWartBlock.AGE) + 1);
+ state = state.setValue(NetherWartBlock.AGE, i);
+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state, 2); // CraftBukkit
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java
index e46d84750bdd7c940f400efda226e12a3fdc3848..6343cd0c33cafb30225cfae17ea1cf15859073b1 100644
--- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java
@@ -87,7 +87,7 @@ public class NoteBlock extends Block {
}
private void playNote(@Nullable Entity entity, BlockState state, Level world, BlockPos pos) {
- if (((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || world.getBlockState(pos.above()).isAir()) {
+ if (world.purpurConfig.noteBlockIgnoreAbove || ((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || world.getBlockState(pos.above()).isAir()) { // Purpur
// CraftBukkit start
// org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE));
// if (event.isCancelled()) {
diff --git a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java
index 7b45d6b9a005036ca5051d089a7be792eb87012f..8806c97ecc6bdd8a64c2d82bb2f58f46ac37c468 100644
--- a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java
@@ -64,6 +64,7 @@ public class ObserverBlock extends DirectionalBlock {
@Override
public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) {
if (state.getValue(ObserverBlock.FACING) == direction && !(Boolean) state.getValue(ObserverBlock.POWERED)) {
+ if (!world.getMinecraftWorld().purpurConfig.disableObserverClocks || !(neighborState.getBlock() instanceof ObserverBlock) || neighborState.getValue(ObserverBlock.FACING).getOpposite() != direction) // Purpur
this.startSignal(world, pos);
}
diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java
index cd943997f11f5ea5c600fdc6db96043fb0fa713c..4adeda49a2e422e11f885bffb311653d99159bf4 100644
--- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java
@@ -186,7 +186,7 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate
@VisibleForTesting
public static void maybeTransferFluid(BlockState state, ServerLevel world, BlockPos pos, float dripChance) {
- if (dripChance <= 0.17578125F || dripChance <= 0.05859375F) {
+ if (dripChance <= world.purpurConfig.cauldronDripstoneWaterFillChance || dripChance <= world.purpurConfig.cauldronDripstoneLavaFillChance) { // Purpur
if (PointedDripstoneBlock.isStalactiteStartPos(state, world, pos)) {
Optional<PointedDripstoneBlock.FluidInfo> optional = PointedDripstoneBlock.getFluidAboveStalactite(world, pos, state);
@@ -195,13 +195,13 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate
float f1;
if (fluidtype == Fluids.WATER) {
- f1 = 0.17578125F;
+ f1 = world.purpurConfig.cauldronDripstoneWaterFillChance; // Purpur
} else {
if (fluidtype != Fluids.LAVA) {
return;
}
- f1 = 0.05859375F;
+ f1 = world.purpurConfig.cauldronDripstoneLavaFillChance; // Purpur
}
if (dripChance < f1) {
diff --git a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java
index 7e04ecba2a14be0f0d47c917368abd2a2bd64a05..5c944e39e611201c6ae4dad14511a54c451204f6 100644
--- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java
@@ -72,7 +72,7 @@ public class PowderSnowBlock extends Block implements BucketPickup {
if (!world.isClientSide) {
// CraftBukkit start
if (entity.isOnFire() && entity.mayInteract(world, pos)) {
- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) {
+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !((world.purpurConfig.powderSnowBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) || entity instanceof Player))) {
return;
}
// CraftBukkit end
diff --git a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java
index 7fddb6fa8fd30ef88346a59f7867aae792f13772..40893e71fe8447b695350273bef9623bd5accdcd 100644
--- a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java
@@ -23,7 +23,7 @@ public class PoweredRailBlock extends BaseRailBlock {
}
protected boolean findPoweredRailSignal(Level world, BlockPos pos, BlockState state, boolean flag, int distance) {
- if (distance >= 8) {
+ if (distance >= world.purpurConfig.railActivationRange) { // Purpur
return false;
} else {
int j = pos.getX();
diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java
index 2ed78cf83c0ae66a6ddba1ff307da89a24b0d0a8..ae17d6a54fad0bd2d71d306f418b5ced2f11b863 100644
--- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java
@@ -141,7 +141,7 @@ public class RespawnAnchorBlock extends Block {
};
Vec3 vec3d = explodedPos.getCenter();
- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // Paper
+ if (world.purpurConfig.respawnAnchorExplode)world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), explosiondamagecalculator, vec3d, (float) world.purpurConfig.respawnAnchorExplosionPower, world.purpurConfig.respawnAnchorExplosionFire, world.purpurConfig.respawnAnchorExplosionEffect); // Paper // Purpur
}
public static boolean canSetSpawn(Level world) {
diff --git a/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java b/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java
index 02d01eabb9606ae8c3b76ad9fa4bb9a525e247b1..ce51fec4a874f9466f9966684c535315dbf40b9e 100644
--- a/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java
@@ -130,7 +130,7 @@ public class SculkShriekerBlock extends BaseEntityBlock implements SimpleWaterlo
@Nullable
@Override
public BlockState getStateForPlacement(BlockPlaceContext ctx) {
- return (BlockState) this.defaultBlockState().setValue(SculkShriekerBlock.WATERLOGGED, ctx.getLevel().getFluidState(ctx.getClickedPos()).getType() == Fluids.WATER);
+ return (BlockState) this.defaultBlockState().setValue(SculkShriekerBlock.WATERLOGGED, ctx.getLevel().getFluidState(ctx.getClickedPos()).getType() == Fluids.WATER).setValue(SculkShriekerBlock.CAN_SUMMON, ctx.getLevel().purpurConfig.sculkShriekerCanSummonDefault); // Purpur
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java b/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java
index b51155ad12515b2d0dd0f202580b9f455c114d9a..dd6c82a418ee299d7a5614cb0260949c198b4149 100644
--- a/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java
@@ -137,7 +137,7 @@ public class ShulkerBoxBlock extends BaseEntityBlock {
public void playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) {
BlockEntity blockEntity = world.getBlockEntity(pos);
if (blockEntity instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity) {
- if (!world.isClientSide && player.isCreative() && !shulkerBoxBlockEntity.isEmpty()) {
+ if (world.purpurConfig.shulkerBoxAllowOversizedStacks || (!world.isClientSide && player.isCreative() && !shulkerBoxBlockEntity.isEmpty())) { // Purpur
ItemStack itemStack = getColoredItemStack(this.getColor());
blockEntity.saveToItem(itemStack);
if (shulkerBoxBlockEntity.hasCustomName()) {
diff --git a/src/main/java/net/minecraft/world/level/block/SlabBlock.java b/src/main/java/net/minecraft/world/level/block/SlabBlock.java
index 18b603d646081926343dea108b55d641df1c2c34..370772b1297b78bcc7419684015830a87c4d9a17 100644
--- a/src/main/java/net/minecraft/world/level/block/SlabBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/SlabBlock.java
@@ -130,4 +130,25 @@ public class SlabBlock extends Block implements SimpleWaterloggedBlock {
return false;
}
}
+
+ // Purpur start
+ public boolean halfBreak(BlockState state, BlockPos pos, net.minecraft.server.level.ServerPlayer player) {
+ if (state.getValue(SlabBlock.TYPE) != SlabType.DOUBLE) {
+ return false;
+ }
+ net.minecraft.world.phys.HitResult result = player.getRayTrace(16, net.minecraft.world.level.ClipContext.Fluid.NONE);
+ if (result.getType() != net.minecraft.world.phys.HitResult.Type.BLOCK) {
+ return false;
+ }
+ double hitY = result.getLocation().y();
+ int blockY = org.bukkit.util.NumberConversions.floor(hitY);
+ player.level().setBlock(pos, state.setValue(SlabBlock.TYPE, (hitY - blockY > 0.5 || blockY - pos.getY() == 1) ? SlabType.BOTTOM : SlabType.TOP), 3);
+ if (!player.getAbilities().instabuild) {
+ net.minecraft.world.entity.item.ItemEntity item = new net.minecraft.world.entity.item.ItemEntity(player.level(), pos.getX(), pos.getY(), pos.getZ(), new ItemStack(asItem()));
+ item.setDefaultPickUpDelay();
+ player.level().addFreshEntity(item);
+ }
+ return true;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java
index 14e00c7feb1c051d56a3d27cd00dcef072dd771a..4952fb1aaaafb55baa0fddb389f966a120a4786c 100644
--- a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java
@@ -81,6 +81,12 @@ public class SnowLayerBlock extends Block {
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
BlockState iblockdata1 = world.getBlockState(pos.below());
+ // Purpur start
+ if (iblockdata1.is(Blocks.BLUE_ICE) && !world.getWorldBorder().world.purpurConfig.snowOnBlueIce) {
+ return false;
+ }
+ // Purpur end
+
return iblockdata1.is(BlockTags.SNOW_LAYER_CANNOT_SURVIVE_ON) ? false : (iblockdata1.is(BlockTags.SNOW_LAYER_CAN_SURVIVE_ON) ? true : Block.isFaceFull(iblockdata1.getCollisionShape(world, pos.below()), Direction.UP) || iblockdata1.is((Block) this) && (Integer) iblockdata1.getValue(SnowLayerBlock.LAYERS) == 8);
}
diff --git a/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java b/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java
index 936d844a5a246138c9f9ae4ae6e318242b8f1420..93f5f226cf6fd6110e4daa02b3f5d9ad253814a0 100644
--- a/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java
@@ -40,6 +40,58 @@ public class SpawnerBlock extends BaseEntityBlock {
return createTickerHelper(type, BlockEntityType.MOB_SPAWNER, world.isClientSide ? SpawnerBlockEntity::clientTick : SpawnerBlockEntity::serverTick);
}
+ // Purpur start
+ @Override
+ public void playerDestroy(Level level, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, BlockEntity blockEntity, ItemStack stack, boolean includeDrops) {
+ if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.drop.spawners") && isSilkTouch(level, stack)) {
+ Optional<net.minecraft.world.entity.EntityType<?>> type = net.minecraft.world.entity.EntityType.by(((SpawnerBlockEntity) blockEntity).getSpawner().nextSpawnData.getEntityToSpawn());
+
+ net.minecraft.world.entity.EntityType<?> entityType = type.orElse(null);
+ final net.kyori.adventure.text.Component mobName = io.papermc.paper.adventure.PaperAdventure.asAdventure(entityType == null ? Component.empty() : entityType.getDescription());
+ CompoundTag display = new CompoundTag();
+ CompoundTag tag = new CompoundTag();
+
+ String name = level.purpurConfig.silkTouchSpawnerName;
+ if (name != null && !name.isEmpty() && !name.equals("Monster Spawner")) {
+ net.kyori.adventure.text.Component displayName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName));
+ if (name.startsWith("<reset>")) {
+ displayName = displayName.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false);
+ }
+ display.put("Name", net.minecraft.nbt.StringTag.valueOf(io.papermc.paper.adventure.PaperAdventure.asJsonString(displayName, java.util.Locale.ROOT)));
+ tag.put("display", display);
+ }
+
+ List<String> lore = level.purpurConfig.silkTouchSpawnerLore;
+ if (lore != null && !lore.isEmpty()) {
+ net.minecraft.nbt.ListTag list = new net.minecraft.nbt.ListTag();
+ for (String line : lore) {
+ net.kyori.adventure.text.Component lineComponent = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(line, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName));
+ if (line.startsWith("<reset>")) {
+ lineComponent = lineComponent.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false);
+ }
+ list.add(net.minecraft.nbt.StringTag.valueOf(io.papermc.paper.adventure.PaperAdventure.asJsonString(lineComponent, java.util.Locale.ROOT)));
+ }
+ display.put("Lore", list);
+ tag.put("display", display);
+ }
+
+ ItemStack item = new ItemStack(Blocks.SPAWNER.asItem());
+ if (entityType != null) {
+ tag.putString("Purpur.mob_type", entityType.getName());
+ tag.putDouble("HideFlags", 32); // hides the "Interact with Spawn Egg" tooltip
+ item.setTag(tag);
+ }
+
+ popResource(level, pos, item);
+ }
+ super.playerDestroy(level, player, pos, state, blockEntity, stack, includeDrops);
+ }
+
+ private boolean isSilkTouch(Level level, ItemStack stack) {
+ return stack != null && level.purpurConfig.silkTouchTools.contains(stack.getItem()) && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.SILK_TOUCH, stack) >= level.purpurConfig.minimumSilkTouchSpawnerRequire;
+ }
+ // Purpur end
+
@Override
public void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) {
super.spawnAfterBreak(state, world, pos, tool, dropExperience);
@@ -48,6 +100,7 @@ public class SpawnerBlock extends BaseEntityBlock {
@Override
public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
+ if (isSilkTouch(worldserver, itemstack)) return 0; // Purpur
if (flag) {
int i = 15 + worldserver.random.nextInt(15) + worldserver.random.nextInt(15);
diff --git a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java
index 4bce895268542531598a01a1bccd8ac1ed703b7d..2276fed1feb4fea59b5bd49b5e4586d49478b3cc 100644
--- a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java
@@ -48,7 +48,7 @@ public class SpongeBlock extends Block {
private boolean removeWaterBreadthFirstSearch(Level world, BlockPos pos) {
BlockStateListPopulator blockList = new BlockStateListPopulator(world); // CraftBukkit - Use BlockStateListPopulator
- BlockPos.breadthFirstTraversal(pos, 6, 65, (blockposition1, consumer) -> {
+ BlockPos.breadthFirstTraversal(pos, world.purpurConfig.spongeAbsorptionRadius, world.purpurConfig.spongeAbsorptionArea, (blockposition1, consumer) -> { // Purpur
Direction[] aenumdirection = SpongeBlock.ALL_DIRECTIONS;
int i = aenumdirection.length;
@@ -67,7 +67,7 @@ public class SpongeBlock extends Block {
FluidState fluid = blockList.getFluidState(blockposition1);
// CraftBukkit end
- if (!fluid.is(FluidTags.WATER)) {
+ if (!fluid.is(FluidTags.WATER) && (!world.purpurConfig.spongeAbsorbsLava || !fluid.is(FluidTags.LAVA)) && (!world.purpurConfig.spongeAbsorbsWaterFromMud || !iblockdata.is(Blocks.MUD))) { // Purpur
return false;
} else {
Block block = iblockdata.getBlock();
@@ -82,6 +82,10 @@ public class SpongeBlock extends Block {
if (iblockdata.getBlock() instanceof LiquidBlock) {
blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit
+ // Purpur start
+ } else if (iblockdata.is(Blocks.MUD)) {
+ blockList.setBlock(blockposition1, Blocks.CLAY.defaultBlockState(), 3);
+ // Purpur end
} else {
if (!iblockdata.is(Blocks.KELP) && !iblockdata.is(Blocks.KELP_PLANT) && !iblockdata.is(Blocks.SEAGRASS) && !iblockdata.is(Blocks.TALL_SEAGRASS)) {
return false;
diff --git a/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java b/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java
index 0a95842c53a9d0286c57bcb42db97e468e30fb7d..0882e67c5cf876e0fc58a4ca4accb4be40418983 100644
--- a/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java
@@ -92,4 +92,16 @@ public class StonecutterBlock extends Block {
public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) {
return false;
}
+
+ // Purpur start
+ @Override
+ public void stepOn(Level level, BlockPos pos, BlockState state, net.minecraft.world.entity.Entity entity) {
+ if (level.purpurConfig.stonecutterDamage > 0.0F && entity instanceof net.minecraft.world.entity.LivingEntity) {
+ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
+ entity.hurt(entity.damageSources().stonecutter(), level.purpurConfig.stonecutterDamage);
+ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null;
+ }
+ super.stepOn(level, pos, state, entity);
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java
index c3f500580d257e1397f2eb7c47b063a6fe6bb405..0d5c6bdfd4aeda472804b493315bf21ac3067e9d 100644
--- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java
@@ -19,7 +19,7 @@ import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
-public class SugarCaneBlock extends Block {
+public class SugarCaneBlock extends Block implements BonemealableBlock { // Purpur
public static final IntegerProperty AGE = BlockStateProperties.AGE_15;
protected static final float AABB_OFFSET = 6.0F;
@@ -106,4 +106,34 @@ public class SugarCaneBlock extends Block {
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(SugarCaneBlock.AGE);
}
+
+ // Purpur start
+ @Override
+ public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, BlockState state, boolean isClient) {
+ if (!((net.minecraft.world.level.Level) world).purpurConfig.sugarCanAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false;
+
+ int reedHeight = 0;
+ while (world.getBlockState(pos.below(reedHeight)).is(this)) {
+ reedHeight++;
+ }
+
+ return reedHeight < ((net.minecraft.world.level.Level) world).paperConfig().maxGrowthHeight.reeds;
+ }
+
+ @Override
+ public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) {
+ return true;
+ }
+
+ @Override
+ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) {
+ int reedHeight = 0;
+ while (world.getBlockState(pos.below(reedHeight)).is(this)) {
+ reedHeight++;
+ }
+ for (int i = 0; i <= world.paperConfig().maxGrowthHeight.reeds - reedHeight; i++) {
+ world.setBlockAndUpdate(pos.above(i), state.setValue(SugarCaneBlock.AGE, 0));
+ }
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java
index 181e99e3f29c70568b6f28f8a5d3bb692f7abad8..58facc048840d3ba0a58c92673340e6e0aa4e305 100644
--- a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java
@@ -160,7 +160,7 @@ public class TurtleEggBlock extends Block {
private boolean shouldUpdateHatchLevel(Level world) {
float f = world.getTimeOfDay(1.0F);
- return (double) f < 0.69D && (double) f > 0.65D ? true : world.random.nextInt(500) == 0;
+ return (double) f < 0.69D && (double) f > 0.65D ? true : world.random.nextInt(world.purpurConfig.turtleEggsRandomTickCrackChance) == 0;
}
@Override
@@ -193,6 +193,31 @@ public class TurtleEggBlock extends Block {
}
private boolean canDestroyEgg(Level world, Entity entity) {
- return !(entity instanceof Turtle) && !(entity instanceof Bat) ? (!(entity instanceof LivingEntity) ? false : entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) : false;
+ // Purpur start
+ if (entity instanceof Turtle || entity instanceof Bat) {
+ return false;
+ }
+ if (world.purpurConfig.turtleEggsBreakFromExpOrbs && entity instanceof net.minecraft.world.entity.ExperienceOrb) {
+ return true;
+ }
+ if (world.purpurConfig.turtleEggsBreakFromItems && entity instanceof net.minecraft.world.entity.item.ItemEntity) {
+ return true;
+ }
+ if (world.purpurConfig.turtleEggsBreakFromMinecarts && entity instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) {
+ return true;
+ }
+ if (!(entity instanceof LivingEntity)) {
+ return false;
+ }
+ if (world.purpurConfig.turtleEggsTramplingFeatherFalling) {
+ java.util.Iterator<ItemStack> armor = entity.getArmorSlots().iterator();
+ return !armor.hasNext() || net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FALL_PROTECTION, armor.next()) < (int) entity.fallDistance;
+ }
+ if (entity instanceof Player) {
+ return true;
+ }
+
+ return world.purpurConfig.turtleEggsBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
+ // Purpur end
}
}
diff --git a/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java b/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java
index 6866605c7ef5361b21130a19a59c3fa3660dfb19..dee5d76d29da13f8639ab5d392cd0143201e71ba 100644
--- a/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java
@@ -27,4 +27,11 @@ public class TwistingVinesBlock extends GrowingPlantHeadBlock {
protected boolean canGrowInto(BlockState state) {
return NetherVines.isValidGrowthState(state);
}
+
+ // Purpur start
+ @Override
+ public int getMaxGrowthAge() {
+ return org.purpurmc.purpur.PurpurConfig.twistingVinesMaxGrowthAge;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java
index e5c135ec059746b75fe58516809584221285cdbe..713c7e6e31a3e1097b612c77a4fce147c9252e0b 100644
--- a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java
@@ -27,4 +27,11 @@ public class WeepingVinesBlock extends GrowingPlantHeadBlock {
protected boolean canGrowInto(BlockState state) {
return NetherVines.isValidGrowthState(state);
}
+
+ // Purpur start
+ @Override
+ public int getMaxGrowthAge() {
+ return org.purpurmc.purpur.PurpurConfig.weepingVinesMaxGrowthAge;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java
index 1aa0e921890d600c9274deb923da04e72b12bcc6..44bd7bee2665a05878fd2df935a700f02cd13a75 100644
--- a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java
@@ -69,6 +69,7 @@ public class WitherSkullBlock extends SkullBlock {
entitywither.moveTo((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.55D, (double) blockposition1.getZ() + 0.5D, shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F, 0.0F);
entitywither.yBodyRot = shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F;
entitywither.makeInvulnerable();
+ entitywither.setSummoner(iblockdata.getBlock().placer == null ? null : iblockdata.getBlock().placer.getUUID()); // Purpur
// CraftBukkit start
if (!world.addFreshEntity(entitywither, SpawnReason.BUILD_WITHER)) {
return;
diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
index 448fa4f4f200430d6ce3051763c7ceb697696146..997d0fab71eacc6466ffe3bc8f6349e5813d6d49 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
@@ -44,6 +44,7 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.AbstractFurnaceBlock;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.phys.Vec3;
// CraftBukkit start
import org.bukkit.craftbukkit.block.CraftBlock;
@@ -207,6 +208,22 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit
// Paper end
}
+ // Purpur start
+ public static void addFuel(ItemStack itemStack, Integer burnTime) {
+ Map<Item, Integer> map = Maps.newLinkedHashMap();
+ map.putAll(getFuel());
+ map.put(itemStack.getItem(), burnTime);
+ cachedBurnDurations = com.google.common.collect.ImmutableMap.copyOf(map);
+ }
+
+ public static void removeFuel(ItemStack itemStack) {
+ Map<Item, Integer> map = Maps.newLinkedHashMap();
+ map.putAll(getFuel());
+ map.remove(itemStack.getItem());
+ cachedBurnDurations = com.google.common.collect.ImmutableMap.copyOf(map);
+ }
+ // Purpur End
+
// CraftBukkit start - add fields and methods
private int maxStack = MAX_STACK;
public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
@@ -324,6 +341,21 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit
}
ItemStack itemstack = (ItemStack) blockEntity.items.get(1);
+ // Purpur start
+ boolean usedLavaFromUnderneath = false;
+ if (world.purpurConfig.furnaceUseLavaFromUnderneath && !blockEntity.isLit() && itemstack.isEmpty() && !blockEntity.items.get(0).isEmpty() && world.getGameTime() % 20 == 0) {
+ BlockPos below = blockEntity.getBlockPos().below();
+ BlockState belowState = world.getBlockStateIfLoaded(below);
+ if (belowState != null && belowState.is(Blocks.LAVA)) {
+ FluidState fluidState = belowState.getFluidState();
+ if (fluidState != null && fluidState.isSource()) {
+ world.setBlock(below, Blocks.AIR.defaultBlockState(), 3);
+ itemstack = Items.LAVA_BUCKET.getDefaultInstance();
+ usedLavaFromUnderneath = true;
+ }
+ }
+ }
+ // Purpur end
boolean flag2 = !((ItemStack) blockEntity.items.get(0)).isEmpty();
boolean flag3 = !itemstack.isEmpty();
@@ -409,6 +441,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit
setChanged(world, pos, state);
}
+ if (usedLavaFromUnderneath) blockEntity.items.set(1, ItemStack.EMPTY); // Purpur
}
private static boolean canBurn(RegistryAccess registryManager, @Nullable Recipe<?> recipe, NonNullList<ItemStack> slots, int count) {
diff --git a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
index 416aa989ebb18a8741cc9d605a1180ab830f6643..e38a0adf5463c48311ad08b8d2e5b5c2d989a3b5 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
@@ -67,7 +67,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
public BarrelBlockEntity(BlockPos pos, BlockState state) {
super(BlockEntityType.BARREL, pos, state);
- this.items = NonNullList.withSize(27, ItemStack.EMPTY);
+ // Purpur start
+ this.items = NonNullList.withSize(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) {
+ case 6 -> 54;
+ case 5 -> 45;
+ case 4 -> 36;
+ case 2 -> 18;
+ case 1 -> 9;
+ default -> 27;
+ }, ItemStack.EMPTY);
+ // Purpur end
this.openersCounter = new ContainerOpenersCounter() {
@Override
protected void onOpen(Level world, BlockPos pos, BlockState state) {
@@ -118,7 +127,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
@Override
public int getContainerSize() {
- return 27;
+ // Purpur start
+ return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) {
+ case 6 -> 54;
+ case 5 -> 45;
+ case 4 -> 36;
+ case 2 -> 18;
+ case 1 -> 9;
+ default -> 27;
+ };
+ // Purpur end
}
@Override
@@ -138,7 +156,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
@Override
protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) {
- return ChestMenu.threeRows(syncId, playerInventory, this);
+ // Purpur start
+ return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) {
+ case 6 -> ChestMenu.sixRows(syncId, playerInventory, this);
+ case 5 -> ChestMenu.fiveRows(syncId, playerInventory, this);
+ case 4 -> ChestMenu.fourRows(syncId, playerInventory, this);
+ case 2 -> ChestMenu.twoRows(syncId, playerInventory, this);
+ case 1 -> ChestMenu.oneRow(syncId, playerInventory, this);
+ default -> ChestMenu.threeRows(syncId, playerInventory, this);
+ };
+ // Purpur end
}
@Override
diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
index 3b866e2c20ee7bfc981ff09b29065530de993778..2e6220c5a6c871401ce9734adfab710dcf86ad44 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
@@ -84,6 +84,16 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
public double getEffectRange() {
if (this.effectRange < 0) {
+ // Purpur Start
+ if (this.level != null) {
+ switch (this.levels) {
+ case 1: return this.level.purpurConfig.beaconLevelOne;
+ case 2: return this.level.purpurConfig.beaconLevelTwo;
+ case 3: return this.level.purpurConfig.beaconLevelThree;
+ case 4: return this.level.purpurConfig.beaconLevelFour;
+ }
+ }
+ // Purpur End
return this.levels * 10 + 10;
} else {
return effectRange;
@@ -155,6 +165,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
int j = pos.getY();
int k = pos.getZ();
BlockPos blockposition1;
+ boolean isTintedGlass = false;
if (blockEntity.lastCheckY < j) {
blockposition1 = pos;
@@ -188,6 +199,9 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
}
}
} else {
+ if (world.purpurConfig.beaconAllowEffectsWithTintedGlass && block.equals(Blocks.TINTED_GLASS)) {
+ isTintedGlass = true;
+ }
if (tileentitybeacon_beaconcolortracker == null || iblockdata1.getLightBlock(world, blockposition1) >= 15 && !iblockdata1.is(Blocks.BEDROCK)) {
blockEntity.checkingBeamSections.clear();
blockEntity.lastCheckY = l;
@@ -207,7 +221,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
blockEntity.levels = BeaconBlockEntity.updateBase(world, i, j, k);
}
- if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) {
+ if (blockEntity.levels > 0 && (!blockEntity.beamSections.isEmpty() || (world.purpurConfig.beaconAllowEffectsWithTintedGlass && isTintedGlass))) {
BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper
BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT);
}
diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
index 41c9f074203915c31c1ae7a160ce509c13383f84..5344393f62af19c3591f54a6ebc40b2e4c3b6226 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
@@ -43,7 +43,7 @@ public class BeehiveBlockEntity extends BlockEntity {
private final List<BeehiveBlockEntity.BeeData> stored = Lists.newArrayList();
@Nullable
public BlockPos savedFlowerPos;
- public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold
+ public int maxBees = org.purpurmc.purpur.PurpurConfig.beeInsideBeeHive; // CraftBukkit - allow setting max amount of bees a hive can hold // Purpur
public BeehiveBlockEntity(BlockPos pos, BlockState state) {
super(BlockEntityType.BEEHIVE, pos, state);
@@ -130,6 +130,36 @@ public class BeehiveBlockEntity extends BlockEntity {
return list;
}
+ // Purpur start
+ public List<Entity> releaseBees(BlockState iblockdata, Level level, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, boolean force) {
+ List<Entity> list = Lists.newArrayList();
+
+ this.stored.removeIf((tileentitybeehive_hivebee) -> {
+ return BeehiveBlockEntity.releaseBee(level, this.worldPosition, iblockdata, tileentitybeehive_hivebee, list, tileentitybeehive_releasestatus, this.savedFlowerPos, force);
+ });
+
+ if (!list.isEmpty()) {
+ super.setChanged();
+ }
+
+ return list;
+ }
+
+ public List<Entity> releaseBee(BlockState iblockdata, Level level, BeeData data, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, boolean force) {
+ List<Entity> list = Lists.newArrayList();
+
+ BeehiveBlockEntity.releaseBee(level, this.worldPosition, iblockdata, data, list, tileentitybeehive_releasestatus, this.savedFlowerPos, force);
+
+ if (!list.isEmpty()) {
+ stored.remove(data);
+
+ super.setChanged();
+ }
+
+ return list;
+ }
+ // Purpur end
+
public void addOccupant(Entity entity, boolean hasNectar) {
this.addOccupantWithPresetTicks(entity, hasNectar, 0);
}
@@ -139,6 +169,12 @@ public class BeehiveBlockEntity extends BlockEntity {
return this.stored.size();
}
+ // Purpur start
+ public List<BeeData> getStored() {
+ return stored;
+ }
+ // Purpur end
+
// Paper start - Add EntityBlockStorage clearEntities
public void clearBees() {
this.stored.clear();
@@ -203,7 +239,7 @@ public class BeehiveBlockEntity extends BlockEntity {
}
private static boolean releaseBee(Level world, BlockPos blockposition, BlockState iblockdata, BeehiveBlockEntity.BeeData tileentitybeehive_hivebee, @Nullable List<Entity> list, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, @Nullable BlockPos blockposition1, boolean force) {
- if (!force && (world.isNight() || world.isRaining()) && tileentitybeehive_releasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) {
+ if (!force && ((world.isNight() && !world.purpurConfig.beeCanWorkAtNight) || (world.isRaining() && !world.purpurConfig.beeCanWorkInRain)) && tileentitybeehive_releasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) { // Purpur
// CraftBukkit end
return false;
} else {
@@ -425,9 +461,9 @@ public class BeehiveBlockEntity extends BlockEntity {
private BeeReleaseStatus() {}
}
- private static class BeeData {
+ public static class BeeData { // Purpur - change from private to public
- final CompoundTag entityData;
+ public final CompoundTag entityData; // Purpur - make public
int ticksInHive;
int exitTickCounter; // Paper - separate counter for checking if bee should exit to reduce exit attempts
final int minOccupationTicks;
diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
index 370a25d2deb54f10a35ee24d9e7e92fbfde60edf..3431f1a00ae2918b91a6b7a449e613e6e12ff6d4 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
@@ -6,6 +6,8 @@ import net.minecraft.CrashReportCategory;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.nbt.StringTag;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.resources.ResourceLocation;
@@ -73,10 +75,27 @@ public abstract class BlockEntity {
if (persistentDataTag instanceof CompoundTag) {
this.persistentDataContainer.putAll((CompoundTag) persistentDataTag);
}
+ // Purpur start
+ if (nbt.contains("Purpur.persistentDisplayName")) {
+ this.persistentDisplayName = nbt.getString("Purpur.persistentDisplayName");
+ }
+ if (nbt.contains("Purpur.persistentLore")) {
+ this.persistentLore = nbt.getList("Purpur.persistentLore", 8);
+ }
+ // Purpur end
}
// CraftBukkit end
- protected void saveAdditional(CompoundTag nbt) {}
+ protected void saveAdditional(CompoundTag nbt) {
+ // Purpur start
+ if (this.persistentDisplayName != null) {
+ nbt.put("Purpur.persistentDisplayName", StringTag.valueOf(this.persistentDisplayName));
+ }
+ if (this.persistentLore != null) {
+ nbt.put("Purpur.persistentLore", this.persistentLore);
+ }
+ // Purpur end
+ }
public final CompoundTag saveWithFullMetadata() {
CompoundTag nbttagcompound = this.saveWithoutMetadata();
@@ -186,10 +205,24 @@ public abstract class BlockEntity {
@Nullable
public Packet<ClientGamePacketListener> getUpdatePacket() {
+ // Purpur start
+ if (this instanceof net.minecraft.world.Nameable nameable && nameable.hasCustomName()) {
+ CompoundTag nbt = this.saveWithoutMetadata();
+ nbt.remove("Items");
+ return net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket.create(this, $ -> nbt);
+ }
+ // Purpur end
return null;
}
public CompoundTag getUpdateTag() {
+ // Purpur start
+ if (this instanceof net.minecraft.world.Nameable nameable && nameable.hasCustomName()) {
+ CompoundTag nbt = this.saveWithoutMetadata();
+ nbt.remove("Items");
+ return nbt;
+ }
+ // Purpur end
return new CompoundTag();
}
@@ -263,4 +296,24 @@ public abstract class BlockEntity {
}
// Paper end
+ // Purpur start
+ private String persistentDisplayName = null;
+ private ListTag persistentLore = null;
+
+ public void setPersistentDisplayName(String json) {
+ this.persistentDisplayName = json;
+ }
+
+ public void setPersistentLore(ListTag lore) {
+ this.persistentLore = lore;
+ }
+
+ public String getPersistentDisplayName() {
+ return this.persistentDisplayName;
+ }
+
+ public ListTag getPersistentLore() {
+ return this.persistentLore;
+ }
+ // Purpur end
}
diff --git a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
index 963a596154091b79ca139af6274aa323518ad1ad..4dcac3899a500d8586580bcfd5b4516e1dcdcd4a 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
@@ -171,7 +171,7 @@ public class ConduitBlockEntity extends BlockEntity {
if ((l > 1 || i1 > 1 || j1 > 1) && (i == 0 && (i1 == 2 || j1 == 2) || j == 0 && (l == 2 || j1 == 2) || k == 0 && (l == 2 || i1 == 2))) {
BlockPos blockposition2 = pos.offset(i, j, k);
BlockState iblockdata = world.getBlockState(blockposition2);
- Block[] ablock = ConduitBlockEntity.VALID_BLOCKS;
+ Block[] ablock = world.purpurConfig.conduitBlocks; // Purpur
int k1 = ablock.length;
for (int l1 = 0; l1 < k1; ++l1) {
@@ -191,7 +191,7 @@ public class ConduitBlockEntity extends BlockEntity {
private static void applyEffects(Level world, BlockPos pos, List<BlockPos> activatingBlocks) {
int i = activatingBlocks.size();
- int j = i / 7 * 16;
+ int j = i / 7 * world.purpurConfig.conduitDistance; // Purpur
int k = pos.getX();
int l = pos.getY();
int i1 = pos.getZ();
@@ -222,21 +222,21 @@ public class ConduitBlockEntity extends BlockEntity {
blockEntity.destroyTarget = ConduitBlockEntity.findDestroyTarget(world, pos, blockEntity.destroyTargetUUID);
blockEntity.destroyTargetUUID = null;
} else if (blockEntity.destroyTarget == null) {
- List<LivingEntity> list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos), (entityliving1) -> {
+ List<LivingEntity> list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos, world), (entityliving1) -> { // Purpur
return entityliving1 instanceof Enemy && entityliving1.isInWaterOrRain();
});
if (!list1.isEmpty()) {
blockEntity.destroyTarget = (LivingEntity) list1.get(world.random.nextInt(list1.size()));
}
- } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), 8.0D)) {
+ } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), world.purpurConfig.conduitDamageDistance)) { // Purpur
blockEntity.destroyTarget = null;
}
if (blockEntity.destroyTarget != null) {
// CraftBukkit start
CraftEventFactory.blockDamage = CraftBlock.at(world, pos);
- if (blockEntity.destroyTarget.hurt(world.damageSources().magic(), 4.0F)) {
+ if (blockEntity.destroyTarget.hurt(world.damageSources().magic(), world.purpurConfig.conduitDamageAmount)) { // Purpur
world.playSound((Player) null, blockEntity.destroyTarget.getX(), blockEntity.destroyTarget.getY(), blockEntity.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F);
}
CraftEventFactory.blockDamage = null;
@@ -262,16 +262,22 @@ public class ConduitBlockEntity extends BlockEntity {
}
private static AABB getDestroyRangeAABB(BlockPos pos) {
+ // Purpur start
+ return getDestroyRangeAABB(pos, null);
+ }
+
+ private static AABB getDestroyRangeAABB(BlockPos pos, Level level) {
+ // Purpur end
int i = pos.getX();
int j = pos.getY();
int k = pos.getZ();
- return (new AABB((double) i, (double) j, (double) k, (double) (i + 1), (double) (j + 1), (double) (k + 1))).inflate(8.0D);
+ return (new AABB((double) i, (double) j, (double) k, (double) (i + 1), (double) (j + 1), (double) (k + 1))).inflate(level == null ? 8.0D : level.purpurConfig.conduitDamageDistance); // Purpur
}
@Nullable
private static LivingEntity findDestroyTarget(Level world, BlockPos pos, UUID uuid) {
- List<LivingEntity> list = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos), (entityliving) -> {
+ List<LivingEntity> list = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos, world), (entityliving) -> { // Purpur
return entityliving.getUUID().equals(uuid);
});
diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java
index 65e1381bb2d10bd212463feb602c60f8fdb9ade1..b7370e64fd0d50e8725d7d5afc30af2e8bc8455d 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java
@@ -24,6 +24,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable
public float tRot;
private static final RandomSource RANDOM = RandomSource.create();
private Component name;
+ private int lapis = 0; // Purpur
public EnchantmentTableBlockEntity(BlockPos pos, BlockState state) {
super(BlockEntityType.ENCHANTING_TABLE, pos, state);
@@ -35,6 +36,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable
if (this.hasCustomName()) {
nbt.putString("CustomName", Component.Serializer.toJson(this.name));
}
+ nbt.putInt("Purpur.Lapis", this.lapis); // Purpur
}
@@ -44,6 +46,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable
if (nbt.contains("CustomName", 8)) {
this.name = io.papermc.paper.util.MCUtil.getBaseComponentFromNbt("CustomName", nbt); // Paper - Catch ParseException
}
+ this.lapis = nbt.getInt("Purpur.Lapis"); // Purpur
}
@@ -117,4 +120,14 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable
public Component getCustomName() {
return this.name;
}
+
+ // Purpur start
+ public int getLapis() {
+ return this.lapis;
+ }
+
+ public void setLapis(int lapis) {
+ this.lapis = lapis;
+ }
+ // Purpur
}
diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java
index 38cde466714e5663cd416b6afd5d2558e139ec09..2d625f18f2ba42ee5a1ebeea78ca395ad6f88b37 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java
@@ -202,16 +202,31 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C
return this.setText((SignText) textChanger.apply(signtext), front);
}
+ // Purpur start
+ private Component translateColors(org.bukkit.entity.Player player, String line, Style style) {
+ if (level.purpurConfig.signAllowColors) {
+ if (player.hasPermission("purpur.sign.color")) line = line.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1");
+ if (player.hasPermission("purpur.sign.style")) line = line.replaceAll("(?i)&([l-or])", "\u00a7$1");
+ if (player.hasPermission("purpur.sign.magic")) line = line.replaceAll("(?i)&([kr])", "\u00a7$1");
+
+ return io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(line));
+ } else {
+ return Component.literal(line).setStyle(style);
+ }
+ }
+ // Purpur end
+
private SignText setMessages(net.minecraft.world.entity.player.Player entityhuman, List<FilteredText> list, SignText signtext, boolean front) { // CraftBukkit
SignText originalText = signtext; // CraftBukkit
for (int i = 0; i < list.size(); ++i) {
FilteredText filteredtext = (FilteredText) list.get(i);
Style chatmodifier = signtext.getMessage(i, entityhuman.isTextFilteringEnabled()).getStyle();
+ org.bukkit.entity.Player player = (org.bukkit.craftbukkit.entity.CraftPlayer) entityhuman.getBukkitEntity(); // Purpur
if (entityhuman.isTextFilteringEnabled()) {
- signtext = signtext.setMessage(i, Component.literal(net.minecraft.SharedConstants.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only
+ signtext = signtext.setMessage(i, translateColors(player, net.minecraft.SharedConstants.filterText(filteredtext.filteredOrEmpty()), chatmodifier)); // Paper - filter sign text to chat only // Purpur
} else {
- signtext = signtext.setMessage(i, Component.literal(net.minecraft.SharedConstants.filterText(filteredtext.raw())).setStyle(chatmodifier), Component.literal(net.minecraft.SharedConstants.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only
+ signtext = signtext.setMessage(i, translateColors(player, net.minecraft.SharedConstants.filterText(filteredtext.raw()), chatmodifier), translateColors(player, net.minecraft.SharedConstants.filterText(filteredtext.filteredOrEmpty()), chatmodifier)); // Paper - filter sign text to chat only // Purpur
}
}
@@ -351,6 +366,28 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C
return ClientboundBlockEntityDataPacket.create(this);
}
+ // Purpur start
+ public ClientboundBlockEntityDataPacket getTranslatedUpdatePacket(boolean filtered, boolean front) {
+ final CompoundTag nbt = new CompoundTag();
+ this.saveAdditional(nbt);
+ final Component[] lines = front ? frontText.getMessages(filtered) : backText.getMessages(filtered);
+ final String side = front ? "front_text" : "back_text";
+ for (int i = 0; i < 4; i++) {
+ final var component = io.papermc.paper.adventure.PaperAdventure.asAdventure(lines[i]);
+ final String line = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().serialize(component);
+ final var text = net.kyori.adventure.text.Component.text(line);
+ final String json = net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(text);
+ if (!nbt.contains(side)) nbt.put(side, new CompoundTag());
+ final CompoundTag sideNbt = nbt.getCompound(side);
+ if (!sideNbt.contains("messages")) sideNbt.put("messages", new net.minecraft.nbt.ListTag());
+ final net.minecraft.nbt.ListTag messagesNbt = sideNbt.getList("messages", Tag.TAG_STRING);
+ messagesNbt.set(i, net.minecraft.nbt.StringTag.valueOf(json));
+ }
+ nbt.putString("PurpurEditor", "true");
+ return ClientboundBlockEntityDataPacket.create(this, entity -> nbt);
+ }
+ // Purpur end
+
@Override
public CompoundTag getUpdateTag() {
return this.saveWithoutMetadata();
diff --git a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java
index 1ec80f9c901dff1c9f29befa5a8e3c3f6f37aaf7..7291e4056b8e46ab59b71818388ac55fbb12993f 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java
@@ -177,6 +177,15 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity {
public static void teleportEntity(Level world, BlockPos pos, BlockState state, Entity entity, TheEndGatewayBlockEntity blockEntity) {
if (world instanceof ServerLevel && !blockEntity.isCoolingDown()) {
+ if (!entity.canChangeDimensions()) return; // Purpur
+ // Purpur start
+ if (world.purpurConfig.imposeTeleportRestrictionsOnGateways && (entity.isVehicle() || entity.isPassenger())) {
+ if (new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, PlayerTeleportEvent.TeleportCause.END_GATEWAY).callEvent()) {
+ teleportEntity(world, pos, state, entity, blockEntity);
+ }
+ return;
+ }
+ // Purpur end
ServerLevel worldserver = (ServerLevel) world;
blockEntity.teleportCooldown = 100;
diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java b/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java
index 744d91546d1a810f60a43c15ed74b4158f341a4a..354538daefa603f6df5a139b6bff87dbb4cef178 100644
--- a/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java
+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java
@@ -86,7 +86,7 @@ public class PistonStructureResolver {
return true;
} else {
int i = 1;
- if (i + this.toPush.size() > 12) {
+ if (i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur
return false;
} else {
while(isSticky(blockState)) {
@@ -98,7 +98,7 @@ public class PistonStructureResolver {
}
++i;
- if (i + this.toPush.size() > 12) {
+ if (i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur
return false;
}
}
@@ -142,7 +142,7 @@ public class PistonStructureResolver {
return true;
}
- if (this.toPush.size() >= 12) {
+ if (this.toPush.size() >= this.level.purpurConfig.pistonBlockPushLimit) { // Purpur
return false;
}
diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
index de4c1e4701236e7d5ec77339c51ad6a9d8288bb6..942ce713afe27ec75d849877a88721ef6334fafa 100644
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -81,7 +81,7 @@ public abstract class BlockBehaviour implements FeatureElement {
protected static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP};
public final boolean hasCollision;
- protected final float explosionResistance;
+ public float explosionResistance; // Purpur - protected final -> public
protected final boolean isRandomlyTicking;
protected final SoundType soundType;
protected final float friction;
@@ -89,7 +89,7 @@ public abstract class BlockBehaviour implements FeatureElement {
protected final float jumpFactor;
protected final boolean dynamicShape;
protected final FeatureFlagSet requiredFeatures;
- protected final BlockBehaviour.Properties properties;
+ public final BlockBehaviour.Properties properties; // Purpur - protected -> public
@Nullable
protected ResourceLocation drops;
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
index 3eeb1f0eac76efe9b7c24f6d5787018c7842d07a..c17b04880b4032984b43dd4fbe1c08f96112abac 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -125,7 +125,7 @@ public class LevelChunk extends ChunkAccess {
this.blockTicks = blockTickScheduler;
this.fluidTicks = fluidTickScheduler;
- this.lightningTick = this.level.getThreadUnsafeRandom().nextInt(100000) << 1; // Pufferfish - initialize lightning tick
+ this.lightningTick = java.util.concurrent.ThreadLocalRandom.current().nextInt(100000) << 1; // Pufferfish - initialize lightning tick // Purpur - any random will do
}
// CraftBukkit start
@@ -552,11 +552,11 @@ public class LevelChunk extends ChunkAccess {
if (LightEngine.hasDifferentLightProperties(this, blockposition, iblockdata1, iblockdata)) {
ProfilerFiller gameprofilerfiller = this.level.getProfiler();
- gameprofilerfiller.push("updateSkyLightSources");
+ //gameprofilerfiller.push("updateSkyLightSources"); // Purpur
this.skyLightSources.update(this, j, i, l);
- gameprofilerfiller.popPush("queueCheckLight");
+ //gameprofilerfiller.popPush("queueCheckLight"); // Purpur
this.level.getChunkSource().getLightEngine().checkBlock(blockposition);
- gameprofilerfiller.pop();
+ //gameprofilerfiller.pop(); // Purpur
}
boolean flag3 = iblockdata1.hasBlockEntity();
@@ -895,7 +895,7 @@ public class LevelChunk extends ChunkAccess {
this.chunkHolder.getEntityChunk().callEntitiesLoadEvent(); // Paper - rewrite chunk system
if (this.needsDecoration) {
- try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper
+ //try (co.aikar.timings.Timing ignored = this.level.timings.chunkLoadPopulate.startTiming()) { // Paper // Purpur
this.needsDecoration = false;
java.util.Random random = new java.util.Random();
random.setSeed(this.level.getSeed());
@@ -915,7 +915,7 @@ public class LevelChunk extends ChunkAccess {
}
}
server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk));
- } // Paper
+ //} // Paper // Purpur
}
}
}
@@ -1270,10 +1270,10 @@ public class LevelChunk extends ChunkAccess {
if (LevelChunk.this.isTicking(blockposition)) {
try {
- ProfilerFiller gameprofilerfiller = LevelChunk.this.level.getProfiler();
+ //ProfilerFiller gameprofilerfiller = LevelChunk.this.level.getProfiler(); // Purpur
- gameprofilerfiller.push(this::getType);
- this.blockEntity.tickTimer.startTiming(); // Spigot
+ //gameprofilerfiller.push(this::getType); // Purpur
+ //this.blockEntity.tickTimer.startTiming(); // Spigot // Purpur
BlockState iblockdata = LevelChunk.this.getBlockState(blockposition);
if (this.blockEntity.getType().isValid(iblockdata)) {
@@ -1284,7 +1284,7 @@ public class LevelChunk extends ChunkAccess {
LevelChunk.LOGGER.warn("Block entity {} @ {} state {} invalid for ticking:", new Object[]{LogUtils.defer(this::getType), LogUtils.defer(this::getPos), iblockdata});
}
- gameprofilerfiller.pop();
+ //gameprofilerfiller.pop(); // Purpur
} catch (Throwable throwable) {
if (throwable instanceof ThreadDeath) throw throwable; // Paper
// Paper start - Prevent tile entity and entity crashes
@@ -1295,7 +1295,7 @@ public class LevelChunk extends ChunkAccess {
// Paper end
// Spigot start
} finally {
- this.blockEntity.tickTimer.stopTiming();
+ //this.blockEntity.tickTimer.stopTiming(); // Purpur
// Spigot end
}
}
diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
index 060e064625969610539dbf969ce773b877a7c579..32cd9df202704cdfb8fa06aaf0e738d483054feb 100644
--- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
+++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java
@@ -112,6 +112,7 @@ public class EntityStorage implements EntityPersistentStorage<Entity> {
ListTag listTag = new ListTag();
final java.util.Map<net.minecraft.world.entity.EntityType<?>, Integer> savedEntityCounts = new java.util.HashMap<>(); // Paper
entities.forEach((entity) -> { // diff here: use entities parameter
+ if (!entity.canSaveToDisk()) return; // Purpur
// Paper start
final EntityType<?> entityType = entity.getType();
final int saveLimit = level.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1);
diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java
index dfeb3e336e06ef01f5401a362755030db942bb07..f74c5eda91a3d521763ec7bc33f23e0c62458cc2 100644
--- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java
+++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java
@@ -49,7 +49,7 @@ public class PhantomSpawner implements CustomSpawner {
int spawnAttemptMaxSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds;
this.nextTick += (spawnAttemptMinSeconds + randomsource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20;
// Paper end
- if (world.getSkyDarken() < 5 && world.dimensionType().hasSkyLight()) {
+ if (world.getSkyDarken() < world.purpurConfig.phantomSpawnMinSkyDarkness && world.dimensionType().hasSkyLight()) { // Purpur
return 0;
} else {
int i = 0;
@@ -61,10 +61,10 @@ public class PhantomSpawner implements CustomSpawner {
if (!entityplayer.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityplayer.isCreative())) { // Paper
BlockPos blockposition = entityplayer.blockPosition();
- if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) {
+ if (!world.dimensionType().hasSkyLight() || (!world.purpurConfig.phantomSpawnOnlyAboveSeaLevel || blockposition.getY() >= world.getSeaLevel()) && (!world.purpurConfig.phantomSpawnOnlyWithVisibleSky || world.canSeeSky(blockposition))) { // Purpur
DifficultyInstance difficultydamagescaler = world.getCurrentDifficultyAt(blockposition);
- if (difficultydamagescaler.isHarderThan(randomsource.nextFloat() * 3.0F)) {
+ if (difficultydamagescaler.isHarderThan(randomsource.nextFloat() * (float) world.purpurConfig.phantomSpawnLocalDifficultyChance)) { // Purpur
ServerStatsCounter serverstatisticmanager = entityplayer.getStats();
int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE);
boolean flag2 = true;
@@ -76,7 +76,7 @@ public class PhantomSpawner implements CustomSpawner {
if (NaturalSpawner.isValidEmptySpawnBlock(world, blockposition1, iblockdata, fluid, EntityType.PHANTOM)) {
SpawnGroupData groupdataentity = null;
- int k = 1 + randomsource.nextInt(difficultydamagescaler.getDifficulty().getId() + 1);
+ int k = world.purpurConfig.phantomSpawnMinPerAttempt + world.random.nextInt((world.purpurConfig.phantomSpawnMaxPerAttempt < 0 ? difficultydamagescaler.getDifficulty().getId() : world.purpurConfig.phantomSpawnMaxPerAttempt - world.purpurConfig.phantomSpawnMinPerAttempt) + 1); // Purpur
for (int l = 0; l < k; ++l) {
// Paper start
diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
index 7d56693102ee558fe784e3a9b9fdcff4b7ad57b9..485bbec4b52f126393332d5cad2d97c2a220f91a 100644
--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
@@ -227,7 +227,7 @@ public abstract class FlowingFluid extends Fluid {
}
}
- if (this.canConvertToSource(world) && j >= 2) {
+ if (this.canConvertToSource(world) && j >= getRequiredSources(world)) {
BlockState iblockdata2 = world.getBlockState(pos.below());
FluidState fluid1 = iblockdata2.getFluidState();
@@ -325,6 +325,12 @@ public abstract class FlowingFluid extends Fluid {
protected abstract boolean canConvertToSource(Level world);
+ // Purpur start
+ protected int getRequiredSources(Level level) {
+ return 2;
+ }
+ // Purpur end
+
protected void spreadTo(LevelAccessor world, BlockPos pos, BlockState state, Direction direction, FluidState fluidState) {
if (state.getBlock() instanceof LiquidBlockContainer) {
((LiquidBlockContainer) state.getBlock()).placeLiquid(world, pos, state, fluidState);
diff --git a/src/main/java/net/minecraft/world/level/material/LavaFluid.java b/src/main/java/net/minecraft/world/level/material/LavaFluid.java
index c3f8e1e2dd89c168b8b4a15b589109db486bc8d7..70ddb3b130ee59a6e200ea5af3ac89f3c3fa9e9b 100644
--- a/src/main/java/net/minecraft/world/level/material/LavaFluid.java
+++ b/src/main/java/net/minecraft/world/level/material/LavaFluid.java
@@ -180,7 +180,7 @@ public abstract class LavaFluid extends FlowingFluid {
@Override
public int getTickDelay(LevelReader world) {
- return world.dimensionType().ultraWarm() ? 10 : 30;
+ return world.dimensionType().ultraWarm() ? world.getWorldBorder().world.purpurConfig.lavaSpeedNether : world.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur
}
@Override
@@ -198,6 +198,13 @@ public abstract class LavaFluid extends FlowingFluid {
world.levelEvent(1501, pos, 0);
}
+ // Purpur start
+ @Override
+ protected int getRequiredSources(Level level) {
+ return level.purpurConfig.lavaInfiniteRequiredSources;
+ }
+ // Purpur end
+
@Override
protected boolean canConvertToSource(Level world) {
return world.getGameRules().getBoolean(GameRules.RULE_LAVA_SOURCE_CONVERSION);
diff --git a/src/main/java/net/minecraft/world/level/material/WaterFluid.java b/src/main/java/net/minecraft/world/level/material/WaterFluid.java
index d280c98aed5262c4ce39526c917de884f25a8584..e7d9f6802520620a1dcf0938256ffe80fc72a6f0 100644
--- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java
+++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java
@@ -64,6 +64,13 @@ public abstract class WaterFluid extends FlowingFluid {
return world.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION);
}
+ // Purpur start
+ @Override
+ protected int getRequiredSources(Level level) {
+ return level.purpurConfig.waterInfiniteRequiredSources;
+ }
+ // Purpur end
+
// Paper start
@Override
protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) {
diff --git a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
index d23481453717f715124156b5d83f6448f720d049..a8af51a25b0f99c3a64d9150fdfcd6b818aa7581 100644
--- a/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
+++ b/src/main/java/net/minecraft/world/level/pathfinder/PathFinder.java
@@ -53,8 +53,8 @@ public class PathFinder {
@Nullable
// Paper start - optimize collection
private Path findPath(ProfilerFiller profiler, Node startNode, List<Map.Entry<Target, BlockPos>> positions, float followRange, int distance, float rangeMultiplier) {
- profiler.push("find_path");
- profiler.markForCharting(MetricCategory.PATH_FINDING);
+ //profiler.push("find_path"); // Purpur
+ //profiler.markForCharting(MetricCategory.PATH_FINDING); // Purpur
// Set<Target> set = positions.keySet();
startNode.g = 0.0F;
startNode.h = this.getBestH(startNode, positions); // Paper - optimize collection
diff --git a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java
index 3583fcf5284bc5883308876dbd9886664b391e28..ba57accc272958da4714896baeadb52c99383561 100644
--- a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java
+++ b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java
@@ -241,7 +241,7 @@ public class WalkNodeEvaluator extends NodeEvaluator {
}
if (blockPathTypes != BlockPathTypes.WALKABLE && (!this.isAmphibious() || blockPathTypes != BlockPathTypes.WATER)) {
- if ((node == null || node.costMalus < 0.0F) && maxYStep > 0 && (blockPathTypes != BlockPathTypes.FENCE || this.canWalkOverFences()) && blockPathTypes != BlockPathTypes.UNPASSABLE_RAIL && blockPathTypes != BlockPathTypes.TRAPDOOR && blockPathTypes != BlockPathTypes.POWDER_SNOW) {
+ if ((node == null || node.costMalus < 0.0F) && maxYStep > 0 && (blockPathTypes != BlockPathTypes.FENCE || this.canWalkOverFences()) && (this.mob.level().purpurConfig.mobsIgnoreRails || blockPathTypes != BlockPathTypes.UNPASSABLE_RAIL) && blockPathTypes != BlockPathTypes.TRAPDOOR && blockPathTypes != BlockPathTypes.POWDER_SNOW) { // Purpur
node = this.findAcceptedNode(x, y + 1, z, maxYStep - 1, prevFeetY, direction, nodeType);
if (node != null && (node.type == BlockPathTypes.OPEN || node.type == BlockPathTypes.WALKABLE) && this.mob.getBbWidth() < 1.0F) {
double g = (double)(x - direction.getStepX()) + 0.5D;
@@ -465,7 +465,7 @@ public class WalkNodeEvaluator extends NodeEvaluator {
return BlockPathTypes.BLOCKED;
} else {
// Paper end
- if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH)) {
+ if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH) || blockState.is(Blocks.STONECUTTER)) { // Purpur
return BlockPathTypes.DANGER_OTHER;
}
@@ -498,7 +498,7 @@ public class WalkNodeEvaluator extends NodeEvaluator {
} else if (!blockState.is(BlockTags.TRAPDOORS) && !blockState.is(Blocks.LILY_PAD) && !blockState.is(Blocks.BIG_DRIPLEAF)) {
if (blockState.is(Blocks.POWDER_SNOW)) {
return BlockPathTypes.POWDER_SNOW;
- } else if (!blockState.is(Blocks.CACTUS) && !blockState.is(Blocks.SWEET_BERRY_BUSH)) {
+ } else if (!blockState.is(Blocks.CACTUS) && !blockState.is(Blocks.SWEET_BERRY_BUSH) && !blockState.is(Blocks.STONECUTTER)) { // Purpur
if (blockState.is(Blocks.HONEY_BLOCK)) {
return BlockPathTypes.STICKY_HONEY;
} else if (blockState.is(Blocks.COCOA)) {
diff --git a/src/main/java/net/minecraft/world/level/portal/PortalShape.java b/src/main/java/net/minecraft/world/level/portal/PortalShape.java
index c461e0d04047db9c0c5ecc04063cebd38bf96ec2..e7554ec800f321e4e34c926c53f2375a8c3aa979 100644
--- a/src/main/java/net/minecraft/world/level/portal/PortalShape.java
+++ b/src/main/java/net/minecraft/world/level/portal/PortalShape.java
@@ -34,7 +34,7 @@ public class PortalShape {
private static final int MIN_HEIGHT = 3;
public static final int MAX_HEIGHT = 21;
private static final BlockBehaviour.StatePredicate FRAME = (iblockdata, iblockaccess, blockposition) -> {
- return iblockdata.is(Blocks.OBSIDIAN);
+ return iblockdata.is(Blocks.OBSIDIAN) || (org.purpurmc.purpur.PurpurConfig.cryingObsidianValidForPortalFrame && iblockdata.is(Blocks.CRYING_OBSIDIAN)); // Purpur
};
private static final float SAFE_TRAVEL_MAX_ENTITY_XY = 4.0F;
private static final double SAFE_TRAVEL_MAX_VERTICAL_DELTA = 1.0D;
diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
index 50713f03c783c63f93710d986d94af544be0615a..e64921f454c88e2ca9e1396fcb1ca7c179ff71a7 100644
--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
@@ -66,6 +66,7 @@ public class MapItemSavedData extends SavedData {
private final Map<String, MapFrame> frameMarkers = Maps.newHashMap();
private int trackedDecorationCount;
private org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper
+ public boolean isExplorerMap; // Purpur
// CraftBukkit start
public final CraftMapView mapView;
diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java
index 31918fa2eb38e42a5ea5366e559f25ea9d7d59ae..15d8e9261a89da30529ac347462c520920ca4e7d 100644
--- a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java
+++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java
@@ -49,6 +49,13 @@ public class LootingEnchantFunction extends LootItemConditionalFunction {
if (entity instanceof LivingEntity) {
int i = EnchantmentHelper.getMobLooting((LivingEntity) entity);
+ // Purpur start
+ if (org.purpurmc.purpur.PurpurConfig.fixProjectileLootingTransfer &&
+ context.getParamOrNull(LootContextParams.DIRECT_KILLER_ENTITY)
+ instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) {
+ i = arrow.lootingLevel;
+ }
+ // Purpur end
// CraftBukkit start - use lootingModifier if set by plugin
if (context.hasParam(LootContextParams.LOOTING_MOD)) {
i = context.getParamOrNull(LootContextParams.LOOTING_MOD);
diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java
index ffc76354ead6937daf366c3d87bcb51d3e4c47f5..5b98d42b5d6bc07265fbb017e51a6281c148436a 100644
--- a/src/main/java/net/minecraft/world/phys/AABB.java
+++ b/src/main/java/net/minecraft/world/phys/AABB.java
@@ -374,4 +374,10 @@ public class AABB {
public static AABB ofSize(Vec3 center, double dx, double dy, double dz) {
return new AABB(center.x - dx / 2.0D, center.y - dy / 2.0D, center.z - dz / 2.0D, center.x + dx / 2.0D, center.y + dy / 2.0D, center.z + dz / 2.0D);
}
+
+ // Purpur - tuinity added method
+ public final AABB offsetY(double dy) {
+ return new AABB(this.minX, this.minY + dy, this.minZ, this.maxX, this.maxY + dy, this.maxZ);
+ }
+ // Purpur
}
diff --git a/src/main/java/net/minecraft/world/ticks/LevelTicks.java b/src/main/java/net/minecraft/world/ticks/LevelTicks.java
index 1d7c663fa0e550bd0cfb9a4b83ccd7e2968666f0..0043c0087896a6df6910b0500da37d84b287c901 100644
--- a/src/main/java/net/minecraft/world/ticks/LevelTicks.java
+++ b/src/main/java/net/minecraft/world/ticks/LevelTicks.java
@@ -86,20 +86,20 @@ public class LevelTicks<T> implements LevelTickAccess<T> {
}
public void tick(long time, int maxTicks, BiConsumer<BlockPos, T> ticker) {
- ProfilerFiller profilerFiller = this.profiler.get();
- profilerFiller.push("collect");
- this.collectTicks(time, maxTicks, profilerFiller);
- profilerFiller.popPush("run");
- profilerFiller.incrementCounter("ticksToRun", this.toRunThisTick.size());
+ //ProfilerFiller profilerFiller = this.profiler.get(); // Purpur
+ //profilerFiller.push("collect"); // Purpur
+ this.collectTicks(time, maxTicks, null); // Purpur
+ //profilerFiller.popPush("run"); // Purpur
+ //profilerFiller.incrementCounter("ticksToRun", this.toRunThisTick.size()); // Purpur
this.runCollectedTicks(ticker);
- profilerFiller.popPush("cleanup");
+ //profilerFiller.popPush("cleanup"); // Purpur
this.cleanupAfterTick();
- profilerFiller.pop();
+ //profilerFiller.pop(); // Purpur
}
private void collectTicks(long time, int maxTicks, ProfilerFiller profiler) {
this.sortContainersToTick(time);
- profiler.incrementCounter("containersToTick", this.containersToTick.size());
+ //profiler.incrementCounter("containersToTick", this.containersToTick.size()); // Purpur
this.drainContainers(time, maxTicks);
this.rescheduleLeftoverContainers();
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
index c1b874cd6e0498fce3cd53fdbaca30d290e004d7..23087d511b609693f0bb06cbaac8b6cf8d0defe2 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
@@ -567,4 +567,213 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa
manager.save();
}
}
+
+ // Purpur start - OfflinePlayer API
+ @Override
+ public boolean getAllowFlight() {
+ if (this.isOnline()) {
+ return this.getPlayer().getAllowFlight();
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return false;
+ if (!data.contains("abilities")) return false;
+ CompoundTag abilities = data.getCompound("abilities");
+ return abilities.getByte("mayfly") == (byte) 1;
+ }
+ }
+
+ @Override
+ public void setAllowFlight(boolean flight) {
+ if (this.isOnline()) {
+ this.getPlayer().setAllowFlight(flight);
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return;
+ if (!data.contains("abilities")) return;
+ CompoundTag abilities = data.getCompound("abilities");
+ abilities.putByte("mayfly", (byte) (flight ? 1 : 0));
+ data.put("abilities", abilities);
+ save(data);
+ }
+ }
+
+ @Override
+ public boolean isFlying() {
+ if (this.isOnline()) {
+ return this.isFlying();
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return false;
+ if (!data.contains("abilities")) return false;
+ CompoundTag abilities = data.getCompound("abilities");
+ return abilities.getByte("flying") == (byte) 1;
+ }
+ }
+
+ @Override
+ public void setFlying(boolean value) {
+ if (this.isOnline()) {
+ this.getPlayer().setFlying(value);
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return;
+ if (!data.contains("abilities")) return;
+ CompoundTag abilities = data.getCompound("abilities");
+ abilities.putByte("mayfly", (byte) (value ? 1 : 0));
+ data.put("abilities", abilities);
+ save(data);
+ }
+ }
+
+ @Override
+ public void setFlySpeed(float value) throws IllegalArgumentException {
+ if (value < -1f || value > 1f) throw new IllegalArgumentException("FlySpeed needs to be between -1 and 1");
+ if (this.isOnline()) {
+ this.getPlayer().setFlySpeed(value);
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return;
+ if (!data.contains("abilities")) return;
+ CompoundTag abilities = data.getCompound("abilities");
+ abilities.putFloat("flySpeed", value);
+ data.put("abilities", abilities);
+ save(data);
+ }
+ }
+
+ @Override
+ public float getFlySpeed() {
+ if (this.isOnline()) {
+ return this.getPlayer().getFlySpeed();
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return 0;
+ if (!data.contains("abilities")) return 0;
+ CompoundTag abilities = data.getCompound("abilities");
+ return abilities.getFloat("flySpeed");
+ }
+ }
+
+ @Override
+ public void setWalkSpeed(float value) throws IllegalArgumentException {
+ if (value < -1f || value > 1f) throw new IllegalArgumentException("WalkSpeed needs to be between -1 and 1");
+ if (this.isOnline()) {
+ this.getPlayer().setWalkSpeed(value);
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return;
+ if (!data.contains("abilities")) return;
+ CompoundTag abilities = data.getCompound("abilities");
+ abilities.putFloat("walkSpeed", value);
+ data.put("abilities", abilities);
+ save(data);
+ }
+ }
+
+ @Override
+ public float getWalkSpeed() {
+ if (this.isOnline()) {
+ return this.getPlayer().getWalkSpeed();
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return 0;
+ if (!data.contains("abilities")) return 0;
+ CompoundTag abilities = data.getCompound("abilities");
+ return abilities.getFloat("walkSpeed");
+ }
+ }
+
+ @Override
+ public Location getLocation() {
+ if (this.isOnline()) {
+ return this.getPlayer().getLocation();
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return null;
+ long worldUUIDMost = data.getLong("WorldUUIDMost");
+ long worldUUIDLeast = data.getLong("WorldUUIDLeast");
+ net.minecraft.nbt.ListTag position = data.getList("Pos", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_DOUBLE);
+ net.minecraft.nbt.ListTag rotation = data.getList("Rotation", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_FLOAT);
+ UUID worldUuid = new UUID(worldUUIDMost, worldUUIDLeast);
+ org.bukkit.World world = server.getWorld(worldUuid);
+ double x = position.getDouble(0);
+ double y = position.getDouble(1);
+ double z = position.getDouble(2);
+ float yaw = rotation.getFloat(0);
+ float pitch = rotation.getFloat(1);
+ return new Location(world, x, y, z, yaw, pitch);
+ }
+ }
+
+ @Override
+ public boolean teleportOffline(Location destination) {
+ if (this.isOnline()) {
+ return this.getPlayer().teleport(destination);
+ } else {
+ return setLocation(destination);
+ }
+ }
+
+ @Override
+ public boolean teleportOffline(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause){
+ if (this.isOnline()) {
+ return this.getPlayer().teleport(destination, cause);
+ } else {
+ return setLocation(destination);
+ }
+ }
+
+ @Override
+ public java.util.concurrent.CompletableFuture<Boolean> 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<Boolean> teleportOfflineAsync(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
+ if (this.isOnline()) {
+ return this.getPlayer().teleportAsync(destination, cause);
+ } else {
+ return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination));
+ }
+ }
+
+ private boolean setLocation(Location location) {
+ CompoundTag data = this.getData();
+ if (data == null) return false;
+ data.putLong("WorldUUIDMost", location.getWorld().getUID().getMostSignificantBits());
+ data.putLong("WorldUUIDLeast", location.getWorld().getUID().getLeastSignificantBits());
+ net.minecraft.nbt.ListTag position = new net.minecraft.nbt.ListTag();
+ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getX()));
+ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getY()));
+ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getZ()));
+ data.put("Pos", position);
+ net.minecraft.nbt.ListTag rotation = new net.minecraft.nbt.ListTag();
+ rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getYaw()));
+ rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getPitch()));
+ data.put("Rotation", rotation);
+ save(data);
+ return true;
+ }
+
+ /**
+ * Safely replaces player's .dat file with provided CompoundTag
+ * @param compoundTag
+ */
+ private void save(CompoundTag compoundTag) {
+ File playerDir = server.console.playerDataStorage.getPlayerDir();
+ try {
+ File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir);
+ net.minecraft.nbt.NbtIo.writeCompressed(compoundTag, tempFile);
+ File playerDataFile = new File(playerDir, this.getUniqueId()+".dat");
+ File playerDataFileOld = new File(playerDir, this.getUniqueId()+".dat_old");
+ net.minecraft.Util.safeReplaceFile(playerDataFile, tempFile, playerDataFileOld);
+ } catch (java.io.IOException e) {
+ e.printStackTrace();
+ }
+ }
+ // Purpur end - OfflinePlayer API
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 74c46cea456f4a736325892bb7b4d0f1b35b62cd..b08d4a2a74392b1d59b1eeeab3108103d83ad96c 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -265,7 +265,7 @@ import javax.annotation.Nullable; // Paper
import javax.annotation.Nonnull; // Paper
public final class CraftServer implements Server {
- private final String serverName = "Pufferfish"; // Paper // Pufferfish
+ private final String serverName = "Purpur"; // Paper // Pufferfish // Purpur
private final String serverVersion;
private final String bukkitVersion = Versioning.getBukkitVersion();
private final Logger logger = Logger.getLogger("Minecraft");
@@ -397,6 +397,20 @@ public final class CraftServer implements Server {
this.dataPackManager = new CraftDataPackManager(this.getServer().getPackRepository());
Bukkit.setServer(this);
+ // Purpur start
+ org.purpurmc.purpur.language.Language.setLanguage(new org.purpurmc.purpur.language.Language() {
+ private net.minecraft.locale.Language language = net.minecraft.locale.Language.getInstance();
+ @Override
+ public boolean has(@org.jetbrains.annotations.NotNull String key) {
+ return language.has(key);
+ }
+
+ @Override
+ public @org.jetbrains.annotations.NotNull String getOrDefault(@org.jetbrains.annotations.NotNull String key) {
+ return language.getOrDefault(key);
+ }
+ });
+ // Purpur end
// Register all the Enchantments and PotionTypes now so we can stop new registration immediately after
Enchantments.SHARPNESS.getClass();
@@ -1057,6 +1071,7 @@ public final class CraftServer implements Server {
org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot
this.console.paperConfigurations.reloadConfigs(this.console);
+ org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur
for (ServerLevel world : this.console.getAllLevels()) {
// world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty
world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters, config.spawnAnimals); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean))
@@ -1072,6 +1087,7 @@ public final class CraftServer implements Server {
}
}
world.spigotConfig.init(); // Spigot
+ world.purpurConfig.init(); // Purpur
}
Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
@@ -1087,6 +1103,7 @@ public final class CraftServer implements Server {
this.reloadData();
org.spigotmc.SpigotConfig.registerCommands(); // Spigot
io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper
+ org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur
this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions");
@@ -1552,6 +1569,55 @@ public final class CraftServer implements Server {
return true;
}
+ // Purpur Start
+ @Override
+ public void addFuel(org.bukkit.Material material, int burnTime) {
+ Preconditions.checkArgument(burnTime > 0, "BurnTime must be greater than 0");
+ net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity.addFuel(net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)), burnTime);
+ }
+
+ @Override
+ public void removeFuel(org.bukkit.Material material) {
+ net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity.removeFuel(net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)));
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration) {
+ sendBlockHighlight(location, duration, "", 0x6400FF00);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, int argb) {
+ sendBlockHighlight(location, duration, "", argb);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text) {
+ sendBlockHighlight(location, duration, text, 0x6400FF00);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text, int argb) {
+ this.worlds.forEach((name, world) -> world.sendBlockHighlight(location, duration, text, argb));
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) {
+ sendBlockHighlight(location, duration, "", color, transparency);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) {
+ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range");
+ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB());
+ }
+
+ @Override
+ public void clearBlockHighlights() {
+ this.worlds.forEach((name, world) -> clearBlockHighlights());
+ }
+ // Purpur End
+
@Override
public List<Recipe> getRecipesFor(ItemStack result) {
Preconditions.checkArgument(result != null, "ItemStack cannot be null");
@@ -2891,6 +2957,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()
@@ -2937,6 +3004,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();
@@ -3150,4 +3229,16 @@ public final class CraftServer implements Server {
}
// Paper end
+
+ // Purpur start
+ @Override
+ public String getServerName() {
+ return this.getProperties().serverName;
+ }
+
+ @Override
+ public boolean isLagging() {
+ return getServer().lagging;
+ }
+ // Purpur end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index f857f490ffba2f25f7c06c5fb1a1905f0b51fbe2..1392e483c363e25d1f16465d876cb7d7c70afa68 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -2290,6 +2290,48 @@ public class CraftWorld extends CraftRegionAccessor implements World {
return (this.getHandle().getDragonFight() == null) ? null : new CraftDragonBattle(this.getHandle().getDragonFight());
}
+ // Purpur start
+ public float getLocalDifficultyAt(Location location) {
+ return getHandle().getCurrentDifficultyAt(io.papermc.paper.util.MCUtil.toBlockPosition(location)).getEffectiveDifficulty();
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration) {
+ sendBlockHighlight(location, duration, "", 0x6400FF00);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, int argb) {
+ sendBlockHighlight(location, duration, "", argb);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text) {
+ sendBlockHighlight(location, duration, text, 0x6400FF00);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text, int argb) {
+ net.minecraft.network.protocol.game.DebugPackets.sendGameTestAddMarker(getHandle(), io.papermc.paper.util.MCUtil.toBlockPosition(location), text, argb, duration);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) {
+ sendBlockHighlight(location, duration, "", color, transparency);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) {
+ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range");
+ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB());
+ }
+
+ @Override
+ public void clearBlockHighlights() {
+ net.minecraft.network.protocol.game.DebugPackets.sendGameTestClearPacket(getHandle());
+ }
+ // Purpur end
+
@Override
public PersistentDataContainer getPersistentDataContainer() {
return this.persistentDataContainer;
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
index 776b7fc26fe96b69be260bbd36efae147d988640..9a3374a1236164194ef0df43ed639296cbe1c731 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -173,6 +173,20 @@ public class Main {
.describedAs("Jar file");
// Paper end
+ // Purpur Start
+ acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings")
+ .withRequiredArg()
+ .ofType(File.class)
+ .defaultsTo(new File("purpur.yml"))
+ .describedAs("Yml file");
+
+ acceptsAll(asList("pufferfish", "pufferfish-settings"), "File for pufferfish settings")
+ .withRequiredArg()
+ .ofType(File.class)
+ .defaultsTo(new File("pufferfish.yml"))
+ .describedAs("Yml file");
+ // Purpur end
+
// Paper start
acceptsAll(asList("server-name"), "Name of the server")
.withRequiredArg()
@@ -284,7 +298,7 @@ public class Main {
System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper
}
- if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) {
+ if (false && Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { // Purpur
Date buildDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(Main.class.getPackage().getImplementationVendor()); // Paper
Calendar deadline = Calendar.getInstance();
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java
index 380897c010521f368848a3e6986d307cf47ff319..3c5eae31dc229b8c5f15215be9462410bcb41f07 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java
@@ -16,8 +16,15 @@ import org.bukkit.entity.Bee;
public class CraftBeehive extends CraftBlockEntityState<BeehiveBlockEntity> implements Beehive {
+ private final List<org.purpurmc.purpur.entity.StoredEntity<Bee>> storage = new ArrayList<>(); // Purpur
+
public CraftBeehive(World world, BeehiveBlockEntity tileEntity) {
super(world, tileEntity);
+ // Purpur start - load bees to be able to modify them individually
+ for(BeehiveBlockEntity.BeeData data : getSnapshot().getStored()) {
+ storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(data, this));
+ }
+ // Purpur end
}
@Override
@@ -66,25 +73,67 @@ public class CraftBeehive extends CraftBlockEntityState<BeehiveBlockEntity> impl
List<Bee> bees = new ArrayList<>();
if (isPlaced()) {
- BeehiveBlockEntity beehive = ((BeehiveBlockEntity) this.getTileEntityFromWorld());
- for (Entity bee : beehive.releaseBees(this.getHandle(), BeeReleaseStatus.BEE_RELEASED, true)) {
+ // Purpur start - change which releaseBees method is called, and use beehive snapshot
+ BeehiveBlockEntity beehive = ((BeehiveBlockEntity) this.getSnapshot());
+ for (Entity bee : beehive.releaseBees(this.getHandle(), getWorldHandle().getMinecraftWorld(), BeeReleaseStatus.BEE_RELEASED, true)) {
+ // Purpur end
bees.add((Bee) bee.getBukkitEntity());
}
}
-
+ storage.clear(); // Purpur
return bees;
}
+ // Purpur start
+ @Override
+ public Bee releaseEntity(org.purpurmc.purpur.entity.StoredEntity<Bee> entity) {
+ ensureNoWorldGeneration();
+
+ if(!getEntities().contains(entity)) {
+ return null;
+ }
+
+ if(isPlaced()) {
+ BeehiveBlockEntity beehive = this.getSnapshot();
+ BeehiveBlockEntity.BeeData data = ((org.purpurmc.purpur.entity.PurpurStoredBee) entity).getHandle();
+
+ List<Entity> list = beehive.releaseBee(getHandle(), getWorldHandle().getMinecraftWorld(), data, BeeReleaseStatus.BEE_RELEASED, true);
+
+ if (list.size() == 1) {
+ storage.remove(entity);
+
+ return (Bee) list.get(0).getBukkitEntity();
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public List<org.purpurmc.purpur.entity.StoredEntity<Bee>> getEntities() {
+ return new ArrayList<>(storage);
+ }
+ // Purpur end
+
@Override
public void addEntity(Bee entity) {
Preconditions.checkArgument(entity != null, "Entity must not be null");
+ int length = getSnapshot().getStored().size(); // Purpur
getSnapshot().addOccupant(((CraftBee) entity).getHandle(), false);
+
+ // Purpur start - check if new bee was added, and if yes, add to stored bees
+ List<BeehiveBlockEntity.BeeData> s = getSnapshot().getStored();
+ if(length < s.size()) {
+ storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(s.get(s.size() - 1), this));
+ }
+ // Purpur end
}
// Paper start - Add EntityBlockStorage clearEntities
@Override
public void clearEntities() {
getSnapshot().clearBees();
+ storage.clear(); // Purpur
}
// Paper end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
index 4e56018b64d11f76c8da43fd8f85c6de72204e36..9607675e6c5bff2183c4420d11fc63eeb5747fb6 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
@@ -21,7 +21,12 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co
@Override
public void sendMessage(String message) {
- this.sendRawMessage(message);
+ // Purpur start
+ String[] parts = message.split("\n");
+ for (String part : parts) {
+ this.sendRawMessage(part);
+ }
+ // Purpur end
}
@Override
@@ -91,7 +96,7 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co
// Paper start
@Override
public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) {
- this.sendRawMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message));
+ this.sendMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); // Purpur
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java
index 3d0ce0803e1da8a2681a3cb41096ac942ece54a1..bcd075a771c7f43c6d1549aeec2ccb20ee168b57 100644
--- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java
+++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java
@@ -59,6 +59,7 @@ public class CraftEnchantment extends Enchantment {
return EnchantmentTarget.CROSSBOW;
case VANISHABLE:
return EnchantmentTarget.VANISHABLE;
+ case BOW_AND_CROSSBOW: return EnchantmentTarget.BOW_AND_CROSSBOW; // Purpur
default:
return null;
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java
index d657fd2c507a5b215aeab0a5f3e9c2ee892a27c8..985e9ec21c60a1f47973bd5fc53b96a6f9b7d04a 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java
@@ -21,12 +21,12 @@ public class CraftEndermite extends CraftMonster implements Endermite {
@Override
public boolean isPlayerSpawned() {
- return false;
+ return getHandle().isPlayerSpawned(); // Purpur
}
@Override
public void setPlayerSpawned(boolean playerSpawned) {
- // Nop
+ getHandle().setPlayerSpawned(playerSpawned); // Purpur
}
// Paper start
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 2dbe8b870fd39b4d22e9725912f443757ae70761..8973c8a3bad120e55269bf1b7b810284ad0fe14c 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -224,6 +224,21 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
this.entityType = (type != null) ? type : EntityType.UNKNOWN;
}
+ @Override
+ public boolean isImmuneToFire() {
+ return getHandle().fireImmune();
+ }
+
+ @Override
+ public void setImmuneToFire(Boolean fireImmune) {
+ getHandle().immuneToFire = fireImmune;
+ }
+
+ @Override
+ public boolean isInDaylight() {
+ return getHandle().isSunBurnTick();
+ }
+
public static CraftEntity getEntity(CraftServer server, Entity entity) {
/*
* Order is *EXTREMELY* important -- keep it right! =D
@@ -601,6 +616,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
// Paper end
if ((!ignorePassengers && this.entity.isVehicle()) || this.entity.isRemoved()) { // Paper - Teleport passenger API
+ // Purpur start
+ if (!entity.isRemoved() && new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent())
+ return teleport(location, cause);
+ // Purpur end
return false;
}
@@ -1507,4 +1526,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
return this.getHandle().getScoreboardName();
}
// Paper end - entity scoreboard name
+
+ // Purpur start
+ @Override
+ public org.bukkit.entity.Player getRider() {
+ net.minecraft.world.entity.player.Player rider = getHandle().getRider();
+ return rider != null ? (org.bukkit.entity.Player) rider.getBukkitEntity() : null;
+ }
+
+ @Override
+ public boolean hasRider() {
+ return getHandle().getRider() != null;
+ }
+
+ @Override
+ public boolean isRidable() {
+ return getHandle().isRidable();
+ }
+
+ @Override
+ public boolean isRidableInWater() {
+ return !getHandle().dismountsUnderwater();
+ }
+ // Purpur end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
index 7db63d9ef93902872937b69f431137336e4abc3a..05ab5d0ae25f154d16c7bf107e93ed8e31e1f992 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
@@ -266,6 +266,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity {
@Override
public void recalculatePermissions() {
this.perm.recalculatePermissions();
+ getHandle().canPortalInstant = hasPermission("purpur.portal.instant"); // Purpur
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java
index 75d10b5322eb0a62bce2855c04a5151eb857d7de..208018981a2a5666c455eb34614b03f617354165 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java
@@ -27,4 +27,17 @@ public class CraftIronGolem extends CraftGolem implements IronGolem {
public void setPlayerCreated(boolean playerCreated) {
this.getHandle().setPlayerCreated(playerCreated);
}
+
+ // Purpur start
+ @Override
+ @org.jetbrains.annotations.Nullable
+ public java.util.UUID getSummoner() {
+ return getHandle().getSummoner();
+ }
+
+ @Override
+ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) {
+ getHandle().setSummoner(summoner);
+ }
+ // Purpur end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
index 5e83fabb20bc2b0668cbf48530053ca1bb9092f3..4ffb4046b63cbc140c76721f51c9a7a09e81844d 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
@@ -154,4 +154,51 @@ public class CraftItem extends CraftEntity implements Item {
public String toString() {
return "CraftItem";
}
+
+ // Purpur start
+ @Override
+ public void setImmuneToCactus(boolean immuneToCactus) {
+ item.immuneToCactus = immuneToCactus;
+ }
+
+ @Override
+ public boolean isImmuneToCactus() {
+ return item.immuneToCactus;
+ }
+
+ @Override
+ public void setImmuneToExplosion(boolean immuneToExplosion) {
+ item.immuneToExplosion = immuneToExplosion;
+ }
+
+ @Override
+ public boolean isImmuneToExplosion() {
+ return item.immuneToExplosion;
+ }
+
+ @Override
+ public void setImmuneToFire(@org.jetbrains.annotations.Nullable Boolean immuneToFire) {
+ item.immuneToFire = (immuneToFire != null && immuneToFire);
+ }
+
+ @Override
+ public void setImmuneToFire(boolean immuneToFire) {
+ this.setImmuneToFire((Boolean) immuneToFire);
+ }
+
+ @Override
+ public boolean isImmuneToFire() {
+ return item.immuneToFire;
+ }
+
+ @Override
+ public void setImmuneToLightning(boolean immuneToLightning) {
+ item.immuneToLightning = immuneToLightning;
+ }
+
+ @Override
+ public boolean isImmuneToLightning() {
+ return item.immuneToLightning;
+ }
+ // Purpur end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
index b25f5931f836fd4d8695120f0bcb7c52deff8583..b473f89c59c0fc080fadd0d4db320895e917fd41 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
@@ -453,7 +453,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
net.minecraft.server.level.ServerPlayer entityPlayer = killer == null ? null : ((CraftPlayer) killer).getHandle();
getHandle().lastHurtByPlayer = entityPlayer;
getHandle().lastHurtByMob = entityPlayer;
- getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : 100; // 100 value taken from EntityLiving#damageEntity
+ getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : getHandle().level().purpurConfig.mobLastHurtByPlayerTime; // 100 value taken from EntityLiving#damageEntity // Purpur
}
// Paper end
@@ -465,7 +465,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
@Override
public boolean addPotionEffect(PotionEffect effect, boolean force) {
org.spigotmc.AsyncCatcher.catchOp("effect add"); // Paper
- this.getHandle().addEffect(new MobEffectInstance(MobEffect.byId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon()), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon
+ this.getHandle().addEffect(new MobEffectInstance(MobEffect.byId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon(), effect.getKey()), EntityPotionEffectEvent.Cause.PLUGIN); // Purpur - add key // Paper - Don't ignore icon
return true;
}
@@ -486,7 +486,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
@Override
public PotionEffect getPotionEffect(PotionEffectType type) {
MobEffectInstance handle = this.getHandle().getEffect(MobEffect.byId(type.getId()));
- return (handle == null) ? null : new PotionEffect(PotionEffectType.getById(MobEffect.getId(handle.getEffect())), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible());
+ return (handle == null) ? null : new PotionEffect(PotionEffectType.getById(MobEffect.getId(handle.getEffect())), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible(), handle.getKey()); // Purpur - add key
}
@Override
@@ -498,7 +498,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
public Collection<PotionEffect> getActivePotionEffects() {
List<PotionEffect> effects = new ArrayList<PotionEffect>();
for (MobEffectInstance handle : this.getHandle().activeEffects.values()) {
- effects.add(new PotionEffect(PotionEffectType.getById(MobEffect.getId(handle.getEffect())), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible()));
+ effects.add(new PotionEffect(PotionEffectType.getById(MobEffect.getId(handle.getEffect())), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible(), handle.getKey())); // Purpur - add key
}
return effects;
}
@@ -912,7 +912,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
return EntityCategory.WATER;
}
- throw new UnsupportedOperationException("Unsupported monster type: " + type + ". This is a bug, report this to Spigot.");
+ throw new UnsupportedOperationException("Unsupported monster type: " + type + ". This is a bug, report this to Purpur."); // Purpur
}
@Override
@@ -1099,4 +1099,32 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
getHandle().knockback(strength, directionX, directionZ);
};
// Paper end
+
+ // Purpur start
+ @Override
+ public float getSafeFallDistance() {
+ return getHandle().safeFallDistance;
+ }
+
+ @Override
+ public void setSafeFallDistance(float safeFallDistance) {
+ getHandle().safeFallDistance = safeFallDistance;
+ }
+
+ @Override
+ public void broadcastItemBreak(org.bukkit.inventory.EquipmentSlot slot) {
+ if (slot == null) return;
+ getHandle().broadcastBreakEvent(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot));
+ }
+
+ @Override
+ public boolean shouldBurnInDay() {
+ return getHandle().shouldBurnInDay();
+ }
+
+ @Override
+ public void setShouldBurnInDay(boolean shouldBurnInDay) {
+ getHandle().setShouldBurnInDay(shouldBurnInDay);
+ }
+ // Purpur end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
index 0ad16ee7b33582d214dab41eeee378d52c8e38ed..16bd1294c219f15ada653ef810bc2d748222d0da 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
@@ -90,4 +90,16 @@ public class CraftLlama extends CraftChestedHorse implements Llama, com.destroys
return this.getHandle().caravanTail == null ? null : (Llama) this.getHandle().caravanTail.getBukkitEntity();
}
// Paper end
+
+ // Purpur start
+ @Override
+ public boolean shouldJoinCaravan() {
+ return getHandle().shouldJoinCaravan;
+ }
+
+ @Override
+ public void setShouldJoinCaravan(boolean shouldJoinCaravan) {
+ getHandle().shouldJoinCaravan = shouldJoinCaravan;
+ }
+ // Purpur end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 4e6fea7cf11b1e29ae7c7098a6f5d06bb5f93cc2..668d825a2469706e4de11629a0b41877de700ca6 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -517,10 +517,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@Override
public void setPlayerListName(String name) {
+ // Purpur start
+ setPlayerListName(name, false);
+ }
+ public void setPlayerListName(String name, boolean useMM) {
+ // Purpur end
if (name == null) {
name = getName();
}
- this.getHandle().listName = name.equals(getName()) ? null : CraftChatMessage.fromStringOrNull(name);
+ this.getHandle().listName = name.equals(getName()) ? null : useMM ? io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name)) : CraftChatMessage.fromStringOrNull(name); // Purpur
for (ServerPlayer player : (List<ServerPlayer>) server.getHandle().players) {
if (player.getBukkitEntity().canSee(this)) {
player.connection.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, this.getHandle()));
@@ -1350,6 +1355,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
}
if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API
+ // Purpur start
+ if (new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent())
+ return teleport(location, cause);
+ // Purpur end
return false;
}
@@ -2496,6 +2505,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
return this.getHandle().getAbilities().walkingSpeed * 2f;
}
+ // Purpur start - OfflinePlayer API
+ @Override
+ public boolean teleportOffline(@NotNull Location destination) {
+ return this.teleport(destination);
+ }
+
+ @Override
+ public boolean teleportOffline(Location destination, PlayerTeleportEvent.TeleportCause cause) {
+ return this.teleport(destination, cause);
+ }
+
+ @Override
+ public java.util.concurrent.CompletableFuture<Boolean> teleportOfflineAsync(@NotNull Location destination) {
+ return this.teleportAsync(destination);
+ }
+
+ @Override
+ public java.util.concurrent.CompletableFuture<Boolean> teleportOfflineAsync(@NotNull Location destination, PlayerTeleportEvent.TeleportCause cause) {
+ return this.teleportAsync(destination, cause);
+ }
+ // Purpur end - OfflinePlayer API
+
private void validateSpeed(float value) {
Preconditions.checkArgument(value <= 1f && value >= -1f, "Speed value (%s) need to be between -1f and 1f", value);
}
@@ -3267,4 +3298,90 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
return this.spigot;
}
// Spigot end
+
+ // Purpur start
+ @Override
+ public boolean usesPurpurClient() {
+ return getHandle().purpurClient;
+ }
+
+ @Override
+ public boolean isAfk() {
+ return getHandle().isAfk();
+ }
+
+ @Override
+ public void setAfk(boolean setAfk) {
+ getHandle().setAfk(setAfk);
+ }
+
+ @Override
+ public void resetIdleTimer() {
+ getHandle().resetLastActionTime();
+ }
+
+ @Override
+ public boolean isSpawnInvulnerable() {
+ return getHandle().isSpawnInvulnerable();
+ }
+
+ @Override
+ public int getSpawnInvulnerableTicks() {
+ return getHandle().spawnInvulnerableTime;
+ }
+
+ @Override
+ public void setSpawnInvulnerableTicks(int spawnInvulnerableTime) {
+ getHandle().spawnInvulnerableTime = spawnInvulnerableTime;
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration) {
+ sendBlockHighlight(location, duration, "", 0x6400FF00);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, int argb) {
+ sendBlockHighlight(location, duration, "", argb);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text) {
+ sendBlockHighlight(location, duration, text, 0x6400FF00);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text, int argb) {
+ if (this.getHandle().connection == null) return;
+ FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
+ buf.writeBlockPos(io.papermc.paper.util.MCUtil.toBlockPosition(location));
+ buf.writeInt(argb);
+ buf.writeUtf(text);
+ buf.writeInt(duration);
+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.DEBUG_GAME_TEST_ADD_MARKER, buf));
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) {
+ sendBlockHighlight(location, duration, "", color, transparency);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) {
+ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range");
+ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB());
+ }
+
+ @Override
+ public void clearBlockHighlights() {
+ if (this.getHandle().connection == null) return;
+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.DEBUG_GAME_TEST_CLEAR, new FriendlyByteBuf(io.netty.buffer.Unpooled.buffer())));
+ }
+
+ @Override
+ public void sendDeathScreen(net.kyori.adventure.text.Component message) {
+ if (this.getHandle().connection == null) return;
+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket(getEntityId(), io.papermc.paper.adventure.PaperAdventure.asVanilla(message)));
+ }
+ // Purpur end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java
index e4a14cdfeb91a3d32e622d27d612605b1bca08e2..898d934aafd6066df45f02fe3406fa83f79b745c 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java
@@ -28,4 +28,17 @@ public class CraftSnowman extends CraftGolem implements Snowman, com.destroystok
public String toString() {
return "CraftSnowman";
}
+
+ // Purpur start
+ @Override
+ @org.jetbrains.annotations.Nullable
+ public java.util.UUID getSummoner() {
+ return getHandle().getSummoner();
+ }
+
+ @Override
+ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) {
+ getHandle().setSummoner(summoner);
+ }
+ // Purpur end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
index f29e221e5b850516c169c03bfbd2b0885d1a841b..9e343d6e6393db17748fd13d76354464e128001f 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
@@ -216,4 +216,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager {
getHandle().getGossips().gossips.clear();
}
// Paper end
+
+ // Purpur start
+ @Override
+ public boolean isLobotomized() {
+ return getHandle().isLobotomized();
+ }
+ // Purpur end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java
index 5a97c92f9b044d8ab7bd3346ceb464455a09046e..e30d8b80734f04b1fa89e8a3cef666116fd7366c 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java
@@ -99,4 +99,17 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok
this.getHandle().makeInvulnerable();
}
// Paper end
+
+ // Purpur start
+ @Override
+ @org.jetbrains.annotations.Nullable
+ public java.util.UUID getSummoner() {
+ return getHandle().getSummoner();
+ }
+
+ @Override
+ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) {
+ getHandle().setSummoner(summoner);
+ }
+ // Purpur end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java
index e16459c9cfcac790edd6d912750d32c68387cbbc..890938ad866e2c588f3f819230ba121b0b436401 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java
@@ -57,4 +57,16 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf {
public void setInterested(boolean flag) {
this.getHandle().setIsInterested(flag);
}
+
+ // Purpur start
+ @Override
+ public boolean isRabid() {
+ return getHandle().isRabid();
+ }
+
+ @Override
+ public void setRabid(boolean isRabid) {
+ getHandle().setRabid(isRabid);
+ }
+ // Purpur end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index 090b1ee57ddef58ca71469ad860960f66da7d5a2..5641df31686d6203a523b04406c2c85eab2240d2 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -590,6 +590,15 @@ public class CraftEventFactory {
// Paper end
craftServer.getPluginManager().callEvent(event);
+ // Purpur start
+ if (who != null) {
+ switch (action) {
+ case LEFT_CLICK_BLOCK, LEFT_CLICK_AIR -> who.processClick(InteractionHand.MAIN_HAND);
+ case RIGHT_CLICK_BLOCK, RIGHT_CLICK_AIR -> who.processClick(InteractionHand.OFF_HAND);
+ }
+ }
+ // Purpur end
+
return event;
}
@@ -1031,6 +1040,7 @@ public class CraftEventFactory {
damageCause = DamageCause.ENTITY_EXPLOSION;
}
event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions, source.isCritical()); // Paper - add critical damage API
+ damager.processClick(InteractionHand.MAIN_HAND); // Purpur
}
event.setCancelled(cancelled);
@@ -1145,6 +1155,7 @@ public class CraftEventFactory {
} else {
entity.lastDamageCancelled = true; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled
}
+ damager.getHandle().processClick(InteractionHand.MAIN_HAND); // Purpur
return event;
}
@@ -1208,6 +1219,7 @@ public class CraftEventFactory {
EntityDamageEvent event;
if (damager != null) {
event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions, critical); // Paper - add critical damage API
+ damager.processClick(InteractionHand.MAIN_HAND); // Purpur
} else {
event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, modifiers, modifierFunctions);
}
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
index 633e6f4922ccaf59979a22885162f42c65bf628a..15001e7c8eae3a89f9d8669be532d56b70d538ef 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
@@ -181,8 +181,19 @@ public class CraftContainer extends AbstractContainerMenu {
case PLAYER:
case CHEST:
case ENDER_CHEST:
+ // Purpur start
+ this.delegate = new ChestMenu(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? MenuType.GENERIC_9x6 : MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9);
+ break;
case BARREL:
- this.delegate = new ChestMenu(MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9);
+ this.delegate = new ChestMenu(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) {
+ case 6 -> MenuType.GENERIC_9x6;
+ case 5 -> MenuType.GENERIC_9x5;
+ case 4 -> MenuType.GENERIC_9x4;
+ case 2 -> MenuType.GENERIC_9x2;
+ case 1 -> MenuType.GENERIC_9x1;
+ default -> MenuType.GENERIC_9x3;
+ }, windowId, bottom, top, top.getContainerSize() / 9);
+ // Purpur end
break;
case DISPENSER:
case DROPPER:
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
index 471ae4458e7ea7c29d7551b32cec98180fbccd4e..23db63c78e9fcf86cd498b3ed36ca50253c2fe97 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
@@ -83,7 +83,7 @@ public class CraftInventory implements Inventory {
@Override
public void setContents(ItemStack[] items) {
- Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize());
+ // Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize()); // Purpur
for (int i = 0; i < this.getSize(); i++) {
if (i >= items.length) {
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java
index 88d3ca586ff6905f18a8ab9f0e229f440ed44088..27dd4eb4781a3c75772860c11db886e1038cecd2 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java
@@ -9,7 +9,7 @@ import org.bukkit.inventory.AnvilInventory;
public class CraftInventoryAnvil extends CraftResultInventory implements AnvilInventory {
private final Location location;
- private final AnvilMenu container;
+ public final AnvilMenu container; // Purpur - private -> public
public CraftInventoryAnvil(Location location, Container inventory, Container resultInventory, AnvilMenu container) {
super(inventory, resultInventory);
@@ -57,4 +57,26 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn
Preconditions.checkArgument(levels >= 0, "Maximum repair cost must be positive (or 0)");
container.maximumRepairCost = levels;
}
+
+ // Purpur start
+ @Override
+ public boolean canBypassCost() {
+ return container.bypassCost;
+ }
+
+ @Override
+ public void setBypassCost(boolean bypassCost) {
+ container.bypassCost = bypassCost;
+ }
+
+ @Override
+ public boolean canDoUnsafeEnchants() {
+ return container.canDoUnsafeEnchants;
+ }
+
+ @Override
+ public void setDoUnsafeEnchants(boolean canDoUnsafeEnchants) {
+ container.canDoUnsafeEnchants = canDoUnsafeEnchants;
+ }
+ // Purpur end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java
index d94ab2607d1ab657a6b37924ce5ebcbbc3984011..f594596029513426f974ba820ed4702703b667a4 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java
@@ -13,6 +13,7 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import org.bukkit.Color;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.configuration.serialization.DelegateDeserialization;
import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta;
import org.bukkit.craftbukkit.potion.CraftPotionUtil;
@@ -42,6 +43,7 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta {
static final ItemMetaKey POTION_COLOR = new ItemMetaKey("CustomPotionColor", "custom-color");
static final ItemMetaKey ID = new ItemMetaKey("Id", "potion-id");
static final ItemMetaKey DEFAULT_POTION = new ItemMetaKey("Potion", "potion-type");
+ static final ItemMetaKey KEY = new ItemMetaKey("Key", "namespacedkey"); // Purpur - add key
// Having an initial "state" in ItemMeta seems bit dirty but the UNCRAFTABLE potion type
// is treated as the empty form of the meta because it represents an empty potion with no effect
@@ -91,7 +93,13 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta {
boolean ambient = effect.getBoolean(AMBIENT.NBT);
boolean particles = effect.contains(SHOW_PARTICLES.NBT, CraftMagicNumbers.NBT.TAG_BYTE) ? effect.getBoolean(SHOW_PARTICLES.NBT) : true;
boolean icon = effect.contains(SHOW_ICON.NBT, CraftMagicNumbers.NBT.TAG_BYTE) ? effect.getBoolean(SHOW_ICON.NBT) : particles;
- this.customEffects.add(new PotionEffect(type, duration, amp, ambient, particles, icon));
+ // Purpur start
+ NamespacedKey key = null;
+ if (tag.contains(KEY.NBT)) {
+ key = NamespacedKey.fromString(effect.getString(KEY.NBT));
+ }
+ this.customEffects.add(new PotionEffect(type, duration, amp, ambient, particles, icon, key));
+ // Purpur end
}
}
}
@@ -138,6 +146,11 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta {
effectData.putBoolean(AMBIENT.NBT, effect.isAmbient());
effectData.putBoolean(SHOW_PARTICLES.NBT, effect.hasParticles());
effectData.putBoolean(SHOW_ICON.NBT, effect.hasIcon());
+ // Purpur start
+ if (effect.hasKey()) {
+ effectData.putString(KEY.NBT, effect.getKey().toString());
+ }
+ // Purpur end
effectList.add(effectData);
}
}
@@ -199,7 +212,7 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta {
if (index != -1) {
if (overwrite) {
PotionEffect old = this.customEffects.get(index);
- if (old.getAmplifier() == effect.getAmplifier() && old.getDuration() == effect.getDuration() && old.isAmbient() == effect.isAmbient()) {
+ if (old.getAmplifier() == effect.getAmplifier() && old.getDuration() == effect.getDuration() && old.isAmbient() == effect.isAmbient() && old.getKey() == effect.getKey()) { // Purpur - add key
return false;
}
this.customEffects.set(index, effect);
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
index 13d25d118eb4d3ef35a4cdfb9bbde9ed83f6c04b..553ecc9b5631ffc0848a798bb3295f16d1722c1f 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
@@ -30,6 +30,7 @@ public interface CraftRecipe extends Recipe {
} else if (bukkit instanceof RecipeChoice.ExactChoice) {
stack = new Ingredient(((RecipeChoice.ExactChoice) bukkit).getChoices().stream().map((mat) -> new net.minecraft.world.item.crafting.Ingredient.ItemValue(CraftItemStack.asNMSCopy(mat))));
stack.exact = true;
+ stack.predicate = ((RecipeChoice.ExactChoice) bukkit).getPredicate(); // Purpur
} else {
throw new IllegalArgumentException("Unknown recipe stack instance " + bukkit);
}
diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java
index 2677e21d8239bf0361a3bc5c9a50c328e54d70f6..544a79d5da661aff19e2019f7b83a3a49350bb68 100644
--- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java
+++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java
@@ -256,6 +256,7 @@ public final class CraftLegacy {
}
static {
+ if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressInitLegacyMaterialError) // Purpur
LOGGER.warn("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!"); // Paper - doesn't need to be an error
if (MinecraftServer.getServer() != null && MinecraftServer.getServer().isDebugging()) {
new Exception().printStackTrace();
diff --git a/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java
index 9683d7d103af66fffd68c11abc38fb4fd2f99482..18a4de76021ff8b500801ef0121003f8dd62a961 100644
--- a/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java
+++ b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java
@@ -46,4 +46,10 @@ public class CraftMapRenderer extends MapRenderer {
}
}
+ // Purpur - start
+ @Override
+ public boolean isExplorerMap() {
+ return this.worldMap.isExplorerMap;
+ }
+ // Purpur - end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java
index acb69821a99aa69bce6d127e10976089c85be223..c5abd73981c5f4b41605eba0d44e6573dfd2a77a 100644
--- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java
+++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java
@@ -101,7 +101,7 @@ public class CraftPotionUtil {
public static MobEffectInstance fromBukkit(PotionEffect effect) {
MobEffect type = MobEffect.byId(effect.getType().getId());
- return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles());
+ return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.getKey()); // Purpur - add key
}
public static PotionEffect toBukkit(MobEffectInstance effect) {
@@ -110,7 +110,7 @@ public class CraftPotionUtil {
int duration = effect.getDuration();
boolean ambient = effect.isAmbient();
boolean particles = effect.isVisible();
- return new PotionEffect(type, duration, amp, ambient, particles);
+ return new PotionEffect(type, duration, amp, ambient, particles, effect.getKey()); // Purpur - add key
}
public static boolean equals(MobEffect mobEffect, PotionEffectType type) {
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
index d7ce4971d9271dbeff4adb9d852e4e7bdf60bf03..a4567188e2fe3f922bb6aeb71a2845d1a1be536f 100644
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
@@ -501,7 +501,7 @@ public class CraftScheduler implements BukkitScheduler {
this.parsePending();
} else {
// this.debugTail = this.debugTail.setNext(new CraftAsyncDebugger(currentTick + CraftScheduler.RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper
- task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Paper"); // Paper
+ task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Purpur"); // Paper // Purpur
// We don't need to parse pending
// (async tasks must live with race-conditions if they attempt to cancel between these few lines of code)
}
@@ -513,10 +513,10 @@ public class CraftScheduler implements BukkitScheduler {
this.runners.remove(task.getTaskId());
}
}
- MinecraftTimings.bukkitSchedulerFinishTimer.startTiming(); // Paper
+ //MinecraftTimings.bukkitSchedulerFinishTimer.startTiming(); // Paper // Purpur
this.pending.addAll(temp);
temp.clear();
- MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming(); // Paper
+ //MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming(); // Paper // Purpur
//this.debugHead = this.debugHead.getNextHead(currentTick); // Paper
}
@@ -559,7 +559,7 @@ public class CraftScheduler implements BukkitScheduler {
}
void parsePending() { // Paper
- if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); // Paper
+ //if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); // Paper // Purpur
CraftTask head = this.head;
CraftTask task = head.getNext();
CraftTask lastTask = head;
@@ -578,7 +578,7 @@ public class CraftScheduler implements BukkitScheduler {
task.setNext(null);
}
this.head = lastTask;
- if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); // Paper
+ //if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); // Paper // Purpur
}
private boolean isReady(final int currentTick) {
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
index 3f45bab0e9f7b3697e6d9d1092a1e6e579f7066f..4f1cf281c4bf68c37982d390da8779dea78dab18 100644
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java
@@ -96,13 +96,13 @@ public class CraftTask implements BukkitTask, Runnable { // Spigot
@Override
public void run() {
- try (Timing ignored = timings.startTiming()) { // Paper
+ //try (Timing ignored = timings.startTiming()) { // Paper // Purpur
if (this.rTask != null) {
this.rTask.run();
} else {
this.cTask.accept(this);
}
- } // Paper
+ //} // Paper // Purpur
}
long getCreatedAt() {
diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
index 500f2eb0df5a07637cd278c263e95592b0037eb6..895431584c476067790f1f131699d49eac3f4b8f 100644
--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java
@@ -115,7 +115,7 @@ public final class CraftScoreboardManager implements ScoreboardManager {
public void getScoreboardScores(ObjectiveCriteria criteria, String name, Consumer<Score> consumer) {
// Paper start - add timings for scoreboard search
// plugins leaking scoreboards will make this very expensive, let server owners debug it easily
- co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.startTimingIfSync();
+ //co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.startTimingIfSync(); // Purpur
try {
// Paper end - add timings for scoreboard search
for (CraftScoreboard scoreboard : this.scoreboards) {
@@ -123,7 +123,7 @@ public final class CraftScoreboardManager implements ScoreboardManager {
board.forAllObjectives(criteria, name, (score) -> consumer.accept(score));
}
} finally { // Paper start - add timings for scoreboard search
- co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.stopTimingIfSync();
+ //co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.stopTimingIfSync(); // Purpur
}
// Paper end - add timings for scoreboard search
}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index 2b4581f92543c11f31bcc1417e90d7f90b2aea20..ffca5970a6259b024c9aa935e22cf72ed8cd8e9f 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -470,7 +470,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
@Override
public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() {
- return new gg.pufferfish.pufferfish.PufferfishVersionFetcher(); // Pufferfish
+ return new com.destroystokyo.paper.PaperVersionFetcher(); // Pufferfish // Purpur
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
index 80553face9c70c2a3d897681e7761df85b22d464..99597258e8e88cd9e2c901c4ac3ff7faeeabee2b 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
@@ -11,7 +11,7 @@ public final class Versioning {
public static String getBukkitVersion() {
String result = "Unknown-Version";
- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/gg.pufferfish.pufferfish/pufferfish-api/pom.properties"); // Pufferfish
+ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.purpurmc.purpur/purpur-api/pom.properties"); // Pufferfish // Purpur
Properties properties = new Properties();
if (stream != null) {
diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java
index 2959f713ce75a1df9c6c7cf5e021690cfcb6e1e7..3fa9539cfb2c35beeba6d44fa05cee971444b7d0 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java
@@ -23,7 +23,15 @@ public final class CommandPermissions {
DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "kick", "Allows the user to kick players", PermissionDefault.OP, commands);
DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "stop", "Allows the user to stop the server", PermissionDefault.OP, commands);
DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "list", "Allows the user to list all online players", PermissionDefault.OP, commands);
- DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "gamemode", "Allows the user to change the gamemode of another player", PermissionDefault.OP, commands);
+ // Purpur start
+ Permission gamemodeVanilla = DefaultPermissions.registerPermission(PREFIX + "gamemode", "Allows the user to change the gamemode", PermissionDefault.OP, commands);
+ for (net.minecraft.world.level.GameType gametype : net.minecraft.world.level.GameType.values()) {
+ Permission gamemodeSelf = DefaultPermissions.registerPermission(PREFIX + "gamemode." + gametype.getName(), "Allows the user to set " + gametype.getName() + " gamemode for self", PermissionDefault.OP);
+ Permission gamemodeOther = DefaultPermissions.registerPermission(PREFIX + "gamemode." + gametype.getName() + ".other", "Allows the user to set " + gametype.getName() + " gamemode for other players", PermissionDefault.OP);
+ gamemodeSelf.addParent(gamemodeOther, true);
+ gamemodeVanilla.addParent(gamemodeSelf, true);
+ }
+ // Purpur end
DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "experience", "Allows the user to give themselves or others arbitrary values of experience", PermissionDefault.OP, commands); // Paper - wrong permission; redirects are de-redirected and the root literal name is used, so xp -> experience
DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "defaultgamemode", "Allows the user to change the default gamemode of the server", PermissionDefault.OP, commands);
DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "seed", "Allows the user to view the seed of the world", PermissionDefault.OP, commands);
diff --git a/src/main/java/org/purpurmc/purpur/PurpurConfig.java b/src/main/java/org/purpurmc/purpur/PurpurConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..3633574e112f217b412217dd243a631dc4e9c40c
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java
@@ -0,0 +1,655 @@
+package org.purpurmc.purpur;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+import com.mojang.datafixers.util.Pair;
+import net.kyori.adventure.bossbar.BossBar;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.effect.MobEffect;
+import net.minecraft.world.effect.MobEffectInstance;
+import net.minecraft.world.entity.EntityDimensions;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.food.FoodProperties;
+import net.minecraft.world.food.Foods;
+import net.minecraft.world.item.enchantment.Enchantment;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import org.bukkit.Bukkit;
+import org.bukkit.command.Command;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.InvalidConfigurationException;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.purpurmc.purpur.command.PurpurCommand;
+import org.purpurmc.purpur.task.TPSBarTask;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+
+@SuppressWarnings("unused")
+public class PurpurConfig {
+ private static final String HEADER = "This is the main configuration file for Purpur.\n"
+ + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n"
+ + "with caution, and make sure you know what each option does before configuring.\n"
+ + "\n"
+ + "If you need help with the configuration or have any questions related to Purpur,\n"
+ + "join us in our Discord guild.\n"
+ + "\n"
+ + "Website: https://purpurmc.org \n"
+ + "Docs: https://purpurmc.org/docs \n";
+ private static File CONFIG_FILE;
+ public static YamlConfiguration config;
+
+ private static Map<String, Command> commands;
+
+ public static int version;
+ static boolean verbose;
+
+ public static void init(File configFile) {
+ CONFIG_FILE = configFile;
+ config = new YamlConfiguration();
+ try {
+ config.load(CONFIG_FILE);
+ } catch (IOException ignore) {
+ } catch (InvalidConfigurationException ex) {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not load purpur.yml, please correct your syntax errors", ex);
+ throw Throwables.propagate(ex);
+ }
+ config.options().header(HEADER);
+ config.options().copyDefaults(true);
+ verbose = getBoolean("verbose", false);
+
+ commands = new HashMap<>();
+ commands.put("purpur", new PurpurCommand("purpur"));
+
+ version = getInt("config-version", 33);
+ set("config-version", 33);
+
+ readConfig(PurpurConfig.class, null);
+
+ Blocks.rebuildCache();
+ }
+
+ protected static void log(String s) {
+ if (verbose) {
+ log(Level.INFO, s);
+ }
+ }
+
+ protected static void log(Level level, String s) {
+ Bukkit.getLogger().log(level, s);
+ }
+
+ public static void registerCommands() {
+ for (Map.Entry<String, Command> 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 <T> List getList(String path, T def) {
+ config.addDefault(path, def);
+ return config.getList(path, config.getList(path));
+ }
+
+ static Map<String, Object> getMap(String path, Map<String, Object> def) {
+ if (def != null && config.getConfigurationSection(path) == null) {
+ config.addDefault(path, def);
+ return def;
+ }
+ return toMap(config.getConfigurationSection(path));
+ }
+
+ private static Map<String, Object> toMap(ConfigurationSection section) {
+ ImmutableMap.Builder<String, Object> 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 = "<red>You cannot mount that mob";
+ public static String afkBroadcastAway = "<yellow><italic>%s is now AFK";
+ public static String afkBroadcastBack = "<yellow><italic>%s is no longer AFK";
+ public static boolean afkBroadcastUseDisplayName = false;
+ public static String afkTabListPrefix = "[AFK] ";
+ public static String afkTabListSuffix = "";
+ public static String creditsCommandOutput = "<green>%s has been shown the end credits";
+ public static String demoCommandOutput = "<green>%s has been shown the demo screen";
+ public static String pingCommandOutput = "<green>%s's ping is %sms";
+ public static String ramCommandOutput = "<green>Ram Usage: <used>/<xmx> (<percent>)";
+ public static String rambarCommandOutput = "<green>Rambar toggled <onoff> for <target>";
+ public static String tpsbarCommandOutput = "<green>Tpsbar toggled <onoff> for <target>";
+ public static String dontRunWithScissors = "<red><italic>Don't run with scissors!";
+ public static String uptimeCommandOutput = "<green>Server uptime is <uptime>";
+ 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 = "<player> slipped and fell on their shears";
+ public static String deathMsgStonecutter = "<player> has sawed themself in half";
+ private static void deathMessages() {
+ deathMsgRunWithScissors = getString("settings.messages.death-message.run-with-scissors", deathMsgRunWithScissors);
+ deathMsgStonecutter = getString("settings.messages.death-message.stonecutter", deathMsgStonecutter);
+ }
+
+ public static boolean advancementOnlyBroadcastToAffectedPlayer = false;
+ public static boolean deathMessageOnlyBroadcastToAffectedPlayer = false;
+ private static void broadcastSettings() {
+ if (version < 13) {
+ boolean oldValue = getBoolean("settings.advancement.only-broadcast-to-affected-player", false);
+ set("settings.broadcasts.advancement.only-broadcast-to-affected-player", oldValue);
+ set("settings.advancement.only-broadcast-to-affected-player", null);
+ }
+ advancementOnlyBroadcastToAffectedPlayer = getBoolean("settings.broadcasts.advancement.only-broadcast-to-affected-player", advancementOnlyBroadcastToAffectedPlayer);
+ deathMessageOnlyBroadcastToAffectedPlayer = getBoolean("settings.broadcasts.death.only-broadcast-to-affected-player", deathMessageOnlyBroadcastToAffectedPlayer);
+ }
+
+ public static String serverModName = "Purpur";
+ private static void serverModName() {
+ serverModName = getString("settings.server-mod-name", serverModName);
+ }
+
+ public static double laggingThreshold = 19.0D;
+ private static void tickLoopSettings() {
+ laggingThreshold = getDouble("settings.lagging-threshold", laggingThreshold);
+ }
+
+ public static boolean useAlternateKeepAlive = false;
+ private static void useAlternateKeepAlive() {
+ useAlternateKeepAlive = getBoolean("settings.use-alternate-keepalive", useAlternateKeepAlive);
+ }
+
+ public static boolean disableGiveCommandDrops = false;
+ private static void disableGiveCommandDrops() {
+ disableGiveCommandDrops = getBoolean("settings.disable-give-dropping", disableGiveCommandDrops);
+ }
+
+ public static String commandRamBarTitle = "<gray>Ram<yellow>:</yellow> <used>/<xmx> (<percent>)";
+ 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 = "<gradient:#55ff55:#00aa00><text></gradient>";
+ public static String commandRamBarTextColorMedium = "<gradient:#ffff55:#ffaa00><text></gradient>";
+ public static String commandRamBarTextColorLow = "<gradient:#ff5555:#aa0000><text></gradient>";
+ public static int commandRamBarTickInterval = 20;
+ public static String commandTPSBarTitle = "<gray>TPS<yellow>:</yellow> <tps> MSPT<yellow>:</yellow> <mspt> Ping<yellow>:</yellow> <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 = "<gradient:#55ff55:#00aa00><text></gradient>";
+ public static String commandTPSBarTextColorMedium = "<gradient:#ffff55:#ffaa00><text></gradient>";
+ public static String commandTPSBarTextColorLow = "<gradient:#ff5555:#aa0000><text></gradient>";
+ 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 = "<days><hours><minutes><seconds>";
+ 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<Enchantment> grindstoneIgnoredEnchants = new HashSet<>();
+ public static boolean grindstoneRemoveAttributes = false;
+ public static boolean grindstoneRemoveDisplay = false;
+ public static int caveVinesMaxGrowthAge = 25;
+ public static int kelpMaxGrowthAge = 25;
+ public static int twistingVinesMaxGrowthAge = 25;
+ public static int weepingVinesMaxGrowthAge = 25;
+ private static void blockSettings() {
+ if (version < 3) {
+ boolean oldValue = getBoolean("settings.barrel.packed-barrels", true);
+ set("settings.blocks.barrel.six-rows", oldValue);
+ set("settings.packed-barrels", null);
+ oldValue = getBoolean("settings.large-ender-chests", true);
+ set("settings.blocks.ender_chest.six-rows", oldValue);
+ set("settings.large-ender-chests", null);
+ }
+ if (version < 20) {
+ boolean oldValue = getBoolean("settings.blocks.barrel.six-rows", false);
+ set("settings.blocks.barrel.rows", oldValue ? 6 : 3);
+ set("settings.blocks.barrel.six-rows", null);
+ }
+ barrelRows = getInt("settings.blocks.barrel.rows", barrelRows);
+ if (barrelRows < 1 || barrelRows > 6) {
+ Bukkit.getLogger().severe("settings.blocks.barrel.rows must be 1-6, resetting to default");
+ barrelRows = 3;
+ }
+ org.bukkit.event.inventory.InventoryType.BARREL.setDefaultSize(switch (barrelRows) {
+ case 6 -> 54;
+ case 5 -> 45;
+ case 4 -> 36;
+ case 2 -> 18;
+ case 1 -> 9;
+ default -> 27;
+ });
+ enderChestSixRows = getBoolean("settings.blocks.ender_chest.six-rows", enderChestSixRows);
+ org.bukkit.event.inventory.InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27);
+ enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows);
+ cryingObsidianValidForPortalFrame = getBoolean("settings.blocks.crying_obsidian.valid-for-portal-frame", cryingObsidianValidForPortalFrame);
+ beeInsideBeeHive = getInt("settings.blocks.beehive.max-bees-inside", beeInsideBeeHive);
+ anvilCumulativeCost = getBoolean("settings.blocks.anvil.cumulative-cost", anvilCumulativeCost);
+ lightningRodRange = getInt("settings.blocks.lightning_rod.range", lightningRodRange);
+ ArrayList<String> defaultCurses = new ArrayList<>(){{
+ add("minecraft:binding_curse");
+ add("minecraft:vanishing_curse");
+ }};
+ if (version < 24 && !getBoolean("settings.blocks.grindstone.ignore-curses", true)) {
+ defaultCurses.clear();
+ }
+ getList("settings.blocks.grindstone.ignored-enchants", defaultCurses).forEach(key -> {
+ Enchantment enchantment = BuiltInRegistries.ENCHANTMENT.get(new ResourceLocation(key.toString()));
+ grindstoneIgnoredEnchants.add(enchantment);
+ });
+ grindstoneRemoveAttributes = getBoolean("settings.blocks.grindstone.remove-attributes", grindstoneRemoveAttributes);
+ grindstoneRemoveDisplay = getBoolean("settings.blocks.grindstone.remove-name-and-lore", grindstoneRemoveDisplay);
+ caveVinesMaxGrowthAge = getInt("settings.blocks.cave_vines.max-growth-age", caveVinesMaxGrowthAge);
+ if (caveVinesMaxGrowthAge > 25) {
+ caveVinesMaxGrowthAge = 25;
+ log(Level.WARNING, "blocks.cave_vines.max-growth-age is set to above maximum allowed value of 25");
+ log(Level.WARNING, "Using value of 25 to prevent issues");
+ }
+ kelpMaxGrowthAge = getInt("settings.blocks.kelp.max-growth-age", kelpMaxGrowthAge);
+ if (kelpMaxGrowthAge > 25) {
+ kelpMaxGrowthAge = 25;
+ log(Level.WARNING, "blocks.kelp.max-growth-age is set to above maximum allowed value of 25");
+ log(Level.WARNING, "Using value of 25 to prevent issues");
+ }
+ twistingVinesMaxGrowthAge = getInt("settings.blocks.twisting_vines.max-growth-age", twistingVinesMaxGrowthAge);
+ if (twistingVinesMaxGrowthAge > 25) {
+ twistingVinesMaxGrowthAge = 25;
+ log(Level.WARNING, "blocks.twisting_vines.max-growth-age is set to above maximum allowed value of 25");
+ log(Level.WARNING, "Using value of 25 to prevent issues");
+ }
+ weepingVinesMaxGrowthAge = getInt("settings.blocks.weeping_vines.max-growth-age", weepingVinesMaxGrowthAge);
+ if (weepingVinesMaxGrowthAge > 25) {
+ weepingVinesMaxGrowthAge = 25;
+ log(Level.WARNING, "blocks.weeping_vines.max-growth-age is set to above maximum allowed value of 25");
+ log(Level.WARNING, "Using value of 25 to prevent issues");
+ }
+ }
+
+ public static boolean allowInfinityMending = false;
+ public static boolean allowCrossbowInfinity = false;
+ public static boolean allowShearsLooting = false;
+ public static boolean allowUnsafeEnchants = false;
+ public static boolean allowInapplicableEnchants = true;
+ public static boolean allowIncompatibleEnchants = true;
+ public static boolean allowHigherEnchantsLevels = true;
+ public static boolean allowUnsafeEnchantCommand = false;
+ public static boolean replaceIncompatibleEnchants = false;
+ public static boolean clampEnchantLevels = true;
+ private static void enchantmentSettings() {
+ if (version < 5) {
+ boolean oldValue = getBoolean("settings.enchantment.allow-infinite-and-mending-together", false);
+ set("settings.enchantment.allow-infinity-and-mending-together", oldValue);
+ set("settings.enchantment.allow-infinite-and-mending-together", null);
+ }
+ if (version < 30) {
+ boolean oldValue = getBoolean("settings.enchantment.allow-unsafe-enchants", false);
+ set("settings.enchantment.anvil.allow-unsafe-enchants", oldValue);
+ set("settings.enchantment.anvil.allow-inapplicable-enchants", true);
+ set("settings.enchantment.anvil.allow-incompatible-enchants", true);
+ set("settings.enchantment.anvil.allow-higher-enchants-levels", true);
+ set("settings.enchantment.allow-unsafe-enchants", null);
+ }
+ allowInfinityMending = getBoolean("settings.enchantment.allow-infinity-and-mending-together", allowInfinityMending);
+ allowCrossbowInfinity = getBoolean("settings.enchantment.allow-infinity-on-crossbow", allowCrossbowInfinity);
+ allowShearsLooting = getBoolean("settings.enchantment.allow-looting-on-shears", allowShearsLooting);
+ allowUnsafeEnchants = getBoolean("settings.enchantment.anvil.allow-unsafe-enchants", allowUnsafeEnchants);
+ allowInapplicableEnchants = getBoolean("settings.enchantment.anvil.allow-inapplicable-enchants", allowInapplicableEnchants);
+ allowIncompatibleEnchants = getBoolean("settings.enchantment.anvil.allow-incompatible-enchants", allowIncompatibleEnchants);
+ allowHigherEnchantsLevels = getBoolean("settings.enchantment.anvil.allow-higher-enchants-levels", allowHigherEnchantsLevels);
+ allowUnsafeEnchantCommand = getBoolean("settings.enchantment.allow-unsafe-enchant-command", allowUnsafeEnchants); // allowUnsafeEnchants as default for backwards compatability
+ replaceIncompatibleEnchants = getBoolean("settings.enchantment.anvil.replace-incompatible-enchants", replaceIncompatibleEnchants);
+ clampEnchantLevels = getBoolean("settings.enchantment.clamp-levels", clampEnchantLevels);
+ }
+
+ public static boolean endermanShortHeight = false;
+ private static void entitySettings() {
+ endermanShortHeight = getBoolean("settings.entity.enderman.short-height", endermanShortHeight);
+ if (endermanShortHeight) EntityType.ENDERMAN.setDimensions(EntityDimensions.scalable(0.6F, 1.9F));
+ }
+
+ public static boolean allowWaterPlacementInTheEnd = true;
+ private static void allowWaterPlacementInEnd() {
+ allowWaterPlacementInTheEnd = getBoolean("settings.allow-water-placement-in-the-end", allowWaterPlacementInTheEnd);
+ }
+
+ public static boolean loggerSuppressInitLegacyMaterialError = false;
+ public static boolean loggerSuppressIgnoredAdvancementWarnings = false;
+ public static boolean loggerSuppressUnrecognizedRecipeErrors = false;
+ public static boolean loggerSuppressSetBlockFarChunk = false;
+ public static boolean loggerSuppressLibraryLoader = false;
+ private static void loggerSettings() {
+ loggerSuppressInitLegacyMaterialError = getBoolean("settings.logger.suppress-init-legacy-material-errors", loggerSuppressInitLegacyMaterialError);
+ loggerSuppressIgnoredAdvancementWarnings = getBoolean("settings.logger.suppress-ignored-advancement-warnings", loggerSuppressIgnoredAdvancementWarnings);
+ loggerSuppressUnrecognizedRecipeErrors = getBoolean("settings.logger.suppress-unrecognized-recipe-errors", loggerSuppressUnrecognizedRecipeErrors);
+ loggerSuppressSetBlockFarChunk = getBoolean("settings.logger.suppress-setblock-in-far-chunk-errors", loggerSuppressSetBlockFarChunk);
+ loggerSuppressLibraryLoader = getBoolean("settings.logger.suppress-library-loader", loggerSuppressLibraryLoader);
+ org.bukkit.plugin.java.JavaPluginLoader.SuppressLibraryLoaderLogger = loggerSuppressLibraryLoader;
+ }
+
+ public static boolean tpsCatchup = true;
+ private static void tpsCatchup() {
+ tpsCatchup = getBoolean("settings.tps-catchup", tpsCatchup);
+ }
+
+ public static boolean useUPnP = false;
+ public static boolean maxJoinsPerSecond = false;
+ public static boolean kickForOutOfOrderChat = true;
+ private static void networkSettings() {
+ useUPnP = getBoolean("settings.network.upnp-port-forwarding", useUPnP);
+ maxJoinsPerSecond = getBoolean("settings.network.max-joins-per-second", maxJoinsPerSecond);
+ kickForOutOfOrderChat = getBoolean("settings.network.kick-for-out-of-order-chat", kickForOutOfOrderChat);
+ }
+
+ public static java.util.regex.Pattern usernameValidCharactersPattern;
+ private static void usernameValidationSettings() {
+ String defaultPattern = "^[a-zA-Z0-9_.]*$";
+ String setPattern = getString("settings.username-valid-characters", defaultPattern);
+ usernameValidCharactersPattern = java.util.regex.Pattern.compile(setPattern == null || setPattern.isBlank() ? defaultPattern : setPattern);
+ }
+
+ private static void foodSettings() {
+ ConfigurationSection properties = config.getConfigurationSection("settings.food-properties");
+ if (properties == null) {
+ config.addDefault("settings.food-properties", new HashMap<>());
+ return;
+ }
+
+ Map<MobEffect, Map<String, Object>> effectDefaults = new HashMap<>();
+ Map<String, Object> EFFECT_DEFAULT = Map.of(
+ "chance", 0.0F,
+ "duration", 0,
+ "amplifier", 0,
+ "ambient", false,
+ "visible", true,
+ "show-icon", true
+ );
+
+ properties.getKeys(false).forEach(foodKey -> {
+ FoodProperties food = Foods.ALL_PROPERTIES.get(foodKey);
+ if (food == null) {
+ PurpurConfig.log(Level.SEVERE, "Invalid food property: " + foodKey);
+ return;
+ }
+ FoodProperties foodDefaults = Foods.DEFAULT_PROPERTIES.get(foodKey);
+ food.setNutrition(properties.getInt(foodKey + ".nutrition", foodDefaults.getNutrition()));
+ food.setSaturationModifier((float) properties.getDouble(foodKey + ".saturation-modifier", foodDefaults.getSaturationModifier()));
+ food.setIsMeat(properties.getBoolean(foodKey + ".is-meat", foodDefaults.isMeat()));
+ food.setCanAlwaysEat(properties.getBoolean(foodKey + ".can-always-eat", foodDefaults.canAlwaysEat()));
+ food.setFastFood(properties.getBoolean(foodKey + ".fast-food", foodDefaults.isFastFood()));
+ ConfigurationSection effects = properties.getConfigurationSection(foodKey + ".effects");
+ if (effects != null) {
+ effectDefaults.clear();
+ foodDefaults.getEffects().forEach(pair -> {
+ MobEffectInstance effect = pair.getFirst();
+ effectDefaults.put(effect.getEffect(), Map.of(
+ "chance", pair.getSecond(),
+ "duration", effect.getDuration(),
+ "amplifier", effect.getAmplifier(),
+ "ambient", effect.isAmbient(),
+ "visible", effect.isVisible(),
+ "show-icon", effect.showIcon()
+ ));
+ });
+ effects.getKeys(false).forEach(effectKey -> {
+ MobEffect effect = BuiltInRegistries.MOB_EFFECT.get(new ResourceLocation(effectKey));
+ if (effect == null) {
+ PurpurConfig.log(Level.SEVERE, "Invalid food property effect for " + foodKey + ": " + effectKey);
+ return;
+ }
+
+ Map<String, Object> effectDefault = effectDefaults.get(effect);
+ if (effectDefault == null) {
+ effectDefault = EFFECT_DEFAULT;
+ }
+
+ food.getEffects().removeIf(pair -> pair.getFirst().getEffect() == effect);
+ float chance = (float) effects.getDouble(effectKey + ".chance", ((Float) effectDefault.get("chance")).doubleValue());
+ int duration = effects.getInt(effectKey + ".duration", (int) effectDefault.get("duration"));
+ if (chance <= 0.0F || duration < 0) {
+ return;
+ }
+ int amplifier = effects.getInt(effectKey + ".amplifier", (int) effectDefault.get("amplifier"));
+ boolean ambient = effects.getBoolean(effectKey + ".ambient", (boolean) effectDefault.get("ambient"));
+ boolean visible = effects.getBoolean(effectKey + ".visible", (boolean) effectDefault.get("visible"));
+ boolean showIcon = effects.getBoolean(effectKey + ".show-icon", (boolean) effectDefault.get("show-icon"));
+ food.getEffects().add(Pair.of(new MobEffectInstance(effect, duration, amplifier, ambient, visible, showIcon), chance));
+ });
+ }
+ });
+ }
+
+ public static boolean fixNetworkSerializedItemsInCreative = false;
+ private static void fixNetworkSerializedCreativeItems() {
+ fixNetworkSerializedItemsInCreative = getBoolean("settings.fix-network-serialized-items-in-creative", fixNetworkSerializedItemsInCreative);
+ }
+
+ public static boolean fixProjectileLootingTransfer = false;
+ private static void fixProjectileLootingTransfer() {
+ fixProjectileLootingTransfer = getBoolean("settings.fix-projectile-looting-transfer", fixProjectileLootingTransfer);
+ }
+
+ public static boolean clampAttributes = true;
+ private static void clampAttributes() {
+ clampAttributes = getBoolean("settings.clamp-attributes", clampAttributes);
+ }
+
+ public static boolean limitArmor = true;
+ private static void limitArmor() {
+ limitArmor = getBoolean("settings.limit-armor", limitArmor);
+ }
+
+ private static void blastResistanceSettings() {
+ getMap("settings.blast-resistance-overrides", Collections.emptyMap()).forEach((blockId, value) -> {
+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId));
+ if (block == Blocks.AIR) {
+ log(Level.SEVERE, "Invalid block for `settings.blast-resistance-overrides`: " + blockId);
+ return;
+ }
+ if (!(value instanceof Number blastResistance)) {
+ log(Level.SEVERE, "Invalid blast resistance for `settings.blast-resistance-overrides." + blockId + "`: " + value);
+ return;
+ }
+ block.explosionResistance = blastResistance.floatValue();
+ });
+ }
+ private static void blockFallMultiplierSettings() {
+ getMap("settings.block-fall-multipliers", Map.ofEntries(
+ Map.entry("minecraft:hay_block", Map.of("damage", 0.2F)),
+ Map.entry("minecraft:white_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:light_gray_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:gray_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:black_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:brown_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:pink_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:red_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:orange_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:yellow_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:green_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:lime_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:cyan_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:light_blue_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:blue_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:purple_bed", Map.of("distance", 0.5F)),
+ Map.entry("minecraft:magenta_bed", Map.of("distance", 0.5F))
+ )).forEach((blockId, value) -> {
+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId));
+ if (block == Blocks.AIR) {
+ log(Level.SEVERE, "Invalid block for `settings.block-fall-multipliers`: " + blockId);
+ return;
+ }
+ if (!(value instanceof Map<?, ?> map)) {
+ log(Level.SEVERE, "Invalid fall multiplier for `settings.block-fall-multipliers." + blockId + "`: " + value
+ + ", expected a map with keys `damage` and `distance` to floats.");
+ return;
+ }
+ Object rawFallDamageMultiplier = map.get("damage");
+ if (rawFallDamageMultiplier == null) rawFallDamageMultiplier = 1F;
+ if (!(rawFallDamageMultiplier instanceof Number fallDamageMultiplier)) {
+ log(Level.SEVERE, "Invalid multiplier for `settings.block-fall-multipliers." + blockId + ".damage`: " + map.get("damage"));
+ return;
+ }
+ Object rawFallDistanceMultiplier = map.get("distance");
+ if (rawFallDistanceMultiplier == null) rawFallDistanceMultiplier = 1F;
+ if (!(rawFallDistanceMultiplier instanceof Number fallDistanceMultiplier)) {
+ log(Level.SEVERE, "Invalid multiplier for `settings.block-fall-multipliers." + blockId + ".distance`: " + map.get("distance"));
+ return;
+ }
+ block.fallDamageMultiplier = fallDamageMultiplier.floatValue();
+ block.fallDistanceMultiplier = fallDistanceMultiplier.floatValue();
+ });
+ }
+
+ public static boolean playerDeathsAlwaysShowItem = false;
+ private static void playerDeathsAlwaysShowItem() {
+ playerDeathsAlwaysShowItem = getBoolean("settings.player-deaths-always-show-item", playerDeathsAlwaysShowItem);
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..078102e636803f38facc049952813ff2f8b63594
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java
@@ -0,0 +1,3239 @@
+package org.purpurmc.purpur;
+
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.item.DyeColor;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.properties.Tilt;
+import org.purpurmc.purpur.tool.Flattenable;
+import org.purpurmc.purpur.tool.Strippable;
+import org.purpurmc.purpur.tool.Tillable;
+import org.purpurmc.purpur.tool.Waxable;
+import org.purpurmc.purpur.tool.Weatherable;
+import org.apache.commons.lang.BooleanUtils;
+import org.bukkit.ChatColor;
+import org.bukkit.World;
+import org.bukkit.configuration.ConfigurationSection;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.logging.Level;
+import static org.purpurmc.purpur.PurpurConfig.log;
+
+@SuppressWarnings("unused")
+public class PurpurWorldConfig {
+
+ private final String worldName;
+ private final World.Environment environment;
+
+ public PurpurWorldConfig(String worldName, World.Environment environment) {
+ this.worldName = worldName;
+ this.environment = environment;
+ init();
+ }
+
+ public void init() {
+ log("-------- World Settings For [" + worldName + "] --------");
+ PurpurConfig.readConfig(PurpurWorldConfig.class, this);
+ }
+
+ private void set(String path, Object val) {
+ PurpurConfig.config.addDefault("world-settings.default." + path, val);
+ PurpurConfig.config.set("world-settings.default." + path, val);
+ if (PurpurConfig.config.get("world-settings." + worldName + "." + path) != null) {
+ PurpurConfig.config.addDefault("world-settings." + worldName + "." + path, val);
+ PurpurConfig.config.set("world-settings." + worldName + "." + path, val);
+ }
+ }
+
+ private ConfigurationSection getConfigurationSection(String path) {
+ ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + "." + path);
+ return section != null ? section : PurpurConfig.config.getConfigurationSection("world-settings.default." + path);
+ }
+
+ private String getString(String path, String def) {
+ PurpurConfig.config.addDefault("world-settings.default." + path, def);
+ return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path));
+ }
+
+ private boolean getBoolean(String path, boolean def) {
+ PurpurConfig.config.addDefault("world-settings.default." + path, def);
+ return PurpurConfig.config.getBoolean("world-settings." + worldName + "." + path, PurpurConfig.config.getBoolean("world-settings.default." + path));
+ }
+
+ private boolean getBoolean(String path, Predicate<Boolean> 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 <T> 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<String, Object> getMap(String path, Map<String, Object> def) {
+ final Map<String, Object> fallback = PurpurConfig.getMap("world-settings.default." + path, def);
+ final Map<String, Object> value = PurpurConfig.getMap("world-settings." + worldName + "." + path, null);
+ return value.isEmpty() ? fallback : value;
+ }
+
+ public float armorstandStepHeight = 0.0F;
+ public boolean armorstandSetNameVisible = true;
+ public boolean armorstandFixNametags = false;
+ public boolean armorstandMovement = true;
+ public boolean armorstandWaterMovement = true;
+ public boolean armorstandWaterFence = true;
+ public boolean armorstandPlaceWithArms = false;
+ private void armorstandSettings() {
+ armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight);
+ armorstandSetNameVisible = getBoolean("gameplay-mechanics.armorstand.set-name-visible-when-placing-with-custom-name", armorstandSetNameVisible);
+ armorstandFixNametags = getBoolean("gameplay-mechanics.armorstand.fix-nametags", armorstandFixNametags);
+ armorstandMovement = getBoolean("gameplay-mechanics.armorstand.can-movement-tick", armorstandMovement);
+ armorstandWaterMovement = getBoolean("gameplay-mechanics.armorstand.can-move-in-water", armorstandWaterMovement);
+ armorstandWaterFence = getBoolean("gameplay-mechanics.armorstand.can-move-in-water-over-fence", armorstandWaterFence);
+ armorstandPlaceWithArms = getBoolean("gameplay-mechanics.armorstand.place-with-arms-visible", armorstandPlaceWithArms);
+ }
+
+ public boolean arrowMovementResetsDespawnCounter = true;
+ private void arrowSettings() {
+ arrowMovementResetsDespawnCounter = getBoolean("gameplay-mechanics.arrow.movement-resets-despawn-counter", arrowMovementResetsDespawnCounter);
+ }
+
+ public boolean useBetterMending = false;
+ public double mendingMultiplier = 1.0;
+ public boolean alwaysTameInCreative = false;
+ public boolean boatEjectPlayersOnLand = false;
+ public boolean boatsDoFallDamage = false;
+ public boolean disableDropsOnCrammingDeath = false;
+ public boolean entitiesCanUsePortals = true;
+ public boolean entitiesPickUpLootBypassMobGriefing = false;
+ public boolean fireballsBypassMobGriefing = false;
+ public boolean imposeTeleportRestrictionsOnGateways = false;
+ public boolean milkCuresBadOmen = true;
+ public boolean milkClearsBeneficialEffects = true;
+ public boolean noteBlockIgnoreAbove = false;
+ public boolean persistentDroppableEntityDisplayNames = true;
+ public boolean persistentTileEntityDisplayNames = false;
+ public boolean projectilesBypassMobGriefing = false;
+ public boolean tickFluids = true;
+ public double mobsBlindnessMultiplier = 1;
+ public double tridentLoyaltyVoidReturnHeight = 0.0D;
+ public double voidDamageHeight = -64.0D;
+ public double voidDamageDealt = 4.0D;
+ public int raidCooldownSeconds = 0;
+ public int animalBreedingCooldownSeconds = 0;
+ public boolean mobsIgnoreRails = false;
+ public boolean rainStopsAfterSleep = true;
+ public boolean thunderStopsAfterSleep = true;
+ public int mobLastHurtByPlayerTime = 100;
+ private void miscGameplayMechanicsSettings() {
+ useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending);
+ mendingMultiplier = getDouble("gameplay-mechanics.mending-multiplier", mendingMultiplier);
+ alwaysTameInCreative = getBoolean("gameplay-mechanics.always-tame-in-creative", alwaysTameInCreative);
+ boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand);
+ boatsDoFallDamage = getBoolean("gameplay-mechanics.boat.do-fall-damage", boatsDoFallDamage);
+ disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath);
+ entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals);
+ entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing);
+ fireballsBypassMobGriefing = getBoolean("gameplay-mechanics.fireballs-bypass-mob-griefing", fireballsBypassMobGriefing);
+ imposeTeleportRestrictionsOnGateways = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-gateways", imposeTeleportRestrictionsOnGateways);
+ milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen);
+ milkClearsBeneficialEffects = getBoolean("gameplay-mechanics.milk-clears-beneficial-effects", milkClearsBeneficialEffects);
+ noteBlockIgnoreAbove = getBoolean("gameplay-mechanics.note-block-ignore-above", noteBlockIgnoreAbove);
+ persistentTileEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityDisplayNames);
+ persistentDroppableEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-droppable-entity-display-names", persistentDroppableEntityDisplayNames);
+ projectilesBypassMobGriefing = getBoolean("gameplay-mechanics.projectiles-bypass-mob-griefing", projectilesBypassMobGriefing);
+ tickFluids = getBoolean("gameplay-mechanics.tick-fluids", tickFluids);
+ mobsBlindnessMultiplier = getDouble("gameplay-mechanics.entity-blindness-multiplier", mobsBlindnessMultiplier);
+ tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight);
+ voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight);
+ voidDamageDealt = getDouble("gameplay-mechanics.void-damage-dealt", voidDamageDealt);
+ raidCooldownSeconds = getInt("gameplay-mechanics.raid-cooldown-seconds", raidCooldownSeconds);
+ animalBreedingCooldownSeconds = getInt("gameplay-mechanics.animal-breeding-cooldown-seconds", animalBreedingCooldownSeconds);
+ mobsIgnoreRails = getBoolean("gameplay-mechanics.mobs-ignore-rails", mobsIgnoreRails);
+ rainStopsAfterSleep = getBoolean("gameplay-mechanics.rain-stops-after-sleep", rainStopsAfterSleep);
+ thunderStopsAfterSleep = getBoolean("gameplay-mechanics.thunder-stops-after-sleep", thunderStopsAfterSleep);
+ mobLastHurtByPlayerTime = getInt("gameplay-mechanics.mob-last-hurt-by-player-time", mobLastHurtByPlayerTime);
+ }
+
+ public int daytimeTicks = 12000;
+ public int nighttimeTicks = 12000;
+ private void daytimeCycleSettings() {
+ daytimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.daytime", daytimeTicks);
+ nighttimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.nighttime", nighttimeTicks);
+ }
+
+ public int drowningAirTicks = 300;
+ public int drowningDamageInterval = 20;
+ public double damageFromDrowning = 2.0F;
+ private void drowningSettings() {
+ drowningAirTicks = getInt("gameplay-mechanics.drowning.air-ticks", drowningAirTicks);
+ drowningDamageInterval = getInt("gameplay-mechanics.drowning.ticks-per-damage", drowningDamageInterval);
+ damageFromDrowning = getDouble("gameplay-mechanics.drowning.damage-from-drowning", damageFromDrowning);
+ }
+
+ public int elytraDamagePerSecond = 1;
+ public double elytraDamageMultiplyBySpeed = 0;
+ public boolean elytraIgnoreUnbreaking = false;
+ public int elytraDamagePerFireworkBoost = 0;
+ public int elytraDamagePerTridentBoost = 0;
+ public boolean elytraKineticDamage = true;
+ private void elytraSettings() {
+ elytraDamagePerSecond = getInt("gameplay-mechanics.elytra.damage-per-second", elytraDamagePerSecond);
+ elytraDamageMultiplyBySpeed = getDouble("gameplay-mechanics.elytra.damage-multiplied-by-speed", elytraDamageMultiplyBySpeed);
+ elytraIgnoreUnbreaking = getBoolean("gameplay-mechanics.elytra.ignore-unbreaking", elytraIgnoreUnbreaking);
+ elytraDamagePerFireworkBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.firework", elytraDamagePerFireworkBoost);
+ elytraDamagePerTridentBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.trident", elytraDamagePerTridentBoost);
+ elytraKineticDamage = getBoolean("gameplay-mechanics.elytra.kinetic-damage", elytraKineticDamage);
+ }
+
+ public int entityLifeSpan = 0;
+ public float entityLeftHandedChance = 0.05f;
+ public boolean entitySharedRandom = true;
+ private void entitySettings() {
+ entityLifeSpan = getInt("gameplay-mechanics.entity-lifespan", entityLifeSpan);
+ entityLeftHandedChance = (float) getDouble("gameplay-mechanics.entity-left-handed-chance", entityLeftHandedChance);
+ entitySharedRandom = getBoolean("settings.entity.shared-random", entitySharedRandom);
+ }
+
+ public boolean explosionClampRadius = true;
+ private void explosionSettings() {
+ explosionClampRadius = getBoolean("gameplay-mechanics.clamp-explosion-radius", explosionClampRadius);
+ }
+
+ public boolean infinityWorksWithoutArrows = false;
+ public boolean infinityWorksWithNormalArrows = true;
+ public boolean infinityWorksWithSpectralArrows = false;
+ public boolean infinityWorksWithTippedArrows = false;
+ private void infinityArrowsSettings() {
+ infinityWorksWithoutArrows = getBoolean("gameplay-mechanics.infinity-bow.works-without-arrows", infinityWorksWithoutArrows);
+ infinityWorksWithNormalArrows = getBoolean("gameplay-mechanics.infinity-bow.normal-arrows", infinityWorksWithNormalArrows);
+ infinityWorksWithSpectralArrows = getBoolean("gameplay-mechanics.infinity-bow.spectral-arrows", infinityWorksWithSpectralArrows);
+ infinityWorksWithTippedArrows = getBoolean("gameplay-mechanics.infinity-bow.tipped-arrows", infinityWorksWithTippedArrows);
+ }
+
+ public List<Item> itemImmuneToCactus = new ArrayList<>();
+ public List<Item> itemImmuneToExplosion = new ArrayList<>();
+ public List<Item> itemImmuneToFire = new ArrayList<>();
+ public List<Item> itemImmuneToLightning = new ArrayList<>();
+ public boolean dontRunWithScissors = false;
+ public boolean ignoreScissorsInWater = false;
+ public boolean ignoreScissorsInLava = false;
+ public double scissorsRunningDamage = 1D;
+ public float enderPearlDamage = 5.0F;
+ public int enderPearlCooldown = 20;
+ public int enderPearlCooldownCreative = 20;
+ public float enderPearlEndermiteChance = 0.05F;
+ public int glowBerriesEatGlowDuration = 0;
+ public boolean shulkerBoxItemDropContentsWhenDestroyed = true;
+ public boolean compassItemShowsBossBar = false;
+ public boolean snowballExtinguishesFire = false;
+ public boolean snowballExtinguishesCandles = false;
+ public boolean snowballExtinguishesCampfires = false;
+ private void itemSettings() {
+ itemImmuneToCactus.clear();
+ getList("gameplay-mechanics.item.immune.cactus", new ArrayList<>()).forEach(key -> {
+ if (key.toString().equals("*")) {
+ BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToCactus.add(item));
+ return;
+ }
+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString()));
+ if (item != Items.AIR) itemImmuneToCactus.add(item);
+ });
+ itemImmuneToExplosion.clear();
+ getList("gameplay-mechanics.item.immune.explosion", new ArrayList<>()).forEach(key -> {
+ if (key.toString().equals("*")) {
+ BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToExplosion.add(item));
+ return;
+ }
+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString()));
+ if (item != Items.AIR) itemImmuneToExplosion.add(item);
+ });
+ itemImmuneToFire.clear();
+ getList("gameplay-mechanics.item.immune.fire", new ArrayList<>()).forEach(key -> {
+ if (key.toString().equals("*")) {
+ BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToFire.add(item));
+ return;
+ }
+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString()));
+ if (item != Items.AIR) itemImmuneToFire.add(item);
+ });
+ itemImmuneToLightning.clear();
+ getList("gameplay-mechanics.item.immune.lightning", new ArrayList<>()).forEach(key -> {
+ if (key.toString().equals("*")) {
+ BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToLightning.add(item));
+ return;
+ }
+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString()));
+ if (item != Items.AIR) itemImmuneToLightning.add(item);
+ });
+ dontRunWithScissors = getBoolean("gameplay-mechanics.item.shears.damage-if-sprinting", dontRunWithScissors);
+ ignoreScissorsInWater = getBoolean("gameplay-mechanics.item.shears.ignore-in-water", ignoreScissorsInWater);
+ ignoreScissorsInLava = getBoolean("gameplay-mechanics.item.shears.ignore-in-lava", ignoreScissorsInLava);
+ scissorsRunningDamage = getDouble("gameplay-mechanics.item.shears.sprinting-damage", scissorsRunningDamage);
+ enderPearlDamage = (float) getDouble("gameplay-mechanics.item.ender-pearl.damage", enderPearlDamage);
+ enderPearlCooldown = getInt("gameplay-mechanics.item.ender-pearl.cooldown", enderPearlCooldown);
+ enderPearlCooldownCreative = getInt("gameplay-mechanics.item.ender-pearl.creative-cooldown", enderPearlCooldownCreative);
+ enderPearlEndermiteChance = (float) getDouble("gameplay-mechanics.item.ender-pearl.endermite-spawn-chance", enderPearlEndermiteChance);
+ glowBerriesEatGlowDuration = getInt("gameplay-mechanics.item.glow_berries.eat-glow-duration", glowBerriesEatGlowDuration);
+ shulkerBoxItemDropContentsWhenDestroyed = getBoolean("gameplay-mechanics.item.shulker_box.drop-contents-when-destroyed", shulkerBoxItemDropContentsWhenDestroyed);
+ compassItemShowsBossBar = getBoolean("gameplay-mechanics.item.compass.holding-shows-bossbar", compassItemShowsBossBar);
+ snowballExtinguishesFire = getBoolean("gameplay-mechanics.item.snowball.extinguish.fire", snowballExtinguishesFire);
+ snowballExtinguishesCandles = getBoolean("gameplay-mechanics.item.snowball.extinguish.candles", snowballExtinguishesCandles);
+ snowballExtinguishesCampfires = getBoolean("gameplay-mechanics.item.snowball.extinguish.campfires", snowballExtinguishesCampfires);
+ }
+
+ public double minecartMaxSpeed = 0.4D;
+ public boolean minecartPlaceAnywhere = false;
+ public boolean minecartControllable = false;
+ public float minecartControllableStepHeight = 1.0F;
+ public double minecartControllableHopBoost = 0.5D;
+ public boolean minecartControllableFallDamage = true;
+ public double minecartControllableBaseSpeed = 0.1D;
+ public Map<Block, Double> minecartControllableBlockSpeeds = new HashMap<>();
+ public double poweredRailBoostModifier = 0.06;
+ private void minecartSettings() {
+ if (PurpurConfig.version < 12) {
+ boolean oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.place-anywhere", minecartPlaceAnywhere);
+ set("gameplay-mechanics.controllable-minecarts.place-anywhere", null);
+ set("gameplay-mechanics.minecart.place-anywhere", oldBool);
+ oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.enabled", minecartControllable);
+ set("gameplay-mechanics.controllable-minecarts.enabled", null);
+ set("gameplay-mechanics.minecart.controllable.enabled", oldBool);
+ double oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.step-height", minecartControllableStepHeight);
+ set("gameplay-mechanics.controllable-minecarts.step-height", null);
+ set("gameplay-mechanics.minecart.controllable.step-height", oldDouble);
+ oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.hop-boost", minecartControllableHopBoost);
+ set("gameplay-mechanics.controllable-minecarts.hop-boost", null);
+ set("gameplay-mechanics.minecart.controllable.hop-boost", oldDouble);
+ oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.fall-damage", minecartControllableFallDamage);
+ set("gameplay-mechanics.controllable-minecarts.fall-damage", null);
+ set("gameplay-mechanics.minecart.controllable.fall-damage", oldBool);
+ oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.base-speed", minecartControllableBaseSpeed);
+ set("gameplay-mechanics.controllable-minecarts.base-speed", null);
+ set("gameplay-mechanics.minecart.controllable.base-speed", oldDouble);
+ ConfigurationSection section = getConfigurationSection("gameplay-mechanics.controllable-minecarts.block-speed");
+ if (section != null) {
+ for (String key : section.getKeys(false)) {
+ if ("grass-block".equals(key)) key = "grass_block"; // oopsie
+ oldDouble = section.getDouble(key, minecartControllableBaseSpeed);
+ set("gameplay-mechanics.controllable-minecarts.block-speed." + key, null);
+ set("gameplay-mechanics.minecart.controllable.block-speed." + key, oldDouble);
+ }
+ set("gameplay-mechanics.controllable-minecarts.block-speed", null);
+ }
+ set("gameplay-mechanics.controllable-minecarts", null);
+ }
+
+ minecartMaxSpeed = getDouble("gameplay-mechanics.minecart.max-speed", minecartMaxSpeed);
+ minecartPlaceAnywhere = getBoolean("gameplay-mechanics.minecart.place-anywhere", minecartPlaceAnywhere);
+ minecartControllable = getBoolean("gameplay-mechanics.minecart.controllable.enabled", minecartControllable);
+ minecartControllableStepHeight = (float) getDouble("gameplay-mechanics.minecart.controllable.step-height", minecartControllableStepHeight);
+ minecartControllableHopBoost = getDouble("gameplay-mechanics.minecart.controllable.hop-boost", minecartControllableHopBoost);
+ minecartControllableFallDamage = getBoolean("gameplay-mechanics.minecart.controllable.fall-damage", minecartControllableFallDamage);
+ minecartControllableBaseSpeed = getDouble("gameplay-mechanics.minecart.controllable.base-speed", minecartControllableBaseSpeed);
+ ConfigurationSection section = getConfigurationSection("gameplay-mechanics.minecart.controllable.block-speed");
+ if (section != null) {
+ for (String key : section.getKeys(false)) {
+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key));
+ if (block != Blocks.AIR) {
+ minecartControllableBlockSpeeds.put(block, section.getDouble(key, minecartControllableBaseSpeed));
+ }
+ }
+ } else {
+ set("gameplay-mechanics.minecart.controllable.block-speed.grass_block", 0.3D);
+ set("gameplay-mechanics.minecart.controllable.block-speed.stone", 0.5D);
+ }
+ poweredRailBoostModifier = getDouble("gameplay-mechanics.minecart.powered-rail.boost-modifier", poweredRailBoostModifier);
+ }
+
+ public float entityHealthRegenAmount = 1.0F;
+ public float entityMinimalHealthPoison = 1.0F;
+ public float entityPoisonDegenerationAmount = 1.0F;
+ public float entityWitherDegenerationAmount = 1.0F;
+ public float humanHungerExhaustionAmount = 0.005F;
+ public float humanSaturationRegenAmount = 1.0F;
+ private void mobEffectSettings() {
+ entityHealthRegenAmount = (float) getDouble("gameplay-mechanics.mob-effects.health-regen-amount", entityHealthRegenAmount);
+ entityMinimalHealthPoison = (float) getDouble("gameplay-mechanics.mob-effects.minimal-health-poison-amount", entityMinimalHealthPoison);
+ entityPoisonDegenerationAmount = (float) getDouble("gameplay-mechanics.mob-effects.poison-degeneration-amount", entityPoisonDegenerationAmount);
+ entityWitherDegenerationAmount = (float) getDouble("gameplay-mechanics.mob-effects.wither-degeneration-amount", entityWitherDegenerationAmount);
+ humanHungerExhaustionAmount = (float) getDouble("gameplay-mechanics.mob-effects.hunger-exhaustion-amount", humanHungerExhaustionAmount);
+ humanSaturationRegenAmount = (float) getDouble("gameplay-mechanics.mob-effects.saturation-regen-amount", humanSaturationRegenAmount);
+ }
+
+ public boolean catSpawning;
+ public boolean patrolSpawning;
+ public boolean phantomSpawning;
+ public boolean villagerTraderSpawning;
+ public boolean villageSiegeSpawning;
+ public boolean mobSpawningIgnoreCreativePlayers = false;
+ private void mobSpawnerSettings() {
+ // values of "default" or null will default to true only if the world environment is normal (aka overworld)
+ Predicate<Boolean> predicate = (bool) -> (bool != null && bool) || (bool == null && environment == World.Environment.NORMAL);
+ catSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-cats", predicate);
+ patrolSpawning = getBoolean("gameplay-mechanics.mob-spawning.raid-patrols", predicate);
+ phantomSpawning = getBoolean("gameplay-mechanics.mob-spawning.phantoms", predicate);
+ villagerTraderSpawning = getBoolean("gameplay-mechanics.mob-spawning.wandering-traders", predicate);
+ villageSiegeSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-sieges", predicate);
+ mobSpawningIgnoreCreativePlayers = getBoolean("gameplay-mechanics.mob-spawning.ignore-creative-players", mobSpawningIgnoreCreativePlayers);
+ }
+
+ public boolean disableObserverClocks = false;
+ private void observerSettings() {
+ disableObserverClocks = getBoolean("blocks.observer.disable-clock", disableObserverClocks);
+ }
+
+ public int playerNetheriteFireResistanceDuration = 0;
+ public int playerNetheriteFireResistanceAmplifier = 0;
+ public boolean playerNetheriteFireResistanceAmbient = false;
+ public boolean playerNetheriteFireResistanceShowParticles = false;
+ public boolean playerNetheriteFireResistanceShowIcon = true;
+ private void playerNetheriteFireResistance() {
+ playerNetheriteFireResistanceDuration = getInt("gameplay-mechanics.player.netherite-fire-resistance.duration", playerNetheriteFireResistanceDuration);
+ playerNetheriteFireResistanceAmplifier = getInt("gameplay-mechanics.player.netherite-fire-resistance.amplifier", playerNetheriteFireResistanceAmplifier);
+ playerNetheriteFireResistanceAmbient = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.ambient", playerNetheriteFireResistanceAmbient);
+ playerNetheriteFireResistanceShowParticles = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-particles", playerNetheriteFireResistanceShowParticles);
+ playerNetheriteFireResistanceShowIcon = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-icon", playerNetheriteFireResistanceShowIcon);
+ }
+
+ public boolean idleTimeoutKick = true;
+ public boolean idleTimeoutTickNearbyEntities = true;
+ public boolean idleTimeoutCountAsSleeping = false;
+ public boolean idleTimeoutUpdateTabList = false;
+ public boolean idleTimeoutTargetPlayer = true;
+ public int playerSpawnInvulnerableTicks = 60;
+ public boolean playerInvulnerableWhileAcceptingResourcePack = false;
+ public String playerDeathExpDropEquation = "expLevel * 7";
+ public int playerDeathExpDropMax = 100;
+ public boolean teleportIfOutsideBorder = false;
+ public boolean teleportOnNetherCeilingDamage = false;
+ public boolean totemOfUndyingWorksInInventory = false;
+ public boolean playerFixStuckPortal = false;
+ public boolean creativeOnePunch = false;
+ public boolean playerSleepNearMonsters = false;
+ public boolean playersSkipNight = true;
+ public double playerCriticalDamageMultiplier = 1.5D;
+ public int playerBurpDelay = 10;
+ public boolean playerBurpWhenFull = false;
+ public int playerPortalWaitTime = 80;
+ public int playerCreativePortalWaitTime = 1;
+ public boolean playerRidableInWater = false;
+ public boolean playerRemoveBindingWithWeakness = false;
+ public int shiftRightClickRepairsMendingPoints = 0;
+ public int playerExpPickupDelay = 2;
+ public boolean playerVoidTrading = false;
+ private void playerSettings() {
+ if (PurpurConfig.version < 19) {
+ boolean oldVal = getBoolean("gameplay-mechanics.player.idle-timeout.mods-target", idleTimeoutTargetPlayer);
+ set("gameplay-mechanics.player.idle-timeout.mods-target", null);
+ set("gameplay-mechanics.player.idle-timeout.mobs-target", oldVal);
+ }
+ idleTimeoutKick = System.getenv("PURPUR_FORCE_IDLE_KICK") == null ? getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick) : Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK"));
+ idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities);
+ idleTimeoutCountAsSleeping = getBoolean("gameplay-mechanics.player.idle-timeout.count-as-sleeping", idleTimeoutCountAsSleeping);
+ idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList);
+ idleTimeoutTargetPlayer = getBoolean("gameplay-mechanics.player.idle-timeout.mobs-target", idleTimeoutTargetPlayer);
+ playerSpawnInvulnerableTicks = getInt("gameplay-mechanics.player.spawn-invulnerable-ticks", playerSpawnInvulnerableTicks);
+ playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack);
+ playerDeathExpDropEquation = getString("gameplay-mechanics.player.exp-dropped-on-death.equation", playerDeathExpDropEquation);
+ playerDeathExpDropMax = getInt("gameplay-mechanics.player.exp-dropped-on-death.maximum", playerDeathExpDropMax);
+ teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder);
+ teleportOnNetherCeilingDamage = getBoolean("gameplay-mechanics.player.teleport-on-nether-ceiling-damage", teleportOnNetherCeilingDamage);
+ totemOfUndyingWorksInInventory = getBoolean("gameplay-mechanics.player.totem-of-undying-works-in-inventory", totemOfUndyingWorksInInventory);
+ playerFixStuckPortal = getBoolean("gameplay-mechanics.player.fix-stuck-in-portal", playerFixStuckPortal);
+ creativeOnePunch = getBoolean("gameplay-mechanics.player.one-punch-in-creative", creativeOnePunch);
+ playerSleepNearMonsters = getBoolean("gameplay-mechanics.player.sleep-ignore-nearby-mobs", playerSleepNearMonsters);
+ playersSkipNight = getBoolean("gameplay-mechanics.player.can-skip-night", playersSkipNight);
+ playerCriticalDamageMultiplier = getDouble("gameplay-mechanics.player.critical-damage-multiplier", playerCriticalDamageMultiplier);
+ playerBurpDelay = getInt("gameplay-mechanics.player.burp-delay", playerBurpDelay);
+ playerBurpWhenFull = getBoolean("gameplay-mechanics.player.burp-when-full", playerBurpWhenFull);
+ playerPortalWaitTime = getInt("gameplay-mechanics.player.portal-wait-time", playerPortalWaitTime);
+ playerCreativePortalWaitTime = getInt("gameplay-mechanics.player.creative-portal-wait-time", playerCreativePortalWaitTime);
+ playerRidableInWater = getBoolean("gameplay-mechanics.player.ridable-in-water", playerRidableInWater);
+ playerRemoveBindingWithWeakness = getBoolean("gameplay-mechanics.player.curse-of-binding.remove-with-weakness", playerRemoveBindingWithWeakness);
+ shiftRightClickRepairsMendingPoints = getInt("gameplay-mechanics.player.shift-right-click-repairs-mending-points", shiftRightClickRepairsMendingPoints);
+ playerExpPickupDelay = getInt("gameplay-mechanics.player.exp-pickup-delay-ticks", playerExpPickupDelay);
+ playerVoidTrading = getBoolean("gameplay-mechanics.player.allow-void-trading", playerVoidTrading);
+ }
+
+ private static boolean projectileDespawnRateSettingsMigrated = false;
+ private void projectileDespawnRateSettings() {
+ if (PurpurConfig.version < 28 && !projectileDespawnRateSettingsMigrated) {
+ migrateProjectileDespawnRateSettings(EntityType.DRAGON_FIREBALL);
+ migrateProjectileDespawnRateSettings(EntityType.EGG);
+ migrateProjectileDespawnRateSettings(EntityType.ENDER_PEARL);
+ migrateProjectileDespawnRateSettings(EntityType.EXPERIENCE_BOTTLE);
+ migrateProjectileDespawnRateSettings(EntityType.FIREWORK_ROCKET);
+ migrateProjectileDespawnRateSettings(EntityType.FISHING_BOBBER);
+ migrateProjectileDespawnRateSettings(EntityType.FIREBALL);
+ migrateProjectileDespawnRateSettings(EntityType.LLAMA_SPIT);
+ migrateProjectileDespawnRateSettings(EntityType.POTION);
+ migrateProjectileDespawnRateSettings(EntityType.SHULKER_BULLET);
+ migrateProjectileDespawnRateSettings(EntityType.SMALL_FIREBALL);
+ migrateProjectileDespawnRateSettings(EntityType.SNOWBALL);
+ migrateProjectileDespawnRateSettings(EntityType.WITHER_SKULL);
+ //PufferfishConfig.save();
+ set("gameplay-mechanics.projectile-despawn-rates", null);
+ // pufferfish's entity_timeout is a global config
+ // we only want to migrate values from the
+ // default world (first world loaded)
+ projectileDespawnRateSettingsMigrated = true;
+ }
+ }
+ private void migrateProjectileDespawnRateSettings(EntityType<?> type) {
+ //String pufferName = "entity_timeouts." + type.id.toUpperCase(Locale.ROOT);
+ //int value = getInt("gameplay-mechanics.projectile-despawn-rates." + type.id, -1);
+ //if (value != -1 && PufferfishConfig.getRawInt(pufferName, -1) == -1) {
+ // PufferfishConfig.setInt(pufferName, value);
+ // type.ttl = value;
+ //}
+ }
+
+ public double bowProjectileOffset = 1.0D;
+ public double crossbowProjectileOffset = 1.0D;
+ public double eggProjectileOffset = 1.0D;
+ public double enderPearlProjectileOffset = 1.0D;
+ public double throwablePotionProjectileOffset = 1.0D;
+ public double tridentProjectileOffset = 1.0D;
+ public double snowballProjectileOffset = 1.0D;
+ private void projectileOffsetSettings() {
+ bowProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.bow", bowProjectileOffset);
+ crossbowProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.crossbow", crossbowProjectileOffset);
+ eggProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.egg", eggProjectileOffset);
+ enderPearlProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.ender-pearl", enderPearlProjectileOffset);
+ throwablePotionProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.throwable-potion", throwablePotionProjectileOffset);
+ tridentProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.trident", tridentProjectileOffset);
+ snowballProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.snowball", snowballProjectileOffset);
+ }
+
+ public int snowballDamage = -1;
+ private void snowballSettings() {
+ snowballDamage = getInt("gameplay-mechanics.projectile-damage.snowball", snowballDamage);
+ }
+
+ public boolean silkTouchEnabled = false;
+ public String silkTouchSpawnerName = "<reset><white>Monster Spawner";
+ public List<String> silkTouchSpawnerLore = new ArrayList<>();
+ public List<Item> 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", "<reset>" + ChatColor.toMM(oldName.replace("{mob}", "<mob>")));
+ List<String> list = new ArrayList<>();
+ getList("gameplay-mechanics.silk-touch.spawner-lore", List.of("Spawns a <mob>"))
+ .forEach(line -> list.add("<reset>" + ChatColor.toMM(line.toString().replace("{mob}", "<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 <mob>"))
+ .forEach(line -> silkTouchSpawnerLore.add(line.toString()));
+ silkTouchTools.clear();
+ getList("gameplay-mechanics.silk-touch.tools", List.of(
+ "minecraft:iron_pickaxe",
+ "minecraft:golden_pickaxe",
+ "minecraft:diamond_pickaxe",
+ "minecraft:netherite_pickaxe"
+ )).forEach(key -> {
+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString()));
+ if (item != Items.AIR) silkTouchTools.add(item);
+ });
+ }
+
+ public Map<Block, Strippable> axeStrippables = new HashMap<>();
+ public Map<Block, Waxable> axeWaxables = new HashMap<>();
+ public Map<Block, Weatherable> axeWeatherables = new HashMap<>();
+ public Map<Block, Tillable> hoeTillables = new HashMap<>();
+ public Map<Block, Flattenable> 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<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:mangrove_wood", Map.of("into", "minecraft:stripped_mangrove_wood", "drops", new HashMap<String, Double>()));
+ }
+ 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<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap<String, Double>()));
+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap<String, Double>()));
+ }
+ if (PurpurConfig.version < 33) {
+ getList("gameplay-mechanics.shovel-turns-block-to-grass-path", new ArrayList<String>(){{
+ 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<String, Double>()));
+ });
+ set("gameplay-mechanics.shovel-turns-block-to-grass-path", null);
+ }
+ getMap("tools.axe.strippables", Map.ofEntries(
+ Map.entry("minecraft:oak_wood", Map.of("into", "minecraft:stripped_oak_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oak_log", Map.of("into", "minecraft:stripped_oak_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:dark_oak_wood", Map.of("into", "minecraft:stripped_dark_oak_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:dark_oak_log", Map.of("into", "minecraft:stripped_dark_oak_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:acacia_wood", Map.of("into", "minecraft:stripped_acacia_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:acacia_log", Map.of("into", "minecraft:stripped_acacia_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:birch_wood", Map.of("into", "minecraft:stripped_birch_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:birch_log", Map.of("into", "minecraft:stripped_birch_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:jungle_wood", Map.of("into", "minecraft:stripped_jungle_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:jungle_log", Map.of("into", "minecraft:stripped_jungle_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:spruce_wood", Map.of("into", "minecraft:stripped_spruce_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:spruce_log", Map.of("into", "minecraft:stripped_spruce_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:mangrove_wood", Map.of("into", "minecraft:stripped_mangrove_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:mangrove_log", Map.of("into", "minecraft:stripped_mangrove_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:cherry_log", Map.of("into", "minecraft:stripped_cherry_log", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:warped_stem", Map.of("into", "minecraft:stripped_warped_stem", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:warped_hyphae", Map.of("into", "minecraft:stripped_warped_hyphae", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:crimson_stem", Map.of("into", "minecraft:stripped_crimson_stem", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:crimson_hyphae", Map.of("into", "minecraft:stripped_crimson_hyphae", "drops", new HashMap<String, Double>())))
+ ).forEach((blockId, obj) -> {
+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId));
+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables`: " + blockId); return; }
+ if (!(obj instanceof Map<?, ?> map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.strippables." + blockId + "`"); return; }
+ String intoId = (String) map.get("into");
+ Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId));
+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables." + blockId + ".into`: " + intoId); return; }
+ Object dropsObj = map.get("drops");
+ if (!(dropsObj instanceof Map<?, ?> dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.strippables." + blockId + ".drops`"); return; }
+ Map<Item, Double> drops = new HashMap<>();
+ dropsMap.forEach((itemId, chance) -> {
+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString()));
+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.strippables." + blockId + ".drops`: " + itemId); return; }
+ drops.put(item, (double) chance);
+ });
+ axeStrippables.put(block, new Strippable(into, drops));
+ });
+ getMap("tools.axe.waxables", Map.ofEntries(
+ Map.entry("minecraft:waxed_copper_block", Map.of("into", "minecraft:copper_block", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_copper", Map.of("into", "minecraft:exposed_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_copper", Map.of("into", "minecraft:weathered_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_copper", Map.of("into", "minecraft:oxidized_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_cut_copper", Map.of("into", "minecraft:cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_cut_copper", Map.of("into", "minecraft:exposed_cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_cut_copper", Map.of("into", "minecraft:weathered_cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_cut_copper", Map.of("into", "minecraft:oxidized_cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_cut_copper_slab", Map.of("into", "minecraft:cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_cut_copper_slab", Map.of("into", "minecraft:exposed_cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_cut_copper_slab", Map.of("into", "minecraft:weathered_cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_cut_copper_slab", Map.of("into", "minecraft:oxidized_cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_cut_copper_stairs", Map.of("into", "minecraft:cut_copper_stairs", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_exposed_cut_copper_stairs", Map.of("into", "minecraft:exposed_cut_copper_stairs", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_weathered_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:waxed_oxidized_cut_copper_stairs", Map.of("into", "minecraft:oxidized_cut_copper_stairs", "drops", new HashMap<String, Double>())))
+ ).forEach((blockId, obj) -> {
+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId));
+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.waxables`: " + blockId); return; }
+ if (!(obj instanceof Map<?, ?> map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.waxables." + blockId + "`"); return; }
+ String intoId = (String) map.get("into");
+ Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId));
+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.waxables." + blockId + ".into`: " + intoId); return; }
+ Object dropsObj = map.get("drops");
+ if (!(dropsObj instanceof Map<?, ?> dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.waxables." + blockId + ".drops`"); return; }
+ Map<Item, Double> drops = new HashMap<>();
+ dropsMap.forEach((itemId, chance) -> {
+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString()));
+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.waxables." + blockId + ".drops`: " + itemId); return; }
+ drops.put(item, (double) chance);
+ });
+ axeWaxables.put(block, new Waxable(into, drops));
+ });
+ getMap("tools.axe.weatherables", Map.ofEntries(
+ Map.entry("minecraft:exposed_copper", Map.of("into", "minecraft:copper_block", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_copper", Map.of("into", "minecraft:exposed_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_copper", Map.of("into", "minecraft:weathered_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:exposed_cut_copper", Map.of("into", "minecraft:cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_cut_copper", Map.of("into", "minecraft:exposed_cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_cut_copper", Map.of("into", "minecraft:weathered_cut_copper", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:exposed_cut_copper_slab", Map.of("into", "minecraft:cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_cut_copper_slab", Map.of("into", "minecraft:exposed_cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_cut_copper_slab", Map.of("into", "minecraft:weathered_cut_copper_slab", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:exposed_cut_copper_stairs", Map.of("into", "minecraft:cut_copper_stairs", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:weathered_cut_copper_stairs", Map.of("into", "minecraft:exposed_cut_copper_stairs", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:oxidized_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap<String, Double>())))
+ ).forEach((blockId, obj) -> {
+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId));
+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.weatherables`: " + blockId); return; }
+ if (!(obj instanceof Map<?, ?> map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.weatherables." + blockId + "`"); return; }
+ String intoId = (String) map.get("into");
+ Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId));
+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.weatherables." + blockId + ".into`: " + intoId); return; }
+ Object dropsObj = map.get("drops");
+ if (!(dropsObj instanceof Map<?, ?> dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.weatherables." + blockId + ".drops`"); return; }
+ Map<Item, Double> drops = new HashMap<>();
+ dropsMap.forEach((itemId, chance) -> {
+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString()));
+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.weatherables." + blockId + ".drops`: " + itemId); return; }
+ drops.put(item, (double) chance);
+ });
+ axeWeatherables.put(block, new Weatherable(into, drops));
+ });
+ getMap("tools.hoe.tillables", Map.ofEntries(
+ Map.entry("minecraft:grass_block", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:dirt_path", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:dirt", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:coarse_dirt", Map.of("condition", "air_above", "into", "minecraft:dirt", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:rooted_dirt", Map.of("condition", "always", "into", "minecraft:dirt", "drops", Map.of("minecraft:hanging_roots", 1.0D))))
+ ).forEach((blockId, obj) -> {
+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId));
+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.hoe.tillables`: " + blockId); return; }
+ if (!(obj instanceof Map<?, ?> map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.hoe.tillables." + blockId + "`"); return; }
+ String conditionId = (String) map.get("condition");
+ Tillable.Condition condition = Tillable.Condition.get(conditionId);
+ if (condition == null) { PurpurConfig.log(Level.SEVERE, "Invalid condition for `tools.hoe.tillables." + blockId + ".condition`: " + conditionId); return; }
+ String intoId = (String) map.get("into");
+ Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId));
+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.hoe.tillables." + blockId + ".into`: " + intoId); return; }
+ Object dropsObj = map.get("drops");
+ if (!(dropsObj instanceof Map<?, ?> dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.hoe.tillables." + blockId + ".drops`"); return; }
+ Map<Item, Double> drops = new HashMap<>();
+ dropsMap.forEach((itemId, chance) -> {
+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString()));
+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.hoe.tillables." + blockId + ".drops`: " + itemId); return; }
+ drops.put(item, (double) chance);
+ });
+ hoeTillables.put(block, new Tillable(condition, into, drops));
+ });
+ getMap("tools.shovel.flattenables", Map.ofEntries(
+ Map.entry("minecraft:grass_block", Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:podzol", Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:coarse_dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:mycelium", Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>())),
+ Map.entry("minecraft:rooted_dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap<String, Double>())))
+ ).forEach((blockId, obj) -> {
+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId));
+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.shovel.flattenables`: " + blockId); return; }
+ if (!(obj instanceof Map<?, ?> map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.shovel.flattenables." + blockId + "`"); return; }
+ String intoId = (String) map.get("into");
+ Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId));
+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.shovel.flattenables." + blockId + ".into`: " + intoId); return; }
+ Object dropsObj = map.get("drops");
+ if (!(dropsObj instanceof Map<?, ?> dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.shovel.flattenables." + blockId + ".drops`"); return; }
+ Map<Item, Double> drops = new HashMap<>();
+ dropsMap.forEach((itemId, chance) -> {
+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString()));
+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.shovel.flattenables." + blockId + ".drops`: " + itemId); return; }
+ drops.put(item, (double) chance);
+ });
+ shovelFlattenables.put(block, new Flattenable(into, drops));
+ });
+ hoeReplantsCrops = getBoolean("tools.hoe.replant-crops", hoeReplantsCrops);
+ hoeReplantsNetherWarts = getBoolean("tools.hoe.replant-nether-warts", hoeReplantsNetherWarts);
+ }
+
+ public boolean anvilAllowColors = false;
+ public boolean anvilColorsUseMiniMessage;
+ public int anvilRepairIngotsAmount = 0;
+ public int anvilDamageObsidianAmount = 0;
+ private void anvilSettings() {
+ anvilAllowColors = getBoolean("blocks.anvil.allow-colors", anvilAllowColors);
+ anvilColorsUseMiniMessage = getBoolean("blocks.anvil.use-mini-message", anvilColorsUseMiniMessage);
+ anvilRepairIngotsAmount = getInt("blocks.anvil.iron-ingots-used-for-repair", anvilRepairIngotsAmount);
+ anvilDamageObsidianAmount = getInt("blocks.anvil.obsidian-used-for-damage", anvilDamageObsidianAmount);
+ }
+
+ public double azaleaGrowthChance = 0.0D;
+ private void azaleaSettings() {
+ azaleaGrowthChance = getDouble("blocks.azalea.growth-chance", azaleaGrowthChance);
+ }
+
+ public int beaconLevelOne = 20;
+ public int beaconLevelTwo = 30;
+ public int beaconLevelThree = 40;
+ public int beaconLevelFour = 50;
+ public boolean beaconAllowEffectsWithTintedGlass = false;
+ private void beaconSettings() {
+ beaconLevelOne = getInt("blocks.beacon.effect-range.level-1", beaconLevelOne);
+ beaconLevelTwo = getInt("blocks.beacon.effect-range.level-2", beaconLevelTwo);
+ beaconLevelThree = getInt("blocks.beacon.effect-range.level-3", beaconLevelThree);
+ beaconLevelFour = getInt("blocks.beacon.effect-range.level-4", beaconLevelFour);
+ beaconAllowEffectsWithTintedGlass = getBoolean("blocks.beacon.allow-effects-with-tinted-glass", beaconAllowEffectsWithTintedGlass);
+ }
+
+ public boolean bedExplode = true;
+ public boolean bedExplodeOnVillagerSleep = false;
+ public double bedExplosionPower = 5.0D;
+ public boolean bedExplosionFire = true;
+ public net.minecraft.world.level.Level.ExplosionInteraction bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ private void bedSettings() {
+ if (PurpurConfig.version < 31) {
+ if ("DESTROY".equals(getString("blocks.bed.explosion-effect", bedExplosionEffect.name()))) {
+ set("blocks.bed.explosion-effect", "BLOCK");
+ }
+ }
+ bedExplode = getBoolean("blocks.bed.explode", bedExplode);
+ bedExplodeOnVillagerSleep = getBoolean("blocks.bed.explode-on-villager-sleep", bedExplodeOnVillagerSleep);
+ bedExplosionPower = getDouble("blocks.bed.explosion-power", bedExplosionPower);
+ bedExplosionFire = getBoolean("blocks.bed.explosion-fire", bedExplosionFire);
+ try {
+ bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.bed.explosion-effect", bedExplosionEffect.name()));
+ } catch (IllegalArgumentException e) {
+ log(Level.SEVERE, "Unknown value for `blocks.bed.explosion-effect`! Using default of `BLOCK`");
+ bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ }
+ }
+
+ public Map<Tilt, Integer> 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<Block> doorRequiresRedstone = new ArrayList<>();
+ private void doorSettings() {
+ getList("blocks.door.requires-redstone", new ArrayList<String>()).forEach(key -> {
+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key.toString()));
+ if (!block.defaultBlockState().isAir()) {
+ doorRequiresRedstone.add(block);
+ }
+ });
+ }
+
+ public boolean dragonEggTeleport = true;
+ private void dragonEggSettings() {
+ dragonEggTeleport = getBoolean("blocks.dragon_egg.teleport", dragonEggTeleport);
+ }
+
+ public boolean baselessEndCrystalExplode = true;
+ public double baselessEndCrystalExplosionPower = 6.0D;
+ public boolean baselessEndCrystalExplosionFire = false;
+ public net.minecraft.world.level.Level.ExplosionInteraction baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ public boolean basedEndCrystalExplode = true;
+ public double basedEndCrystalExplosionPower = 6.0D;
+ public boolean basedEndCrystalExplosionFire = false;
+ public net.minecraft.world.level.Level.ExplosionInteraction basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ public int endCrystalCramming = 0;
+ public boolean endCrystalPlaceAnywhere = false;
+ private void endCrystalSettings() {
+ if (PurpurConfig.version < 31) {
+ if ("DESTROY".equals(getString("blocks.end-crystal.baseless.explosion-effect", baselessEndCrystalExplosionEffect.name()))) {
+ set("blocks.end-crystal.baseless.explosion-effect", "BLOCK");
+ }
+ if ("DESTROY".equals(getString("blocks.end-crystal.base.explosion-effect", basedEndCrystalExplosionEffect.name()))) {
+ set("blocks.end-crystal.base.explosion-effect", "BLOCK");
+ }
+ }
+ baselessEndCrystalExplode = getBoolean("blocks.end-crystal.baseless.explode", baselessEndCrystalExplode);
+ baselessEndCrystalExplosionPower = getDouble("blocks.end-crystal.baseless.explosion-power", baselessEndCrystalExplosionPower);
+ baselessEndCrystalExplosionFire = getBoolean("blocks.end-crystal.baseless.explosion-fire", baselessEndCrystalExplosionFire);
+ try {
+ baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.end-crystal.baseless.explosion-effect", baselessEndCrystalExplosionEffect.name()));
+ } catch (IllegalArgumentException e) {
+ log(Level.SEVERE, "Unknown value for `blocks.end-crystal.baseless.explosion-effect`! Using default of `BLOCK`");
+ baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ }
+ basedEndCrystalExplode = getBoolean("blocks.end-crystal.base.explode", basedEndCrystalExplode);
+ basedEndCrystalExplosionPower = getDouble("blocks.end-crystal.base.explosion-power", basedEndCrystalExplosionPower);
+ basedEndCrystalExplosionFire = getBoolean("blocks.end-crystal.base.explosion-fire", basedEndCrystalExplosionFire);
+ try {
+ basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.end-crystal.base.explosion-effect", basedEndCrystalExplosionEffect.name()));
+ } catch (IllegalArgumentException e) {
+ log(Level.SEVERE, "Unknown value for `blocks.end-crystal.base.explosion-effect`! Using default of `BLOCK`");
+ basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ }
+ endCrystalCramming = getInt("blocks.end-crystal.cramming-amount", endCrystalCramming);
+ endCrystalPlaceAnywhere = getBoolean("gameplay-mechanics.item.end-crystal.place-anywhere", endCrystalPlaceAnywhere);
+ }
+
+ public boolean farmlandBypassMobGriefing = false;
+ public boolean farmlandGetsMoistFromBelow = false;
+ public boolean farmlandAlpha = false;
+ public boolean farmlandTramplingDisabled = false;
+ public boolean farmlandTramplingOnlyPlayers = false;
+ public boolean farmlandTramplingFeatherFalling = false;
+ public double farmlandTrampleHeight = -1D;
+ private void farmlandSettings() {
+ farmlandBypassMobGriefing = getBoolean("blocks.farmland.bypass-mob-griefing", farmlandBypassMobGriefing);
+ farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow);
+ farmlandAlpha = getBoolean("blocks.farmland.use-alpha-farmland", farmlandAlpha);
+ farmlandTramplingDisabled = getBoolean("blocks.farmland.disable-trampling", farmlandTramplingDisabled);
+ farmlandTramplingOnlyPlayers = getBoolean("blocks.farmland.only-players-trample", farmlandTramplingOnlyPlayers);
+ farmlandTramplingFeatherFalling = getBoolean("blocks.farmland.feather-fall-distance-affects-trampling", farmlandTramplingFeatherFalling);
+ farmlandTrampleHeight = getDouble("blocks.farmland.trample-height", farmlandTrampleHeight);
+ }
+
+ public double floweringAzaleaGrowthChance = 0.0D;
+ private void floweringAzaleaSettings() {
+ floweringAzaleaGrowthChance = getDouble("blocks.flowering_azalea.growth-chance", floweringAzaleaGrowthChance);
+ }
+
+ public boolean furnaceUseLavaFromUnderneath = false;
+ private void furnaceSettings() {
+ if (PurpurConfig.version < 17) {
+ furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.infinite-fuel", furnaceUseLavaFromUnderneath);
+ boolean oldValue = getBoolean("blocks.furnace.infinite-fuel", furnaceUseLavaFromUnderneath);
+ set("blocks.furnace.infinite-fuel", null);
+ set("blocks.furnace.use-lava-from-underneath", oldValue);
+ }
+ furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.use-lava-from-underneath", furnaceUseLavaFromUnderneath);
+ }
+
+ public boolean endPortalSafeTeleporting = true;
+ private void endPortalSettings() {
+ endPortalSafeTeleporting = getBoolean("blocks.end_portal.safe-teleporting", endPortalSafeTeleporting);
+ }
+
+ public boolean mobsSpawnOnPackedIce = true;
+ public boolean mobsSpawnOnBlueIce = true;
+ public boolean snowOnBlueIce = true;
+ private void iceSettings() {
+ mobsSpawnOnPackedIce = getBoolean("blocks.packed_ice.allow-mob-spawns", mobsSpawnOnPackedIce);
+ mobsSpawnOnBlueIce = getBoolean("blocks.blue_ice.allow-mob-spawns", mobsSpawnOnBlueIce);
+ snowOnBlueIce = getBoolean("blocks.blue_ice.allow-snow-formation", snowOnBlueIce);
+ }
+
+ public int lavaInfiniteRequiredSources = 2;
+ public int lavaSpeedNether = 10;
+ public int lavaSpeedNotNether = 30;
+ private void lavaSettings() {
+ lavaInfiniteRequiredSources = getInt("blocks.lava.infinite-required-sources", lavaInfiniteRequiredSources);
+ lavaSpeedNether = getInt("blocks.lava.speed.nether", lavaSpeedNether);
+ lavaSpeedNotNether = getInt("blocks.lava.speed.not-nether", lavaSpeedNotNether);
+ }
+
+ public int pistonBlockPushLimit = 12;
+ private void pistonSettings() {
+ pistonBlockPushLimit = getInt("blocks.piston.block-push-limit", pistonBlockPushLimit);
+ }
+
+ public boolean magmaBlockDamageWhenSneaking = false;
+ public boolean magmaBlockDamageWithFrostWalker = false;
+ private void magmaBlockSettings() {
+ magmaBlockDamageWhenSneaking = getBoolean("blocks.magma-block.damage-when-sneaking", magmaBlockDamageWhenSneaking);
+ magmaBlockDamageWithFrostWalker = getBoolean("blocks.magma-block.damage-with-frost-walker", magmaBlockDamageWithFrostWalker);
+ }
+
+ public boolean powderSnowBypassMobGriefing = false;
+ private void powderSnowSettings() {
+ powderSnowBypassMobGriefing = getBoolean("blocks.powder_snow.bypass-mob-griefing", powderSnowBypassMobGriefing);
+ }
+
+ public int railActivationRange = 8;
+ private void railSettings() {
+ railActivationRange = getInt("blocks.powered-rail.activation-range", railActivationRange);
+ }
+
+ public boolean respawnAnchorExplode = true;
+ public double respawnAnchorExplosionPower = 5.0D;
+ public boolean respawnAnchorExplosionFire = true;
+ public net.minecraft.world.level.Level.ExplosionInteraction respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ private void respawnAnchorSettings() {
+ if (PurpurConfig.version < 31) {
+ if ("DESTROY".equals(getString("blocks.respawn_anchor.explosion-effect", respawnAnchorExplosionEffect.name()))) {
+ set("blocks.respawn_anchor.explosion-effect", "BLOCK");
+ }
+ }
+ respawnAnchorExplode = getBoolean("blocks.respawn_anchor.explode", respawnAnchorExplode);
+ respawnAnchorExplosionPower = getDouble("blocks.respawn_anchor.explosion-power", respawnAnchorExplosionPower);
+ respawnAnchorExplosionFire = getBoolean("blocks.respawn_anchor.explosion-fire", respawnAnchorExplosionFire);
+ try {
+ respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.respawn_anchor.explosion-effect", respawnAnchorExplosionEffect.name()));
+ } catch (IllegalArgumentException e) {
+ log(Level.SEVERE, "Unknown value for `blocks.respawn_anchor.explosion-effect`! Using default of `BLOCK`");
+ respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK;
+ }
+ }
+
+ public boolean fixSandDuping = true;
+ private void sandSettings() {
+ fixSandDuping = getBoolean("blocks.sand.fix-duping", fixSandDuping);
+ }
+
+ public boolean sculkShriekerCanSummonDefault = false;
+ private void sculkShriekerSettings() {
+ sculkShriekerCanSummonDefault = getBoolean("blocks.sculk_shrieker.can-summon-default", sculkShriekerCanSummonDefault);
+ }
+
+ public boolean shulkerBoxAllowOversizedStacks = false;
+ private void shulkerBoxSettings() {
+ shulkerBoxAllowOversizedStacks = getBoolean("blocks.shulker_box.allow-oversized-stacks", shulkerBoxAllowOversizedStacks);
+ }
+
+ public boolean signAllowColors = false;
+ private void signSettings() {
+ signAllowColors = getBoolean("blocks.sign.allow-colors", signAllowColors);
+ }
+
+ public boolean slabHalfBreak = false;
+ private void slabSettings() {
+ slabHalfBreak = getBoolean("blocks.slab.break-individual-slabs-when-sneaking", slabHalfBreak);
+ }
+
+ public boolean spawnerDeactivateByRedstone = false;
+ public boolean spawnerFixMC238526 = false;
+ private void spawnerSettings() {
+ spawnerDeactivateByRedstone = getBoolean("blocks.spawner.deactivate-by-redstone", spawnerDeactivateByRedstone);
+ spawnerFixMC238526 = getBoolean("blocks.spawner.fix-mc-238526", spawnerFixMC238526);
+ }
+
+ public int spongeAbsorptionArea = 65;
+ public int spongeAbsorptionRadius = 6;
+ public boolean spongeAbsorbsLava = false;
+ public boolean spongeAbsorbsWaterFromMud = false;
+ private void spongeSettings() {
+ spongeAbsorptionArea = getInt("blocks.sponge.absorption.area", spongeAbsorptionArea);
+ spongeAbsorptionRadius = getInt("blocks.sponge.absorption.radius", spongeAbsorptionRadius);
+ spongeAbsorbsLava = getBoolean("blocks.sponge.absorbs-lava", spongeAbsorbsLava);
+ spongeAbsorbsWaterFromMud = getBoolean("blocks.sponge.absorbs-water-from-mud", spongeAbsorbsWaterFromMud);
+ }
+
+ public float stonecutterDamage = 0.0F;
+ private void stonecutterSettings() {
+ stonecutterDamage = (float) getDouble("blocks.stonecutter.damage", stonecutterDamage);
+ }
+
+ public boolean turtleEggsBreakFromExpOrbs = true;
+ public boolean turtleEggsBreakFromItems = true;
+ public boolean turtleEggsBreakFromMinecarts = true;
+ public boolean turtleEggsBypassMobGriefing = false;
+ public int turtleEggsRandomTickCrackChance = 500;
+ public boolean turtleEggsTramplingFeatherFalling = false;
+ private void turtleEggSettings() {
+ turtleEggsBreakFromExpOrbs = getBoolean("blocks.turtle_egg.break-from-exp-orbs", turtleEggsBreakFromExpOrbs);
+ turtleEggsBreakFromItems = getBoolean("blocks.turtle_egg.break-from-items", turtleEggsBreakFromItems);
+ turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts);
+ turtleEggsBypassMobGriefing = getBoolean("blocks.turtle_egg.bypass-mob-griefing", turtleEggsBypassMobGriefing);
+ turtleEggsRandomTickCrackChance = getInt("blocks.turtle_egg.random-tick-crack-chance", turtleEggsRandomTickCrackChance);
+ turtleEggsTramplingFeatherFalling = getBoolean("blocks.turtle_egg.feather-fall-distance-affects-trampling", turtleEggsTramplingFeatherFalling);
+ }
+
+ public int waterInfiniteRequiredSources = 2;
+ private void waterSources() {
+ waterInfiniteRequiredSources = getInt("blocks.water.infinite-required-sources", waterInfiniteRequiredSources);
+ }
+
+ public boolean babiesAreRidable = true;
+ public boolean untamedTamablesAreRidable = true;
+ public boolean useNightVisionWhenRiding = false;
+ public boolean useDismountsUnderwaterTag = true;
+ private void ridableSettings() {
+ babiesAreRidable = getBoolean("ridable-settings.babies-are-ridable", babiesAreRidable);
+ untamedTamablesAreRidable = getBoolean("ridable-settings.untamed-tamables-are-ridable", untamedTamablesAreRidable);
+ useNightVisionWhenRiding = getBoolean("ridable-settings.use-night-vision", useNightVisionWhenRiding);
+ useDismountsUnderwaterTag = getBoolean("ridable-settings.use-dismounts-underwater-tag", useDismountsUnderwaterTag);
+ }
+
+ public boolean allayRidable = false;
+ public boolean allayRidableInWater = true;
+ public boolean allayControllable = true;
+ public List<String> allayRespectNBT = new ArrayList<>();
+ private void allaySettings() {
+ allayRidable = getBoolean("mobs.allay.ridable", allayRidable);
+ allayRidableInWater = getBoolean("mobs.allay.ridable-in-water", allayRidableInWater);
+ allayControllable = getBoolean("mobs.allay.controllable", allayControllable);
+ allayRespectNBT.clear();
+ getList("mobs.allay.respect-nbt", new ArrayList<>()).forEach(key -> allayRespectNBT.add(key.toString()));
+ }
+
+ public boolean axolotlRidable = false;
+ public boolean axolotlControllable = true;
+ public double axolotlMaxHealth = 14.0D;
+ public int axolotlBreedingTicks = 6000;
+ public boolean axolotlTakeDamageFromWater = false;
+ public boolean axolotlAlwaysDropExp = false;
+ private void axolotlSettings() {
+ axolotlRidable = getBoolean("mobs.axolotl.ridable", axolotlRidable);
+ axolotlControllable = getBoolean("mobs.axolotl.controllable", axolotlControllable);
+ axolotlMaxHealth = getDouble("mobs.axolotl.attributes.max_health", axolotlMaxHealth);
+ axolotlBreedingTicks = getInt("mobs.axolotl.breeding-delay-ticks", axolotlBreedingTicks);
+ axolotlTakeDamageFromWater = getBoolean("mobs.axolotl.takes-damage-from-water", axolotlTakeDamageFromWater);
+ axolotlAlwaysDropExp = getBoolean("mobs.axolotl.always-drop-exp", axolotlAlwaysDropExp);
+ }
+
+ public boolean batRidable = false;
+ public boolean batRidableInWater = true;
+ public boolean batControllable = true;
+ public double batMaxY = 320D;
+ public double batMaxHealth = 6.0D;
+ public double batFollowRange = 16.0D;
+ public double batKnockbackResistance = 0.0D;
+ public double batMovementSpeed = 0.6D;
+ public double batFlyingSpeed = 0.6D;
+ public double batArmor = 0.0D;
+ public double batArmorToughness = 0.0D;
+ public double batAttackKnockback = 0.0D;
+ public boolean batTakeDamageFromWater = false;
+ public boolean batAlwaysDropExp = false;
+ private void batSettings() {
+ batRidable = getBoolean("mobs.bat.ridable", batRidable);
+ batRidableInWater = getBoolean("mobs.bat.ridable-in-water", batRidableInWater);
+ batControllable = getBoolean("mobs.bat.controllable", batControllable);
+ batMaxY = getDouble("mobs.bat.ridable-max-y", batMaxY);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.bat.attributes.max-health", batMaxHealth);
+ set("mobs.bat.attributes.max-health", null);
+ set("mobs.bat.attributes.max_health", oldValue);
+ }
+ batMaxHealth = getDouble("mobs.bat.attributes.max_health", batMaxHealth);
+ batFollowRange = getDouble("mobs.bat.attributes.follow_range", batFollowRange);
+ batKnockbackResistance = getDouble("mobs.bat.attributes.knockback_resistance", batKnockbackResistance);
+ batMovementSpeed = getDouble("mobs.bat.attributes.movement_speed", batMovementSpeed);
+ batFlyingSpeed = getDouble("mobs.bat.attributes.flying_speed", batFlyingSpeed);
+ batArmor = getDouble("mobs.bat.attributes.armor", batArmor);
+ batArmorToughness = getDouble("mobs.bat.attributes.armor_toughness", batArmorToughness);
+ batAttackKnockback = getDouble("mobs.bat.attributes.attack_knockback", batAttackKnockback);
+ batTakeDamageFromWater = getBoolean("mobs.bat.takes-damage-from-water", batTakeDamageFromWater);
+ batAlwaysDropExp = getBoolean("mobs.bat.always-drop-exp", batAlwaysDropExp);
+ }
+
+ public boolean beeRidable = false;
+ public boolean beeRidableInWater = true;
+ public boolean beeControllable = true;
+ public double beeMaxY = 320D;
+ public double beeMaxHealth = 10.0D;
+ public int beeBreedingTicks = 6000;
+ public boolean beeTakeDamageFromWater = false;
+ public boolean beeCanWorkAtNight = false;
+ public boolean beeCanWorkInRain = false;
+ public boolean beeAlwaysDropExp = false;
+ public boolean beeDiesAfterSting = true;
+ private void beeSettings() {
+ beeRidable = getBoolean("mobs.bee.ridable", beeRidable);
+ beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater);
+ beeControllable = getBoolean("mobs.bee.controllable", beeControllable);
+ beeMaxY = getDouble("mobs.bee.ridable-max-y", beeMaxY);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.bee.attributes.max-health", beeMaxHealth);
+ set("mobs.bee.attributes.max-health", null);
+ set("mobs.bee.attributes.max_health", oldValue);
+ }
+ beeMaxHealth = getDouble("mobs.bee.attributes.max_health", beeMaxHealth);
+ beeBreedingTicks = getInt("mobs.bee.breeding-delay-ticks", beeBreedingTicks);
+ beeTakeDamageFromWater = getBoolean("mobs.bee.takes-damage-from-water", beeTakeDamageFromWater);
+ beeCanWorkAtNight = getBoolean("mobs.bee.can-work-at-night", beeCanWorkAtNight);
+ beeCanWorkInRain = getBoolean("mobs.bee.can-work-in-rain", beeCanWorkInRain);
+ beeAlwaysDropExp = getBoolean("mobs.bee.always-drop-exp", beeAlwaysDropExp);
+ beeDiesAfterSting = getBoolean("mobs.bee.dies-after-sting", beeDiesAfterSting);
+ }
+
+ public boolean blazeRidable = false;
+ public boolean blazeRidableInWater = true;
+ public boolean blazeControllable = true;
+ public double blazeMaxY = 320D;
+ public double blazeMaxHealth = 20.0D;
+ public boolean blazeTakeDamageFromWater = true;
+ public boolean blazeAlwaysDropExp = false;
+ private void blazeSettings() {
+ blazeRidable = getBoolean("mobs.blaze.ridable", blazeRidable);
+ blazeRidableInWater = getBoolean("mobs.blaze.ridable-in-water", blazeRidableInWater);
+ blazeControllable = getBoolean("mobs.blaze.controllable", blazeControllable);
+ blazeMaxY = getDouble("mobs.blaze.ridable-max-y", blazeMaxY);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.blaze.attributes.max-health", blazeMaxHealth);
+ set("mobs.blaze.attributes.max-health", null);
+ set("mobs.blaze.attributes.max_health", oldValue);
+ }
+ blazeMaxHealth = getDouble("mobs.blaze.attributes.max_health", blazeMaxHealth);
+ blazeTakeDamageFromWater = getBoolean("mobs.blaze.takes-damage-from-water", blazeTakeDamageFromWater);
+ blazeAlwaysDropExp = getBoolean("mobs.blaze.always-drop-exp", blazeAlwaysDropExp);
+ }
+
+ public boolean camelRidableInWater = false;
+ public double camelMaxHealthMin = 32.0D;
+ public double camelMaxHealthMax = 32.0D;
+ public double camelJumpStrengthMin = 0.42D;
+ public double camelJumpStrengthMax = 0.42D;
+ public double camelMovementSpeedMin = 0.09D;
+ public double camelMovementSpeedMax = 0.09D;
+ public int camelBreedingTicks = 6000;
+ private void camelSettings() {
+ camelRidableInWater = getBoolean("mobs.camel.ridable-in-water", camelRidableInWater);
+ camelMaxHealthMin = getDouble("mobs.camel.attributes.max_health.min", camelMaxHealthMin);
+ camelMaxHealthMax = getDouble("mobs.camel.attributes.max_health.max", camelMaxHealthMax);
+ camelJumpStrengthMin = getDouble("mobs.camel.attributes.jump_strength.min", camelJumpStrengthMin);
+ camelJumpStrengthMax = getDouble("mobs.camel.attributes.jump_strength.max", camelJumpStrengthMax);
+ camelMovementSpeedMin = getDouble("mobs.camel.attributes.movement_speed.min", camelMovementSpeedMin);
+ camelMovementSpeedMax = getDouble("mobs.camel.attributes.movement_speed.max", camelMovementSpeedMax);
+ camelBreedingTicks = getInt("mobs.camel.breeding-delay-ticks", camelBreedingTicks);
+ }
+
+ public boolean catRidable = false;
+ public boolean catRidableInWater = true;
+ public boolean catControllable = true;
+ public double catMaxHealth = 10.0D;
+ public int catSpawnDelay = 1200;
+ public int catSpawnSwampHutScanRange = 16;
+ public int catSpawnVillageScanRange = 48;
+ public int catBreedingTicks = 6000;
+ public DyeColor catDefaultCollarColor = DyeColor.RED;
+ public boolean catTakeDamageFromWater = false;
+ public boolean catAlwaysDropExp = false;
+ private void catSettings() {
+ catRidable = getBoolean("mobs.cat.ridable", catRidable);
+ catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater);
+ catControllable = getBoolean("mobs.cat.controllable", catControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.cat.attributes.max-health", catMaxHealth);
+ set("mobs.cat.attributes.max-health", null);
+ set("mobs.cat.attributes.max_health", oldValue);
+ }
+ catMaxHealth = getDouble("mobs.cat.attributes.max_health", catMaxHealth);
+ catSpawnDelay = getInt("mobs.cat.spawn-delay", catSpawnDelay);
+ catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange);
+ catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange);
+ catBreedingTicks = getInt("mobs.cat.breeding-delay-ticks", catBreedingTicks);
+ try {
+ catDefaultCollarColor = DyeColor.valueOf(getString("mobs.cat.default-collar-color", catDefaultCollarColor.name()));
+ } catch (IllegalArgumentException ignore) {
+ catDefaultCollarColor = DyeColor.RED;
+ }
+ catTakeDamageFromWater = getBoolean("mobs.cat.takes-damage-from-water", catTakeDamageFromWater);
+ catAlwaysDropExp = getBoolean("mobs.cat.always-drop-exp", catAlwaysDropExp);
+ }
+
+ public boolean caveSpiderRidable = false;
+ public boolean caveSpiderRidableInWater = true;
+ public boolean caveSpiderControllable = true;
+ public double caveSpiderMaxHealth = 12.0D;
+ public boolean caveSpiderTakeDamageFromWater = false;
+ public boolean caveSpiderAlwaysDropExp = false;
+ private void caveSpiderSettings() {
+ caveSpiderRidable = getBoolean("mobs.cave_spider.ridable", caveSpiderRidable);
+ caveSpiderRidableInWater = getBoolean("mobs.cave_spider.ridable-in-water", caveSpiderRidableInWater);
+ caveSpiderControllable = getBoolean("mobs.cave_spider.controllable", caveSpiderControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.cave_spider.attributes.max-health", caveSpiderMaxHealth);
+ set("mobs.cave_spider.attributes.max-health", null);
+ set("mobs.cave_spider.attributes.max_health", oldValue);
+ }
+ caveSpiderMaxHealth = getDouble("mobs.cave_spider.attributes.max_health", caveSpiderMaxHealth);
+ caveSpiderTakeDamageFromWater = getBoolean("mobs.cave_spider.takes-damage-from-water", caveSpiderTakeDamageFromWater);
+ caveSpiderAlwaysDropExp = getBoolean("mobs.cave_spider.always-drop-exp", caveSpiderAlwaysDropExp);
+ }
+
+ public boolean chickenRidable = false;
+ public boolean chickenRidableInWater = false;
+ public boolean chickenControllable = true;
+ public double chickenMaxHealth = 4.0D;
+ public boolean chickenRetaliate = false;
+ public int chickenBreedingTicks = 6000;
+ public boolean chickenTakeDamageFromWater = false;
+ public boolean chickenAlwaysDropExp = false;
+ private void chickenSettings() {
+ chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable);
+ chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater);
+ chickenControllable = getBoolean("mobs.chicken.controllable", chickenControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.chicken.attributes.max-health", chickenMaxHealth);
+ set("mobs.chicken.attributes.max-health", null);
+ set("mobs.chicken.attributes.max_health", oldValue);
+ }
+ chickenMaxHealth = getDouble("mobs.chicken.attributes.max_health", chickenMaxHealth);
+ chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate);
+ chickenBreedingTicks = getInt("mobs.chicken.breeding-delay-ticks", chickenBreedingTicks);
+ chickenTakeDamageFromWater = getBoolean("mobs.chicken.takes-damage-from-water", chickenTakeDamageFromWater);
+ chickenAlwaysDropExp = getBoolean("mobs.chicken.always-drop-exp", chickenAlwaysDropExp);
+ }
+
+ public boolean codRidable = false;
+ public boolean codControllable = true;
+ public double codMaxHealth = 3.0D;
+ public boolean codTakeDamageFromWater = false;
+ public boolean codAlwaysDropExp = false;
+ private void codSettings() {
+ codRidable = getBoolean("mobs.cod.ridable", codRidable);
+ codControllable = getBoolean("mobs.cod.controllable", codControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.cod.attributes.max-health", codMaxHealth);
+ set("mobs.cod.attributes.max-health", null);
+ set("mobs.cod.attributes.max_health", oldValue);
+ }
+ codMaxHealth = getDouble("mobs.cod.attributes.max_health", codMaxHealth);
+ codTakeDamageFromWater = getBoolean("mobs.cod.takes-damage-from-water", codTakeDamageFromWater);
+ codAlwaysDropExp = getBoolean("mobs.cod.always-drop-exp", codAlwaysDropExp);
+ }
+
+ public boolean cowRidable = false;
+ public boolean cowRidableInWater = true;
+ public boolean cowControllable = true;
+ public double cowMaxHealth = 10.0D;
+ public int cowFeedMushrooms = 0;
+ public int cowBreedingTicks = 6000;
+ public boolean cowTakeDamageFromWater = false;
+ public double cowNaturallyAggressiveToPlayersChance = 0.0D;
+ public double cowNaturallyAggressiveToPlayersDamage = 2.0D;
+ public boolean cowAlwaysDropExp = false;
+ private void cowSettings() {
+ if (PurpurConfig.version < 22) {
+ double oldValue = getDouble("mobs.cow.naturally-aggressive-to-players-chance", cowNaturallyAggressiveToPlayersChance);
+ set("mobs.cow.naturally-aggressive-to-players-chance", null);
+ set("mobs.cow.naturally-aggressive-to-players.chance", oldValue);
+ }
+ cowRidable = getBoolean("mobs.cow.ridable", cowRidable);
+ cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater);
+ cowControllable = getBoolean("mobs.cow.controllable", cowControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.cow.attributes.max-health", cowMaxHealth);
+ set("mobs.cow.attributes.max-health", null);
+ set("mobs.cow.attributes.max_health", oldValue);
+ }
+ cowMaxHealth = getDouble("mobs.cow.attributes.max_health", cowMaxHealth);
+ cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms);
+ cowBreedingTicks = getInt("mobs.cow.breeding-delay-ticks", cowBreedingTicks);
+ cowTakeDamageFromWater = getBoolean("mobs.cow.takes-damage-from-water", cowTakeDamageFromWater);
+ cowNaturallyAggressiveToPlayersChance = getDouble("mobs.cow.naturally-aggressive-to-players.chance", cowNaturallyAggressiveToPlayersChance);
+ cowNaturallyAggressiveToPlayersDamage = getDouble("mobs.cow.naturally-aggressive-to-players.damage", cowNaturallyAggressiveToPlayersDamage);
+ cowAlwaysDropExp = getBoolean("mobs.cow.always-drop-exp", cowAlwaysDropExp);
+ }
+
+ public boolean creeperRidable = false;
+ public boolean creeperRidableInWater = true;
+ public boolean creeperControllable = true;
+ public double creeperMaxHealth = 20.0D;
+ public double creeperChargedChance = 0.0D;
+ public boolean creeperAllowGriefing = true;
+ public boolean creeperBypassMobGriefing = false;
+ public boolean creeperTakeDamageFromWater = false;
+ public boolean creeperExplodeWhenKilled = false;
+ public boolean creeperHealthRadius = false;
+ public boolean creeperAlwaysDropExp = false;
+ public double creeperHeadVisibilityPercent = 0.5D;
+ public boolean creeperEncircleTarget = false;
+ private void creeperSettings() {
+ creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable);
+ creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater);
+ creeperControllable = getBoolean("mobs.creeper.controllable", creeperControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.creeper.attributes.max-health", creeperMaxHealth);
+ set("mobs.creeper.attributes.max-health", null);
+ set("mobs.creeper.attributes.max_health", oldValue);
+ }
+ creeperMaxHealth = getDouble("mobs.creeper.attributes.max_health", creeperMaxHealth);
+ creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance);
+ creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing);
+ creeperBypassMobGriefing = getBoolean("mobs.creeper.bypass-mob-griefing", creeperBypassMobGriefing);
+ creeperTakeDamageFromWater = getBoolean("mobs.creeper.takes-damage-from-water", creeperTakeDamageFromWater);
+ creeperExplodeWhenKilled = getBoolean("mobs.creeper.explode-when-killed", creeperExplodeWhenKilled);
+ creeperHealthRadius = getBoolean("mobs.creeper.health-impacts-explosion", creeperHealthRadius);
+ creeperAlwaysDropExp = getBoolean("mobs.creeper.always-drop-exp", creeperAlwaysDropExp);
+ creeperHeadVisibilityPercent = getDouble("mobs.creeper.head-visibility-percent", creeperHeadVisibilityPercent);
+ creeperEncircleTarget = getBoolean("mobs.creeper.encircle-target", creeperEncircleTarget);
+ }
+
+ public boolean dolphinRidable = false;
+ public boolean dolphinControllable = true;
+ public int dolphinSpitCooldown = 20;
+ public float dolphinSpitSpeed = 1.0F;
+ public float dolphinSpitDamage = 2.0F;
+ public double dolphinMaxHealth = 10.0D;
+ public boolean dolphinDisableTreasureSearching = false;
+ public boolean dolphinTakeDamageFromWater = false;
+ public double dolphinNaturallyAggressiveToPlayersChance = 0.0D;
+ public boolean dolphinAlwaysDropExp = false;
+ private void dolphinSettings() {
+ dolphinRidable = getBoolean("mobs.dolphin.ridable", dolphinRidable);
+ dolphinControllable = getBoolean("mobs.dolphin.controllable", dolphinControllable);
+ dolphinSpitCooldown = getInt("mobs.dolphin.spit.cooldown", dolphinSpitCooldown);
+ dolphinSpitSpeed = (float) getDouble("mobs.dolphin.spit.speed", dolphinSpitSpeed);
+ dolphinSpitDamage = (float) getDouble("mobs.dolphin.spit.damage", dolphinSpitDamage);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.dolphin.attributes.max-health", dolphinMaxHealth);
+ set("mobs.dolphin.attributes.max-health", null);
+ set("mobs.dolphin.attributes.max_health", oldValue);
+ }
+ dolphinMaxHealth = getDouble("mobs.dolphin.attributes.max_health", dolphinMaxHealth);
+ dolphinDisableTreasureSearching = getBoolean("mobs.dolphin.disable-treasure-searching", dolphinDisableTreasureSearching);
+ dolphinTakeDamageFromWater = getBoolean("mobs.dolphin.takes-damage-from-water", dolphinTakeDamageFromWater);
+ dolphinNaturallyAggressiveToPlayersChance = getDouble("mobs.dolphin.naturally-aggressive-to-players-chance", dolphinNaturallyAggressiveToPlayersChance);
+ dolphinAlwaysDropExp = getBoolean("mobs.dolphin.always-drop-exp", dolphinAlwaysDropExp);
+ }
+
+ public boolean donkeyRidableInWater = false;
+ public double donkeyMaxHealthMin = 15.0D;
+ public double donkeyMaxHealthMax = 30.0D;
+ public double donkeyJumpStrengthMin = 0.5D;
+ public double donkeyJumpStrengthMax = 0.5D;
+ public double donkeyMovementSpeedMin = 0.175D;
+ public double donkeyMovementSpeedMax = 0.175D;
+ public int donkeyBreedingTicks = 6000;
+ public boolean donkeyTakeDamageFromWater = false;
+ public boolean donkeyAlwaysDropExp = false;
+ private void donkeySettings() {
+ donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater);
+ if (PurpurConfig.version < 10) {
+ double oldMin = getDouble("mobs.donkey.attributes.max-health.min", donkeyMaxHealthMin);
+ double oldMax = getDouble("mobs.donkey.attributes.max-health.max", donkeyMaxHealthMax);
+ set("mobs.donkey.attributes.max-health", null);
+ set("mobs.donkey.attributes.max_health.min", oldMin);
+ set("mobs.donkey.attributes.max_health.max", oldMax);
+ }
+ donkeyMaxHealthMin = getDouble("mobs.donkey.attributes.max_health.min", donkeyMaxHealthMin);
+ donkeyMaxHealthMax = getDouble("mobs.donkey.attributes.max_health.max", donkeyMaxHealthMax);
+ donkeyJumpStrengthMin = getDouble("mobs.donkey.attributes.jump_strength.min", donkeyJumpStrengthMin);
+ donkeyJumpStrengthMax = getDouble("mobs.donkey.attributes.jump_strength.max", donkeyJumpStrengthMax);
+ donkeyMovementSpeedMin = getDouble("mobs.donkey.attributes.movement_speed.min", donkeyMovementSpeedMin);
+ donkeyMovementSpeedMax = getDouble("mobs.donkey.attributes.movement_speed.max", donkeyMovementSpeedMax);
+ donkeyBreedingTicks = getInt("mobs.donkey.breeding-delay-ticks", donkeyBreedingTicks);
+ donkeyTakeDamageFromWater = getBoolean("mobs.donkey.takes-damage-from-water", donkeyTakeDamageFromWater);
+ donkeyAlwaysDropExp = getBoolean("mobs.donkey.always-drop-exp", donkeyAlwaysDropExp);
+ }
+
+ public boolean drownedRidable = false;
+ public boolean drownedRidableInWater = true;
+ public boolean drownedControllable = true;
+ public double drownedMaxHealth = 20.0D;
+ public double drownedSpawnReinforcements = 0.1D;
+ public boolean drownedJockeyOnlyBaby = true;
+ public double drownedJockeyChance = 0.05D;
+ public boolean drownedJockeyTryExistingChickens = true;
+ public boolean drownedTakeDamageFromWater = false;
+ public boolean drownedBreakDoors = false;
+ public boolean drownedAlwaysDropExp = false;
+ private void drownedSettings() {
+ drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable);
+ drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater);
+ drownedControllable = getBoolean("mobs.drowned.controllable", drownedControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.drowned.attributes.max-health", drownedMaxHealth);
+ set("mobs.drowned.attributes.max-health", null);
+ set("mobs.drowned.attributes.max_health", oldValue);
+ }
+ drownedMaxHealth = getDouble("mobs.drowned.attributes.max_health", drownedMaxHealth);
+ drownedSpawnReinforcements = getDouble("mobs.drowned.attributes.spawn_reinforcements", drownedSpawnReinforcements);
+ drownedJockeyOnlyBaby = getBoolean("mobs.drowned.jockey.only-babies", drownedJockeyOnlyBaby);
+ drownedJockeyChance = getDouble("mobs.drowned.jockey.chance", drownedJockeyChance);
+ drownedJockeyTryExistingChickens = getBoolean("mobs.drowned.jockey.try-existing-chickens", drownedJockeyTryExistingChickens);
+ drownedTakeDamageFromWater = getBoolean("mobs.drowned.takes-damage-from-water", drownedTakeDamageFromWater);
+ drownedBreakDoors = getBoolean("mobs.drowned.can-break-doors", drownedBreakDoors);
+ drownedAlwaysDropExp = getBoolean("mobs.drowned.always-drop-exp", drownedAlwaysDropExp);
+ }
+
+ public boolean elderGuardianRidable = false;
+ public boolean elderGuardianControllable = true;
+ public double elderGuardianMaxHealth = 80.0D;
+ public boolean elderGuardianTakeDamageFromWater = false;
+ public boolean elderGuardianAlwaysDropExp = false;
+ private void elderGuardianSettings() {
+ elderGuardianRidable = getBoolean("mobs.elder_guardian.ridable", elderGuardianRidable);
+ elderGuardianControllable = getBoolean("mobs.elder_guardian.controllable", elderGuardianControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.elder_guardian.attributes.max-health", elderGuardianMaxHealth);
+ set("mobs.elder_guardian.attributes.max-health", null);
+ set("mobs.elder_guardian.attributes.max_health", oldValue);
+ }
+ elderGuardianMaxHealth = getDouble("mobs.elder_guardian.attributes.max_health", elderGuardianMaxHealth);
+ elderGuardianTakeDamageFromWater = getBoolean("mobs.elder_guardian.takes-damage-from-water", elderGuardianTakeDamageFromWater);
+ elderGuardianAlwaysDropExp = getBoolean("mobs.elder_guardian.always-drop-exp", elderGuardianAlwaysDropExp);
+ }
+
+ public boolean enchantmentTableLapisPersists = false;
+ private void enchantmentTableSettings() {
+ enchantmentTableLapisPersists = getBoolean("blocks.enchantment-table.lapis-persists", enchantmentTableLapisPersists);
+ }
+
+ public boolean enderDragonRidable = false;
+ public boolean enderDragonRidableInWater = true;
+ public boolean enderDragonControllable = true;
+ public double enderDragonMaxY = 320D;
+ public double enderDragonMaxHealth = 200.0D;
+ public boolean enderDragonAlwaysDropsFullExp = false;
+ public boolean enderDragonBypassMobGriefing = false;
+ public boolean enderDragonTakeDamageFromWater = false;
+ public boolean enderDragonCanRideVehicles = false;
+ private void enderDragonSettings() {
+ enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable);
+ enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater);
+ enderDragonControllable = getBoolean("mobs.ender_dragon.controllable", enderDragonControllable);
+ enderDragonMaxY = getDouble("mobs.ender_dragon.ridable-max-y", enderDragonMaxY);
+ if (PurpurConfig.version < 8) {
+ double oldValue = getDouble("mobs.ender_dragon.max-health", enderDragonMaxHealth);
+ set("mobs.ender_dragon.max-health", null);
+ set("mobs.ender_dragon.attributes.max_health", oldValue);
+ } else if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.ender_dragon.attributes.max-health", enderDragonMaxHealth);
+ set("mobs.ender_dragon.attributes.max-health", null);
+ set("mobs.ender_dragon.attributes.max_health", oldValue);
+ }
+ enderDragonMaxHealth = getDouble("mobs.ender_dragon.attributes.max_health", enderDragonMaxHealth);
+ enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp);
+ enderDragonBypassMobGriefing = getBoolean("mobs.ender_dragon.bypass-mob-griefing", enderDragonBypassMobGriefing);
+ enderDragonTakeDamageFromWater = getBoolean("mobs.ender_dragon.takes-damage-from-water", enderDragonTakeDamageFromWater);
+ enderDragonCanRideVehicles = getBoolean("mobs.ender_dragon.can-ride-vehicles", enderDragonCanRideVehicles);
+ }
+
+ public boolean endermanRidable = false;
+ public boolean endermanRidableInWater = true;
+ public boolean endermanControllable = true;
+ public double endermanMaxHealth = 40.0D;
+ public boolean endermanAllowGriefing = true;
+ public boolean endermanDespawnEvenWithBlock = false;
+ public boolean endermanBypassMobGriefing = false;
+ public boolean endermanTakeDamageFromWater = true;
+ public boolean endermanAggroEndermites = true;
+ public boolean endermanAggroEndermitesOnlyIfPlayerSpawned = false;
+ public boolean endermanIgnorePlayerDragonHead = false;
+ public boolean endermanDisableStareAggro = false;
+ public boolean endermanIgnoreProjectiles = false;
+ public boolean endermanAlwaysDropExp = false;
+ private void endermanSettings() {
+ endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable);
+ endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater);
+ endermanControllable = getBoolean("mobs.enderman.controllable", endermanControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.enderman.attributes.max-health", endermanMaxHealth);
+ set("mobs.enderman.attributes.max-health", null);
+ set("mobs.enderman.attributes.max_health", oldValue);
+ }
+ if (PurpurConfig.version < 15) {
+ // remove old option
+ set("mobs.enderman.aggressive-towards-spawned-endermites", null);
+ }
+ endermanMaxHealth = getDouble("mobs.enderman.attributes.max_health", endermanMaxHealth);
+ endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing);
+ endermanDespawnEvenWithBlock = getBoolean("mobs.enderman.can-despawn-with-held-block", endermanDespawnEvenWithBlock);
+ endermanBypassMobGriefing = getBoolean("mobs.enderman.bypass-mob-griefing", endermanBypassMobGriefing);
+ endermanTakeDamageFromWater = getBoolean("mobs.enderman.takes-damage-from-water", endermanTakeDamageFromWater);
+ endermanAggroEndermites = getBoolean("mobs.enderman.aggressive-towards-endermites", endermanAggroEndermites);
+ endermanAggroEndermitesOnlyIfPlayerSpawned = getBoolean("mobs.enderman.aggressive-towards-endermites-only-spawned-by-player-thrown-ender-pearls", endermanAggroEndermitesOnlyIfPlayerSpawned);
+ endermanIgnorePlayerDragonHead = getBoolean("mobs.enderman.ignore-players-wearing-dragon-head", endermanIgnorePlayerDragonHead);
+ endermanDisableStareAggro = getBoolean("mobs.enderman.disable-player-stare-aggression", endermanDisableStareAggro);
+ endermanIgnoreProjectiles = getBoolean("mobs.enderman.ignore-projectiles", endermanIgnoreProjectiles);
+ endermanAlwaysDropExp = getBoolean("mobs.enderman.always-drop-exp", endermanAlwaysDropExp);
+ }
+
+ public boolean endermiteRidable = false;
+ public boolean endermiteRidableInWater = true;
+ public boolean endermiteControllable = true;
+ public double endermiteMaxHealth = 8.0D;
+ public boolean endermiteTakeDamageFromWater = false;
+ public boolean endermiteAlwaysDropExp = false;
+ private void endermiteSettings() {
+ endermiteRidable = getBoolean("mobs.endermite.ridable", endermiteRidable);
+ endermiteRidableInWater = getBoolean("mobs.endermite.ridable-in-water", endermiteRidableInWater);
+ endermiteControllable = getBoolean("mobs.endermite.controllable", endermiteControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.endermite.attributes.max-health", endermiteMaxHealth);
+ set("mobs.endermite.attributes.max-health", null);
+ set("mobs.endermite.attributes.max_health", oldValue);
+ }
+ endermiteMaxHealth = getDouble("mobs.endermite.attributes.max_health", endermiteMaxHealth);
+ endermiteTakeDamageFromWater = getBoolean("mobs.endermite.takes-damage-from-water", endermiteTakeDamageFromWater);
+ endermiteAlwaysDropExp = getBoolean("mobs.endermite.always-drop-exp", endermiteAlwaysDropExp);
+ }
+
+ public boolean evokerRidable = false;
+ public boolean evokerRidableInWater = true;
+ public boolean evokerControllable = true;
+ public double evokerMaxHealth = 24.0D;
+ public boolean evokerBypassMobGriefing = false;
+ public boolean evokerTakeDamageFromWater = false;
+ public boolean evokerAlwaysDropExp = false;
+ private void evokerSettings() {
+ evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable);
+ evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater);
+ evokerControllable = getBoolean("mobs.evoker.controllable", evokerControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.evoker.attributes.max-health", evokerMaxHealth);
+ set("mobs.evoker.attributes.max-health", null);
+ set("mobs.evoker.attributes.max_health", oldValue);
+ }
+ evokerMaxHealth = getDouble("mobs.evoker.attributes.max_health", evokerMaxHealth);
+ evokerBypassMobGriefing = getBoolean("mobs.evoker.bypass-mob-griefing", evokerBypassMobGriefing);
+ evokerTakeDamageFromWater = getBoolean("mobs.evoker.takes-damage-from-water", evokerTakeDamageFromWater);
+ evokerAlwaysDropExp = getBoolean("mobs.evoker.always-drop-exp", evokerAlwaysDropExp);
+ }
+
+ public boolean foxRidable = false;
+ public boolean foxRidableInWater = true;
+ public boolean foxControllable = true;
+ public double foxMaxHealth = 10.0D;
+ public boolean foxTypeChangesWithTulips = false;
+ public int foxBreedingTicks = 6000;
+ public boolean foxBypassMobGriefing = false;
+ public boolean foxTakeDamageFromWater = false;
+ public boolean foxAlwaysDropExp = false;
+ private void foxSettings() {
+ foxRidable = getBoolean("mobs.fox.ridable", foxRidable);
+ foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater);
+ foxControllable = getBoolean("mobs.fox.controllable", foxControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.fox.attributes.max-health", foxMaxHealth);
+ set("mobs.fox.attributes.max-health", null);
+ set("mobs.fox.attributes.max_health", oldValue);
+ }
+ foxMaxHealth = getDouble("mobs.fox.attributes.max_health", foxMaxHealth);
+ foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips);
+ foxBreedingTicks = getInt("mobs.fox.breeding-delay-ticks", foxBreedingTicks);
+ foxBypassMobGriefing = getBoolean("mobs.fox.bypass-mob-griefing", foxBypassMobGriefing);
+ foxTakeDamageFromWater = getBoolean("mobs.fox.takes-damage-from-water", foxTakeDamageFromWater);
+ foxAlwaysDropExp = getBoolean("mobs.fox.always-drop-exp", foxAlwaysDropExp);
+ }
+
+ public boolean frogRidable = false;
+ public boolean frogRidableInWater = true;
+ public boolean frogControllable = true;
+ public float frogRidableJumpHeight = 0.65F;
+ public int frogBreedingTicks = 6000;
+ private void frogSettings() {
+ frogRidable = getBoolean("mobs.frog.ridable", frogRidable);
+ frogRidableInWater = getBoolean("mobs.frog.ridable-in-water", frogRidableInWater);
+ frogControllable = getBoolean("mobs.frog.controllable", frogControllable);
+ frogRidableJumpHeight = (float) getDouble("mobs.frog.ridable-jump-height", frogRidableJumpHeight);
+ frogBreedingTicks = getInt("mobs.frog.breeding-delay-ticks", frogBreedingTicks);
+ }
+
+ public boolean ghastRidable = false;
+ public boolean ghastRidableInWater = true;
+ public boolean ghastControllable = true;
+ public double ghastMaxY = 320D;
+ public double ghastMaxHealth = 10.0D;
+ public boolean ghastTakeDamageFromWater = false;
+ public boolean ghastAlwaysDropExp = false;
+ private void ghastSettings() {
+ ghastRidable = getBoolean("mobs.ghast.ridable", ghastRidable);
+ ghastRidableInWater = getBoolean("mobs.ghast.ridable-in-water", ghastRidableInWater);
+ ghastControllable = getBoolean("mobs.ghast.controllable", ghastControllable);
+ ghastMaxY = getDouble("mobs.ghast.ridable-max-y", ghastMaxY);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.ghast.attributes.max-health", ghastMaxHealth);
+ set("mobs.ghast.attributes.max-health", null);
+ set("mobs.ghast.attributes.max_health", oldValue);
+ }
+ ghastMaxHealth = getDouble("mobs.ghast.attributes.max_health", ghastMaxHealth);
+ ghastTakeDamageFromWater = getBoolean("mobs.ghast.takes-damage-from-water", ghastTakeDamageFromWater);
+ ghastAlwaysDropExp = getBoolean("mobs.ghast.always-drop-exp", ghastAlwaysDropExp);
+ }
+
+ public boolean giantRidable = false;
+ public boolean giantRidableInWater = true;
+ public boolean giantControllable = true;
+ public double giantMovementSpeed = 0.5D;
+ public double giantAttackDamage = 50.0D;
+ public double giantMaxHealth = 100.0D;
+ public float giantStepHeight = 2.0F;
+ public float giantJumpHeight = 1.0F;
+ public boolean giantHaveAI = false;
+ public boolean giantHaveHostileAI = false;
+ public boolean giantTakeDamageFromWater = false;
+ public boolean giantAlwaysDropExp = false;
+ private void giantSettings() {
+ giantRidable = getBoolean("mobs.giant.ridable", giantRidable);
+ giantRidableInWater = getBoolean("mobs.giant.ridable-in-water", giantRidableInWater);
+ giantControllable = getBoolean("mobs.giant.controllable", giantControllable);
+ giantMovementSpeed = getDouble("mobs.giant.movement-speed", giantMovementSpeed);
+ giantAttackDamage = getDouble("mobs.giant.attack-damage", giantAttackDamage);
+ if (PurpurConfig.version < 8) {
+ double oldValue = getDouble("mobs.giant.max-health", giantMaxHealth);
+ set("mobs.giant.max-health", null);
+ set("mobs.giant.attributes.max_health", oldValue);
+ } else if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.giant.attributes.max-health", giantMaxHealth);
+ set("mobs.giant.attributes.max-health", null);
+ set("mobs.giant.attributes.max_health", oldValue);
+ }
+ giantMaxHealth = getDouble("mobs.giant.attributes.max_health", giantMaxHealth);
+ giantStepHeight = (float) getDouble("mobs.giant.step-height", giantStepHeight);
+ giantJumpHeight = (float) getDouble("mobs.giant.jump-height", giantJumpHeight);
+ giantHaveAI = getBoolean("mobs.giant.have-ai", giantHaveAI);
+ giantHaveHostileAI = getBoolean("mobs.giant.have-hostile-ai", giantHaveHostileAI);
+ giantTakeDamageFromWater = getBoolean("mobs.giant.takes-damage-from-water", giantTakeDamageFromWater);
+ giantAlwaysDropExp = getBoolean("mobs.giant.always-drop-exp", giantAlwaysDropExp);
+ }
+
+ public boolean glowSquidRidable = false;
+ public boolean glowSquidControllable = true;
+ public double glowSquidMaxHealth = 10.0D;
+ public boolean glowSquidsCanFly = false;
+ public boolean glowSquidTakeDamageFromWater = false;
+ public boolean glowSquidAlwaysDropExp = false;
+ private void glowSquidSettings() {
+ glowSquidRidable = getBoolean("mobs.glow_squid.ridable", glowSquidRidable);
+ glowSquidControllable = getBoolean("mobs.glow_squid.controllable", glowSquidControllable);
+ glowSquidMaxHealth = getDouble("mobs.glow_squid.attributes.max_health", glowSquidMaxHealth);
+ glowSquidsCanFly = getBoolean("mobs.glow_squid.can-fly", glowSquidsCanFly);
+ glowSquidTakeDamageFromWater = getBoolean("mobs.glow_squid.takes-damage-from-water", glowSquidTakeDamageFromWater);
+ glowSquidAlwaysDropExp = getBoolean("mobs.glow_squid.always-drop-exp", glowSquidAlwaysDropExp);
+ }
+
+ public boolean goatRidable = false;
+ public boolean goatRidableInWater = true;
+ public boolean goatControllable = true;
+ public double goatMaxHealth = 10.0D;
+ public int goatBreedingTicks = 6000;
+ public boolean goatTakeDamageFromWater = false;
+ public boolean goatAlwaysDropExp = false;
+ private void goatSettings() {
+ goatRidable = getBoolean("mobs.goat.ridable", goatRidable);
+ goatRidableInWater = getBoolean("mobs.goat.ridable-in-water", goatRidableInWater);
+ goatControllable = getBoolean("mobs.goat.controllable", goatControllable);
+ goatMaxHealth = getDouble("mobs.goat.attributes.max_health", goatMaxHealth);
+ goatBreedingTicks = getInt("mobs.goat.breeding-delay-ticks", goatBreedingTicks);
+ goatTakeDamageFromWater = getBoolean("mobs.goat.takes-damage-from-water", goatTakeDamageFromWater);
+ goatAlwaysDropExp = getBoolean("mobs.goat.always-drop-exp", goatAlwaysDropExp);
+ }
+
+ public boolean guardianRidable = false;
+ public boolean guardianControllable = true;
+ public double guardianMaxHealth = 30.0D;
+ public boolean guardianTakeDamageFromWater = false;
+ public boolean guardianAlwaysDropExp = false;
+ private void guardianSettings() {
+ guardianRidable = getBoolean("mobs.guardian.ridable", guardianRidable);
+ guardianControllable = getBoolean("mobs.guardian.controllable", guardianControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.guardian.attributes.max-health", guardianMaxHealth);
+ set("mobs.guardian.attributes.max-health", null);
+ set("mobs.guardian.attributes.max_health", oldValue);
+ }
+ guardianMaxHealth = getDouble("mobs.guardian.attributes.max_health", guardianMaxHealth);
+ guardianTakeDamageFromWater = getBoolean("mobs.guardian.takes-damage-from-water", guardianTakeDamageFromWater);
+ guardianAlwaysDropExp = getBoolean("mobs.guardian.always-drop-exp", guardianAlwaysDropExp);
+ }
+
+ public boolean forceHalloweenSeason = false;
+ public float chanceHeadHalloweenOnEntity = 0.25F;
+ private void halloweenSetting() {
+ forceHalloweenSeason = getBoolean("gameplay-mechanics.halloween.force", forceHalloweenSeason);
+ chanceHeadHalloweenOnEntity = (float) getDouble("gameplay-mechanics.halloween.head-chance", chanceHeadHalloweenOnEntity);
+ }
+
+ public boolean hoglinRidable = false;
+ public boolean hoglinRidableInWater = true;
+ public boolean hoglinControllable = true;
+ public double hoglinMaxHealth = 40.0D;
+ public int hoglinBreedingTicks = 6000;
+ public boolean hoglinTakeDamageFromWater = false;
+ public boolean hoglinAlwaysDropExp = false;
+ private void hoglinSettings() {
+ hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable);
+ hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater);
+ hoglinControllable = getBoolean("mobs.hoglin.controllable", hoglinControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.hoglin.attributes.max-health", hoglinMaxHealth);
+ set("mobs.hoglin.attributes.max-health", null);
+ set("mobs.hoglin.attributes.max_health", oldValue);
+ }
+ hoglinMaxHealth = getDouble("mobs.hoglin.attributes.max_health", hoglinMaxHealth);
+ hoglinBreedingTicks = getInt("mobs.hoglin.breeding-delay-ticks", hoglinBreedingTicks);
+ hoglinTakeDamageFromWater = getBoolean("mobs.hoglin.takes-damage-from-water", hoglinTakeDamageFromWater);
+ hoglinAlwaysDropExp = getBoolean("mobs.hoglin.always-drop-exp", hoglinAlwaysDropExp);
+ }
+
+ public boolean horseRidableInWater = false;
+ public double horseMaxHealthMin = 15.0D;
+ public double horseMaxHealthMax = 30.0D;
+ public double horseJumpStrengthMin = 0.4D;
+ public double horseJumpStrengthMax = 1.0D;
+ public double horseMovementSpeedMin = 0.1125D;
+ public double horseMovementSpeedMax = 0.3375D;
+ public int horseBreedingTicks = 6000;
+ public boolean horseTakeDamageFromWater = false;
+ public boolean horseAlwaysDropExp = false;
+ private void horseSettings() {
+ horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater);
+ if (PurpurConfig.version < 10) {
+ double oldMin = getDouble("mobs.horse.attributes.max-health.min", horseMaxHealthMin);
+ double oldMax = getDouble("mobs.horse.attributes.max-health.max", horseMaxHealthMax);
+ set("mobs.horse.attributes.max-health", null);
+ set("mobs.horse.attributes.max_health.min", oldMin);
+ set("mobs.horse.attributes.max_health.max", oldMax);
+ }
+ horseMaxHealthMin = getDouble("mobs.horse.attributes.max_health.min", horseMaxHealthMin);
+ horseMaxHealthMax = getDouble("mobs.horse.attributes.max_health.max", horseMaxHealthMax);
+ horseJumpStrengthMin = getDouble("mobs.horse.attributes.jump_strength.min", horseJumpStrengthMin);
+ horseJumpStrengthMax = getDouble("mobs.horse.attributes.jump_strength.max", horseJumpStrengthMax);
+ horseMovementSpeedMin = getDouble("mobs.horse.attributes.movement_speed.min", horseMovementSpeedMin);
+ horseMovementSpeedMax = getDouble("mobs.horse.attributes.movement_speed.max", horseMovementSpeedMax);
+ horseBreedingTicks = getInt("mobs.horse.breeding-delay-ticks", horseBreedingTicks);
+ horseTakeDamageFromWater = getBoolean("mobs.horse.takes-damage-from-water", horseTakeDamageFromWater);
+ horseAlwaysDropExp = getBoolean("mobs.horse.always-drop-exp", horseAlwaysDropExp);
+ }
+
+ public boolean huskRidable = false;
+ public boolean huskRidableInWater = true;
+ public boolean huskControllable = true;
+ public double huskMaxHealth = 20.0D;
+ public double huskSpawnReinforcements = 0.1D;
+ public boolean huskJockeyOnlyBaby = true;
+ public double huskJockeyChance = 0.05D;
+ public boolean huskJockeyTryExistingChickens = true;
+ public boolean huskTakeDamageFromWater = false;
+ public boolean huskAlwaysDropExp = false;
+ private void huskSettings() {
+ huskRidable = getBoolean("mobs.husk.ridable", huskRidable);
+ huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater);
+ huskControllable = getBoolean("mobs.husk.controllable", huskControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.husk.attributes.max-health", huskMaxHealth);
+ set("mobs.husk.attributes.max-health", null);
+ set("mobs.husk.attributes.max_health", oldValue);
+ }
+ huskMaxHealth = getDouble("mobs.husk.attributes.max_health", huskMaxHealth);
+ huskSpawnReinforcements = getDouble("mobs.husk.attributes.spawn_reinforcements", huskSpawnReinforcements);
+ huskJockeyOnlyBaby = getBoolean("mobs.husk.jockey.only-babies", huskJockeyOnlyBaby);
+ huskJockeyChance = getDouble("mobs.husk.jockey.chance", huskJockeyChance);
+ huskJockeyTryExistingChickens = getBoolean("mobs.husk.jockey.try-existing-chickens", huskJockeyTryExistingChickens);
+ huskTakeDamageFromWater = getBoolean("mobs.husk.takes-damage-from-water", huskTakeDamageFromWater);
+ huskAlwaysDropExp = getBoolean("mobs.husk.always-drop-exp", huskAlwaysDropExp);
+ }
+
+ public boolean illusionerRidable = false;
+ public boolean illusionerRidableInWater = true;
+ public boolean illusionerControllable = true;
+ public double illusionerMovementSpeed = 0.5D;
+ public double illusionerFollowRange = 18.0D;
+ public double illusionerMaxHealth = 32.0D;
+ public boolean illusionerTakeDamageFromWater = false;
+ public boolean illusionerAlwaysDropExp = false;
+ private void illusionerSettings() {
+ illusionerRidable = getBoolean("mobs.illusioner.ridable", illusionerRidable);
+ illusionerRidableInWater = getBoolean("mobs.illusioner.ridable-in-water", illusionerRidableInWater);
+ illusionerControllable = getBoolean("mobs.illusioner.controllable", illusionerControllable);
+ illusionerMovementSpeed = getDouble("mobs.illusioner.movement-speed", illusionerMovementSpeed);
+ illusionerFollowRange = getDouble("mobs.illusioner.follow-range", illusionerFollowRange);
+ if (PurpurConfig.version < 8) {
+ double oldValue = getDouble("mobs.illusioner.max-health", illusionerMaxHealth);
+ set("mobs.illusioner.max-health", null);
+ set("mobs.illusioner.attributes.max_health", oldValue);
+ } else if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth);
+ set("mobs.illusioner.attributes.max-health", null);
+ set("mobs.illusioner.attributes.max_health", oldValue);
+ }
+ illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max_health", illusionerMaxHealth);
+ illusionerTakeDamageFromWater = getBoolean("mobs.illusioner.takes-damage-from-water", illusionerTakeDamageFromWater);
+ illusionerAlwaysDropExp = getBoolean("mobs.illusioner.always-drop-exp", illusionerAlwaysDropExp);
+ }
+
+ public boolean ironGolemRidable = false;
+ public boolean ironGolemRidableInWater = true;
+ public boolean ironGolemControllable = true;
+ public boolean ironGolemCanSwim = false;
+ public double ironGolemMaxHealth = 100.0D;
+ public boolean ironGolemTakeDamageFromWater = false;
+ public boolean ironGolemPoppyCalm = false;
+ public boolean ironGolemHealCalm = false;
+ public boolean ironGolemAlwaysDropExp = false;
+ private void ironGolemSettings() {
+ ironGolemRidable = getBoolean("mobs.iron_golem.ridable", ironGolemRidable);
+ ironGolemRidableInWater = getBoolean("mobs.iron_golem.ridable-in-water", ironGolemRidableInWater);
+ ironGolemControllable = getBoolean("mobs.iron_golem.controllable", ironGolemControllable);
+ ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.iron_golem.attributes.max-health", ironGolemMaxHealth);
+ set("mobs.iron_golem.attributes.max-health", null);
+ set("mobs.iron_golem.attributes.max_health", oldValue);
+ }
+ ironGolemMaxHealth = getDouble("mobs.iron_golem.attributes.max_health", ironGolemMaxHealth);
+ ironGolemTakeDamageFromWater = getBoolean("mobs.iron_golem.takes-damage-from-water", ironGolemTakeDamageFromWater);
+ ironGolemPoppyCalm = getBoolean("mobs.iron_golem.poppy-calms-anger", ironGolemPoppyCalm);
+ ironGolemHealCalm = getBoolean("mobs.iron_golem.healing-calms-anger", ironGolemHealCalm);
+ ironGolemAlwaysDropExp = getBoolean("mobs.iron_golem.always-drop-exp", ironGolemAlwaysDropExp);
+ }
+
+ public boolean llamaRidable = false;
+ public boolean llamaRidableInWater = false;
+ public boolean llamaControllable = true;
+ public double llamaMaxHealthMin = 15.0D;
+ public double llamaMaxHealthMax = 30.0D;
+ public double llamaJumpStrengthMin = 0.5D;
+ public double llamaJumpStrengthMax = 0.5D;
+ public double llamaMovementSpeedMin = 0.175D;
+ public double llamaMovementSpeedMax = 0.175D;
+ public int llamaBreedingTicks = 6000;
+ public boolean llamaTakeDamageFromWater = false;
+ public boolean llamaJoinCaravans = true;
+ public boolean llamaAlwaysDropExp = false;
+ private void llamaSettings() {
+ llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable);
+ llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater);
+ llamaControllable = getBoolean("mobs.llama.controllable", llamaControllable);
+ if (PurpurConfig.version < 10) {
+ double oldMin = getDouble("mobs.llama.attributes.max-health.min", llamaMaxHealthMin);
+ double oldMax = getDouble("mobs.llama.attributes.max-health.max", llamaMaxHealthMax);
+ set("mobs.llama.attributes.max-health", null);
+ set("mobs.llama.attributes.max_health.min", oldMin);
+ set("mobs.llama.attributes.max_health.max", oldMax);
+ }
+ llamaMaxHealthMin = getDouble("mobs.llama.attributes.max_health.min", llamaMaxHealthMin);
+ llamaMaxHealthMax = getDouble("mobs.llama.attributes.max_health.max", llamaMaxHealthMax);
+ llamaJumpStrengthMin = getDouble("mobs.llama.attributes.jump_strength.min", llamaJumpStrengthMin);
+ llamaJumpStrengthMax = getDouble("mobs.llama.attributes.jump_strength.max", llamaJumpStrengthMax);
+ llamaMovementSpeedMin = getDouble("mobs.llama.attributes.movement_speed.min", llamaMovementSpeedMin);
+ llamaMovementSpeedMax = getDouble("mobs.llama.attributes.movement_speed.max", llamaMovementSpeedMax);
+ llamaBreedingTicks = getInt("mobs.llama.breeding-delay-ticks", llamaBreedingTicks);
+ llamaTakeDamageFromWater = getBoolean("mobs.llama.takes-damage-from-water", llamaTakeDamageFromWater);
+ llamaJoinCaravans = getBoolean("mobs.llama.join-caravans", llamaJoinCaravans);
+ llamaAlwaysDropExp = getBoolean("mobs.llama.always-drop-exp", llamaAlwaysDropExp);
+ }
+
+ public boolean magmaCubeRidable = false;
+ public boolean magmaCubeRidableInWater = true;
+ public boolean magmaCubeControllable = true;
+ public String magmaCubeMaxHealth = "size * size";
+ public String magmaCubeAttackDamage = "size";
+ public Map<Integer, Double> magmaCubeMaxHealthCache = new HashMap<>();
+ public Map<Integer, Double> magmaCubeAttackDamageCache = new HashMap<>();
+ public boolean magmaCubeTakeDamageFromWater = false;
+ public boolean magmaCubeAlwaysDropExp = false;
+ private void magmaCubeSettings() {
+ magmaCubeRidable = getBoolean("mobs.magma_cube.ridable", magmaCubeRidable);
+ magmaCubeRidableInWater = getBoolean("mobs.magma_cube.ridable-in-water", magmaCubeRidableInWater);
+ magmaCubeControllable = getBoolean("mobs.magma_cube.controllable", magmaCubeControllable);
+ if (PurpurConfig.version < 10) {
+ String oldValue = getString("mobs.magma_cube.attributes.max-health", magmaCubeMaxHealth);
+ set("mobs.magma_cube.attributes.max-health", null);
+ set("mobs.magma_cube.attributes.max_health", oldValue);
+ }
+ magmaCubeMaxHealth = getString("mobs.magma_cube.attributes.max_health", magmaCubeMaxHealth);
+ magmaCubeAttackDamage = getString("mobs.magma_cube.attributes.attack_damage", magmaCubeAttackDamage);
+ magmaCubeMaxHealthCache.clear();
+ magmaCubeAttackDamageCache.clear();
+ magmaCubeTakeDamageFromWater = getBoolean("mobs.magma_cube.takes-damage-from-water", magmaCubeTakeDamageFromWater);
+ magmaCubeAlwaysDropExp = getBoolean("mobs.magma_cube.always-drop-exp", magmaCubeAlwaysDropExp);
+ }
+
+ public boolean mooshroomRidable = false;
+ public boolean mooshroomRidableInWater = true;
+ public boolean mooshroomControllable = true;
+ public double mooshroomMaxHealth = 10.0D;
+ public int mooshroomBreedingTicks = 6000;
+ public boolean mooshroomTakeDamageFromWater = false;
+ public boolean mooshroomAlwaysDropExp = false;
+ private void mooshroomSettings() {
+ mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable);
+ mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater);
+ mooshroomControllable = getBoolean("mobs.mooshroom.controllable", mooshroomControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.mooshroom.attributes.max-health", mooshroomMaxHealth);
+ set("mobs.mooshroom.attributes.max-health", null);
+ set("mobs.mooshroom.attributes.max_health", oldValue);
+ }
+ mooshroomMaxHealth = getDouble("mobs.mooshroom.attributes.max_health", mooshroomMaxHealth);
+ mooshroomBreedingTicks = getInt("mobs.mooshroom.breeding-delay-ticks", mooshroomBreedingTicks);
+ mooshroomTakeDamageFromWater = getBoolean("mobs.mooshroom.takes-damage-from-water", mooshroomTakeDamageFromWater);
+ mooshroomAlwaysDropExp = getBoolean("mobs.mooshroom.always-drop-exp", mooshroomAlwaysDropExp);
+ }
+
+ public boolean muleRidableInWater = false;
+ public double muleMaxHealthMin = 15.0D;
+ public double muleMaxHealthMax = 30.0D;
+ public double muleJumpStrengthMin = 0.5D;
+ public double muleJumpStrengthMax = 0.5D;
+ public double muleMovementSpeedMin = 0.175D;
+ public double muleMovementSpeedMax = 0.175D;
+ public int muleBreedingTicks = 6000;
+ public boolean muleTakeDamageFromWater = false;
+ public boolean muleAlwaysDropExp = false;
+ private void muleSettings() {
+ muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater);
+ if (PurpurConfig.version < 10) {
+ double oldMin = getDouble("mobs.mule.attributes.max-health.min", muleMaxHealthMin);
+ double oldMax = getDouble("mobs.mule.attributes.max-health.max", muleMaxHealthMax);
+ set("mobs.mule.attributes.max-health", null);
+ set("mobs.mule.attributes.max_health.min", oldMin);
+ set("mobs.mule.attributes.max_health.max", oldMax);
+ }
+ muleMaxHealthMin = getDouble("mobs.mule.attributes.max_health.min", muleMaxHealthMin);
+ muleMaxHealthMax = getDouble("mobs.mule.attributes.max_health.max", muleMaxHealthMax);
+ muleJumpStrengthMin = getDouble("mobs.mule.attributes.jump_strength.min", muleJumpStrengthMin);
+ muleJumpStrengthMax = getDouble("mobs.mule.attributes.jump_strength.max", muleJumpStrengthMax);
+ muleMovementSpeedMin = getDouble("mobs.mule.attributes.movement_speed.min", muleMovementSpeedMin);
+ muleMovementSpeedMax = getDouble("mobs.mule.attributes.movement_speed.max", muleMovementSpeedMax);
+ muleBreedingTicks = getInt("mobs.mule.breeding-delay-ticks", muleBreedingTicks);
+ muleTakeDamageFromWater = getBoolean("mobs.mule.takes-damage-from-water", muleTakeDamageFromWater);
+ muleAlwaysDropExp = getBoolean("mobs.mule.always-drop-exp", muleAlwaysDropExp);
+ }
+
+ public boolean ocelotRidable = false;
+ public boolean ocelotRidableInWater = true;
+ public boolean ocelotControllable = true;
+ public double ocelotMaxHealth = 10.0D;
+ public int ocelotBreedingTicks = 6000;
+ public boolean ocelotTakeDamageFromWater = false;
+ public boolean ocelotAlwaysDropExp = false;
+ public boolean ocelotSpawnUnderSeaLevel = false;
+ private void ocelotSettings() {
+ ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable);
+ ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater);
+ ocelotControllable = getBoolean("mobs.ocelot.controllable", ocelotControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.ocelot.attributes.max-health", ocelotMaxHealth);
+ set("mobs.ocelot.attributes.max-health", null);
+ set("mobs.ocelot.attributes.max_health", oldValue);
+ }
+ ocelotMaxHealth = getDouble("mobs.ocelot.attributes.max_health", ocelotMaxHealth);
+ ocelotBreedingTicks = getInt("mobs.ocelot.breeding-delay-ticks", ocelotBreedingTicks);
+ ocelotTakeDamageFromWater = getBoolean("mobs.ocelot.takes-damage-from-water", ocelotTakeDamageFromWater);
+ ocelotAlwaysDropExp = getBoolean("mobs.ocelot.always-drop-exp", ocelotAlwaysDropExp);
+ ocelotSpawnUnderSeaLevel = getBoolean("mobs.ocelot.spawn-below-sea-level", ocelotSpawnUnderSeaLevel);
+ }
+
+ public boolean pandaRidable = false;
+ public boolean pandaRidableInWater = true;
+ public boolean pandaControllable = true;
+ public double pandaMaxHealth = 20.0D;
+ public int pandaBreedingTicks = 6000;
+ public boolean pandaTakeDamageFromWater = false;
+ public boolean pandaAlwaysDropExp = false;
+ private void pandaSettings() {
+ pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable);
+ pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater);
+ pandaControllable = getBoolean("mobs.panda.controllable", pandaControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.panda.attributes.max-health", pandaMaxHealth);
+ set("mobs.panda.attributes.max-health", null);
+ set("mobs.panda.attributes.max_health", oldValue);
+ }
+ pandaMaxHealth = getDouble("mobs.panda.attributes.max_health", pandaMaxHealth);
+ pandaBreedingTicks = getInt("mobs.panda.breeding-delay-ticks", pandaBreedingTicks);
+ pandaTakeDamageFromWater = getBoolean("mobs.panda.takes-damage-from-water", pandaTakeDamageFromWater);
+ pandaAlwaysDropExp = getBoolean("mobs.panda.always-drop-exp", pandaAlwaysDropExp);
+ }
+
+ public boolean parrotRidable = false;
+ public boolean parrotRidableInWater = true;
+ public boolean parrotControllable = true;
+ public double parrotMaxY = 320D;
+ public double parrotMaxHealth = 6.0D;
+ public boolean parrotTakeDamageFromWater = false;
+ public boolean parrotBreedable = false;
+ public boolean parrotAlwaysDropExp = false;
+ private void parrotSettings() {
+ parrotRidable = getBoolean("mobs.parrot.ridable", parrotRidable);
+ parrotRidableInWater = getBoolean("mobs.parrot.ridable-in-water", parrotRidableInWater);
+ parrotControllable = getBoolean("mobs.parrot.controllable", parrotControllable);
+ parrotMaxY = getDouble("mobs.parrot.ridable-max-y", parrotMaxY);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.parrot.attributes.max-health", parrotMaxHealth);
+ set("mobs.parrot.attributes.max-health", null);
+ set("mobs.parrot.attributes.max_health", oldValue);
+ }
+ parrotMaxHealth = getDouble("mobs.parrot.attributes.max_health", parrotMaxHealth);
+ parrotTakeDamageFromWater = getBoolean("mobs.parrot.takes-damage-from-water", parrotTakeDamageFromWater);
+ parrotBreedable = getBoolean("mobs.parrot.can-breed", parrotBreedable);
+ parrotAlwaysDropExp = getBoolean("mobs.parrot.always-drop-exp", parrotAlwaysDropExp);
+ }
+
+ public boolean phantomRidable = false;
+ public boolean phantomRidableInWater = true;
+ public boolean phantomControllable = true;
+ public double phantomMaxY = 320D;
+ public float phantomFlameDamage = 1.0F;
+ public int phantomFlameFireTime = 8;
+ public boolean phantomAllowGriefing = false;
+ public String phantomMaxHealth = "20.0";
+ public String phantomAttackDamage = "6 + size";
+ public Map<Integer, Double> phantomMaxHealthCache = new HashMap<>();
+ public Map<Integer, Double> phantomAttackDamageCache = new HashMap<>();
+ public double phantomAttackedByCrystalRadius = 0.0D;
+ public float phantomAttackedByCrystalDamage = 1.0F;
+ public double phantomOrbitCrystalRadius = 0.0D;
+ public int phantomSpawnMinSkyDarkness = 5;
+ public boolean phantomSpawnOnlyAboveSeaLevel = true;
+ public boolean phantomSpawnOnlyWithVisibleSky = true;
+ public double phantomSpawnLocalDifficultyChance = 3.0D;
+ public int phantomSpawnMinPerAttempt = 1;
+ public int phantomSpawnMaxPerAttempt = -1;
+ public int phantomBurnInLight = 0;
+ public boolean phantomIgnorePlayersWithTorch = false;
+ public boolean phantomBurnInDaylight = true;
+ public boolean phantomFlamesOnSwoop = false;
+ public boolean phantomTakeDamageFromWater = false;
+ public boolean phantomAlwaysDropExp = false;
+ public int phantomMinSize = 0;
+ public int phantomMaxSize = 0;
+ private void phantomSettings() {
+ phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable);
+ phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater);
+ phantomControllable = getBoolean("mobs.phantom.controllable", phantomControllable);
+ phantomMaxY = getDouble("mobs.phantom.ridable-max-y", phantomMaxY);
+ phantomFlameDamage = (float) getDouble("mobs.phantom.flames.damage", phantomFlameDamage);
+ phantomFlameFireTime = getInt("mobs.phantom.flames.fire-time", phantomFlameFireTime);
+ phantomAllowGriefing = getBoolean("mobs.phantom.allow-griefing", phantomAllowGriefing);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.phantom.attributes.max-health", Double.parseDouble(phantomMaxHealth));
+ set("mobs.phantom.attributes.max-health", null);
+ set("mobs.phantom.attributes.max_health", String.valueOf(oldValue));
+ }
+ if (PurpurConfig.version < 25) {
+ double oldValue = getDouble("mobs.phantom.attributes.max_health", Double.parseDouble(phantomMaxHealth));
+ set("mobs.phantom.attributes.max_health", String.valueOf(oldValue));
+ }
+ phantomMaxHealth = getString("mobs.phantom.attributes.max_health", phantomMaxHealth);
+ phantomAttackDamage = getString("mobs.phantom.attributes.attack_damage", phantomAttackDamage);
+ phantomMaxHealthCache.clear();
+ phantomAttackDamageCache.clear();
+ phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius);
+ phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage);
+ phantomOrbitCrystalRadius = getDouble("mobs.phantom.orbit-crystal-radius", phantomOrbitCrystalRadius);
+ phantomSpawnMinSkyDarkness = getInt("mobs.phantom.spawn.min-sky-darkness", phantomSpawnMinSkyDarkness);
+ phantomSpawnOnlyAboveSeaLevel = getBoolean("mobs.phantom.spawn.only-above-sea-level", phantomSpawnOnlyAboveSeaLevel);
+ phantomSpawnOnlyWithVisibleSky = getBoolean("mobs.phantom.spawn.only-with-visible-sky", phantomSpawnOnlyWithVisibleSky);
+ phantomSpawnLocalDifficultyChance = getDouble("mobs.phantom.spawn.local-difficulty-chance", phantomSpawnLocalDifficultyChance);
+ phantomSpawnMinPerAttempt = getInt("mobs.phantom.spawn.per-attempt.min", phantomSpawnMinPerAttempt);
+ phantomSpawnMaxPerAttempt = getInt("mobs.phantom.spawn.per-attempt.max", phantomSpawnMaxPerAttempt);
+ phantomBurnInLight = getInt("mobs.phantom.burn-in-light", phantomBurnInLight);
+ phantomBurnInDaylight = getBoolean("mobs.phantom.burn-in-daylight", phantomBurnInDaylight);
+ phantomIgnorePlayersWithTorch = getBoolean("mobs.phantom.ignore-players-with-torch", phantomIgnorePlayersWithTorch);
+ phantomFlamesOnSwoop = getBoolean("mobs.phantom.flames-on-swoop", phantomFlamesOnSwoop);
+ phantomTakeDamageFromWater = getBoolean("mobs.phantom.takes-damage-from-water", phantomTakeDamageFromWater);
+ phantomAlwaysDropExp = getBoolean("mobs.phantom.always-drop-exp", phantomAlwaysDropExp);
+ phantomMinSize = Mth.clamp(getInt("mobs.phantom.size.min", phantomMinSize), 0, 64);
+ phantomMaxSize = Mth.clamp(getInt("mobs.phantom.size.max", phantomMaxSize), 0, 64);
+ if (phantomMinSize > phantomMaxSize) {
+ phantomMinSize = phantomMinSize ^ phantomMaxSize;
+ phantomMaxSize = phantomMinSize ^ phantomMaxSize;
+ phantomMinSize = phantomMinSize ^ phantomMaxSize;
+ }
+ }
+
+ public boolean pigRidable = false;
+ public boolean pigRidableInWater = false;
+ public boolean pigControllable = true;
+ public double pigMaxHealth = 10.0D;
+ public boolean pigGiveSaddleBack = false;
+ public int pigBreedingTicks = 6000;
+ public boolean pigTakeDamageFromWater = false;
+ public boolean pigAlwaysDropExp = false;
+ private void pigSettings() {
+ pigRidable = getBoolean("mobs.pig.ridable", pigRidable);
+ pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater);
+ pigControllable = getBoolean("mobs.pig.controllable", pigControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.pig.attributes.max-health", pigMaxHealth);
+ set("mobs.pig.attributes.max-health", null);
+ set("mobs.pig.attributes.max_health", oldValue);
+ }
+ pigMaxHealth = getDouble("mobs.pig.attributes.max_health", pigMaxHealth);
+ pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack);
+ pigBreedingTicks = getInt("mobs.pig.breeding-delay-ticks", pigBreedingTicks);
+ pigTakeDamageFromWater = getBoolean("mobs.pig.takes-damage-from-water", pigTakeDamageFromWater);
+ pigAlwaysDropExp = getBoolean("mobs.pig.always-drop-exp", pigAlwaysDropExp);
+ }
+
+ public boolean piglinRidable = false;
+ public boolean piglinRidableInWater = true;
+ public boolean piglinControllable = true;
+ public double piglinMaxHealth = 16.0D;
+ public boolean piglinBypassMobGriefing = false;
+ public boolean piglinTakeDamageFromWater = false;
+ public int piglinPortalSpawnModifier = 2000;
+ public boolean piglinAlwaysDropExp = false;
+ public double piglinHeadVisibilityPercent = 0.5D;
+ public boolean piglinIgnoresArmorWithGoldTrim = false;
+ private void piglinSettings() {
+ piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable);
+ piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater);
+ piglinControllable = getBoolean("mobs.piglin.controllable", piglinControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.piglin.attributes.max-health", piglinMaxHealth);
+ set("mobs.piglin.attributes.max-health", null);
+ set("mobs.piglin.attributes.max_health", oldValue);
+ }
+ piglinMaxHealth = getDouble("mobs.piglin.attributes.max_health", piglinMaxHealth);
+ piglinBypassMobGriefing = getBoolean("mobs.piglin.bypass-mob-griefing", piglinBypassMobGriefing);
+ piglinTakeDamageFromWater = getBoolean("mobs.piglin.takes-damage-from-water", piglinTakeDamageFromWater);
+ piglinPortalSpawnModifier = getInt("mobs.piglin.portal-spawn-modifier", piglinPortalSpawnModifier);
+ piglinAlwaysDropExp = getBoolean("mobs.piglin.always-drop-exp", piglinAlwaysDropExp);
+ piglinHeadVisibilityPercent = getDouble("mobs.piglin.head-visibility-percent", piglinHeadVisibilityPercent);
+ piglinIgnoresArmorWithGoldTrim = getBoolean("mobs.piglin.ignores-armor-with-gold-trim", piglinIgnoresArmorWithGoldTrim);
+ }
+
+ public boolean piglinBruteRidable = false;
+ public boolean piglinBruteRidableInWater = true;
+ public boolean piglinBruteControllable = true;
+ public double piglinBruteMaxHealth = 50.0D;
+ public boolean piglinBruteTakeDamageFromWater = false;
+ public boolean piglinBruteAlwaysDropExp = false;
+ private void piglinBruteSettings() {
+ piglinBruteRidable = getBoolean("mobs.piglin_brute.ridable", piglinBruteRidable);
+ piglinBruteRidableInWater = getBoolean("mobs.piglin_brute.ridable-in-water", piglinBruteRidableInWater);
+ piglinBruteControllable = getBoolean("mobs.piglin_brute.controllable", piglinBruteControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.piglin_brute.attributes.max-health", piglinBruteMaxHealth);
+ set("mobs.piglin_brute.attributes.max-health", null);
+ set("mobs.piglin_brute.attributes.max_health", oldValue);
+ }
+ piglinBruteMaxHealth = getDouble("mobs.piglin_brute.attributes.max_health", piglinBruteMaxHealth);
+ piglinBruteTakeDamageFromWater = getBoolean("mobs.piglin_brute.takes-damage-from-water", piglinBruteTakeDamageFromWater);
+ piglinBruteAlwaysDropExp = getBoolean("mobs.piglin_brute.always-drop-exp", piglinBruteAlwaysDropExp);
+ }
+
+ public boolean pillagerRidable = false;
+ public boolean pillagerRidableInWater = true;
+ public boolean pillagerControllable = true;
+ public double pillagerMaxHealth = 24.0D;
+ public boolean pillagerBypassMobGriefing = false;
+ public boolean pillagerTakeDamageFromWater = false;
+ public boolean pillagerAlwaysDropExp = false;
+ private void pillagerSettings() {
+ pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable);
+ pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater);
+ pillagerControllable = getBoolean("mobs.pillager.controllable", pillagerControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.pillager.attributes.max-health", pillagerMaxHealth);
+ set("mobs.pillager.attributes.max-health", null);
+ set("mobs.pillager.attributes.max_health", oldValue);
+ }
+ pillagerMaxHealth = getDouble("mobs.pillager.attributes.max_health", pillagerMaxHealth);
+ pillagerBypassMobGriefing = getBoolean("mobs.pillager.bypass-mob-griefing", pillagerBypassMobGriefing);
+ pillagerTakeDamageFromWater = getBoolean("mobs.pillager.takes-damage-from-water", pillagerTakeDamageFromWater);
+ pillagerAlwaysDropExp = getBoolean("mobs.pillager.always-drop-exp", pillagerAlwaysDropExp);
+ }
+
+ public boolean polarBearRidable = false;
+ public boolean polarBearRidableInWater = true;
+ public boolean polarBearControllable = true;
+ public double polarBearMaxHealth = 30.0D;
+ public String polarBearBreedableItemString = "";
+ public Item polarBearBreedableItem = null;
+ public int polarBearBreedingTicks = 6000;
+ public boolean polarBearTakeDamageFromWater = false;
+ public boolean polarBearAlwaysDropExp = false;
+ private void polarBearSettings() {
+ polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable);
+ polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater);
+ polarBearControllable = getBoolean("mobs.polar_bear.controllable", polarBearControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.polar_bear.attributes.max-health", polarBearMaxHealth);
+ set("mobs.polar_bear.attributes.max-health", null);
+ set("mobs.polar_bear.attributes.max_health", oldValue);
+ }
+ polarBearMaxHealth = getDouble("mobs.polar_bear.attributes.max_health", polarBearMaxHealth);
+ polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString);
+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(polarBearBreedableItemString));
+ if (item != Items.AIR) polarBearBreedableItem = item;
+ polarBearBreedingTicks = getInt("mobs.polar_bear.breeding-delay-ticks", polarBearBreedingTicks);
+ polarBearTakeDamageFromWater = getBoolean("mobs.polar_bear.takes-damage-from-water", polarBearTakeDamageFromWater);
+ polarBearAlwaysDropExp = getBoolean("mobs.polar_bear.always-drop-exp", polarBearAlwaysDropExp);
+ }
+
+ public boolean pufferfishRidable = false;
+ public boolean pufferfishControllable = true;
+ public double pufferfishMaxHealth = 3.0D;
+ public boolean pufferfishTakeDamageFromWater = false;
+ public boolean pufferfishAlwaysDropExp = false;
+ private void pufferfishSettings() {
+ pufferfishRidable = getBoolean("mobs.pufferfish.ridable", pufferfishRidable);
+ pufferfishControllable = getBoolean("mobs.pufferfish.controllable", pufferfishControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.pufferfish.attributes.max-health", pufferfishMaxHealth);
+ set("mobs.pufferfish.attributes.max-health", null);
+ set("mobs.pufferfish.attributes.max_health", oldValue);
+ }
+ pufferfishMaxHealth = getDouble("mobs.pufferfish.attributes.max_health", pufferfishMaxHealth);
+ pufferfishTakeDamageFromWater = getBoolean("mobs.pufferfish.takes-damage-from-water", pufferfishTakeDamageFromWater);
+ pufferfishAlwaysDropExp = getBoolean("mobs.pufferfish.always-drop-exp", pufferfishAlwaysDropExp);
+ }
+
+ public boolean rabbitRidable = false;
+ public boolean rabbitRidableInWater = true;
+ public boolean rabbitControllable = true;
+ public double rabbitMaxHealth = 3.0D;
+ public double rabbitNaturalToast = 0.0D;
+ public double rabbitNaturalKiller = 0.0D;
+ public int rabbitBreedingTicks = 6000;
+ public boolean rabbitBypassMobGriefing = false;
+ public boolean rabbitTakeDamageFromWater = false;
+ public boolean rabbitAlwaysDropExp = false;
+ private void rabbitSettings() {
+ rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable);
+ rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater);
+ rabbitControllable = getBoolean("mobs.rabbit.controllable", rabbitControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.rabbit.attributes.max-health", rabbitMaxHealth);
+ set("mobs.rabbit.attributes.max-health", null);
+ set("mobs.rabbit.attributes.max_health", oldValue);
+ }
+ rabbitMaxHealth = getDouble("mobs.rabbit.attributes.max_health", rabbitMaxHealth);
+ rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast);
+ rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller);
+ rabbitBreedingTicks = getInt("mobs.rabbit.breeding-delay-ticks", rabbitBreedingTicks);
+ rabbitBypassMobGriefing = getBoolean("mobs.rabbit.bypass-mob-griefing", rabbitBypassMobGriefing);
+ rabbitTakeDamageFromWater = getBoolean("mobs.rabbit.takes-damage-from-water", rabbitTakeDamageFromWater);
+ rabbitAlwaysDropExp = getBoolean("mobs.rabbit.always-drop-exp", rabbitAlwaysDropExp);
+ }
+
+ public boolean ravagerRidable = false;
+ public boolean ravagerRidableInWater = false;
+ public boolean ravagerControllable = true;
+ public double ravagerMaxHealth = 100.0D;
+ public boolean ravagerBypassMobGriefing = false;
+ public boolean ravagerTakeDamageFromWater = false;
+ public List<Block> ravagerGriefableBlocks = new ArrayList<>();
+ public boolean ravagerAlwaysDropExp = false;
+ private void ravagerSettings() {
+ ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable);
+ ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater);
+ ravagerControllable = getBoolean("mobs.ravager.controllable", ravagerControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.ravager.attributes.max-health", ravagerMaxHealth);
+ set("mobs.ravager.attributes.max-health", null);
+ set("mobs.ravager.attributes.max_health", oldValue);
+ }
+ ravagerMaxHealth = getDouble("mobs.ravager.attributes.max_health", ravagerMaxHealth);
+ ravagerBypassMobGriefing = getBoolean("mobs.ravager.bypass-mob-griefing", ravagerBypassMobGriefing);
+ ravagerTakeDamageFromWater = getBoolean("mobs.ravager.takes-damage-from-water", ravagerTakeDamageFromWater);
+ getList("mobs.ravager.griefable-blocks", new ArrayList<String>(){{
+ add("minecraft:oak_leaves");
+ add("minecraft:spruce_leaves");
+ add("minecraft:birch_leaves");
+ add("minecraft:jungle_leaves");
+ add("minecraft:acacia_leaves");
+ add("minecraft:dark_oak_leaves");
+ add("minecraft:beetroots");
+ add("minecraft:carrots");
+ add("minecraft:potatoes");
+ add("minecraft:wheat");
+ }}).forEach(key -> {
+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key.toString()));
+ if (!block.defaultBlockState().isAir()) {
+ ravagerGriefableBlocks.add(block);
+ }
+ });
+ ravagerAlwaysDropExp = getBoolean("mobs.ravager.always-drop-exp", ravagerAlwaysDropExp);
+ }
+
+ public boolean salmonRidable = false;
+ public boolean salmonControllable = true;
+ public double salmonMaxHealth = 3.0D;
+ public boolean salmonTakeDamageFromWater = false;
+ public boolean salmonAlwaysDropExp = false;
+ private void salmonSettings() {
+ salmonRidable = getBoolean("mobs.salmon.ridable", salmonRidable);
+ salmonControllable = getBoolean("mobs.salmon.controllable", salmonControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.salmon.attributes.max-health", salmonMaxHealth);
+ set("mobs.salmon.attributes.max-health", null);
+ set("mobs.salmon.attributes.max_health", oldValue);
+ }
+ salmonMaxHealth = getDouble("mobs.salmon.attributes.max_health", salmonMaxHealth);
+ salmonTakeDamageFromWater = getBoolean("mobs.salmon.takes-damage-from-water", salmonTakeDamageFromWater);
+ salmonAlwaysDropExp = getBoolean("mobs.salmon.always-drop-exp", salmonAlwaysDropExp);
+ }
+
+ public boolean sheepRidable = false;
+ public boolean sheepRidableInWater = true;
+ public boolean sheepControllable = true;
+ public double sheepMaxHealth = 8.0D;
+ public int sheepBreedingTicks = 6000;
+ public boolean sheepBypassMobGriefing = false;
+ public boolean sheepTakeDamageFromWater = false;
+ public boolean sheepAlwaysDropExp = false;
+ public boolean sheepShearJebRandomColor = false;
+ private void sheepSettings() {
+ sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable);
+ sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater);
+ sheepControllable = getBoolean("mobs.sheep.controllable", sheepControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.sheep.attributes.max-health", sheepMaxHealth);
+ set("mobs.sheep.attributes.max-health", null);
+ set("mobs.sheep.attributes.max_health", oldValue);
+ }
+ sheepMaxHealth = getDouble("mobs.sheep.attributes.max_health", sheepMaxHealth);
+ sheepBreedingTicks = getInt("mobs.sheep.breeding-delay-ticks", sheepBreedingTicks);
+ sheepBypassMobGriefing = getBoolean("mobs.sheep.bypass-mob-griefing", sheepBypassMobGriefing);
+ sheepTakeDamageFromWater = getBoolean("mobs.sheep.takes-damage-from-water", sheepTakeDamageFromWater);
+ sheepAlwaysDropExp = getBoolean("mobs.sheep.always-drop-exp", sheepAlwaysDropExp);
+ sheepShearJebRandomColor = getBoolean("mobs.sheep.jeb-shear-random-color", sheepShearJebRandomColor);
+ }
+
+ public boolean shulkerRidable = false;
+ public boolean shulkerRidableInWater = true;
+ public boolean shulkerControllable = true;
+ public double shulkerMaxHealth = 30.0D;
+ public boolean shulkerTakeDamageFromWater = false;
+ public float shulkerSpawnFromBulletBaseChance = 1.0F;
+ public boolean shulkerSpawnFromBulletRequireOpenLid = true;
+ public double shulkerSpawnFromBulletNearbyRange = 8.0D;
+ public String shulkerSpawnFromBulletNearbyEquation = "(nearby - 1) / 5.0";
+ public boolean shulkerSpawnFromBulletRandomColor = false;
+ public boolean shulkerChangeColorWithDye = false;
+ public boolean shulkerAlwaysDropExp = false;
+ private void shulkerSettings() {
+ shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable);
+ shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater);
+ shulkerControllable = getBoolean("mobs.shulker.controllable", shulkerControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.shulker.attributes.max-health", shulkerMaxHealth);
+ set("mobs.shulker.attributes.max-health", null);
+ set("mobs.shulker.attributes.max_health", oldValue);
+ }
+ shulkerMaxHealth = getDouble("mobs.shulker.attributes.max_health", shulkerMaxHealth);
+ shulkerTakeDamageFromWater = getBoolean("mobs.shulker.takes-damage-from-water", shulkerTakeDamageFromWater);
+ shulkerSpawnFromBulletBaseChance = (float) getDouble("mobs.shulker.spawn-from-bullet.base-chance", shulkerSpawnFromBulletBaseChance);
+ shulkerSpawnFromBulletRequireOpenLid = getBoolean("mobs.shulker.spawn-from-bullet.require-open-lid", shulkerSpawnFromBulletRequireOpenLid);
+ shulkerSpawnFromBulletNearbyRange = getDouble("mobs.shulker.spawn-from-bullet.nearby-range", shulkerSpawnFromBulletNearbyRange);
+ shulkerSpawnFromBulletNearbyEquation = getString("mobs.shulker.spawn-from-bullet.nearby-equation", shulkerSpawnFromBulletNearbyEquation);
+ shulkerSpawnFromBulletRandomColor = getBoolean("mobs.shulker.spawn-from-bullet.random-color", shulkerSpawnFromBulletRandomColor);
+ shulkerChangeColorWithDye = getBoolean("mobs.shulker.change-color-with-dye", shulkerChangeColorWithDye);
+ shulkerAlwaysDropExp = getBoolean("mobs.shulker.always-drop-exp", shulkerAlwaysDropExp);
+ }
+
+ public boolean silverfishRidable = false;
+ public boolean silverfishRidableInWater = true;
+ public boolean silverfishControllable = true;
+ public double silverfishMaxHealth = 8.0D;
+ public boolean silverfishBypassMobGriefing = false;
+ public boolean silverfishTakeDamageFromWater = false;
+ public boolean silverfishAlwaysDropExp = false;
+ private void silverfishSettings() {
+ silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable);
+ silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater);
+ silverfishControllable = getBoolean("mobs.silverfish.controllable", silverfishControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.silverfish.attributes.max-health", silverfishMaxHealth);
+ set("mobs.silverfish.attributes.max-health", null);
+ set("mobs.silverfish.attributes.max_health", oldValue);
+ }
+ silverfishMaxHealth = getDouble("mobs.silverfish.attributes.max_health", silverfishMaxHealth);
+ silverfishBypassMobGriefing = getBoolean("mobs.silverfish.bypass-mob-griefing", silverfishBypassMobGriefing);
+ silverfishTakeDamageFromWater = getBoolean("mobs.silverfish.takes-damage-from-water", silverfishTakeDamageFromWater);
+ silverfishAlwaysDropExp = getBoolean("mobs.silverfish.always-drop-exp", silverfishAlwaysDropExp);
+ }
+
+ public boolean skeletonRidable = false;
+ public boolean skeletonRidableInWater = true;
+ public boolean skeletonControllable = true;
+ public double skeletonMaxHealth = 20.0D;
+ public boolean skeletonTakeDamageFromWater = false;
+ public boolean skeletonAlwaysDropExp = false;
+ public double skeletonHeadVisibilityPercent = 0.5D;
+ public int skeletonFeedWitherRoses = 0;
+ public String skeletonBowAccuracy = "14 - difficulty * 4";
+ public Map<Integer, Float> skeletonBowAccuracyMap = new HashMap<>();
+ private void skeletonSettings() {
+ skeletonRidable = getBoolean("mobs.skeleton.ridable", skeletonRidable);
+ skeletonRidableInWater = getBoolean("mobs.skeleton.ridable-in-water", skeletonRidableInWater);
+ skeletonControllable = getBoolean("mobs.skeleton.controllable", skeletonControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.skeleton.attributes.max-health", skeletonMaxHealth);
+ set("mobs.skeleton.attributes.max-health", null);
+ set("mobs.skeleton.attributes.max_health", oldValue);
+ }
+ skeletonMaxHealth = getDouble("mobs.skeleton.attributes.max_health", skeletonMaxHealth);
+ skeletonTakeDamageFromWater = getBoolean("mobs.skeleton.takes-damage-from-water", skeletonTakeDamageFromWater);
+ skeletonAlwaysDropExp = getBoolean("mobs.skeleton.always-drop-exp", skeletonAlwaysDropExp);
+ skeletonHeadVisibilityPercent = getDouble("mobs.skeleton.head-visibility-percent", skeletonHeadVisibilityPercent);
+ skeletonFeedWitherRoses = getInt("mobs.skeleton.feed-wither-roses", skeletonFeedWitherRoses);
+ final String defaultSkeletonBowAccuracy = skeletonBowAccuracy;
+ skeletonBowAccuracy = getString("mobs.skeleton.bow-accuracy", skeletonBowAccuracy);
+ for (int i = 1; i < 4; i++) {
+ final float divergence;
+ try {
+ divergence = ((Number) Entity.scriptEngine.eval("let difficulty = " + i + "; " + skeletonBowAccuracy)).floatValue();
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ break;
+ }
+ skeletonBowAccuracyMap.put(i, divergence);
+ }
+ }
+
+ public boolean skeletonHorseRidableInWater = true;
+ public boolean skeletonHorseCanSwim = false;
+ public double skeletonHorseMaxHealthMin = 15.0D;
+ public double skeletonHorseMaxHealthMax = 15.0D;
+ public double skeletonHorseJumpStrengthMin = 0.4D;
+ public double skeletonHorseJumpStrengthMax = 1.0D;
+ public double skeletonHorseMovementSpeedMin = 0.2D;
+ public double skeletonHorseMovementSpeedMax = 0.2D;
+ public boolean skeletonHorseTakeDamageFromWater = false;
+ public boolean skeletonHorseAlwaysDropExp = false;
+ private void skeletonHorseSettings() {
+ skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater);
+ skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.skeleton_horse.attributes.max-health", skeletonHorseMaxHealthMin);
+ set("mobs.skeleton_horse.attributes.max-health", null);
+ set("mobs.skeleton_horse.attributes.max_health.min", oldValue);
+ set("mobs.skeleton_horse.attributes.max_health.max", oldValue);
+ }
+ skeletonHorseMaxHealthMin = getDouble("mobs.skeleton_horse.attributes.max_health.min", skeletonHorseMaxHealthMin);
+ skeletonHorseMaxHealthMax = getDouble("mobs.skeleton_horse.attributes.max_health.max", skeletonHorseMaxHealthMax);
+ skeletonHorseJumpStrengthMin = getDouble("mobs.skeleton_horse.attributes.jump_strength.min", skeletonHorseJumpStrengthMin);
+ skeletonHorseJumpStrengthMax = getDouble("mobs.skeleton_horse.attributes.jump_strength.max", skeletonHorseJumpStrengthMax);
+ skeletonHorseMovementSpeedMin = getDouble("mobs.skeleton_horse.attributes.movement_speed.min", skeletonHorseMovementSpeedMin);
+ skeletonHorseMovementSpeedMax = getDouble("mobs.skeleton_horse.attributes.movement_speed.max", skeletonHorseMovementSpeedMax);
+ skeletonHorseTakeDamageFromWater = getBoolean("mobs.skeleton_horse.takes-damage-from-water", skeletonHorseTakeDamageFromWater);
+ skeletonHorseAlwaysDropExp = getBoolean("mobs.skeleton_horse.always-drop-exp", skeletonHorseAlwaysDropExp);
+ }
+
+ public boolean slimeRidable = false;
+ public boolean slimeRidableInWater = true;
+ public boolean slimeControllable = true;
+ public String slimeMaxHealth = "size * size";
+ public String slimeAttackDamage = "size";
+ public Map<Integer, Double> slimeMaxHealthCache = new HashMap<>();
+ public Map<Integer, Double> slimeAttackDamageCache = new HashMap<>();
+ public boolean slimeTakeDamageFromWater = false;
+ public boolean slimeAlwaysDropExp = false;
+ private void slimeSettings() {
+ slimeRidable = getBoolean("mobs.slime.ridable", slimeRidable);
+ slimeRidableInWater = getBoolean("mobs.slime.ridable-in-water", slimeRidableInWater);
+ slimeControllable = getBoolean("mobs.slime.controllable", slimeControllable);
+ if (PurpurConfig.version < 10) {
+ String oldValue = getString("mobs.slime.attributes.max-health", slimeMaxHealth);
+ set("mobs.slime.attributes.max-health", null);
+ set("mobs.slime.attributes.max_health", oldValue);
+ }
+ slimeMaxHealth = getString("mobs.slime.attributes.max_health", slimeMaxHealth);
+ slimeAttackDamage = getString("mobs.slime.attributes.attack_damage", slimeAttackDamage);
+ slimeMaxHealthCache.clear();
+ slimeAttackDamageCache.clear();
+ slimeTakeDamageFromWater = getBoolean("mobs.slime.takes-damage-from-water", slimeTakeDamageFromWater);
+ slimeAlwaysDropExp = getBoolean("mobs.slime.always-drop-exp", slimeAlwaysDropExp);
+ }
+
+ public boolean snowGolemRidable = false;
+ public boolean snowGolemRidableInWater = true;
+ public boolean snowGolemControllable = true;
+ public boolean snowGolemLeaveTrailWhenRidden = false;
+ public double snowGolemMaxHealth = 4.0D;
+ public boolean snowGolemDropsPumpkin = true;
+ public boolean snowGolemPutPumpkinBack = false;
+ public int snowGolemSnowBallMin = 20;
+ public int snowGolemSnowBallMax = 20;
+ public float snowGolemSnowBallModifier = 10.0F;
+ public double snowGolemAttackDistance = 1.25D;
+ public boolean snowGolemBypassMobGriefing = false;
+ public boolean snowGolemTakeDamageFromWater = true;
+ public boolean snowGolemAlwaysDropExp = false;
+ private void snowGolemSettings() {
+ snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable);
+ snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater);
+ snowGolemControllable = getBoolean("mobs.snow_golem.controllable", snowGolemControllable);
+ snowGolemLeaveTrailWhenRidden = getBoolean("mobs.snow_golem.leave-trail-when-ridden", snowGolemLeaveTrailWhenRidden);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.snow_golem.attributes.max-health", snowGolemMaxHealth);
+ set("mobs.snow_golem.attributes.max-health", null);
+ set("mobs.snow_golem.attributes.max_health", oldValue);
+ }
+ snowGolemMaxHealth = getDouble("mobs.snow_golem.attributes.max_health", snowGolemMaxHealth);
+ snowGolemDropsPumpkin = getBoolean("mobs.snow_golem.drop-pumpkin-when-sheared", snowGolemDropsPumpkin);
+ snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack);
+ snowGolemSnowBallMin = getInt("mobs.snow_golem.min-shoot-interval-ticks", snowGolemSnowBallMin);
+ snowGolemSnowBallMax = getInt("mobs.snow_golem.max-shoot-interval-ticks", snowGolemSnowBallMax);
+ snowGolemSnowBallModifier = (float) getDouble("mobs.snow_golem.snow-ball-modifier", snowGolemSnowBallModifier);
+ snowGolemAttackDistance = getDouble("mobs.snow_golem.attack-distance", snowGolemAttackDistance);
+ snowGolemBypassMobGriefing = getBoolean("mobs.snow_golem.bypass-mob-griefing", snowGolemBypassMobGriefing);
+ snowGolemTakeDamageFromWater = getBoolean("mobs.snow_golem.takes-damage-from-water", snowGolemTakeDamageFromWater);
+ snowGolemAlwaysDropExp = getBoolean("mobs.snow_golem.always-drop-exp", snowGolemAlwaysDropExp);
+ }
+
+ public boolean snifferRidable = false;
+ public boolean snifferRidableInWater = true;
+ public boolean snifferControllable = true;
+ public double snifferMaxHealth = 14.0D;
+ public int snifferBreedingTicks = 6000;
+ private void snifferSettings() {
+ snifferRidable = getBoolean("mobs.sniffer.ridable", snifferRidable);
+ snifferRidableInWater = getBoolean("mobs.sniffer.ridable-in-water", snifferRidableInWater);
+ snifferControllable = getBoolean("mobs.sniffer.controllable", snifferControllable);
+ snifferMaxHealth = getDouble("mobs.sniffer.attributes.max_health", snifferMaxHealth);
+ snifferBreedingTicks = getInt("mobs.sniffer.breeding-delay-ticks", chickenBreedingTicks);
+ }
+
+ public boolean squidRidable = false;
+ public boolean squidControllable = true;
+ public double squidMaxHealth = 10.0D;
+ public boolean squidImmuneToEAR = true;
+ public double squidOffsetWaterCheck = 0.0D;
+ public boolean squidsCanFly = false;
+ public boolean squidTakeDamageFromWater = false;
+ public boolean squidAlwaysDropExp = false;
+ private void squidSettings() {
+ squidRidable = getBoolean("mobs.squid.ridable", squidRidable);
+ squidControllable = getBoolean("mobs.squid.controllable", squidControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.squid.attributes.max-health", squidMaxHealth);
+ set("mobs.squid.attributes.max-health", null);
+ set("mobs.squid.attributes.max_health", oldValue);
+ }
+ squidMaxHealth = getDouble("mobs.squid.attributes.max_health", squidMaxHealth);
+ squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR);
+ squidOffsetWaterCheck = getDouble("mobs.squid.water-offset-check", squidOffsetWaterCheck);
+ squidsCanFly = getBoolean("mobs.squid.can-fly", squidsCanFly);
+ squidTakeDamageFromWater = getBoolean("mobs.squid.takes-damage-from-water", squidTakeDamageFromWater);
+ squidAlwaysDropExp = getBoolean("mobs.squid.always-drop-exp", squidAlwaysDropExp);
+ }
+
+ public boolean spiderRidable = false;
+ public boolean spiderRidableInWater = false;
+ public boolean spiderControllable = true;
+ public double spiderMaxHealth = 16.0D;
+ public boolean spiderTakeDamageFromWater = false;
+ public boolean spiderAlwaysDropExp = false;
+ private void spiderSettings() {
+ spiderRidable = getBoolean("mobs.spider.ridable", spiderRidable);
+ spiderRidableInWater = getBoolean("mobs.spider.ridable-in-water", spiderRidableInWater);
+ spiderControllable = getBoolean("mobs.spider.controllable", spiderControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.spider.attributes.max-health", spiderMaxHealth);
+ set("mobs.spider.attributes.max-health", null);
+ set("mobs.spider.attributes.max_health", oldValue);
+ }
+ spiderMaxHealth = getDouble("mobs.spider.attributes.max_health", spiderMaxHealth);
+ spiderTakeDamageFromWater = getBoolean("mobs.spider.takes-damage-from-water", spiderTakeDamageFromWater);
+ spiderAlwaysDropExp = getBoolean("mobs.spider.always-drop-exp", spiderAlwaysDropExp);
+ }
+
+ public boolean strayRidable = false;
+ public boolean strayRidableInWater = true;
+ public boolean strayControllable = true;
+ public double strayMaxHealth = 20.0D;
+ public boolean strayTakeDamageFromWater = false;
+ public boolean strayAlwaysDropExp = false;
+ private void straySettings() {
+ strayRidable = getBoolean("mobs.stray.ridable", strayRidable);
+ strayRidableInWater = getBoolean("mobs.stray.ridable-in-water", strayRidableInWater);
+ strayControllable = getBoolean("mobs.stray.controllable", strayControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.stray.attributes.max-health", strayMaxHealth);
+ set("mobs.stray.attributes.max-health", null);
+ set("mobs.stray.attributes.max_health", oldValue);
+ }
+ strayMaxHealth = getDouble("mobs.stray.attributes.max_health", strayMaxHealth);
+ strayTakeDamageFromWater = getBoolean("mobs.stray.takes-damage-from-water", strayTakeDamageFromWater);
+ strayAlwaysDropExp = getBoolean("mobs.stray.always-drop-exp", strayAlwaysDropExp);
+ }
+
+ public boolean striderRidable = false;
+ public boolean striderRidableInWater = false;
+ public boolean striderControllable = true;
+ public double striderMaxHealth = 20.0D;
+ public int striderBreedingTicks = 6000;
+ public boolean striderGiveSaddleBack = false;
+ public boolean striderTakeDamageFromWater = true;
+ public boolean striderAlwaysDropExp = false;
+ private void striderSettings() {
+ striderRidable = getBoolean("mobs.strider.ridable", striderRidable);
+ striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater);
+ striderControllable = getBoolean("mobs.strider.controllable", striderControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.strider.attributes.max-health", striderMaxHealth);
+ set("mobs.strider.attributes.max-health", null);
+ set("mobs.strider.attributes.max_health", oldValue);
+ }
+ striderMaxHealth = getDouble("mobs.strider.attributes.max_health", striderMaxHealth);
+ striderBreedingTicks = getInt("mobs.strider.breeding-delay-ticks", striderBreedingTicks);
+ striderGiveSaddleBack = getBoolean("mobs.strider.give-saddle-back", striderGiveSaddleBack);
+ striderTakeDamageFromWater = getBoolean("mobs.strider.takes-damage-from-water", striderTakeDamageFromWater);
+ striderAlwaysDropExp = getBoolean("mobs.strider.always-drop-exp", striderAlwaysDropExp);
+ }
+
+ public boolean tadpoleRidable = false;
+ public boolean tadpoleRidableInWater = true;
+ public boolean tadpoleControllable = true;
+ private void tadpoleSettings() {
+ tadpoleRidable = getBoolean("mobs.tadpole.ridable", tadpoleRidable);
+ tadpoleRidableInWater = getBoolean("mobs.tadpole.ridable-in-water", tadpoleRidableInWater);
+ tadpoleControllable = getBoolean("mobs.tadpole.controllable", tadpoleControllable);
+ }
+
+ public boolean traderLlamaRidable = false;
+ public boolean traderLlamaRidableInWater = false;
+ public boolean traderLlamaControllable = true;
+ public double traderLlamaMaxHealthMin = 15.0D;
+ public double traderLlamaMaxHealthMax = 30.0D;
+ public double traderLlamaJumpStrengthMin = 0.5D;
+ public double traderLlamaJumpStrengthMax = 0.5D;
+ public double traderLlamaMovementSpeedMin = 0.175D;
+ public double traderLlamaMovementSpeedMax = 0.175D;
+ public int traderLlamaBreedingTicks = 6000;
+ public boolean traderLlamaTakeDamageFromWater = false;
+ public boolean traderLlamaAlwaysDropExp = false;
+ private void traderLlamaSettings() {
+ traderLlamaRidable = getBoolean("mobs.trader_llama.ridable", traderLlamaRidable);
+ traderLlamaRidableInWater = getBoolean("mobs.trader_llama.ridable-in-water", traderLlamaRidableInWater);
+ traderLlamaControllable = getBoolean("mobs.trader_llama.controllable", traderLlamaControllable);
+ if (PurpurConfig.version < 10) {
+ double oldMin = getDouble("mobs.trader_llama.attributes.max-health.min", traderLlamaMaxHealthMin);
+ double oldMax = getDouble("mobs.trader_llama.attributes.max-health.max", traderLlamaMaxHealthMax);
+ set("mobs.trader_llama.attributes.max-health", null);
+ set("mobs.trader_llama.attributes.max_health.min", oldMin);
+ set("mobs.trader_llama.attributes.max_health.max", oldMax);
+ }
+ traderLlamaMaxHealthMin = getDouble("mobs.trader_llama.attributes.max_health.min", traderLlamaMaxHealthMin);
+ traderLlamaMaxHealthMax = getDouble("mobs.trader_llama.attributes.max_health.max", traderLlamaMaxHealthMax);
+ traderLlamaJumpStrengthMin = getDouble("mobs.trader_llama.attributes.jump_strength.min", traderLlamaJumpStrengthMin);
+ traderLlamaJumpStrengthMax = getDouble("mobs.trader_llama.attributes.jump_strength.max", traderLlamaJumpStrengthMax);
+ traderLlamaMovementSpeedMin = getDouble("mobs.trader_llama.attributes.movement_speed.min", traderLlamaMovementSpeedMin);
+ traderLlamaMovementSpeedMax = getDouble("mobs.trader_llama.attributes.movement_speed.max", traderLlamaMovementSpeedMax);
+ traderLlamaBreedingTicks = getInt("mobs.trader_llama.breeding-delay-ticks", traderLlamaBreedingTicks);
+ traderLlamaTakeDamageFromWater = getBoolean("mobs.trader_llama.takes-damage-from-water", traderLlamaTakeDamageFromWater);
+ traderLlamaAlwaysDropExp = getBoolean("mobs.trader_llama.always-drop-exp", traderLlamaAlwaysDropExp);
+ }
+
+ public boolean tropicalFishRidable = false;
+ public boolean tropicalFishControllable = true;
+ public double tropicalFishMaxHealth = 3.0D;
+ public boolean tropicalFishTakeDamageFromWater = false;
+ public boolean tropicalFishAlwaysDropExp = false;
+ private void tropicalFishSettings() {
+ tropicalFishRidable = getBoolean("mobs.tropical_fish.ridable", tropicalFishRidable);
+ tropicalFishControllable = getBoolean("mobs.tropical_fish.controllable", tropicalFishControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.tropical_fish.attributes.max-health", tropicalFishMaxHealth);
+ set("mobs.tropical_fish.attributes.max-health", null);
+ set("mobs.tropical_fish.attributes.max_health", oldValue);
+ }
+ tropicalFishMaxHealth = getDouble("mobs.tropical_fish.attributes.max_health", tropicalFishMaxHealth);
+ tropicalFishTakeDamageFromWater = getBoolean("mobs.tropical_fish.takes-damage-from-water", tropicalFishTakeDamageFromWater);
+ tropicalFishAlwaysDropExp = getBoolean("mobs.tropical_fish.always-drop-exp", tropicalFishAlwaysDropExp);
+ }
+
+ public boolean turtleRidable = false;
+ public boolean turtleRidableInWater = true;
+ public boolean turtleControllable = true;
+ public double turtleMaxHealth = 30.0D;
+ public int turtleBreedingTicks = 6000;
+ public boolean turtleTakeDamageFromWater = false;
+ public boolean turtleAlwaysDropExp = false;
+ private void turtleSettings() {
+ turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable);
+ turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater);
+ turtleControllable = getBoolean("mobs.turtle.controllable", turtleControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.turtle.attributes.max-health", turtleMaxHealth);
+ set("mobs.turtle.attributes.max-health", null);
+ set("mobs.turtle.attributes.max_health", oldValue);
+ }
+ turtleMaxHealth = getDouble("mobs.turtle.attributes.max_health", turtleMaxHealth);
+ turtleBreedingTicks = getInt("mobs.turtle.breeding-delay-ticks", turtleBreedingTicks);
+ turtleTakeDamageFromWater = getBoolean("mobs.turtle.takes-damage-from-water", turtleTakeDamageFromWater);
+ turtleAlwaysDropExp = getBoolean("mobs.turtle.always-drop-exp", turtleAlwaysDropExp);
+ }
+
+ public boolean vexRidable = false;
+ public boolean vexRidableInWater = true;
+ public boolean vexControllable = true;
+ public double vexMaxY = 320D;
+ public double vexMaxHealth = 14.0D;
+ public boolean vexTakeDamageFromWater = false;
+ public boolean vexAlwaysDropExp = false;
+ private void vexSettings() {
+ vexRidable = getBoolean("mobs.vex.ridable", vexRidable);
+ vexRidableInWater = getBoolean("mobs.vex.ridable-in-water", vexRidableInWater);
+ vexControllable = getBoolean("mobs.vex.controllable", vexControllable);
+ vexMaxY = getDouble("mobs.vex.ridable-max-y", vexMaxY);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.vex.attributes.max-health", vexMaxHealth);
+ set("mobs.vex.attributes.max-health", null);
+ set("mobs.vex.attributes.max_health", oldValue);
+ }
+ vexMaxHealth = getDouble("mobs.vex.attributes.max_health", vexMaxHealth);
+ vexTakeDamageFromWater = getBoolean("mobs.vex.takes-damage-from-water", vexTakeDamageFromWater);
+ vexAlwaysDropExp = getBoolean("mobs.vex.always-drop-exp", vexAlwaysDropExp);
+ }
+
+ public boolean villagerRidable = false;
+ public boolean villagerRidableInWater = true;
+ public boolean villagerControllable = true;
+ public double villagerMaxHealth = 20.0D;
+ public boolean villagerFollowEmeraldBlock = false;
+ public boolean villagerCanBeLeashed = false;
+ public boolean villagerCanBreed = true;
+ public int villagerBreedingTicks = 6000;
+ public boolean villagerClericsFarmWarts = false;
+ public boolean villagerClericFarmersThrowWarts = true;
+ public boolean villagerBypassMobGriefing = false;
+ public boolean villagerTakeDamageFromWater = false;
+ public boolean villagerAllowTrading = true;
+ public boolean villagerAlwaysDropExp = false;
+ public int villagerMinimumDemand = 0;
+ public boolean villagerLobotomizeEnabled = false;
+ public int villagerLobotomizeCheckInterval = 100;
+ public boolean villagerDisplayTradeItem = true;
+ public int villagerSpawnIronGolemRadius = 0;
+ public int villagerSpawnIronGolemLimit = 0;
+ private void villagerSettings() {
+ villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable);
+ villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater);
+ villagerControllable = getBoolean("mobs.villager.controllable", villagerControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.villager.attributes.max-health", villagerMaxHealth);
+ set("mobs.villager.attributes.max-health", null);
+ set("mobs.villager.attributes.max_health", oldValue);
+ }
+ villagerMaxHealth = getDouble("mobs.villager.attributes.max_health", villagerMaxHealth);
+ villagerFollowEmeraldBlock = getBoolean("mobs.villager.follow-emerald-blocks", villagerFollowEmeraldBlock);
+ villagerCanBeLeashed = getBoolean("mobs.villager.can-be-leashed", villagerCanBeLeashed);
+ villagerCanBreed = getBoolean("mobs.villager.can-breed", villagerCanBreed);
+ villagerBreedingTicks = getInt("mobs.villager.breeding-delay-ticks", villagerBreedingTicks);
+ villagerClericsFarmWarts = getBoolean("mobs.villager.clerics-farm-warts", villagerClericsFarmWarts);
+ villagerClericFarmersThrowWarts = getBoolean("mobs.villager.cleric-wart-farmers-throw-warts-at-villagers", villagerClericFarmersThrowWarts);
+ villagerBypassMobGriefing = getBoolean("mobs.villager.bypass-mob-griefing", villagerBypassMobGriefing);
+ villagerTakeDamageFromWater = getBoolean("mobs.villager.takes-damage-from-water", villagerTakeDamageFromWater);
+ villagerAllowTrading = getBoolean("mobs.villager.allow-trading", villagerAllowTrading);
+ villagerAlwaysDropExp = getBoolean("mobs.villager.always-drop-exp", villagerAlwaysDropExp);
+ villagerMinimumDemand = getInt("mobs.villager.minimum-demand", villagerMinimumDemand);
+ if (PurpurConfig.version < 9) {
+ boolean oldValue = getBoolean("mobs.villager.lobotomize-1x1", villagerLobotomizeEnabled);
+ set("mobs.villager.lobotomize.enabled", oldValue);
+ set("mobs.villager.lobotomize-1x1", null);
+ }
+ if (PurpurConfig.version < 27) {
+ int oldValue = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheckInterval);
+ set("mobs.villager.lobotomize.check-interval", oldValue == 60 ? 100 : oldValue);
+ }
+ villagerLobotomizeEnabled = getBoolean("mobs.villager.lobotomize.enabled", villagerLobotomizeEnabled);
+ villagerLobotomizeCheckInterval = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheckInterval);
+ villagerDisplayTradeItem = getBoolean("mobs.villager.display-trade-item", villagerDisplayTradeItem);
+ villagerSpawnIronGolemRadius = getInt("mobs.villager.spawn-iron-golem.radius", villagerSpawnIronGolemRadius);
+ villagerSpawnIronGolemLimit = getInt("mobs.villager.spawn-iron-golem.limit", villagerSpawnIronGolemLimit);
+ }
+
+ public boolean vindicatorRidable = false;
+ public boolean vindicatorRidableInWater = true;
+ public boolean vindicatorControllable = true;
+ public double vindicatorMaxHealth = 24.0D;
+ public double vindicatorJohnnySpawnChance = 0D;
+ public boolean vindicatorTakeDamageFromWater = false;
+ public boolean vindicatorAlwaysDropExp = false;
+ private void vindicatorSettings() {
+ vindicatorRidable = getBoolean("mobs.vindicator.ridable", vindicatorRidable);
+ vindicatorRidableInWater = getBoolean("mobs.vindicator.ridable-in-water", vindicatorRidableInWater);
+ vindicatorControllable = getBoolean("mobs.vindicator.controllable", vindicatorControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.vindicator.attributes.max-health", vindicatorMaxHealth);
+ set("mobs.vindicator.attributes.max-health", null);
+ set("mobs.vindicator.attributes.max_health", oldValue);
+ }
+ vindicatorMaxHealth = getDouble("mobs.vindicator.attributes.max_health", vindicatorMaxHealth);
+ vindicatorJohnnySpawnChance = getDouble("mobs.vindicator.johnny.spawn-chance", vindicatorJohnnySpawnChance);
+ vindicatorTakeDamageFromWater = getBoolean("mobs.vindicator.takes-damage-from-water", vindicatorTakeDamageFromWater);
+ vindicatorAlwaysDropExp = getBoolean("mobs.vindicator.always-drop-exp", vindicatorAlwaysDropExp);
+ }
+
+ public boolean wanderingTraderRidable = false;
+ public boolean wanderingTraderRidableInWater = true;
+ public boolean wanderingTraderControllable = true;
+ public double wanderingTraderMaxHealth = 20.0D;
+ public boolean wanderingTraderFollowEmeraldBlock = false;
+ public boolean wanderingTraderCanBeLeashed = false;
+ public boolean wanderingTraderTakeDamageFromWater = false;
+ public boolean wanderingTraderAllowTrading = true;
+ public boolean wanderingTraderAlwaysDropExp = false;
+ private void wanderingTraderSettings() {
+ wanderingTraderRidable = getBoolean("mobs.wandering_trader.ridable", wanderingTraderRidable);
+ wanderingTraderRidableInWater = getBoolean("mobs.wandering_trader.ridable-in-water", wanderingTraderRidableInWater);
+ wanderingTraderControllable = getBoolean("mobs.wandering_trader.controllable", wanderingTraderControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.wandering_trader.attributes.max-health", wanderingTraderMaxHealth);
+ set("mobs.wandering_trader.attributes.max-health", null);
+ set("mobs.wandering_trader.attributes.max_health", oldValue);
+ }
+ wanderingTraderMaxHealth = getDouble("mobs.wandering_trader.attributes.max_health", wanderingTraderMaxHealth);
+ wanderingTraderFollowEmeraldBlock = getBoolean("mobs.wandering_trader.follow-emerald-blocks", wanderingTraderFollowEmeraldBlock);
+ wanderingTraderCanBeLeashed = getBoolean("mobs.wandering_trader.can-be-leashed", wanderingTraderCanBeLeashed);
+ wanderingTraderTakeDamageFromWater = getBoolean("mobs.wandering_trader.takes-damage-from-water", wanderingTraderTakeDamageFromWater);
+ wanderingTraderAllowTrading = getBoolean("mobs.wandering_trader.allow-trading", wanderingTraderAllowTrading);
+ wanderingTraderAlwaysDropExp = getBoolean("mobs.wandering_trader.always-drop-exp", wanderingTraderAlwaysDropExp);
+ }
+
+ public boolean wardenRidable = false;
+ public boolean wardenRidableInWater = true;
+ public boolean wardenControllable = true;
+ private void wardenSettings() {
+ wardenRidable = getBoolean("mobs.warden.ridable", wardenRidable);
+ wardenRidableInWater = getBoolean("mobs.warden.ridable-in-water", wardenRidableInWater);
+ wardenControllable = getBoolean("mobs.warden.controllable", wardenControllable);
+ }
+
+ public boolean witchRidable = false;
+ public boolean witchRidableInWater = true;
+ public boolean witchControllable = true;
+ public double witchMaxHealth = 26.0D;
+ public boolean witchTakeDamageFromWater = false;
+ public boolean witchAlwaysDropExp = false;
+ private void witchSettings() {
+ witchRidable = getBoolean("mobs.witch.ridable", witchRidable);
+ witchRidableInWater = getBoolean("mobs.witch.ridable-in-water", witchRidableInWater);
+ witchControllable = getBoolean("mobs.witch.controllable", witchControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.witch.attributes.max-health", witchMaxHealth);
+ set("mobs.witch.attributes.max-health", null);
+ set("mobs.witch.attributes.max_health", oldValue);
+ }
+ witchMaxHealth = getDouble("mobs.witch.attributes.max_health", witchMaxHealth);
+ witchTakeDamageFromWater = getBoolean("mobs.witch.takes-damage-from-water", witchTakeDamageFromWater);
+ witchAlwaysDropExp = getBoolean("mobs.witch.always-drop-exp", witchAlwaysDropExp);
+ }
+
+ public boolean witherRidable = false;
+ public boolean witherRidableInWater = true;
+ public boolean witherControllable = true;
+ public double witherMaxY = 320D;
+ public double witherMaxHealth = 300.0D;
+ public float witherHealthRegenAmount = 1.0f;
+ public int witherHealthRegenDelay = 20;
+ public boolean witherBypassMobGriefing = false;
+ public boolean witherTakeDamageFromWater = false;
+ public boolean witherCanRideVehicles = false;
+ public float witherExplosionRadius = 1.0F;
+ public boolean witherPlaySpawnSound = true;
+ public boolean witherAlwaysDropExp = false;
+ private void witherSettings() {
+ witherRidable = getBoolean("mobs.wither.ridable", witherRidable);
+ witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater);
+ witherControllable = getBoolean("mobs.wither.controllable", witherControllable);
+ witherMaxY = getDouble("mobs.wither.ridable-max-y", witherMaxY);
+ if (PurpurConfig.version < 8) {
+ double oldValue = getDouble("mobs.wither.max-health", witherMaxHealth);
+ set("mobs.wither.max_health", null);
+ set("mobs.wither.attributes.max-health", oldValue);
+ } else if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.wither.attributes.max-health", witherMaxHealth);
+ set("mobs.wither.attributes.max-health", null);
+ set("mobs.wither.attributes.max_health", oldValue);
+ }
+ witherMaxHealth = getDouble("mobs.wither.attributes.max_health", witherMaxHealth);
+ witherHealthRegenAmount = (float) getDouble("mobs.wither.health-regen-amount", witherHealthRegenAmount);
+ witherHealthRegenDelay = getInt("mobs.wither.health-regen-delay", witherHealthRegenDelay);
+ witherBypassMobGriefing = getBoolean("mobs.wither.bypass-mob-griefing", witherBypassMobGriefing);
+ witherTakeDamageFromWater = getBoolean("mobs.wither.takes-damage-from-water", witherTakeDamageFromWater);
+ witherCanRideVehicles = getBoolean("mobs.wither.can-ride-vehicles", witherCanRideVehicles);
+ witherExplosionRadius = (float) getDouble("mobs.wither.explosion-radius", witherExplosionRadius);
+ witherPlaySpawnSound = getBoolean("mobs.wither.play-spawn-sound", witherPlaySpawnSound);
+ witherAlwaysDropExp = getBoolean("mobs.wither.always-drop-exp", witherAlwaysDropExp);
+ }
+
+ public boolean witherSkeletonRidable = false;
+ public boolean witherSkeletonRidableInWater = true;
+ public boolean witherSkeletonControllable = true;
+ public double witherSkeletonMaxHealth = 20.0D;
+ public boolean witherSkeletonTakeDamageFromWater = false;
+ public boolean witherSkeletonAlwaysDropExp = false;
+ private void witherSkeletonSettings() {
+ witherSkeletonRidable = getBoolean("mobs.wither_skeleton.ridable", witherSkeletonRidable);
+ witherSkeletonRidableInWater = getBoolean("mobs.wither_skeleton.ridable-in-water", witherSkeletonRidableInWater);
+ witherSkeletonControllable = getBoolean("mobs.wither_skeleton.controllable", witherSkeletonControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.wither_skeleton.attributes.max-health", witherSkeletonMaxHealth);
+ set("mobs.wither_skeleton.attributes.max-health", null);
+ set("mobs.wither_skeleton.attributes.max_health", oldValue);
+ }
+ witherSkeletonMaxHealth = getDouble("mobs.wither_skeleton.attributes.max_health", witherSkeletonMaxHealth);
+ witherSkeletonTakeDamageFromWater = getBoolean("mobs.wither_skeleton.takes-damage-from-water", witherSkeletonTakeDamageFromWater);
+ witherSkeletonAlwaysDropExp = getBoolean("mobs.wither_skeleton.always-drop-exp", witherSkeletonAlwaysDropExp);
+ }
+
+ public boolean wolfRidable = false;
+ public boolean wolfRidableInWater = true;
+ public boolean wolfControllable = true;
+ public double wolfMaxHealth = 8.0D;
+ public DyeColor wolfDefaultCollarColor = DyeColor.RED;
+ public boolean wolfMilkCuresRabies = true;
+ public double wolfNaturalRabid = 0.0D;
+ public int wolfBreedingTicks = 6000;
+ public boolean wolfTakeDamageFromWater = false;
+ public boolean wolfAlwaysDropExp = false;
+ private void wolfSettings() {
+ wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable);
+ wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater);
+ wolfControllable = getBoolean("mobs.wolf.controllable", wolfControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.wolf.attributes.max-health", wolfMaxHealth);
+ set("mobs.wolf.attributes.max-health", null);
+ set("mobs.wolf.attributes.max_health", oldValue);
+ }
+ wolfMaxHealth = getDouble("mobs.wolf.attributes.max_health", wolfMaxHealth);
+ try {
+ wolfDefaultCollarColor = DyeColor.valueOf(getString("mobs.wolf.default-collar-color", wolfDefaultCollarColor.name()));
+ } catch (IllegalArgumentException ignore) {
+ wolfDefaultCollarColor = DyeColor.RED;
+ }
+ wolfMilkCuresRabies = getBoolean("mobs.wolf.milk-cures-rabid-wolves", wolfMilkCuresRabies);
+ wolfNaturalRabid = getDouble("mobs.wolf.spawn-rabid-chance", wolfNaturalRabid);
+ wolfBreedingTicks = getInt("mobs.wolf.breeding-delay-ticks", wolfBreedingTicks);
+ wolfTakeDamageFromWater = getBoolean("mobs.wolf.takes-damage-from-water", wolfTakeDamageFromWater);
+ wolfAlwaysDropExp = getBoolean("mobs.wolf.always-drop-exp", wolfAlwaysDropExp);
+ }
+
+ public boolean zoglinRidable = false;
+ public boolean zoglinRidableInWater = true;
+ public boolean zoglinControllable = true;
+ public double zoglinMaxHealth = 40.0D;
+ public boolean zoglinTakeDamageFromWater = false;
+ public boolean zoglinAlwaysDropExp = false;
+ private void zoglinSettings() {
+ zoglinRidable = getBoolean("mobs.zoglin.ridable", zoglinRidable);
+ zoglinRidableInWater = getBoolean("mobs.zoglin.ridable-in-water", zoglinRidableInWater);
+ zoglinControllable = getBoolean("mobs.zoglin.controllable", zoglinControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.zoglin.attributes.max-health", zoglinMaxHealth);
+ set("mobs.zoglin.attributes.max-health", null);
+ set("mobs.zoglin.attributes.max_health", oldValue);
+ }
+ zoglinMaxHealth = getDouble("mobs.zoglin.attributes.max_health", zoglinMaxHealth);
+ zoglinTakeDamageFromWater = getBoolean("mobs.zoglin.takes-damage-from-water", zoglinTakeDamageFromWater);
+ zoglinAlwaysDropExp = getBoolean("mobs.zoglin.always-drop-exp", zoglinAlwaysDropExp);
+ }
+
+ public boolean zombieRidable = false;
+ public boolean zombieRidableInWater = true;
+ public boolean zombieControllable = true;
+ public double zombieMaxHealth = 20.0D;
+ public double zombieSpawnReinforcements = 0.1D;
+ public boolean zombieJockeyOnlyBaby = true;
+ public double zombieJockeyChance = 0.05D;
+ public boolean zombieJockeyTryExistingChickens = true;
+ public boolean zombieAggressiveTowardsVillagerWhenLagging = true;
+ public boolean zombieBypassMobGriefing = false;
+ public boolean zombieTakeDamageFromWater = false;
+ public boolean zombieAlwaysDropExp = false;
+ public double zombieHeadVisibilityPercent = 0.5D;
+ private void zombieSettings() {
+ zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable);
+ zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater);
+ zombieControllable = getBoolean("mobs.zombie.controllable", zombieControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.zombie.attributes.max-health", zombieMaxHealth);
+ set("mobs.zombie.attributes.max-health", null);
+ set("mobs.zombie.attributes.max_health", oldValue);
+ }
+ zombieMaxHealth = getDouble("mobs.zombie.attributes.max_health", zombieMaxHealth);
+ zombieSpawnReinforcements = getDouble("mobs.zombie.attributes.spawn_reinforcements", zombieSpawnReinforcements);
+ zombieJockeyOnlyBaby = getBoolean("mobs.zombie.jockey.only-babies", zombieJockeyOnlyBaby);
+ zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance);
+ zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens);
+ zombieAggressiveTowardsVillagerWhenLagging = getBoolean("mobs.zombie.aggressive-towards-villager-when-lagging", zombieAggressiveTowardsVillagerWhenLagging);
+ zombieBypassMobGriefing = getBoolean("mobs.zombie.bypass-mob-griefing", zombieBypassMobGriefing);
+ zombieTakeDamageFromWater = getBoolean("mobs.zombie.takes-damage-from-water", zombieTakeDamageFromWater);
+ zombieAlwaysDropExp = getBoolean("mobs.zombie.always-drop-exp", zombieAlwaysDropExp);
+ zombieHeadVisibilityPercent = getDouble("mobs.zombie.head-visibility-percent", zombieHeadVisibilityPercent);
+ }
+
+ public boolean zombieHorseRidableInWater = false;
+ public boolean zombieHorseCanSwim = false;
+ public double zombieHorseMaxHealthMin = 15.0D;
+ public double zombieHorseMaxHealthMax = 15.0D;
+ public double zombieHorseJumpStrengthMin = 0.4D;
+ public double zombieHorseJumpStrengthMax = 1.0D;
+ public double zombieHorseMovementSpeedMin = 0.2D;
+ public double zombieHorseMovementSpeedMax = 0.2D;
+ public double zombieHorseSpawnChance = 0.0D;
+ public boolean zombieHorseTakeDamageFromWater = false;
+ public boolean zombieHorseAlwaysDropExp = false;
+ private void zombieHorseSettings() {
+ zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater);
+ zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.zombie_horse.attributes.max-health", zombieHorseMaxHealthMin);
+ set("mobs.zombie_horse.attributes.max-health", null);
+ set("mobs.zombie_horse.attributes.max_health.min", oldValue);
+ set("mobs.zombie_horse.attributes.max_health.max", oldValue);
+ }
+ zombieHorseMaxHealthMin = getDouble("mobs.zombie_horse.attributes.max_health.min", zombieHorseMaxHealthMin);
+ zombieHorseMaxHealthMax = getDouble("mobs.zombie_horse.attributes.max_health.max", zombieHorseMaxHealthMax);
+ zombieHorseJumpStrengthMin = getDouble("mobs.zombie_horse.attributes.jump_strength.min", zombieHorseJumpStrengthMin);
+ zombieHorseJumpStrengthMax = getDouble("mobs.zombie_horse.attributes.jump_strength.max", zombieHorseJumpStrengthMax);
+ zombieHorseMovementSpeedMin = getDouble("mobs.zombie_horse.attributes.movement_speed.min", zombieHorseMovementSpeedMin);
+ zombieHorseMovementSpeedMax = getDouble("mobs.zombie_horse.attributes.movement_speed.max", zombieHorseMovementSpeedMax);
+ zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance);
+ zombieHorseTakeDamageFromWater = getBoolean("mobs.zombie_horse.takes-damage-from-water", zombieHorseTakeDamageFromWater);
+ zombieHorseAlwaysDropExp = getBoolean("mobs.zombie_horse.always-drop-exp", zombieHorseAlwaysDropExp);
+ }
+
+ public boolean zombieVillagerRidable = false;
+ public boolean zombieVillagerRidableInWater = true;
+ public boolean zombieVillagerControllable = true;
+ public double zombieVillagerMaxHealth = 20.0D;
+ public double zombieVillagerSpawnReinforcements = 0.1D;
+ public boolean zombieVillagerJockeyOnlyBaby = true;
+ public double zombieVillagerJockeyChance = 0.05D;
+ public boolean zombieVillagerJockeyTryExistingChickens = true;
+ public boolean zombieVillagerTakeDamageFromWater = false;
+ public int zombieVillagerCuringTimeMin = 3600;
+ public int zombieVillagerCuringTimeMax = 6000;
+ public boolean zombieVillagerCureEnabled = true;
+ public boolean zombieVillagerAlwaysDropExp = false;
+ private void zombieVillagerSettings() {
+ zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable);
+ zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater);
+ zombieVillagerControllable = getBoolean("mobs.zombie_villager.controllable", zombieVillagerControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.zombie_villager.attributes.max-health", zombieVillagerMaxHealth);
+ set("mobs.zombie_villager.attributes.max-health", null);
+ set("mobs.zombie_villager.attributes.max_health", oldValue);
+ }
+ zombieVillagerMaxHealth = getDouble("mobs.zombie_villager.attributes.max_health", zombieVillagerMaxHealth);
+ zombieVillagerSpawnReinforcements = getDouble("mobs.zombie_villager.attributes.spawn_reinforcements", zombieVillagerSpawnReinforcements);
+ zombieVillagerJockeyOnlyBaby = getBoolean("mobs.zombie_villager.jockey.only-babies", zombieVillagerJockeyOnlyBaby);
+ zombieVillagerJockeyChance = getDouble("mobs.zombie_villager.jockey.chance", zombieVillagerJockeyChance);
+ zombieVillagerJockeyTryExistingChickens = getBoolean("mobs.zombie_villager.jockey.try-existing-chickens", zombieVillagerJockeyTryExistingChickens);
+ zombieVillagerTakeDamageFromWater = getBoolean("mobs.zombie_villager.takes-damage-from-water", zombieVillagerTakeDamageFromWater);
+ zombieVillagerCuringTimeMin = getInt("mobs.zombie_villager.curing_time.min", zombieVillagerCuringTimeMin);
+ zombieVillagerCuringTimeMax = getInt("mobs.zombie_villager.curing_time.max", zombieVillagerCuringTimeMax);
+ zombieVillagerCureEnabled = getBoolean("mobs.zombie_villager.cure.enabled", zombieVillagerCureEnabled);
+ zombieVillagerAlwaysDropExp = getBoolean("mobs.zombie_villager.always-drop-exp", zombieVillagerAlwaysDropExp);
+ }
+
+ public boolean zombifiedPiglinRidable = false;
+ public boolean zombifiedPiglinRidableInWater = true;
+ public boolean zombifiedPiglinControllable = true;
+ public double zombifiedPiglinMaxHealth = 20.0D;
+ public double zombifiedPiglinSpawnReinforcements = 0.0D;
+ public boolean zombifiedPiglinJockeyOnlyBaby = true;
+ public double zombifiedPiglinJockeyChance = 0.05D;
+ public boolean zombifiedPiglinJockeyTryExistingChickens = true;
+ public boolean zombifiedPiglinCountAsPlayerKillWhenAngry = true;
+ public boolean zombifiedPiglinTakeDamageFromWater = false;
+ public boolean zombifiedPiglinAlwaysDropExp = false;
+ private void zombifiedPiglinSettings() {
+ zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable);
+ zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater);
+ zombifiedPiglinControllable = getBoolean("mobs.zombified_piglin.controllable", zombifiedPiglinControllable);
+ if (PurpurConfig.version < 10) {
+ double oldValue = getDouble("mobs.zombified_piglin.attributes.max-health", zombifiedPiglinMaxHealth);
+ set("mobs.zombified_piglin.attributes.max-health", null);
+ set("mobs.zombified_piglin.attributes.max_health", oldValue);
+ }
+ zombifiedPiglinMaxHealth = getDouble("mobs.zombified_piglin.attributes.max_health", zombifiedPiglinMaxHealth);
+ zombifiedPiglinSpawnReinforcements = getDouble("mobs.zombified_piglin.attributes.spawn_reinforcements", zombifiedPiglinSpawnReinforcements);
+ zombifiedPiglinJockeyOnlyBaby = getBoolean("mobs.zombified_piglin.jockey.only-babies", zombifiedPiglinJockeyOnlyBaby);
+ zombifiedPiglinJockeyChance = getDouble("mobs.zombified_piglin.jockey.chance", zombifiedPiglinJockeyChance);
+ zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens);
+ zombifiedPiglinCountAsPlayerKillWhenAngry = getBoolean("mobs.zombified_piglin.count-as-player-kill-when-angry", zombifiedPiglinCountAsPlayerKillWhenAngry);
+ zombifiedPiglinTakeDamageFromWater = getBoolean("mobs.zombified_piglin.takes-damage-from-water", zombifiedPiglinTakeDamageFromWater);
+ zombifiedPiglinAlwaysDropExp = getBoolean("mobs.zombified_piglin.always-drop-exp", zombifiedPiglinAlwaysDropExp);
+ }
+
+ public float hungerStarvationDamage = 1.0F;
+ private void hungerSettings() {
+ hungerStarvationDamage = (float) getDouble("hunger.starvation-damage", hungerStarvationDamage);
+ }
+
+ public int conduitDistance = 16;
+ public double conduitDamageDistance = 8;
+ public float conduitDamageAmount = 4;
+ public Block[] conduitBlocks;
+ private void conduitSettings() {
+ conduitDistance = getInt("blocks.conduit.effect-distance", conduitDistance);
+ conduitDamageDistance = getDouble("blocks.conduit.mob-damage.distance", conduitDamageDistance);
+ conduitDamageAmount = (float) getDouble("blocks.conduit.mob-damage.damage-amount", conduitDamageAmount);
+ List<Block> conduitBlockList = new ArrayList<>();
+ getList("blocks.conduit.valid-ring-blocks", new ArrayList<String>(){{
+ add("minecraft:prismarine");
+ add("minecraft:prismarine_bricks");
+ add("minecraft:sea_lantern");
+ add("minecraft:dark_prismarine");
+ }}).forEach(key -> {
+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key.toString()));
+ if (!block.defaultBlockState().isAir()) {
+ conduitBlockList.add(block);
+ }
+ });
+ conduitBlocks = conduitBlockList.toArray(Block[]::new);
+ }
+
+ public float cauldronRainChance = 0.05F;
+ public float cauldronPowderSnowChance = 0.1F;
+ public float cauldronDripstoneWaterFillChance = 0.17578125F;
+ public float cauldronDripstoneLavaFillChance = 0.05859375F;
+ private void cauldronSettings() {
+ cauldronRainChance = (float) getDouble("blocks.cauldron.fill-chances.rain", cauldronRainChance);
+ cauldronPowderSnowChance = (float) getDouble("blocks.cauldron.fill-chances.powder-snow", cauldronPowderSnowChance);
+ cauldronDripstoneWaterFillChance = (float) getDouble("blocks.cauldron.fill-chances.dripstone-water", cauldronDripstoneWaterFillChance);
+ cauldronDripstoneLavaFillChance = (float) getDouble("blocks.cauldron.fill-chances.dripstone-lava", cauldronDripstoneLavaFillChance);
+ }
+
+ public float shearsCanDefuseTntChance = 0.00F;
+ public boolean shearsCanDefuseTnt = false;
+ private void shearsCanDefuseTntSettings() {
+ shearsCanDefuseTntChance = (float) getDouble("gameplay-mechanics.item.shears.defuse-tnt-chance", 0.00D);
+ shearsCanDefuseTnt = shearsCanDefuseTntChance > 0.00F;
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/command/CompassCommand.java b/src/main/java/org/purpurmc/purpur/command/CompassCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..79b8490832d2a0cc7846ddcb091cb6bcac74ea45
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/command/CompassCommand.java
@@ -0,0 +1,27 @@
+package org.purpurmc.purpur.command;
+
+import com.mojang.brigadier.CommandDispatcher;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.Commands;
+import net.minecraft.server.level.ServerPlayer;
+import org.purpurmc.purpur.task.CompassTask;
+
+public class CompassCommand {
+ public static void register(CommandDispatcher<CommandSourceStack> 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<CommandSourceStack> 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<ServerPlayer> 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<CommandSourceStack> 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<ServerPlayer> targets) {
+ for (ServerPlayer player : targets) {
+ ClientboundGameEventPacket packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.DEMO_EVENT, 0);
+ player.connection.send(packet);
+ String output = String.format(PurpurConfig.demoCommandOutput, player.getGameProfile().getName());
+ sender.sendSuccess(output, false);
+ }
+ return targets.size();
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/command/PingCommand.java b/src/main/java/org/purpurmc/purpur/command/PingCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..179727c6b3171c040d1aaf069525f61a9a2d54d9
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/command/PingCommand.java
@@ -0,0 +1,33 @@
+package org.purpurmc.purpur.command;
+
+import com.mojang.brigadier.CommandDispatcher;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.Commands;
+import net.minecraft.commands.arguments.EntityArgument;
+import net.minecraft.server.level.ServerPlayer;
+import org.purpurmc.purpur.PurpurConfig;
+import org.bukkit.craftbukkit.util.CraftChatMessage;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class PingCommand {
+ public static void register(CommandDispatcher<CommandSourceStack> 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<ServerPlayer> targets) {
+ for (ServerPlayer player : targets) {
+ String output = String.format(PurpurConfig.pingCommandOutput, player.getGameProfile().getName(), player.latency);
+ sender.sendSuccess(output, false);
+ }
+ return targets.size();
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..2621e54879e9ab0029a875f1d09eee67878b90d5
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java
@@ -0,0 +1,66 @@
+package org.purpurmc.purpur.command;
+
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import org.purpurmc.purpur.PurpurConfig;
+import org.bukkit.ChatColor;
+import org.bukkit.Location;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class PurpurCommand extends Command {
+ public PurpurCommand(String name) {
+ super(name);
+ this.description = "Purpur related commands";
+ this.usageMessage = "/purpur [reload | version]";
+ this.setPermission("bukkit.command.purpur");
+ }
+
+ @Override
+ public List<String> 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<CommandSourceStack> 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<ServerPlayer> 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<CommandSourceStack> 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<CommandSourceStack> 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<ServerPlayer> 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<CommandSourceStack> 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, "<days>", PurpurConfig.uptimeDay, PurpurConfig.uptimeDays, TimeUnit.DAYS, TimeUnit.MILLISECONDS::toDays);
+ process(data, "<hours>", PurpurConfig.uptimeHour, PurpurConfig.uptimeHours, TimeUnit.HOURS, TimeUnit.MILLISECONDS::toHours);
+ process(data, "<minutes>", PurpurConfig.uptimeMinute, PurpurConfig.uptimeMinutes, TimeUnit.MINUTES, TimeUnit.MILLISECONDS::toMinutes);
+ data.hide = false; // never hide seconds
+ process(data, "<seconds>", 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<Long, Long> func) {
+ if (data.format.contains(replace)) {
+ long val = func.apply(data.millis);
+ if (data.hide) data.hide = val == 0;
+ if (!data.hide) data.millis -= unit.toMillis(val);
+ data.format = data.format.replace(replace, data.hide ? "" : String.format(val == 1 ? singular : plural, val));
+ }
+ }
+
+ private static class Data {
+ String format;
+ boolean hide;
+ long millis;
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java
new file mode 100644
index 0000000000000000000000000000000000000000..ed494e0ad278813a0eb261101447b84cca3ad7aa
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java
@@ -0,0 +1,71 @@
+package org.purpurmc.purpur.controller;
+
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.player.Player;
+
+public class FlyingMoveControllerWASD extends MoveControllerWASD {
+ protected final float groundSpeedModifier;
+ protected final float flyingSpeedModifier;
+ protected int tooHighCooldown = 0;
+ protected boolean setNoGravityFlag;
+
+ public FlyingMoveControllerWASD(Mob entity) {
+ this(entity, 1.0F);
+ }
+
+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier) {
+ this(entity, groundSpeedModifier, 1.0F, true);
+ }
+
+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, float flyingSpeedModifier) {
+ this(entity, groundSpeedModifier, flyingSpeedModifier, true);
+ }
+
+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, float flyingSpeedModifier, boolean setNoGravityFlag) {
+ super(entity);
+ this.groundSpeedModifier = groundSpeedModifier;
+ this.flyingSpeedModifier = flyingSpeedModifier;
+ this.setNoGravityFlag = setNoGravityFlag;
+ }
+
+ @Override
+ public void purpurTick(Player rider) {
+ float forward = Math.max(0.0F, rider.getForwardMot());
+ float vertical = forward == 0.0F ? 0.0F : -(rider.xRotO / 45.0F);
+ float strafe = rider.getStrafeMot();
+
+ if (rider.jumping && spacebarEvent(entity)) {
+ entity.onSpacebar();
+ }
+
+ if (entity.getY() >= entity.getMaxY() || --tooHighCooldown > 0) {
+ if (tooHighCooldown <= 0) {
+ tooHighCooldown = 20;
+ }
+ entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.05D, 0.0D));
+ vertical = 0.0F;
+ }
+
+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED));
+ float speed = (float) getSpeedModifier();
+
+ if (entity.onGround) {
+ speed *= groundSpeedModifier; // TODO = fix this!
+ } else {
+ speed *= flyingSpeedModifier;
+ }
+
+ if (setNoGravityFlag) {
+ entity.setNoGravity(forward > 0);
+ }
+
+ entity.setSpeed(speed);
+ entity.setVerticalMot(vertical);
+ entity.setStrafeMot(strafe);
+ entity.setForwardMot(forward);
+
+ setForward(entity.getForwardMot());
+ setStrafe(entity.getStrafeMot());
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java
new file mode 100644
index 0000000000000000000000000000000000000000..9383c07fa53141127106a1f289366a040960d52e
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java
@@ -0,0 +1,63 @@
+package org.purpurmc.purpur.controller;
+
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.phys.Vec3;
+
+public class FlyingWithSpacebarMoveControllerWASD extends FlyingMoveControllerWASD {
+ public FlyingWithSpacebarMoveControllerWASD(Mob entity) {
+ super(entity);
+ }
+
+ public FlyingWithSpacebarMoveControllerWASD(Mob entity, float groundSpeedModifier) {
+ super(entity, groundSpeedModifier);
+ }
+
+ @Override
+ public void purpurTick(Player rider) {
+ float forward = rider.getForwardMot();
+ float strafe = rider.getStrafeMot() * 0.5F;
+ float vertical = 0;
+
+ if (forward < 0.0F) {
+ forward *= 0.5F;
+ strafe *= 0.5F;
+ }
+
+ float speed = (float) entity.getAttributeValue(Attributes.MOVEMENT_SPEED);
+
+ if (entity.onGround) {
+ speed *= groundSpeedModifier;
+ }
+
+ if (rider.jumping && spacebarEvent(entity) && !entity.onSpacebar()) {
+ entity.setNoGravity(true);
+ vertical = 1.0F;
+ } else {
+ entity.setNoGravity(false);
+ }
+
+ if (entity.getY() >= entity.getMaxY() || --tooHighCooldown > 0) {
+ if (tooHighCooldown <= 0) {
+ tooHighCooldown = 20;
+ }
+ entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.2D, 0.0D));
+ vertical = 0.0F;
+ }
+
+ setSpeedModifier(speed);
+ entity.setSpeed((float) getSpeedModifier());
+ entity.setVerticalMot(vertical);
+ entity.setStrafeMot(strafe);
+ entity.setForwardMot(forward);
+
+ setForward(entity.getForwardMot());
+ setStrafe(entity.getStrafeMot());
+
+ Vec3 mot = entity.getDeltaMovement();
+ if (mot.y > 0.2D) {
+ entity.setDeltaMovement(mot.x, 0.2D, mot.z);
+ }
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java
new file mode 100644
index 0000000000000000000000000000000000000000..b8c25c96e95dd5ec3ad9fa4c41bd6c08e144832d
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java
@@ -0,0 +1,76 @@
+package org.purpurmc.purpur.controller;
+
+
+import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.control.LookControl;
+import net.minecraft.world.entity.player.Player;
+
+public class LookControllerWASD extends LookControl {
+ protected final Mob entity;
+ private float yOffset = 0;
+ private float xOffset = 0;
+
+ public LookControllerWASD(Mob entity) {
+ super(entity);
+ this.entity = entity;
+ }
+
+ // tick
+ @Override
+ public void tick() {
+ if (entity.getRider() != null && entity.isControllable()) {
+ purpurTick(entity.getRider());
+ } else {
+ vanillaTick();
+ }
+ }
+
+ protected void purpurTick(Player rider) {
+ setYawPitch(rider.getYRot(), rider.getXRot());
+ }
+
+ public void vanillaTick() {
+ super.tick();
+ }
+
+ public void setYawPitch(float yRot, float xRot) {
+ entity.setXRot(normalizePitch(xRot + xOffset));
+ entity.setYRot(normalizeYaw(yRot + yOffset));
+ entity.setYHeadRot(entity.getYRot());
+ entity.xRotO = entity.getXRot();
+ entity.yRotO = entity.getYRot();
+
+ entity.tracker.broadcast(new ClientboundMoveEntityPacket
+ .PosRot(entity.getId(),
+ (short) 0, (short) 0, (short) 0,
+ (byte) Mth.floor(entity.getYRot() * 256.0F / 360.0F),
+ (byte) Mth.floor(entity.getXRot() * 256.0F / 360.0F),
+ entity.onGround));
+ }
+
+ public void setOffsets(float yaw, float pitch) {
+ yOffset = yaw;
+ xOffset = pitch;
+ }
+
+ public float normalizeYaw(float yaw) {
+ yaw %= 360.0f;
+ if (yaw >= 180.0f) {
+ yaw -= 360.0f;
+ } else if (yaw < -180.0f) {
+ yaw += 360.0f;
+ }
+ return yaw;
+ }
+
+ public float normalizePitch(float pitch) {
+ if (pitch > 90.0f) {
+ pitch = 90.0f;
+ } else if (pitch < -90.0f) {
+ pitch = -90.0f;
+ }
+ return pitch;
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java
new file mode 100644
index 0000000000000000000000000000000000000000..21fd6ea2a482758a3016e3bc2cdebe2d89267481
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java
@@ -0,0 +1,89 @@
+package org.purpurmc.purpur.controller;
+
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.ai.control.MoveControl;
+import net.minecraft.world.entity.player.Player;
+import org.purpurmc.purpur.event.entity.RidableSpacebarEvent;
+
+public class MoveControllerWASD extends MoveControl {
+ protected final Mob entity;
+ private final double speedModifier;
+
+ public MoveControllerWASD(Mob entity) {
+ this(entity, 1.0D);
+ }
+
+ public MoveControllerWASD(Mob entity, double speedModifier) {
+ super(entity);
+ this.entity = entity;
+ this.speedModifier = speedModifier;
+ }
+
+ @Override
+ public boolean hasWanted() {
+ return entity.getRider() != null ? strafeForwards != 0 || strafeRight != 0 : super.hasWanted();
+ }
+
+ @Override
+ public void tick() {
+ if (entity.getRider() != null && entity.isControllable()) {
+ purpurTick(entity.getRider());
+ } else {
+ vanillaTick();
+ }
+ }
+
+ public void vanillaTick() {
+ super.tick();
+ }
+
+ public void purpurTick(Player rider) {
+ float forward = rider.getForwardMot() * 0.5F;
+ float strafe = rider.getStrafeMot() * 0.25F;
+
+ if (forward <= 0.0F) {
+ forward *= 0.5F;
+ }
+
+ float yawOffset = 0;
+ if (strafe != 0) {
+ if (forward == 0) {
+ yawOffset += strafe > 0 ? -90 : 90;
+ forward = Math.abs(strafe * 2);
+ } else {
+ yawOffset += strafe > 0 ? -30 : 30;
+ strafe /= 2;
+ if (forward < 0) {
+ yawOffset += strafe > 0 ? -110 : 110;
+ forward *= -1;
+ }
+ }
+ } else if (forward < 0) {
+ yawOffset -= 180;
+ forward *= -1;
+ }
+
+ ((LookControllerWASD) entity.getLookControl()).setOffsets(yawOffset, 0);
+
+ if (rider.jumping && spacebarEvent(entity) && !entity.onSpacebar() && entity.onGround) {
+ entity.jumpFromGround();
+ }
+
+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED) * speedModifier);
+
+ entity.setSpeed((float) getSpeedModifier());
+ entity.setForwardMot(forward);
+
+ setForward(entity.getForwardMot());
+ setStrafe(entity.getStrafeMot());
+ }
+
+ public static boolean spacebarEvent(Mob entity) {
+ if (RidableSpacebarEvent.getHandlerList().getRegisteredListeners().length > 0) {
+ return new RidableSpacebarEvent(entity.getBukkitEntity()).callEvent();
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java
new file mode 100644
index 0000000000000000000000000000000000000000..ba2a37dad43e238e54632975abea8ee6fafaa9e0
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java
@@ -0,0 +1,50 @@
+package org.purpurmc.purpur.controller;
+
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.attributes.Attributes;
+import net.minecraft.world.entity.player.Player;
+
+public class WaterMoveControllerWASD extends MoveControllerWASD {
+ private final double speedModifier;
+
+ public WaterMoveControllerWASD(Mob entity) {
+ this(entity, 1.0D);
+ }
+
+ public WaterMoveControllerWASD(Mob entity, double speedModifier) {
+ super(entity);
+ this.speedModifier = speedModifier;
+ }
+
+ @Override
+ public void purpurTick(Player rider) {
+ float forward = rider.getForwardMot();
+ float strafe = rider.getStrafeMot() * 0.5F; // strafe slower by default
+ float vertical = -(rider.xRotO / 90);
+
+ if (forward == 0.0F) {
+ // strafe slower if not moving forward
+ strafe *= 0.5F;
+ // do not move vertically if not moving forward
+ vertical = 0.0F;
+ } else if (forward < 0.0F) {
+ // water animals can't swim backwards
+ forward = 0.0F;
+ vertical = 0.0F;
+ }
+
+ if (rider.jumping && spacebarEvent(entity)) {
+ entity.onSpacebar();
+ }
+
+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED) * speedModifier);
+ entity.setSpeed((float) getSpeedModifier() * 0.1F);
+
+ entity.setForwardMot(forward * (float) speedModifier);
+ entity.setStrafeMot(strafe * (float) speedModifier);
+ entity.setVerticalMot(vertical * (float) speedModifier);
+
+ setForward(entity.getForwardMot());
+ setStrafe(entity.getStrafeMot());
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce614ae6b1fa0b31c1ee8dacb69134bb20c949f4
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java
@@ -0,0 +1,106 @@
+package org.purpurmc.purpur.entity;
+
+import net.minecraft.core.particles.ParticleTypes;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.util.Mth;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.animal.Dolphin;
+import net.minecraft.world.entity.projectile.LlamaSpit;
+import net.minecraft.world.entity.projectile.ProjectileUtil;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.EntityHitResult;
+import net.minecraft.world.phys.HitResult;
+import net.minecraft.world.phys.Vec3;
+
+public class DolphinSpit extends LlamaSpit {
+ public LivingEntity dolphin;
+ public int ticksLived;
+
+ public DolphinSpit(EntityType<? extends LlamaSpit> type, Level world) {
+ super(type, world);
+ }
+
+ public DolphinSpit(Level world, Dolphin dolphin) {
+ this(EntityType.LLAMA_SPIT, world);
+ setOwner(dolphin.getRider() != null ? dolphin.getRider() : dolphin);
+ this.dolphin = dolphin;
+ this.setPos(
+ dolphin.getX() - (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(dolphin.yBodyRot * 0.017453292F),
+ dolphin.getEyeY() - 0.10000000149011612D,
+ dolphin.getZ() + (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(dolphin.yBodyRot * 0.017453292F));
+ }
+
+ // Purpur start
+ @Override
+ public boolean canSaveToDisk() {
+ return false;
+ }
+ // Purpur end
+
+ public void tick() {
+ super_tick();
+
+ Vec3 mot = this.getDeltaMovement();
+ HitResult hitResult = ProjectileUtil.getHitResult(this.position(), this, this::canHitEntity, mot, level());
+
+ this.preOnHit(hitResult);
+
+ double x = this.getX() + mot.x;
+ double y = this.getY() + mot.y;
+ double z = this.getZ() + mot.z;
+
+ this.updateRotation();
+
+ Vec3 motDouble = mot.scale(2.0);
+ for (int i = 0; i < 5; i++) {
+ ((ServerLevel) level()).sendParticles(null, ParticleTypes.BUBBLE,
+ getX() + random.nextFloat() / 2 - 0.25F,
+ getY() + random.nextFloat() / 2 - 0.25F,
+ getZ() + random.nextFloat() / 2 - 0.25F,
+ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D, true);
+ }
+
+ if (++ticksLived > 20) {
+ this.discard();
+ } else {
+ this.setDeltaMovement(mot.scale(0.99D));
+ if (!this.isNoGravity()) {
+ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D));
+ }
+
+ this.setPos(x, y, z);
+ }
+ }
+
+ @Override
+ public void shoot(double x, double y, double z, float speed, float inaccuracy) {
+ setDeltaMovement(new Vec3(x, y, z).normalize().add(
+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy)
+ .scale(speed));
+ }
+
+ @Override
+ protected void onHitEntity(EntityHitResult entityHitResult) {
+ Entity shooter = this.getOwner();
+ if (shooter instanceof LivingEntity) {
+ entityHitResult.getEntity().hurt(entityHitResult.getEntity().damageSources().mobProjectile(this, (LivingEntity) shooter), level().purpurConfig.dolphinSpitDamage);
+ }
+ }
+
+ @Override
+ protected void onHitBlock(BlockHitResult blockHitResult) {
+ if (this.hitCancelled) {
+ return;
+ }
+ BlockState state = this.level().getBlockState(blockHitResult.getBlockPos());
+ state.onProjectileHit(this.level(), state, blockHitResult, this);
+ this.discard();
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java
new file mode 100644
index 0000000000000000000000000000000000000000..ea8b928b6d82689e71bbcc39ab497491072dfba6
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java
@@ -0,0 +1,121 @@
+package org.purpurmc.purpur.entity;
+
+import net.minecraft.core.particles.ParticleTypes;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.util.Mth;
+import net.minecraft.world.damagesource.DamageSource;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.decoration.ArmorStand;
+import net.minecraft.world.entity.monster.Phantom;
+import net.minecraft.world.entity.projectile.LlamaSpit;
+import net.minecraft.world.entity.projectile.ProjectileUtil;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockBehaviour;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.BlockHitResult;
+import net.minecraft.world.phys.EntityHitResult;
+import net.minecraft.world.phys.HitResult;
+import net.minecraft.world.phys.Vec3;
+
+public class PhantomFlames extends LlamaSpit {
+ public Phantom phantom;
+ public int ticksLived;
+ public boolean canGrief = false;
+
+ public PhantomFlames(EntityType<? extends LlamaSpit> type, Level world) {
+ super(type, world);
+ }
+
+ public PhantomFlames(Level world, Phantom phantom) {
+ this(EntityType.LLAMA_SPIT, world);
+ setOwner(phantom.getRider() != null ? phantom.getRider() : phantom);
+ this.phantom = phantom;
+ this.setPos(
+ phantom.getX() - (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(phantom.yBodyRot * 0.017453292F),
+ phantom.getEyeY() - 0.10000000149011612D,
+ phantom.getZ() + (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(phantom.yBodyRot * 0.017453292F));
+ }
+
+ // Purpur start
+ @Override
+ public boolean canSaveToDisk() {
+ return false;
+ }
+ // Purpur end
+
+ public void tick() {
+ super_tick();
+
+ Vec3 mot = this.getDeltaMovement();
+ HitResult hitResult = ProjectileUtil.getHitResult(this.position(), this, this::canHitEntity, mot, level());
+
+ this.preOnHit(hitResult);
+
+ double x = this.getX() + mot.x;
+ double y = this.getY() + mot.y;
+ double z = this.getZ() + mot.z;
+
+ this.updateRotation();
+
+ Vec3 motDouble = mot.scale(2.0);
+ for (int i = 0; i < 5; i++) {
+ ((ServerLevel) level()).sendParticles(null, ParticleTypes.FLAME,
+ getX() + random.nextFloat() / 2 - 0.25F,
+ getY() + random.nextFloat() / 2 - 0.25F,
+ getZ() + random.nextFloat() / 2 - 0.25F,
+ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D, true);
+ }
+
+ if (++ticksLived > 20) {
+ this.discard();
+ } else if (this.level().getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) {
+ this.discard();
+ } else if (this.isInWaterOrBubble()) {
+ this.discard();
+ } else {
+ this.setDeltaMovement(mot.scale(0.99D));
+ if (!this.isNoGravity()) {
+ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D));
+ }
+
+ this.setPos(x, y, z);
+ }
+ }
+
+ @Override
+ public void shoot(double x, double y, double z, float speed, float inaccuracy) {
+ setDeltaMovement(new Vec3(x, y, z).normalize().add(
+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy)
+ .scale(speed));
+ }
+
+ @Override
+ protected void onHitEntity(EntityHitResult entityHitResult) {
+ Entity shooter = this.getOwner();
+ if (shooter instanceof LivingEntity) {
+ Entity target = entityHitResult.getEntity();
+ if (canGrief || (target instanceof LivingEntity && !(target instanceof ArmorStand))) {
+ boolean hurt = target.hurt(target.damageSources().mobProjectile(this, (LivingEntity) shooter), level().purpurConfig.phantomFlameDamage);
+ if (hurt && level().purpurConfig.phantomFlameFireTime > 0) {
+ target.setSecondsOnFire(level().purpurConfig.phantomFlameFireTime);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onHitBlock(BlockHitResult blockHitResult) {
+ if (this.hitCancelled) {
+ return;
+ }
+ if (this.canGrief) {
+ BlockState state = this.level().getBlockState(blockHitResult.getBlockPos());
+ state.onProjectileHit(this.level(), state, blockHitResult, this);
+ }
+ this.discard();
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java b/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java
new file mode 100644
index 0000000000000000000000000000000000000000..8efca1d91188ac4db911a8eb0fa9ea2cc3c48e28
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java
@@ -0,0 +1,102 @@
+package org.purpurmc.purpur.entity;
+
+import io.papermc.paper.adventure.PaperAdventure;
+import net.kyori.adventure.text.Component;
+import net.minecraft.nbt.Tag;
+import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
+import org.bukkit.block.EntityBlockStorage;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
+import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry;
+import org.bukkit.entity.Bee;
+import org.bukkit.entity.EntityType;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Locale;
+
+public class PurpurStoredBee implements StoredEntity<Bee> {
+ private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
+
+ private final EntityBlockStorage<Bee> 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<Bee> blockStorage) {
+ this.handle = data;
+ this.blockStorage = blockStorage;
+
+ this.customName = handle.entityData.contains("CustomName", Tag.TAG_STRING)
+ ? PaperAdventure.asAdventure(net.minecraft.network.chat.Component.Serializer.fromJson(handle.entityData.getString("CustomName")))
+ : null;
+
+ if(handle.entityData.contains("BukkitValues", Tag.TAG_COMPOUND)) {
+ this.persistentDataContainer.putAll(handle.entityData.getCompound("BukkitValues"));
+ }
+ }
+
+ public BeehiveBlockEntity.BeeData getHandle() {
+ return handle;
+ }
+
+ @Override
+ public @Nullable Component customName() {
+ return customName;
+ }
+
+ @Override
+ public void customName(@Nullable Component customName) {
+ this.customName = customName;
+ }
+
+ @Override
+ public @Nullable String getCustomName() {
+ return PaperAdventure.asPlain(customName, Locale.US);
+ }
+
+ @Override
+ public void setCustomName(@Nullable String name) {
+ customName(name != null ? Component.text(name) : null);
+ }
+
+ @Override
+ public @NotNull PersistentDataContainer getPersistentDataContainer() {
+ return persistentDataContainer;
+ }
+
+ @Override
+ public boolean hasBeenReleased() {
+ return !blockStorage.getEntities().contains(this);
+ }
+
+ @Override
+ public @Nullable Bee release() {
+ return blockStorage.releaseEntity(this);
+ }
+
+ @Override
+ public @Nullable EntityBlockStorage<Bee> getBlockStorage() {
+ if(hasBeenReleased()) {
+ return null;
+ }
+
+ return blockStorage;
+ }
+
+ @Override
+ public @NotNull EntityType getType() {
+ return EntityType.BEE;
+ }
+
+ @Override
+ public void update() {
+ handle.entityData.put("BukkitValues", this.persistentDataContainer.toTagCompound());
+ if(customName == null) {
+ handle.entityData.remove("CustomName");
+ } else {
+ handle.entityData.putString("CustomName", net.minecraft.network.chat.Component.Serializer.toJson(PaperAdventure.asVanilla(customName)));
+ }
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java
new file mode 100644
index 0000000000000000000000000000000000000000..8babdaddd8b33278aea0369dbbeeb445abe45016
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java
@@ -0,0 +1,20 @@
+package org.purpurmc.purpur.entity.ai;
+
+import net.minecraft.world.entity.Mob;
+import net.minecraft.world.entity.ai.goal.Goal;
+
+import java.util.EnumSet;
+
+public class HasRider extends Goal {
+ public final Mob entity;
+
+ public HasRider(Mob entity) {
+ this.entity = entity;
+ setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.TARGET, Flag.UNKNOWN_BEHAVIOR));
+ }
+
+ @Override
+ public boolean canUse() {
+ return entity.getRider() != null && entity.isControllable();
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java
new file mode 100644
index 0000000000000000000000000000000000000000..432f4f3d82af2f19820890b68d33189a9f2c69f9
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java
@@ -0,0 +1,17 @@
+package org.purpurmc.purpur.entity.ai;
+
+import net.minecraft.world.entity.animal.horse.AbstractHorse;
+
+public class HorseHasRider extends HasRider {
+ public final AbstractHorse horse;
+
+ public HorseHasRider(AbstractHorse entity) {
+ super(entity);
+ this.horse = entity;
+ }
+
+ @Override
+ public boolean canUse() {
+ return super.canUse() && horse.isSaddled();
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java
new file mode 100644
index 0000000000000000000000000000000000000000..18a95e043cbffa65eeaaf65ff7695e5dc939820c
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java
@@ -0,0 +1,17 @@
+package org.purpurmc.purpur.entity.ai;
+
+import net.minecraft.world.entity.animal.horse.Llama;
+
+public class LlamaHasRider extends HasRider {
+ public final Llama llama;
+
+ public LlamaHasRider(Llama entity) {
+ super(entity);
+ this.llama = entity;
+ }
+
+ @Override
+ public boolean canUse() {
+ return super.canUse() && llama.isSaddled() && llama.isControllable();
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java b/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java
new file mode 100644
index 0000000000000000000000000000000000000000..9660716f4162a4441c6e1b0baddef8f5086566c5
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java
@@ -0,0 +1,91 @@
+package org.purpurmc.purpur.entity.ai;
+
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.entity.ai.goal.Goal;
+import net.minecraft.world.entity.animal.IronGolem;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.block.Blocks;
+
+import java.util.EnumSet;
+import java.util.UUID;
+
+public class ReceiveFlower extends Goal {
+ private final IronGolem irongolem;
+ private ServerPlayer target;
+ private int cooldown;
+
+ public ReceiveFlower(IronGolem entity) {
+ this.irongolem = entity;
+ setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK));
+ }
+
+ @Override
+ public boolean canUse() {
+ if (this.irongolem.getOfferFlowerTick() > 0) {
+ return false;
+ }
+ if (!this.irongolem.isAngry()) {
+ return false;
+ }
+ UUID uuid = this.irongolem.getPersistentAngerTarget();
+ if (uuid == null) {
+ return false;
+ }
+ Entity target = ((ServerLevel) this.irongolem.level()).getEntity(uuid);
+ if (!(target instanceof ServerPlayer player)) {
+ return false;
+ }
+ InteractionHand hand = getPoppyHand(player);
+ if (hand == null) {
+ return false;
+ }
+ removeFlower(player, hand);
+ this.target = player;
+ return true;
+ }
+
+ @Override
+ public boolean canContinueToUse() {
+ return this.cooldown > 0;
+ }
+
+ @Override
+ public void start() {
+ this.cooldown = 100;
+ this.irongolem.stopBeingAngry();
+ this.irongolem.offerFlower(true);
+ }
+
+ @Override
+ public void stop() {
+ this.irongolem.offerFlower(false);
+ this.target = null;
+ }
+
+ @Override
+ public void tick() {
+ this.irongolem.getLookControl().setLookAt(this.target, 30.0F, 30.0F);
+ --this.cooldown;
+ }
+
+ private InteractionHand getPoppyHand(ServerPlayer player) {
+ if (isPoppy(player.getMainHandItem())) {
+ return InteractionHand.MAIN_HAND;
+ }
+ if (isPoppy(player.getOffhandItem())) {
+ return InteractionHand.OFF_HAND;
+ }
+ return null;
+ }
+
+ private void removeFlower(ServerPlayer player, InteractionHand hand) {
+ player.setItemInHand(hand, ItemStack.EMPTY);
+ }
+
+ private boolean isPoppy(ItemStack item) {
+ return item.getItem() == Blocks.POPPY.asItem();
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/gui/GUIColor.java b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f2e7e0b81620c8581949bd5f0bdb567cd93c17e
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java
@@ -0,0 +1,54 @@
+package org.purpurmc.purpur.gui;
+
+import net.md_5.bungee.api.ChatColor;
+
+import java.awt.Color;
+import java.util.HashMap;
+import java.util.Map;
+
+public enum GUIColor {
+ BLACK(ChatColor.BLACK, new Color(0x000000)),
+ DARK_BLUE(ChatColor.DARK_BLUE, new Color(0x0000AA)),
+ DARK_GREEN(ChatColor.DARK_GREEN, new Color(0x00AA00)),
+ DARK_AQUA(ChatColor.DARK_AQUA, new Color(0x009999)),
+ DARK_RED(ChatColor.DARK_RED, new Color(0xAA0000)),
+ DARK_PURPLE(ChatColor.DARK_PURPLE, new Color(0xAA00AA)),
+ GOLD(ChatColor.GOLD, new Color(0xBB8800)),
+ GRAY(ChatColor.GRAY, new Color(0x888888)),
+ DARK_GRAY(ChatColor.DARK_GRAY, new Color(0x444444)),
+ BLUE(ChatColor.BLUE, new Color(0x5555FF)),
+ GREEN(ChatColor.GREEN, new Color(0x55FF55)),
+ AQUA(ChatColor.AQUA, new Color(0x55DDDD)),
+ RED(ChatColor.RED, new Color(0xFF5555)),
+ LIGHT_PURPLE(ChatColor.LIGHT_PURPLE, new Color(0xFF55FF)),
+ YELLOW(ChatColor.YELLOW, new Color(0xFFBB00)),
+ WHITE(ChatColor.WHITE, new Color(0xBBBBBB));
+
+ private final ChatColor chat;
+ private final Color color;
+
+ private static final Map<ChatColor, GUIColor> BY_CHAT = new HashMap<>();
+
+ GUIColor(ChatColor chat, Color color) {
+ this.chat = chat;
+ this.color = color;
+ }
+
+ public Color getColor() {
+ return color;
+ }
+
+ public String getCode() {
+ return chat.toString();
+ }
+
+ public static GUIColor getColor(ChatColor chat) {
+ return BY_CHAT.get(chat);
+ }
+
+ static {
+ for (GUIColor color : values()) {
+ BY_CHAT.put(color.chat, color);
+ }
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java
new file mode 100644
index 0000000000000000000000000000000000000000..33e89b4c00fa8318506b36cbe49fe4e412e0a9a1
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java
@@ -0,0 +1,78 @@
+package org.purpurmc.purpur.gui;
+
+import com.google.common.collect.Sets;
+import net.md_5.bungee.api.ChatColor;
+import net.md_5.bungee.api.chat.BaseComponent;
+import net.md_5.bungee.api.chat.TextComponent;
+
+import javax.swing.JTextPane;
+import javax.swing.Timer;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyleContext;
+import java.util.Set;
+
+public class JColorTextPane extends JTextPane {
+ private static final GUIColor DEFAULT_COLOR = GUIColor.BLACK;
+
+ public void append(String msg) {
+ // TODO: update to use adventure instead
+ BaseComponent[] components = TextComponent.fromLegacyText(DEFAULT_COLOR.getCode() + msg, ChatColor.BLACK);
+ for (BaseComponent component : components) {
+ String text = component.toPlainText();
+ if (text == null || text.isEmpty()) {
+ continue;
+ }
+
+ GUIColor guiColor = GUIColor.getColor(component.getColor());
+
+ StyleContext context = StyleContext.getDefaultStyleContext();
+ AttributeSet attr = context.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, guiColor.getColor());
+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Bold, component.isBold() || guiColor != DEFAULT_COLOR);
+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Italic, component.isItalic());
+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Underline, component.isUnderlined());
+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.StrikeThrough, component.isStrikethrough());
+ //attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Blink, component.isObfuscated()); // no such thing as Blink, sadly
+
+ try {
+ int pos = getDocument().getLength();
+ getDocument().insertString(pos, text, attr);
+
+ if (component.isObfuscated()) {
+ // dirty hack to blink some text
+ Blink blink = new Blink(pos, text.length(), attr, context.addAttribute(attr, StyleConstants.Foreground, getBackground()));
+ BLINKS.add(blink);
+ }
+ } catch (BadLocationException ignore) {
+ }
+ }
+ }
+
+ private static final Set<Blink> BLINKS = Sets.newHashSet();
+ private static boolean SYNC_BLINK;
+
+ static {
+ new Timer(500, e -> {
+ SYNC_BLINK = !SYNC_BLINK;
+ BLINKS.forEach(Blink::blink);
+ }).start();
+ }
+
+ public class Blink {
+ private final int start, length;
+ private final AttributeSet attr1, attr2;
+
+ private Blink(int start, int length, AttributeSet attr1, AttributeSet attr2) {
+ this.start = start;
+ this.length = length;
+ this.attr1 = attr1;
+ this.attr2 = attr2;
+ }
+
+ private void blink() {
+ getStyledDocument().setCharacterAttributes(start, length, SYNC_BLINK ? attr1 : attr2, true);
+ }
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f526883495b3222746de3d0442e9e4fb5107036
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java
@@ -0,0 +1,26 @@
+package org.purpurmc.purpur.item;
+
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.effect.MobEffectInstance;
+import net.minecraft.world.effect.MobEffects;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.item.ItemNameBlockItem;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import org.bukkit.event.entity.EntityPotionEffectEvent;
+
+public class GlowBerryItem extends ItemNameBlockItem {
+ public GlowBerryItem(Block block, Properties settings) {
+ super(block, settings);
+ }
+
+ @Override
+ public ItemStack finishUsingItem(ItemStack stack, Level world, LivingEntity user) {
+ ItemStack result = super.finishUsingItem(stack, world, user);
+ if (world.purpurConfig.glowBerriesEatGlowDuration > 0 && user instanceof ServerPlayer player) {
+ player.addEffect(new MobEffectInstance(MobEffects.GLOWING, world.purpurConfig.glowBerriesEatGlowDuration), EntityPotionEffectEvent.Cause.FOOD);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..c038fb2bbb0f0e78380bc24bbd6348b869669a90
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java
@@ -0,0 +1,36 @@
+package org.purpurmc.purpur.item;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.BlockItem;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.entity.SpawnerBlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+
+public class SpawnerItem extends BlockItem {
+
+ public SpawnerItem(Block block, Properties settings) {
+ super(block, settings);
+ }
+
+ @Override
+ protected boolean updateCustomBlockEntityTag(BlockPos pos, Level level, Player player, ItemStack stack, BlockState state) {
+ boolean handled = super.updateCustomBlockEntityTag(pos, level, player, stack, state);
+ if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.place.spawners")) {
+ BlockEntity spawner = level.getBlockEntity(pos);
+ if (spawner instanceof SpawnerBlockEntity && stack.hasTag()) {
+ CompoundTag tag = stack.getTag();
+ if (tag.contains("Purpur.mob_type")) {
+ EntityType.byString(tag.getString("Purpur.mob_type")).ifPresent(type ->
+ ((SpawnerBlockEntity) spawner).getSpawner().setEntityId(type, level, level.random, pos));
+ }
+ }
+ }
+ return handled;
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java b/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..2ebbaf5faa92a88bfb4d61298951e5b74157d1e1
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java
@@ -0,0 +1,81 @@
+package org.purpurmc.purpur.task;
+
+import com.google.common.io.ByteArrayDataInput;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import io.netty.buffer.Unpooled;
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.PluginBase;
+import org.bukkit.plugin.messaging.PluginMessageListener;
+import org.jetbrains.annotations.NotNull;
+
+public class BeehiveTask implements PluginMessageListener {
+ public static final ResourceLocation BEEHIVE_C2S = new ResourceLocation("purpur", "beehive_c2s");
+ public static final ResourceLocation BEEHIVE_S2C = new ResourceLocation("purpur", "beehive_s2c");
+
+ private static BeehiveTask instance;
+
+ public static BeehiveTask instance() {
+ if (instance == null) {
+ instance = new BeehiveTask();
+ }
+ return instance;
+ }
+
+ private final PluginBase plugin = new MinecraftInternalPlugin();
+
+ private BeehiveTask() {
+ }
+
+ public void register() {
+ Bukkit.getMessenger().registerOutgoingPluginChannel(this.plugin, BEEHIVE_S2C.toString());
+ Bukkit.getMessenger().registerIncomingPluginChannel(this.plugin, BEEHIVE_C2S.toString(), this);
+ }
+
+ public void unregister() {
+ Bukkit.getMessenger().unregisterOutgoingPluginChannel(this.plugin, BEEHIVE_S2C.toString());
+ Bukkit.getMessenger().unregisterIncomingPluginChannel(this.plugin, BEEHIVE_C2S.toString());
+ }
+
+ @Override
+ public void onPluginMessageReceived(@NotNull String channel, Player player, byte[] bytes) {
+ ByteArrayDataInput in = in(bytes);
+ long packedPos = in.readLong();
+ BlockPos pos = BlockPos.of(packedPos);
+
+ ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle();
+
+ BlockEntity blockEntity = serverPlayer.level().getBlockEntity(pos);
+ if (!(blockEntity instanceof BeehiveBlockEntity beehive)) {
+ return;
+ }
+
+ ByteArrayDataOutput out = out();
+
+ out.writeInt(beehive.getOccupantCount());
+ out.writeLong(packedPos);
+
+ FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(out.toByteArray()));
+ serverPlayer.connection.send(new ClientboundCustomPayloadPacket(BEEHIVE_S2C, buf));
+ }
+
+ @SuppressWarnings("UnstableApiUsage")
+ private static ByteArrayDataOutput out() {
+ return ByteStreams.newDataOutput();
+ }
+
+ @SuppressWarnings("UnstableApiUsage")
+ private static ByteArrayDataInput in(byte[] bytes) {
+ return ByteStreams.newDataInput(bytes);
+ }
+}
diff --git a/src/main/java/org/purpurmc/purpur/task/BossBarTask.java b/src/main/java/org/purpurmc/purpur/task/BossBarTask.java
new file mode 100644
index 0000000000000000000000000000000000000000..114f273dd7f8b8a3c02f0651f6944859b33a65d4
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/task/BossBarTask.java
@@ -0,0 +1,121 @@
+package org.purpurmc.purpur.task;
+
+import net.kyori.adventure.bossbar.BossBar;
+import net.minecraft.server.level.ServerPlayer;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.UUID;
+
+public abstract class BossBarTask extends BukkitRunnable {
+ private final Map<UUID, BossBar> bossbars = new HashMap<>();
+ private boolean started;
+
+ abstract BossBar createBossBar();
+
+ abstract void updateBossBar(BossBar bossbar, Player player);
+
+ @Override
+ public void run() {
+ Iterator<Map.Entry<UUID, BossBar>> iter = bossbars.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry<UUID, BossBar> 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<Item, Double> drops;
+
+ public Actionable(Block into, Map<Item, Double> drops) {
+ this.into = into;
+ this.drops = drops;
+ }
+
+ public Block into() {
+ return into;
+ }
+
+ public Map<Item, Double> 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<Item, Double> 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<Item, Double> 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<Item, Double> 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<UseOnContext> predicate;
+
+ Condition(Predicate<UseOnContext> predicate) {
+ this.predicate = predicate;
+ }
+
+ public Predicate<UseOnContext> predicate() {
+ return predicate;
+ }
+
+ private static final Map<String, Condition> 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<Item, Double> 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<Item, Double> drops) {
+ super(into, drops);
+ }
+}
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
index 68557964e27fa1e5ba218178f9bcc0b28e3a78d9..43d9e7287cf0e498ccbff9b865bb813e7fb567c0 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -15,6 +15,7 @@ import net.minecraft.world.entity.ambient.AmbientCreature;
import net.minecraft.world.entity.animal.Animal;
import net.minecraft.world.entity.animal.Bee;
import net.minecraft.world.entity.animal.Sheep;
+import net.minecraft.world.entity.animal.Squid;
import net.minecraft.world.entity.animal.WaterAnimal;
import net.minecraft.world.entity.animal.horse.Llama;
import net.minecraft.world.entity.boss.EnderDragonPart;
@@ -169,7 +170,7 @@ public class ActivationRange
*/
public static void activateEntities(Level world)
{
- MinecraftTimings.entityActivationCheckTimer.startTiming();
+ //MinecraftTimings.entityActivationCheckTimer.startTiming(); // Purpur
final int miscActivationRange = world.spigotConfig.miscActivationRange;
final int raiderActivationRange = world.spigotConfig.raiderActivationRange;
final int animalActivationRange = world.spigotConfig.animalActivationRange;
@@ -203,6 +204,7 @@ public class ActivationRange
continue;
}
+ if (!player.level().purpurConfig.idleTimeoutTickNearbyEntities && player.isAfk()) continue; // Purpur
// Paper start
int worldHeight = world.getHeight();
ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, worldHeight, maxRange );
@@ -243,7 +245,7 @@ public class ActivationRange
}
// Paper end
}
- MinecraftTimings.entityActivationCheckTimer.stopTiming();
+ //MinecraftTimings.entityActivationCheckTimer.stopTiming(); // Purpur
}
/**
@@ -396,6 +398,7 @@ public class ActivationRange
*/
public static boolean checkIfActive(Entity entity)
{
+ if (entity.level().purpurConfig.squidImmuneToEAR && entity instanceof Squid) return true; // Purpur
// Never safe to skip fireworks or entities not yet added to chunk
if ( entity instanceof FireworkRocketEntity ) {
return true;
diff --git a/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/src/main/java/org/spigotmc/TicksPerSecondCommand.java
index bf970bf3356a914459c2d6db93537ce2d32c7e18..08221c7256f41ca511a4a61ffb2b979325aebee9 100644
--- a/src/main/java/org/spigotmc/TicksPerSecondCommand.java
+++ b/src/main/java/org/spigotmc/TicksPerSecondCommand.java
@@ -39,7 +39,7 @@ public class TicksPerSecondCommand extends Command
}
net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text();
- builder.append(net.kyori.adventure.text.Component.text("TPS from last 1m, 5m, 15m: ", net.kyori.adventure.text.format.NamedTextColor.GOLD));
+ builder.append(net.kyori.adventure.text.Component.text("TPS from last 5s, 1m, 5m, 15m: ", net.kyori.adventure.text.format.NamedTextColor.GOLD));
builder.append(net.kyori.adventure.text.Component.join(net.kyori.adventure.text.JoinConfiguration.commas(true), tpsAvg));
sender.sendMessage(builder.asComponent());
if (args.length > 0 && args[0].equals("mem") && sender.hasPermission("bukkit.command.tpsmemory")) {
diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
index 98fba0288be9ed2cb18ffba5cf81148157dd4fcf..0801dea155048ac5383295f4fef9bd597b678535 100644
--- a/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
@@ -96,7 +96,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
private WatchdogThread(long timeoutTime, boolean restart)
{
- super( "Paper Watchdog Thread" );
+ super( "Watchdog Thread" ); // Purpur - use a generic name
this.timeoutTime = timeoutTime;
this.restart = restart;
earlyWarningEvery = Math.min(io.papermc.paper.configuration.GlobalConfiguration.get().watchdog.earlyWarningEvery, timeoutTime); // Paper
@@ -155,14 +155,14 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
if (isLongTimeout) {
// Paper end
log.log( Level.SEVERE, "------------------------------" );
- log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper
+ log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Purpur bug." ); // Paper // Purpur
log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" );
log.log( Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring" );
log.log( Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once" );
log.log( Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes" );
- log.log( Level.SEVERE, "If you are unsure or still think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues" );
+ log.log( Level.SEVERE, "If you are unsure or still think this is a Purpur bug, please report this to https://github.com/PurpurMC/Purpur/issues" ); // Purpur
log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" );
- log.log( Level.SEVERE, "Paper version: " + Bukkit.getServer().getVersion() );
+ log.log( Level.SEVERE, "Purpur version: " + Bukkit.getServer().getVersion() ); // Purpur
//
if ( net.minecraft.world.level.Level.lastPhysicsProblem != null )
{
@@ -185,12 +185,12 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
// Paper end
} else
{
- log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---");
+ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PURPUR - THIS IS NOT A BUG OR A CRASH - " + Bukkit.getServer().getVersion() + " ---"); // Purpur
log.log(Level.SEVERE, "The server has not responded for " + (currentTime - lastTick) / 1000 + " seconds! Creating thread dump");
}
// Paper end - Different message for short timeout
log.log( Level.SEVERE, "------------------------------" );
- log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper
+ log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Purpur!):" ); // Paper // Purpur
io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.dumpAllChunkLoadInfo(isLongTimeout); // Paper // Paper - rewrite chunk system
this.dumpTickingInfo(); // Paper - log detailed tick information
WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
@@ -206,7 +206,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
WatchdogThread.dumpThread( thread, log );
}
} else {
- log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PAPER - THIS IS NOT A BUG OR A CRASH ---");
+ log.log(Level.SEVERE, "--- DO NOT REPORT THIS TO PURPUR - THIS IS NOT A BUG OR A CRASH ---"); // Purpur
}
log.log( Level.SEVERE, "------------------------------" );
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index 675cd61221e807aadf28322b46c3daa1370241b5..0769f5c4711a3b7f59489e611ed01ad8367e5db1 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -2,7 +2,16 @@
<Configuration status="WARN" packages="com.mojang.util" shutdownHook="disable">
<Appenders>
<Queue name="ServerGuiConsole">
- <PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n" />
+ <!-- Purpur start - copied from TerminalConsole -->
+ <PatternLayout>
+ <LoggerNamePatternSelector defaultPattern="%highlightGUIError{[%d{HH:mm:ss} %level]: [%logger] %stripAnsi{%msg}%n%xEx{full}}">
+ <!-- Log root, Minecraft, Mojang and Bukkit loggers without prefix -->
+ <!-- Disable prefix for various plugins that bypass the plugin logger -->
+ <PatternMatch key=",net.minecraft.,Minecraft,com.mojang.,com.sk89q.,ru.tehkode.,Minecraft.AWE"
+ pattern="%highlightGUIError{[%d{HH:mm:ss} %level]: %stripAnsi{%msg}%n%xEx{full}}" />
+ </LoggerNamePatternSelector>
+ </PatternLayout>
+ <!-- Purpur end -->
</Queue>
<TerminalConsole name="TerminalConsole">
<PatternLayout>
diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png
index 8b924977b7886df9ab8790b1e4ff9b1c04a2af45..518591dd83289e041a16e2c2e7d7e7640d4b2e1b 100644
GIT binary patch
literal 9260
zcmWk!Wmptl7+qlLS~|W3b}1=IK|*5b5@|#_l<sa=8WvatrA11R22nr|NnMaG>0AUP
zC6t~Ie$33hbMG7HJ?G9mbDv4n)lnlSVI~2AK;#<g%KEog%-unRcYDr_v#bVzaIbZ>
z4OMQt9~6LaN1#s_Fh>FQQNXigV2~Fm&;xWcfNA!dm*#+<AE1{FEHmANcmS@U0MZHA
zq5{4N0?jhO^GKi`0eA)ic?N)M2w;>B)G7nz8bF6QFw6s>Er4EOK&%?bF$4;AfiW&%
zoCBC(2Ns!cigf^wAiz2NZqBnLz$Fx@R=#VuNdgNjK)EK+fB@F$fJ{?BsqoI$ED5Mo
z1rqE4Uti#zCSVl@%tLSX$$)HQ;Jq4<X9&m@1K;lhU82An{k!~!c!1|oK)CCj)k`zL
zCi=$D8*m8&O0@v%xH|}5fxyT;;EVX3!)zlU#TwAa0U})iEG^I@3Mdu=w$VVaJ5Znx
zSj7V}Wq?V_T{5lGcXw_}bc^WLZL$sUMFN;)2g<a7J|Vy?5x6baE%sXz_dr15Es$k$
zH?+hIv`PXo4nT=E5bXeTirx7asks}vHMw<i3qi8{cK*!=xjR3)FK^sK?uNFh?r<p-
z0by=;T_3+YjBO7Aw-6xO3Fs07Y>~ho`CY^a7vP8(5UvMwUftX}AK<&Iwsa{VUUgUh
z2@c>vB_LZ0XlDV+Z-B?IZf;?2Q{C)P0>ZVx9Q3a7hXhz;0*3DaKX~ub_6P=EnF2ok
zcR7!90-gbPNmi@e3BV~F2=oSCMBV|pg>WmCTgLr-01Q8169d>q-)SS&0%(`GBd$p2
zE<~Lo5bOy|!S8arrBXcyc!~mEJ_FLt@08Ob4a7eO9#jGH#Xy)lfU>@0<fH6e8I4kJ
z7MX4=67Dc|ivYi90GW5dXURJT1sd*9ACdqo3^ytTfc9&kQ65ONy_4K20id4_#Jm93
z>405w;9)s13%z-g3FI09k4gZC@SEGh21MSdMw@%zE`WMpeH{Z3F$F%s4K+W4gOWCo
zM2#hp$Mq4nbl;~?VJ4<NUuWsASJv?DFM7g4C);&i&IVdq%(iQjnL@27^^@Jri6QaR
zg+BD=^Ns1jwlsgwXL_n9+x5BrdaDaxQ#=io9-SQSHw7Gy_Sc0Q?ybyCzcPDry1qJF
z^cHD#ygM~9^trw`H{4lQRpR95;QQidQ@uT1O_dcTIT>luX8W7#1E`a&x!wY&zb88l
zN1p;u{w(({h5e>J1%Y6K8p;U6z`4mh7wrra#_v{h<9beb_Uu_ssLuklU5mIC>bM*t
zcnDp3!57GGcIWN~=GwhPk`|qj+4X29PCWJ%!mm8dv^)i!1KFLpM5tu4Zu)l4x1`+4
zuool8`RpVod-L>kH&(y$T!#xcuSSC206r^ApC6SB-*ez^5f|E=>CVr`YArBlJ*aH=
zv9&F3YdaKfe*MZ~i5LpP{mi>QT}i><&#tzbf_0y?pPchedeE97X-j8))~zv`+-R^c
zXW+`~MJv5ye2+%Kvb|-$zve#6Fq3j>e(Z$inlS;)z#xljVJ?019I=wkGZKCb>mY`{
zC8KIq#mXl@3vR}_b~1^fB_&=iJ$$n&3S*05tirE<8!l{ZZCzis{#|hXxvFTM`o}iR
zq}ASGAx!t@bKd6MjeH3iBEIB}%Yan>#e?6>$^PrcHJlA)z3Hwi9`j3t3g5m_#CTR2
zJWL`g(S4CEDe2|vYOlPr-diIV^oy)GsZk29-<yVVGHbejzQC8^`{V3vch}lU;GSV)
zWUEw7*#_T-ft2DurlUb6Bn#+45v>%}jHck!wdB5f==uk13XY9^5>Drj(Jal(oLc`8
z7D=J9cm#rVYK(uzdmUKaOiVbY*(WAOkmLNmxVRVy4%oluYjcE3ejCZtD>w+KsRMI;
z87X<1i$@!2V<|7#NM2W6ZEQ5}eehW7L)rSeirL`4wOgYhpON3G_`a-$K8jR7b1;WL
z!~?6_@enfHT1uN@893H{iZNi1|9834^3*)D`mGffWf%2a)wvAK&u>(jHANwD#zmPX
zb+%6usxsHvd2-@g4UX8e(%gfc=yCIMYT;LE&!w^Hm;hjMmbIls&Ko=!h)JjeBiK7j
zJg8+Wwf=llHxatzP37JYP2MYV!uiXB_G}g(g)9DJKdnb9^WJMK(TfEmW8+Fb{$ocI
zd<hygvdBllzJ0kNZASu33Un4&+r&~a)41eJ3!MhXcjbDoUN=&oi2g{dRXK=@i<6J9
zoiY7U_z35v8sReE0DX5hcZp14VF7*snLdQ?^x(}~L&LN5JQarEK;d~?atxJEiW~(l
z=6JJhIoF7@p*hml|C90`zF53MN}Wuxs)hXg-8cCHJ~aNGn^PR|nyGBlHZgx3X0ANi
z&liq2M=md)gb#cnx8QU4U}JO?YB^DzOO-gVH*WR)5<<<%N%zr+`n%CR-=wwxG4FQ0
zK{o06w4{YkYmAK#N*SLn;A&pufHQYdTJ-WNI0uh&x<siAgQimDUgoBAEI5rJAT9qX
zypcV8YFfqfX`y?AafaQ3n^mRoNoK1CdWSpGnvZW)XaS)q8lQq^6DYJ!n1^u~c_SM*
z%G`v6J|vk-FH!z<_2x&>_IJNIpT-3}l*&5tlx{G{r<aPm*zC(^CdA2!QQi|d5PB(L
zh@XxXDHU1rG=h}Ti~yF=QikEnK1jU~&|;}G9Knf;rYZQk+Uup_agUusdU0-U{!*aN
zR;9bgf3oK3O%cVg7x%pq#~u9dHXR}IKZ7)6*QDba1>8LcDpgZGm&_LCN*L6GgC7m*
z9Ckq~9^@3|Q4>gQQNDOZ2HyVd9pW<)EoE)RyU2C+kH**{1?C-Xq|NDRXjCBSZu!dW
zkWoaF%%=W+tXxD?vrJiW1>QdCcU?IwSAAVKXSF08Ri&so^yCRhU8OJJ-7;U}%kOXh
z;uh`2naxh}@bu)!tqCu)Xf)+K3I8Q5)ZcBvHOp43NX8R{ud%yohLd|fFsJPE=-WnM
zy9)c2e5+K_=dT7ym;4dn(y2MkPN*C+c_fH{ZfA<eqZ7iXr)ku8csPhEA+Akxi$KAg
z-h3<Y%z1#ZDX$X<a8;fk_lC)L>Wh`@@h_TYs$2*8y!HC~F*!SDduQi((Yakpx9^)x
zJx6lr@C*IFi6C32_c%iv6B`G$;M6Q37L;6Uui3x9r{WSzT7DwjzcfM?D1LbD#A$qd
zu4>LOEHC&2!$+8)#A2;xEg(p|Jdy(=RRcM>kUoYG*Qz;+&oyU511wbY(v-jRqxsns
z%#|?EAim6T>_zCj2<-iPVp$G)<1~m`qSX7m3`(@)$3!@_Il;X3RXlkq+00DbtS7gY
z>#I3K0|TFc5y9cN<3t=oP_kgwm69oEvtnYz&nRnvO7Njr5W~StAj9NIY?n)l*H$Iz
z2YURl{gS{bBrJXar(<X`b+qmA<Hz9ybx1QJee{cc;4t%vmR7QAMgxf6GEt6F?BBy1
z94Tj)f5Zt4qg#QtIdG@A*TWX=WKtRlsE@3m;%fJ$k(?qoOYs)gBAFQ111+v<*N4~V
zZ_FNe)#J^uTp*^u#n~J27r9bC44USao|ue*#!;>$8yfT4n#w=5%{AEV-@Grkiy0_J
z=#<qY)GB#66BSj7s#u*(E#Rl7$gh<(?x09V`bqa&5GwxOGG&RQbIkmkKb~RheM`F8
z(}WfHETX8@gZc}jzLm7NRwGg;Edmp-T8e7-fB0SGQS$kJC=*D`BS`u0znE`o9E9^m
z`mI&W3YjJr{hOw{#_fs?j;M6wbDl3X1c>2;?ULHsk`lZB<ca+B67*4=c=+)tQdJZv
z6pk9`nxIR=8QaMz^7MW8O*Gg3rCl71KFqr>!Pq?X9XYS_xO{hASO}zKeLGkV?N1%z
z>{sH;{En>vxtZU%K&AU5%Gwj}B-+Pkl=AbytOz{;6ed;B5v9!jh)%X6>$P18-6BW0
z9}ExHska~pvMsOcE?mMPyWj`s*pbbrIhx_ij%6Esl?wROHn#vuvN1k)+M*(8X8`A{
zS4o(sjn)kE#;i6MtveA?5(&g98!Mc<Q@HVtbelr4nO2#M57ZVI|LPs9)e=Z~n%sAc
zkUYOyc(Eq^j|gw+1Hq+<SGO#-LgS2RC|%bq8Ad#P3F?hy0EblKib{VN=wp|_NDX?V
z_KIK+$2K@#Gk;Y5f^~~iW>r!J5=}Qa@!L1e14aJEQmcLzXKl5nn2bAJ$tZtP$P8Z1
zZ>}d+7jWYR^n<^KOqhO^N==PYrD)O9EEFNs=im5ICPNsHVP!Zx`PfqbpT-5=sn650
zHSeafmr~){Zr!JcC1%$s2t+!}=}Ip+y3BYtETmoWiR}1P><_9ZZ0FT%gEZStrgdbp
zI0aw6jiD;PvU!g!GH_nZQDTX%5h%9pk4-fdm}0u~yzd;=Cni~sWY3r;O}XL;q&8-M
z47pHP%S*mY4oBx}PU^}#j%4iThs7lM7N=#@Fdn{XdiIW0L(=<T>C2~Fu=G5&Vq%Yr
zY&qWfrH35E^L#BnOgMwRc8B)xMX+GlbUbtr`nPrR=jS(Tb=kQ6o7eVqUZd`0fo9D@
z`vyo59#*A!saE#!$G7Cxfh7n>ZWX!0gkro``0W{b9=XxNGqu#u6^e-7Kk1lUvA@1|
zbiNNGZAv0UHvN5<U^P<r)g@-ialS1%)x3@yJ98Y|-=-l+Boj89EtWS_YS|uX*d!1h
zuEy_=OEANsq$z9D#HF5bZ{?yihjwlq>m{KO8QNS~$+u-B>s_5L`L`jBdrke`Xe{0N
zj*umKyN|_A>O3@@8y|M457h5tzNEqIDV$3|c%QOiW7;H(RNLM9W0najA-;HEwdD>u
zkW^fo^J0f?_u(^NWw(nT>JS~~smIbnC8QxFcuQkHNQUJyj;eYMTmvVN^O|RPDvczq
z>kslY@C$0YBmZR=Y@IEsCWJ9}K@HOu`c4n=$kr9!;n+9We=f8unlK5@c*o|I7(=zG
zS}lWvH_fp;i}4qt9>h_?QzN~ap@7nLh*7DJwfvLZA<V1CA-;}Ue~o+;dKMLq-ri8y
zikJ69`xKaRWgS_7`*rKX(ZaYWcag1=J-5B>S*_S4BW3*FwwCv)lD1Vg1t7><usf4m
zup`Lc+-g*3BF+4XfNOLe4Z%60c!!YYai||v?*gK{Zsre$>@!DLp5Su@bc$d;D_VX!
z57eDlJm5IkuTyc>aw+Wt#V5hkX-B+t+|!Fa@@x7=`8`3dZm3$aHF*y2VcD$(D>J3y
z%YwpO#gY%mqyin8q9uH?7OBC}Uv-@<Msy*~Nvry^su7=0x82-E)nk8ChDluiAzV&U
z2<tOh8_)LPqk+>)Hl0)Z2+y3x`{XluljPt{<4TU-m$XDvYU9HDGKSg&cT(@MVe23D
z<<w-7WUrsFSZxtTfqSUs&=EIdlxvhm#YJxmhO0_;`8k=7skM9}9DkoBCfVpFC0Ohf
z%}`!2CA@!leO$E-;~Y_b`cgTypBF@7iEd+J^tUv^tjfq6f2i#0!U;Q!z8b*8of34=
zl@qQBGh&YiI|>wCdy-3tTl}j4kzaCmrni9<xi<;Vr$W&`EbLm?-R1s>Diizn#a{@0
zu;<>ZBofe$`#ML)Z*Aj8mbPS3-(VgSdF3LA8LJgBPj{3IaB~uLBuylK<S`<40r_-M
zoAmqT`YvtUUk(<9&TZp3#LaaUTWN+^#exVp-Py|>O*>ev*i|tuS(Wff3`Ogj^{GsE
zb-&NcY_)d|4#vv+AW}skSWrN{p$KOp?oug)BPoM*O}*h=wrr-PGYCt{qwP-&I!~hn
z&+*{EI5^09slk8i%kFcTjCcO6Oc}M9iiS}cuLLw0fyMPfjZ_M`;HWDHP^ke=f*5^!
z(0!2p!N9nZ8J+AP5sfunmbw64l@2)3@53_`Q@g&4FYKeDLbz2F-Pl@Er#INAtM()n
z<;MFT;osC<bQdAh7ukANy6Uo{LpNoJ5JR#ZS?MQZM$7>}L2=Y4P1?cm)V{7nauSgh
zx4)k@#lLT}(uM?{Z&NvzT8pnJk@-MDvv4wOKsY+l9S8OdOw}cex#$t*B6ppAEloQy
z8u`9L`Ky$*$*G}A=)h6BjVn=_YuPie5epXe0vLKZP=Pxps%a&uGrdg4y#R{YobnDn
zWlMl5J;9|5ev`f`BO3Uh(yuK%@i^`+fAimq+_>*)z(;wrFdXn2nVJw1y_>PcV%p-)
zt#ZCU`}~DIE5y1BiI$qSd5XIebq(uRB-FnL#^x=bDHO-ls3({4R}-PgePOPH8ggGN
z^EAdT|N11qATWCZQ}ZY#=hum&;_xeyp*|O{VN?%jM$?&+DK=8LzLP4?U!YQ_JT0`M
z`m!W@TCB5mlr9&jJ{^cX4Ye=uf(7g!BDG1LQfZM#P5u-^$L1K~rOMk<H0023v|Tg!
z@<`0{o=Z{LBf>7oWI24W>ZJVoZ8!*NX>jC%2pgw@7$v*amGL8JnA`*TjN)y=JPix$
zHom^xjfr~ZFvhO?xbJHg&jfEZgboIqN@pk*%G-#&cX5DM>_>dMo{P<(#6bOBlgEyG
zzi7pMrVsz<*TT+n%Xo*+rUV<N^pGwpI6#DG$bDv{0i5)d@1mV;xmg}Wb_OS@^o9%P
zF#V0~d?t@Ez~S$v#O2LL)9K+Q5V<4HkHp@q@knGeChs~ZMNcp|{!@^b#M?o5^kyL^
zMbPEA3LRg=<{=5)D@heWx381fV@moa582tdi<JjliS(nCwtPwb(v(Engz{oD@CcvF
zzd~$BNPA|mS2m$u|0%)V15A+WyQcy7y)R#^UDlG!`;Y}^5uP{JDnW?6B0HvlM~UCq
z^av2YiIwqT7Y_Q@*IN|+7mdeRzX${b#y+sKNTa`!M}{7Wdxu_0l|Ok2RJHxgZb{;y
zSn+B7HpQh+`q7w*lcY?}HhJ_+aBfuLcN;Q|-fHZEuh@~+keZD+8(s01NP<A_Pz6>N
ztJqe<9EEg`YU)i$PQ+gn%oA5sG>b*6QUjZVf+Ju4QKWr32^B^>#t1pQ*dNT#SxuC8
zGNJ9mH6R-92O+m#!DECXMrB|3+|cZ$?^qoag;w8-vr)Z5hx@2MVoazOwqdLo2-lu(
ziwF<xL&6n-Lw^N|f3DMxm<_)@Rd-usrtOFEhAX5<Uqh8=vZhDSEcrKIX29ctvIybD
zk8_USJ*Q4~re>(a3SNH{+U_=d6KFp}-N^eInrfTZ{p%Yc;K{NXO^Sl0qy(%)%sUXL
zA6Ic}&$4}O+ulD-?|#%otX@aO$zD_RW;|WXeL`u5;iz`2Ja937zabkT*wwyB&2HIU
z%>tpNcvDWCsG=IjRr(V|Z7N`9^tphbhiC}fIrVcS>=>)uMStm;g&z-HK{fFz2ud&X
zKo;y87PhB3IZGZ&D%bJ2dZ<-Z7I$fCrPWl6O0ir})>f~+rM8k@!Q(EpWCD9f!k>me
zRhs@Q3_eZ!{s3QHUMaN0uS<rI{r;Qp%hmh)`<m<F-c_OKUDcphTas*?ZC+jIOHR&7
z=5Y_AhBB%7F7^Itr>hhn$<I{W5YxK8Eq=;a0}Jxgt{imOB@yy@CUt3IuCdj!9h?un
zN4A}f(~$LvrbNyHMqSzYcIG)*5EV(scW-Ek5Zl#i_wj!wga3K!Juv_kS1E>y!N+)a
z6F#K#O21}#Wz2NGar1LUq3S|3=0~&VTjxVeqcysO1QIGwgglMcjXc3^9R9i556MW!
zA%G1+Y)mPX16<vM|IO3}+7XE&8+HWuH=itySZvI)L1tD8NSy32dia>RcVB#B?R~Xl
zIeR6G9EBEo<!NPj`1l_*1iQMLG>U+Sk|=Q=4gQ<j4<3!sVEnV_N2#qa$LMWH+-Ra`
zE{KmwwH#KDkB2w<NET$K>dT~j9ecG~G45m|E+IkhfuBxT`M%^wZkNkXpL0AjS!$%u
zg-={lr6QW@$p<#-f}T{y|0rAEpY~$9`Ffd=BP+`1#;?ge=@mLGkdmI~u?Dai2i+}>
zr-d}7M&xTHsK7@<3$tPd^Yg3fLzxDa!oOJ_N!hGt&$}5!9&TOIuzhIP>)^&CM1CIF
zY>A-293)fzq2lbB(1wKk=>#3=G1eTT&8(iBSJab=V@AGnDSwOhxKg{m8sa~TR||^x
zH?*-f-l|Zq;GkYEt~~O`56i(Z6gi`*^TxMZuc-f=NM<b?&atrP|2+?p`a~XaL3?_v
zis~@WSUcV;T#VWjWjy+4jE`|*KuFWU*vs)CvUsO+SBkchc5D3%U8KBf)6Vc;<WwfM
z7Fy4B1nXku#^XBwDJk9<N9Fr#sAp04Mx*Q>lvry~%u2a>kz*@R^tJ@{7%-A@wfa)Z
z0~dL|Z*tTjEX7ED(M%2gZ|Zi_L4TWr%=BR=y4%;?-COo)8t(xaW)#f?Uhc9^d-1?K
z=UDW-cu<en^c|7YEA({$pTeUd86t0Lz1+?=lZO1%7LXFlksTzVWhH&*Oe%8Zxk(VS
z(7LDUjeg`?*4VZ*llKa~3TUeTF5<7e?ceps(n^0fhev>8J#%~t1$>T7o{K5NXC7Z^
z;*V>fuSp>3%L~xxUi%mG*w7fJS9FTtSX8!B_=C>_+{-q^3-7Yf_esz?&oN2)zkaPH
z=7a>>$VgQh6LDY?h_Px)L~(27=d%@0UX!M7G~G|aMRJuU!|xkIYM`l6jEi407=MtW
z4YA)qqP8SmCj;2g*%y+BObhJICRimEtC(yhX|B>fl9#O|OsP0h{YfJM(l{DjCSf;_
zp3jZ#S5YfJ*b;<R6mYOg2I?kRF;LN1+TK7+`pf>u0&zd}UOl+UMYMCH3kOBnUGziK
zo__#J<(BObj|aew5%LI%9K>!}5UVhW*2b`jvkYQXN*iuj#{{Oatl}s!dyhU&jWEAS
zdKj4YrSTiJ_b6i{@5nk1r@W#B=YNdtri~y>StL>N%B6Gj6O_fwN*T-2>1Q1KuQAXE
zF|^na>CtL$iCMfa_^Bio0%XY3)>F9Uqzf-cky+Lb_H)#4=L+?00yMptx;^o9v}fkd
zWN%~1Sj%)#ggvPi=IpG67#q)qrgFi21qV^a=2vb1s|%}692RR2)zei^W-5JD7Y!4^
zKfyt1dP(!slOA11Of$_Q<M(4m-xsoF{41>uYE4W{Uu+DO027{)!<nRB+N?VnRnnx#
zy^B5g{ySEwgMklX&zT92x9SjzIneNuB2gJOMJthYmAQreiVrQT!%NQ^lBcyRR^NG6
zJ%pn-fN{KiYSi6-uMpnG0ugSpzPxf%T(Wa2M($NIp_OnjvKOe~)nh$5O{EGSLt1Gn
zM*j&=mv6vnH=K{@JOu`Su-l>7+#Y;Ih{M{`=>H7?QlKD!&7BO7X`Q1_b071zS3lNp
z@&Iv~QHe@3e;q{$b7pE51fo%ee_qC4)CR?kSh?lvkF}D2UuE>>^dhlmktVcN14leO
zB6JLU^U}S?*F=`1q~sTqli@HC^RzgoXWg7-*R1R~2xL-K^`XjV%3c-JMf~;^ILFoK
z%NIwJAU)}8T@nI{XRp;rWaD7cuvC1dj!R2oZ(K-<6)PFEe1C`3{NVSlqPF<~-&+*O
z8>_z7E5JY^sY(1!jgGp<)OE9!*nx9!RX3U}7tvu5m2XXS(V8#x+t;nz?=xkI(DDsz
z;3foX=tMqziX%?kztoj_MYP1$@9}OUi0@Z>26`$9-KBzz0d+H_Z`PU9?gSA=7?H}0
z{c+|*bet;11)bfWS<)JER^z_5PKJN$@9unBRX+W*oX^32ly<xVw{^8wg!tS@5$mOA
zvV-*+V_0#NIvJfmlhan*-J;}~VIOEsd}aFrm(Zij%h4&)Q}r_gg_CQ0C$l#So^7p>
zKo%s>e!Q4Gb1DeiV*R&fzDz|t8*WBSo1ove3somHjybczT^qp^D^Tpx97n^9WuuqI
zTCFUlzMc<9(@Ng!L9ebpnk>0!&~jV<V^T|KFe>#<spUg}5Y$_N>PR~Lz8p-9KECK6
z;7<rs=qt;RJT@>0`vR#D6@#_MoD+$Wl*AAbY|FVo2J6vOlP={WYAqT#$Q!S^VW9|!
zl45qcN$prx36zxggrb_z0Ri=i%$I(SJ6jHx(uR<;b(=zb>GDa-cZTt=EWQ$^+AKKp
z!qtgInkt}{k=PN-erHn(dHX?T{g44@;}a%oYTE7q*K<=mU`H~sCkX#6pyH?M+0W>N
zUr<(Whv)VbXit9iJY4V&;_6xGNVK9d_P*yRzW_7PgbR$4WPdD<VSusjLlI()Q82sY
z+kMUXO0rqfgfSoPD0eyWlcPt4ltYz_5Af6<BEHeWh}&>P<bwXH=XFQhN1kLWK^`vl
zvxIFVF&S7E-eW6|LBSSrFcLP*e;W(Rwc$TlH>8K}{V;@u;E04A#lw1fxY9qPYX%78
z<E6hN;j7lL0qtd*NWA&tJf%UHY`A)%NK96vr*%^?g-zB@SE_dfYL5oe%)}Y=P|Tm(
z47xne{xGmP7m>?2?m&UC)5IREFbfd%r<*8}KZbL-)2J(s!Ni>DER#ah4jLH0*2GPn
zFP?pTI`d2+=5;~B0pYU=P03Hk<$aGh?7&|CDV>N4L&S*{xjJ6{dn!jPj@;!U64ZH$
zxcv;Jen{NFvhysj-qs<-RN>Q}{r7$Q(|cMV#FVvG1ygSsC^0)`=DwB(4Z7$pIxu3h
zGr}-!!|aUp$DUaVeC9=coIL@I)g|Fm7a7u6JRy|q_3SIEsEkSTn?R2}SIm-}g0D#x
zbFUgC%_08XTvJ@!3#F5}f?b~Rz6CpeASaHdWnNs?a*HD&&M#AO;^>?RDV?gRN(Q&_
zi^f)H(H|$Fg#yiJBfM<kXbR+8_qS-OW-~?AcS^3}(a>*7wTkz_6un^+@bW^qdO0rV
z#80Zot!buo$fS$UtrjI&1`2u(g>;~*Z@I;ZrS@=@)pCyAT-4)ZtWEg^;2QgIBuYCv
zCOT@SpdJ=?l}vNgolDevUl8e7VBri&69qGA<l<=PFtgy72x<!`D%&pU{QUIO<p$OA
zjF-bg6OyK;s8eZg@0b9$JowZZYM%*}>FHzdn>vz;Z2PIH&X0IdJxq5&G|lD{8;FN)
zyoId^NoH`da-U0H$$EV_2u9QwI2NQ6xr#xsr4!7>PZTS-+^Ser28(<ufWAbzK^iL}
zs)K+^CDFW1{QfC7@xj|5V|KHulJ#Aef4_lBRp1=q2?oTXqKfE0ULI7834iAL955Do
zSy~-pe;|nZurhRdl$28Y`nt@lo;`yDk8TFLI)hSu#B=h*&Ttu}xf-}FJI8wOj2n%E
zu;MZ@I;|)66I4G&8E?FnLgR&+MXh9y*Pbe>?H>pkyr(t|p)Hl*Rcc?7hPzry4)mHP
zWLX9>Jcr0gs2&(aNg9b2@BIl*r~2X;kn=eI%P577nIX=auf8fXGcC+->CfU?wHoJM
z@{%ht0!KC%-tamvUWKvNx!08PvLF(w-EK-u?Lgd+WfX$LOYI=5t00MKD|Q)#@9mL5
zWZQ?eQkgCCqwANoAm&<MrorQa@YB;5=z7i<ld%Ofgz_!qgzU};NJx4Xbn2tO*M2c9
z9d?~K8;6b~@JV2`9HJOh8+HrL{3(yRSote{jC^a9g67~g%|9M3Il5>K4|fw*k7ipV
zB1v2pR!seE-oV-2@pAb2ne;%k;&0+LB7z16@)VH1MO9)MHU9kSp)dCNVB91j?4mmO
zv3NP2%#IeX=+W7Ni#8IjoTpc(!3<Z^&wgraQ+Ie66!#M2gbtQ34Uz6O<}jO*XaM8g
z6C;4cj!mOQ|J||_)bfCXJ$~f0tTvCt3sM@w#~JVwVq5y+y(mvHNdc`$`Um)5h0U2x
z)Mu{QO70#@WOQ8uNn4C0bVBT<?z(@Eo(2Ew(3$a-w+Ab}7*^JBmw<FTZ2pMaB)izF
zgR1AVZ@axD<4}7I2AeCsuJwt>T*dt7!EX8Vl<I;?FO50vnpx1YTJ1jCX)(4)Nj{N%
zQ849yy&@&j=J^g(>AzQq6uvFcs%W{$71OuAvW8Wpseg+*PVYLv?WMN=C|H@$;E<?2
zy{V$HT%SB%FYRPvzJD%)C7_fo`ov>_S5fVp$L;GyupU7jZ$kfvC58X?uLqEZijH!v
HqBZh=dic2S
literal 16900
zcmaicV|1Ng^k&Q(Hn!T>_Kj`Zc4OO48rwD-G<MS1Mq@Vi-2P_Pf6b?v4@vGy-W=|C
zpS_>w*(Xv_UIGaL4*?7e3`t5-R2lSh^xqd84Cs4}W^FDQn9zijsF13M{zVR~<`0dB
z;hww3Rk_uLO*yyZ^N(arMN#SjFcHEi60E_fZug<B-Z(hc9|bGkSKT99oyHP$K-u|n
zr9jc`PyX#|q=B419>`IjtJ^LVtno=lKj+Jze{_WszRIN1X*HUTCH>C_wc;+D)6YYT
z*RWmTUi`Puu_Uwkj<o$pPrusj%r}Dj%{SY7MbnC{1HmJy;yKgqG6L^Ez8~9WV_rtC
zkBu))Zv;Ih0AviFcp(<NpM4Iud9PHSRtyW*Q9aG$D&A6MQR80UdQHb@G&*iYdL;l6
z&*F~9IaLf)qv{I13YG3pb-Q9r9@Z>6-qwu_Ue*kO&$%=o%J?6*rej_Ock3znkGIb6
zWm&yS2Z9LS7slFgUx+?ilDgQBdj7`ruw|IVzJ@wV{&tD)G@SPTMW@9Wl5lcsuU~6`
z7raw|%Or|@P<iuewhar*LG$fv{PV-rumN!^YF+sriw!F(vXddsdC8crID6#HGQV_Y
zQNMQwU{~ee<Y?4p>nlh`7!!rA1H$`p;<B)P>zz}+92Tp2bFmKDAL`nrC>)<{qBHso
zvJ6|o^vMxL?frh4XZ`3WdH7<mM;zSn%>s_NI0p@{EElbnX*!yp;Vtx&K&w$&to`sW
z79>enm;xWhu;ZKKIN}-h!eBK<r?3Weu&JNQ#rPT>ZM6j$9~*Q(SlE*i_bHS0o#tPY
z5-j+ww|x>h9%`RLUixM!e%f<G!vp5vXXFX8kGG@oBs1697je+0Nx}#TpoAWD*|IYb
z-Ssx&KBQO74>0qVAe5GH83X6?!#^_j-M@lO@*-aD%NMF2;Hg^Wgh@}elrPA3o_&(-
zeNyws4es~%;K1o+pfG(Z!G-nFWzl7)ejRNxY?M~uI=I&MYuz@4>GLH*ptjlQJ`LYr
z*KIIVzBhKHIDwe`X2hc@gsdjzXxX%b<_#kc$vIHFi2)-XM1=fs(`g?0)M{lcJXwp<
zBgIdDXM&n-=+_%;1a?sE$oeN{r%w=8tFfAl<Qy{<_vK)o%n;sjWBhI?x8=90Lxkg`
zxkx20^#e}g0<LSzIxbF{Tmco72&OwY!+=|%_J|fBLX?~-X)wN)AGYA$bh|T>QopAk
z%wrVN=r>)oZ0w7^M~Xi~qp6lEaABgF<Z2SwJ{3&bce8KBXsfF_q3EEvBsuR{Xk!fV
z#)f;iYiQ^(Y|-yJ2)^R36EIGnWJP5LR`fjt>(ck7V3Un;@cg|ODuD7@fw~OZ;^TQV
z$&4AiUj}-4;o`6JV$Y4C<S!r5P|PN44CIQ+sDKy+`zUdEn?bMs=B=5}O}j~Et~>2G
z8hVweUdzl78hW<t^KJYb^Pi*-y`5~j=yosxBRqeq6FN})Y2R*>zD|&J_)oRr2JdJP
zA&lca);^P(q@hQb9-kqN<EgkX9B1Lg0x!^RwSp0@(*X;oZOCftvUGI4_lBx=xUhLb
zo3nPmzU-m&+)f%w8l)wW0-YsCBels@kMfp_Lh9??mD=nZ*zvU*Wd{-d!a#GMo*nMA
z=lqHGJ=0_2?8Y-csf@Z852{7hrkD&)J1n#!Wx(38*yZz}JzrCxB@jR9tG6LU{M;p+
zwQ)kSjO%)r1@%(I`<3J)ARrMR`c8)=f1Lt6$3>XVo9An7Q3NoAtyRQw-@JUDD$o<f
znMRf7r(dF(J|Ki=lKXJy=}$IiE1fIb0^Ym|0TW#X<*B86mU(PHgf6oWN~L>luryjE
z3{zzbZhStP-K;xw@Yxf-B=4h(p=4f`k8p2DH$>qQLPR!szD!2|vJ}J`C6=EoRwG^+
z;`ZDv1SGVO+?IqSxpxSM^_V~@2E+~dZQdl+oz;TP1MX+XXwugMy?Z5AoZ7#R33Y@T
zM)w4;9L0szO3>6i#4fV3q49@wu&`zcvQ!d8!m*dpn&7pp0Y=;QbiyOzhC7)Ki7tDt
zXaIqysWqx53ZgHlO)|YRDG**$7&F{0a8VEECY`3;yx)F>2;4Xr&gC;Iqiqx;orWkF
z8xk0Ty-mK&z`^~Fbs#S;;Qd@1ZFJh4R`+H>Wx$xgn>^oka;w9~QfR>rS7lYHG?D#o
z6Jo`Qg<Muls%8W(@Bn4~pcP6}Gk0gmCKDnOK3h(_4kg0j6MLXiCxDBi%<rTP>_-DP
zX@kdURs~L5?afF*73QF!=HQ?vIysP;FNCMBfA*}*&%$eDHh5L|y~D=C^v8(wdtcYZ
z)8Q|56BuZ~3~KpF-oKg|5Uf@Ac15Z>sP<9hpm(E>^cgr8dMxGhn7mnWA+JPK+EGR;
zCfK+V1&Xi1M6CUFIA+oJqr(aF3W_=ph7h;IVlqq&xJ=d(CqczQwL>f*A$gJW_|iZw
z&>!^cGyI)UH(_%jFMta0ci8K;?^D#C4_`@%@wP6R4qvs8y@ecdj|*ia7Exg3*BpG4
z%Dqav(-_hWolzv04-3Ygs)Z~U$`R?hQq2Is2`RWS%z4?!GF2CryzMjCEFg_Y%K+yz
zG8tm;0X{;XG5?BBT|pMZ296(fGUtoF_$Ryrso&s;Cc!g3a;pYOn-tjPvW+1)iAQ)I
zaPyG(wl0MZUqz_Z!4+oEh$t>QIaiZ+J1|fQdfugliOCAg+6D!~3<-k#gA8N#Rk3@5
z&u3Yevetsi3m`sm2Ntt>FV(PfME~wR=LFu+2@Noy&wr###hgP3mjy&H03re#97OQ%
zsZ;NtktNoC?s@G44<dmW{f5jWvTyiJLH^yQs|kPo;mUvH?YAxCAg|ea`1SCn%9nW+
zAtbqiTV^rUI1*#_DYehL-JvZU|HMeqE-qAdM%s9}lUT3VidcPhzcs{J{1Pbv9kWhq
zc>Num-@G1zw*?jMf)<DEgHaOi16Tc_GG^ay^ID~7jj9}99V+-ffa!?g&F0&L_z(A~
zM}JT5(~DuP{Yy+if=%fxqzfhy4MOrHp2A$WEHs?v`3DSZv0AY|>dA`SWJHyI-Lp=m
zyv8V97L8$~?>Sf(&Ee27TQvEf=-_%~EL56_n`*ZRVS`=4Ka4&HGjr9P8e3rf;8BK&
z&0s~H!Z|V-mPt9vUj?5&%Sa@;XK~`TS$ylgW4|1h&I!<9c6_zoDdR2)FLErHw%Sow
zwc_2ZKizcAMchMvZ^6OY8)<qG>uiUt&RwA(`3@dzgihQ1MSrNi;ruq-C+?oVa@U0x
z(>^4ei3Bedg+!LX52G(u@W4P&3sdv45%OawU(*aQat~OuEf?Hi6Zi>__qCd)nw0_j
zvUwA_6WQ5tnFsl_AZNz8L8L*=L4?0A>inj9l&C`<n8!WYp@GJJsV7VD2F>AC71u=H
z?bu{Q_=al@1+|F&El|te2eQB@?#+g(D(LjFx>w=0X;CJ|CQc@tuin_)Rd$KH$Y9P9
z${MAq+Ns2`>_SLAfKm9~%?U2bK6><zn<Ur6w>hiDEbdUD#NMd$hR*wFx8TxWVY3Za
zM&tRPhR$htT-*KlZT-SGBy4YD;6aZfAz^Jt1`=ABifztn#D_;u)2WTa-Bo^EKL;=o
zDc6Ov2x3y<odK`qKZ@d;Abc>bU1B6gkFjv-UvyFl^(EFkIb4ht2Z<LM*J8D=dG^q^
zMkYxOjCGDOM{O^I*O5+BWk`rv1mWOE@+P54krJ^tP#<l=ckM4=+6h&OIU&z>(*io4
zW(6^Rp7OMxVh73mYH?bkbxgXB=+<qd2^f6f<36>TL>U^8OY>=P$oXPkGAmF?6#80T
z+e?24uzuJC8?nCu`7)ef&Nu8x+`0%wOB9wmZ^(+|&$!T80~3uj?NRH)aNhf~#vN9e
zem1VW#bKd$SZ4ufS0-pzoJ%P7UWdT@8yg`1+kpYLV153t;UJy~P8@7sO+#<ky&oE2
zaTRY)D<ZM)m%mo_4}){09c-QN`>{ePIXcSgw}v2XayA<>Jxh}D)tMOGRgJY0QEJs`
z{>aB;ssVeqKi-6L#(PnBpPuOu<4Rf*GWVk8BdM<!bAw+)5FLpck)j0P;g-XUo>Cd}
zc^_!LU3n2YWBEk1?0<%f@MkB;t#h0%&cixNCZn@Lft$eDVl6z=l@Ga}k<7cF5n!!o
zXet^Q3;AyG!j)+$=3U>7D5c<q^S#x_L*FxCE%0h8`d^(!P9b1&)aUD8>Ef)=<q!3`
zEx>YMZ)jSZ?)!6EoSa3kU!<iV+p|9{t1Yog7AUs!^759kl+_ECQKgKtv<Me0u){xU
zvNK4lG5Lj18W*ZXoYw+7Q{~Io3%NJQ!=lyM&XLM?;1=x9;!=Y@!qr8739mD3ld??W
zk}v8<+^du4Lfdz_gYEgeF&99Myp%KlUPme7^F^!q+sB0B0C>3W2Xn`K`Pq<Ha|d)N
z;q~h1DC+D-+Dz}H`Ee}lrc`+QC0$^;y5!2lQxMKotyYaHh3Dh`k6e%Q%VuYLdoeFJ
zH~ZfHz~PX7I>R|ML`Ju!A)|K2`l1><Ha2;a0S_c>ErJG>o*qIC72B&jHYe36od@P!
zi)qQ9Y7g*>N;Y4;sSLlPxvM;q-Tzw2m;Zx=x>{mk0;Ed5zA?Hb1FrDGc6-;m+iSFU
zc22aC&R^-iyw5vE$D?GWWo7A5o@@>d3_uD92sGM_-tlsdQ?ZbAnF4LsSxDj&0TFgO
zFbB*@;0<;Y0es>tB&~M12_up)gRS(Ce{seFR$9$~MC8~S%gCTV+2AIiH`gndEW2~H
z`z|RK5KuxIccy|<V?aP`0WazqQSmO_S2;aqzDxX>!;Bkm8puw0EcWFE{ij71G*o4(
z0~y!3%z_nq1kdh3x<;XVQS{_v?Q3|H1so1Z#CL|Zm2Z&7-mTO?&1?U-oogOAE4Cm{
z`d4o(XCnWH-J^hx&?7X^xHns&B`u2*skUy`s~w=0252bVaZy(}U?e5?u>fG!UbYaS
z4Gz$YBX|~|U$??YUR+zxw2g5F_OJB7viI^}qx|ouEswnc0o{D4T~~|912EVr9)4P&
zS=*@uBmgy>GC)sz_8A$Iga2y-R#LKP$zyVe7P=4Vrn@Q)Fp6mG;Nall=^07<{OPT~
zPDD~5M}Py>^H&ikOMCrXaXjFMyNuyNg$gXaPOE4z3=$o3<OMi7)&qF(4hc$VL&fJj
z1E&LS!gpH;axN~M{o&Ywv1H1dv$~$Wx93|NHp>Jt(guFuvAQbA?*MR;Dx}r~+zsgJ
zzCtQ*$r?UAKNl$E39K|(pdcV17*;zU{VtG7{)QDicnC&XAit07AxkJs2xbNxkEh-l
ztI=-hZ#0{5e0{huHk5pMKFXUdk-_HT=8j~#**>ze%L-Vq--ELbc7OqlEqqgfDL$7|
z^zia3^m~7il#>&4bK{s6W!C%o9eQ_nw_LRXoq&)qk2e`~Carh!_+@C+^?4E@nB?8v
zrP(B~aF_-3_5wx4#3EgX2f|T2iDX6dBot9e+}zxz-+7y;fop?^#LWumnJ%(ER<|F>
z44(0)x_-m7iZI17bV#w5<;|{V>IZ-R+z|XI2d!L0M$z{_<K@~dl`C(tpC1Y)Szw}R
zq&}HG+Lrj-8Y`=$N~2}?8b|l9dGHv+zjnS*Kq`-?mfB^p+k|=E9!9dNC=>~PzI|b}
z_>I9TkwT-USfkDE<T3o2UFH-DLvho7UH5}kxNU*8zhI%MKhH(OJ-i?BY$W~-^|55)
zWa=r&p}q^d3PUbX_6!#<7Dh`;%jaA$@p7{>yuoB7YJe7^SUeW*JCd>d31w)Viag>w
zE)Hcnu_U(A@CEh^w;UM0IVsDf+yNUB)lCpiM=a>2dMS<By}F7IgoNFjDG)Kn@=#YA
zK3S^4|Mhywi<Y3>Vx95URpuHBLGh>h8fgM&77%eeba~6*@>lA8=;7iEw2QP4d^IvP
z8fpiWc?lq5kxp*C)nS|HY^i2ov(x?A!{1u(mk%xyJ_nmAsx{Zt=LV=Ta0-O}2|y4O
z5yIAhMw5|xp<jAS{T1goL&?&tBz5keV*#2g$~m*;O60P60)&DePvpc$(-fJzJYIL@
zMljJ}1>3lvw|Ps$0W*KZd^Wlj=W@{AaG=^es3_){Y~Jis`IYYiWN~ho|DLil1qRD5
zN6xAlvXG=U-8`VKVHr!k-;5Bi)EfnJRTtvY$;jR$#e%~lxMV?xboY;JA{IT_^y}D0
zw1mJ8tVoSO-(}a<iGU($L_s!qHr~lX-p3`LpC;{qQD>bsB6M8b$Zqe)Ok0$OkaA#I
z48@e8TAlv;PmB6dbP|{7<%qt@Ea>I;PRL4)=M`_G!A40Y$Xy1Mum)I0#!3<77H4)u
zI6c{)TUsy&o^*@2H9Bp>QJA#S8$`zN?+@z^IIQL|VxYEQfVw~Oc}Wq!FS`G2T=aDu
z-DMYe(1$x=331oN(i#yV%?Q)lcY`}FpGRp*74@@$fX%pE+dAGOh5QRhJ&mcaXOhk4
zLi_pirw^Zws;d9n^#IE8T1ypZDX|crNABquU?iL2;Ql%<xZgIthm*+Me<Sv144IQh
z+y#X35h0I(f?Jn7vV3}cbE`r@p<fw1A#t1bZc1YTd?oNP`S~O<1l*vNz*T7GKk&TY
z>4Vg5cNBt}OJdbLKnEi|`g2q%v70%e<MWo{Z54-bRg#0&`qxlWo}1oq$2e7Jg~<kZ
zzW&tUlrY9b8!wMZ!)FG}oq-Cgt7G(fIFBUcbt4fJ%!@avinPuQhDC|WkXbV}j>M&7
z5gdFef<yi8=NIQs=cn8~Fk<)<8{fRekO|}3F~KUG-K#19**0R4d)JJ>u8Ix3n54MC
zW40SGT11ajrrm5AI24T?-2$|VMsU%VX}AMmt>Pr~B}#An{>%QG>_1FQYV^)CExzx2
z&7E_9c!fpiCLci|F3H*eM2DQQRtQp4>V2&#1RP=KX3ZVw#OXuFxj$VDmM<G<u~~j<
zr)S^na7COim80bdHj8W`F@;C%2>&HQD{*dc7301976VQyI69%EFvxxn>qC&L<E!j`
zIHGshQ9G04HH3kRXKn^}+y*oVrx5jxdh0M60xK|Sd)@G1l7$%@oa4CL9SGkajQnDZ
zq^Mqq7mh3yOT@pX=+DK)rD+NiZ3~dhN|m0JtBX_NP2nRs2(SK@;dIooJ>o-`%ImvM
zCv>AXKPcD26Z_;m`1pw)uF6Mp=RnShU^yM81!?jbl!v#-kSa#RLhSOG0?yp1YB6Jr
zW=GrO|0zIRSHiH?DYiO+$EpdMkwz#4I6V(J12-W0+dAo4J*?nDQrFI<*}a92Y%1bU
z`RC_4<V2ee4eMNw8s%FkpJh5UxnvQ!odmE*L6UqK9@Z+6xHczFTyw(v>tyg7>R(8{
zA8*g?PWv##WoF+p0bJe>whg#+(1_+A+<aAEfj?>)9HS$|n?k;(r=Le*vR;57rn)2&
zEkD8KBSZm#3Drt?t!*#s#>0+yUNysIKRg=t`KSOcSHieiUP0z8F_$tZ(ciPnq_o~@
z%-{zh<J*Veq)ewvHPIm<P)@9do?q;~Z}j;4gSzD8<V-%gspfzg<TvK(YEGM>bs{i7
zt~8q8%WO|MF(FE_y<dA>e*bl_-@NcA!S9$IMb6x0`e_oNF!hy5a)<B9TQy2D<cFAc
zF?41xX{OMZmefU3+w>H^H)5)t(}ek4a1Nc~FF4@f;5aO%aB&3O%B8NuMWWCzYb`d>
zQ-&3)G|5M|pzcLy>pA(p=?3&XKn+v0^`HNsS?M0eb+60BxF|&Y{?>MI^x``)Vp}1V
z;<0N$BUc(0=p=y>zD3k<q}Y8+AcCcIhZE%FEx<NLcJ~PH4f57sm^|KipT6?7YhrO<
z5n4J2NHz)P@VNF5$KnrBFZ`vUnel)AgDsoGqc%y9n%hT)4PFSW=~V|s-Gnq#m>_I~
zMC>T|r<IzMPZsUpMmccM`~7q^-gBxE8n1Uoi@@k@#WI2$Y*f#I7`5xI_*1MgU2N2@
z^eG)oSYCiMe_2*N+|r=0Vu@%7?B{{Xx;a?lDd3cv9kkEP*W;ZaA8JRpl=-jMJ%r^M
zCe<r8%uD7nt!zE<kwG@ud2YkLV(WH~-pw~fnJZrqo`&ZGr=v%-HmRL^lg5w%)?Xdf
z8GyHmjcJ}p(SA=9aPzv&i8wZs^1@?kH(d$pau473%lc-?eyx*})9B>n!T!wN%lqT@
z&Afsj|04$m&CH2M?F|6yeqb+e`&JWTP^~~z(;c>5;z6RuFKe)%3j|YzeZB9c)5E08
zvX9?L9%?PT7Vu(RAIXR}s*=I<uRwy_BSL{QL;Eu-Qa(o`mnTNne9Sa30EPPEJt+@<
zp#ohDc&Gd*U!MV!j5B~M)TLn{`N4eLPTO+kv$bEVK;t!H(BsE%ztuJNxvUZm<n?`V
zX;2AC&F&+U382#5nDIK+u;g9D2ceKb>*@Qp<*vA{&7B2uwdBH$_I`33U5di9weG|3
zx-Iy`1L`R>G-q<+w-{f5qc<7ls}^cT4Y^Qi+meHXFIDgqkt0wpdBZGY?LB+q9&o`T
zd18L5%R+44Ml^UNbEw58BXP#{+I#J1$;VGO`#6Grd<=RWgP+T+ktE6H^>C;%(}szj
zK;wt^oW<tgof(@F90Mq+=n&8JLg&8fAC)T&bMQR|%m&TaFS`11YvCXVKCa{ZL8~Bl
zX!HfBiRXgv4WYI!Z!S^;rJig#<C+@{PjVn61MRZd6~tP@hcr-a@OEY_3Jo#X{yUxB
zCbb<x53jHQ<07TdnY6iL20clAriMFMj02|lPk!CdidDvC{5bvm4$t3wF6Em!UD54g
zDwqgD^Rl(wYb>!yG4Fz=zm4zKw@$Wdo`VJm=879kp$F&$uMP_qiKSB4L@SV)<o?t!
z@b8}I9N>g55F9Rb=3ocrK>iqIRR9n!X0Do*Ldi{9M&^sg&T_TZz~>`tbXc$p%%BI%
z#MahUA?U0t#2ZA4_41*w&52#TXU^_G4)$#uGOnpIb{Gs?Bge_xP|beH;cUSBec^gk
zu;a`And#3j5LZ)LAL<cm7Q+mP2=~Fd!STmi<e5Z8e4wG<pEWU}FV0~dCjlgckVACH
zq9q6%IKSam)`{4|E{#}*z9J$;s9GrM5PCf_#PW!sFXBVO08lMbOJy_uZixMCC|@VD
zV`k3ntJG>L9lQ0{$A?tzx&K6M(;#M))7n&`7KTkT>KvjI7O4?mTa;X`81yn7WAir6
z^Dv#2{~#3{X=5gyP*2v`3yoLJl)--n2rC2}*3n8(L~4ohHzT6QbyEu{!K3q#&p9Lp
z?3#RrZR0JWoh5V%Au%m2?uSB&R<iQA92+*Y@+cI6j44t_h023EBCpi<I5`60E*hIL
z>O!i99khjDd#7P;NaxJ<_f>mYXQOtXqBZif<x5V;e8$sJ4ucprdS6=76OH3DIx00;
zr@?!2AN?pOs)?RY{8}AkNKVZJa%;%y+M^NF<4tc9%D-iY`=)tTYcBWKE<%Yiw9%%D
zS*EjFv(hfL)a~iYFgm5X_PF5~>oWn1d5WC&hmG;&Gv(>!l)|)selJ-m-pz9Og@*rA
z%Xl~n+gHI_Rjy513U_dEaq-~ZLm%H7RpV<IR0p~J+;&2?kV82msqT8fkP1sSj2%4`
z1)^UjAV%_(0=dQf^t|3Rqv$6qMVAAHX%%m(_6P>bREoW=Zu*D?n%JFyy6(v}{RCOy
z>_wu--o5bv-4rRuWG0oN3a2+(f)C6nR0%>9HdI1mB`d{jE6Q4vSf>>{@~N-bGMc6~
zn=1MB2?XIjZuOC!s@-pN5{60UUw-L4f1L-3Ohud?4)I$4Y&#w^A*ij(1$$3|Vskv}
z#YKCOBnHKh5QN8fd|k)wI{^HZj_1!`{L&>R(m@P^tYk*J)5>eCrio9{j>kWLDCGrM
z*O<)utCbjQiH>aHzD!~>S<PU3pyI^|2H^|uA8K8K@16lp(bU!op*y#_y`x#B*bbDc
z7LCa{Z6vjY3|g#Hj9@0vV=JdXah1mvnC-C=(k%WxIkMjH1PFK%C1_nf?QEs`jYDCF
zUTUHpRm64A3!+5iuiW+nnU1zIUP;N%T?I<%OK~d}&sT$agrSxf=YC~O3^hi4ze58t
zYrh*M$%Mt*g#V6dL?bm7a==9py)xK`hVB_Ta-nZ_kJFQw=~*NkZ)SVx&6coZl;7FQ
zN4qWzPH870+<`J%9aos>NyzV|B?uyizaR*!v`(g6N5ks=aSqWHk#wzbQOx2Ehc(>s
zfl`oSK+EzLOKDeK?n<u>#pu;5qF1g-8bXyN##%K`x2R14CxOh8w&P-kz4U}>3Q=A&
zwAa>sCXe?|fR^Y+S9_jW;=!_GK`1Bc2HY6Y)*s}A##+#}239~LV&Q~wL&4n_6^@vW
z;nGUYJ$5-C#kJr2EtD&Ty$t-H)#GyT->}39LWB1gdo%LwqR8{YbRBL*-FCEc5iY{;
z#TpZ~y8yolNKuWi&enqz%<*)Y)j#ff)9q1ezkI|N7|zr3<o?*+;JRvZ-Y?YN3nrDc
z<Onp!j9Mf+5A2NRh3|Az8KhKm@KH&niH`ddg;Z;SxUyCP16j;Grz-FV0d?P3g)Le|
zos7y#E&CJ+9vSa&X1`JVNHhrwj&NnqqCPt(M^2wsW(6k!Uf|=Y$zG%w@JT7|R|gxi
zr3+j8jJ3EnSpUKST|4`Vq!l90IE9{SoFqR+GHa1EC1bt2R5F5fF*>b=T|b>+m?)d%
zKJ;1@L~w8ZQn0MxZS*{ew-;Ohn^Jl!+U{m|QvgB~tai**t#d>0E=CMjN*SZ+36QnO
z4NrSN!Cd>9SLf?=!Hjh+ek}c}ND_U`vvi9(MS>7nGZ*l<Hmq_}pg^NoxPAelAVczK
z+9v-jKscGR%3D?J^Xp3qcvM>Pm%4(7(bhfuTHod8y%;N{YO_KMV}N<7D)x5snD;XG
zzCOH#WK2$4mAvQWFCCZW#F8TRInJ+=$6eR`V~dES6+!6-=6lkVCHyCW^Bb-$@=b%3
zi%hxQwAp^EOp|zR61~UikJsM89qE@P3@X5J>+K)hO6K`Z$80UqhLV&|mVt3wQ#G4H
zi4>T}s*jr9pkN+B@=LbuMW8^kzEFQde*yOdnXiUws9u#OD8dYzm?0F`qCm7pBCNNz
zOJB@PR!5?2&9Zw_Jg~i=TwmStKiYq<aCxk}5?tZbG5<T2QE@w{`v9b{e*GpE>1_@$
zZKB*^u}y2o({7rV#Nl+8<Rdkl0a@$MpN!_-&_Ccw-kxLT);QIY%C|Au!%Igfx^3nY
zqQW?uNhGyO*g%79wi{Xl<pL%^<L*Ucm}hQ29FcEt&?fH3+ltiY=y5&ppGG-@oEz4J
z7QH5KxK71nNG<)%_=$zL><i?GEBH?(B40WD(*2LZ1LB`N{Ao5PmAglN&FZpl>$2T5
zthMF3X`+*;4Q-~<qaR}9Th9vMz1AXL>&-*4NzrU=7>#}h=jB}<^tsAch7Ac~Vq;V7
ziknpCHOP}_P8F&VE%6e`WG~EVa?$ra`knKZrYWbIZ_w@4vO+{B!(Pb&!YhY8pCfe=
zjxF8x>Zh3;#gw`fu})grVJcf=Ohg_<xsdZ&$;Db2&61EKPttRh=b4(sN_y`B$-^iU
zbaR-Yb11Loh#pK7^C%^llk_r#NFww#waCKFozWylT7w{l+sUF-C2bd{Wnaa2cZe^u
zn|G4%4HN4LI(1E&Cy+D;QqbqgF=GjrLR+E06_dwL=4wv4Tj*+|*(R0fY_3G+nX##|
z9LQLMOV`Lu0>Xc9m?(57$!NXQ#N%;Q{V}EjtmA$m<@Ie2(h2j9T2Xq=0<2R#daW&$
z85=lCIqjn+?h$SF4u|?#DOOKg9>2c{9GSdlh{<(WR;Mb+bxH>u95roevUiqSmcdG*
zEL`{Qv+mA#hjLxuC*l?ROBgDsPYkDNU%;m09$2^ni=SVA=kS_<QrbUz1Y8%cg`w>)
z_h->URCbhQr89T-a-Gg9Dk?P`CT8-=f%@A28AYMmma&Ks#DNDsr^|eI%nHBQ0Nps*
z<{@u^G-9krSD|^{Vm?_nRkW_T!;E*n95To#4sxn;9FH2W%&T043S^Vg_Bk^^&J9*H
z=-^Zd6GYUG(CMkA?hy<&4Tc5fn4$3ys+ZiGw!07qHH1zPDzAJY;{8Oj#B1-LTAZ>D
zKqX)c%j0#o|H%z2zdkxYKaV6<&nEMgP`q%2&v+2dsa++rFeWoOnf$VkCAY6|8|kw{
zdwe(maC?oeGlx#HVClH?)W&QZ`+=l3PIeQ%9cb~nWxJ9)YD|MPt`v?0-3bMcbZ<2Z
zG7xSnH{QoOr#C@?R{C$168|JMfCxcPAVuEhewgQpYO@AfbP3Fw+|Vi7h~L@$6ydj5
zyf7_h9Rp$0Gii0mkT9xddqw>hIVCXV203~$D~swIj_)TV=zX)@-tK6Hb66mM;EywH
zsMV;{!i^8fva<OFy6>e3b)iz7_f6$4yU2i-b%Bh|o@eU2$RD^G(AtWlyl0^8dxd<9
zCi_xU0%&wFugtmc%-uOk=xMY?lR%{7BQRZ~b8}1<=DQI)v2*#3|70VNVV*?SK4O}0
z-HEICfCoyTwy@{F=Ac>4KISQEgQLDcj|>j}h<?bSz+1B0{-w9kD!eM3*<Z37%?4E*
zkA{ZE<$MVE{8K_UuE}NuEQ7P^4<ITksnw<(11+kf3MpfIy*u6n*}`3yO2>zn(*RSn
zZw&u6!^Z2~7ae&u`+{IHYm_vxJJ@RRZ!LoCjQ2ecK6E;Aqey<dg6j^l0`!YnxYi9$
zM6LAhrXuv}BqgdM(}PZ8CZas7EFSpef@p;1<$!_e)*`_#yxN-Rs6oNz6|Hvb!y~|q
zh|&aXdTokY2g!RF%s;~-*j|$hW4@1<n{R1pndLxAptQ|@z=;7T$_-oy6r5g`(6WW7
z0~Lg5P%%i9;@gCpDpoF$H4@@H)CjjK;d~ijGr8!04az5G=lEzh!m;dMSOO20Zv`}Y
zr-iB|ED^!%pcBHh?<gu=GhyRLC1tsuIE(YJXUH?a_pCjE<xhHzrjd&pxx`;jQzh5;
zl9Q4KN4`!eE6v~vYIt=mO!=-hn!UAAu}eYoAW6h3plLh*H$37JSU(h+uGkpx@7+$Q
zFHJlY-*f#a+nGt2y#)horiF~LDlif$em(#7hPWT7k)?Nq{j<MPS$NS8i1>JZxfuAC
zaFBgBIQO4DawgA~vN)BCS%`;S38kn@9kWOTMq)$V$+z&4nDQvH*{(1#N58$C)v2#;
zJW|ch#FaXRBNNj6mX)HNV{_ScADWB7#Jn(Th}B15lvrI|-2<dL5!1=&wWue31zOTq
zw^i}lLoabQhZfQf?iUFP9Z5m3!A3{9j?q)ToPigJcwL-KMw|?59r7;lq=EA1Xyn|3
zKQFEpiW@9}A<zAO?vr_<V%};_IxKbySSVeCdLh1TCD(W}kZUFmMeb{5>fj-=SL1AY
zQrI&y#`tyxRIyenc$G7)m}|d;5&h;8q8?ap1~7v{vEXIAhojO|^XI$6=K!f+>;5yx
zJJXiq*Z?mW;Ak{?4<=)9$$a@6Q*<UTmpguGcnDIPC0WEYN#Q;#Yxy$|D3``2G%7BN
z0Yu^RQ7okX8CBPqG!lDN%^_d=COePPay&UYI#6#@B{KaL`8fF_auJMF1vvL@@Ng<C
zI<Vd6`Flf-AW}D7j+&*Un2E<)hp>=1_%}Nx&bGA3oqS%{I)k3y{#DALAzrPw)h(FU
zj}8a8Xte($dBp<ijg|@?5L~1^;NP*a?DW~(Zh!0u1DnIboQI)1jmk@=vdiYoethVK
z2VA2EQv@N8+$L;v?}g`7We;lAQ0N7Cs45%8&+P5um4~~FV_#?}YNMf!&GB+#_IG>T
z_ZLeg50aO#<yc2n3)}HjIAy6<VTQX8SM42|2g1dr((CMP{B(Y6qxk|d#EXAUaxXkM
zwUwD<6NhB^T_hSjX`KSqm$ECgHu=6Ocle)oFKYFN8Tma6BWbCWiB;waOh;6`(c*u4
zqG$he^u#%iy<Uw`Ct;c4{~nZS%#WV4@bfxg(X2g|KN3$5q}$mfwzscUhZSWBB*Pr~
zM-+k3z<RHH>zhmy?M*+dS#c4NyP>CZSyS+OOi>@2;)lr;&A$)(OEO;kV+bz6O57by
zyW>9>Ij2^Du|A83(r~$46%S7?Ancv<t1a_}DOz@l5HE6yFlo?8Jw?4@@8O%XR>(6R
zJK?TL+k$9p$KMJgY}hdrTzyS}0it==hvU?8YM**7M}l@-<ok|B?D9J>W{&s26~NM6
z#U8(RCX-=6Lw%{$D&=aKSfE%aJ<__RASP1DaZcJPva<-yi3NH#t$OuNk6wlp&CD~1
zanJ|7AhF;l{a^)Qhr<C0*mv)OH?=aSzsFD-;L^+K4SEaqsYLqhx9tkX_6ia?J#83$
z`$z06sIM{&fPSt1-%z9uNqIz!!`X7AZNbDv=pR>_9Bo;2ZG8=}0whx#r7zZ6W`Fs5
zJEbvhZVJVsORu$w4Y1HyT1E4?Vka&kS*mSpBuKM>OAT~3W;g7KLGzfQWF~QJ1)H6S
zFCOXwP_auqzKSygLBPB}EH;Q1gXb@Wm*lZWfM<8NWGZM_*$8Ze)0+^IpqCyco5T+P
z>!edzc-RMsx%H6~4%a*u{&6!V2Xf)f8oOKEEtBAhvI#TkSv+Ago-TMSQ(2q}=S0FP
zL(1v}1vp6Ya1@zfO!}Dq3ke|~@mmFXu2dHEQWpO$6X$;c8V@V*w>NACSkmSKF-THX
zXc85Wu2(uhx0b@}vaeA-YhO(oJ!8ZlugSxzOn{tnI7h@dCB`UVE~EEY_ww_|qDlb|
zQh0>qvDy{uar91x0J$!N&ch{3*B*?y730`NAZJT0IXU?T1Oo1Zc+QnB&!+ZYLh%_v
zV;)6DQs1sEzvoxu0r{lou-yG%CgwotYzFK>vqr!e>KRehvaz@y)fTge`_wgV2*|2H
zVl|vbxEx$3ymn~uGqN65%FYqJ<_)*Uqs49;KY2h*(Xa?Tk7AFfl-xf>irJoUyL*;0
z19&1GQV*5Ni~#kTnaq0ymCiLjk_=0q&=&|cG{r57n*6NwV6zJl<AE{?uiy^?^PFEl
zHL69trWdxghat&0+%;d3D%)bwcJp!RtqFvYL{}8g0Q6YuQRDUcg4GskLUHlFezjgb
z%oGcmW{c;iGpDCy?cU95%R+Qk73F1uDmg--Py0a;zrr32XFHuef2iiC4FRw~6D^mv
zgMdY9dT?<uc8v)5UGd`0_-us5eL?}U1d|_P=m;QXl76{#yY>5K*ED&DsZy8iEL_rr
zgsLXr6cN9-S7dCo0TeKI3ByoGNNBIG{4b4m4=LB^FstU0B?!6TBZ1v~zn%e*Xk=B)
z@_rySE6i<YPde}>HcIxSfbe^sRAkjZKFfR!7A5uNa|Q%HSV{);)`X_I$=Rz#g9)RV
zjIuDE+A6IDHt@No<L%X=db;Hw`M7lZ{F)`q!D5Htt7nHrf@-5e-`j-vV+h{^xhA8s
z$s-;kt^*QI)B|UnrciuVNlIMmjGIErd$4j48G;5;lAAA$Ev}+q;LPGdoNB5SegP#K
z{r5Q+H?7HkfTtUE_k9@xH;}4o67QOQ?Hl585(7w&`EOai?w1T9lK$xN+LxkuSRGel
zqy!S_YM9)(Tp?r#S;~dB<|bI?1gcloG<=?UibWT`0kG_<eKrPzOC}*3Hy088)4@Z+
zv_SccFl?&OC^;g&?yV!ni}*NhUvPXkw)lcuC^*Zf0?cUPiE6Ma9gu1!C^&e?-3+~^
zXl50oV>y^%sCnU|?kL3tCMU12QN7688MFeYr;%^{CT)BqX<4rY8gFNo(^2<+x6~@>
z0Y;8%xJK3sk3si!JoTyNPRqf>i>%mkw_b{g-~}-aAljQww_S1L53kdn=uMD<c17D#
z?H*+c=osa2w*s-S4Z~_SUsrKZwWYR;6)|&Y4rFt<B;x%HbWu)tqyir54O=xC@8VBz
zgT4^EGyVX(hb0g9pv%V|#R4Op=lH^tCrs~K9#Hm6!z@M3QY`N@z7d4`-v-z^-t=yw
zgL!IF60pMfQRkwZjPjYEk%wzZ^3xZK3Q^@$Emwcl&{&RZg@DVEDLYS0N>ZM5$#ndk
z&22o*u=b&^trc3UMGkzzrL*~$;t?gd{w8WCC+z$)6{fY`v4CL%;?|JZtR3}&oLz8*
zT?G#HsX)xAYvWho@h=pJpzsjcWp0%LD4s08onG)Nb4)MY=8K^XfVvcKVvP||0{idF
zr>Wx=dX&);ID@-|u5Y#BAa0c8rW_t)Xfo<vlc#AAL?V;xK!(VoUOQfwYKLZS&i<-9
z;;vFoDu&qH04x=`k?XASkyK@GT+_OrFRio<oEjgbKi&<{<pem1Bh|_{rd}3b6M_~k
z#ug2gX33LeGUF6ujY8^-+NBC3N3Fo!SH?DlQ(x%FqF&l0I?|LVAq9|-_ZAzs`MtE0
zz6|*#9u5wvDGZ0bong!tha)Mz6I;9$ZWQF+WC+Z02hfO#<HL~fql9E=*Ih)@0t$FV
z%W2<l>4c@By|jKCCPsr7DjJ6t;eTIrmF;CpM`~(ysWB=S@seY-cC;IYp7eGp3%$l}
z)oc?3j<N+0i5LM!Lg2p)9=a<RaaY_xwy2ck^!-q?ggIYnFfpFqE!+Gptg``+3UZ&B
z|G7`opGD})?f*>DrN<0qs>+yfj#><OZl7$l^xn+cudeRjEGbCoGdi+?_*DlwButhM
z2c4jyp#csao|(;WM}d0ov1}Oihu(2H(;^$M+d1l4WRg4(PiAdTKH$e65RXehqGcYp
ziS@I=L*)Cyiu;g}I&7*~m`bn>o^%eHp8`K^wUK{qUM_Xl#K;;VHK+>&$DqLQV1~<L
zJDOoVC28%&(mauG>BoxLuBrt&0}DAhEKn_^ER<H!yx{%QhA9tM0kEdHkOyS`=c%lV
zq{%S;|CVPO-A_h<n$FyCVaW`RarknRGNlHhU*Qp*V8JL9k4MsRCTKGmV-lT;?XB>`
zz-29QNvC|8F%an87xNYKcn*LCu89T8nVkc&?~&O83)5GbY)slt*#=)i7s;A<N)hyx
zwh^cOb?iKU)IbRP=ka+qf+JT_=N|faOQrq_JH^K1`TFgfF^F{DYfaT@vyY6ISleTm
zGL!<vL!F>_C=2r7N7+fk`X1KngTDCyUEafq@X5m_z1=DeiD@Q38P{+Ou8AdwgrjC5
zajlbj!7Ae^jZ~9GGnmvF%|dV*Siz7~1$lG}zFHP5%BV8TD09lQN!w79WRZ;`=PM(z
z0;YT`0PcRb5SM~SQ_OKjwTc~?W_G_IPe||U$;Um2U%fe+7X>%Nvy!xcXUbbT1miw0
z=$X7_W&m0ay!h~`ae>C68mu@al*ia7R0saqO=sn$tE@ww372nWLhU^>%{WE>Eoln8
zaeH(5Zly+xlW1Z@B{Z2HqS52V*oh`BC}k&quf19RS}N6$l#0qGWzl9DQkZ@85<PA+
zldE}FJS4PxXhbzMsRmrwaRz~V3QLN=WqdEEvulNbgjbr&&1P5w4PCwA3{?jrWGCM~
zX0DmWj<kYm`dP~kIl-=0KWu`Jc;838I{cbhtw0szBJu5{7M^n&!<`QEsTb#X9`$lT
z57Hwj9Ps0kuw}6a#aAGdM8Uyjl-gC)G1hP^b}DQCDLs;KR8(o5(@&tS<A5C%$Hu%&
zajcvfB#m7XUTidGKu@9pZHG2y*-%G$Ih9jXO6!k#h<t!VB52|u)nv~y!>(#UMH4E)
z!&hPrOmR$HRF*}2C{e3A#U3h9d)gN68^|>O9=TO4Ga~u#5kl0}_*QP9IxEl~Ce;Vj
zS3zvyQ+p-TKYiV8z>J$akDBH=i$W7}&)8|aN%_17$7$H|;eKWRKgAtrMwoyE;#kJp
z>iJ{R+d4p$2q2;Y5EBQ7>@E&mk*MzVW>!EDsQ9Pd1Icl|=0d^U2HU!hP6MLe0bwp2
zA=U!|OQM?{{^8dU?o^&w|I~Y5fw~zw)IT&*mzBRUy1Ljo^-=Z`fvN|N_J<BBAdS~k
z!ALu4Q%-z*R{oO&4hJxm{nHfwfAoO3sOznOQIr1~0QZdfH=zHAn6N<6r7+IV)Vf=N
z-qCbD?!=wp{X>gxG~k*Hc%03VftQZkoi*AD{-11-bt2%}_=-R;7ZY`jOzsFyAEWb!
zVJNLPL#@4|8iv-c@m4Lu!^Uc7?VOsDWty>@T6^QN67|~9P?w&boWVpR2)d)gI@s*$
zT0uPct)H#x^_Y(_q2El&g2<(pF8niAzCde(;c)XAp3awn@Z)3{qMO$l1?#O_cXL+a
zB+yS96Q;w{xIBw9%-h2xp$%a(D0`Noi$$31BbukCM_lu$4sG_+rWsH9U`eD0eY3t3
z@`vkyB5OW$_NhyNPE(&_JPvYO1XVd%SiaJPVza|ZguGogD*p`OzJ!Odk4wR7o=G7;
zQFEN*_9WQcO`Vliy5G@VCnZ;Qb~fJ44e1$o^Tw=L_lA;Z-8Dw0CC}X_m5Q_J*xP61
z2tVQGAnU9PA@k;{9QL{c=-~c_joC`W*8qxTI)7}foE-)SU;g6SD;S1P5oGCta0DrC
zGXz?khB$Fn{Ycwuk%t&RTyJ!Mz8mnC0U+AYu}PkaA-t-gE*25%;RVKNKyWz!scpu6
zZDKFBX5S4#lCQK!Ip%UxMsP%cC4T!8d`;mo#M{(B)h;Ilk3UVA`-O^+JuQDuUnt-K
z=jEH2NuzvVs7mGT0rJ;Nz54;;pVk-{O`o<8h5~yAG9cx)%sJ+#d0-B8j!9{+{>1@9
zYiz-m^g@6wE8^*umZD0JhIN!|&Ok-?2XhJ@B|oI&FfS^$rs90JhlZBoJW`e5b9j^-
zWO>uD9oB-o4QKEBn$akVeT1MeUX-s%#m~lP<b&9IWGgbi$*OIzpK_3n^}!I)WVooa
z#6_PWZ8QA$W_OwNhR51iZ{AQ8gp5IC_qMYoKTO<wW7Lxu951+0Q*<Z_wU3?^MX!Qs
zKFIi5(!_vxR?8&ce&YV47mZ8#83_LZ{o>XZR!_h7SU~%Y_rx{QlrO<RbvccdJya54
zEx3&41--x^p6UqQ4-K0v<iu-hD(5Xrz}`FaganW<;qBVLd$lhu5Wn*9nE3U4nha#d
zqjK-c{nPa2P{JRx8UPnyz`s1eOA2Taz`!T`m}fLvc<|5nl(qw*BvH~+UyPRhKzyrh
zxOKlLj5wBR=EU`4w)nCr{hTY!q<lpX5(s|w3QJ3;mfaaus{2>`$o+{oUb!PIS+x5N
z+{O+YLa6?IE1#&A?RMZ&J}!O!vj>Os^y>J_BMi^Cu8;>FP)!5eagStg`4k8`f<9)s
zLv>uniXJHc5tD}2a*xO+UycHT8lGykAS#<PAVxm$cbPxfCa|;$;o~~iE+XVlgHCw7
zC#7nl^r~{M*TsmN95D{cIk!IhJ_X-wC?{Y<`4dUpmRY}yVmTSPk&p-vIF$+M=~`b#
z6Xw_$9|qJhncu-46MlAh5ITV>tq7H&?$Q|yXO#aH{77;M;}%#Rn*u_i#Q#=kFoCjB
zxM)O)sW@_wx=K{lJ|iyESH0iv9Nr111eP3eEA!SenTb%U12{RS*7qj0=;%^Kd#QiJ
ziYTEU=jFY{zWsSqmqmw<7L@5T1o7NxWhht`9gu$(b|QZnjVAE)D;lyC=><hR)|0rK
z4G=-Wk9u*;^2!F@ZPDmuT=Fj`zK22q4P|a@naT2k6OIr&5bt7mM+Zi{1dh<!#Q+MV
zNV$%grklh|8>~hv=8piE3T9#-QVKCSaq-q&xr*zuRbfKtru+;Kkp5Si5+<6{tz}rp
zigZWmiiYYR#xdxCbhhJz=wN$k9zPcR8H;AJErv2><3*Bm51h&CEJlpT9yo<pH&s}f
zXzYb2r<w#K%?wiq#0>5`<Zq9U!x1gwEv^qA_gUtKoaUH|F#B+-I$;m;h@imuPKwTl
zn58Fdpf)L2vT#)zU+}?#P)g7lir`yLqN5l4uSh3fGa;S@>1`w{pnaAJ%0k=ISmg0E
zo$J6^H1-w0!^WV5w|yx36dtal`WN}DGpD-gqYjDTfjIaLtR}xxCDSo6v=}KHRM^9@
z&T;nw5x5ee(K3%Z3QQF%sMId_cIRpr&3g$f><9ZoX7X_c7g4f{y)mf(?;`TLI@jLv
z?N)ryzDJ)LsBZU+VnRH0X1E}KJ!}%#n_-<YL8nvAJG1pi@pQ)DNORI)b_#$>hEY9w
z`8(=7Fd9^wGY;{_ggJK@ZR?yW!1!^^d;F^x%}=DG(7K8XMm$L~K*Np|t>vZmA5%Y|
zINrWxnZFq_J7&ksTGEluekfNRCX$8u^xk+?w8Q1iII^7LA8Wc=uh=>E34C14fN(+~
zjb&LKSzG|ur8^cG=n*d|U)DK;5`-D7c>o{;1qb8{cYdL5^ll*Y29ag^ZWs(}{Dq?&
z7Vt6fu%BVSoqvD;RYW!I!KS^e-kCz_2@FvAByt<`2mpv<fkZr|^`JQiBI_`lhGk(!
z_N*Sb+Ln<Xn})Qgi4eM-7qPD_UOjLAm9k+61#g=VRLjj+Bq9RB{*-?aNH;_9hvInV
z1PSqXdP+6AuCc1VrYiH#W?-)Rn#1F?YJ)<4bv{8UexqS@(f!Bn_=kD5>xlE{aWp)%
z7->KZs4&!M+Z9|_;(Qr<M|^-9J`VLlA0T%cdqRyI>bPRGNC2zLU&;bq*v@zaDlNR7
zR!OB(0w7?XvMI3w1tc_A&fY$=RO&K>9q)K{?KeL9#X2nl`k!ouFF)XFC@Tui*%L4~
zwNvTu3}=K5TH;uDS!^k3d+!l_hx$f?(hkYU(6NBYx@mz*Y6dZ7D@JF^5^p{aiT5zv
z;Xjc--#|sw407DGZz<4^FBXBq5F)zwTQ|65$~FTfyft2wOiY&QG(ydKoz#wa?YKny
z)9C@EX0c#XN}}K5dNFdMNo^+Os>0sS^c;E5Ky4zm)q;>J{J+z3sdUj)7tN@@gZSf7
zJ|wiD$oI`e{Xe-gDV9P_(x}i7AaPVJn&m~NMi(84-RGbXy6@{lY?h66ze7!6Ee=i!
zInre-6PCHrI9+8v4+)Zge*esLVEy0*)t)o|)801Zf98hgQ=EZH2bpZ=)5NN_2yjw#
zP8Ewr(5WN{8DJpt*e!|G(gvZ5Pxywag$Agdns%%4+I<chK&;6@52mh48>H>|FMw9b
zKb<-v)*Cb*Ao~hb;B*`Ee&trZYBi`{$ru%gmKbuXcPNb3lD3H3Jimki7;BEFp{bxX
zFJ7Rk<~$d5(AGs1%w=$DDrj&3=?C4wX`U{m8^^=Z8R3YTB_A>ZA<nn`Er>OkmldWl
zwo0ZyTNCB`dfUZA+chm*()HWtA2!JQ3>g${<ZEUqmU&IH*$?ZzOs2IhHpYEix|NPQ
zJ-Pi715VXGJVDjN><!w*tid~hXaqgXjMCinP(wu6AN~G*C5MrlRjoO?J1hQ>8%Vr%
zasf==&095e)fG}M%iIsk{PaQ>2|D59ppz^2pExvb9Ou9EI^`kN!0aXr*u3p0ex0b4
z=AnHH#@v>`#o*LjN-yB0^^l)H2Nm=yD3|>1aNigv$f`s680kxF8B%d>SUG)YF0R~W
z$TI5rvll2~&q4RSwu3})*@1!~z4l}@NsY#MwV(2<h@*@k1>Y=hbLZh-ce*Eq3<#rZ
zxra}au9h@`-JaCDeW|)St?N40z`g~4rjZ?xu=?#W;cJyHNPXCV2DuxD%N1A2hAlFH
zwTJm(6XPn#dA&{dq>&yd{5Lp=pa<%$*em=~TdQ%rn_v#5`><qe0k3yPzhk;_7^Ch6
z``4jh8^vb#=_?9Hh_)q6T)5{?KdaF@G)h>I!IS>M^uNpl#N|wC@HMBcRTMT#SL;d7
z<(&BuA6dLkkx|8fWw@PXzCeCBgDx@HJs@)L+j8y~gZ<df6K`wk{>)7)${p-|O7{G?
z&|M6FI|A*^d_U+Of-3`+w(c~-YsQby|NH)g|G7xv|Nek^|Jex)g~z+)I0xPC0460S
LFIp>X81%mY^Bg|U
diff --git a/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java
index b2d510459bcf90a3611f3d91dae4ccc3d29b4079..7a052f6deaa30f8a177a2aaf172f9da6c308a22b 100644
--- a/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java
+++ b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java
@@ -37,7 +37,7 @@ public class VanillaMobGoalTest {
}
List<Class<?>> classes;
- try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft").scan()) {
+ try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft", "org.purpurmc.purpur.entity.ai").scan()) { // Purpur
classes = scanResult.getSubclasses(net.minecraft.world.entity.ai.goal.Goal.class.getName()).loadClasses();
}
diff --git a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java
index 4f43882d930ab8816e75b216d9a61a06b79df265..40fc8b6579cc29e68720a99ac12f8adacc1d95be 100644
--- a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java
+++ b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java
@@ -45,6 +45,7 @@ public class MinecraftCommandPermissionsTest extends AbstractTestingBase {
Set<String> foundPerms = new HashSet<>();
for (CommandNode<CommandSourceStack> child : root.getChildren()) {
final String vanillaPerm = VanillaCommandWrapper.getPermission(child);
+ if (TO_SKIP.contains(vanillaPerm)) continue; // Purpur
if (!perms.contains(vanillaPerm)) {
missing.add("Missing permission for " + child.getName() + " (" + vanillaPerm + ") command");
} else {
@@ -57,6 +58,25 @@ public class MinecraftCommandPermissionsTest extends AbstractTestingBase {
}
private static final List<String> TO_SKIP = List.of(
+ // Purpur start
+ "minecraft.command.compass",
+ "minecraft.command.credits",
+ "minecraft.command.demo",
+ "minecraft.command.ping",
+ "minecraft.command.ram",
+ "minecraft.command.rambar",
+ "minecraft.command.tpsbar",
+ "minecraft.command.uptime",
+ "minecraft.command.debug",
+ "minecraft.command.gamemode.adventure",
+ "minecraft.command.gamemode.adventure.other",
+ "minecraft.command.gamemode.creative",
+ "minecraft.command.gamemode.creative.other",
+ "minecraft.command.gamemode.spectator",
+ "minecraft.command.gamemode.spectator.other",
+ "minecraft.command.gamemode.survival",
+ "minecraft.command.gamemode.survival.other",
+ // Purpur end
"minecraft.command.selector"
);
diff --git a/src/test/java/org/bukkit/potion/PotionTest.java b/src/test/java/org/bukkit/potion/PotionTest.java
index 83226ec2fa977819e12a499eb3765232543c17b3..a742774dabaee0629f4e6adabee5f3ec4b3be41c 100644
--- a/src/test/java/org/bukkit/potion/PotionTest.java
+++ b/src/test/java/org/bukkit/potion/PotionTest.java
@@ -9,6 +9,7 @@ import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.item.alchemy.Potion;
+import org.bukkit.NamespacedKey;
import org.bukkit.support.AbstractTestingBase;
import org.junit.Test;
@@ -47,4 +48,27 @@ public class PotionTest extends AbstractTestingBase {
assertEquals("Same type not returned by name " + key, bukkit, byName);
}
}
+
+ // Purpur start
+ @Test
+ public void testNamespacedKey() {
+ NamespacedKey key = new NamespacedKey("testnamespace", "testkey");
+ PotionEffect namedSpacedEffect = new PotionEffect(PotionEffectType.DOLPHINS_GRACE, 20, 0, true, true, true, key);
+ assertNotNull(namedSpacedEffect.getKey());
+ assertTrue(namedSpacedEffect.hasKey());
+ assertFalse(namedSpacedEffect.withKey(null).hasKey());
+
+ PotionEffect effect = new PotionEffect(PotionEffectType.DOLPHINS_GRACE, 20, 0, true, true, true);
+ assertNull(effect.getKey());
+ assertFalse(effect.hasKey());
+ assertTrue(namedSpacedEffect.withKey(key).hasKey());
+
+ Map<String, Object> s1 = namedSpacedEffect.serialize();
+ Map<String, Object> s2 = effect.serialize();
+ assertTrue(s1.containsKey("namespacedKey"));
+ assertFalse(s2.containsKey("namespacedKey"));
+ assertNotNull(new PotionEffect(s1).getKey());
+ assertNull(new PotionEffect(s2).getKey());
+ }
+ // Purpur end
}