From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Github Actions Date: Mon, 25 Sep 2023 03:15:20 +0000 Subject: [PATCH] Purpur Server Changes Original license: MIT Original project: https://github.com/PurpurMC/Purpur Commit: 642956fe6151a87488d861ccb98e4d46d355d599 Patches below are removed in this patch: Metrics change in Purpur-config-files.patch Brand change in Rebrand.patch some code in Fix-pufferfish-issues.patch Fix-decompile-errors.patch Alternative-Keepalive-Handling.patch Logger-settings-suppressing-pointless-logs.patch Fix-outdated-server-showing-in-ping-before-server-fu.patch Fix-cow-rotation-when-shearing-mooshroom.patch End-gateway-should-check-if-entity-can-use-portal.patch Skip-events-if-there-s-no-listeners.patch Add-5-second-tps-average-in-tps.patch Arrows-should-not-reset-despawn-counter.patch Add-toggle-for-sand-duping-fix.patch Halloween-options-and-optimizations.patch MC-238526-Fix-spawner-not-spawning-water-animals-cor.patch Remove-Timings.patch Remove-Mojang-Profiler.patch MC-121706-Fix-mobs-not-looking-up-and-down-when-stra.patch Fire-Immunity-API.patch diff --git a/build.gradle.kts b/build.gradle.kts index 6fbaa00e7bf665bab2c54e7b4c3e68a5cf14533d..54372661e263807e717e5cfdc395311486bbeaa5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -66,6 +66,12 @@ dependencies { runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18") runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18") + // Purpur start + implementation("org.mozilla:rhino-runtime:1.7.14") + implementation("org.mozilla:rhino-engine:1.7.14") + implementation("dev.omega24:upnp4j:1.0") + // Purpur end + testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") testImplementation("org.hamcrest:hamcrest:2.2") @@ -182,7 +188,7 @@ fun TaskContainer.registerRunTask( name: String, block: JavaExec.() -> Unit ): TaskProvider = register(name) { - group = "paper" + group = "paperweight" // Purpur mainClass.set("org.bukkit.craftbukkit.Main") standardInput = System.`in` workingDir = rootProject.layout.projectDirectory diff --git a/src/log4jPlugins/java/org/purpurmc/purpur/gui/HighlightErrorConverter.java b/src/log4jPlugins/java/org/purpurmc/purpur/gui/HighlightErrorConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..15a226e3854d731f7724025ea3459c8ace07630c --- /dev/null +++ b/src/log4jPlugins/java/org/purpurmc/purpur/gui/HighlightErrorConverter.java @@ -0,0 +1,85 @@ +package org.purpurmc.purpur.gui.util; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.pattern.ConverterKeys; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; +import org.apache.logging.log4j.core.pattern.PatternConverter; +import org.apache.logging.log4j.core.pattern.PatternFormatter; +import org.apache.logging.log4j.core.pattern.PatternParser; +import org.apache.logging.log4j.util.PerformanceSensitive; + +import java.util.List; + +@Plugin(name = "highlightGUIError", category = PatternConverter.CATEGORY) +@ConverterKeys({"highlightGUIError"}) +@PerformanceSensitive("allocation") +public final class HighlightErrorConverter extends LogEventPatternConverter { + private static final String ERROR = "\u00A74\u00A7l"; // Bold Red + private static final String WARN = "\u00A7e\u00A7l"; // Bold Yellow + + private final List formatters; + + private HighlightErrorConverter(List formatters) { + super("highlightGUIError", null); + this.formatters = formatters; + } + + @Override + public void format(LogEvent event, StringBuilder toAppendTo) { + Level level = event.getLevel(); + if (level.isMoreSpecificThan(Level.ERROR)) { + format(ERROR, event, toAppendTo); + return; + } else if (level.isMoreSpecificThan(Level.WARN)) { + format(WARN, event, toAppendTo); + return; + } + for (PatternFormatter formatter : formatters) { + formatter.format(event, toAppendTo); + } + } + + private void format(String style, LogEvent event, StringBuilder toAppendTo) { + int start = toAppendTo.length(); + toAppendTo.append(style); + int end = toAppendTo.length(); + + for (PatternFormatter formatter : formatters) { + formatter.format(event, toAppendTo); + } + + if (toAppendTo.length() == end) { + toAppendTo.setLength(start); + } + } + + @Override + public boolean handlesThrowable() { + for (final PatternFormatter formatter : formatters) { + if (formatter.handlesThrowable()) { + return true; + } + } + return false; + } + + public static HighlightErrorConverter newInstance(Configuration config, String[] options) { + if (options.length != 1) { + LOGGER.error("Incorrect number of options on highlightGUIError. Expected 1 received " + options.length); + return null; + } + + if (options[0] == null) { + LOGGER.error("No pattern supplied on highlightGUIError"); + return null; + } + + PatternParser parser = PatternLayout.createPatternParser(config); + List formatters = parser.parse(options[0]); + return new HighlightErrorConverter(formatters); + } +} diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java index 59699c59fdfc611177fdb3136f84ab539b17d9c9..4819c043e193603581598c91d44d407a08ecd5fb 100644 --- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java +++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java @@ -137,6 +137,10 @@ public class MobGoalHelper { static { // TODO these kinda should be checked on each release, in case obfuscation changes deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee"); + // Purpur start + deobfuscationMap.put("zombie_1", "zombie_attack_villager"); + deobfuscationMap.put("drowned_1", "drowned_attack_villager"); + // Purpur end ignored.add("goal_selector_1"); ignored.add("goal_selector_2"); diff --git a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java index f0fce4113fb07c64adbec029d177c236cbdcbae8..e94224ed280247ee69dfdff8dc960f2b8729be33 100644 --- a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java +++ b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java @@ -78,10 +78,10 @@ public class PaperPluginsCommand extends BukkitCommand { this.setAliases(Arrays.asList("pl")); } - private static List formatProviders(TreeMap> plugins) { + private static List formatProviders(TreeMap> plugins, @NotNull CommandSender sender) { // Purpur List components = new ArrayList<>(plugins.size()); for (PluginProvider entry : plugins.values()) { - components.add(formatProvider(entry)); + components.add(formatProvider(entry, sender)); // Purpur } boolean isFirst = true; @@ -109,7 +109,7 @@ public class PaperPluginsCommand extends BukkitCommand { return formattedSublists; } - private static Component formatProvider(PluginProvider provider) { + private static Component formatProvider(PluginProvider provider, @NotNull CommandSender sender) { // Purpur TextComponent.Builder builder = Component.text(); if (provider instanceof SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) { builder.append(LEGACY_PLUGIN_STAR); @@ -117,12 +117,64 @@ public class PaperPluginsCommand extends BukkitCommand { String name = provider.getMeta().getName(); Component pluginName = Component.text(name, fromStatus(provider)) - .clickEvent(ClickEvent.runCommand("/version " + name)); + // Purpur start + .clickEvent(ClickEvent.suggestCommand("/version " + name)); + + if (sender instanceof org.bukkit.entity.Player && sender.hasPermission("bukkit.command.version")) { + // Event components + String description = provider.getMeta().getDescription(); + TextComponent.Builder hover = Component.text(); + hover.append(Component.text("Version: ", NamedTextColor.WHITE)).append(Component.text(provider.getMeta().getVersion(), NamedTextColor.GREEN)); + + if (description != null) { + hover.append(Component.newline()) + .append(Component.text("Description: ", NamedTextColor.WHITE)) + .append(Component.text(description, NamedTextColor.GREEN)); + } + + if (provider.getMeta().getWebsite() != null) { + hover.append(Component.newline()) + .append(Component.text("Website: ", NamedTextColor.WHITE)) + .append(Component.text(provider.getMeta().getWebsite(), NamedTextColor.GREEN)); + } + + if (!provider.getMeta().getAuthors().isEmpty()) { + hover.append(Component.newline()); + if (provider.getMeta().getAuthors().size() == 1) { + hover.append(Component.text("Author: ")); + } else { + hover.append(Component.text("Authors: ")); + } + + hover.append(getAuthors(provider.getMeta())); + } + + pluginName.hoverEvent(hover.build()); + } builder.append(pluginName); + // Purpur end + + return builder.build(); + } + + // Purpur start + @NotNull + private static TextComponent getAuthors(@NotNull final PluginMeta pluginMeta) { + TextComponent.Builder builder = Component.text(); + List authors = pluginMeta.getAuthors(); + + for (int i = 0; i < authors.size(); i++) { + if (i > 0) { + builder.append(Component.text(i < authors.size() - 1 ? ", " : " and ", NamedTextColor.WHITE)); + } + + builder.append(Component.text(authors.get(i), NamedTextColor.GREEN)); + } return builder.build(); } + // Purpur end private static Component asPlainComponents(String strings) { net.kyori.adventure.text.TextComponent.Builder builder = Component.text(); @@ -182,24 +234,24 @@ public class PaperPluginsCommand extends BukkitCommand { } } - Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE); + //Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE); //.append(INFO_ICON_START.hoverEvent(SERVER_PLUGIN_INFO)); TODO: Add docs - sender.sendMessage(infoMessage); + //sender.sendMessage(infoMessage); // Purpur if (!paperPlugins.isEmpty()) { - sender.sendMessage(PAPER_HEADER); + sender.sendMessage(PAPER_HEADER.append(Component.text(" (%s):".formatted(paperPlugins.size())))); // Purpur } - for (Component component : formatProviders(paperPlugins)) { + for (Component component : formatProviders(paperPlugins, sender)) { // Purpur sender.sendMessage(component); } if (!spigotPlugins.isEmpty()) { - sender.sendMessage(BUKKIT_HEADER); + sender.sendMessage(BUKKIT_HEADER.append(Component.text(" (%s):".formatted(spigotPlugins.size())))); // Purpur } - for (Component component : formatProviders(spigotPlugins)) { + for (Component component : formatProviders(spigotPlugins, sender)) { // Purpur sender.sendMessage(component); } diff --git a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java index a8e813ca89b033f061e695288b3383bdcf128531..1ab65af9359d19530bba7f985a604d2a430ee234 100644 --- a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java +++ b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java @@ -54,9 +54,9 @@ public final class SysoutCatcher { final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(clazz); // Instead of just printing the message, send it to the plugin's logger - plugin.getLogger().log(this.level, this.prefix + line); + plugin.getLogger().log(this.level, /*this.prefix +*/ line); // Purpur - prefix not needed - if (SysoutCatcher.SUPPRESS_NAGS) { + if (true || SysoutCatcher.SUPPRESS_NAGS) { // Purpur - nagging is annoying return; } if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) { diff --git a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java index 708e5bb9bbf0476fcc2c4b92c6830b094703b43e..6141f716b15ad47ac2ac4c9ce92a3897b3ad8807 100644 --- a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java +++ b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java @@ -104,6 +104,7 @@ public class PluginInitializerManager { @SuppressWarnings("unchecked") java.util.List files = ((java.util.List) optionSet.valuesOf("add-plugin")).stream().map(File::toPath).toList(); 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..cb78dac8e072b5cb3c6e52e17c9ecdf708aeedc1 --- /dev/null +++ b/src/main/java/io/papermc/paper/plugin/provider/source/SparkProviderSource.java @@ -0,0 +1,115 @@ +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 implements ProviderSource { + + public static final SparkProviderSource INSTANCE = new SparkProviderSource(); + private static final FileProviderSource FILE_PROVIDER_SOURCE = new FileProviderSource("File '%s' specified by Purpur"::formatted); + private static final Logger LOGGER = LogUtils.getClassLogger(); + + @Override + public Path prepareContext(Path context) { + // first, check if user doesn't want spark at all + if (Boolean.getBoolean("Purpur.IReallyDontWantSpark")) { + return null; // boo! + } + + // second, check if user has their own spark + if (hasSpark()) { + LOGGER.info("Purpur: Using user-provided spark plugin instead of our own."); + return null; // 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 + return FILE_PROVIDER_SOURCE.prepareContext(context); + + } catch (Throwable e) { + LOGGER.error("Purpur: Failed to download and install spark plugin", e); + } + return null; + } + + @Override + public void registerProviders(final EntrypointHandler entrypointHandler, final Path context) { + if (context == null) { + return; + } + + try { + FILE_PROVIDER_SOURCE.registerProviders(entrypointHandler, context); + } catch (IllegalArgumentException ignored) { + // Ignore illegal argument exceptions from jar checking + } catch (Exception e) { + LOGGER.error("Error loading our spark plugin: " + e.getMessage(), e); + } + } + + private static boolean hasSpark() { + for (PluginProvider provider : LaunchEntryPointHandler.INSTANCE.get(Entrypoint.PLUGIN).getRegisteredProviders()) { + if (provider.getMeta().getName().equalsIgnoreCase("spark")) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java index f341813e9713e39bfe142ca34b751de3d8efd25b..546ff84046856ecfe0f2a07d3ba3f886f8df4dca 100644 --- a/src/main/java/net/minecraft/commands/CommandSourceStack.java +++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java @@ -230,6 +230,19 @@ public class CommandSourceStack implements ExecutionCommandSource").replacement(bukkitPermission).build()))); + } + return false; + } + // Purpur end + public Vec3 getPosition() { return this.worldPosition; } @@ -335,6 +348,30 @@ public class CommandSourceStack implements ExecutionCommandSource io.papermc.paper.adventure.PaperAdventure.asVanilla(message), broadcastToOps); + } + // Purpur end + public void sendSuccess(Supplier feedbackSupplier, boolean broadcastToOps) { boolean flag1 = this.source.acceptsSuccess() && !this.silent; boolean flag2 = broadcastToOps && this.source.shouldInformAdmins() && !this.silent; diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java index 3842fcd51f2a56aaa6d2fd190e837f5862e6ab87..edbdcdac30a9c80849045c5225bb1d6bdd091e2d 100644 --- a/src/main/java/net/minecraft/commands/Commands.java +++ b/src/main/java/net/minecraft/commands/Commands.java @@ -219,8 +219,8 @@ public class Commands { JfrCommand.register(this.dispatcher); } - if (SharedConstants.IS_RUNNING_IN_IDE) { - TestCommand.register(this.dispatcher); + if (org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands || SharedConstants.IS_RUNNING_IN_IDE) { // Purpur + if (!org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands) TestCommand.register(this.dispatcher); // Purpur ResetChunksCommand.register(this.dispatcher); RaidCommand.register(this.dispatcher); DebugPathCommand.register(this.dispatcher); @@ -248,6 +248,14 @@ public class Commands { SetPlayerIdleTimeoutCommand.register(this.dispatcher); StopCommand.register(this.dispatcher); WhitelistCommand.register(this.dispatcher); + org.purpurmc.purpur.command.CreditsCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.DemoCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.PingCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.UptimeCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.TPSBarCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.CompassCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.RamBarCommand.register(this.dispatcher); // Purpur + org.purpurmc.purpur.command.RamCommand.register(this.dispatcher); // Purpur } if (environment.includeIntegrated) { diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java index 676a1499747b071515479130875157263d3a8352..fc1bba350030c076405711716e9830f8ae7f3953 100644 --- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java +++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java @@ -200,10 +200,10 @@ public class EntitySelector { if (this.playerName != null) { entityplayer = source.getServer().getPlayerList().getPlayerByName(this.playerName); - return (List) (entityplayer == null ? Collections.emptyList() : Lists.newArrayList(new ServerPlayer[]{entityplayer})); + return entityplayer == null || !canSee(source, entityplayer) ? Collections.emptyList() : Lists.newArrayList(entityplayer); // Purpur } else if (this.entityUUID != null) { entityplayer = source.getServer().getPlayerList().getPlayer(this.entityUUID); - return (List) (entityplayer == null ? Collections.emptyList() : Lists.newArrayList(new ServerPlayer[]{entityplayer})); + return entityplayer == null || !canSee(source, entityplayer) ? Collections.emptyList() : Lists.newArrayList(entityplayer); // Purpur } else { Vec3 vec3d = (Vec3) this.position.apply(source.getPosition()); Predicate predicate = this.getPredicate(vec3d); @@ -215,7 +215,7 @@ public class EntitySelector { ServerPlayer entityplayer1 = (ServerPlayer) entity; if (predicate.test(entityplayer1)) { - return Lists.newArrayList(new ServerPlayer[]{entityplayer1}); + return !canSee(source, entityplayer1) ? Collections.emptyList() : Lists.newArrayList(entityplayer1); // Purpur } } @@ -226,6 +226,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(); @@ -233,7 +234,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; @@ -278,4 +279,10 @@ public class EntitySelector { public static Component joinNames(List entities) { return ComponentUtils.formatList(entities, Entity::getDisplayName); } + + // Purpur start + private boolean canSee(CommandSourceStack sender, ServerPlayer target) { + return !org.purpurmc.purpur.PurpurConfig.hideHiddenPlayersFromEntitySelector || !(sender.getEntity() instanceof ServerPlayer player) || player.getBukkitEntity().canSee(target.getBukkitEntity()); + } + // Purpur end } diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java index e17fa30966dea2836fb791becd032fc7d5cc2611..dd06323731533fb9b1b25a56844a4e39e8a1c4ea 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 - Optimize Bit Operations by inlining + // Purpur start + public BlockPos(net.minecraft.world.entity.Entity entity) { + super(entity.getBlockX(), entity.getBlockY(), entity.getBlockZ()); + } + // Purpur end + public BlockPos(int x, int y, int z) { super(x, y, z); } diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java index 2a9fc1f1dfc0c5894c1e74dad5a79ae9b02ac74f..f27fe4370cb027df4fce239eccde28bd8dcd301c 100644 --- a/src/main/java/net/minecraft/core/Direction.java +++ b/src/main/java/net/minecraft/core/Direction.java @@ -253,6 +253,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); } @@ -365,6 +371,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 e2e1273d787536d2fe1bdbbf8af36eb5ac220599..a3fe92e1db6755a9111ab58e84d61f429d72010f 100644 --- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java @@ -1203,6 +1203,23 @@ public interface DispenseItemBehavior { } } }); + // Purpur start + DispenserBlock.registerBehavior(Items.ANVIL, (new OptionalDispenseItemBehavior() { + @Override + public ItemStack execute(BlockSource dispenser, ItemStack stack) { + Level level = dispenser.level(); + if (!level.purpurConfig.dispenserPlaceAnvils) return super.execute(dispenser, stack); + Direction facing = dispenser.blockEntity().getBlockState().getValue(DispenserBlock.FACING); + BlockPos pos = dispenser.pos().relative(facing); + BlockState state = level.getBlockState(pos); + if (state.isAir()) { + level.setBlockAndUpdate(pos, Blocks.ANVIL.defaultBlockState().setValue(net.minecraft.world.level.block.AnvilBlock.FACING, facing.getAxis() == Direction.Axis.Y ? Direction.NORTH : facing.getClockWise())); + stack.shrink(1); + } + return stack; + } + })); + // Purpur end } static Vec3 getEntityPokingOutOfBlockPos(BlockSource pointer, EntityType entityType, 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 8d65cdb013706a932c2c73231108b2810b99e1c7..5b1938fc849db743e65cd7eed0f83ba059b9525e 100644 --- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java +++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java @@ -104,7 +104,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior { if (ishearable.readyForShearing()) { // CraftBukkit start // Paper start - Add drops to shear events - org.bukkit.event.block.BlockShearEntityEvent event = CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem, ishearable.generateDefaultDrops()); + org.bukkit.event.block.BlockShearEntityEvent event = CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem, ishearable.generateDefaultDrops(net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MOB_LOOTING, CraftItemStack.asNMSCopy(craftItem)))); // Purpur if (event.isCancelled()) { // Paper end - Add drops to shear events continue; diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java index 9e37578ac79a63bb72cc04aae7ba4f6f927a2ce4..121651717b13d90751c20947ea46bdeb0d8da587 100644 --- a/src/main/java/net/minecraft/network/Connection.java +++ b/src/main/java/net/minecraft/network/Connection.java @@ -570,11 +570,20 @@ public class Connection extends SimpleChannelInboundHandler> { private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world private static int joinAttemptsThisTick; // Paper - Buffer joins to world private static int currTick; // Paper - Buffer joins to world + private static int tickSecond; // Purpur public void tick() { this.flushQueue(); // Paper start - Buffer joins to world if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) { Connection.currTick = net.minecraft.server.MinecraftServer.currentTick; + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.maxJoinsPerSecond) { + if (++Connection.tickSecond > 20) { + Connection.tickSecond = 0; + Connection.joinAttemptsThisTick = 0; + } + } else + // Purpur end Connection.joinAttemptsThisTick = 0; } // Paper end - Buffer joins to world diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java index b863249ff7e13cf4939c8961601f0564c62fd661..bdcfd80f937c34956911373905d66424bbff8e1d 100644 --- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java +++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java @@ -95,6 +95,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; } @@ -640,6 +642,17 @@ public class FriendlyByteBuf extends ByteBuf { this.writeBoolean(false); } else { this.writeBoolean(true); + // Purpur start + if (hasItemSerializeEvent) { + var event = new org.purpurmc.purpur.event.packet.NetworkItemSerializeEvent(stack.asBukkitCopy()); + event.callEvent(); + ItemStack newStack = ItemStack.fromBukkitCopy(event.getItemStack()); + if (org.purpurmc.purpur.PurpurConfig.fixNetworkSerializedItemsInCreative && !ItemStack.matches(stack, newStack)) { + stack.save(newStack.getOrCreateTagElement("Purpur.OriginalItem")); + } + stack = newStack; + } + // Purpur end Item item = stack.getItem(); this.writeId(BuiltInRegistries.ITEM, item); diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java index 9ec6145fe04ec64bbee8ec6a837719caebdbc6f5..358d610ad020cada1bb83e393deeeaaec05a2791 100644 --- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java +++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetTimePacket.java @@ -5,7 +5,7 @@ import net.minecraft.network.protocol.Packet; public class ClientboundSetTimePacket implements Packet { private final long gameTime; - private final long dayTime; + private long dayTime; public void setDayTime(long dayTime) { this.dayTime = dayTime; } // Purpur public ClientboundSetTimePacket(long time, long timeOfDay, boolean doDaylightCycle) { this.gameTime = time; diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java index 96cb26fae947006ae7bceb3187ed2c28c4214f16..8e391265a40e3f7a97a26e1bdf5da9fde5d199fc 100644 --- a/src/main/java/net/minecraft/server/Main.java +++ b/src/main/java/net/minecraft/server/Main.java @@ -141,6 +141,10 @@ public class Main { // Paper start - load config files early for access below if needed org.bukkit.configuration.file.YamlConfiguration bukkitConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("bukkit-settings")); org.bukkit.configuration.file.YamlConfiguration spigotConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("spigot-settings")); + // Purpur start + org.bukkit.configuration.file.YamlConfiguration purpurConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) optionset.valueOf("purpur-settings")); + org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands = purpurConfiguration.getBoolean("settings.register-minecraft-debug-commands"); + // Purpur end // Paper end - load config files early for access below if needed if (optionset.has("initSettings")) { // CraftBukkit diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index a001fd51a7eb4ff97a84965679170f2da6619a9b..330f0f66ab16c88b2a827f09f99c42671a53a410 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -276,6 +276,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); public int autosavePeriod; public Commands vanillaCommandDispatcher; @@ -286,12 +287,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Purpur Iterator iterator = this.getAllLevels().iterator(); // Paper - Throw exception on world create while being ticked; move down while (iterator.hasNext()) { ServerLevel worldserver = (ServerLevel) iterator.next(); worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent + worldserver.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers worldserver.updateLagCompensationTick(); // Paper - lag compensation @@ -2710,6 +2736,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { final io.papermc.paper.adventure.ChatDecorationProcessor processor = new io.papermc.paper.adventure.ChatDecorationProcessor(this, sender, commandSourceStack, message); diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java index b954c85f4aaf58f36fbeee67691a18b03c398767..b7c6717efcefe4efae3fd69ebaa3697bfd3dc9c8 100644 --- a/src/main/java/net/minecraft/server/PlayerAdvancements.java +++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java @@ -250,6 +250,7 @@ public class PlayerAdvancements { advancement.value().display().ifPresent((advancementdisplay) -> { // Paper start - Add Adventure message to PlayerAdvancementDoneEvent if (event.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(event.message()), false); // Paper end } diff --git a/src/main/java/net/minecraft/server/commands/EnchantCommand.java b/src/main/java/net/minecraft/server/commands/EnchantCommand.java index 37d9c354af887c474094b1a364782007a5f2035d..ec86231077f6a1e03068507555539c5b550ddf31 100644 --- a/src/main/java/net/minecraft/server/commands/EnchantCommand.java +++ b/src/main/java/net/minecraft/server/commands/EnchantCommand.java @@ -48,7 +48,7 @@ public class EnchantCommand { private static int enchant(CommandSourceStack source, Collection targets, Holder enchantment, int level) throws CommandSyntaxException { Enchantment enchantment2 = enchantment.value(); - if (level > enchantment2.getMaxLevel()) { + if (!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && level > enchantment2.getMaxLevel()) { // Purpur throw ERROR_LEVEL_TOO_HIGH.create(level, enchantment2.getMaxLevel()); } else { int i = 0; @@ -58,7 +58,7 @@ public class EnchantCommand { LivingEntity livingEntity = (LivingEntity)entity; ItemStack itemStack = livingEntity.getMainHandItem(); if (!itemStack.isEmpty()) { - if (enchantment2.canEnchant(itemStack) && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantments(itemStack).keySet(), enchantment2)) { + if ((enchantment2.canEnchant(itemStack) && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantments(itemStack).keySet(), enchantment2)) || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && !itemStack.hasEnchantment(enchantment2))) { // Purpur itemStack.enchant(enchantment2, level); ++i; } else if (targets.size() == 1) { diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java index f7c9127346261d83413ca03a1cdaa84975ae17d6..0cb50229df7df79ad3185a5695707392301653d9 100644 --- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java +++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java @@ -45,6 +45,18 @@ public class GameModeCommand { } private static int setMode(CommandContext context, Collection targets, GameType gameMode) { + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.commandGamemodeRequiresPermission) { + String gamemode = gameMode.getName(); + CommandSourceStack sender = context.getSource(); + if (!sender.testPermission(2, "minecraft.command.gamemode." + gamemode)) { + return 0; + } + if (sender.getEntity() instanceof ServerPlayer player && (targets.size() > 1 || !targets.contains(player)) && !sender.testPermission(2, "minecraft.command.gamemode." + gamemode + ".other")) { + return 0; + } + } + // Purpur end int i = 0; for(ServerPlayer serverPlayer : targets) { diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java index 1b459a8ee8a6bc039e742d65796bc76660a1c765..599172b994d75484f7c7e0ce6d3d3d771c1c44d0 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); // CraftBukkit - 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 b676d1605236b75cf6095a28c2a7a76f3466eea8..8749010decf3a7d9490762c7641530bd91f4e19c 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -98,6 +98,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface return; } // Paper start - Use TerminalConsoleAppender + if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - has no GUI or has console (did not double-click) new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start(); /* jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader; @@ -224,6 +225,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command GaleCommands.registerCommands(this); // Gale - Gale commands - register commands com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics + // 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(); // Paper - load version history now io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // Paper - init PaperBrigadierProvider @@ -280,6 +290,30 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface if (true) throw new IllegalStateException("Failed to bind to port", ioexception); // Paper - Propagate failed to bind to port error return false; } + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.useUPnP) { + LOGGER.info("[UPnP] Attempting to start UPnP port forwarding service..."); + if (dev.omega24.upnp4j.UPnP4J.isUPnPAvailable()) { + if (dev.omega24.upnp4j.UPnP4J.isOpen(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) { + this.upnp = false; + LOGGER.info("[UPnP] Port {} is already open", this.getPort()); + } else if (dev.omega24.upnp4j.UPnP4J.open(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) { + this.upnp = true; + LOGGER.info("[UPnP] Successfully opened port {}", this.getPort()); + } else { + this.upnp = false; + LOGGER.info("[UPnP] Failed to open port {}", this.getPort()); + } + + if (upnp) { + LOGGER.info("[UPnP] {}:{}", dev.omega24.upnp4j.UPnP4J.getExternalIP(), this.getPort()); + } + } else { + this.upnp = false; + LOGGER.error("[UPnP] Service is unavailable"); + } + } + // Purpur end // CraftBukkit start // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up @@ -352,6 +386,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface } if (org.dreeam.leaf.config.modules.async.AsyncMobSpawning.enabled) mobSpawnExecutor.start(); // Pufferfish + org.purpurmc.purpur.task.BossBarTask.startAll(); // Purpur + if (org.purpurmc.purpur.PurpurConfig.beeCountPayload) org.purpurmc.purpur.task.BeehiveTask.instance().register(); // Purpur return true; } } diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java index aa46e6e22998c62c89a56fbcabc13fdf58dd7dba..ee22ca05dccba4914d32125586299c1b7433a532 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java @@ -57,6 +57,7 @@ public class DedicatedServerProperties extends Settings finalizers = Lists.newArrayList(); final AtomicBoolean isClosing = new AtomicBoolean(); + // Purpur start + private final CommandHistory history = new CommandHistory(); + private String currentCommand = ""; + private int historyIndex = 0; + // Purpur end public static MinecraftServerGui showFrameFor(final DedicatedServer server) { try { @@ -51,7 +56,7 @@ public class MinecraftServerGui extends JComponent { ; } - final JFrame jframe = new JFrame("Minecraft server"); + final JFrame jframe = new JFrame("Purpur Minecraft server"); // Purpur final MinecraftServerGui servergui = new MinecraftServerGui(server); jframe.setDefaultCloseOperation(2); @@ -59,7 +64,7 @@ public class MinecraftServerGui extends JComponent { jframe.pack(); jframe.setLocationRelativeTo((Component) null); jframe.setVisible(true); - jframe.setName("Minecraft server"); // Paper - Improve ServerGUI + jframe.setName("Purpur Minecraft server"); // Paper - Improve ServerGUI // Purpur // Paper start - Improve ServerGUI try { @@ -71,7 +76,7 @@ public class MinecraftServerGui extends JComponent { jframe.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent windowevent) { if (!servergui.isClosing.getAndSet(true)) { - jframe.setTitle("Minecraft server - shutting down!"); + jframe.setTitle("Purpur Minecraft server - shutting down!"); // Purpur server.halt(true); servergui.runFinalizers(); } @@ -125,7 +130,7 @@ public class MinecraftServerGui extends JComponent { private JComponent buildChatPanel() { JPanel jpanel = new JPanel(new BorderLayout()); - JTextArea jtextarea = new JTextArea(); + org.purpurmc.purpur.gui.JColorTextPane jtextarea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur JScrollPane jscrollpane = new JScrollPane(jtextarea, 22, 30); jtextarea.setEditable(false); @@ -137,10 +142,43 @@ public class MinecraftServerGui extends JComponent { if (!s.isEmpty()) { this.server.handleConsoleInput(s, this.server.createCommandSourceStack()); + // Purpur start + history.add(s); + historyIndex = -1; + // Purpur end } jtextfield.setText(""); }); + // Purpur start + jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up"); + jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down"); + jtextfield.getActionMap().put("up", new javax.swing.AbstractAction() { + @Override + public void actionPerformed(java.awt.event.ActionEvent actionEvent) { + if (historyIndex < 0) { + currentCommand = jtextfield.getText(); + } + if (historyIndex < history.size() - 1) { + jtextfield.setText(history.get(++historyIndex)); + } + } + }); + jtextfield.getActionMap().put("down", new javax.swing.AbstractAction() { + @Override + public void actionPerformed(java.awt.event.ActionEvent actionEvent) { + if (historyIndex >= 0) { + if (historyIndex == 0) { + --historyIndex; + jtextfield.setText(currentCommand); + } else { + --historyIndex; + jtextfield.setText(history.get(historyIndex)); + } + } + } + }); + // Purpur end jtextarea.addFocusListener(new FocusAdapter() { public void focusGained(FocusEvent focusevent) {} }); @@ -176,7 +214,7 @@ public class MinecraftServerGui extends JComponent { } private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper - public void print(JTextArea textArea, JScrollPane scrollPane, String message) { + public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String message) { // Purpur if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(() -> { this.print(textArea, scrollPane, message); @@ -190,11 +228,14 @@ public class MinecraftServerGui extends JComponent { flag = (double) jscrollbar.getValue() + jscrollbar.getSize().getHeight() + (double) (MinecraftServerGui.MONOSPACED.getSize() * 4) > (double) jscrollbar.getMaximum(); } + /* // Purpur try { document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(message).replaceAll(""), (AttributeSet) null); // CraftBukkit } catch (BadLocationException badlocationexception) { ; } + */ // Purpur + textArea.append(message); // Purpur if (flag) { jscrollbar.setValue(Integer.MAX_VALUE); @@ -202,4 +243,16 @@ public class MinecraftServerGui extends JComponent { } } + + // Purpur start + public static class CommandHistory extends java.util.LinkedList { + @Override + public boolean add(String command) { + if (size() > 1000) { + remove(); + } + return super.offerFirst(command); + } + } + // Purpur end } diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java index 04249a147f2c0a41499d1149a1f9749689074232..515e3f444e317de1d82421ad414c1ac10d4668ab 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -73,7 +73,7 @@ public class ServerEntity { @Nullable private List> trackedDataValues; // CraftBukkit start - private final Set trackedPlayers; + public final Set trackedPlayers; // Purpur - package -> public public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer> consumer, Set trackedPlayers) { this.trackedPlayers = trackedPlayers; diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 1fe45a264032047c67d84ab387e230af2f0eff20..0c376236d0cbc6033865f25db592c42a0d3d30d7 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 - BlockPhysicsEvent public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current) + public boolean hasRidableMoveEvent = false; // Purpur public LevelChunk getChunkIfLoaded(int x, int z) { return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately @@ -708,7 +711,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 @@ -770,6 +790,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 @@ -816,7 +837,7 @@ public class ServerLevel extends Level implements WorldGenLevel { int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); long j; - if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) { + if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) { // CraftBukkit start j = this.levelData.getDayTime() + 24000L; TimeSkipEvent event = new TimeSkipEvent(this.getWorld(), TimeSkipEvent.SkipReason.NIGHT_SKIP, (j - j % 24000L) - this.getDayTime()); @@ -907,7 +928,7 @@ public class ServerLevel extends Level implements WorldGenLevel { final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); MinecraftServer.LOGGER.error(msg, throwable); getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerInternalException(msg, throwable))); - entity.discard(); + entity.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Purpur // Paper end } // Gale end - Airplane - remove lambda from ticking guard - copied from guardEntityTick @@ -938,6 +959,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); } @@ -946,8 +974,22 @@ public class ServerLevel extends Level implements WorldGenLevel { public void setDayTime(long timeOfDay) { this.serverLevelData.setDayTime(timeOfDay); + // Purpur start + this.preciseTime = timeOfDay; + this.forceTime = false; + } + public void setDayTime(double i) { + this.serverLevelData.setDayTime((long) i); + this.forceTime = true; + // Purpur end } + // Purpur start + public boolean isForceTime() { + return this.forceTime; + } + // Purpur end + public void tickCustomSpawners(boolean spawnMonsters, boolean spawnAnimals) { Iterator iterator = this.customSpawners.iterator(); @@ -990,10 +1032,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 - Configurable spawn chances for skeleton horses 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 @@ -1113,7 +1163,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); @@ -1162,11 +1212,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)); } @@ -1306,6 +1372,7 @@ public class ServerLevel extends Level implements WorldGenLevel { @VisibleForTesting public void resetWeatherCycle() { // CraftBukkit start + if (this.purpurConfig.rainStopsAfterSleep) // Purpur this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents // If we stop due to everyone sleeping we should reset the weather duration to some other random value. // Not that everyone ever manages to get the whole server to sleep at the same time.... @@ -1313,6 +1380,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 - Add cause to Weather/ThunderChangeEvents // CraftBukkit start // If we stop due to everyone sleeping we should reset the weather duration to some other random value. @@ -2809,7 +2877,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 - Fix merchant inventory not closing on entity removal - if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { + if (!entity.level().purpurConfig.playerVoidTrading && entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { // Purpur merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); } // Paper end - Fix merchant inventory not closing on entity removal diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java index f80b2d4e9a46d84fdaa0f674c562736a1e5c1635..0cc07f1df65a86cc4e2c0a254ea2cb782a797901 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -282,6 +282,10 @@ public class ServerPlayer extends Player { public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent public @Nullable String clientBrandName = null; // Paper - Brand support public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event + public boolean purpurClient = false; // Purpur + private boolean tpsBar = false; // Purpur + private boolean compassBar = false; // Purpur + private boolean ramBar = false; // Purpur // Paper start - replace player chunk loader private final java.util.concurrent.atomic.AtomicReference viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1)); @@ -569,6 +573,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 @@ -635,6 +642,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 @@ -763,6 +773,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() { @@ -1000,6 +1019,7 @@ public class ServerPlayer extends Player { })); PlayerTeam scoreboardteam = this.getTeam(); + if (org.purpurmc.purpur.PurpurConfig.deathMessageOnlyBroadcastToAffectedPlayer) this.sendSystemMessage(ichatbasecomponent); else // Purpur if (scoreboardteam != null && scoreboardteam.getDeathMessageVisibility() != Team.Visibility.ALWAYS) { if (scoreboardteam.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) { this.server.getPlayerList().broadcastSystemToTeam(this, ichatbasecomponent); @@ -1103,6 +1123,16 @@ public class ServerPlayer extends Player { 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)) { @@ -1246,6 +1276,7 @@ public class ServerPlayer extends Player { playerlist.sendPlayerPermissionLevel(this); worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); this.unsetRemoved(); + this.portalPos = io.papermc.paper.util.MCUtil.toBlockPosition(exit); // Purpur // CraftBukkit end this.setServerLevel(worldserver); @@ -1401,7 +1432,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); } } @@ -1441,7 +1472,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(); @@ -1546,6 +1589,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)); } @@ -1880,6 +1924,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); @@ -2207,8 +2271,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; } @@ -2760,4 +2884,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 tpsBar() { + return this.tpsBar; + } + + public void tpsBar(boolean tpsBar) { + this.tpsBar = tpsBar; + } + + public boolean compassBar() { + return this.compassBar; + } + + public void compassBar(boolean compassBar) { + this.compassBar = compassBar; + } + + public boolean ramBar() { + return this.ramBar; + } + + public void ramBar(boolean ramBar) { + this.ramBar = ramBar; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java index 840dc3c57dd60d5f16155fc0c6f8c9fea49685c9..8e87d799586103b8a743ebc0cfb8da7fca3c856a 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java @@ -397,6 +397,7 @@ public class ServerPlayerGameMode { } else {capturedBlockEntity = true;} // Paper - Send block entities after destroy prediction return false; } + if (this.player.level().purpurConfig.slabHalfBreak && this.player.isShiftKeyDown() && iblockdata.getBlock() instanceof net.minecraft.world.level.block.SlabBlock && ((net.minecraft.world.level.block.SlabBlock) iblockdata.getBlock()).halfBreak(iblockdata, pos, this.player)) return true; // Purpur } // CraftBukkit end @@ -516,6 +517,7 @@ public class ServerPlayerGameMode { public InteractionHand interactHand; public ItemStack interactItemStack; public InteractionResult useItemOn(ServerPlayer player, Level world, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) { + if (shiftClickMended(stack)) return InteractionResult.SUCCESS; // Purpur BlockPos blockposition = hitResult.getBlockPos(); BlockState iblockdata = world.getBlockState(blockposition); InteractionResult enuminteractionresult = InteractionResult.PASS; @@ -577,7 +579,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()) { @@ -618,4 +620,18 @@ public class ServerPlayerGameMode { public void setLevel(ServerLevel world) { this.level = world; } + + // Purpur start + public boolean shiftClickMended(ItemStack itemstack) { + if (this.player.level().purpurConfig.shiftRightClickRepairsMendingPoints > 0 && this.player.isShiftKeyDown() && this.player.getBukkitEntity().hasPermission("purpur.mending_shift_click")) { + int points = Math.min(this.player.totalExperience, this.player.level().purpurConfig.shiftRightClickRepairsMendingPoints); + if (points > 0 && itemstack.isDamaged() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MENDING, itemstack) > 0) { + this.player.giveExperiencePoints(-points); + this.player.level().addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(this.player.level(), this.player.getX(), this.player.getY(), this.player.getZ(), points, org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN, this.player, this.player)); + return true; + } + } + return false; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java index d791c2f2facfc46d8664225c8b28f95f92df3413..61cd58a5bb3d228b29b9cc4db11be03d4c833223 100644 --- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java @@ -64,6 +64,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack private static final long KEEPALIVE_LIMIT = KEEPALIVE_LIMIT_IN_SECONDS * 1000; // Gale end - Purpur - send multiple keep-alive packets protected static final ResourceLocation MINECRAFT_BRAND = new ResourceLocation("brand"); // Paper - Brand support + protected static final ResourceLocation PURPUR_CLIENT = new ResourceLocation("purpur", "client"); // Purpur public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit this.server = minecraftserver; @@ -157,6 +158,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); this.disconnect("Invalid payload REGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause } + // Purpur start + } else if (identifier.equals(PURPUR_CLIENT)) { + try { + player.purpurClient = true; + } catch (Exception ignore) { + } + // Purpur end } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) { try { String channels = payload.toString(com.google.common.base.Charsets.UTF_8); diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java index e71dfe3db4a2cf616d91c987a38da1509f379c88..fc57aad909a843a7ebd8490f125b424872f71ed5 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java @@ -335,6 +335,20 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } // Gale end - make max interaction distance configurable + // Purpur start + private final com.google.common.cache.LoadingCache kickPermissionCache = com.google.common.cache.CacheBuilder.newBuilder() + .maximumSize(1000) + .expireAfterWrite(1, java.util.concurrent.TimeUnit.MINUTES) + .build( + new com.google.common.cache.CacheLoader<>() { + @Override + public Boolean load(CraftPlayer player) { + return player.hasPermission("purpur.bypassIdleKick"); + } + } + ); + // Purpur end + @Override public void tick() { if (this.ackBlockChangesUpTo > -1) { @@ -402,6 +416,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } 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 } @@ -649,10 +669,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl this.lastYaw = to.getYaw(); this.lastPitch = to.getPitch(); + if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur + Location oldTo = to.clone(); PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); this.cserver.getPluginManager().callEvent(event); - // If the event is cancelled we move the player back to their old location. if (event.isCancelled()) { this.teleport(from); @@ -722,6 +743,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl 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; } @@ -1158,10 +1180,15 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl 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; + // Purpur start + int slot = packet.getSlot(); + ItemStack itemstack = Inventory.isHotbarSlot(slot) || slot == Inventory.SLOT_OFFHAND ? this.player.getInventory().getItem(slot) : ItemStack.EMPTY; + // Purpur end 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; } @@ -1185,6 +1212,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl 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; } @@ -1238,13 +1266,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl 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)); @@ -1256,10 +1287,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl private void updateBookPages(List list, UnaryOperator unaryoperator, ItemStack itemstack, int slot, ItemStack handItem) { // CraftBukkit ListTag nbttaglist = new ListTag(); + // Purpur start + boolean hasPerm = getCraftPlayer().hasPermission("purpur.book.color.edit"); if (this.player.isTextFilteringEnabled()) { - Stream stream = list.stream().map((filteredtext) -> { // CraftBukkit - decompile error - return StringTag.valueOf((String) unaryoperator.apply(filteredtext.filteredOrEmpty())); + Stream stream = list.stream().map(s -> color(s.filteredOrEmpty(), hasPerm, false)).map((s) -> { // CraftBukkit - decompile error + return StringTag.valueOf((String) unaryoperator.apply(s)); }); + // Purpur end Objects.requireNonNull(nbttaglist); stream.forEach(nbttaglist::add); @@ -1269,11 +1303,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl 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 } } @@ -1286,6 +1320,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl this.player.getInventory().setItem(slot, CraftEventFactory.handleEditBookEvent(this.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()); @@ -1335,8 +1379,16 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl @Override public void handleMovePlayer(ServerboundMovePlayerPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) { + // Purpur start + boolean invalidX = Double.isNaN(packet.getX(0.0D)); + boolean invalidY = Double.isNaN(packet.getY(0.0D)); + boolean invalidZ = Double.isNaN(packet.getZ(0.0D)); + boolean invalidYaw = !Floats.isFinite(packet.getYRot(0.0F)); + boolean invalidPitch = !Floats.isFinite(packet.getXRot(0.0F)); + if (invalidX || invalidY || invalidZ || invalidYaw || invalidPitch) { 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(); @@ -1520,7 +1572,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl if (!event.isAllowed()) { movedWrongly = true; if (event.getLogWarning()) - ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); + ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!, ({})", this.player.getName().getString(), d11); // Purpur } } @@ -1587,6 +1639,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl this.lastYaw = to.getYaw(); this.lastPitch = to.getPitch(); + if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur + Location oldTo = to.clone(); PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); this.cserver.getPluginManager().callEvent(event); @@ -1622,6 +1676,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl 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(); @@ -1661,6 +1722,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl return false; } // Paper end - optimise out extra getCubes + + // Purpur start + public boolean isScissor(ItemStack stack) { + return stack.is(Items.SHEARS) && (stack.getTag() == null || stack.getTag().getInt("CustomModelData") == 0); + } + // Purpur end + private boolean isPlayerCollidingWithAnythingNew(LevelReader world, AABB box, double newX, double newY, double newZ) { AABB axisalignedbb1 = this.player.getBoundingBox().move(newX - this.player.getX(), newY - this.player.getY(), newZ - this.player.getZ()); Iterable iterable = world.getCollisions(this.player, axisalignedbb1.deflate(9.999999747378752E-6D)); @@ -1671,7 +1739,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl do { if (!iterator.hasNext()) { - return false; + return !org.purpurmc.purpur.PurpurConfig.kickForOutOfOrderChat; // Purpur // Leaf TODO - Need check this } voxelshape1 = (VoxelShape) iterator.next(); @@ -2006,6 +2074,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl boolean cancelled; if (movingobjectposition == null || movingobjectposition.getType() != HitResult.Type.BLOCK) { + if (this.player.gameMode.shiftClickMended(itemstack)) return; // Purpur org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack, enumhand); cancelled = event.useItemInHand() == Event.Result.DENY; } else { @@ -2728,6 +2797,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl AABB axisalignedbb = entity.getBoundingBox(); if (axisalignedbb.distanceToSqr(this.player.getEyePosition()) < ServerGamePacketListenerImpl.getMaxInteractionDistanceSquared(this.player.level())) { // Gale - make max interaction distance configurable + if (entity instanceof Mob mob) mob.ticksSinceLastInteraction = 0; // Purpur packet.dispatch(new ServerboundInteractPacket.Handler() { private void performInteraction(InteractionHand enumhand, ServerGamePacketListenerImpl.EntityInteraction playerconnection_a, PlayerInteractEntityEvent event) { // CraftBukkit ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand); @@ -2741,6 +2811,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event); + player.processClick(enumhand); // Purpur + // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { entity.getEntityData().resendPossiblyDesyncedEntity(player); // Paper - The entire mob gets deleted, so resend it. @@ -3315,6 +3387,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl } } } + // 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(); diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java index e072edfb1f51f3e219e6deb10b19b4b2502ea87f..4bd1d7c4328e13ae3e173836ced22125857bcae1 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -285,7 +285,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!"); ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(s1)); // Spigot } else { - ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username")); + ServerLoginPacketListenerImpl.this.disconnect(org.purpurmc.purpur.PurpurConfig.unverifiedUsername.equals("default") ? Component.translatable("multiplayer.disconnect.unverified_username") : io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.unverifiedUsername))); // Purpur ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", s1); } } catch (AuthenticationUnavailableException authenticationunavailableexception) { diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java index 893e034895d7f49662720c4f76b2c37f70199e76..3c6002e33d1b86b60ccfa11ef615aa8f7adaf81b 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -503,6 +503,7 @@ public abstract class PlayerList { scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam); } // Paper end - Configurable player collision + org.purpurmc.purpur.task.BossBarTask.addToAll(player); // Purpur if (GaleGlobalConfiguration.get().logToConsole.playerLoginLocations) { // Gale - JettPack - make logging login location configurable PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ()); // Gale start - JettPack - make logging login location configurable @@ -621,6 +622,7 @@ public abstract class PlayerList { } public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) { // Paper end - Fix kick event leave message not being sent + org.purpurmc.purpur.task.BossBarTask.removeFromAll(entityplayer.getBukkitEntity()); // Purpur ServerLevel worldserver = entityplayer.serverLevel(); entityplayer.awardStat(Stats.LEAVE_GAME); @@ -776,7 +778,7 @@ public abstract class PlayerList { event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure } else { // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null; - if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile)) { + if (this.players.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameprofile))) { // Purpur event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure } } @@ -1128,6 +1130,20 @@ public abstract class PlayerList { } // CraftBukkit end + // Purpur Start + public void broadcastMiniMessage(@Nullable String message, boolean overlay) { + if (message != null && !message.isEmpty()) { + this.broadcastMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), overlay); + } + } + + public void broadcastMessage(@Nullable net.kyori.adventure.text.Component message, boolean overlay) { + if (message != null) { + this.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), overlay); + } + } + // Purpur end + public void broadcastAll(Packet packet, ResourceKey dimension) { Iterator iterator = this.players.iterator(); @@ -1231,6 +1247,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)); } @@ -1239,6 +1256,27 @@ public abstract class PlayerList { player.getBukkitEntity().recalculatePermissions(); // CraftBukkit this.server.getCommands().sendCommands(player); } // Paper - Add sendOpLevel API + + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.enderChestSixRows && org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) { + org.bukkit.craftbukkit.entity.CraftHumanEntity bukkit = player.getBukkitEntity(); + if (bukkit.hasPermission("purpur.enderchest.rows.six")) { + player.sixRowEnderchestSlotCount = 54; + } else if (bukkit.hasPermission("purpur.enderchest.rows.five")) { + player.sixRowEnderchestSlotCount = 45; + } else if (bukkit.hasPermission("purpur.enderchest.rows.four")) { + player.sixRowEnderchestSlotCount = 36; + } else if (bukkit.hasPermission("purpur.enderchest.rows.three")) { + player.sixRowEnderchestSlotCount = 27; + } else if (bukkit.hasPermission("purpur.enderchest.rows.two")) { + player.sixRowEnderchestSlotCount = 18; + } else if (bukkit.hasPermission("purpur.enderchest.rows.one")) { + player.sixRowEnderchestSlotCount = 9; + } + } else { + player.sixRowEnderchestSlotCount = -1; + } + //Purpur end } public boolean isWhiteListed(GameProfile profile) { diff --git a/src/main/java/net/minecraft/server/players/SleepStatus.java b/src/main/java/net/minecraft/server/players/SleepStatus.java index 823efad652d8ff9e96b99375b102fef6f017716e..caa8a69bde0c212c36dd990a67836ac2f95548c0 100644 --- a/src/main/java/net/minecraft/server/players/SleepStatus.java +++ b/src/main/java/net/minecraft/server/players/SleepStatus.java @@ -19,7 +19,7 @@ public class SleepStatus { public boolean areEnoughDeepSleeping(int percentage, List players) { // CraftBukkit start - int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping; }).count(); + int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping || (eh.level().purpurConfig.idleTimeoutCountAsSleeping && eh.isAfk()); }).count(); // Purpur boolean anyDeepSleep = players.stream().anyMatch(Player::isSleepingLongEnough); return anyDeepSleep && j >= this.sleepersNeeded(percentage); @@ -52,7 +52,7 @@ public class SleepStatus { if (!entityplayer.isSpectator()) { ++this.activePlayers; - if (entityplayer.isSleeping() || entityplayer.fauxSleeping) { // CraftBukkit + if ((entityplayer.isSleeping() || entityplayer.fauxSleeping) || (entityplayer.level().purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk())) { // CraftBukkit // Purpur ++this.sleepingPlayers; } // CraftBukkit start diff --git a/src/main/java/net/minecraft/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 9c99b2e365aacb8309f29acb9025faccd2c676b3..1653d154edb38a6450ee51cb5e5b8e3d37a1784e 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 1561b85a45f52a8162f43553f8485bfe084b8f1f..ba4466c49a7c1713007140efdc2eb805e2516b4c 100644 --- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java +++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java @@ -27,6 +27,8 @@ public class DamageSource { private boolean withSweep = false; private boolean melting = false; private boolean poison = false; + private boolean scissors = false; // Purpur + private boolean stonecutter = false; // Purpur private Entity customCausingEntity = null; // This field is a helper for when causing entity damage is not set by vanilla public DamageSource sweep() { @@ -56,6 +58,26 @@ public class DamageSource { return this.poison; } + // Purpur start + public DamageSource scissors() { + this.scissors = true; + return this; + } + + public boolean isScissors() { + return this.scissors; + } + + public DamageSource stonecutter() { + this.stonecutter = true; + return this; + } + + public boolean isStonecutter() { + return this.stonecutter; + } + // Purpur end + public Entity getCausingEntity() { return (this.customCausingEntity != null) ? this.customCausingEntity : this.causingEntity; } @@ -94,6 +116,8 @@ public class DamageSource { damageSource.withSweep = this.isSweep(); damageSource.poison = this.isPoison(); damageSource.melting = this.isMelting(); + damageSource.scissors = this.isScissors(); // Purpur + damageSource.stonecutter = this.isStonecutter(); // Purpur return damageSource; } // CraftBukkit end @@ -166,10 +190,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 a47473c9875c70c52b9a61e0156e55961f34c694..b88fed81a7df6c81521f74425bd81443b4841137 100644 --- a/src/main/java/net/minecraft/world/damagesource/DamageSources.java +++ b/src/main/java/net/minecraft/world/damagesource/DamageSources.java @@ -44,11 +44,15 @@ public class DamageSources { // CraftBukkit start private final DamageSource melting; private final DamageSource poison; + private final DamageSource scissors; // Purpur + private final DamageSource stonecutter; // Purpur public DamageSources(RegistryAccess registryManager) { this.damageTypes = registryManager.registryOrThrow(Registries.DAMAGE_TYPE); this.melting = this.source(DamageTypes.ON_FIRE).melting(); this.poison = this.source(DamageTypes.MAGIC).poison(); + this.scissors = this.source(DamageTypes.MAGIC).scissors(); // Purpur + this.stonecutter = this.source(DamageTypes.MAGIC).stonecutter(); // Purpur // CraftBukkit end this.inFire = this.source(DamageTypes.IN_FIRE); this.lightningBolt = this.source(DamageTypes.LIGHTNING_BOLT); @@ -277,4 +281,13 @@ public class DamageSources { public DamageSource genericKill() { return this.genericKill; } + // Purpur start + public DamageSource scissors() { + return this.scissors; + } + + public DamageSource stonecutter() { + return this.stonecutter; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/effect/HungerMobEffect.java b/src/main/java/net/minecraft/world/effect/HungerMobEffect.java index 3aad6bd0a1fb7bb3f9b7dab2c10c875864900750..31bd845130e363dd11c225dfd1e9dd896aea8aac 100644 --- a/src/main/java/net/minecraft/world/effect/HungerMobEffect.java +++ b/src/main/java/net/minecraft/world/effect/HungerMobEffect.java @@ -15,7 +15,7 @@ class HungerMobEffect extends MobEffect { if (entity instanceof Player) { Player entityhuman = (Player) entity; - entityhuman.causeFoodExhaustion(0.005F * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent + entityhuman.causeFoodExhaustion(entity.level().purpurConfig.humanHungerExhaustionAmount * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent // Purpur } } diff --git a/src/main/java/net/minecraft/world/effect/MobEffectInstance.java b/src/main/java/net/minecraft/world/effect/MobEffectInstance.java index bf304db1f9506d3e83d16cd632b9155c39346634..d94497083f3da89dbef6479ef8f70591e630e3e7 100644 --- a/src/main/java/net/minecraft/world/effect/MobEffectInstance.java +++ b/src/main/java/net/minecraft/world/effect/MobEffectInstance.java @@ -36,6 +36,7 @@ public class MobEffectInstance implements Comparable { private boolean showIcon; @Nullable public MobEffectInstance hiddenEffect; + private org.bukkit.NamespacedKey key; // Purpur - add key private final Optional factorData; public MobEffectInstance(MobEffect type) { @@ -54,17 +55,36 @@ public class MobEffectInstance implements Comparable { this(type, duration, amplifier, ambient, visible, visible); } + // Purpur start + public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean visible, @Nullable org.bukkit.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(), (org.bukkit.NamespacedKey)null); + } + + public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon, @Nullable org.bukkit.NamespacedKey key) { + this(type, duration, amplifier, ambient, showParticles, showIcon, (MobEffectInstance)null, type.createFactorData(), key); + // Purpur end } public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon, @Nullable MobEffectInstance hiddenEffect, Optional factorCalculationData) { + // Purpur start + this(type, duration, amplifier, ambient, showParticles, showIcon, hiddenEffect, factorCalculationData, (org.bukkit.NamespacedKey) null); + } + + public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon, @Nullable MobEffectInstance hiddenEffect, Optional factorCalculationData, @Nullable org.bukkit.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; } @@ -85,6 +105,7 @@ public class MobEffectInstance implements Comparable { this.ambient = that.ambient; this.visible = that.visible; this.showIcon = that.showIcon; + this.key = that.key; // Purpur - add key } public boolean update(MobEffectInstance that) { @@ -129,6 +150,13 @@ public class MobEffectInstance implements Comparable { bl = true; } + // Purpur start + if (that.key != this.key) { + this.key = that.key; + bl = true; + } + // Purpur end + return bl; } @@ -172,6 +200,17 @@ public class MobEffectInstance implements Comparable { return this.showIcon; } + // Purpur start + public boolean hasKey() { + return this.key != null; + } + + @Nullable + public org.bukkit.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; @@ -232,6 +271,12 @@ public class MobEffectInstance implements Comparable { string = string + ", Show Icon: false"; } + // Purpur start + if (this.hasKey()) { + string = string + ", Key: " + this.key; + } + // Purpur end + return string; } @@ -247,7 +292,7 @@ public class MobEffectInstance implements Comparable { return false; } else { MobEffectInstance mobEffectInstance = (MobEffectInstance)object; - return this.duration == mobEffectInstance.duration && this.amplifier == mobEffectInstance.amplifier && this.ambient == mobEffectInstance.ambient && this.effect.equals(mobEffectInstance.effect); + return this.duration == mobEffectInstance.duration && this.amplifier == mobEffectInstance.amplifier && this.ambient == mobEffectInstance.ambient && this.effect.equals(mobEffectInstance.effect) && this.key == mobEffectInstance.key; // Purpur - add key } } @@ -272,6 +317,11 @@ public class MobEffectInstance implements Comparable { nbt.putBoolean("ambient", this.isAmbient()); nbt.putBoolean("show_particles", this.isVisible()); nbt.putBoolean("show_icon", 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); @@ -306,6 +356,13 @@ public class MobEffectInstance implements Comparable { bl3 = nbt.getBoolean("show_icon"); } + // Purpur start + org.bukkit.NamespacedKey key = null; + if (nbt.contains("key")) { + key = org.bukkit.NamespacedKey.fromString(nbt.getString("key")); + } + // Purpur end + MobEffectInstance mobEffectInstance = null; if (nbt.contains("hidden_effect", 10)) { mobEffectInstance = loadSpecifiedEffect(type, nbt.getCompound("hidden_effect")); @@ -318,7 +375,7 @@ public class MobEffectInstance implements Comparable { optional = Optional.empty(); } - return new MobEffectInstance(type, j, Math.max(i, 0), bl, bl2, bl3, mobEffectInstance, optional); + return new MobEffectInstance(type, j, Math.max(i, 0), bl, bl2, bl3, mobEffectInstance, optional, key); // Purpur - add key } @Override diff --git a/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java b/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java index 196204a8661c7750408997e052ec706f44161fc6..393cd9fac5b2fd39e4248d0abd4930e6b2ff73a4 100644 --- a/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java +++ b/src/main/java/net/minecraft/world/effect/PoisonMobEffect.java @@ -11,8 +11,8 @@ class PoisonMobEffect extends MobEffect { @Override public void applyEffectTick(LivingEntity entity, int amplifier) { super.applyEffectTick(entity, amplifier); - 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 } } diff --git a/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java b/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java index 551b20f86347aeca4824b7a424ad7de7c0ff072e..06bb4ad98aa9ca38b8d423681b1ad4b821f5e47d 100644 --- a/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java +++ b/src/main/java/net/minecraft/world/effect/RegenerationMobEffect.java @@ -12,7 +12,7 @@ class RegenerationMobEffect extends MobEffect { public void applyEffectTick(LivingEntity entity, int amplifier) { super.applyEffectTick(entity, amplifier); if (entity.getHealth() < entity.getMaxHealth()) { - entity.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit + entity.heal(entity.level().purpurConfig.entityHealthRegenAmount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit // Purpur } } diff --git a/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java b/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java index b994ae09621934df2cdd6a83a7d8ecb44649fb16..c2b812c992db1ac9cd391da902c8d819a6ec2e6d 100644 --- a/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java +++ b/src/main/java/net/minecraft/world/effect/SaturationMobEffect.java @@ -23,7 +23,7 @@ class SaturationMobEffect extends InstantenousMobEffect { int oldFoodLevel = entityhuman.getFoodData().foodLevel; org.bukkit.event.entity.FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(entityhuman, amplifier + 1 + oldFoodLevel); if (!event.isCancelled()) { - entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F); + entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, entity.level().purpurConfig.humanSaturationRegenAmount); // Purpur } ((CraftPlayer) entityhuman.getBukkitEntity()).sendHealthUpdate(); diff --git a/src/main/java/net/minecraft/world/effect/WitherMobEffect.java b/src/main/java/net/minecraft/world/effect/WitherMobEffect.java index 1f9e0c139988c4c44a26552881647d36965aa4fa..b8d612d22aca74a08b53393c0723a2ae3a0b9ddf 100644 --- a/src/main/java/net/minecraft/world/effect/WitherMobEffect.java +++ b/src/main/java/net/minecraft/world/effect/WitherMobEffect.java @@ -10,7 +10,7 @@ class WitherMobEffect extends MobEffect { @Override public void applyEffectTick(LivingEntity entity, int amplifier) { super.applyEffectTick(entity, amplifier); - entity.hurt(entity.damageSources().wither(), 1.0F); + entity.hurt(entity.damageSources().wither(), entity.level().purpurConfig.entityWitherDegenerationAmount); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java index f146fb4a599d36e0a9492936312e609cbbfed641..679209274fbbf8e10498ecec852b0994af2a39e7 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -160,7 +160,7 @@ import org.bukkit.plugin.PluginManager; // CraftBukkit end public abstract class Entity implements Nameable, EntityAccess, CommandSource, ScoreHolder { - + public static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur // CraftBukkit start private static final int CURRENT_LEVEL = 2; public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation @@ -338,7 +338,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S public double xOld; public double yOld; public double zOld; - private float maxUpStep; + public float maxUpStep; // Purpur - private -> public public boolean noPhysics; public final RandomSource random; public int tickCount; @@ -380,7 +380,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S private final Set tags; private final double[] pistonDeltas; private long pistonDeltasGameTime; - private EntityDimensions dimensions; + protected EntityDimensions dimensions; // Purpur - private -> protected private float eyeHeight; public boolean isInPowderSnow; public boolean wasInPowderSnow; @@ -562,6 +562,25 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S 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; } @@ -582,7 +601,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S this.bb = Entity.INITIAL_AABB; this.stuckSpeedMultiplier = Vec3.ZERO; this.nextStep = 1.0F; - this.random = SHARED_RANDOM; // Paper - Share random for entities to make them more random + this.random = world == null || world.purpurConfig.entitySharedRandom ? SHARED_RANDOM : RandomSource.create(); // Paper - Share random for entities to make them more random // Purpur this.remainingFireTicks = -this.getFireImmuneTicks(); this.fluidHeight = new Object2DoubleArrayMap(2); this.fluidOnEyes = new HashSet(); @@ -920,10 +939,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S 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 - Configurable nether ceiling damage + if (this.level().purpurConfig.teleportOnNetherCeilingDamage && this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER && this instanceof ServerPlayer player) player.teleport(io.papermc.paper.util.MCUtil.toLocation(this.level, this.level.getSharedSpawnPos())); else // Purpur this.onBelowWorld(); } @@ -1896,7 +1916,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S return this.isInWater() || flag; } - void updateInWaterStateAndDoWaterCurrentPushing() { + public void updateInWaterStateAndDoWaterCurrentPushing() { // Purpur - package-private -> public Entity entity = this.getVehicle(); if (entity instanceof Boat) { @@ -3050,6 +3070,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S 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); } } @@ -3089,6 +3116,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S return false; } // CraftBukkit end + + // Purpur start + if (this.rider != null && this.passengers.get(0) == this.rider) { + onDismount(this.rider); + this.rider = null; + } + // Purpur end + if (this.passengers.size() == 1 && this.passengers.get(0) == entity) { this.passengers = ImmutableList.of(); } else { @@ -3168,12 +3203,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S 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; @@ -3412,7 +3450,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S } 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() { @@ -3877,7 +3915,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S } public boolean canChangeDimensions() { - return !this.isPassenger() && !this.isVehicle() && isAlive() && valid; // Paper - Fix item duplication and teleport issues + return !this.isPassenger() && !this.isVehicle() && isAlive() && valid && (level().purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer); // Paper - Fix item duplication and teleport issues // Purpur } public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) { @@ -4180,6 +4218,20 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S 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) {} @@ -4457,6 +4509,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S this.yRotO = this.getYRot(); } + // Purpur start + public AABB getAxisForFluidCheck() { + return this.getBoundingBox().deflate(0.001D); + } + // Purpur end + public boolean updateFluidHeightAndDoFluidPushing(TagKey tag, double speed) { if (false && this.touchingUnloadedChunk()) { // Gale - Airplane - reduce entity fluid lookups if no fluids - cost of a lookup here is the same cost as below, so skip return false; @@ -5035,4 +5093,45 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S return ((net.minecraft.server.level.ServerChunkCache) level.getChunkSource()).isPositionTicking(this); } // Paper end - Expose entity id counter + + // Purpur start + @Nullable + private Player rider = null; + + @Nullable + public Player getRider() { + return rider; + } + + public boolean isRidable() { + return false; + } + + public boolean isControllable() { + return true; + } + + public void onMount(Player rider) { + if (this instanceof Mob) { + ((Mob) this).setTarget(null, null, false); + ((Mob) this).getNavigation().stop(); + } + rider.setJumping(false); // fixes jump on mount + } + + public void onDismount(Player player) { + } + + public boolean onSpacebar() { + return false; + } + + public boolean onClick(InteractionHand hand) { + return false; + } + + public boolean processClick(InteractionHand hand) { + return false; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java index d8cc5614502db7025349e085381b6b32ad32296a..f1b9e83206cc67e6ef29ebe088351b0aaa5eb349 100644 --- a/src/main/java/net/minecraft/world/entity/EntitySelector.java +++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java @@ -40,6 +40,7 @@ public final class EntitySelector { return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks; }; // Paper end - Ability to control player's insomnia and phantoms + public static Predicate notAfk = (player) -> !player.isAfk(); // Purpur private EntitySelector() {} // Paper start - Affects Spawning API diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java index dc11683ee4d8a6b7a1c42bcae36dc6e8105cd994..a9e2a758669550530eb29475ba99fe42e520f6ae 100644 --- a/src/main/java/net/minecraft/world/entity/EntityType.java +++ b/src/main/java/net/minecraft/world/entity/EntityType.java @@ -313,13 +313,24 @@ public class EntityType implements FeatureElement, EntityTypeT private Component description; @Nullable private ResourceLocation lootTable; - private final EntityDimensions dimensions; + private EntityDimensions dimensions; // Purpur - remove final + public void setDimensions(EntityDimensions dimensions) { this.dimensions = dimensions; } // Purpur private final FeatureFlagSet requiredFeatures; private static EntityType register(String id, EntityType.Builder type) { // CraftBukkit - decompile error return (EntityType) Registry.register(BuiltInRegistries.ENTITY_TYPE, id, (EntityType) type.build(id)); // CraftBukkit - decompile error } + // Purpur start + public static EntityType getFromBukkitType(org.bukkit.entity.EntityType bukkitType) { + return getFromKey(new ResourceLocation(bukkitType.getKey().toString())); + } + + public static EntityType getFromKey(ResourceLocation location) { + return BuiltInRegistries.ENTITY_TYPE.get(location); + } + // Purpur end + public static ResourceLocation getKey(EntityType type) { return BuiltInRegistries.ENTITY_TYPE.getKey(type); } @@ -531,6 +542,16 @@ public class EntityType implements FeatureElement, EntityTypeT return this.category; } + // Purpur start + public String getName() { + return BuiltInRegistries.ENTITY_TYPE.getKey(this).getPath(); + } + + public String getTranslatedName() { + return getDescription().getString(); + } + // Purpur end + public String getDescriptionId() { if (this.descriptionId == null) { this.descriptionId = Util.makeDescriptionId("entity", BuiltInRegistries.ENTITY_TYPE.getKey(this)); @@ -598,6 +619,12 @@ public class EntityType implements FeatureElement, EntityTypeT entity.load(nbt); }, () -> { EntityType.LOGGER.warn("Skipping Entity with id {}", nbt.getString("id")); + // Purpur start - log skipped entity's position + try { + ListTag pos = nbt.getList("Pos", 6); + EntityType.LOGGER.warn("Location: {} {},{},{}", world.getWorld().getName(), pos.getDouble(0), pos.getDouble(1), pos.getDouble(2)); + } catch (Throwable ignore) {} + // Purpur end }); } diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java index 36422fb394a158f36c84ba0ee03cc704956c91b2..9a3210e34decb4096533c58f36687e31330198c4 100644 --- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java +++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java @@ -314,7 +314,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 - PlayerPickupExperienceEvent - player.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(player, 2, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; + player.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(player, this.level().purpurConfig.playerExpPickupDelay, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; // Purpur player.take(this, 1); int i = this.repairPlayerItems(player, this.value); @@ -332,7 +332,7 @@ public class ExperienceOrb extends Entity { } private int repairPlayerItems(Player player, int amount) { - Entry entry = EnchantmentHelper.getRandomItemWith(Enchantments.MENDING, player, ItemStack::isDamaged); + Entry entry = level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedEquipment(Enchantments.MENDING, player) : EnchantmentHelper.getRandomItemWith(Enchantments.MENDING, player, ItemStack::isDamaged); // Purpur if (entry != null) { ItemStack itemstack = (ItemStack) entry.getValue(); @@ -360,13 +360,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 1bb8b6e91c44cd13411d96d749fa64835c75a267..b18cbe85330e26de6f6cbfcc3d51a7418bd93877 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 9e49ccc50ddd23c974dd0b82da538f8113820c84..dccb8934428b0c29e35f2bf9968fd32443dc11eb 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 { protected boolean skipDropExperience; // CraftBukkit start public int expToDrop; + public float safeFallDistance = 3.0F; // Purpur public boolean forceDrops; public ArrayList drops = new ArrayList<>(); // Paper - Restore vanilla drops behavior 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 - Friction API + 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).add(Attributes.MAX_ABSORPTION); } + 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); @@ -424,6 +430,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))); } } @@ -435,7 +442,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(); @@ -447,7 +454,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 } } @@ -804,6 +811,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 @@ -890,6 +898,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 @@ -1033,10 +1046,31 @@ public abstract class LivingEntity extends Entity implements Attackable { if (entity != null) { EntityType entitytypes = entity.getType(); + // Gale start - Petal - reduce skull ItemStack lookups for reduced visibility + // Purpur start + if (entitytypes == EntityType.SKELETON && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.SKELETON_SKULL)) { + d0 *= entity.level().purpurConfig.skeletonHeadVisibilityPercent; + } else if (entitytypes == EntityType.ZOMBIE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.ZOMBIE_HEAD)) { + d0 *= entity.level().purpurConfig.zombieHeadVisibilityPercent; + } else if (entitytypes == EntityType.CREEPER && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.CREEPER_HEAD)) { + d0 *= entity.level().purpurConfig.creeperHeadVisibilityPercent; + } else if ((entitytypes == EntityType.PIGLIN || entitytypes == EntityType.PIGLIN_BRUTE) && this.getItemBySlot(EquipmentSlot.HEAD).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; + } + } - if (entitytypes == EntityType.SKELETON && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.SKELETON_SKULL) || entitytypes == EntityType.ZOMBIE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.ZOMBIE_HEAD) || entitytypes == EntityType.PIGLIN && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.PIGLIN_HEAD) || entitytypes == EntityType.PIGLIN_BRUTE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.PIGLIN_HEAD) || entitytypes == EntityType.CREEPER && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.CREEPER_HEAD)) { // Gale - Petal - reduce skull ItemStack lookups for reduced visibility - d0 *= 0.5D; } + // Purpur end + // Gale end } return d0; @@ -1096,6 +1130,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; @@ -1514,13 +1549,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) { @@ -1628,6 +1663,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); @@ -1794,7 +1841,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(); @@ -1840,6 +1887,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; @@ -1848,6 +1896,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, () -> { @@ -2131,7 +2180,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 } } @@ -2354,6 +2403,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. @@ -2574,7 +2637,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() { @@ -2768,7 +2831,7 @@ public abstract class LivingEntity extends Entity implements Attackable { } protected long lastJumpTime = 0L; // Paper - Prevent excessive velocity through repeated crits - protected void jumpFromGround() { + public void jumpFromGround() { // Purpur - protected -> public Vec3 vec3d = this.getDeltaMovement(); // Paper start - Prevent excessive velocity through repeated crits long time = System.nanoTime(); @@ -2920,6 +2983,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); } } @@ -3530,8 +3594,10 @@ public abstract class LivingEntity extends Entity implements Attackable { this.pushEntities(); // Paper start - Add EntityMoveEvent - if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { - if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { + // Purpur start + if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) { + if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) { + // Purpur end Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone()); @@ -3541,12 +3607,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 - Add EntityMoveEvent 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() { @@ -3567,7 +3669,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 1c729ea3e40b12ddb95294c666f1568bd69ce4c0..70b5f3ce8d94765e84215015dd0c268f783990c9 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java @@ -66,6 +66,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; @@ -136,6 +137,7 @@ public abstract class Mob extends LivingEntity implements Targeting { private BlockPos restrictCenter; private float restrictRadius; + public int ticksSinceLastInteraction; // Purpur public boolean aware = true; // CraftBukkit protected Mob(EntityType type, Level world) { @@ -151,8 +153,8 @@ public abstract class Mob extends LivingEntity implements Targeting { this.goalSelector = new GoalSelector(); this.targetSelector = new GoalSelector(); // Gale end - Purpur - remove vanilla profiler - this.lookControl = new LookControl(this); - this.moveControl = new MoveControl(this); + this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this); // Purpur + this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this); // Purpur this.jumpControl = new JumpControl(this); this.bodyRotationControl = this.createBodyControl(); this.navigation = this.createNavigation(world); @@ -327,6 +329,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 @@ -371,8 +374,28 @@ public abstract class Mob extends LivingEntity implements Targeting { this.resetAmbientSoundTime(); this.playAmbientSound(); } + incrementTicksSinceLastInteraction(); // Purpur } + // Purpur start + private void incrementTicksSinceLastInteraction() { + ++this.ticksSinceLastInteraction; + if (getRider() != null) { + this.ticksSinceLastInteraction = 0; + return; + } + if (this.level().purpurConfig.entityLifeSpan <= 0) { + return; // feature disabled + } + if (!this.removeWhenFarAway(0) || isPersistenceRequired() || requiresCustomPersistence() || hasCustomName()) { + return; // mob persistent + } + if (this.ticksSinceLastInteraction > this.level().purpurConfig.entityLifeSpan) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + } + } + // Purpur end + @Override protected void playHurtSound(DamageSource source) { this.resetAmbientSoundTime(); @@ -562,6 +585,7 @@ public abstract class Mob extends LivingEntity implements Targeting { } nbt.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit + nbt.putInt("Purpur.ticksSinceLastInteraction", this.ticksSinceLastInteraction); // Purpur } @Override @@ -632,6 +656,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 @@ -675,7 +704,7 @@ public abstract class Mob extends LivingEntity implements Targeting { @Override public void aiStep() { super.aiStep(); - if (!this.level().isClientSide && this.canPickUpLoot() && this.isAlive() && !this.dead && this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (!this.level().isClientSide && this.canPickUpLoot() && this.isAlive() && !this.dead && (this.level().purpurConfig.entitiesPickUpLootBypassMobGriefing || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { Vec3i baseblockposition = this.getPickupReach(); List list = this.level().getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ())); Iterator iterator = list.iterator(); @@ -1159,6 +1188,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) { @@ -1253,7 +1288,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); @@ -1301,6 +1336,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 - Expand EntityUnleashEvent org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.getAbilities().instabuild); @@ -1374,7 +1410,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() { @@ -1685,6 +1721,7 @@ public abstract class Mob extends LivingEntity implements Targeting { this.setLastHurtMob(target); } + if (target instanceof ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur return flag; } @@ -1710,28 +1747,7 @@ public abstract class Mob extends LivingEntity implements Targeting { // Gale end - JettPack - optimize sun burn tick - cache eye blockpos public boolean isSunBurnTick() { - if (this.level().isDay() && !this.level().isClientSide) { - // Gale start - JettPack - optimize sun burn tick - optimizations and cache eye blockpos - int positionHashCode = this.position.hashCode(); - if (this.cached_position_hashcode != positionHashCode) { - this.cached_eye_blockpos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); - this.cached_position_hashcode = positionHashCode; - } - - float f = this.getLightLevelDependentMagicValue(cached_eye_blockpos); // Pass BlockPos to getBrightness - - // Check brightness first - if (f <= 0.5F) return false; - if (this.random.nextFloat() * 30.0F >= (f - 0.4F) * 2.0F) return false; - // Gale end - JettPack - optimize sun burn tick - optimizations and cache eye blockpos - boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; - - if (!flag && this.level().canSeeSky(this.cached_eye_blockpos)) { // Gale - JettPack - optimize sun burn tick - optimizations and cache eye blockpos - return true; - } - } - - return false; + return super.isSunBurnTick(); } @Override @@ -1779,4 +1795,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 2ee48ac3b665db2b02bcb1a30ec972d43a3725b0..59e8f5431ce5026209e1428b5fa5b5485dcfebc7 100644 --- a/src/main/java/net/minecraft/world/entity/Shearable.java +++ b/src/main/java/net/minecraft/world/entity/Shearable.java @@ -8,7 +8,7 @@ public interface Shearable { boolean readyForShearing(); // Paper start - custom shear drops; ensure all implementing entities override this - default java.util.List generateDefaultDrops() { + default java.util.List generateDefaultDrops(int looting) { // Purpur return java.util.Collections.emptyList(); } // Paper end - custom shear drops 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 0c804e597b08eb1fce53a1161aad3405b63c8058..7394c2d99499faaa4489a7eee1dd8b73ec08668d 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 @@ -27,14 +27,22 @@ public class AttributeMap { // Gale end - Lithium - replace AI attributes with optimized collections private final AttributeSupplier supplier; private final java.util.function.Function createInstance; // Gale - Airplane - reduce entity allocations + 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); // Gale - Airplane - reduce entity allocations } private void onAttributeModified(AttributeInstance instance) { - if (instance.getAttribute().isClientSyncable()) { + if (instance.getAttribute().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute()))) { // Purpur this.dirtyAttributes.add(instance); } @@ -46,7 +54,7 @@ public class AttributeMap { public Collection getSyncableAttributes() { return this.attributes.values().stream().filter((attribute) -> { - return attribute.getAttribute().isClientSyncable(); + return attribute.getAttribute().isClientSyncable() && (entity == null || entity.shouldSendAttribute(attribute.getAttribute())); // Purpur }).collect(Collectors.toList()); } diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java index 0a13e076b42bc8c0e7d4962379c207dea89f0435..62cb9f0c052222deecf76ebb8dbdd9652e691b2c 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 @@ -81,7 +81,89 @@ import org.slf4j.Logger; public class DefaultAttributes { private static final Logger LOGGER = LogUtils.getLogger(); - private static final Map, AttributeSupplier> SUPPLIERS = ImmutableMap., AttributeSupplier>builder().put(EntityType.ALLAY, Allay.createAttributes().build()).put(EntityType.ARMOR_STAND, LivingEntity.createLivingAttributes().build()).put(EntityType.AXOLOTL, Axolotl.createAttributes().build()).put(EntityType.BAT, Bat.createAttributes().build()).put(EntityType.BEE, Bee.createAttributes().build()).put(EntityType.BLAZE, Blaze.createAttributes().build()).put(EntityType.CAT, Cat.createAttributes().build()).put(EntityType.CAMEL, Camel.createAttributes().build()).put(EntityType.CAVE_SPIDER, CaveSpider.createCaveSpider().build()).put(EntityType.CHICKEN, Chicken.createAttributes().build()).put(EntityType.COD, AbstractFish.createAttributes().build()).put(EntityType.COW, Cow.createAttributes().build()).put(EntityType.CREEPER, Creeper.createAttributes().build()).put(EntityType.DOLPHIN, Dolphin.createAttributes().build()).put(EntityType.DONKEY, AbstractChestedHorse.createBaseChestedHorseAttributes().build()).put(EntityType.DROWNED, Zombie.createAttributes().build()).put(EntityType.ELDER_GUARDIAN, ElderGuardian.createAttributes().build()).put(EntityType.ENDERMAN, EnderMan.createAttributes().build()).put(EntityType.ENDERMITE, Endermite.createAttributes().build()).put(EntityType.ENDER_DRAGON, EnderDragon.createAttributes().build()).put(EntityType.EVOKER, Evoker.createAttributes().build()).put(EntityType.BREEZE, Breeze.createAttributes().build()).put(EntityType.FOX, Fox.createAttributes().build()).put(EntityType.FROG, Frog.createAttributes().build()).put(EntityType.GHAST, Ghast.createAttributes().build()).put(EntityType.GIANT, Giant.createAttributes().build()).put(EntityType.GLOW_SQUID, GlowSquid.createAttributes().build()).put(EntityType.GOAT, Goat.createAttributes().build()).put(EntityType.GUARDIAN, Guardian.createAttributes().build()).put(EntityType.HOGLIN, Hoglin.createAttributes().build()).put(EntityType.HORSE, AbstractHorse.createBaseHorseAttributes().build()).put(EntityType.HUSK, Zombie.createAttributes().build()).put(EntityType.ILLUSIONER, Illusioner.createAttributes().build()).put(EntityType.IRON_GOLEM, IronGolem.createAttributes().build()).put(EntityType.LLAMA, Llama.createAttributes().build()).put(EntityType.MAGMA_CUBE, MagmaCube.createAttributes().build()).put(EntityType.MOOSHROOM, Cow.createAttributes().build()).put(EntityType.MULE, AbstractChestedHorse.createBaseChestedHorseAttributes().build()).put(EntityType.OCELOT, Ocelot.createAttributes().build()).put(EntityType.PANDA, Panda.createAttributes().build()).put(EntityType.PARROT, Parrot.createAttributes().build()).put(EntityType.PHANTOM, Monster.createMonsterAttributes().build()).put(EntityType.PIG, Pig.createAttributes().build()).put(EntityType.PIGLIN, Piglin.createAttributes().build()).put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build()).put(EntityType.PILLAGER, Pillager.createAttributes().build()).put(EntityType.PLAYER, Player.createAttributes().build()).put(EntityType.POLAR_BEAR, PolarBear.createAttributes().build()).put(EntityType.PUFFERFISH, AbstractFish.createAttributes().build()).put(EntityType.RABBIT, Rabbit.createAttributes().build()).put(EntityType.RAVAGER, Ravager.createAttributes().build()).put(EntityType.SALMON, AbstractFish.createAttributes().build()).put(EntityType.SHEEP, Sheep.createAttributes().build()).put(EntityType.SHULKER, Shulker.createAttributes().build()).put(EntityType.SILVERFISH, Silverfish.createAttributes().build()).put(EntityType.SKELETON, AbstractSkeleton.createAttributes().build()).put(EntityType.SKELETON_HORSE, SkeletonHorse.createAttributes().build()).put(EntityType.SLIME, Monster.createMonsterAttributes().build()).put(EntityType.SNIFFER, Sniffer.createAttributes().build()).put(EntityType.SNOW_GOLEM, SnowGolem.createAttributes().build()).put(EntityType.SPIDER, Spider.createAttributes().build()).put(EntityType.SQUID, Squid.createAttributes().build()).put(EntityType.STRAY, AbstractSkeleton.createAttributes().build()).put(EntityType.STRIDER, Strider.createAttributes().build()).put(EntityType.TADPOLE, Tadpole.createAttributes().build()).put(EntityType.TRADER_LLAMA, Llama.createAttributes().build()).put(EntityType.TROPICAL_FISH, AbstractFish.createAttributes().build()).put(EntityType.TURTLE, Turtle.createAttributes().build()).put(EntityType.VEX, Vex.createAttributes().build()).put(EntityType.VILLAGER, Villager.createAttributes().build()).put(EntityType.VINDICATOR, Vindicator.createAttributes().build()).put(EntityType.WARDEN, Warden.createAttributes().build()).put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build()).put(EntityType.WITCH, Witch.createAttributes().build()).put(EntityType.WITHER, WitherBoss.createAttributes().build()).put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build()).put(EntityType.WOLF, Wolf.createAttributes().build()).put(EntityType.ZOGLIN, Zoglin.createAttributes().build()).put(EntityType.ZOMBIE, Zombie.createAttributes().build()).put(EntityType.ZOMBIE_HORSE, ZombieHorse.createAttributes().build()).put(EntityType.ZOMBIE_VILLAGER, Zombie.createAttributes().build()).put(EntityType.ZOMBIFIED_PIGLIN, ZombifiedPiglin.createAttributes().build()).build(); + private static final Map, AttributeSupplier> SUPPLIERS = ImmutableMap., AttributeSupplier>builder() + .put(EntityType.ALLAY, Allay.createAttributes().build()) + .put(EntityType.ARMOR_STAND, LivingEntity.createLivingAttributes().build()) + .put(EntityType.AXOLOTL, Axolotl.createAttributes().build()) + .put(EntityType.BAT, Bat.createAttributes().build()) + .put(EntityType.BEE, Bee.createAttributes().build()) + .put(EntityType.BLAZE, Blaze.createAttributes().build()) + .put(EntityType.CAT, Cat.createAttributes().build()) + .put(EntityType.CAMEL, Camel.createAttributes().build()) + .put(EntityType.CAVE_SPIDER, CaveSpider.createCaveSpider().build()) + .put(EntityType.CHICKEN, Chicken.createAttributes().build()) + .put(EntityType.COD, AbstractFish.createAttributes().build()) + .put(EntityType.COW, Cow.createAttributes().build()) + .put(EntityType.CREEPER, Creeper.createAttributes().build()) + .put(EntityType.DOLPHIN, Dolphin.createAttributes().build()) + .put(EntityType.DONKEY, AbstractChestedHorse.createBaseChestedHorseAttributes().build()) + .put(EntityType.DROWNED, Zombie.createAttributes().build()) + .put(EntityType.ELDER_GUARDIAN, ElderGuardian.createAttributes().build()) + .put(EntityType.ENDERMAN, EnderMan.createAttributes().build()) + .put(EntityType.ENDERMITE, Endermite.createAttributes().build()) + .put(EntityType.ENDER_DRAGON, EnderDragon.createAttributes().build()) + .put(EntityType.EVOKER, Evoker.createAttributes().build()) + .put(EntityType.BREEZE, Breeze.createAttributes().build()) + .put(EntityType.FOX, Fox.createAttributes().build()) + .put(EntityType.FROG, Frog.createAttributes().build()) + .put(EntityType.GHAST, Ghast.createAttributes().build()) + .put(EntityType.GIANT, Giant.createAttributes().build()) + .put(EntityType.GLOW_SQUID, GlowSquid.createAttributes().build()) + .put(EntityType.GOAT, Goat.createAttributes().build()) + .put(EntityType.GUARDIAN, Guardian.createAttributes().build()) + .put(EntityType.HOGLIN, Hoglin.createAttributes().build()) + .put(EntityType.HORSE, AbstractHorse.createBaseHorseAttributes().build()) + .put(EntityType.HUSK, Zombie.createAttributes().build()) + .put(EntityType.ILLUSIONER, Illusioner.createAttributes().build()) + .put(EntityType.IRON_GOLEM, IronGolem.createAttributes().build()) + .put(EntityType.LLAMA, Llama.createAttributes().build()) + .put(EntityType.MAGMA_CUBE, MagmaCube.createAttributes().build()) + .put(EntityType.MOOSHROOM, Cow.createAttributes().build()) + .put(EntityType.MULE, AbstractChestedHorse.createBaseChestedHorseAttributes().build()) + .put(EntityType.OCELOT, Ocelot.createAttributes().build()) + .put(EntityType.PANDA, Panda.createAttributes().build()) + .put(EntityType.PARROT, Parrot.createAttributes().build()) + .put(EntityType.PHANTOM, net.minecraft.world.entity.monster.Phantom.createAttributes().build()) // Purpur + .put(EntityType.PIG, Pig.createAttributes().build()) + .put(EntityType.PIGLIN, Piglin.createAttributes().build()) + .put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build()) + .put(EntityType.PILLAGER, Pillager.createAttributes().build()) + .put(EntityType.PLAYER, Player.createAttributes().build()) + .put(EntityType.POLAR_BEAR, PolarBear.createAttributes().build()) + .put(EntityType.PUFFERFISH, AbstractFish.createAttributes().build()) + .put(EntityType.RABBIT, Rabbit.createAttributes().build()) + .put(EntityType.RAVAGER, Ravager.createAttributes().build()) + .put(EntityType.SALMON, AbstractFish.createAttributes().build()) + .put(EntityType.SHEEP, Sheep.createAttributes().build()) + .put(EntityType.SHULKER, Shulker.createAttributes().build()) + .put(EntityType.SILVERFISH, Silverfish.createAttributes().build()) + .put(EntityType.SKELETON, AbstractSkeleton.createAttributes().build()) + .put(EntityType.SKELETON_HORSE, SkeletonHorse.createAttributes().build()) + .put(EntityType.SLIME, Monster.createMonsterAttributes().build()) + .put(EntityType.SNIFFER, Sniffer.createAttributes().build()) + .put(EntityType.SNOW_GOLEM, SnowGolem.createAttributes().build()) + .put(EntityType.SPIDER, Spider.createAttributes().build()) + .put(EntityType.SQUID, Squid.createAttributes().build()) + .put(EntityType.STRAY, AbstractSkeleton.createAttributes().build()) + .put(EntityType.STRIDER, Strider.createAttributes().build()) + .put(EntityType.TADPOLE, Tadpole.createAttributes().build()) + .put(EntityType.TRADER_LLAMA, Llama.createAttributes().build()) + .put(EntityType.TROPICAL_FISH, AbstractFish.createAttributes().build()) + .put(EntityType.TURTLE, Turtle.createAttributes().build()) + .put(EntityType.VEX, Vex.createAttributes().build()) + .put(EntityType.VILLAGER, Villager.createAttributes().build()) + .put(EntityType.VINDICATOR, Vindicator.createAttributes().build()) + .put(EntityType.WARDEN, Warden.createAttributes().build()) + .put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build()) + .put(EntityType.WITCH, Witch.createAttributes().build()) + .put(EntityType.WITHER, WitherBoss.createAttributes().build()) + .put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build()) + .put(EntityType.WOLF, Wolf.createAttributes().build()) + .put(EntityType.ZOGLIN, Zoglin.createAttributes().build()) + .put(EntityType.ZOMBIE, Zombie.createAttributes().build()) + .put(EntityType.ZOMBIE_HORSE, ZombieHorse.createAttributes().build()) + .put(EntityType.ZOMBIE_VILLAGER, Zombie.createAttributes().build()) + .put(EntityType.ZOMBIFIED_PIGLIN, ZombifiedPiglin.createAttributes().build()).build(); public static AttributeSupplier getSupplier(EntityType type) { return SUPPLIERS.get(type); diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java b/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java index f0703302e7dbbda88de8c648d20d87c55ed9b1e0..a913ebabaa5f443afa987b972355a8f8d1723c78 100644 --- a/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java +++ b/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java @@ -29,6 +29,7 @@ public class RangedAttribute extends Attribute { @Override public double sanitizeValue(double value) { + if (!org.purpurmc.purpur.PurpurConfig.clampAttributes) return Double.isNaN(value) ? this.minValue : value; // Purpur return Double.isNaN(value) ? this.minValue : Mth.clamp(value, this.minValue, this.maxValue); } } diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java index d3a2a6dee2d83b3df0ddc521c080f7d72b709461..a1acc479c9d6a838e3180da3ac8a3a02d22db5c1 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java @@ -40,17 +40,19 @@ public class HarvestFarmland extends Behavior { private long nextOkStartTime; private int timeWorkedSoFar; private final List validFarmlandAroundVillager = Lists.newArrayList(); + private boolean clericWartFarmer = false; // Purpur public HarvestFarmland() { super(ImmutableMap.of(MemoryModuleType.LOOK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.SECONDARY_JOB_SITE, MemoryStatus.VALUE_PRESENT)); } protected boolean checkExtraStartConditions(ServerLevel world, Villager entity) { - if (!world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (!world.purpurConfig.villagerBypassMobGriefing && !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur return false; - } else if (entity.getVillagerData().getProfession() != VillagerProfession.FARMER) { + } else if (entity.getVillagerData().getProfession() != VillagerProfession.FARMER && !(world.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC)) { // Purpur return false; } else { + if (!this.clericWartFarmer && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC) this.clericWartFarmer = true; // Purpur BlockPos.MutableBlockPos blockposition_mutableblockposition = entity.blockPosition().mutable(); this.validFarmlandAroundVillager.clear(); @@ -81,6 +83,7 @@ public class HarvestFarmland extends Behavior { Block block = iblockdata.getBlock(); Block block1 = world.getBlockState(pos.below()).getBlock(); + if (this.clericWartFarmer) return block == Blocks.NETHER_WART && iblockdata.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3 || iblockdata.isAir() && block1 == Blocks.SOUL_SAND; // Purpur return block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata) || iblockdata.isAir() && block1 instanceof FarmBlock; } @@ -106,20 +109,20 @@ public class HarvestFarmland extends Behavior { Block block = iblockdata.getBlock(); Block block1 = world.getBlockState(this.aboveFarmlandPos.below()).getBlock(); - if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) { + if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata) && !this.clericWartFarmer || this.clericWartFarmer && block == Blocks.NETHER_WART && iblockdata.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3) { // Purpur if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state world.destroyBlock(this.aboveFarmlandPos, true, entity); } // CraftBukkit } - if (iblockdata.isAir() && block1 instanceof FarmBlock && entity.hasFarmSeeds()) { + if (iblockdata.isAir() && (block1 instanceof FarmBlock && !this.clericWartFarmer || this.clericWartFarmer && block1 == Blocks.SOUL_SAND) && entity.hasFarmSeeds()) { // Purpur SimpleContainer inventorysubcontainer = entity.getInventory(); for (int j = 0; j < inventorysubcontainer.getContainerSize(); ++j) { ItemStack itemstack = inventorysubcontainer.getItem(j); boolean flag = false; - if (!itemstack.isEmpty() && itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS)) { + if (!itemstack.isEmpty() && (itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS) || this.clericWartFarmer && itemstack.getItem() == net.minecraft.world.item.Items.NETHER_WART)) { Item item = itemstack.getItem(); if (item instanceof BlockItem) { @@ -135,7 +138,7 @@ public class HarvestFarmland extends Behavior { } if (flag) { - world.playSound((Player) null, (double) this.aboveFarmlandPos.getX(), (double) this.aboveFarmlandPos.getY(), (double) this.aboveFarmlandPos.getZ(), SoundEvents.CROP_PLANTED, SoundSource.BLOCKS, 1.0F, 1.0F); + world.playSound((Player) null, (double) this.aboveFarmlandPos.getX(), (double) this.aboveFarmlandPos.getY(), (double) this.aboveFarmlandPos.getZ(), this.clericWartFarmer ? SoundEvents.NETHER_WART_PLANTED : SoundEvents.CROP_PLANTED, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur itemstack.shrink(1); if (itemstack.isEmpty()) { inventorysubcontainer.setItem(j, ItemStack.EMPTY); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java b/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java index 42ae4d293a420f0b8eb476df6389b2e7a693895f..97c20c5b89e6d7e4ed844eff39ee55dfa8988d37 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java @@ -57,7 +57,7 @@ public class InteractWithDoor { if (iblockdata.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> { return blockbase_blockdata.getBlock() instanceof DoorBlock; - })) { + }) && !DoorBlock.requiresRedstone(entityliving.level(), iblockdata, blockposition)) { // Purpur DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock(); if (!blockdoor.isOpen(iblockdata)) { @@ -79,7 +79,7 @@ public class InteractWithDoor { if (iblockdata1.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> { return blockbase_blockdata.getBlock() instanceof DoorBlock; - })) { + }) && !DoorBlock.requiresRedstone(entityliving.level(), iblockdata, blockposition1)) { // Purpur DoorBlock blockdoor1 = (DoorBlock) iblockdata1.getBlock(); if (!blockdoor1.isOpen(iblockdata1)) { @@ -122,7 +122,7 @@ public class InteractWithDoor { if (!iblockdata.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> { return blockbase_blockdata.getBlock() instanceof DoorBlock; - })) { + }) || DoorBlock.requiresRedstone(entity.level(), iblockdata, blockposition)) { // Purpur iterator.remove(); } else { DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock(); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java index c3e0b1f11b58668f9f24eb421abc340e1b49dfac..d809fa4f96e2c528075f544355397903996ecaf6 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java @@ -42,6 +42,7 @@ public class ShowTradesToPlayer extends Behavior { @Override public boolean canStillUse(ServerLevel world, Villager entity, long time) { + if (!entity.level().purpurConfig.villagerDisplayTradeItem) return false; // Purpur return this.checkExtraStartConditions(world, entity) && this.lookTime > 0 && entity.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).isPresent(); } diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java b/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java index 94fb9bcf601832ee934331c0376de8707b5043c5..44701d0bed85c46e658bc3f2a738126bd748e5c6 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 @@ -60,6 +60,12 @@ public class TradeWithVillager extends Behavior { throwHalfStack(entity, WHEAT_SINGLETON_ARRAY, villager); // Gale - optimize villager data storage } + // Purpur start + if (world.purpurConfig.villagerClericsFarmWarts && world.purpurConfig.villagerClericFarmersThrowWarts && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC && entity.getInventory().countItem(Items.NETHER_WART) > Items.NETHER_WART.getMaxStackSize() / 2) { + throwHalfStack(entity, ImmutableSet.of(Items.NETHER_WART), villager); + } + // Purpur end + // Gale start - optimize villager data storage if (this.trades != null && entity.getInventory().hasAnyOf(this.trades)) { throwHalfStack(entity, this.trades, villager); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java index cd7a90ec1073b2b452ca70decefe6a594445003b..47672e48c1cae73cffe532d622b296343fc12ef0 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java @@ -30,8 +30,13 @@ public class VillagerGoalPackages { } public static ImmutableList>> getWorkPackage(VillagerProfession profession, float speed) { + // Purpur start + return getWorkPackage(profession, speed, false); + } + public static ImmutableList>> getWorkPackage(VillagerProfession profession, float speed, boolean clericsFarmWarts) { + // Purpur end WorkAtPoi workAtPoi; - if (profession == VillagerProfession.FARMER) { + if (profession == VillagerProfession.FARMER || (clericsFarmWarts && profession == VillagerProfession.CLERIC)) { // Purpur workAtPoi = new WorkAtComposter(); } else { workAtPoi = new WorkAtPoi(); diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java index 0cc411dd39d981187c9e9a3c5eb8043b19a09b98..f7032f4ea55f5aca293c2640686238b7af0f9c80 100644 --- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java +++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java @@ -127,8 +127,10 @@ public class VillagerMakeLove extends Behavior { return Optional.empty(); } // Move age setting down - parent.setAge(6000); - partner.setAge(6000); + // Purpur start + parent.setAge(world.purpurConfig.villagerBreedingTicks); + partner.setAge(world.purpurConfig.villagerBreedingTicks); + // Purpur end world.addFreshEntityWithPassengers(entityvillager2, CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit end world.broadcastEntityEvent(entityvillager2, (byte) 12); diff --git a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java index 03d77c6d4697d4934ca65e3329982f0efe089820..ce7e3e90993b76225dc820a21e04267e488f5f7b 100644 --- a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java +++ b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java @@ -29,6 +29,20 @@ public class MoveControl implements Control { this.mob = entity; } + // Purpur start + public void setSpeedModifier(double speed) { + this.speedModifier = speed; + } + + public void setForward(float forward) { + this.strafeForwards = forward; + } + + public void setStrafe(float strafe) { + this.strafeRight = strafe; + } + // Purpur end + public boolean hasWanted() { return this.operation == MoveControl.Operation.MOVE_TO; } diff --git a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java index 7df56705a4a0de2dc4ff7ab133fc26612c219162..384bed4505b6cabb1ae151cd2c4eb5e56f24563f 100644 --- a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java +++ b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java @@ -3,7 +3,7 @@ package net.minecraft.world.entity.ai.control; import net.minecraft.util.Mth; import net.minecraft.world.entity.Mob; -public class SmoothSwimmingLookControl extends LookControl { +public class SmoothSwimmingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur private final int maxYRotFromCenter; private static final int HEAD_TILT_X = 10; private static final int HEAD_TILT_Y = 20; @@ -14,7 +14,7 @@ public class SmoothSwimmingLookControl extends LookControl { } @Override - public void tick() { + public void vanillaTick() { // Purpur if (this.lookAtCooldown > 0) { --this.lookAtCooldown; this.getYRotD().ifPresent((yaw) -> { diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java index a85885ee51df585fa11ae9f8fcd67ff2a71c5a18..d81509e08e70ec5b2f837c9dc66b1254c86854e4 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java @@ -32,7 +32,7 @@ public class BreakDoorGoal extends DoorInteractGoal { @Override public boolean canUse() { - return !super.canUse() ? false : (!this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.isValidDifficulty(this.mob.level().getDifficulty()) && !this.isOpen()); + return !super.canUse() ? false : ((!this.mob.level().purpurConfig.zombieBypassMobGriefing && !this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) ? false : this.isValidDifficulty(this.mob.level().getDifficulty()) && !this.isOpen()); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java index 4e2c23ccdf4e4a4d65b291dbe20952bae1838bff..0da884a833f6c707fea512e826658c3bb73f7a77 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/LlamaFollowCaravanGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java index 21725aee29e9120d1c7e1e19f91c21a73a28844f..3fc9528201fb96d6a0f905afe0b6a82ec88a7235 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java @@ -22,6 +22,7 @@ public class LlamaFollowCaravanGoal extends Goal { @Override public boolean canUse() { + if (!this.llama.level().purpurConfig.llamaJoinCaravans || !this.llama.shouldJoinCaravan) return false; // Purpur if (!this.llama.isLeashed() && !this.llama.inCaravan()) { List list = this.llama.level().getEntities(this.llama, this.llama.getBoundingBox().inflate(9.0D, 4.0D, 9.0D), (entity) -> { EntityType entityType = entity.getType(); @@ -71,6 +72,7 @@ public class LlamaFollowCaravanGoal extends Goal { @Override public boolean canContinueToUse() { + if (!this.llama.shouldJoinCaravan) return false; // Purpur if (this.llama.inCaravan() && this.llama.getCaravanHead().isAlive() && this.firstIsLeashed(this.llama, 0)) { double d = this.llama.distanceToSqr(this.llama.getCaravanHead()); if (d > 676.0D) { diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java index 5580a396a56c6e0f364a5368985ee99b9e2be0a8..3facfd6eee17cb0b59425494c966e19833660dd2 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 b0944fa1f3849dd24cd010fa0a6638f5fd7179d1..a3074ec9b430c9d0a0ef33fe353db643849fab7d 100644 --- a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java +++ b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java @@ -67,7 +67,7 @@ public class RunAroundLikeCrazyGoal extends Goal { int i = this.horse.getTemper(); int j = this.horse.getMaxTemper(); - if (j > 0 && this.horse.getRandom().nextInt(j) < i && !CraftEventFactory.callEntityTameEvent(this.horse, ((CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent + if ((this.horse.level().purpurConfig.alwaysTameInCreative && ((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(entityhuman); return; } diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java index 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/sensing/SecondaryPoiSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java index 53b0519bbc5d52490040eaf0fe449648f021d0c2..b323157327203f5614d0051284b6bb247a5a564d 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 @@ -31,6 +31,13 @@ public class SecondaryPoiSensor extends Sensor { return; } // Gale end - Lithium - skip secondary POI sensor if absent + // Purpur start - make sure clerics don't wander to soul sand when the option is off + Brain brain = entity.getBrain(); + if (!world.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == net.minecraft.world.entity.npc.VillagerProfession.CLERIC) { + brain.eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE); + return; + } + // Purpur end ResourceKey resourceKey = world.dimension(); BlockPos blockPos = entity.blockPosition(); @Nullable ArrayList list = null; // Gale - optimize villager data storage @@ -52,7 +59,7 @@ public class SecondaryPoiSensor extends Sensor { } } - Brain brain = entity.getBrain(); + //Brain brain = entity.getBrain(); // Purpur - moved up // Gale start - optimize villager data storage if (list != null) { list.trimToSize(); diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java index 29963fbccdd9adadcf46970473ff1fccc68572f0..903063332195af1bf1ef88f9b5c54b807447398f 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 4f7e6f5c7b38ad19beeb5010b3a385c2efc5ef4a..51a8c84f13268d97b1d052db1efe464dad395c6f 100644 --- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java +++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java @@ -19,6 +19,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; @@ -46,12 +47,59 @@ public class Bat extends AmbientCreature { public Bat(EntityType type, Level world) { super(type, world); + this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.075F); // Purpur if (!world.isClientSide) { this.setResting(true); } } + // Purpur start + @Override + public boolean shouldSendAttribute(net.minecraft.world.entity.ai.attributes.Attribute attribute) { return attribute != Attributes.FLYING_SPEED; } // 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() && (float) this.tickCount % 10.0F == 0.0F; @@ -101,7 +149,7 @@ public class Bat extends AmbientCreature { protected void pushEntities() {} public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D); + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur } public boolean isResting() { @@ -134,6 +182,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(); @@ -212,6 +268,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); 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 081d1e38b7b1f286e138b0981aaa760e58761215..a64ab2058d4e3439bf6ee418f3192b83c346eb85 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Animal.java +++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java @@ -42,6 +42,7 @@ public abstract class Animal extends AgeableMob { @Nullable public UUID loveCause; public ItemStack breedItem; // CraftBukkit - Add breedItem variable + public abstract int getPurpurBreedTime(); // Purpur protected Animal(EntityType type, Level world) { super(type, world); @@ -151,7 +152,7 @@ public abstract class Animal extends AgeableMob { if (this.isFood(itemstack)) { int i = this.getAge(); - if (!this.level().isClientSide && i == 0 && this.canFallInLove()) { + if (!this.level().isClientSide && i == 0 && this.canFallInLove() && (this.level().purpurConfig.animalBreedingCooldownSeconds <= 0 || !this.level().hasBreedingCooldown(player.getUUID(), this.getClass()))) { // Purpur final ItemStack breedCopy = itemstack.copy(); // Paper - Fix EntityBreedEvent copying this.usePlayerItem(player, hand, itemstack); this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying @@ -242,12 +243,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()) { @@ -275,8 +284,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 f9521a6e115f0c975a7885b024c99eae300b63bf..997ab942be9f742804041b07d607e7dd6473ba96 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Bee.java +++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java @@ -43,6 +43,7 @@ import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.MobType; +import net.minecraft.world.entity.MoverType; import net.minecraft.world.entity.NeutralMob; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.entity.Pose; @@ -147,6 +148,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { public Bee(EntityType type, Level world) { super(type, world); this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60); + final org.purpurmc.purpur.controller.FlyingMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.25F, 1.0F, false); // Purpur // Paper start - Fix 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 - Fix MC-167279 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)); @@ -355,7 +406,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 { @@ -395,6 +446,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) { @@ -427,6 +479,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); @@ -742,6 +814,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); @@ -798,6 +871,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 @@ -844,6 +918,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; @@ -888,16 +963,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 f760ce7d9df79ef58f8963de3e901cba3e12fcaa..6af5e1dfcfd739e0bc857f648c189151d5a795c8 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Cat.java +++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java @@ -98,6 +98,51 @@ public class Cat extends TamableAnimal implements VariantHolder { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.catRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.catRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.catControllable; + } + + @Override + public void onMount(Player rider) { + super.onMount(rider); + setInSittingPose(false); + setLying(false); + setRelaxStateOne(false); + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.catMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.catBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.catTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.catAlwaysDropExp; + } + public ResourceLocation getResourceLocation() { return this.getVariant().texture(); } @@ -106,6 +151,7 @@ public class Cat extends TamableAnimal implements VariantHolder { protected void registerGoals() { this.temptGoal = new Cat.CatTemptGoal(this, 0.6D, Cat.TEMPT_INGREDIENT, true); this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new PanicGoal(this, 1.5D)); this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this)); this.goalSelector.addGoal(3, new Cat.CatRelaxOnOwnerGoal(this)); @@ -118,6 +164,7 @@ public class Cat extends TamableAnimal implements VariantHolder { this.goalSelector.addGoal(10, new BreedGoal(this, 0.8D)); this.goalSelector.addGoal(11, new WaterAvoidingRandomStrollGoal(this, 0.8D, 1.0000001E-5F)); this.goalSelector.addGoal(12, new LookAtPlayerGoal(this, Player.class, 10.0F)); + this.targetSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Rabbit.class, false, (Predicate) null)); this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR)); } @@ -309,6 +356,14 @@ public class Cat extends TamableAnimal implements VariantHolder { return Mth.lerp(tickDelta, this.relaxStateOneAmountO, this.relaxStateOneAmount); } + // Purpur start + @Override + public void tame(Player player) { + setCollarColor(level().purpurConfig.catDefaultCollarColor); + super.tame(player); + } + // Purpur end + @Nullable @Override public Cat getBreedOffspring(ServerLevel world, AgeableMob entity) { @@ -374,6 +429,7 @@ public class Cat extends TamableAnimal implements VariantHolder { @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { + if (getRider() != null) return InteractionResult.PASS; // Purpur ItemStack itemstack = player.getItemInHand(hand); Item item = itemstack.getItem(); @@ -420,7 +476,7 @@ public class Cat extends TamableAnimal implements VariantHolder { } } else if (this.isFood(itemstack)) { this.usePlayerItem(player, hand, itemstack); - if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit + if ((this.level().purpurConfig.alwaysTameInCreative && player.getAbilities().instabuild) || (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled())) { // CraftBukkit // Purpur this.tame(player); this.setOrderedToSit(true); this.level().broadcastEntityEvent(this, (byte) 7); diff --git a/src/main/java/net/minecraft/world/entity/animal/Chicken.java b/src/main/java/net/minecraft/world/entity/animal/Chicken.java index 5e7e6526e2750d508a870dfbdbb46e520d201796..0388ff9ce0ffe1029d977a65a528a0d9f228a3ee 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Chicken.java +++ b/src/main/java/net/minecraft/world/entity/animal/Chicken.java @@ -55,16 +55,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 @@ -73,7 +122,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 3cdd9f379c7e2d46ea47c9ef55b121c93ec0bb4a..be4ccc42d6f598cbaaf39aafbd49b594ac7b06fe 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; @@ -30,6 +31,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; import org.joml.Vector3f; @@ -40,25 +42,74 @@ import org.bukkit.event.player.PlayerBucketFillEvent; // CraftBukkit end public class Cow extends Animal { + private boolean isNaturallyAggressiveToPlayers; // Purpur public Cow(EntityType type, Level world) { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.cowRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.cowRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.cowControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.cowMaxHealth); + this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.cowNaturallyAggressiveToPlayersDamage); // Purpur + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.cowBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.cowTakeDamageFromWater; + } + + @Override + public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.MobSpawnType spawnReason, net.minecraft.world.entity.SpawnGroupData entityData, net.minecraft.nbt.CompoundTag entityNbt) { + this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance; + return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt); + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.cowAlwaysDropExp; + } + @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new PanicGoal(this, 2.0D)); + this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); + if (level().purpurConfig.cowFeedMushrooms > 0) this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, Ingredient.of(Items.WHEAT, Blocks.RED_MUSHROOM.asItem(), Blocks.BROWN_MUSHROOM.asItem()), false)); else // Purpur this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, Ingredient.of(Items.WHEAT), false)); this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25D)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, target -> isNaturallyAggressiveToPlayers)); // Purpur } public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D); + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur } @Override @@ -88,6 +139,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()) { @@ -95,7 +147,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 @@ -104,6 +156,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); } @@ -124,4 +180,69 @@ public class Cow extends Animal { protected Vector3f getPassengerAttachmentPoint(Entity passenger, EntityDimensions dimensions, float scaleFactor) { return new Vector3f(0.0F, dimensions.height - 0.03125F * scaleFactor, 0.0F); } + + // 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, org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + if (!player.getAbilities().instabuild) { + stack.shrink(1); + } + for (int i = 0; i < 15; ++i) { + ((ServerLevel) level()).sendParticles(((ServerLevel) level()).players(), null, ParticleTypes.HAPPY_VILLAGER, + getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1, + random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true); + } + return InteractionResult.SUCCESS; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java index 45646c69ea73936a8916756fde37dd3f39db0d04..61bb29de8f1eaa833db95fcc38ab6e18c1a2243c 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java +++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java @@ -83,19 +83,104 @@ public class Dolphin extends WaterAnimal { public static final Predicate ALLOWED_ITEMS = (entityitem) -> { return !entityitem.hasPickUpDelay() && entityitem.isAlive() && entityitem.isInWater(); }; + private boolean isNaturallyAggressiveToPlayers; // Purpur + private int spitCooldown; // Purpur public Dolphin(EntityType type, Level world) { super(type, world); - this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); + // Purpur start + class DolphinMoveControl extends SmoothSwimmingMoveControl { + private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterMoveControllerWASD; + private final Dolphin dolphin; + + public DolphinMoveControl(Dolphin dolphin, int pitchChange, int yawChange, float speedInWater, float speedInAir, boolean buoyant) { + super(dolphin, pitchChange, yawChange, speedInWater, speedInAir, buoyant); + this.dolphin = dolphin; + this.waterMoveControllerWASD = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(dolphin); + } + + @Override + public void tick() { + if (dolphin.getRider() != null && dolphin.isControllable()) { + purpurTick(dolphin.getRider()); + } else { + super.tick(); + } + } + + public void purpurTick(Player rider) { + if (dolphin.getAirSupply() < 150) { + // if drowning override player WASD controls to find air + super.tick(); + } else { + waterMoveControllerWASD.purpurTick(rider); + dolphin.setDeltaMovement(dolphin.getDeltaMovement().add(0.0D, 0.005D, 0.0D)); + } + } + }; + this.moveControl = new DolphinMoveControl(this, 85, 10, 0.02F, 0.1F, true); + // Purpur end this.lookControl = new SmoothSwimmingLookControl(this, 10); this.setCanPickUpLoot(true); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.dolphinRidable; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.dolphinControllable; + } + + @Override + public boolean onSpacebar() { + if (spitCooldown == 0 && getRider() != null) { + spitCooldown = level().purpurConfig.dolphinSpitCooldown; + + org.bukkit.craftbukkit.entity.CraftPlayer player = (org.bukkit.craftbukkit.entity.CraftPlayer) getRider().getBukkitEntity(); + if (!player.hasPermission("allow.special.dolphin")) { + return false; + } + + org.bukkit.Location loc = player.getEyeLocation(); + loc.setPitch(loc.getPitch() - 10); + org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(10).add(loc.toVector()); + + org.purpurmc.purpur.entity.DolphinSpit spit = new org.purpurmc.purpur.entity.DolphinSpit(level(), this); + spit.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), level().purpurConfig.dolphinSpitSpeed, 5.0F); + + level().addFreshEntity(spit); + playSound(SoundEvents.DOLPHIN_ATTACK, 1.0F, 1.0F + (random.nextFloat() - random.nextFloat()) * 0.2F); + return true; + } + return false; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.dolphinMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.dolphinTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.dolphinAlwaysDropExp; + } + @Nullable @Override public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) { this.setAirSupply(this.getMaxAirSupply()); this.setXRot(0.0F); + this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance; // Purpur return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt); } @@ -160,17 +245,21 @@ public class Dolphin extends WaterAnimal { protected void registerGoals() { this.goalSelector.addGoal(0, new BreathAirGoal(this)); this.goalSelector.addGoal(0, new TryFindWaterGoal(this)); + this.goalSelector.addGoal(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() { @@ -221,7 +310,7 @@ public class Dolphin extends WaterAnimal { @Override protected boolean canRide(Entity entity) { - return true; + return boardingCooldown <= 0; // Purpur - make dolphin honor ride cooldown like all other non-boss mobs; } @Override @@ -256,6 +345,11 @@ public class Dolphin extends WaterAnimal { @Override public void tick() { super.tick(); + // Purpur start + if (spitCooldown > 0) { + spitCooldown--; + } + // Purpur end if (this.isNoAi()) { this.setAirSupply(this.getMaxAirSupply()); } else { @@ -401,6 +495,7 @@ public class Dolphin extends WaterAnimal { @Override public boolean canUse() { + if (this.dolphin.level().purpurConfig.dolphinDisableTreasureSearching) return false; // Purpur return this.dolphin.gotFish() && this.dolphin.getAirSupply() >= 100; } 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 f7a7810fdc2f74b79fa14470493485e4b74539ab..445c1993a18da93e89792b7953e5eb71777c7874 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Fox.java +++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java @@ -36,6 +36,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; @@ -148,6 +149,64 @@ public class Fox extends Animal implements VariantHolder { this.setCanPickUpLoot(true); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.foxRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.foxRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.foxControllable; + } + + @Override + public float getJumpPower() { + return getRider() != null && this.isControllable() ? 0.5F : super.getJumpPower(); + } + + @Override + public void onMount(Player rider) { + super.onMount(rider); + setCanPickUpLoot(false); + clearStates(); + setIsPouncing(false); + spitOutItem(getItemBySlot(EquipmentSlot.MAINHAND)); + setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY); + } + + @Override + public void onDismount(Player rider) { + super.onDismount(rider); + setCanPickUpLoot(true); + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.foxMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.foxBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.foxTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.foxAlwaysDropExp; + } + @Override protected void defineSynchedData() { super.defineSynchedData(); @@ -167,6 +226,7 @@ public class Fox extends Animal implements VariantHolder { return entityliving instanceof AbstractSchoolingFish; }); this.goalSelector.addGoal(0, new Fox.FoxFloatGoal()); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur 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)); @@ -193,6 +253,7 @@ public class Fox extends Animal implements VariantHolder { this.goalSelector.addGoal(11, new Fox.FoxSearchForItemsGoal()); this.goalSelector.addGoal(12, new Fox.FoxLookAtPlayerGoal(this, Player.class, 24.0F)); this.goalSelector.addGoal(13, new Fox.PerchAndSearchGoal()); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(3, new Fox.DefendTrustedTargetGoal(LivingEntity.class, false, false, (entityliving) -> { return Fox.TRUSTED_TARGET_SELECTOR.test(entityliving) && !this.trusts(entityliving.getUUID()); })); @@ -349,6 +410,11 @@ public class Fox extends Animal implements VariantHolder { } private void setTargetGoals() { + // Purpur start - do not add duplicate goals + this.targetSelector.removeGoal(this.landTargetGoal); + this.targetSelector.removeGoal(this.turtleEggTargetGoal); + this.targetSelector.removeGoal(this.fishTargetGoal); + // Purpur end if (this.getVariant() == Fox.Type.RED) { this.targetSelector.addGoal(4, this.landTargetGoal); this.targetSelector.addGoal(4, this.turtleEggTargetGoal); @@ -382,6 +448,7 @@ public class Fox extends Animal implements VariantHolder { public void setVariant(Fox.Type variant) { this.entityData.set(Fox.DATA_TYPE_ID, variant.getId()); + this.setTargetGoals(); // Purpur - fix API bug not updating pathfinders on type change } List getTrustedUUIDs() { @@ -728,6 +795,29 @@ public class Fox extends Animal implements VariantHolder { } // Paper end + // 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) { @@ -785,16 +875,16 @@ public class Fox extends Animal implements VariantHolder { return new Vec3(0.0D, (double) (0.55F * this.getEyeHeight()), (double) (this.getBbWidth() * 0.4F)); } - public class FoxLookControl extends LookControl { + public class FoxLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur public FoxLookControl() { super(Fox.this); } @Override - public void tick() { + public void vanillaTick() { // Purpur if (!Fox.this.isSleeping()) { - super.tick(); + super.vanillaTick(); // Purpur } } @@ -805,16 +895,16 @@ public class Fox extends Animal implements VariantHolder { } } - private class FoxMoveControl extends MoveControl { + private class FoxMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur public FoxMoveControl() { super(Fox.this); } @Override - public void tick() { + public void vanillaTick() { // Purpur if (Fox.this.canMove()) { - super.tick(); + super.vanillaTick(); // Purpur } } @@ -932,8 +1022,10 @@ public class Fox extends Animal implements VariantHolder { CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer2, this.animal, this.partner, entityfox); } - this.animal.setAge(6000); - this.partner.setAge(6000); + // Purpur start + this.animal.setAge(this.animal.getPurpurBreedTime()); + this.partner.setAge(this.partner.getPurpurBreedTime()); + // Purpur end this.animal.resetLove(); this.partner.resetLove(); worldserver.addFreshEntityWithPassengers(entityfox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason @@ -1319,7 +1411,7 @@ public class Fox extends Animal implements VariantHolder { } protected void onReachedTarget() { - if (Fox.this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + if (Fox.this.level().purpurConfig.foxBypassMobGriefing || Fox.this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur BlockState iblockdata = Fox.this.level().getBlockState(this.blockPos); if (iblockdata.is(Blocks.SWEET_BERRY_BUSH)) { diff --git a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java index 6cfe0d6c46caa122db107c607d27a2bdcd82f7a8..442eb602f5c82550a87e218e2013171b718abd62 100644 --- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java +++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java @@ -60,14 +60,59 @@ public class IronGolem extends AbstractGolem implements NeutralMob { private int remainingPersistentAngerTime; @Nullable private UUID persistentAngerTarget; + @Nullable private UUID summoner; // Purpur public IronGolem(EntityType type, Level world) { super(type, world); this.setMaxUpStep(1.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.ironGolemRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ironGolemRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.ironGolemControllable; + } + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ironGolemMaxHealth); + } + // Purpur end + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.ironGolemTakeDamageFromWater; + } + + @Nullable + public UUID getSummoner() { + return summoner; + } + + public void setSummoner(@Nullable UUID summoner) { + this.summoner = summoner; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.ironGolemAlwaysDropExp; + } + @Override protected void registerGoals() { + if (level().purpurConfig.ironGolemCanSwim) this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur + if (this.level().purpurConfig.ironGolemPoppyCalm) this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.ReceiveFlower(this)); // Purpur + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0D, true)); this.goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9D, 32.0F)); this.goalSelector.addGoal(2, new MoveBackToVillageGoal(this, 0.6D, false)); @@ -75,6 +120,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { this.goalSelector.addGoal(5, new OfferFlowerGoal(this)); this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new DefendVillageTargetGoal(this)); this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); @@ -139,6 +185,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { public void addAdditionalSaveData(CompoundTag nbt) { super.addAdditionalSaveData(nbt); nbt.putBoolean("PlayerCreated", this.isPlayerCreated()); + if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur this.addPersistentAngerSaveData(nbt); } @@ -146,6 +193,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { public void readAdditionalSaveData(CompoundTag nbt) { super.readAdditionalSaveData(nbt); this.setPlayerCreated(nbt.getBoolean("PlayerCreated")); + if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur this.readPersistentAngerSaveData(this.level(), nbt); } @@ -270,13 +318,13 @@ public class IronGolem extends AbstractGolem implements NeutralMob { ItemStack itemstack = player.getItemInHand(hand); if (!itemstack.is(Items.IRON_INGOT)) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } else { float f = this.getHealth(); this.heal(25.0F); if (this.getHealth() == f) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } else { float f1 = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F; @@ -285,6 +333,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob { itemstack.shrink(1); } + if (this.level().purpurConfig.ironGolemHealCalm && isAngry() && getHealth() == getMaxHealth()) stopBeingAngry(); // Purpur return InteractionResult.sidedSuccess(this.level().isClientSide); } } diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java index 6659abb4ab8a13a48c154d2e3f273eb13b202e7f..560981601c6a43fee99372eafd7bd746798845a6 100644 --- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java +++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java @@ -64,6 +64,43 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder drops = this.generateDefaultDrops(); + List drops = this.generateDefaultDrops(net.minecraft.world.item.enchantment.EnchantmentHelper.getMobLooting(player)); // Purpur org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); if (event != null) { if (event.isCancelled()) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); } @@ -152,7 +189,7 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder> optional = this.getEffectsFromItemStack(itemstack); if (optional.isEmpty()) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } if (!player.getAbilities().instabuild) { @@ -176,13 +213,13 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder generateDefaultDrops() { + public List generateDefaultDrops(int looting) { // Purpur List dropEntities = new java.util.ArrayList<>(5); - for (int i = 0; i < 5; ++i) { + for (int i = 0; i < 5 + (org.purpurmc.purpur.PurpurConfig.allowShearsLooting ? looting : 0); ++i) { // Purpur dropEntities.add(new ItemStack(this.getVariant().getBlockState().getBlock())); } return dropEntities; 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 4300fab61765dd224fab084d118aae7294fc9de6..3c5f25300d1c7800144a459cc8bf598352a62a35 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Ocelot.java +++ b/src/main/java/net/minecraft/world/entity/animal/Ocelot.java @@ -70,6 +70,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); } @@ -101,12 +138,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)); } @@ -256,7 +295,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 d683c49fdf2d1e5b0f2620641f9c241e82f96825..adfa18c941b5070692ed855d1d609993ca49a01d 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Panda.java +++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java @@ -115,6 +115,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); @@ -276,6 +323,7 @@ public class Panda extends Animal { @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur 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)); @@ -291,6 +339,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])); } @@ -614,7 +663,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()) { @@ -637,7 +689,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); @@ -655,7 +707,7 @@ public class Panda extends Animal { this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying } else { if (this.level().isClientSide || this.isSitting() || this.isInWater()) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } this.tryToSit(); @@ -674,7 +726,7 @@ public class Panda extends Animal { return InteractionResult.SUCCESS; } else { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } } @@ -719,7 +771,7 @@ public class Panda extends Animal { return new Vector3f(0.0F, dimensions.height - (this.isBaby() ? 0.4375F : 0.0F) * scaleFactor, 0.0F); } - private static class PandaMoveControl extends MoveControl { + private static class PandaMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur private final Panda panda; @@ -729,9 +781,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 f3f48225c2a1e4bd3d0091d1b4b7e4e150850ed2..670cf5a74554b0b420706db2fbce3a8e5dca147b 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java +++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java @@ -131,12 +131,88 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, Level world) { super(type, world); - this.moveControl = new FlyingMoveControl(this, 10, false); + // Purpur start + final org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); + class ParrotMoveControl extends FlyingMoveControl { + public ParrotMoveControl(Mob entity, int maxPitchChange, boolean noGravity) { + super(entity, maxPitchChange, noGravity); + } + + @Override + public void tick() { + if (mob.getRider() != null && mob.isControllable()) { + flyingController.purpurTick(mob.getRider()); + } else { + super.tick(); + } + } + + @Override + public boolean hasWanted() { + return mob.getRider() != null && mob.isControllable() ? getForwardMot() != 0 || getStrafeMot() != 0 : super.hasWanted(); + } + } + this.moveControl = new ParrotMoveControl(this, 10, false); + // Purpur end this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F); this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, -1.0F); this.setPathfindingMalus(BlockPathTypes.COCOA, -1.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.parrotRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.parrotRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.parrotControllable; + } + + @Override + public double getMaxY() { + return level().purpurConfig.parrotMaxY; + } + + @Override + public void travel(Vec3 vec3) { + super.travel(vec3); + if (getRider() != null && this.isControllable() && !onGround) { + float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2; + setSpeed(speed); + Vec3 mot = getDeltaMovement(); + move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.25, speed)); + setDeltaMovement(mot.scale(0.9D)); + } + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.parrotMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return 6000; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.parrotTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.parrotAlwaysDropExp; + } + @Nullable @Override public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) { @@ -155,8 +231,11 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { @@ -308,13 +388,13 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, Level world) { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.polarBearRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.polarBearRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.polarBearControllable; + } + + @Override + public boolean onSpacebar() { + if (!isStanding()) { + if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0) { + setStanding(true); + playSound(SoundEvents.POLAR_BEAR_WARNING, 1.0F, 1.0F); + } + } + return false; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.polarBearMaxHealth); + } + + public boolean canMate(Animal other) { + if (other == this) { + return false; + } else if (this.isStanding()) { + return false; + } else if (this.getTarget() != null) { + return false; + } else if (!(other instanceof PolarBear)) { + return false; + } else { + PolarBear bear = (PolarBear) other; + if (bear.isStanding()) { + return false; + } + if (bear.getTarget() != null) { + return false; + } + return this.isInLove() && bear.isInLove(); + } + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.polarBearBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.polarBearTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.polarBearAlwaysDropExp; + } + @Nullable @Override public AgeableMob getBreedOffspring(ServerLevel world, AgeableMob entity) { @@ -73,19 +143,27 @@ public class PolarBear extends Animal implements NeutralMob { @Override public boolean isFood(ItemStack stack) { - return false; + return level().purpurConfig.polarBearBreedableItem != null && stack.getItem() == level().purpurConfig.polarBearBreedableItem; // Purpur } @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new PolarBear.PolarBearMeleeAttackGoal()); this.goalSelector.addGoal(1, new PolarBear.PolarBearPanicGoal()); + // Purpur start + if (level().purpurConfig.polarBearBreedableItem != null) { + this.goalSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.BreedGoal(this, 1.0D)); + this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, net.minecraft.world.item.crafting.Ingredient.of(level().purpurConfig.polarBearBreedableItem), false)); + } + // Purpur end this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25D)); this.goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(7, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new PolarBear.PolarBearHurtByTargetGoal()); this.targetSelector.addGoal(2, new PolarBear.PolarBearAttackPlayersGoal()); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt)); @@ -202,6 +280,11 @@ public class PolarBear extends Animal implements NeutralMob { this.updatePersistentAnger((ServerLevel)this.level(), true); } + // Purpur start + if (isStanding() && --standTimer <= 0) { + setStanding(false); + } + // Purpur end } @Override @@ -231,6 +314,7 @@ public class PolarBear extends Animal implements NeutralMob { public void setStanding(boolean warning) { this.entityData.set(DATA_STANDING_ID, warning); + standTimer = warning ? 20 : -1; // Purpur } public float getStandingAnimationScale(float tickDelta) { diff --git a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java index a197337f2e09f53cf382022569c8836745d78769..54f5206b686c3cf4d2e5b470c07047a518f5dd00 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java +++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java @@ -45,6 +45,33 @@ public class Pufferfish extends AbstractFish { this.refreshDimensions(); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.pufferfishRidable; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.pufferfishControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pufferfishMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.pufferfishTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.pufferfishAlwaysDropExp; + } + @Override protected void defineSynchedData() { super.defineSynchedData(); diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java index cf4859814a60468f683e3afe285b4934d35e9704..eae2488f2a46e543b496b7a2919aabbb55dcb825 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java +++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java @@ -87,6 +87,7 @@ public class Rabbit extends Animal implements VariantHolder { private boolean wasOnGround; private int jumpDelayTicks; public int moreCarrotTicks; + private boolean actualJump; // Purpur public Rabbit(EntityType type, Level world) { super(type, world); @@ -94,9 +95,75 @@ public class Rabbit extends Animal implements VariantHolder { this.moveControl = new Rabbit.RabbitMoveControl(this); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.rabbitRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.rabbitRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.rabbitControllable; + } + + @Override + public boolean onSpacebar() { + if (onGround) { + actualJump = true; + jumpFromGround(); + actualJump = false; + } + return true; + } + + private void handleJumping() { + if (onGround) { + RabbitJumpControl jumpController = (RabbitJumpControl) jumpControl; + if (!wasOnGround) { + setJumping(false); + jumpController.setCanJump(false); + } + if (!jumpController.wantJump()) { + if (moveControl.hasWanted()) { + startJumping(); + } + } else if (!jumpController.canJump()) { + jumpController.setCanJump(true); + } + } + wasOnGround = onGround; + } + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.rabbitMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.rabbitBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.rabbitTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.rabbitAlwaysDropExp; + } + // Purpur end + @Override public void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level())); this.goalSelector.addGoal(1, new Rabbit.RabbitPanicGoal(this, 2.2D)); this.goalSelector.addGoal(2, new BreedGoal(this, 0.8D)); @@ -111,6 +178,14 @@ public class Rabbit extends Animal implements VariantHolder { @Override protected float getJumpPower() { + // Purpur start + if (getRider() != null && this.isControllable()) { + if (getForwardMot() < 0) { + setSpeed(getForwardMot() * 2F); + } + return actualJump ? 0.5F : 0.3F; + } + // Purpur end float f = 0.3F; if (this.horizontalCollision || this.moveControl.hasWanted() && this.moveControl.getWantedY() > this.getY() + 0.5D) { @@ -135,7 +210,7 @@ public class Rabbit extends Animal implements VariantHolder { } @Override - protected void jumpFromGround() { + public void jumpFromGround() { // Purpur - protected -> public super.jumpFromGround(); double d0 = this.moveControl.getSpeedModifier(); @@ -185,6 +260,13 @@ public class Rabbit extends Animal implements VariantHolder { @Override public void customServerAiStep() { + // Purpur start + if (getRider() != null && this.isControllable()) { + handleJumping(); + return; + } + // Purpur end + if (this.jumpDelayTicks > 0) { --this.jumpDelayTicks; } @@ -402,10 +484,23 @@ public class Rabbit extends Animal implements VariantHolder { } this.setVariant(entityrabbit_variant); + + // Purpur start + if (entityrabbit_variant != Variant.EVIL && world.getLevel().purpurConfig.rabbitNaturalToast > 0D && random.nextDouble() <= world.getLevel().purpurConfig.rabbitNaturalToast) { + setCustomName(Component.translatable("Toast")); + } + // Purpur end + return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData, entityNbt); } private static Rabbit.Variant getRandomRabbitVariant(LevelAccessor world, BlockPos pos) { + // Purpur start + Level level = world.getMinecraftWorld(); + if (level.purpurConfig.rabbitNaturalKiller > 0D && world.getRandom().nextDouble() <= level.purpurConfig.rabbitNaturalKiller) { + return Rabbit.Variant.EVIL; + } + // Purpur end Holder holder = world.getBiome(pos); int i = world.getRandom().nextInt(100); @@ -469,7 +564,7 @@ public class Rabbit extends Animal implements VariantHolder { } } - private static class RabbitMoveControl extends MoveControl { + private static class RabbitMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur private final Rabbit rabbit; private double nextJumpSpeed; @@ -480,14 +575,14 @@ public class Rabbit extends Animal implements VariantHolder { } @Override - public void tick() { + public void vanillaTick() { // Purpur if (this.rabbit.onGround() && !this.rabbit.jumping && !((Rabbit.RabbitJumpControl) this.rabbit.jumpControl).wantJump()) { this.rabbit.setSpeedModifier(0.0D); } else if (this.hasWanted()) { this.rabbit.setSpeedModifier(this.nextJumpSpeed); } - super.tick(); + super.vanillaTick(); // Purpur } @Override @@ -549,7 +644,7 @@ public class Rabbit extends Animal implements VariantHolder { @Override public boolean canUse() { if (this.nextStartTick <= 0) { - if (!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 865f244de1605303f22d8944174b0fe00115cab0..981429215b01ae1f03735d5ffc584108013b137e 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Sheep.java +++ b/src/main/java/net/minecraft/world/entity/animal/Sheep.java @@ -119,10 +119,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)); @@ -254,7 +292,7 @@ public class Sheep extends Animal implements Shearable { if (!this.level().isClientSide && this.readyForShearing()) { // CraftBukkit start // Paper start - custom shear drops - java.util.List drops = this.generateDefaultDrops(); + java.util.List drops = this.generateDefaultDrops(net.minecraft.world.item.enchantment.EnchantmentHelper.getMobLooting(player)); // Purpur org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); if (event != null) { if (event.isCancelled()) { @@ -281,12 +319,13 @@ public class Sheep extends Animal implements Shearable { @Override public void shear(SoundSource shearedSoundCategory) { // Paper start - custom shear drops - this.shear(shearedSoundCategory, this.generateDefaultDrops()); + this.shear(shearedSoundCategory, this.generateDefaultDrops(0)); // Purpur } @Override - public java.util.List generateDefaultDrops() { + public java.util.List generateDefaultDrops(int looting) { // Purpur int count = 1 + this.random.nextInt(3); + if (org.purpurmc.purpur.PurpurConfig.allowShearsLooting) count += looting; // Purpur java.util.List dropEntities = new java.util.ArrayList<>(count); for (int j = 0; j < count; ++j) { dropEntities.add(new ItemStack(Sheep.ITEM_BY_DYE.get(this.getColor()))); 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 9eab1170cb123d3b60a02314702516704f959ab7..a4f5e54a04311243bf804f579a7ed028bac22031 100644 --- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java +++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java @@ -49,17 +49,56 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM private static final EntityDataAccessor DATA_PUMPKIN_ID = SynchedEntityData.defineId(SnowGolem.class, EntityDataSerializers.BYTE); private static final byte PUMPKIN_FLAG = 16; private static final float EYE_HEIGHT = 1.7F; + @Nullable private java.util.UUID summoner; // Purpur public SnowGolem(EntityType type, Level world) { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.snowGolemRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.snowGolemRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.snowGolemControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.snowGolemMaxHealth); + } + + @Nullable + public java.util.UUID getSummoner() { + return summoner; + } + + public void setSummoner(@Nullable java.util.UUID summoner) { + this.summoner = summoner; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.snowGolemAlwaysDropExp; + } + @Override protected void registerGoals() { - this.goalSelector.addGoal(1, new RangedAttackGoal(this, 1.25D, 20, 10.0F)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + this.goalSelector.addGoal(1, new RangedAttackGoal(this, level().purpurConfig.snowGolemAttackDistance, level().purpurConfig.snowGolemSnowBallMin, level().purpurConfig.snowGolemSnowBallMax, level().purpurConfig.snowGolemSnowBallModifier)); // Purpur this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D, 1.0000001E-5F)); this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(4, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Mob.class, 10, true, false, (entityliving) -> { return entityliving instanceof Enemy; })); @@ -79,6 +118,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM public void addAdditionalSaveData(CompoundTag nbt) { super.addAdditionalSaveData(nbt); nbt.putBoolean("Pumpkin", this.hasPumpkin()); + if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur } @Override @@ -87,12 +127,13 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM if (nbt.contains("Pumpkin")) { this.setPumpkin(nbt.getBoolean("Pumpkin")); } + if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur } @Override public boolean isSensitiveToWater() { - return true; + return this.level().purpurConfig.snowGolemTakeDamageFromWater; // Purpur } @Override @@ -103,10 +144,11 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM this.hurt(this.damageSources().melting(), 1.0F); // CraftBukkit - DamageSources.ON_FIRE -> 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,11 +196,11 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM if (itemstack.is(Items.SHEARS) && this.readyForShearing()) { // CraftBukkit start // Paper start - custom shear drops - java.util.List drops = this.generateDefaultDrops(); + java.util.List drops = this.generateDefaultDrops(net.minecraft.world.item.enchantment.EnchantmentHelper.getMobLooting(player)); // Purpur org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops); if (event != null) { if (event.isCancelled()) { - return InteractionResult.PASS; + return tryRide(player, hand); // Purpur } drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops()); } @@ -173,19 +215,36 @@ 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) { // Paper start - custom shear drops - this.shear(shearedSoundCategory, this.generateDefaultDrops()); + this.shear(shearedSoundCategory, this.generateDefaultDrops(0)); // Purpur } @Override - public java.util.List generateDefaultDrops() { + // Purpur start + public java.util.List generateDefaultDrops(int looting) { + if (org.purpurmc.purpur.PurpurConfig.allowShearsLooting) { + java.util.ArrayList list = new java.util.ArrayList<>(); + for (int i = 0; i < 1 + looting; i++) { + list.add(new ItemStack(Items.CARVED_PUMPKIN)); + } + return java.util.Collections.unmodifiableList(list); + } + // Purpur end return java.util.Collections.singletonList(new ItemStack(Items.CARVED_PUMPKIN)); } 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 4f32597c7af34d599f6658fe4962d41624e60419..cba0823bfd10a3e7fd3a7548b9cc11a77ce07ce7 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Squid.java +++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java @@ -44,13 +44,66 @@ public class Squid extends WaterAnimal { public Squid(EntityType type, Level world) { super(type, world); - //this.random.setSeed((long)this.getId()); // Paper - Share random for entities to make them more random + if (!world.purpurConfig.entitySharedRandom) this.random.setSeed((long) this.getId()); // Paper - Share random for entities to make them more random // Purpur this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.squidRidable; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.squidControllable; + } + + protected void rotateVectorAroundY(org.bukkit.util.Vector vector, double degrees) { + double rad = Math.toRadians(degrees); + double cos = Math.cos(rad); + double sine = Math.sin(rad); + double x = vector.getX(); + double z = vector.getZ(); + vector.setX(cos * x - sine * z); + vector.setZ(sine * x + cos * z); + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.squidMaxHealth); + } + + @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 dbdb6c432448b151fa4421f14235f8bad23dc720..6bf8ed96eaed4a500164f21cb5ead32a7e5a224d 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java +++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java @@ -86,6 +86,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... } @@ -188,6 +225,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)); @@ -344,13 +382,15 @@ public class Turtle extends Animal { return new Vector3f(0.0F, dimensions.height + (this.isBaby() ? 0.0F : 0.15625F) * scaleFactor, -0.25F * scaleFactor); } - 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() { @@ -370,7 +410,7 @@ public class Turtle extends Animal { } @Override - public void tick() { + public void vanillaTick() { // Purpur this.updateSpeed(); if (this.operation == MoveControl.Operation.MOVE_TO && !this.turtle.getNavigation().isDone()) { double d0 = this.wantedX - this.turtle.getX(); @@ -386,7 +426,7 @@ public class Turtle extends Animal { this.turtle.setYRot(this.rotlerp(this.turtle.getYRot(), f, 90.0F)); this.turtle.yBodyRot = this.turtle.getYRot(); - float f1 = (float) (this.speedModifier * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); + float f1 = (float) (this.getSpeedModifier() * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED)); this.turtle.setSpeed(Mth.lerp(0.125F, this.turtle.getSpeed(), f1)); this.turtle.setDeltaMovement(this.turtle.getDeltaMovement().add(0.0D, (double) this.turtle.getSpeed() * d1 * 0.1D, 0.0D)); diff --git a/src/main/java/net/minecraft/world/entity/animal/Wolf.java b/src/main/java/net/minecraft/world/entity/animal/Wolf.java index 2d20b2c1f58beb1ad8c9012d8124e476899e6be6..a90055fe8819a32180754b6060a0f88e81d1a3b6 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; @@ -86,6 +93,37 @@ public class Wolf extends TamableAnimal implements NeutralMob { return entitytypes == EntityType.SHEEP || entitytypes == EntityType.RABBIT || entitytypes == EntityType.FOX; }; + // Purpur start - rabid wolf spawn chance + private boolean isRabid = false; + private static final Predicate RABID_PREDICATE = entity -> entity instanceof ServerPlayer || entity instanceof Mob; + private final Goal PATHFINDER_VANILLA = new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR); + private final Goal PATHFINDER_RABID = new NonTameRandomTargetGoal<>(this, LivingEntity.class, false, RABID_PREDICATE); + private static final class AvoidRabidWolfGoal extends AvoidEntityGoal { + private final Wolf wolf; + + public AvoidRabidWolfGoal(Wolf wolf, float distance, double minSpeed, double maxSpeed) { + super(wolf, Wolf.class, distance, minSpeed, maxSpeed); + this.wolf = wolf; + } + + @Override + public boolean canUse() { + return super.canUse() && !this.wolf.isRabid() && this.toAvoid != null && this.toAvoid.isRabid(); // wolves which are not rabid run away from rabid wolves + } + + @Override + public void start() { + this.wolf.setTarget(null); + super.start(); + } + + @Override + public void tick() { + this.wolf.setTarget(null); + super.tick(); + } + } + // Purpur end private static final float START_HEALTH = 8.0F; private static final float TAME_HEALTH = 20.0F; private float interestedAngle; @@ -105,12 +143,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)); @@ -119,11 +238,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)); @@ -150,6 +270,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); } @@ -159,6 +280,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); } @@ -203,6 +328,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; @@ -407,7 +537,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); @@ -418,6 +548,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 c521ff04be40bfa892021f67acc1b324551fcd5e..eb60d2d99155aae4a761051175fbbddf9ed5dad9 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 @@ -100,10 +100,23 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS private float spinningAnimationTicks; private float spinningAnimationTicks0; public boolean forceDancing = false; // CraftBukkit + private org.purpurmc.purpur.controller.FlyingMoveControllerWASD purpurController; // Purpur public Allay(EntityType type, Level world) { super(type, world); - this.moveControl = new FlyingMoveControl(this, 20, true); + // Purpur start + this.purpurController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.1F, 0.5F); + this.moveControl = new FlyingMoveControl(this, 20, true) { + @Override + public void tick() { + if (mob.getRider() != null && mob.isControllable()) { + purpurController.purpurTick(mob.getRider()); + } else { + super.tick(); + } + } + }; + // Purpur end this.setCanPickUpLoot(this.canPickUpLoot()); this.vibrationUser = new Allay.VibrationUser(); this.vibrationData = new VibrationSystem.Data(); @@ -117,6 +130,28 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS } // CraftBukkit end + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.allayRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.allayRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.allayControllable; + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + } + // Purpur end + @Override protected Brain.Provider brainProvider() { return Brain.provider(Allay.MEMORY_TYPES, Allay.SENSOR_TYPES); @@ -224,7 +259,7 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep() { - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel) this.level(), this); AllayAi.updateActivity(this); super.customServerAiStep(); @@ -367,9 +402,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 d0c624a6a000c2a41e41d14dd785a7bf9612afe8..bcc49ca8afa9794952883098a586d0e1b89d04d5 100644 --- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java +++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java @@ -98,6 +98,43 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder getModelRotationValues() { return this.modelRotationValues; @@ -278,7 +315,7 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder { public final AnimationState croakAnimationState = new AnimationState(); public final AnimationState tongueAnimationState = new AnimationState(); public final AnimationState swimIdleAnimationState = new AnimationState(); + private org.purpurmc.purpur.controller.MoveControllerWASD purpurLandController; // Purpur + private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurWaterController; // Purpur public Frog(EntityType type, Level world) { super(type, world); this.lookControl = new Frog.FrogLookControl(this); this.setPathfindingMalus(BlockPathTypes.WATER, 4.0F); this.setPathfindingMalus(BlockPathTypes.TRAPDOOR, -1.0F); - this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); + // Purpur start + this.purpurLandController = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.2F); + this.purpurWaterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F); + this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) { + @Override + public void tick() { + net.minecraft.world.entity.player.Player rider = mob.getRider(); + if (rider != null && mob.isControllable()) { + if (mob.isInWater()) { + purpurWaterController.purpurTick(rider); + mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, -0.005D, 0.0D)); + } else { + purpurLandController.purpurTick(rider); + } + } else { + super.tick(); + } + } + }; + // Purpur end this.setMaxUpStep(1.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.frogRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.frogRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.frogControllable; + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + } + + @Override + public float getJumpPower() { + return (getRider() != null && isControllable()) ? level().purpurConfig.frogRidableJumpHeight * this.getBlockJumpFactor() : super.getJumpPower(); + } + // Purpur end + + public int getPurpurBreedTime() { + return this.level().purpurConfig.frogBreedingTicks; + } + @Override protected Brain.Provider brainProvider() { return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); @@ -162,7 +215,7 @@ public class Frog extends Animal implements VariantHolder { private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep() { - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel)this.level(), this); FrogAi.updateActivity(this); super.customServerAiStep(); @@ -345,7 +398,7 @@ public class Frog extends Animal implements VariantHolder { return world.getBlockState(pos.below()).is(BlockTags.FROGS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos); } - class FrogLookControl extends LookControl { + class FrogLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur FrogLookControl(Mob entity) { super(entity); } diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java index 257687004b03e17cf3f5c0ea4c4cfeb7f34033e4..93e321a0b8fdce12968f03b3cfe6724ae564b823 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 @@ -48,13 +48,50 @@ public class Tadpole extends AbstractFish { protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS); protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING); public boolean ageLocked; // Paper + private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurController; // Purpur public Tadpole(EntityType type, Level world) { super(type, world); - this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true); + // Purpur start + this.purpurController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F); + this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) { + @Override + public void tick() { + Player rider = mob.getRider(); + if (rider != null && mob.isControllable()) { + purpurController.purpurTick(rider); + mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, 0.002D, 0.0D)); + } else { + super.tick(); + } + } + }; + // Purpur end this.lookControl = new SmoothSwimmingLookControl(this, 10); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.tadpoleRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.tadpoleRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.tadpoleControllable; + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + } + // Purpur end + @Override protected PathNavigation createNavigation(Level world) { return new WaterBoundPathNavigation(this, world); @@ -83,8 +120,8 @@ public class Tadpole extends AbstractFish { private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep() { - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish - this.getBrain().tick((ServerLevel) this.level(), this); + 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); // Dreeam TODO - should remove this? TadpoleAi.updateActivity(this); super.customServerAiStep(); } diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java index d75178611f2c74af45e39c9e37770e2c56773b1d..af81fa69336d21408de3703def803af9e303be4a 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 @@ -92,6 +92,38 @@ public class Goat extends Animal { return InstrumentItem.create(Items.GOAT_HORN, (Holder) holderset.getRandomElement(randomsource).get()); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.goatRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.goatRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.goatControllable; + } + // Purpur end + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.goatBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.goatTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.goatAlwaysDropExp; + } + @Override protected Brain.Provider brainProvider() { return Brain.provider(Goat.MEMORY_TYPES, Goat.SENSOR_TYPES); @@ -194,7 +226,7 @@ public class Goat extends Animal { private int behaviorTick = 0; // Pufferfish @Override protected void customServerAiStep() { - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel) this.level(), this); GoatAi.updateActivity(this); super.customServerAiStep(); @@ -393,6 +425,7 @@ public class Goat extends Animal { // Paper start - Goat ram API public void ram(net.minecraft.world.entity.LivingEntity entity) { + if(!new org.purpurmc.purpur.event.entity.GoatRamEntityEvent((org.bukkit.entity.Goat) getBukkitEntity(), (org.bukkit.entity.LivingEntity) entity.getBukkitLivingEntity()).callEvent()) return; // Purpur Brain brain = this.getBrain(); brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position()); brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS); diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java index 815eb15086976b8f9e03bf8182d9ed50aec14720..3170f9044f18b8c609433ddbd3ef9ac330644a0f 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 @@ -149,12 +149,60 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, protected AbstractHorse(EntityType type, Level world) { super(type, world); + this.moveControl = new net.minecraft.world.entity.ai.control.MoveControl(this); // Purpur - use vanilla controller + this.lookControl = new net.minecraft.world.entity.ai.control.LookControl(this); // Purpur - use vanilla controller this.setMaxUpStep(1.0F); this.createInventory(); } + // Purpur start + @Override + public boolean isRidable() { + return false; // vanilla handles + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.generateMaxHealth(random)); + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.generateSpeed(random)); + this.getAttribute(Attributes.JUMP_STRENGTH).setBaseValue(this.generateJumpStrength(random)); + } + + protected double generateMaxHealth(double min, double max) { + if (min == max) return min; + int diff = Mth.floor(max - min); + double base = max - diff; + int first = Mth.floor((double) diff / 2); + int rest = diff - first; + return base + random.nextInt(first + 1) + random.nextInt(rest + 1); + } + + protected double generateJumpStrength(double min, double max) { + if (min == max) return min; + return min + (max - min) * this.random.nextDouble(); + } + + protected double generateSpeed(double min, double max) { + if (min == max) return min; + return min + (max - min) * this.random.nextDouble(); + } + + protected float generateMaxHealth(RandomSource random) { + return 15.0F + (float) random.nextInt(8) + (float) random.nextInt(9); + } + + protected double generateJumpStrength(RandomSource random) { + return 0.4000000059604645D + random.nextDouble() * 0.2D + random.nextDouble() * 0.2D + random.nextDouble() * 0.2D; + } + + protected double generateSpeed(RandomSource random) { + return (0.44999998807907104D + random.nextDouble() * 0.3D + random.nextDouble() * 0.3D + random.nextDouble() * 0.3D) * 0.25D; + } + @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur this.goalSelector.addGoal(1, new PanicGoal(this, 1.2D)); this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2D)); this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D, AbstractHorse.class)); @@ -165,6 +213,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(); } @@ -337,7 +386,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() { @@ -1231,7 +1280,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, entityData = new AgeableMob.AgeableMobGroupData(0.2F); } - this.randomizeAttributes(world.getRandom()); + // this.randomizeAttributes(world.getRandom()); // Purpur - replaced by initAttributes() return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData, entityNbt); } diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java index e0dfee0e0ce091d5ae0ec740e939c2c50915c104..72ad12175325091397459e06743875cce6df8d94 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java @@ -15,6 +15,43 @@ public class Donkey extends AbstractChestedHorse { super(type, world); } + // Purpur start + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.donkeyRidableInWater; + } + // Purpur end + + @Override + public float generateMaxHealth(net.minecraft.util.RandomSource random) { + return (float) generateMaxHealth(this.level().purpurConfig.donkeyMaxHealthMin, this.level().purpurConfig.donkeyMaxHealthMax); + } + + @Override + public double generateJumpStrength(net.minecraft.util.RandomSource random) { + return generateJumpStrength(this.level().purpurConfig.donkeyJumpStrengthMin, this.level().purpurConfig.donkeyJumpStrengthMax); + } + + @Override + public double generateSpeed(net.minecraft.util.RandomSource random) { + return generateSpeed(this.level().purpurConfig.donkeyMovementSpeedMin, this.level().purpurConfig.donkeyMovementSpeedMax); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.donkeyBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.donkeyTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.donkeyAlwaysDropExp; + } + @Override protected SoundEvent getAmbientSound() { return SoundEvents.DONKEY_AMBIENT; diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java index 5f5dc651d570989ec1294c31a14dcfede466b80a..3b1faa63e46a48e83ea672cf6da444a1d7e13270 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java @@ -40,6 +40,43 @@ public class Horse extends AbstractHorse implements VariantHolder { super(type, world); } + // Purpur start + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.horseRidableInWater; + } + // Purpur end + + @Override + public float generateMaxHealth(RandomSource random) { + return (float) generateMaxHealth(this.level().purpurConfig.horseMaxHealthMin, this.level().purpurConfig.horseMaxHealthMax); + } + + @Override + public double generateJumpStrength(RandomSource random) { + return generateJumpStrength(this.level().purpurConfig.horseJumpStrengthMin, this.level().purpurConfig.horseJumpStrengthMax); + } + + @Override + public double generateSpeed(RandomSource random) { + return generateSpeed(this.level().purpurConfig.horseMovementSpeedMin, this.level().purpurConfig.horseMovementSpeedMax); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.horseBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.horseTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.horseAlwaysDropExp; + } + @Override protected void randomizeAttributes(RandomSource random) { this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double)generateMaxHealth(random::nextInt)); diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java index 6623674136b0f865d5b3d7a10d3bf05793b82f87..22abcf70f51a6752ab6d3f421366adb196e50dfc 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 @@ -75,9 +75,84 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) { super(type, world); + // Purpur start + this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this) { + @Override + public void tick() { + if (entity.getRider() != null && entity.isControllable() && isSaddled()) { + purpurTick(entity.getRider()); + } else { + vanillaTick(); + } + } + }; + this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) { + @Override + public void tick() { + if (entity.getRider() != null && entity.isControllable() && isSaddled()) { + purpurTick(entity.getRider()); + } else { + vanillaTick(); + } + } + }; + // Purpur end + } + + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.llamaRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.llamaRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.llamaControllable; + } + + @Override + public boolean isSaddled() { + return super.isSaddled() || (isTamed() && getSwag() != null); + } + // Purpur end + + @Override + public float generateMaxHealth(RandomSource random) { + return (float) generateMaxHealth(this.level().purpurConfig.llamaMaxHealthMin, this.level().purpurConfig.llamaMaxHealthMax); + } + + @Override + public double generateJumpStrength(RandomSource random) { + return generateJumpStrength(this.level().purpurConfig.llamaJumpStrengthMin, this.level().purpurConfig.llamaJumpStrengthMax); + } + + @Override + public double generateSpeed(RandomSource random) { + return generateSpeed(this.level().purpurConfig.llamaMovementSpeedMin, this.level().purpurConfig.llamaMovementSpeedMax); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.llamaBreedingTicks; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.llamaTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.llamaAlwaysDropExp; } public boolean isTraderLlama() { @@ -111,7 +186,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder public super.jumpFromGround(); double d0 = this.moveControl.getSpeedModifier(); diff --git a/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java b/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java index 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 036640d49a5e891e9a0f767abe33f1f51d6d4cde..fcca438db945c1c7327cf3a979de01f07fe453ea 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 @@ -31,6 +31,12 @@ public class EndCrystal extends Entity { private static final EntityDataAccessor DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN); public int time; public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals + // Purpur start + private net.minecraft.world.entity.monster.Phantom targetPhantom; + private int phantomBeamTicks = 0; + private int phantomDamageCooldown = 0; + private int idleCooldown = 0; + // Purpur end public EndCrystal(EntityType type, Level world) { super(type, world); @@ -43,6 +49,22 @@ public class EndCrystal extends Entity { this.setPos(x, y, z); } + public boolean shouldExplode() { + return showsBottom() ? level().purpurConfig.basedEndCrystalExplode : level().purpurConfig.baselessEndCrystalExplode; + } + + public float getExplosionPower() { + return (float) (showsBottom() ? level().purpurConfig.basedEndCrystalExplosionPower : level().purpurConfig.baselessEndCrystalExplosionPower); + } + + public boolean hasExplosionFire() { + return showsBottom() ? level().purpurConfig.basedEndCrystalExplosionFire : level().purpurConfig.baselessEndCrystalExplosionFire; + } + + public Level.ExplosionInteraction getExplosionEffect() { + return showsBottom() ? level().purpurConfig.basedEndCrystalExplosionEffect : level().purpurConfig.baselessEndCrystalExplosionEffect; + } + @Override protected Entity.MovementEmission getMovementEmission() { return Entity.MovementEmission.NONE; @@ -78,9 +100,53 @@ public class EndCrystal extends Entity { } } // Paper end - Fix invulnerable end crystals + if (this.level().purpurConfig.endCrystalCramming > 0 && this.level().getEntitiesOfClass(EndCrystal.class, getBoundingBox()).size() > this.level().purpurConfig.endCrystalCramming) this.hurt(this.damageSources().cramming(), 6.0F); // Purpur + } + + // Purpur start + if (level().purpurConfig.phantomAttackedByCrystalRadius <= 0 || --idleCooldown > 0) { + return; // on cooldown + } + + if (targetPhantom == null) { + for (net.minecraft.world.entity.monster.Phantom phantom : level().getEntitiesOfClass(net.minecraft.world.entity.monster.Phantom.class, getBoundingBox().inflate(level().purpurConfig.phantomAttackedByCrystalRadius))) { + if (phantom.hasLineOfSight(this)) { + attackPhantom(phantom); + break; + } + } + } else { + setBeamTarget(new BlockPos(targetPhantom).offset(0, -2, 0)); + if (--phantomBeamTicks > 0 && targetPhantom.isAlive()) { + phantomDamageCooldown--; + if (targetPhantom.hasLineOfSight(this)) { + if (phantomDamageCooldown <= 0) { + phantomDamageCooldown = 20; + targetPhantom.hurt(targetPhantom.damageSources().indirectMagic(this, this), level().purpurConfig.phantomAttackedByCrystalDamage); + } + } else { + forgetPhantom(); // no longer in sight + } + } else { + forgetPhantom(); // attacked long enough + } } + } + + private void attackPhantom(net.minecraft.world.entity.monster.Phantom phantom) { + phantomDamageCooldown = 0; + phantomBeamTicks = 60; + targetPhantom = phantom; + } + private void forgetPhantom() { + targetPhantom = null; + setBeamTarget(null); + phantomBeamTicks = 0; + phantomDamageCooldown = 0; + idleCooldown = 60; } + // Purpur end @Override protected void addAdditionalSaveData(CompoundTag nbt) { @@ -124,16 +190,18 @@ public class EndCrystal extends Entity { } // CraftBukkit end if (!source.is(DamageTypeTags.IS_EXPLOSION)) { + if (shouldExplode()) {// Purpur DamageSource damagesource1 = source.getEntity() != null ? this.damageSources().explosion(this, source.getEntity()) : null; // CraftBukkit start - ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false); + ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, getExplosionPower(), hasExplosionFire()); // Purpur if (event.isCancelled()) { return false; } this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause - 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 + } else this.unsetRemoved(); // Purpur } else { this.remove(Entity.RemovalReason.KILLED, EntityRemoveEvent.Cause.DEATH); // CraftBukkit - add Bukkit remove cause } diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java index 1df13af62af7d0bbd92c84d424a07da66bb8583f..ff4b188084d43af9e8ed60e6a77996018e589807 100644 --- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java @@ -108,6 +108,7 @@ public class EnderDragon extends Mob implements Enemy { @Nullable private BlockPos podium; // Paper end - Allow changing the EnderDragon podium + private boolean hadRider; // Purpur public EnderDragon(EntityType entitytypes, Level world) { super(EntityType.ENDER_DRAGON, world); @@ -130,6 +131,37 @@ public class EnderDragon extends Mob implements Enemy { 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, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE); // 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) { @@ -144,6 +176,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); } @@ -205,6 +258,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()); @@ -231,6 +315,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; @@ -243,9 +329,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; @@ -279,7 +365,7 @@ public class EnderDragon extends Mob implements Enemy { } this.phaseManager.getCurrentPhase().doClientTick(); - } else { + } else if (!hasRider) { // Purpur DragonPhaseInstance idragoncontroller = this.phaseManager.getCurrentPhase(); idragoncontroller.doServerTick(); @@ -348,7 +434,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)); @@ -392,7 +478,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); } @@ -524,7 +610,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; @@ -668,7 +754,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; } @@ -1104,6 +1190,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 12440ee2dccc0a697fb403765f2e1b987ccc0283..736845ca1f7c184b89ddb418339ec3ad22c3447f 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 @@ -85,20 +85,59 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob return entityliving.getMobType() != MobType.UNDEAD && entityliving.attackable(); }; private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR); + @Nullable private java.util.UUID summoner; // Purpur + private int shootCooldown = 0; // Purpur // Paper start private boolean canPortal = false; public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; } // Paper end + private org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD purpurController; // Purpur public WitherBoss(EntityType type, Level world) { super(type, world); this.bossEvent = (ServerBossEvent) (new ServerBossEvent(this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS)).setDarkenScreen(true); - this.moveControl = new FlyingMoveControl(this, 10, false); + // Purpur start + this.purpurController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.1F); + this.moveControl = new FlyingMoveControl(this, 10, false) { + @Override + public void tick() { + if (mob.getRider() != null && mob.isControllable()) { + purpurController.purpurTick(mob.getRider()); + } else { + super.tick(); + } + } + }; + // Purpur end this.setHealth(this.getMaxHealth()); this.xpReward = 50; } + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.witherTakeDamageFromWater; + } + + @Nullable + public java.util.UUID getSummoner() { + return summoner; + } + + public void setSummoner(@Nullable java.util.UUID summoner) { + this.summoner = summoner; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.witherAlwaysDropExp; + } + @Override protected PathNavigation createNavigation(Level world) { FlyingPathNavigation navigationflying = new FlyingPathNavigation(this, world); @@ -109,13 +148,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)); } @@ -133,6 +272,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 @@ -142,6 +282,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 } @@ -257,6 +398,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) { @@ -273,7 +424,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; @@ -298,7 +449,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 { @@ -358,7 +509,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()); @@ -391,8 +542,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()); @@ -578,11 +731,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 @@ -597,6 +750,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 eadcebd7845ee716e33c0ac0544502da1a6c5941..8e71d4d3874ff154eae423a8fb8f15ae08143f4d 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java @@ -100,10 +100,12 @@ public class ArmorStand extends LivingEntity { private boolean noTickPoseDirty = false; private boolean noTickEquipmentDirty = false; // Paper end - Allow ArmorStands not to tick + public boolean canMovementTick = true; // Purpur public ArmorStand(EntityType type, Level world) { super(type, world); if (world != null) this.canTick = world.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick + if (world != null) this.canMovementTick = world.purpurConfig.armorstandMovement; // Purpur this.handItems = NonNullList.withSize(2, ItemStack.EMPTY); this.armorItems = NonNullList.withSize(4, ItemStack.EMPTY); this.headPose = ArmorStand.DEFAULT_HEAD_POSE; @@ -113,6 +115,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) { @@ -607,7 +610,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()); } @@ -678,6 +681,7 @@ public class ArmorStand extends LivingEntity { @Override public void tick() { + maxUpStep = level().purpurConfig.armorstandStepHeight; // Purpur // Paper start - Allow ArmorStands not to tick if (!this.canTick) { if (this.noTickPoseDirty) { @@ -1005,4 +1009,18 @@ public class ArmorStand extends LivingEntity { } } // Paper end + + // Purpur start + @Override + public void updateInWaterStateAndDoWaterCurrentPushing() { + if (this.level().purpurConfig.armorstandWaterMovement && + (this.level().purpurConfig.armorstandWaterFence || !(level().getBlockState(blockPosition().below()).getBlock() instanceof net.minecraft.world.level.block.FenceBlock))) + super.updateInWaterStateAndDoWaterCurrentPushing(); + } + + @Override + public void aiStep() { + if (this.canMovementTick && this.canMove) super.aiStep(); + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java index 80303f9466b8c7097151be313afc9a383693d18a..e5b7598a5613a8281a991e42af5b950bcc2995ca 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java +++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java @@ -268,7 +268,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 d9016807bc21c38a5c38170e1335c79b39355bcb..62cdc36a21c0203ed98d2946a1efdf549a5ca3ea 100644 --- a/src/main/java/net/minecraft/world/entity/decoration/Painting.java +++ b/src/main/java/net/minecraft/world/entity/decoration/Painting.java @@ -121,7 +121,7 @@ public class Painting extends HangingEntity implements VariantHolder holder = loadVariant(nbt).orElseGet(Painting::getDefaultVariant); + Holder holder = loadVariant(nbt).orElseGet(() -> (Holder.Reference) getDefaultVariant()); // Purpur - decompile error TODO: still needed? this.setVariant(holder); this.direction = Direction.from2DDataValue(nbt.getByte("facing")); super.readAdditionalSaveData(nbt); @@ -159,7 +159,13 @@ public class Painting extends HangingEntity implements VariantHolder type, Level world) { super(type, world); @@ -393,7 +399,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; @@ -596,6 +611,12 @@ public class ItemEntity extends Entity implements TraceableEntity { public void setItem(ItemStack stack) { this.getEntityData().set(ItemEntity.DATA_ITEM, stack); this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate + // Purpur start + if (level().purpurConfig.itemImmuneToCactus.contains(stack.getItem())) immuneToCactus = true; + if (level().purpurConfig.itemImmuneToExplosion.contains(stack.getItem())) immuneToExplosion = true; + if (level().purpurConfig.itemImmuneToFire.contains(stack.getItem())) immuneToFire = true; + if (level().purpurConfig.itemImmuneToLightning.contains(stack.getItem())) immuneToLightning = true; + // level end } @Override diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java index e712bd07ea2946167782473a536e0c72fab4bccd..6d934405cd18d63943171448743cafd5c52026e2 100644 --- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java @@ -200,4 +200,29 @@ public class PrimedTnt extends Entity implements TraceableEntity { return !level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid(); } // Paper end - Option to prevent TNT from moving in water + // Purpur start - Shears can defuse TNT + @Override + public net.minecraft.world.InteractionResult interact(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) { + 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 586e3e92ccc275446df6dbbff9bf010a37a9aa8f..3a4cd10a3614cd5e29c49ecde490d5e564d9e568 100644 --- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java +++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java @@ -66,16 +66,19 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo protected AbstractSkeleton(EntityType type, Level world) { super(type, world); this.reassessWeaponGoal(); + this.setShouldBurnInDay(true); // Purpur } @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(2, new RestrictSunGoal(this)); this.goalSelector.addGoal(3, new FleeSunGoal(this, 1.0D)); this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Wolf.class, 6.0F, 1.0D, 1.2D)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0])); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); @@ -99,35 +102,14 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo } // Paper start - shouldBurnInDay API - 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 - shouldBurnInDay API @Override public void aiStep() { - boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - shouldBurnInDay API - - if (flag) { - ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); - - if (!itemstack.isEmpty()) { - if (itemstack.isDamageableItem()) { - 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(); } @@ -205,7 +187,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 +218,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo this.reassessWeaponGoal(); // Paper start - shouldBurnInDay API if (nbt.contains("Paper.ShouldBurnInDay")) { - this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); + // this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); // Purpur - implemented in LivingEntity } // Paper end - shouldBurnInDay API } @@ -245,7 +227,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 - shouldBurnInDay API diff --git a/src/main/java/net/minecraft/world/entity/monster/Blaze.java b/src/main/java/net/minecraft/world/entity/monster/Blaze.java index 17aa7676ab624440651850bbe5689f8a6c9dbeed..a8b58469fd8a1ed4ec0ce443cf05557903527bd7 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Blaze.java +++ b/src/main/java/net/minecraft/world/entity/monster/Blaze.java @@ -32,26 +32,73 @@ public class Blaze extends Monster { public Blaze(EntityType type, Level world) { super(type, world); - this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); + this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); // Purpur + if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F); this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F); this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F); this.xpReward = 10; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.blazeRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.blazeRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.blazeControllable; + } + + @Override + public double getMaxY() { + return level().purpurConfig.blazeMaxY; + } + + @Override + public void travel(Vec3 vec3) { + super.travel(vec3); + if (getRider() != null && this.isControllable() && !onGround) { + float speed = (float) getAttributeValue(Attributes.FLYING_SPEED); + setSpeed(speed); + Vec3 mot = getDeltaMovement(); + move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed)); + setDeltaMovement(mot.scale(0.9D)); + } + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.blazeMaxHealth); + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.blazeAlwaysDropExp; + } + @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(4, new Blaze.BlazeAttackGoal(this)); this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0D)); this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D, 0.0F)); this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new HurtByTargetGoal(this)).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); } public static AttributeSupplier.Builder createAttributes() { - return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0D).add(Attributes.MOVEMENT_SPEED, (double)0.23F).add(Attributes.FOLLOW_RANGE, 48.0D); + return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0D).add(Attributes.MOVEMENT_SPEED, (double)0.23F).add(Attributes.FOLLOW_RANGE, 48.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur } @Override @@ -101,11 +148,19 @@ public class Blaze extends Monster { @Override public boolean isSensitiveToWater() { - return true; + return this.level().purpurConfig.blazeTakeDamageFromWater; // Purpur } @Override protected void customServerAiStep() { + // Purpur start + if (getRider() != null && this.isControllable()) { + Vec3 mot = getDeltaMovement(); + setDeltaMovement(mot.x(), getVerticalMot() > 0 ? 0.07D : -0.07D, mot.z()); + return; + } + // Purpur end + --this.nextHeightOffsetChangeTick; if (this.nextHeightOffsetChangeTick <= 0) { this.nextHeightOffsetChangeTick = 100; diff --git a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java index 70d25bb45ad603095a1f5812cc396dfc5f16a1e1..c9bd400473166999479f5eef1edad529d3aafe01 100644 --- a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java +++ b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java @@ -29,6 +29,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 9657796d08f4a102d9d5ff7685f2a152d1a87fda..54315fb84e3289f0ad8305c2c2cec980a5b2c627 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java +++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java @@ -60,21 +60,99 @@ public class Creeper extends Monster implements PowerableMob { public int maxSwell = 30; public int explosionRadius = 3; private int droppedSkulls; + // Purpur start + private int spacebarCharge = 0; + private int prevSpacebarCharge = 0; + private int powerToggleDelay = 0; + private boolean exploding = false; + // Purpur end public Creeper(EntityType type, Level world) { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.creeperRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.creeperRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.creeperControllable; + } + + @Override + protected void customServerAiStep() { + if (powerToggleDelay > 0) { + powerToggleDelay--; + } + if (getRider() != null && this.isControllable()) { + if (getRider().getForwardMot() != 0 || getRider().getStrafeMot() != 0) { + spacebarCharge = 0; + setIgnited(false); + setSwellDir(-1); + } + if (spacebarCharge == prevSpacebarCharge) { + spacebarCharge = 0; + } + prevSpacebarCharge = spacebarCharge; + } + super.customServerAiStep(); + } + + @Override + public void onMount(Player rider) { + super.onMount(rider); + setIgnited(false); + setSwellDir(-1); + } + + @Override + public boolean onSpacebar() { + if (powerToggleDelay > 0) { + return true; // just toggled power, do not jump or ignite + } + spacebarCharge++; + if (spacebarCharge > maxSwell - 2) { + spacebarCharge = 0; + if (getRider() != null && getRider().getBukkitEntity().hasPermission("allow.powered.creeper")) { + powerToggleDelay = 20; + setPowered(!isPowered()); + setIgnited(false); + setSwellDir(-1); + return true; + } + } + if (!isIgnited()) { + if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0 && + getRider().getBukkitEntity().hasPermission("allow.special.creeper")) { + setIgnited(true); + setSwellDir(1); + return true; + } + } + return getForwardMot() == 0 && getStrafeMot() == 0; // do not jump if standing still + } + // Purpur end + @Override protected void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); this.goalSelector.addGoal(2, new SwellGoal(this)); + this.goalSelector.addGoal(3, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Ocelot.class, 6.0F, 1.0D, 1.2D)); this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Cat.class, 6.0F, 1.0D, 1.2D)); this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, false)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(6, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0])); } @@ -174,6 +252,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; @@ -265,15 +374,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(EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause this.spawnLingeringCloud(); // CraftBukkit start @@ -283,7 +394,7 @@ public class Creeper extends Monster implements PowerableMob { } // CraftBukkit end } - + this.exploding = false; // Purpur } private void spawnLingeringCloud() { @@ -325,6 +436,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 - CreeperIgniteEvent diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java index 01897af1e6253b987734a24c052daf2ce1314092..7600e747d91ae888eb801cfafcb09bffb76c8e62 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 - Check drowned for villager aggression config + // Purpur start + if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false) { // Paper - Check drowned for villager aggression config + @Override + public boolean canUse() { + return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canUse(); + } + + @Override + public boolean canContinueToUse() { + return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canContinueToUse(); + } + }); + // Purpur end this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false)); this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); @@ -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 91ff663b2260d1cdd1388c93068e4cd9d0331aea..cb189457305916f509d624beb303feff35d0c358 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java +++ b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java @@ -36,6 +36,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 9898e05682c2d47953c95e355d51c701a3b0cc1b..d945b0d74a4f87e7eea6cc8625c0da47ac53dfe7 100644 --- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java @@ -95,12 +95,40 @@ public class EnderMan extends Monster implements NeutralMob { public EnderMan(EntityType type, Level world) { super(type, world); this.setMaxUpStep(1.0F); - this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); + if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur + } + + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.endermanRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.endermanRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.endermanControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.endermanMaxHealth); + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.endermanAlwaysDropExp; } @Override protected void registerGoals() { this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new EnderMan.EndermanFreezeWhenLookedAt(this)); this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false)); this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D, 0.0F)); @@ -108,9 +136,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)); } @@ -247,7 +276,7 @@ public class EnderMan extends Monster implements NeutralMob { // Paper end - EndermanAttackPlayerEvent 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(); @@ -289,12 +318,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 - EndermanEscapeEvent @@ -415,6 +444,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; @@ -429,6 +460,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 - EndermanEscapeEvent for (int i = 0; i < 64; ++i) { if (this.teleport()) { @@ -475,7 +507,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 { @@ -522,7 +554,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 @@ -567,7 +608,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 b8ce2a9ad151b20f0f4e9e8e34a57069d8d77128..965362c281315c15fb70a83a6949d7825bebf15b 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Endermite.java +++ b/src/main/java/net/minecraft/world/entity/monster/Endermite.java @@ -37,20 +37,63 @@ public class Endermite extends Monster { private static final int MAX_LIFE = 2400; public int life; + private boolean isPlayerSpawned; // Purpur public Endermite(EntityType type, Level world) { super(type, world); this.xpReward = 3; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.endermiteRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.endermiteRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.endermiteControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.endermiteMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.endermiteTakeDamageFromWater; + } + + public boolean isPlayerSpawned() { + return this.isPlayerSpawned; + } + + public void setPlayerSpawned(boolean playerSpawned) { + this.isPlayerSpawned = playerSpawned; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.endermiteAlwaysDropExp; + } + @Override protected void registerGoals() { this.goalSelector.addGoal(1, new FloatGoal(this)); + this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level())); this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false)); this.goalSelector.addGoal(3, new WaterAvoidingRandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); } @@ -93,12 +136,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 e67cb165a0d706d38e4970fb3d63f59a29808a76..daee6c4c0c2d43b65cdfd691bbbdc72465702dfe 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Evoker.java +++ b/src/main/java/net/minecraft/world/entity/monster/Evoker.java @@ -51,10 +51,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()); @@ -63,6 +96,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)); @@ -327,7 +361,7 @@ public class Evoker extends SpellcasterIllager { return false; } else if (Evoker.this.tickCount < this.nextAttackTickCount) { return false; - } else if (!Evoker.this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { + } else if (!Evoker.this.level().purpurConfig.evokerBypassMobGriefing && !Evoker.this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur return false; } else { List list = Evoker.this.level().getNearbyEntities(Sheep.class, this.wololoTargeting, Evoker.this, Evoker.this.getBoundingBox().inflate(16.0D, 4.0D, 16.0D)); diff --git a/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java index c135bc245f59a1af706f98b9d140dee77016b12f..640f0c378a18cf0a820ad544bb3b172b698c6bfc 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java +++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java @@ -45,11 +45,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; })); @@ -97,6 +133,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(); @@ -104,7 +155,7 @@ public class Ghast extends FlyingMob implements Enemy { } public static AttributeSupplier.Builder createAttributes() { - return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D); + return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur } @Override @@ -171,7 +222,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; @@ -182,7 +233,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 793c72bb7b86e404926085629121d6cad19a2740..d13eed85d5399cd6991b3ad90f05a5805a3a2678 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Giant.java +++ b/src/main/java/net/minecraft/world/entity/monster/Giant.java @@ -1,18 +1,123 @@ package net.minecraft.world.entity.monster; import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.Difficulty; +import net.minecraft.world.DifficultyInstance; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityDimensions; import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.MobSpawnType; import net.minecraft.world.entity.Pose; +import net.minecraft.world.entity.SpawnGroupData; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.ai.goal.FloatGoal; +import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; +import net.minecraft.world.entity.ai.goal.MeleeAttackGoal; +import net.minecraft.world.entity.ai.goal.MoveTowardsRestrictionGoal; +import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; +import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; +import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; +import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; +import net.minecraft.world.entity.animal.IronGolem; +import net.minecraft.world.entity.animal.Turtle; +import net.minecraft.world.entity.npc.Villager; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.ServerLevelAccessor; + +import javax.annotation.Nullable; public class Giant extends Monster { public Giant(EntityType type, Level world) { super(type, world); + this.safeFallDistance = 10.0F; // Purpur + } + + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.giantRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.giantRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.giantControllable; + } + + @Override + protected void registerGoals() { + if (level().purpurConfig.giantHaveAI) { + this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); + this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); + this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 16.0F)); + this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0D)); + if (level().purpurConfig.giantHaveHostileAI) { + this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); + this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class)); + this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); + this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Villager.class, false)); + this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, IronGolem.class, true)); + this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, true)); + } + } + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.giantTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.giantAlwaysDropExp; + } + // Purpur end + + @Override + protected void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.giantMaxHealth); + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.giantMovementSpeed); + this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.giantAttackDamage); + } + + @Override + public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) { + SpawnGroupData groupData = super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt); + if (groupData == null) { + populateDefaultEquipmentSlots(this.random, difficulty); + populateDefaultEquipmentEnchantments(this.random, difficulty); + } + return groupData; + } + + @Override + protected void populateDefaultEquipmentSlots(net.minecraft.util.RandomSource random, DifficultyInstance difficulty) { + super.populateDefaultEquipmentSlots(this.random, difficulty); + // TODO make configurable + if (random.nextFloat() < (level().getDifficulty() == Difficulty.HARD ? 0.1F : 0.05F)) { + this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.IRON_SWORD)); + } + } + + @Override + public float getJumpPower() { + // make giants jump as high as everything else relative to their size + // 1.0 makes bottom of feet about as high as their waist when they jump + return level().purpurConfig.giantJumpHeight; } @Override @@ -31,6 +136,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 fd41ef66e2e12ec3a888bb376ef4363343914fcd..01c558673f0bb5034bca9df0e473375e7b7e724e 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Guardian.java +++ b/src/main/java/net/minecraft/world/entity/monster/Guardian.java @@ -70,15 +70,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); @@ -87,6 +123,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))); } @@ -347,7 +384,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) { @@ -364,7 +401,7 @@ public class Guardian extends Monster { return new Vector3f(0.0F, dimensions.height + 0.125F * scaleFactor, 0.0F); } - private static class GuardianMoveControl extends MoveControl { + private static class GuardianMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur private final Guardian guardian; @@ -373,8 +410,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(); @@ -385,7 +431,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 72b8290bebe8ed9bc3c464b30cfe5d2d664310f5..06a5106a94a44c1d21537410d801cdd945503d69 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Husk.java +++ b/src/main/java/net/minecraft/world/entity/monster/Husk.java @@ -22,6 +22,59 @@ public class Husk extends Zombie { public Husk(EntityType type, Level world) { super(type, world); + this.setShouldBurnInDay(false); // Purpur + } + + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.huskRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.huskRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.huskControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.huskMaxHealth); + } + + @Override + protected void randomizeReinforcementsChance() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.huskSpawnReinforcements); + } + + @Override + public boolean jockeyOnlyBaby() { + return level().purpurConfig.huskJockeyOnlyBaby; + } + + @Override + public double jockeyChance() { + return level().purpurConfig.huskJockeyChance; + } + + @Override + public boolean jockeyTryExistingChickens() { + return level().purpurConfig.huskJockeyTryExistingChickens; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.huskTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.huskAlwaysDropExp; } public static boolean checkHuskSpawnRules(EntityType type, ServerLevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { @@ -30,7 +83,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 fb84b35e34063075e69e00e430bc00e7c3b9d62c..a8b3431c67442c5440b063426a1adc423f5c0658 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java +++ b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java @@ -59,10 +59,45 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.illusionerRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.illusionerRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.illusionerControllable; + } + // Purpur end + + @Override + protected void initAttributes() { + this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.illusionerMovementSpeed); + this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(this.level().purpurConfig.illusionerFollowRange); + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.illusionerMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.illusionerTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.illusionerAlwaysDropExp; + } + @Override protected void registerGoals() { super.registerGoals(); this.goalSelector.addGoal(0, new FloatGoal(this)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new SpellcasterIllager.SpellcasterCastingSpellGoal()); this.goalSelector.addGoal(4, new Illusioner.IllusionerMirrorSpellGoal()); this.goalSelector.addGoal(5, new Illusioner.IllusionerBlindnessSpellGoal()); @@ -70,6 +105,7 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob { this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D)); this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); this.targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(this, Player.class, true)).setUnseenMemoryTicks(300)); this.targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)).setUnseenMemoryTicks(300)); diff --git a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java index 2858ea5562d06c11e5c7337a2b123f9be7a3f62e..1ad97267394d3717b1871336193cdc91f3ffb276 100644 --- a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java +++ b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java @@ -25,6 +25,58 @@ public class MagmaCube extends Slime { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.magmaCubeRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.magmaCubeRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.magmaCubeControllable; + } + + @Override + public float getJumpPower() { + return 0.42F * this.getBlockJumpFactor(); // from EntityLiving + } + // Purpur end + + @Override + protected String getMaxHealthEquation() { + return level().purpurConfig.magmaCubeMaxHealth; + } + + @Override + protected String getAttackDamageEquation() { + return level().purpurConfig.magmaCubeAttackDamage; + } + + @Override + protected java.util.Map getMaxHealthCache() { + return level().purpurConfig.magmaCubeMaxHealthCache; + } + + @Override + protected java.util.Map getAttackDamageCache() { + return level().purpurConfig.magmaCubeAttackDamageCache; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.magmaCubeTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.magmaCubeAlwaysDropExp; + } + public static AttributeSupplier.Builder createAttributes() { return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, (double)0.2F); } @@ -70,11 +122,12 @@ public class MagmaCube extends Slime { } @Override - protected void jumpFromGround() { + public void jumpFromGround() { // Purpur - protected -> public Vec3 vec3 = this.getDeltaMovement(); float f = (float)this.getSize() * 0.1F; this.setDeltaMovement(vec3.x, (double)(this.getJumpPower() + f), vec3.z); this.hasImpulse = true; + this.actualJump = false; // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java index fbca4d6bc84e8be359b989ef089469838f896910..1be6f724fa6447fe733c8374ac295c6d4db6eb87 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 187037c43ebb5b245ffa4b50163d443490668744..b70065edff5da1f564aae14b3032b2498d2bb998 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java +++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java @@ -50,6 +50,8 @@ public class Phantom extends FlyingMob implements Enemy { Vec3 moveTargetPoint; public BlockPos anchorPoint; Phantom.AttackPhase attackPhase; + private static final net.minecraft.world.item.crafting.Ingredient TORCH = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.item.Items.TORCH, net.minecraft.world.item.Items.SOUL_TORCH); // Purpur + Vec3 crystalPosition; // Purpur public Phantom(EntityType type, Level world) { super(type, world); @@ -59,6 +61,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 @@ -73,9 +161,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()); } @@ -91,7 +187,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() { @@ -121,6 +220,21 @@ public class Phantom extends FlyingMob implements Enemy { return true; } + private double getFromCache(java.util.function.Supplier equation, java.util.function.Supplier> cache, java.util.function.Supplier defaultValue) { + int size = getPhantomSize(); + Double value = cache.get().get(size); + if (value == null) { + try { + value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue(); + } catch (javax.script.ScriptException e) { + e.printStackTrace(); + value = defaultValue.get(); + } + cache.get().put(size, value); + } + return value; + } + @Override public void tick() { super.tick(); @@ -141,14 +255,12 @@ public class Phantom extends FlyingMob implements Enemy { this.level().addParticle(ParticleTypes.MYCELIUM, this.getX() - (double) f2, this.getY() + (double) f4, this.getZ() - (double) f3, 0.0D, 0.0D, 0.0D); } + if (level().purpurConfig.phantomFlamesOnSwoop && attackPhase == AttackPhase.SWOOP) shoot(); // Purpur } @Override public void aiStep() { - if (this.isAlive() && shouldBurnInDay && this.isSunBurnTick()) { // Paper - shouldBurnInDay API - this.setSecondsOnFire(8); - } - + // Purpur - moved down to shouldBurnInDay() super.aiStep(); } @@ -160,7 +272,11 @@ public class Phantom extends FlyingMob implements Enemy { @Override public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) { this.anchorPoint = this.blockPosition().above(5); - this.setPhantomSize(0); + // Purpur start + int min = world.getLevel().purpurConfig.phantomMinSize; + int max = world.getLevel().purpurConfig.phantomMaxSize; + this.setPhantomSize(min == max ? min : world.getRandom().nextInt(max + 1 - min) + min); + // Purpur end return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt); } @@ -176,7 +292,7 @@ public class Phantom extends FlyingMob implements Enemy { if (nbt.hasUUID("Paper.SpawningEntity")) { this.spawningEntity = nbt.getUUID("Paper.SpawningEntity"); } - if (nbt.contains("Paper.ShouldBurnInDay")) { + if (false && nbt.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); } // Paper end @@ -193,7 +309,7 @@ public class Phantom extends FlyingMob implements Enemy { if (this.spawningEntity != null) { nbt.putUUID("Paper.SpawningEntity", this.spawningEntity); } - nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); + // nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); // Purpur - implemented in LivingEntity // Paper end } @@ -262,8 +378,14 @@ public class Phantom extends FlyingMob implements Enemy { return spawningEntity; } public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; } - private boolean shouldBurnInDay = true; - public boolean shouldBurnInDay() { return shouldBurnInDay; } + // private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity - keep methods for ABI compatibility + // 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 { @@ -273,7 +395,125 @@ public class Phantom extends FlyingMob implements Enemy { private AttackPhase() {} } - private class PhantomMoveControl extends MoveControl { + // Purpur start + class FindCrystalGoal extends Goal { + private final Phantom phantom; + private net.minecraft.world.entity.boss.enderdragon.EndCrystal crystal; + private Comparator comparator; + + FindCrystalGoal(Phantom phantom) { + this.phantom = phantom; + this.comparator = Comparator.comparingDouble(phantom::distanceToSqr); + this.setFlags(EnumSet.of(Flag.LOOK)); + } + + @Override + public boolean canUse() { + double range = maxTargetRange(); + List crystals = level().getEntitiesOfClass(net.minecraft.world.entity.boss.enderdragon.EndCrystal.class, phantom.getBoundingBox().inflate(range)); + if (crystals.isEmpty()) { + return false; + } + crystals.sort(comparator); + crystal = crystals.get(0); + if (phantom.distanceToSqr(crystal) > range * range) { + crystal = null; + return false; + } + return true; + } + + @Override + public boolean canContinueToUse() { + if (crystal == null || !crystal.isAlive()) { + return false; + } + double range = maxTargetRange(); + return phantom.distanceToSqr(crystal) <= (range * range) * 2; + } + + @Override + public void start() { + phantom.crystalPosition = new Vec3(crystal.getX(), crystal.getY() + (phantom.random.nextInt(10) + 10), crystal.getZ()); + } + + @Override + public void stop() { + crystal = null; + phantom.crystalPosition = null; + super.stop(); + } + + private double maxTargetRange() { + return phantom.level().purpurConfig.phantomOrbitCrystalRadius; + } + } + + class OrbitCrystalGoal extends Goal { + private final Phantom phantom; + private float offset; + private float radius; + private float verticalChange; + private float direction; + + OrbitCrystalGoal(Phantom phantom) { + this.phantom = phantom; + this.setFlags(EnumSet.of(Flag.MOVE)); + } + + @Override + public boolean canUse() { + return phantom.isCirclingCrystal(); + } + + @Override + public void start() { + this.radius = 5.0F + phantom.random.nextFloat() * 10.0F; + this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F; + this.direction = phantom.random.nextBoolean() ? 1.0F : -1.0F; + updateOffset(); + } + + @Override + public void tick() { + if (phantom.random.nextInt(350) == 0) { + this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F; + } + if (phantom.random.nextInt(250) == 0) { + ++this.radius; + if (this.radius > 15.0F) { + this.radius = 5.0F; + this.direction = -this.direction; + } + } + if (phantom.random.nextInt(450) == 0) { + this.offset = phantom.random.nextFloat() * 2.0F * 3.1415927F; + updateOffset(); + } + if (phantom.moveTargetPoint.distanceToSqr(phantom.getX(), phantom.getY(), phantom.getZ()) < 4.0D) { + updateOffset(); + } + if (phantom.moveTargetPoint.y < phantom.getY() && !phantom.level().isEmptyBlock(new BlockPos(phantom).below(1))) { + this.verticalChange = Math.max(1.0F, this.verticalChange); + updateOffset(); + } + if (phantom.moveTargetPoint.y > phantom.getY() && !phantom.level().isEmptyBlock(new BlockPos(phantom).above(1))) { + this.verticalChange = Math.min(-1.0F, this.verticalChange); + updateOffset(); + } + } + + private void updateOffset() { + this.offset += this.direction * 15.0F * 0.017453292F; + phantom.moveTargetPoint = phantom.crystalPosition.add( + this.radius * Mth.cos(this.offset), + -4.0F + this.verticalChange, + this.radius * Mth.sin(this.offset)); + } + } + // Purpur end + + private class PhantomMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur private float speed = 0.1F; @@ -281,8 +521,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; @@ -328,14 +579,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 { @@ -422,6 +679,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; @@ -567,6 +830,7 @@ public class Phantom extends FlyingMob implements Enemy { this.nextScanTick = reducedTickDelay(60); List list = Phantom.this.level().getNearbyPlayers(this.attackTargeting, Phantom.this, Phantom.this.getBoundingBox().inflate(16.0D, 64.0D, 16.0D)); + if (level().purpurConfig.phantomIgnorePlayersWithTorch) list.removeIf(human -> TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND)));// Purpur if (!list.isEmpty()) { list.sort(Comparator.comparing((Entity e) -> { return e.getY(); }).reversed()); // CraftBukkit - decompile error Iterator iterator = list.iterator(); diff --git a/src/main/java/net/minecraft/world/entity/monster/Pillager.java b/src/main/java/net/minecraft/world/entity/monster/Pillager.java index 05ed2f06a41f3b12e0432a37faf98d0b1fea7a8b..d5becd13774f9a2ead77d58e777ffc9aea10cb60 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Pillager.java +++ b/src/main/java/net/minecraft/world/entity/monster/Pillager.java @@ -66,15 +66,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, new Class[]{Raider.class})).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java index 151acc43c96b4545ce92d3d559d8e1591874b4b5..2d834e57253f666f04f8e0b47c8b8a45458c0a75 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java +++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java @@ -71,14 +71,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 MeleeAttackGoal(this, 1.0D, true)); this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.4D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(2, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true, (entityliving) -> { @@ -136,7 +176,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 { @@ -146,7 +186,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(); @@ -156,7 +196,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 f3c2a2ffb74daa89a516db4c188ce675c79932bf..ab21a44905a4154013cd65acdecbf55b741da086 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; @@ -49,6 +51,8 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.AbstractArrow; import net.minecraft.world.entity.projectile.ShulkerBullet; 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; @@ -97,12 +101,59 @@ public class Shulker extends AbstractGolem implements VariantHolder= f) { + if ((!this.level().purpurConfig.shulkerSpawnFromBulletRequireOpenLid || !this.isClosed()) && this.teleportSomewhere()) { + // Purpur start + float chance = this.level().purpurConfig.shulkerSpawnFromBulletBaseChance; + if (!this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation.isBlank()) { + int nearby = this.level().getEntities((EntityTypeTest) EntityType.SHULKER, axisalignedbb.inflate(this.level().purpurConfig.shulkerSpawnFromBulletNearbyRange), Entity::isAlive).size(); + try { + chance -= ((Number) scriptEngine.eval("let nearby = " + nearby + "; " + this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation)).floatValue(); + } catch (javax.script.ScriptException e) { + e.printStackTrace(); + chance -= (nearby - 1) / 5.0F; + } + } + if (this.level().random.nextFloat() <= chance) { Shulker entityshulker = (Shulker) EntityType.SHULKER.create(this.level()); + // Purpur end if (entityshulker != null) { entityshulker.setVariant(this.getVariant()); @@ -591,7 +651,7 @@ public class Shulker extends AbstractGolem implements VariantHolder getVariant() { - return Optional.ofNullable(this.getColor()); + return Optional.ofNullable(this.level().purpurConfig.shulkerSpawnFromBulletRandomColor ? DyeColor.random(this.level().random) : this.getColor()); // Purpur } @Nullable @@ -601,7 +661,7 @@ public class Shulker extends AbstractGolem implements VariantHolder(this, Player.class, true)); } @@ -187,7 +221,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); @@ -225,7 +259,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 92974452d8f63fde8524cfac305ee2ef5212f840..30ff77f5f137614b5d0d2df6dc90f47c97e8ab13 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, org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + if (!player.getAbilities().instabuild) { + stack.shrink(1); + } + + for (int i = 0; i < 15; ++i) { + ((ServerLevel) level()).sendParticles(((ServerLevel) level()).players(), null, ParticleTypes.HAPPY_VILLAGER, + getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1, + random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true); + } + return InteractionResult.SUCCESS; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java index 3d9107d2c19a09215445aa0e0aacc32f9f82a536..0f77f00e9a02d1f982f285617604e7291b70a2a4 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Slime.java +++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java @@ -62,6 +62,7 @@ public class Slime extends Mob implements Enemy { public float squish; public float oSquish; private boolean wasOnGround; + protected boolean actualJump; // Purpur public Slime(EntityType type, Level world) { super(type, world); @@ -69,12 +70,89 @@ public class Slime extends Mob implements Enemy { this.moveControl = new Slime.SlimeMoveControl(this); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.slimeRidable; + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.slimeTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.slimeAlwaysDropExp; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.slimeRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.slimeControllable; + } + + @Override + public float getJumpPower() { + float height = super.getJumpPower(); + return getRider() != null && this.isControllable() && actualJump ? height * 1.5F : height; + } + + @Override + public boolean onSpacebar() { + if (onGround && getRider() != null && this.isControllable()) { + actualJump = true; + if (getRider().getForwardMot() == 0 || getRider().getStrafeMot() == 0) { + jumpFromGround(); // jump() here if not moving + } + } + return true; // do not jump() in wasd controller, let vanilla controller handle + } + + protected String getMaxHealthEquation() { + return level().purpurConfig.slimeMaxHealth; + } + + protected String getAttackDamageEquation() { + return level().purpurConfig.slimeAttackDamage; + } + + protected java.util.Map getMaxHealthCache() { + return level().purpurConfig.slimeMaxHealthCache; + } + + protected java.util.Map getAttackDamageCache() { + return level().purpurConfig.slimeAttackDamageCache; + } + + protected double getFromCache(java.util.function.Supplier equation, java.util.function.Supplier> cache, java.util.function.Supplier defaultValue) { + int size = getSize(); + Double value = cache.get().get(size); + if (value == null) { + try { + value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue(); + } catch (javax.script.ScriptException e) { + e.printStackTrace(); + value = defaultValue.get(); + } + cache.get().put(size, value); + } + return value; + } + // Purpur end + @Override protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(1, new Slime.SlimeFloatGoal(this)); this.goalSelector.addGoal(2, new Slime.SlimeAttackGoal(this)); this.goalSelector.addGoal(3, new Slime.SlimeRandomDirectionGoal(this)); this.goalSelector.addGoal(5, new Slime.SlimeKeepOnJumpingGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving) -> { return Math.abs(entityliving.getY() - this.getY()) <= 4.0D; })); @@ -99,9 +177,9 @@ public class Slime extends Mob implements Enemy { this.entityData.set(Slime.ID_SIZE, j); this.reapplyPosition(); this.refreshDimensions(); - this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double) (j * j)); + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(this::getMaxHealthEquation, this::getMaxHealthCache, () -> (double) size * size)); // Purpur this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue((double) (0.2F + 0.1F * (float) j)); - this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) j); + this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(this::getAttackDamageEquation, this::getAttackDamageCache, () -> (double) j)); // Purpur if (heal) { this.setHealth(this.getMaxHealth()); } @@ -386,11 +464,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 @@ -424,7 +503,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; @@ -443,21 +522,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) { @@ -474,7 +565,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 7618364e5373fe17cfe45a5a4ee9ab25e591581c..b44ffeb4cc0ef63fdd25683f60c5a20fcdeb9135 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Spider.java +++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java @@ -53,14 +53,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 8185cef34b9300561a00f9e62b98f1aa818a3f5a..e01c91385935b71bb9aa7259b95cb963140e8c94 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Stray.java +++ b/src/main/java/net/minecraft/world/entity/monster/Stray.java @@ -21,6 +21,38 @@ public class Stray extends AbstractSkeleton { super(type, world); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.strayRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.strayRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.strayControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.strayMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.strayTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.strayAlwaysDropExp; + } + public static boolean checkStraySpawnRules(EntityType type, ServerLevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { BlockPos blockPos = pos; diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java index 61162ecd43dc5e6f7898daecdec49f444e6d869b..2f49b528601a1feb7246fe7a9b83ce828c2d78fc 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Strider.java +++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java @@ -94,12 +94,44 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { super(type, world); this.steering = new ItemBasedSteering(this.entityData, Strider.DATA_BOOST_TIME, Strider.DATA_SADDLE_ID); this.blocksBuilding = true; - this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); + if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur this.setPathfindingMalus(BlockPathTypes.LAVA, 0.0F); this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F); this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F); } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.striderRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.striderRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.striderControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.striderMaxHealth); + } + + @Override + public int getPurpurBreedTime() { + return this.level().purpurConfig.striderBreedingTicks; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.striderAlwaysDropExp; + } + public static boolean checkStriderSpawnRules(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); @@ -161,6 +193,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { @Override protected void registerGoals() { this.goalSelector.addGoal(1, new PanicGoal(this, 1.65D)); + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); this.temptGoal = new TemptGoal(this, 1.4D, Strider.TEMPT_ITEMS, false); this.goalSelector.addGoal(3, this.temptGoal); @@ -414,7 +447,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { @Override public boolean isSensitiveToWater() { - return true; + return this.level().purpurConfig.striderTakeDamageFromWater; // Purpur } @Override @@ -456,6 +489,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); @@ -468,7 +514,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 f443006c1e32feee97b32312814e2447a50c45e2..834abf1160034543fe3e89fa1c8d4bb55ffc5dc1 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 @@ -251,14 +312,14 @@ public class Vex extends Monster implements TraceableEntity { return new Vector3f(0.0F, dimensions.height - 0.0625F * scaleFactor, 0.0F); } - 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(); @@ -267,7 +328,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 a6af5ac9d09834940d3dd4c80b16450b90484edb..ee9d5933f74584452a1b27a392e7d36dac5b9bc9 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 MeleeAttackGoal(this, 1.0D, false)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, Raider.class)).setAlertOthers()); this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true)); @@ -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 f9ffc5f4cbfdcf5c7351a883d2e5c26492175283..4af36f8d0647f881a842e248d02d747115e1cc70 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Witch.java +++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java @@ -59,6 +59,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(); @@ -67,10 +99,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 20a65c11ededcd7170704b70118da6200151fbab..e97cb4e166c2e9ac6d93ed5b15350758326e7e74 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 3e79562f51efbac51785fa0998d6a1a6e3036939..4f242ba9131f341606af13ac543af8a0d7ed5e30 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java @@ -69,6 +69,38 @@ public class Zoglin extends Monster implements Enemy, HoglinBase { this.xpReward = 5; } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.zoglinRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zoglinRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.zoglinControllable; + } + // Purpur end + + @Override + public void initAttributes() { + this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zoglinMaxHealth); + } + + @Override + public boolean isSensitiveToWater() { + return this.level().purpurConfig.zoglinTakeDamageFromWater; + } + + @Override + protected boolean isAlwaysExperienceDropper() { + return this.level().purpurConfig.zoglinAlwaysDropExp; + } + @Override protected Brain.Provider brainProvider() { return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); @@ -200,6 +232,7 @@ public class Zoglin extends Monster implements Enemy, HoglinBase { @Override protected void customServerAiStep() { + if (getRider() == null || !this.isControllable()) // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel)this.level(), this); this.updateActivity(); } diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java index 5c40e994007dbf46ebc12c1e6a6ca90379471b74..6d69d936a2346f42abe8f867d51040121213b6ff 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java @@ -96,22 +96,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 - Add more Zombie API + // private boolean shouldBurnInDay = true; // Paper - Add more Zombie API // Purpur - implemented in LivingEntity public Zombie(EntityType type, Level world) { super(type, world); this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(type, world.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper - Configurable door breaking difficulty + 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 - Add zombie targets turtle egg config this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur this.addBehaviourGoals(); } @@ -121,7 +168,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)); } @@ -243,30 +302,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(); } @@ -304,6 +340,7 @@ public class Zombie extends Monster { } + public boolean shouldBurnInDay() { return isSunSensitive(); } // Purpur - for ABI compatibility public boolean isSunSensitive() { return this.shouldBurnInDay; // Paper - Add more Zombie API } @@ -433,7 +470,7 @@ public class Zombie extends Monster { nbt.putBoolean("CanBreakDoors", this.canBreakDoors()); nbt.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1); nbt.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1); - nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API + //nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper - Add more Zombie API // Purpur - implemented in LivingEntity } @Override @@ -447,7 +484,7 @@ public class Zombie extends Monster { } // Paper start - Add more Zombie API if (nbt.contains("Paper.ShouldBurnInDay")) { - this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); + // this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); // Purpur - implemented in LivingEntity } // Paper end - Add more Zombie API @@ -520,19 +557,20 @@ public class Zombie extends Monster { if (object instanceof Zombie.ZombieGroupData) { Zombie.ZombieGroupData entityzombie_groupdatazombie = (Zombie.ZombieGroupData) object; - if (entityzombie_groupdatazombie.isBaby) { - this.setBaby(true); + // Purpur start + if (!jockeyOnlyBaby() || entityzombie_groupdatazombie.isBaby) { + this.setBaby(entityzombie_groupdatazombie.isBaby); if (entityzombie_groupdatazombie.canSpawnJockey) { - if ((double) randomsource.nextFloat() < 0.05D) { - List list = world.getEntitiesOfClass(Chicken.class, this.getBoundingBox().inflate(5.0D, 3.0D, 5.0D), EntitySelector.ENTITY_NOT_BEING_RIDDEN); + if ((double) randomsource.nextFloat() < jockeyChance()) { + List list = jockeyTryExistingChickens() ? world.getEntitiesOfClass(Chicken.class, this.getBoundingBox().inflate(5.0D, 3.0D, 5.0D), EntitySelector.ENTITY_NOT_BEING_RIDDEN) : java.util.Collections.emptyList(); + // Purpur end if (!list.isEmpty()) { Chicken entitychicken = (Chicken) list.get(0); entitychicken.setChickenJockey(true); this.startRiding(entitychicken); - } - } else if ((double) randomsource.nextFloat() < 0.05D) { + } else { // Purpur Chicken entitychicken1 = (Chicken) EntityType.CHICKEN.create(this.level()); if (entitychicken1 != null) { @@ -542,6 +580,7 @@ public class Zombie extends Monster { this.startRiding(entitychicken1); world.addFreshEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit } + } // Purpur } } } @@ -588,7 +627,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 9274015f58c71f991903bd28434a4832afd074b2..d713afdc687b44595c40690f76e5d6c7ccb501c6 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java @@ -82,6 +82,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(); @@ -168,13 +220,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 fbabbd0808304f5d0d12f987d00c9e43a89fb1c9..feba8a264bae656244f60296d0511a8046297f73 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java +++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java @@ -64,6 +64,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; @@ -111,7 +158,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { this.maybeAlertOthers(); } - if (this.isAngry()) { + if (this.isAngry() && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur this.lastHurtByPlayerTime = this.tickCount; } @@ -166,7 +213,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { this.ticksUntilNextAlert = ZombifiedPiglin.ALERT_INTERVAL.sample(this.random); } - if (entityliving instanceof Player) { + if (entityliving instanceof Player && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur this.setLastHurtByPlayer((Player) entityliving); } @@ -246,7 +293,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { @Override protected void randomizeReinforcementsChance() { - this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(0.0D); + this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombifiedPiglinSpawnReinforcements); // Purpur } @Nullable diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java index f270c54028867ae0026bba3f214c3da6d015befb..169d21552ad75a8854c4209d53996e95feed7cbe 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 @@ -69,6 +69,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(); @@ -131,7 +168,7 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { private int behaviorTick; // Pufferfish @Override protected void customServerAiStep() { - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel)this.level(), this); HoglinAi.updateActivity(this); if (this.isConverting()) { diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java index 2eeb23ce4125b2538e92e19bf73f55398d2dbfc0..0127c4522a7656b7a3bdd967f946f2aa72566442 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); @@ -303,7 +335,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento private int behaviorTick; // Pufferfish @Override protected void customServerAiStep() { - if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish + if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel) this.level(), this); PiglinAi.updateActivity(this); super.customServerAiStep(); @@ -400,7 +432,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 4f4f557b7f4232ec3b90dda43c6bed30521318ba..dd4313e0507d3adda0ec84c79f1af13ecc2d7ef3 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 @@ -599,20 +599,33 @@ public class PiglinAi { Iterator iterator = iterable.iterator(); Item item; + ItemStack itemstack; // Purpur do { if (!iterator.hasNext()) { return false; } - ItemStack itemstack = (ItemStack) iterator.next(); + itemstack = (ItemStack) iterator.next(); // Purpur item = itemstack.getItem(); - } while (!(item instanceof ArmorItem) || ((ArmorItem) item).getMaterial() != ArmorMaterials.GOLD); + } while (!(item instanceof ArmorItem) || ((ArmorItem) item).getMaterial() != ArmorMaterials.GOLD && (!entity.level().purpurConfig.piglinIgnoresArmorWithGoldTrim || !isWearingGoldTrim(entity, itemstack))); // Purpur return true; } + // Purpur start + private static boolean isWearingGoldTrim(LivingEntity entity, ItemStack itemstack) { + Optional optionalArmorTrim = net.minecraft.world.item.armortrim.ArmorTrim.getTrim(entity.level().registryAccess(), itemstack, true); + + 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 5aab051998b67b7ba95cbf568de60e325b905eab..1805d4e15328fa99595641ea64fc2e3e659c555e 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); } @@ -85,6 +117,7 @@ public class PiglinBrute extends AbstractPiglin { @Override protected void customServerAiStep() { + if (getRider() == null || this.isControllable()) // Purpur - only use brain if no rider this.getBrain().tick((ServerLevel)this.level(), this); PiglinBruteAi.updateActivity(this); PiglinBruteAi.maybePlayActivitySound(this); diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java index 5cad15c512919ce6b0cae9f45e9011dd66134622..07fa613d75f3659145945245926e9068057e3ed2 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 @@ -123,8 +123,32 @@ public class Warden extends Monster implements VibrationSystem { this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F); this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F); this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F); + this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.5F); // Purpur } + // Purpur start + @Override + public boolean isRidable() { + return level().purpurConfig.wardenRidable; + } + + @Override + public boolean dismountsUnderwater() { + return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wardenRidableInWater; + } + + @Override + public boolean isControllable() { + return level().purpurConfig.wardenControllable; + } + + @Override + protected void registerGoals() { + this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur + } + // Purpur end + @Override public Packet getAddEntityPacket() { return new ClientboundAddEntityPacket(this, this.hasPose(Pose.EMERGING) ? 1 : 0); @@ -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 4e6c2f6b2e54a4c126e9a026b9cad05ce835ad66..69553b5b3c56998e4ae40876b1458929b335ad5d 100644 --- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java +++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java @@ -42,6 +42,7 @@ import org.bukkit.event.entity.VillagerAcquireTradeEvent; // CraftBukkit end public abstract class AbstractVillager extends AgeableMob implements InventoryCarrier, Npc, Merchant { + static final net.minecraft.world.item.crafting.Ingredient TEMPT_ITEMS = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.level.block.Blocks.EMERALD_BLOCK.asItem()); // Purpur // CraftBukkit start private CraftMerchant craftMerchant; diff --git a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java index 5f407535298a31a34cfe114dd863fd6a9b977707..29c7e33fe961020e5a0007287fe9b6631689f1b8 100644 --- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java +++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java @@ -30,7 +30,7 @@ public class CatSpawner implements CustomSpawner { if (this.nextTick > 0) { return 0; } else { - this.nextTick = 1200; + this.nextTick = world.purpurConfig.catSpawnDelay; // Purpur Player player = world.getRandomPlayer(); if (player == null) { return 0; @@ -63,11 +63,15 @@ public class CatSpawner implements CustomSpawner { } private int spawnInVillage(ServerLevel world, BlockPos pos) { - int i = 48; + // Purpur start + int range = world.purpurConfig.catSpawnVillageScanRange; + if (range <= 0) return 0; + if (world.getPoiManager().getCountInRange((entry) -> { return entry.is(PoiTypes.HOME); - }, pos, 48, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { - List list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(48.0D, 8.0D, 48.0D)); + }, pos, range, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { + List list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(range, 8.0D, range)); + // Purpur end if (list.size() < 5) { return this.spawnCat(pos, world); } @@ -77,8 +81,11 @@ public class CatSpawner implements CustomSpawner { } private int spawnInHut(ServerLevel world, BlockPos pos) { - int i = 16; - List list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(16.0D, 8.0D, 16.0D)); + // Purpur start + int range = world.purpurConfig.catSpawnSwampHutScanRange; + if (range <= 0) return 0; + List list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(range, 8.0D, range)); + // Purpur end return list.size() < 1 ? this.spawnCat(pos, world) : 0; } diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java index 34812d4ab115f31a6ad1cf8cbc345dda4339c075..248ef4e472d569395eda57298298f788bf9db6f7 100644 --- a/src/main/java/net/minecraft/world/entity/npc/Villager.java +++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java @@ -146,6 +146,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler }); public long nextGolemPanic = -1; // Pufferfish + private boolean isLobotomized = false; public boolean isLobotomized() { return this.isLobotomized; } // Purpur + private int notLobotomizedCount = 0; // Purpur public Villager(EntityType entityType, Level world) { this(entityType, world, VillagerType.PLAINS); @@ -158,6 +160,91 @@ 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; + boolean shouldCheckForTradeLocked = this.level().purpurConfig.villagerLobotomizeWaitUntilTradeLocked; + if (this.notLobotomizedCount > 3) { + // check half as often if not lobotomized for the last 3+ consecutive checks + interval *= 2; + } + if (this.level().getGameTime() % interval == 0) { + // offset Y for short blocks like dirt_path/farmland + this.isLobotomized = !(shouldCheckForTradeLocked && this.getVillagerXp() == 0) && !canTravelFrom(BlockPos.containing(this.position().x, this.getBoundingBox().minY + 0.0625D, this.position().z)); + + if (this.isLobotomized) { + this.notLobotomizedCount = 0; + } else { + this.notLobotomizedCount++; + } + } + return this.isLobotomized; + } + + private boolean canTravelFrom(BlockPos pos) { + return canTravelTo(pos.east()) || canTravelTo(pos.west()) || canTravelTo(pos.north()) || canTravelTo(pos.south()); + } + + private boolean canTravelTo(BlockPos pos) { + net.minecraft.world.level.block.state.BlockState state = this.level().getBlockStateIfLoaded(pos); + if (state == null) { + // chunk not loaded + return false; + } + net.minecraft.world.level.block.Block bottom = state.getBlock(); + if (bottom instanceof net.minecraft.world.level.block.FenceBlock || + bottom instanceof net.minecraft.world.level.block.FenceGateBlock || + bottom instanceof net.minecraft.world.level.block.WallBlock) { + // bottom block is too tall to get over + return false; + } + net.minecraft.world.level.block.Block top = level().getBlockState(pos.above()).getBlock(); + // only if both blocks have no collision + return !bottom.hasCollision && !top.hasCollision; } @Override @@ -194,7 +281,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler brain.addActivity(Activity.PLAY, VillagerGoalPackages.getPlayPackage(0.5F)); } else { brain.setSchedule(Schedule.VILLAGER_DEFAULT); - brain.addActivityWithConditions(Activity.WORK, VillagerGoalPackages.getWorkPackage(villagerprofession, 0.5F), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))); + brain.addActivityWithConditions(Activity.WORK, VillagerGoalPackages.getWorkPackage(villagerprofession, 0.5F, this.level().purpurConfig.villagerClericsFarmWarts), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))); // Purpur } brain.addActivity(Activity.CORE, VillagerGoalPackages.getCorePackage(villagerprofession, 0.5F)); @@ -257,13 +344,21 @@ 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 + // Purpur start + if (this.level().purpurConfig.villagerLobotomizeEnabled) { + // treat as inactive if lobotomized + inactive = inactive || checkLobotomized(); + } else { + this.isLobotomized = false; + } // Pufferfish start - if (!inactive && this.behaviorTick++ % this.activatedPriority == 0) { + 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 - } // Pufferfish end + else if (this.isLobotomized && shouldRestock()) restock(); + // Purpur end if (this.assignProfessionWhenSpawned) { this.assignProfessionWhenSpawned = false; } @@ -319,7 +414,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler if (!itemstack.is(Items.VILLAGER_SPAWN_EGG) && this.isAlive() && !this.isTrading() && !this.isSleeping()) { if (this.isBaby()) { this.setUnhappy(); - return InteractionResult.sidedSuccess(this.level().isClientSide); + return tryRide(player, hand, InteractionResult.sidedSuccess(this.level().isClientSide)); // Purpur } else { boolean flag = this.getOffers().isEmpty(); @@ -332,9 +427,10 @@ public class Villager extends AbstractVillager implements ReputationEventHandler } if (flag) { - return InteractionResult.sidedSuccess(this.level().isClientSide); + return tryRide(player, hand, InteractionResult.sidedSuccess(this.level().isClientSide)); // Purpur } else { - if (!this.level().isClientSide && !this.offers.isEmpty()) { + if (level().purpurConfig.villagerRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur + if (this.level().purpurConfig.villagerAllowTrading && !this.offers.isEmpty()) { this.startTrading(player); } @@ -503,7 +599,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler while (iterator.hasNext()) { MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); - merchantrecipe.updateDemand(); + merchantrecipe.updateDemand(this.level().purpurConfig.villagerMinimumDemand); // Purpur } } @@ -753,7 +849,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler @Override public boolean canBreed() { - return this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0; + return this.level().purpurConfig.villagerCanBreed && this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0; // Purpur } private boolean hungry() { @@ -967,6 +1063,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); }); } @@ -1024,6 +1125,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler } public void spawnGolemIfNeeded(ServerLevel world, long time, int requiredCount) { + if (world.purpurConfig.villagerSpawnIronGolemRadius > 0 && world.getEntitiesOfClass(net.minecraft.world.entity.animal.IronGolem.class, getBoundingBox().inflate(world.purpurConfig.villagerSpawnIronGolemRadius)).size() > world.purpurConfig.villagerSpawnIronGolemLimit) return; // Purpur if (this.wantsToSpawnGolem(time)) { AABB axisalignedbb = this.getBoundingBox().inflate(10.0D, 10.0D, 10.0D); List list = world.getEntitiesOfClass(Villager.class, axisalignedbb); @@ -1088,6 +1190,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler @Override public void startSleeping(BlockPos pos) { + // Purpur start + if (level().purpurConfig.bedExplodeOnVillagerSleep && this.level().getBlockState(pos).getBlock() instanceof net.minecraft.world.level.block.BedBlock) { + this.level().explode(null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (float) this.level().purpurConfig.bedExplosionPower, this.level().purpurConfig.bedExplosionFire, this.level().purpurConfig.bedExplosionEffect); + return; + } + // Purpur end super.startSleeping(pos); this.brain.setMemory(MemoryModuleType.LAST_SLEPT, this.level().getGameTime()); // CraftBukkit - decompile error this.brain.eraseMemory(MemoryModuleType.WALK_TARGET); diff --git a/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java b/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java index 95197b601d93c30a7645d67c89c7608fc00a8de6..b571731bbce25f17568370bb523cb9b0293a2f01 100644 --- a/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java +++ b/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java @@ -26,7 +26,7 @@ public record VillagerProfession(String name, Predicate> heldJob public static final VillagerProfession ARMORER = register("armorer", PoiTypes.ARMORER, SoundEvents.VILLAGER_WORK_ARMORER); public static final VillagerProfession BUTCHER = register("butcher", PoiTypes.BUTCHER, SoundEvents.VILLAGER_WORK_BUTCHER); public static final VillagerProfession CARTOGRAPHER = register("cartographer", PoiTypes.CARTOGRAPHER, SoundEvents.VILLAGER_WORK_CARTOGRAPHER); - public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, SoundEvents.VILLAGER_WORK_CLERIC); + public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, ImmutableSet.of(Items.NETHER_WART), ImmutableSet.of(Blocks.SOUL_SAND), SoundEvents.VILLAGER_WORK_CLERIC); // Purpur public static final VillagerProfession FARMER = register("farmer", PoiTypes.FARMER, new Item[] {Items.WHEAT, Items.WHEAT_SEEDS, Items.BEETROOT_SEEDS, Items.BONE_MEAL}, Blocks.FARMLAND, SoundEvents.VILLAGER_WORK_FARMER); // Gale - optimize villager data storage public static final VillagerProfession FISHERMAN = register("fisherman", PoiTypes.FISHERMAN, SoundEvents.VILLAGER_WORK_FISHERMAN); public static final VillagerProfession FLETCHER = register("fletcher", PoiTypes.FLETCHER, SoundEvents.VILLAGER_WORK_FLETCHER); diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java index 8d1cc1a644415be251f469ab1cb2ebc09fe5c3eb..b133c186d2d1412aa623ba3db68091bc69c282a5 100644 --- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java +++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java @@ -71,6 +71,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)); @@ -78,7 +115,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)); @@ -91,6 +128,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)); @@ -118,9 +156,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 d7bddedb19c10f62fd1f7d3128453ad706ed16be..752b38d45d59d8b3cd492246e5aa4f378a78734d 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 c300b272c820786c8c4b9d83e184c5969468e1e0..fc840fff5fe2dc33986f8e5d3a60c3c853fe6151 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -186,11 +186,21 @@ public abstract class Player extends LivingEntity { public float hurtDir; // Paper - protected -> public public boolean affectsSpawning = true; // Paper - Affects Spawning API public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage + public int sixRowEnderchestSlotCount = -1; // Purpur + public int burpDelay = 0; // Purpur + public boolean canPortalInstant = false; // Purpur // CraftBukkit start public boolean fauxSleeping; public int oldLevel = -1; + public void setAfk(boolean afk) { + } + + public boolean isAfk() { + return false; + } + @Override public CraftHumanEntity getBukkitEntity() { return (CraftHumanEntity) super.getBukkitEntity(); @@ -199,6 +209,19 @@ public abstract class Player extends LivingEntity { public final int sendAllPlayerInfoBucketIndex; // Gale - Purpur - spread out sending all player info + // 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; @@ -244,6 +267,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); @@ -358,6 +387,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() { @@ -448,7 +487,7 @@ public abstract class Player extends LivingEntity { @Override public int getPortalWaitTime() { - return Math.max(1, this.level().getGameRules().getInt(this.abilities.invulnerable ? GameRules.RULE_PLAYERS_NETHER_PORTAL_CREATIVE_DELAY : GameRules.RULE_PLAYERS_NETHER_PORTAL_DEFAULT_DELAY)); + return Math.max(1, canPortalInstant ? 1 : this.level().getGameRules().getInt(this.abilities.invulnerable ? GameRules.RULE_PLAYERS_NETHER_PORTAL_CREATIVE_DELAY : GameRules.RULE_PLAYERS_NETHER_PORTAL_DEFAULT_DELAY)); } @Override @@ -604,7 +643,7 @@ public abstract class Player extends LivingEntity { while (iterator.hasNext()) { Entity entity = (Entity) iterator.next(); - if (entity.getType() == EntityType.EXPERIENCE_ORB) { + if (entity.getType() == EntityType.EXPERIENCE_ORB && entity.level().purpurConfig.playerExpPickupDelay >= 0) { // Purpur list1.add(entity); } else if (!entity.isRemoved()) { this.touch(entity); @@ -1295,7 +1334,7 @@ public abstract class Player extends LivingEntity { flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits flag2 = flag2 && !this.isSprinting(); if (flag2) { - f *= 1.5F; + f *= this.level().purpurConfig.playerCriticalDamageMultiplier; // Purpur } f += f1; @@ -1938,9 +1977,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; } @@ -2016,6 +2065,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()) { @@ -2296,7 +2350,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); } @@ -2390,6 +2444,7 @@ public abstract class Player extends LivingEntity { } public static boolean isValidUsername(String name) { + if (true) return org.purpurmc.purpur.PurpurConfig.usernameValidCharactersPattern.matcher(name).matches(); // Purpur // Paper start - username validation overriding if (name == null || name.isEmpty() || name.length() > 16) { return false; 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 9c7b05fec22a8b84c29d7210f1104030a20cc7aa..6f220fae380575622dee0d99d20704f0f30e883a 100644 --- a/src/main/java/net/minecraft/world/entity/player/StackedContents.java +++ b/src/main/java/net/minecraft/world/entity/player/StackedContents.java @@ -42,7 +42,6 @@ public class StackedContents { if (this.extrasMap != null && stack.hasTag() && this.extrasMap.accountStack(stack, j)) return; // Paper - Improve exact choice recipe ingredients; if an exact ingredient, don't include it this.put(i, j); } - } public static int getStackingIndex(ItemStack stack) { 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 488a98e20b4f405d1ce4a224d2d2b5151dec369b..609695ef44fa46bd9a0418e9ac5fe3d105daa505 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java +++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java @@ -75,6 +75,7 @@ public abstract class AbstractArrow extends Projectile { @Nullable private List piercedAndKilledEntities; public ItemStack pickupItemStack; + public int lootingLevel; // Purpur // Spigot Start @Override @@ -642,6 +643,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 9d89872c5958f3e8d6c1ef4fd93f9b8b85296851..6a94c86acce5afbf1e9c8e7d664b3eb2d79ab5ab 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java +++ b/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java @@ -19,20 +19,20 @@ public class LargeFireball extends Fireball { public LargeFireball(EntityType type, Level world) { super(type, world); - this.isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit + this.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; - this.isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit + this.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 8f5376543cca9cbfb2a014f67ec373d984b0df64..3673d1442778331ece25f8faca95b3499cafe46e 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java +++ b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java @@ -29,6 +29,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 d3a055e221ec62070f6557d1686febadf1249402..4a3fe54e9b3af58cba9b09478f75ea788f4880f2 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java @@ -70,7 +70,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { int maxChunkLoadsPerProjectile = maxProjectileChunkLoadsConfig.perProjectile.max; if (maxChunkLoadsPerProjectile >= 0 && this.chunksLoadedByProjectile >= maxChunkLoadsPerProjectile) { if (maxProjectileChunkLoadsConfig.perProjectile.removeFromWorldAfterReachLimit) { - this.discard(); + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); // Purpur } else if (maxProjectileChunkLoadsConfig.perProjectile.resetMovementAfterReachLimit) { this.setDeltaMovement(0, this.getDeltaMovement().y, 0); } @@ -343,7 +343,7 @@ 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); } public boolean mayBreak(Level world) { diff --git a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java index 6724fe4470aeea338eb4cfd10a7e61fbcac1e5b7..dd38f32ac6a62905c9a79dacf85cf073fa6941b3 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java +++ b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java @@ -27,7 +27,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) { - this.isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + this.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 5e82549ea2e80b3968b793b7b4b685c4891e9a91..bb61e1132c28274175215a679befdcfa2496b099 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java @@ -58,11 +58,41 @@ public class Snowball extends ThrowableItemProjectile { protected void onHitEntity(EntityHitResult entityHitResult) { super.onHitEntity(entityHitResult); Entity entity = entityHitResult.getEntity(); - int i = entity instanceof Blaze ? 3 : 0; + int i = entity.level().purpurConfig.snowballDamage >= 0 ? entity.level().purpurConfig.snowballDamage : entity instanceof Blaze ? 3 : 0; // Purpur entity.hurt(this.damageSources().thrown(this, this.getOwner()), (float) i); } + // Purpur start - borrowed and modified code from ThrownPotion#onHitBlock and ThrownPotion#dowseFire + @Override + protected void onHitBlock(net.minecraft.world.phys.BlockHitResult blockHitResult) { + super.onHitBlock(blockHitResult); + + if (!this.level().isClientSide) { + net.minecraft.core.BlockPos blockposition = blockHitResult.getBlockPos(); + net.minecraft.core.BlockPos blockposition1 = blockposition.relative(blockHitResult.getDirection()); + + net.minecraft.world.level.block.state.BlockState iblockdata = this.level().getBlockState(blockposition); + + if (this.level().purpurConfig.snowballExtinguishesFire && this.level().getBlockState(blockposition1).is(net.minecraft.world.level.block.Blocks.FIRE)) { + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition1, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) { + this.level().removeBlock(blockposition1, false); + } + } else if (this.level().purpurConfig.snowballExtinguishesCandles && net.minecraft.world.level.block.AbstractCandleBlock.isLit(iblockdata)) { + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.setValue(net.minecraft.world.level.block.AbstractCandleBlock.LIT, false))) { + net.minecraft.world.level.block.AbstractCandleBlock.extinguish(null, iblockdata, this.level(), blockposition); + } + } else if (this.level().purpurConfig.snowballExtinguishesCampfires && net.minecraft.world.level.block.CampfireBlock.isLitCampfire(iblockdata)) { + if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false))) { + this.level().levelEvent(null, 1009, blockposition, 0); + net.minecraft.world.level.block.CampfireBlock.dowse(this.getOwner(), this.level(), blockposition, iblockdata); + this.level().setBlockAndUpdate(blockposition, iblockdata.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false)); + } + } + } + } + // Purpur end + @Override protected void onHit(HitResult hitResult) { super.onHit(hitResult); diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java index 28690877c443ceb2bdf20e6d251c9d32f667814c..326d1db39e3d3cd46cb5a584e2a7b82dd46ff8fa 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java +++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java @@ -71,10 +71,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); } @@ -86,7 +87,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { entityplayer.connection.teleport(teleEvent.getTo()); entity.resetFallDistance(); - entity.hurt(this.damageSources().fall().customCausingEntity(this), 5.0F); // CraftBukkit + entity.hurt(this.damageSources().fall().customCausingEntity(this), this.level().purpurConfig.enderPearlDamage); // CraftBukkit // Purpur } // CraftBukkit end this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_TELEPORT, SoundSource.PLAYERS); 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 8ae7d62b72fb72d893e68b02b645d48374595ae6..2bd77524313ae7b32f710e7d197e81a2ddd12965 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java +++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java @@ -63,7 +63,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 5c7a6fe97b1f0b55b4a5dddbb684e4424688f866..dba95e8fbe3e39869e1bbbcc75a010da5cf00262 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java +++ b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java @@ -99,7 +99,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()) { @@ -111,6 +111,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 031355802d86f51489a58561b8806d7d672e4a3b..fb61df507e844a2eed27cbe295ab9378b5f780f5 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raider.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java @@ -322,7 +322,7 @@ public abstract class Raider extends PatrollingMonster { @Override public boolean canUse() { - if (!this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items + if ((!this.mob.level().purpurConfig.pillagerBypassMobGriefing && !this.mob.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items // Purpur Raid raid = this.mob.getCurrentRaid(); if (this.mob.hasActiveRaid() && !this.mob.getCurrentRaid().isOver() && this.mob.canBeLeader() && !ItemStack.matches(this.mob.getItemBySlot(EquipmentSlot.HEAD), Raid.LEADER_BANNER)) { // Gale - Lithium - cache ominous banner item diff --git a/src/main/java/net/minecraft/world/entity/raid/Raids.java b/src/main/java/net/minecraft/world/entity/raid/Raids.java index 31831811ce16265e9828fa34d9e67d8ac195d723..a1f74718240da3dfb0fc53f337ec3bf1636def75 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raids.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raids.java @@ -29,6 +29,7 @@ import net.minecraft.world.phys.Vec3; public class Raids extends SavedData { private static final String RAID_FILE_ID = "raids"; + public final Map playerCooldowns = Maps.newHashMap(); public final Map raidMap = Maps.newHashMap(); private final ServerLevel level; private int nextAvailableID; @@ -54,6 +55,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()) { @@ -138,11 +150,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 d514ec1e4cbdc579c3a61533998437903afdc8b6..eb5bd5cfd131042e366872bf599a315d83dc732b 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java @@ -105,12 +105,14 @@ public abstract class AbstractMinecart extends VehicleEntity { private double flyingY = 0.95; private double flyingZ = 0.95; public double maxSpeed = 0.4D; + public double storedMaxSpeed; // Purpur // CraftBukkit end protected AbstractMinecart(EntityType type, Level world) { super(type, world); this.targetDeltaMovement = Vec3.ZERO; this.blocksBuilding = true; + if (world != null) maxSpeed = storedMaxSpeed = world.purpurConfig.minecartMaxSpeed; // Purpur } protected AbstractMinecart(EntityType type, Level world, double x, double y, double z) { @@ -294,6 +296,12 @@ public abstract class AbstractMinecart extends VehicleEntity { @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(); @@ -451,16 +459,62 @@ public abstract class AbstractMinecart extends VehicleEntity { 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()) { @@ -622,7 +676,7 @@ public abstract class AbstractMinecart extends VehicleEntity { 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 db6aa75d642f4a7258f197933671907faf79c8f2..81e0930acccd014e977b88d22e10346627f0edb0 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java @@ -521,6 +521,7 @@ public class Boat extends VehicleEntity implements VariantHolder { if (f > 0.0F) { this.landFriction = f; + if (level().purpurConfig.boatEjectPlayersOnLand) ejectPassengers(); // Purpur return Boat.Status.ON_LAND; } else { return Boat.Status.IN_AIR; @@ -967,7 +968,13 @@ public class Boat extends VehicleEntity implements VariantHolder { @Override public ItemStack getPickResult() { - return new ItemStack(this.getDropItem()); + // Purpur start + final ItemStack boat = new ItemStack(this.getDropItem()); + if (this.level().purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { + boat.setHoverName(this.getCustomName()); + } + return boat; + // Purpur end } public static enum Type implements StringRepresentable { diff --git a/src/main/java/net/minecraft/world/food/FoodData.java b/src/main/java/net/minecraft/world/food/FoodData.java index c3448707fd8a632b457cc97b35d08a9c6933d5ee..e8079d126e6c0cf0b15c01afb6498922ee05964c 100644 --- a/src/main/java/net/minecraft/world/food/FoodData.java +++ b/src/main/java/net/minecraft/world/food/FoodData.java @@ -33,8 +33,10 @@ public class FoodData { // CraftBukkit end public void eat(int food, float saturationModifier) { + int oldValue = this.foodLevel; // Purpur this.foodLevel = Math.min(food + this.foodLevel, 20); this.saturationLevel = Math.min(this.saturationLevel + (float) food * saturationModifier * 2.0F, (float) this.foodLevel); + if (this.entityhuman.level().purpurConfig.playerBurpWhenFull && this.foodLevel == 20 && oldValue < 20) this.entityhuman.burpDelay = this.entityhuman.level().purpurConfig.playerBurpDelay; // Purpur } public void eat(Item item, ItemStack stack) { @@ -100,7 +102,7 @@ public class FoodData { ++this.tickTimer; if (this.tickTimer >= this.starvationRate) { // CraftBukkit - add regen rate manipulation if (player.getHealth() > 10.0F || enumdifficulty == Difficulty.HARD || player.getHealth() > 1.0F && enumdifficulty == Difficulty.NORMAL) { - player.hurt(player.damageSources().starve(), 1.0F); + player.hurt(player.damageSources().starve(), player.level().purpurConfig.hungerStarvationDamage); // Purpur } this.tickTimer = 0; diff --git a/src/main/java/net/minecraft/world/food/FoodProperties.java b/src/main/java/net/minecraft/world/food/FoodProperties.java index 9967ba762567631f2bdb1e4f8fe16a13ea927b46..6c945ae8fe8b1517e312c688f829fab41f12d9f4 100644 --- a/src/main/java/net/minecraft/world/food/FoodProperties.java +++ b/src/main/java/net/minecraft/world/food/FoodProperties.java @@ -2,15 +2,22 @@ package net.minecraft.world.food; import com.google.common.collect.Lists; import com.mojang.datafixers.util.Pair; + +import java.util.ArrayList; import java.util.List; import net.minecraft.world.effect.MobEffectInstance; public class FoodProperties { - private final int nutrition; - private final float saturationModifier; - private final boolean isMeat; - private final boolean canAlwaysEat; - private final boolean fastFood; + // Purpur start + private int nutrition; public void setNutrition(int nutrition) { this.nutrition = nutrition; } + private float saturationModifier; public void setSaturationModifier(float saturation) { this.saturationModifier = saturation; } + private boolean isMeat; public void setIsMeat(boolean isMeat) { this.isMeat = isMeat; } + private boolean canAlwaysEat; public void setCanAlwaysEat(boolean canAlwaysEat) { this.canAlwaysEat = canAlwaysEat; } + private boolean fastFood; public void setFastFood(boolean isFastFood) { this.fastFood = isFastFood; } + public FoodProperties copy() { + return new FoodProperties(this.nutrition, this.saturationModifier, this.isMeat, this.canAlwaysEat, this.fastFood, new ArrayList<>(this.effects)); + } + // Purpur end private final List> effects; FoodProperties(int hunger, float saturationModifier, boolean meat, boolean alwaysEdible, boolean snack, List> statusEffects) { diff --git a/src/main/java/net/minecraft/world/food/Foods.java b/src/main/java/net/minecraft/world/food/Foods.java index b16d9e2eaa589f19c563ee70b1a56d67dbcdecb0..71beab673f04cd051c46ea37f8c847316885d38d 100644 --- a/src/main/java/net/minecraft/world/food/Foods.java +++ b/src/main/java/net/minecraft/world/food/Foods.java @@ -4,6 +4,9 @@ import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffects; public class Foods { + public static final java.util.Map ALL_PROPERTIES = new java.util.HashMap<>(); // Purpur + public static final java.util.Map DEFAULT_PROPERTIES = new java.util.HashMap<>(); // Purpur + public static final FoodProperties APPLE = (new FoodProperties.Builder()).nutrition(4).saturationMod(0.3F).build(); public static final FoodProperties BAKED_POTATO = (new FoodProperties.Builder()).nutrition(5).saturationMod(0.6F).build(); public static final FoodProperties BEEF = (new FoodProperties.Builder()).nutrition(3).saturationMod(0.3F).meat().build(); diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java index 48f634a7521d31c1e9dfd3cfc83139d428dbd37a..fa185a8145843edf44fc0aeedb6c36b2b13263ae 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 1af7e1548f0648890a1ef2fc0ff4e4c3a56c947c..decea1697c075e7549ccc7501c8e59357d198a60 100644 --- a/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java @@ -147,7 +147,13 @@ public abstract class AbstractFurnaceMenu extends RecipeBookMenu { } else if (slot != 1 && slot != 0) { if (this.canSmelt(itemstack1)) { if (!this.moveItemStackTo(itemstack1, 0, 1, false)) { - return ItemStack.EMPTY; + // Purpur start - fix #625 + if (this.isFuel(itemstack1)) { + if (!this.moveItemStackTo(itemstack1, 1, 2, false)) { + return ItemStack.EMPTY; + } + } + // Purpur end } } else if (this.isFuel(itemstack1)) { if (!this.moveItemStackTo(itemstack1, 1, 2, false)) { diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java index cab3e0ba471c93764b5949ad68a0f2cce4d00099..2913d69fcff4b6df68586146b7323cea33eba74b 100644 --- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java @@ -23,6 +23,13 @@ import org.slf4j.Logger; import org.bukkit.craftbukkit.inventory.CraftInventoryView; // CraftBukkit end +// Purpur start +import net.minecraft.nbt.IntTag; +import net.minecraft.network.protocol.game.ClientboundContainerSetDataPacket; +import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; +import net.minecraft.server.level.ServerPlayer; +// Purpur end + public class AnvilMenu extends ItemCombinerMenu { public static final int INPUT_SLOT = 0; @@ -51,6 +58,8 @@ public class AnvilMenu extends ItemCombinerMenu { public int maximumRepairCost = 40; private CraftInventoryView bukkitEntity; // CraftBukkit end + public boolean bypassCost = false; // Purpur + public boolean canDoUnsafeEnchants = false; // Purpur public AnvilMenu(int syncId, Inventory inventory) { this(syncId, inventory, ContainerLevelAccess.NULL); @@ -78,12 +87,15 @@ public class AnvilMenu extends ItemCombinerMenu { @Override protected boolean mayPickup(Player player, boolean present) { - return (player.getAbilities().instabuild || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && present; // CraftBukkit - allow cost 0 like a free item + return (player.getAbilities().instabuild || player.experienceLevel >= this.cost.get()) && (bypassCost || this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST) && present; // CraftBukkit - allow cost 0 like a free item // Purpur } @Override protected void onTake(Player player, ItemStack stack) { + ItemStack itemstack = activeQuickItem == null ? stack : activeQuickItem; // Purpur + if (org.purpurmc.purpur.event.inventory.AnvilTakeResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)).callEvent(); // Purpur if (!player.getAbilities().instabuild) { + if (bypassCost) ((ServerPlayer) player).lastSentExp = -1; else // Purpur player.giveExperienceLevels(-this.cost.get()); } @@ -134,6 +146,12 @@ public class AnvilMenu extends ItemCombinerMenu { @Override public void createResult() { + // Purpur start + bypassCost = false; + canDoUnsafeEnchants = false; + if (org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent(getBukkitView()).callEvent(); + // Purpur end + ItemStack itemstack = this.inputSlots.getItem(0); this.cost.set(1); @@ -210,7 +228,8 @@ public class AnvilMenu extends ItemCombinerMenu { int i2 = (Integer) map1.get(enchantment); i2 = l1 == i2 ? i2 + 1 : Math.max(i2, l1); - boolean flag3 = enchantment.canEnchant(itemstack); + boolean flag3 = canDoUnsafeEnchants || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants && org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants) || enchantment.canEnchant(itemstack); // Purpur + boolean flag4 = true; // Purpur if (this.player.getAbilities().instabuild || itemstack.is(Items.ENCHANTED_BOOK)) { flag3 = true; @@ -222,16 +241,20 @@ public class AnvilMenu extends ItemCombinerMenu { Enchantment enchantment1 = (Enchantment) iterator1.next(); if (enchantment1 != enchantment && !enchantment.isCompatibleWith(enchantment1)) { - flag3 = false; + flag4 = canDoUnsafeEnchants || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants && org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants); // Purpur flag3 -> flag4 + if (!flag4 && org.purpurmc.purpur.PurpurConfig.replaceIncompatibleEnchants) { + iterator1.remove(); + flag4 = true; + } ++i; } } - if (!flag3) { + if (!flag3 || !flag4) { // Purpur flag2 = true; } else { flag1 = true; - if (i2 > enchantment.getMaxLevel()) { + if ((!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants || !org.purpurmc.purpur.PurpurConfig.allowHigherEnchantsLevels) && i2 > enchantment.getMaxLevel()) { // Purpur i2 = enchantment.getMaxLevel(); } @@ -276,6 +299,54 @@ public class AnvilMenu extends ItemCombinerMenu { if (!this.itemName.equals(itemstack.getHoverName().getString())) { b1 = 1; i += b1; + // Purpur start + if (this.player != null) { + org.bukkit.craftbukkit.entity.CraftHumanEntity player = this.player.getBukkitEntity(); + String name = this.itemName; + boolean removeItalics = false; + if (player.hasPermission("purpur.anvil.remove_italics")) { + if (name.startsWith("&r")) { + name = name.substring(2); + removeItalics = true; + } else if (name.startsWith("")) { + name = name.substring(3); + removeItalics = true; + } else if (name.startsWith("")) { + name = name.substring(7); + removeItalics = true; + } + } + if (this.player.level().purpurConfig.anvilAllowColors) { + if (player.hasPermission("purpur.anvil.color")) { + java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?i)&([0-9a-fr])").matcher(name); + while (matcher.find()) { + String match = matcher.group(1); + name = name.replace("&" + match, "\u00a7" + match.toLowerCase(java.util.Locale.ROOT)); + } + //name = name.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1"); + } + if (player.hasPermission("purpur.anvil.format")) { + java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?i)&([k-or])").matcher(name); + while (matcher.find()) { + String match = matcher.group(1); + name = name.replace("&" + match, "\u00a7" + match.toLowerCase(java.util.Locale.ROOT)); + } + //name = name.replaceAll("(?i)&([l-or])", "\u00a7$1"); + } + } + net.kyori.adventure.text.Component component; + if (this.player.level().purpurConfig.anvilColorsUseMiniMessage && player.hasPermission("purpur.anvil.minimessage")) { + component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.bukkit.ChatColor.stripColor(name)); + } else { + component = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(name); + } + if (removeItalics) { + component = component.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); + } + itemstack1.setHoverName(io.papermc.paper.adventure.PaperAdventure.asVanilla(component)); + } + else + // Purpur end itemstack1.setHoverName(Component.literal(this.itemName)); } } else if (itemstack.hasCustomHoverName()) { @@ -293,6 +364,13 @@ public class AnvilMenu extends ItemCombinerMenu { this.cost.set(this.maximumRepairCost - 1); // CraftBukkit } + // Purpur start + if (bypassCost && cost.get() >= maximumRepairCost) { + itemstack.addTagElement("Purpur.realCost", IntTag.valueOf(cost.get())); + cost.set(maximumRepairCost - 1); + } + // Purpur end + if (this.cost.get() >= this.maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit itemstack1 = ItemStack.EMPTY; } @@ -315,11 +393,17 @@ public class AnvilMenu extends ItemCombinerMenu { org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemstack1); // CraftBukkit this.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 343f44db579839eb61376f876b5eff2e615dc2e5..849b6a30d412d10f1e2e6e88f2d8d990e4a720e4 100644 --- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java +++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java @@ -40,6 +40,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 { static final ResourceLocation EMPTY_SLOT_LAPIS_LAZULI = new ResourceLocation("item/empty_slot_lapis_lazuli"); @@ -74,6 +80,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(); @@ -99,6 +121,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) { @@ -344,6 +377,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 23462de504932bd351b8dfacde514fe361343912..af99ce32872e079beb6ac1caf3a8ac4c3cae4648 100644 --- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java +++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java @@ -95,9 +95,11 @@ public class GrindstoneMenu extends AbstractContainerMenu { @Override public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) { + ItemStack itemstack = activeQuickItem == null ? stack : activeQuickItem; // Purpur context.execute((world, blockposition) -> { + org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent grindstoneTakeResultEvent = new org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), this.getExperienceAmount(world)); grindstoneTakeResultEvent.callEvent(); // Purpur if (world instanceof ServerLevel) { - ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Paper + ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), grindstoneTakeResultEvent.getExperienceAmount(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Paper // Purpur } world.levelEvent(1042, blockposition, 0); @@ -130,7 +132,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { Enchantment enchantment = (Enchantment) entry.getKey(); Integer integer = (Integer) entry.getValue(); - if (!enchantment.isCurse()) { + if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(enchantment)) { // Purpur j += enchantment.getMinCost(integer); } } @@ -230,7 +232,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { Entry entry = (Entry) iterator.next(); Enchantment enchantment = (Enchantment) entry.getKey(); - if (!enchantment.isCurse() || EnchantmentHelper.getItemEnchantmentLevel(enchantment, itemstack2) == 0) { + if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(enchantment) || EnchantmentHelper.getItemEnchantmentLevel(enchantment, itemstack2) == 0) { // Purpur itemstack2.enchant(enchantment, (Integer) entry.getValue()); } } @@ -250,7 +252,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { } Map map = (Map) EnchantmentHelper.getEnchantments(item).entrySet().stream().filter((entry) -> { - return ((Enchantment) entry.getKey()).isCurse(); + return org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains((Enchantment) entry.getKey()); // Purpur }).collect(Collectors.toMap(Entry::getKey, Entry::getValue)); EnchantmentHelper.setEnchantments(map, itemstack1); @@ -266,6 +268,20 @@ public class GrindstoneMenu extends AbstractContainerMenu { itemstack1.setRepairCost(AnvilMenu.calculateIncreasedRepairCost(itemstack1.getBaseRepairCost())); } + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveAttributes && itemstack1.getTag() != null) { + for (String key : itemstack1.getTag().getAllKeys()) { + if (!key.equals("display")) { + itemstack1.getTag().remove(key); + } + } + } + + if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveDisplay && itemstack1.getTag() != null) { + itemstack1.getTag().remove("display"); + } + // Purpur end + return itemstack1; } @@ -327,7 +343,9 @@ public class GrindstoneMenu extends AbstractContainerMenu { return ItemStack.EMPTY; } + this.activeQuickItem = itemstack; // Purpur slot1.onTake(player, itemstack1); + this.activeQuickItem = null; // Purpur } return itemstack; diff --git a/src/main/java/net/minecraft/world/inventory/InventoryMenu.java b/src/main/java/net/minecraft/world/inventory/InventoryMenu.java index 9af1da3858d6cf79b8bfaf99dde1370ccc50d023..1acb41fab25bdbc4109913b111dbe3b0e106af3f 100644 --- a/src/main/java/net/minecraft/world/inventory/InventoryMenu.java +++ b/src/main/java/net/minecraft/world/inventory/InventoryMenu.java @@ -95,7 +95,7 @@ public class InventoryMenu extends RecipeBookMenu { public boolean mayPickup(Player playerEntity) { ItemStack itemstack = this.getItem(); - return !itemstack.isEmpty() && !playerEntity.isCreative() && EnchantmentHelper.hasBindingCurse(itemstack) ? false : super.mayPickup(playerEntity); + return !itemstack.isEmpty() && !playerEntity.isCreative() && EnchantmentHelper.hasBindingCurse(itemstack) ? playerEntity.level().purpurConfig.playerRemoveBindingWithWeakness && playerEntity.hasEffect(net.minecraft.world.effect.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 62e1b7096fa659778b737b3d520389e73138dc5d..3756de835ea87e3a4fb87cbf77365ffd87957ea9 100644 --- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java +++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java @@ -178,7 +178,9 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { return ItemStack.EMPTY; } + this.activeQuickItem = itemstack; // Purpur slot1.onTake(player, itemstack1); + this.activeQuickItem = null; // Purpur } return itemstack; diff --git a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java index 4703f23316f82a1a942907b46d2d6dcb7d70ec37..162798f57a05b78121fa6c4fadf5adee80fbe221 100644 --- a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java +++ b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java @@ -30,11 +30,18 @@ public class PlayerEnderChestContainer extends SimpleContainer { } public PlayerEnderChestContainer(Player owner) { - super(27); + super(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? 54 : 27); // Purpur this.owner = owner; // CraftBukkit end } + // Purpur start + @Override + public int getContainerSize() { + return owner.sixRowEnderchestSlotCount < 0 ? super.getContainerSize() : owner.sixRowEnderchestSlotCount; + } + // Purpur end + public void setActiveChest(EnderChestBlockEntity blockEntity) { this.activeChest = blockEntity; } diff --git a/src/main/java/net/minecraft/world/item/ArmorItem.java b/src/main/java/net/minecraft/world/item/ArmorItem.java index 6b81be03f87967124b046708557e05d519aa79e4..b47cc957f9e4936b15b80a6f685ddddb5b289298 100644 --- a/src/main/java/net/minecraft/world/item/ArmorItem.java +++ b/src/main/java/net/minecraft/world/item/ArmorItem.java @@ -67,7 +67,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.level().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.level(); 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 2e75fd06e9e379eb95ebfe55086ffc327706ab2f..2918b1aca8fae6a319881a631dc727b6d375a33c 100644 --- a/src/main/java/net/minecraft/world/item/AxeItem.java +++ b/src/main/java/net/minecraft/world/item/AxeItem.java @@ -33,13 +33,15 @@ public class AxeItem extends DiggerItem { Level level = context.getLevel(); BlockPos blockPos = context.getClickedPos(); Player player = context.getPlayer(); - Optional optional = this.evaluateNewBlockState(level, blockPos, player, level.getBlockState(blockPos)); + Optional optional = this.evaluateActionable(level, blockPos, player, level.getBlockState(blockPos)); // Purpur if (optional.isEmpty()) { return InteractionResult.PASS; } else { + org.purpurmc.purpur.tool.Actionable actionable = optional.get(); // Purpur + BlockState state = actionable.into().withPropertiesOf(level.getBlockState(blockPos)); // Purpur ItemStack itemStack = context.getItemInHand(); // Paper start - EntityChangeBlockEvent - if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional.get())) { + if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state)) { // Purpur return InteractionResult.PASS; } // Paper end @@ -47,35 +49,40 @@ public class AxeItem extends DiggerItem { CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); } - level.setBlock(blockPos, optional.get(), 11); - level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, optional.get())); + // Purpur start + level.setBlock(blockPos, state, 11); + actionable.drops().forEach((drop, chance) -> { + if (level.random.nextDouble() < chance) { + Block.popResourceFromFace(level, blockPos, context.getClickedFace(), new ItemStack(drop)); + } + }); + level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, state)); + // Purpur end if (player != null) { itemStack.hurtAndBreak(1, player, (p) -> { p.broadcastBreakEvent(context.getHand()); }); } - return InteractionResult.sidedSuccess(level.isClientSide); + return InteractionResult.SUCCESS; // Purpur - force arm swing } } - private Optional evaluateNewBlockState(Level world, BlockPos pos, @Nullable Player player, BlockState state) { - Optional optional = this.getStripped(state); + private Optional evaluateActionable(Level world, BlockPos pos, @Nullable Player player, BlockState state) { // Purpur + Optional optional = Optional.ofNullable(world.purpurConfig.axeStrippables.get(state.getBlock())); // Purpur if (optional.isPresent()) { - world.playSound(player, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); + world.playSound(STRIPPABLES.containsKey(state.getBlock()) ? player : null, pos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound return optional; } else { - Optional optional2 = WeatheringCopper.getPrevious(state); + Optional optional2 = Optional.ofNullable(world.purpurConfig.axeWeatherables.get(state.getBlock())); // Purpur if (optional2.isPresent()) { - world.playSound(player, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); + world.playSound(WeatheringCopper.getPrevious(state).isPresent() ? player : null, pos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound world.levelEvent(player, 3005, pos, 0); return optional2; } else { - Optional optional3 = Optional.ofNullable(HoneycombItem.WAX_OFF_BY_BLOCK.get().get(state.getBlock())).map((block) -> { - return block.withPropertiesOf(state); - }); + Optional optional3 = Optional.ofNullable(world.purpurConfig.axeWaxables.get(state.getBlock())); // Purpur if (optional3.isPresent()) { - world.playSound(player, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); + world.playSound(HoneycombItem.WAX_OFF_BY_BLOCK.get().containsKey(state.getBlock()) ? player : null, pos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound world.levelEvent(player, 3004, pos, 0); return optional3; } else { diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java index 8d2c0accadaf0c5d28e7db6e62a05f6c619cf02f..f33fcd7bca6f315c4b4cf1f5063f4772790ad20d 100644 --- a/src/main/java/net/minecraft/world/item/BlockItem.java +++ b/src/main/java/net/minecraft/world/item/BlockItem.java @@ -152,7 +152,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 @@ -287,7 +304,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 67a5a201d0b26ca7b27e6d0c3ffb9f8b6e16bce0..ec3d60b561de45349b705b7f14592be930af4b91 100644 --- a/src/main/java/net/minecraft/world/item/BoatItem.java +++ b/src/main/java/net/minecraft/world/item/BoatItem.java @@ -71,6 +71,11 @@ public class BoatItem extends Item { 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 6371f326fc86cfc53e39bf8ed13b646f7705fbbc..3dec0c5fc8dece5341634eaf8e94fe1964bf4038 100644 --- a/src/main/java/net/minecraft/world/item/BucketItem.java +++ b/src/main/java/net/minecraft/world/item/BucketItem.java @@ -195,7 +195,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { // CraftBukkit end if (!flag2) { return movingobjectpositionblock != null && this.emptyContents(entityhuman, world, movingobjectpositionblock.getBlockPos().relative(movingobjectpositionblock.getDirection()), (BlockHitResult) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit - } else if (world.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) { + } else if ((world.dimensionType().ultraWarm() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) && this.content.is(FluidTags.WATER)) { // Purpur int i = blockposition.getX(); int j = blockposition.getY(); int k = blockposition.getZ(); @@ -203,7 +203,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 f3a428f80c265639250114498b10067b4bf1ada1..211d8e59a9b3460b346e5f8cf581df70b05d1b8f 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 - Add EntityLoadCrossbowEvent 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 EntityLoadCrossbowEvent + boolean flag = !consume || shooter instanceof Player && ((Player) shooter).getAbilities().instabuild || (org.purpurmc.purpur.PurpurConfig.allowCrossbowInfinity && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY_ARROWS, crossbow) > 0); // Paper - Add EntityLoadCrossbowEvent // 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 a3bd507793994e9cc87a956871a8afbb8ca9460d..ef2197a23aef0a4215fae09bd4618e449e14c64e 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 - PlayerLaunchProjectileEvent com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) 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 a0bc8bb8d88e96457a2be0befe1fd69ef4973e11..26b0084f0b8c707f6d2b90e4cf6de7e92b4617c0 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 8c8cf8705107c95d9a4eab28b5845ae13c4ffb3c..8031e38c66468676b3b4a7443d6678eec6b1e8a4 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 - PlayerLaunchProjectileEvent com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) 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 - PlayerLaunchProjectileEvent if (user instanceof net.minecraft.server.level.ServerPlayer) { diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java index 0821c06a4c66edc8fcee09fc192335a588d2944b..56d7c05c93bc074f6caba51b1741f25d6f0861cd 100644 --- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java +++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java @@ -15,6 +15,7 @@ import net.minecraft.util.ByIdMap; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.FireworkRocketEntity; import net.minecraft.world.item.context.UseOnContext; @@ -69,6 +70,14 @@ public class FireworkRocketItem extends Item { com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) fireworkRocketEntity.getBukkitEntity()); if (event.callEvent() && world.addFreshEntity(fireworkRocketEntity)) { user.awardStat(Stats.ITEM_USED.get(this)); + // Purpur start + if (world.purpurConfig.elytraDamagePerFireworkBoost > 0) { + ItemStack chestItem = user.getItemBySlot(EquipmentSlot.CHEST); + if (chestItem.getItem() == Items.ELYTRA) { + chestItem.hurtAndBreak(world.purpurConfig.elytraDamagePerFireworkBoost, user, (entityliving) -> entityliving.broadcastBreakEvent(EquipmentSlot.CHEST)); + } + } + // Purpur end if (event.shouldConsume() && !user.getAbilities().instabuild) { itemStack.shrink(1); } else ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); diff --git a/src/main/java/net/minecraft/world/item/HangingEntityItem.java b/src/main/java/net/minecraft/world/item/HangingEntityItem.java index b2ad6d230de2c29f371178bccde1111c7532ee70..6667926519a0f1c151e53f59cce36e7417dfc1cd 100644 --- a/src/main/java/net/minecraft/world/item/HangingEntityItem.java +++ b/src/main/java/net/minecraft/world/item/HangingEntityItem.java @@ -48,7 +48,7 @@ public class HangingEntityItem extends Item { return InteractionResult.FAIL; } else { Level world = context.getLevel(); - Object object; + Entity object; // Purpur if (this.type == EntityType.PAINTING) { Optional optional = Painting.create(world, blockposition1, enumdirection); @@ -72,6 +72,11 @@ public class HangingEntityItem extends Item { if (nbttagcompound != null) { EntityType.updateCustomEntityTag(world, entityhuman, (Entity) object, nbttagcompound); + // Purpur start + if (world.purpurConfig.persistentDroppableEntityDisplayNames && itemstack.hasCustomHoverName()) { + object.setCustomName(itemstack.getHoverName()); + } + // Purpur end } if (((HangingEntity) object).survives()) { diff --git a/src/main/java/net/minecraft/world/item/HoeItem.java b/src/main/java/net/minecraft/world/item/HoeItem.java index 180aec596110309aade13d2080f8824d152b07cb..552c31c0f3746dd35388395036e70a925bf00bd4 100644 --- a/src/main/java/net/minecraft/world/item/HoeItem.java +++ b/src/main/java/net/minecraft/world/item/HoeItem.java @@ -34,15 +34,23 @@ public class HoeItem extends DiggerItem { public InteractionResult useOn(UseOnContext context) { Level level = context.getLevel(); BlockPos blockPos = context.getClickedPos(); - Pair, Consumer> pair = TILLABLES.get(level.getBlockState(blockPos).getBlock()); - if (pair == null) { - return InteractionResult.PASS; - } else { - Predicate predicate = pair.getFirst(); - Consumer consumer = pair.getSecond(); + // Purpur start + Block clickedBlock = level.getBlockState(blockPos).getBlock(); + var tillable = level.purpurConfig.hoeTillables.get(clickedBlock); + if (tillable == null) { return InteractionResult.PASS; } else { + Predicate predicate = tillable.condition().predicate(); + Consumer consumer = (ctx) -> { + level.setBlock(blockPos, tillable.into().defaultBlockState(), 11); + tillable.drops().forEach((drop, chance) -> { + if (level.random.nextDouble() < chance) { + Block.popResourceFromFace(level, blockPos, ctx.getClickedFace(), new ItemStack(drop)); + } + }); + }; + // Purpur end if (predicate.test(context)) { Player player = context.getPlayer(); - level.playSound(player, blockPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F); + if (!TILLABLES.containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound if (!level.isClientSide) { consumer.accept(context); if (player != null) { @@ -52,7 +60,7 @@ public class HoeItem extends DiggerItem { } } - return InteractionResult.sidedSuccess(level.isClientSide); + return InteractionResult.SUCCESS; // Purpur - force arm swing } else { return InteractionResult.PASS; } diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java index a39201ea35edb1be068ff7269dcee614d0477050..d997e54428902fda805c3a79d8b112c76e15d363 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -454,6 +454,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; @@ -485,6 +486,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 } @@ -613,6 +615,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"); } @@ -632,7 +644,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) { @@ -687,6 +699,12 @@ public final class ItemStack { if (this.hurt(amount, entity.getRandom(), entity /*instanceof ServerPlayer ? (ServerPlayer) entity : null*/)) { // Paper - Add EntityDamageItemEvent 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); @@ -1217,7 +1235,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 } @@ -1225,6 +1243,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 f692149d91b525bda6dc79d489d7496ea24037e8..cf68dca4096556b0c2594c76fcf113419e56dae0 100644 --- a/src/main/java/net/minecraft/world/item/Items.java +++ b/src/main/java/net/minecraft/world/item/Items.java @@ -316,7 +316,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); @@ -1224,7 +1224,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); @@ -1367,6 +1367,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 d8dd99ec8bf7444c5a3c426db3a9c13e334dc0ff..8d3c1897044f9a2bbe1911e1a72dc9a00fb246df 100644 --- a/src/main/java/net/minecraft/world/item/MapItem.java +++ b/src/main/java/net/minecraft/world/item/MapItem.java @@ -235,6 +235,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 3aa73cd44aa8c86b78c35bc1788e4f83018c49ed..66a8b28275619079e3bcbcc460146976d533d54e 100644 --- a/src/main/java/net/minecraft/world/item/MinecartItem.java +++ b/src/main/java/net/minecraft/world/item/MinecartItem.java @@ -119,8 +119,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 instanceof ServerLevel) { @@ -145,6 +146,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 a0b3c2d3b3f86ecd6cb80ff767839d29ca4f4f68..61b4430d6dd73b5406c4879e41ff80a1b6f67f84 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 newEntity = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); newEntity.setCustomName(event.getName() != null ? io.papermc.paper.adventure.PaperAdventure.asVanilla(event.getName()) : null); + if (user.level().purpurConfig.armorstandFixNametags && entity instanceof net.minecraft.world.entity.decoration.ArmorStand) entity.setCustomNameVisible(true); // Purpur if (event.isPersistent() && newEntity instanceof Mob) { ((Mob) newEntity).setPersistenceRequired(); // Paper end - Add PlayerNameEntityEvent 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 bc8186a5bc3a98b35fad570729dd4ba52efab238..caab0c1e2bc5696080750797cbf1c93f57799f7d 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 - PlayerLaunchProjectileEvent com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) 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 3bfbf7daa190b03f978b9fc72233c18c383b6659..afed52ed201193867f19054c5b379270a6fbe480 100644 --- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java +++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java @@ -68,6 +68,16 @@ public class SpawnEggItem extends Item { Spawner spawner = (Spawner) tileentity; 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 + spawner.setEntityId(entitytypes, world.getRandom()); world.sendBlockUpdated(blockposition, iblockdata, iblockdata, 3); world.gameEvent((Entity) context.getPlayer(), GameEvent.BLOCK_CHANGE, blockposition); diff --git a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java index f47f793c62a919fb65c081ddb82d597a978d3b20..3bbb44ae3da68afbd6012df68dee277a7dbf98c0 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 - PlayerLaunchProjectileEvent com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownPotion.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 fa876ddf54780728e7f3ecfe02aa8a16b8ef6f8d..dcd6f241271beecf9601e34547225d7921958c20 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 - PlayerLaunchProjectileEvent com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) entitythrowntrident.getBukkitEntity()); @@ -131,6 +139,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 7c29750e534eae4266bf7a63c50e3827401d6569..e8e9a3370ba07dc0ca47c8352f6f04a449f2268f 100644 --- a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java +++ b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java @@ -36,6 +36,7 @@ public final class Ingredient implements Predicate { @Nullable private IntList stackingIds; public boolean exact; // CraftBukkit + public Predicate predicate; // Purpur public static final Codec CODEC = Ingredient.codec(true); public static final Codec CODEC_NONEMPTY = Ingredient.codec(false); @@ -67,6 +68,12 @@ public final class Ingredient implements Predicate { } else if (this.isEmpty()) { return itemstack.isEmpty(); } else { + // Purpur start + if (predicate != null) { + return predicate.test(itemstack.asBukkitCopy()); + } + // Purpur end + ItemStack[] aitemstack = this.getItems(); int i = aitemstack.length; diff --git a/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java b/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java index 518d85a13c37a2f7d32ca0718323181048559986..27512787b37381a5236b1b473e9ce3f06df8e2d0 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java +++ b/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java @@ -7,6 +7,14 @@ public class ArrowInfiniteEnchantment extends Enchantment { super(weight, EnchantmentCategory.BOW, slotTypes); } + // Purpur start + @Override + public boolean canEnchant(net.minecraft.world.item.ItemStack stack) { + // we have to cheat the system because this class is loaded before purpur's config is loaded + return (org.purpurmc.purpur.PurpurConfig.allowCrossbowInfinity ? EnchantmentCategory.BOW_AND_CROSSBOW : EnchantmentCategory.BOW).canEnchant(stack.getItem()); + } + // Purpur end + @Override public int getMinCost(int level) { return 20; @@ -19,6 +27,6 @@ public class ArrowInfiniteEnchantment extends Enchantment { @Override public boolean checkCompatibility(Enchantment other) { - return other instanceof MendingEnchantment ? false : super.checkCompatibility(other); + return other instanceof MendingEnchantment ? org.purpurmc.purpur.PurpurConfig.allowInfinityMending : super.checkCompatibility(other); } } diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java index 246516e67db0b8b197b287c067d5a0163d8bde22..fc2c35f57436371cb0111aedfd289ac95d506d07 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java +++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java @@ -121,6 +121,20 @@ public enum EnchantmentCategory { public boolean canEnchant(Item item) { return item instanceof Vanishable || Block.byItem(item) instanceof Vanishable || BREAKABLE.canEnchant(item); } + // Purpur start + }, + BOW_AND_CROSSBOW { + @Override + public boolean canEnchant(Item item) { + return item instanceof BowItem || item instanceof CrossbowItem; + } + }, + WEAPON_AND_SHEARS { + @Override + public boolean canEnchant(Item item) { + return WEAPON.canEnchant(item) || item instanceof net.minecraft.world.item.ShearsItem; + } + // Purpur end }; public abstract boolean canEnchant(Item item); diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java index ecf640b00007a386290f8dfe9935a8aa610079fd..1eec84e217f6dc929091fa7451cd235ef3623822 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java +++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java @@ -46,7 +46,7 @@ public class EnchantmentHelper { } public static int getEnchantmentLevel(CompoundTag nbt) { - return Mth.clamp(nbt.getInt("lvl"), 0, 255); + return Mth.clamp(nbt.getInt("lvl"), 0, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels) ? 255 : 32767); // Purpur } @Nullable @@ -278,6 +278,29 @@ public class EnchantmentHelper { return getItemEnchantmentLevel(Enchantments.CHANNELING, stack) > 0; } + // Purpur start + @Nullable + public static Map.Entry getMostDamagedEquipment(Enchantment enchantment, LivingEntity entity) { + Map map = enchantment.getSlotItems(entity); + if (map.isEmpty()) { + return null; + } + Map.Entry item = null; + float maxPercent = 0F; + for (Map.Entry entry : map.entrySet()) { + ItemStack itemstack = entry.getValue(); + if (!itemstack.isEmpty() && itemstack.isDamaged() && getItemEnchantmentLevel(enchantment, itemstack) > 0) { + float percent = itemstack.getDamagePercent(); + if (item == null || percent > maxPercent) { + item = entry; + maxPercent = percent; + } + } + } + return item; + } + // Purpur end + @Nullable public static Map.Entry getRandomItemWith(Enchantment enchantment, LivingEntity entity) { return getRandomItemWith(enchantment, entity, (stack) -> { diff --git a/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java b/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java index 7f1ffc0ac402fcf0ec086986e959ecc9f78dde03..1351d52374d1c2367932e5ecd5f4637955fb14c9 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(rarity, 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 02feea12c998f37098b72becf6bfaf6b27d155de..9c89a85d934955c9388cfe1361f13e70e699d279 100644 --- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java +++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java @@ -149,7 +149,12 @@ public class MerchantOffer { } public void updateDemand() { - this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962 + // Purpur start + this.updateDemand(0); + } + public void updateDemand(int minimumDemand) { + this.demand = Math.max(minimumDemand, this.demand + this.uses - (this.maxUses - this.uses)); // Paper - Fix MC-163962 + // Purpur end } public ItemStack assemble() { diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java index 65c3e91ac4541c0150057dc9f012eb1ee566516e..40c199812ecf7b16fe5a17c18cb0d6d3ce258910 100644 --- a/src/main/java/net/minecraft/world/level/BaseSpawner.java +++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java @@ -57,6 +57,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 ed84c87a3f76bc0254c1abb189e6b8b808823465..5bc68b1ef33f297e9a4b1bdd7d69f86a46ad6fbd 100644 --- a/src/main/java/net/minecraft/world/level/EntityGetter.java +++ b/src/main/java/net/minecraft/world/level/EntityGetter.java @@ -191,7 +191,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 aa784c1794b80b57fd9c1d738a744e0a8ab39d03..68fc000a76f62d764ee60f0f4e3af3121e6c7a57 100644 --- a/src/main/java/net/minecraft/world/level/Explosion.java +++ b/src/main/java/net/minecraft/world/level/Explosion.java @@ -97,7 +97,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; @@ -425,10 +425,27 @@ public class Explosion { public void explode() { // CraftBukkit start - if (this.radius < 0.1F) { + if ((this.level == null || this.level.purpurConfig.explosionClampRadius) && this.radius < 0.1F) { // Purpur return; } // CraftBukkit end + + // Purpur start - add PreExplodeEvents + if(this.source != null){ + Location location = new Location(this.level.getWorld(), this.x, this.y, this.z); + if(!new org.purpurmc.purpur.event.entity.PreEntityExplodeEvent(this.source.getBukkitEntity(), location, this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F).callEvent()) { + this.wasCanceled = true; + return; + } + }else { + Location location = new Location(this.level.getWorld(), this.x, this.y, this.z); + if(!new org.purpurmc.purpur.event.PreBlockExplodeEvent(location.getBlock(), this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F, this.damageSource.explodedBlockState).callEvent()) { + this.wasCanceled = true; + return; + } + } + //Purpur end + this.level.gameEvent(this.source, GameEvent.EXPLODE, new Vec3(this.x, this.y, this.z)); Set set = Sets.newHashSet(); boolean flag = true; diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java index 33cbba9f92cfbb6508dd8e2c5e35c63a49cdd85c..7583ce583b06306930cbae49b0386a6630bb916f 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -175,6 +175,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { // Gale end - Gale configuration public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray + public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur public final co.aikar.timings.WorldTimingsHandler timings; // Paper public static BlockPos lastPhysicsProblem; // Spigot private org.spigotmc.TickLimiter entityLimiter; @@ -194,6 +195,49 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } // Paper end - fix and optimise world upgrading + // Purpur start + private com.google.common.cache.Cache playerBreedingCooldowns; + + private com.google.common.cache.Cache getNewBreedingCooldownCache() { + return com.google.common.cache.CacheBuilder.newBuilder().expireAfterWrite(this.purpurConfig.animalBreedingCooldownSeconds, java.util.concurrent.TimeUnit.SECONDS).build(); + } + + public void resetBreedingCooldowns() { + this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); + } + + public boolean hasBreedingCooldown(java.util.UUID player, Class animalType) { // Purpur + return this.playerBreedingCooldowns.getIfPresent(new BreedingCooldownPair(player, animalType)) != null; + } + + public void addBreedingCooldown(java.util.UUID player, Class animalType) { + this.playerBreedingCooldowns.put(new BreedingCooldownPair(player, animalType), new Object()); + } + + private static final class BreedingCooldownPair { + private final java.util.UUID playerUUID; + private final Class animalType; + + public BreedingCooldownPair(java.util.UUID playerUUID, Class animalType) { + this.playerUUID = playerUUID; + this.animalType = animalType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BreedingCooldownPair that = (BreedingCooldownPair) o; + return playerUUID.equals(that.playerUUID) && animalType.equals(that.animalType); + } + + @Override + public int hashCode() { + return java.util.Objects.hash(playerUUID, animalType); + } + } + // Purpur end + public CraftWorld getWorld() { return this.world; } @@ -233,6 +277,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config this.galeConfig = galeWorldConfigCreator.apply(this.spigotConfig); // Gale - Gale configuration + this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur + this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur this.generator = gen; this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); @@ -1920,4 +1966,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { return null; } // Paper end - optimize redstone (Alternate Current) + + // 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 ffa5f8be4aefb451352e1c6db84f36adca3a044d..88b13e6eeec956c10b486e4811b1e0dae0fd990e 100644 --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java +++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java @@ -256,7 +256,7 @@ public final class NaturalSpawner { blockposition_mutableblockposition.set(l, i, i1); double d0 = (double) l + 0.5D; double d1 = (double) i1 + 0.5D; - Player entityhuman = world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); + Player entityhuman = world.getNearestPlayer(d0, (double) i, d1, -1.0D, world.purpurConfig.mobSpawningIgnoreCreativePlayers); // Purpur if (entityhuman != null) { double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); diff --git a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java index 9fca247f51e8b4d445f61ab5c16faf3928626deb..583bb1282fedcab75dbe4359a9f53b76a538d903 100644 --- a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java +++ b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java @@ -62,6 +62,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 3ecc92439fc85d224ff52f41c5e34079e042a5e6..2336fea8c65d64a77a1afa4b8b976fb0d7da00eb 100644 --- a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java +++ b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java @@ -49,6 +49,20 @@ public class AzaleaBlock extends BushBlock implements BonemealableBlock { @Override public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + // Purpur start + growTree(world, random, pos, state); + } + + @Override + public void randomTick(net.minecraft.world.level.block.state.BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { + double chance = state.getBlock() == Blocks.FLOWERING_AZALEA ? world.purpurConfig.floweringAzaleaGrowthChance : world.purpurConfig.azaleaGrowthChance; + if (chance > 0.0D && world.getMaxLocalRawBrightness(pos.above()) > 9 && random.nextDouble() < chance) { + growTree(world, random, pos, state); + } + } + + private void growTree(ServerLevel world, RandomSource random, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) { + // Purpur end TreeGrower.AZALEA.growTree(world, world.getChunkSource().getGenerator(), pos, state, random); } diff --git a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java index 2a65c7b859b1126dbac9819a01ca2652e20498a9..3192edee23b899107b17e354ddfb3159ac2ef429 100644 --- a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java @@ -39,6 +39,7 @@ public abstract class BaseCoralPlantTypeBlock extends Block implements SimpleWat } protected static boolean scanForWater(BlockState state, BlockGetter world, BlockPos pos) { + if (!((net.minecraft.world.level.LevelAccessor) world).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur if (state.getValue(WATERLOGGED)) { return true; } else { diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java index 38fcde81d797dc46409f5a9ed426fe296d79bdfa..83fa72b5a8fde431e7035fe5cacc50e33ae506bf 100644 --- a/src/main/java/net/minecraft/world/level/block/BedBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java @@ -106,7 +106,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock Vec3 vec3d = pos.getCenter(); - world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // Paper - add exploded state + 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); // Paper - add exploded state // 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 @@ -159,7 +159,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); // Paper - add exploded state + 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); // Paper - add exploded state // Purpur return InteractionResult.SUCCESS; } } @@ -183,7 +183,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock @Override public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { - super.fallOn(world, state, pos, entity, fallDistance * 0.5F); + super.fallOn(world, state, pos, entity, fallDistance); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java index 0d92bd6f1e4f3470a62f573add3490220e60ef7a..2e89b22de852f43f2694be52043799f07f14800b 100644 --- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java @@ -243,7 +243,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 154b5a8520b13928686c6eb0e2481c3dd1d1de28..8c242b67f162a40311d7d987fd76771c38c3eadb 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java @@ -63,6 +63,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 { public static final MapCodec CODEC = simpleCodec(Block::new); @@ -89,6 +96,10 @@ public class Block extends BlockBehaviour implements ItemLike { public static final int UPDATE_LIMIT = 512; protected final StateDefinition stateDefinition; private BlockState defaultBlockState; + // Purpur start + public float fallDamageMultiplier = 1.0F; + public float fallDistanceMultiplier = 1.0F; + // Purpur end // Paper start public final boolean isDestroyable() { return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || @@ -320,7 +331,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); } @@ -339,7 +350,7 @@ public class Block extends BlockBehaviour implements ItemLike { event.setExpToDrop(block.getExpDrop(state, serverLevel, pos, net.minecraft.world.item.ItemStack.EMPTY, true)); // Paper - Properly handle xp dropping event.callEvent(); for (org.bukkit.inventory.ItemStack drop : event.getDrops()) { - popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); + popResource(serverLevel, pos, applyDisplayNameAndLoreFromTile(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop), blockEntity)); // Purpur } state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, false); // Paper - Properly handle xp dropping block.popExperience(serverLevel, pos, event.getExpToDrop()); // Paper - Properly handle xp dropping @@ -356,13 +367,53 @@ public class Block extends BlockBehaviour implements ItemLike { // Paper end - Properly handle xp dropping if (world instanceof ServerLevel) { Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> { - Block.popResource(world, pos, itemstack1); + Block.popResource(world, pos, applyDisplayNameAndLoreFromTile(itemstack1, blockEntity)); // Purpur }); state.spawnAfterBreak((ServerLevel) world, pos, tool, dropExperience); // Paper - Properly handle xp dropping } } + // 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); @@ -446,7 +497,17 @@ public class Block extends BlockBehaviour implements ItemLike { } // Paper - fix drops not preventing stats/food exhaustion } - public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {} + // Purpur start + @Nullable protected LivingEntity placer = null; + + public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) { + this.placer = placer; + } + + public void forgetPlacer() { + this.placer = null; + } + // Purpur end public boolean isPossibleToRespawnInThis(BlockState state) { return !state.isSolid() && !state.liquid(); @@ -465,7 +526,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 9a2de546dc2af2ad4bf5d32ca6583f0e1f3f70d8..3d48293c34fa37ba5091c6058dadb32a8f74a009 100644 --- a/src/main/java/net/minecraft/world/level/block/Blocks.java +++ b/src/main/java/net/minecraft/world/level/block/Blocks.java @@ -1145,8 +1145,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))); @@ -1215,7 +1215,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 Block 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 bed3d9c781c7d3ca260027b4737970889a54689c..db1941ed32d141327a8b11e54b3ff9900072ad36 100644 --- a/src/main/java/net/minecraft/world/level/block/BushBlock.java +++ b/src/main/java/net/minecraft/world/level/block/BushBlock.java @@ -52,4 +52,24 @@ public abstract class BushBlock extends Block { public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { return type == PathComputationType.AIR && !this.hasCollision ? true : super.isPathfindable(state, world, pos, type); } + + // Purpur start + public void playerDestroyAndReplant(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, net.minecraft.world.item.ItemStack itemInHand, net.minecraft.world.level.ItemLike itemToReplant) { + player.awardStat(net.minecraft.stats.Stats.BLOCK_MINED.get(this)); + player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); + java.util.List dropList = Block.getDrops(state, (net.minecraft.server.level.ServerLevel) world, pos, blockEntity, player, itemInHand); + + boolean planted = false; + for (net.minecraft.world.item.ItemStack itemToDrop : dropList) { + if (!planted && itemToDrop.getItem() == itemToReplant) { + world.setBlock(pos, defaultBlockState(), 3); + itemToDrop.setCount(itemToDrop.getCount() - 1); + planted = true; + } + Block.popResource(world, pos, itemToDrop); + } + + state.spawnAfterBreak((net.minecraft.server.level.ServerLevel) world, pos, itemInHand, true); + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java index ba4aaf850af36a84517c70581e141157c4f15b99..02ea708a5b5df9f753194cdc9312fc830af85c68 100644 --- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java @@ -23,7 +23,7 @@ import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit -public class CactusBlock extends Block { +public class CactusBlock extends Block implements BonemealableBlock { // Purpur public static final MapCodec CODEC = simpleCodec(CactusBlock::new); public static final IntegerProperty AGE = BlockStateProperties.AGE_15; @@ -114,7 +114,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; } @@ -134,4 +134,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(final LevelReader world, final BlockPos pos, final BlockState state) { + if (!((Level) world).purpurConfig.cactusAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false; + + int cactusHeight = 0; + while (world.getBlockState(pos.below(cactusHeight)).is(this)) { + cactusHeight++; + } + + return cactusHeight < ((Level) world).paperConfig().maxGrowthHeight.cactus; + } + + @Override + public boolean isBonemealSuccess(Level world, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + int cactusHeight = 0; + while (world.getBlockState(pos.below(cactusHeight)).is(this)) { + cactusHeight++; + } + for (int i = 0; i <= world.paperConfig().maxGrowthHeight.cactus - cactusHeight; i++) { + world.setBlockAndUpdate(pos.above(i), state.setValue(CactusBlock.AGE, 0)); + } + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java index 9c7ee02d3aa3c33b45db4dc5c079495a69d60b15..5eed401d3d722c6553240aba4a8e2337ee32b263 100644 --- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java @@ -133,7 +133,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 cdd7ab3fe589d089c0c03508721f46f6c136fc8a..6f148028c0fe503e9f6b327596d0954ce9e53269 100644 --- a/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java @@ -71,7 +71,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); @@ -81,7 +81,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 } } } @@ -89,6 +89,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 47b6b83842201620bd6620f5acf11bb14334e35d..b4d2499ae39fd3f14b2600a9663ea8a823bdfbe4 100644 --- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java @@ -36,7 +36,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 1ec8c2d24ec8468d20f322d67143b9454ff266eb..558f243ee0923af9a8cb9e1bb6f4a0e5c8341fe4 100644 --- a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java @@ -95,4 +95,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/ChangeOverTimeBlock.java b/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java index daae7fd6e0148cfba8e359d990748a0c83a3376e..0e06b1bcd906e92c083dc74d56d6d0a2a36f62a7 100644 --- a/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ChangeOverTimeBlock.java @@ -67,7 +67,7 @@ public interface ChangeOverTimeBlock> { } float f = (float) (k + 1) / (float) (k + j + 1); - float f1 = f * f * this.getChanceModifier(); + float f1 = world.purpurConfig.disableOxidationProximityPenalty ? this.getChanceModifier() : f * f * this.getChanceModifier(); // Purpur return random.nextFloat() < f1 ? this.getNext(state) : Optional.empty(); } diff --git a/src/main/java/net/minecraft/world/level/block/ChestBlock.java b/src/main/java/net/minecraft/world/level/block/ChestBlock.java index 305bce4d833116cc21e64fdcdfe13f03e94ff4ba..58838f3c443f80eb53c33f8aa764645240263da2 100644 --- a/src/main/java/net/minecraft/world/level/block/ChestBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ChestBlock.java @@ -358,6 +358,7 @@ public class ChestBlock extends AbstractChestBlock implements } private static boolean isBlockedChestByBlock(BlockGetter world, BlockPos pos) { + if (world instanceof Level && ((Level) world).purpurConfig.chestOpenWithBlockOnTop) return false; // Purpur BlockPos blockposition1 = pos.above(); return world.getBlockState(blockposition1).isRedstoneConductor(world, blockposition1); diff --git a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java index 689ba0cc4d3571efc2664e45fde00af724007934..1eebce1bb69dbf8888e5ad3818af3bff43c0ff93 100644 --- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java @@ -238,20 +238,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) { @@ -262,6 +270,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 8fd8285e07de4a0457da507501e49a807542f3b1..e580c5a141bebdc45893b5abde01e633c864fc13 100644 --- a/src/main/java/net/minecraft/world/level/block/CoralBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CoralBlock.java @@ -59,6 +59,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 5b96d1ae4bd8546311e986bc312b1f85883a67f4..2077cb5dcf2352c9d5b502744aeb9a66df256939 100644 --- a/src/main/java/net/minecraft/world/level/block/CropBlock.java +++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java @@ -179,7 +179,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 - Add EntityInsideBlockEvent - 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); } @@ -214,4 +214,15 @@ public class CropBlock extends BushBlock implements BonemealableBlock { protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(CropBlock.AGE); } + + // Purpur start + @Override + public void playerDestroy(Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops, boolean dropExp) { + if (world.purpurConfig.hoeReplantsCrops && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) { + super.playerDestroyAndReplant(world, player, pos, state, blockEntity, itemInHand, getBaseSeedId()); + } else { + super.playerDestroy(world, player, pos, state, blockEntity, itemInHand, includeDrops, dropExp); + } + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/DoorBlock.java b/src/main/java/net/minecraft/world/level/block/DoorBlock.java index ed57fbcfcff29a71026b0600b02daf4178d78429..31a5d3a5642123983b8c7df49be04f25141d15a2 100644 --- a/src/main/java/net/minecraft/world/level/block/DoorBlock.java +++ b/src/main/java/net/minecraft/world/level/block/DoorBlock.java @@ -198,6 +198,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); @@ -301,4 +302,18 @@ public class DoorBlock extends Block { flag = false; return flag; } + + // Purpur start + public static boolean requiresRedstone(Level level, BlockState state, BlockPos pos) { + if (level.purpurConfig.doorRequiresRedstone.contains(state.getBlock())) { + // force update client + BlockPos otherPos = pos.relative(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN); + BlockState otherState = level.getBlockState(otherPos); + level.sendBlockUpdated(pos, state, state, 3); + level.sendBlockUpdated(otherPos, otherState, otherState, 3); + return true; + } + return false; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java b/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java index 7f365143ce5c62e734eceb855ba0a02ab3a99b27..bbb266cbe23da2573d3dfb3a6edd57461988d3c5 100644 --- a/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java +++ b/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java @@ -49,8 +49,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 790cf30c2bfd4e2530ff563dfcf05e25554248b3..ba69e9b37ce9f2d8b439d471e6de770da38d6971 100644 --- a/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java +++ b/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java @@ -30,6 +30,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 { public static final MapCodec CODEC = simpleCodec(EnchantmentTableBlock::new); @@ -128,4 +130,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 4ba24bced9a2de4616a0418857d3738e0e322ea0..6660bea735dda46ab9493601f1f53b8e0075ca83 100644 --- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java +++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java @@ -54,6 +54,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 - Add EntityInsideBlockEvent if (world instanceof ServerLevel && entity.canChangeDimensions() && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-pos.getX()), (double) (-pos.getY()), (double) (-pos.getZ()))), state.getShape(world, pos), BooleanOp.AND)) { + // Purpur start + if (entity.isPassenger() || entity.isVehicle()) { + if (new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, PlayerTeleportEvent.TeleportCause.END_PORTAL).callEvent()) { + this.entityInside(state, world, pos, entity); + } + return; + } + // Purpur end ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends ServerLevel worldserver = ((ServerLevel) world).getServer().getLevel(resourcekey); 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 ddca14f1224327a738415fb8b37398d8df0aa9c8..fe3236295790b9e250486835176cae810160c33a 100644 --- a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java +++ b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java @@ -92,6 +92,34 @@ public class EnderChestBlock extends AbstractChestBlock i EnderChestBlockEntity enderChestBlockEntity = (EnderChestBlockEntity)blockEntity; playerEnderChestContainer.setActiveChest(enderChestBlockEntity); player.openMenu(new SimpleMenuProvider((syncId, inventory, playerx) -> { + // Purpur start + if (org.purpurmc.purpur.PurpurConfig.enderChestSixRows) { + if (org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) { + org.bukkit.craftbukkit.entity.CraftHumanEntity bukkitPlayer = player.getBukkitEntity(); + if (bukkitPlayer.hasPermission("purpur.enderchest.rows.six")) { + player.sixRowEnderchestSlotCount = 54; + return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer); + } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.five")) { + player.sixRowEnderchestSlotCount = 45; + return ChestMenu.fiveRows(syncId, inventory, playerEnderChestContainer); + } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.four")) { + player.sixRowEnderchestSlotCount = 36; + return ChestMenu.fourRows(syncId, inventory, playerEnderChestContainer); + } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.three")) { + player.sixRowEnderchestSlotCount = 27; + return ChestMenu.threeRows(syncId, inventory, playerEnderChestContainer); + } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.two")) { + player.sixRowEnderchestSlotCount = 18; + return ChestMenu.twoRows(syncId, inventory, playerEnderChestContainer); + } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.one")) { + player.sixRowEnderchestSlotCount = 9; + return ChestMenu.oneRow(syncId, inventory, playerEnderChestContainer); + } + } + player.sixRowEnderchestSlotCount = -1; + return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer); + } + // Purpur end return ChestMenu.threeRows(syncId, inventory, playerEnderChestContainer); }, CONTAINER_TITLE)); player.awardStat(Stats.OPEN_ENDERCHEST); diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java index 6e4c852c93f2418ea69e485ed3a10cbe3a6e3bd2..0c39126ce51439cce05747161aba43bce33a12d8 100644 --- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java +++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java @@ -110,7 +110,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) { @@ -124,6 +124,22 @@ public class FarmBlock extends Block { return; } + // Purpur start + if (world.purpurConfig.farmlandTramplingDisabled) return; + if (world.purpurConfig.farmlandTramplingOnlyPlayers && !(entity instanceof Player)) return; + if (world.purpurConfig.farmlandAlpha) { + Block block = world.getBlockState(pos.below()).getBlock(); + if (block instanceof FenceBlock || block instanceof WallBlock) { + return; + } + } + if (world.purpurConfig.farmlandTramplingFeatherFalling) { + Iterator armor = entity.getArmorSlots().iterator(); + if (armor.hasNext() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FALL_PROTECTION, armor.next()) >= (int) entity.fallDistance) { + return; + } + } + // Purpur end if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) { return; } @@ -171,7 +187,7 @@ public class FarmBlock extends Block { } } - return false; + return ((ServerLevel) world).purpurConfig.farmlandGetsMoistFromBelow && world.getFluidState(pos.relative(Direction.DOWN)).is(FluidTags.WATER); // Purpur; // Paper end - Perf: remove abstract block iteration } diff --git a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java index 30300ef3ec839dfa944c992ab50db4d3859bb02e..0d64b19dbbca9d563d90cabf0e2d32f76bfc0c62 100644 --- a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java +++ b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java @@ -34,12 +34,12 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements @Override public BlockState getStateForPlacement(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 @@ -55,7 +55,7 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements } else { modifier = world.spigotConfig.caveVinesModifier; } - if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution + if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < getMaxGrowthAge() && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution // Purpur // Spigot end BlockPos blockposition1 = pos.relative(this.growthDirection); @@ -77,11 +77,11 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements } public BlockState getMaxAgeState(BlockState state) { - return (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, 25); + return (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, getMaxGrowthAge()); // Purpur } public boolean isMaxAge(BlockState state) { - return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) == 25; + return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) >= getMaxGrowthAge(); // Purpur } protected BlockState updateBodyAfterConvertedFromHead(BlockState from, BlockState to) { @@ -123,13 +123,13 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements @Override public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { BlockPos blockposition1 = pos.relative(this.growthDirection); - int i = Math.min((Integer) state.getValue(GrowingPlantHeadBlock.AGE) + 1, 25); + int i = Math.min((Integer) state.getValue(GrowingPlantHeadBlock.AGE) + 1, getMaxGrowthAge()); // Purpur int j = this.getBlocksToGrowWhenBonemealed(random); for (int k = 0; k < j && this.canGrowInto(world.getBlockState(blockposition1)); ++k) { world.setBlockAndUpdate(blockposition1, (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, i)); blockposition1 = blockposition1.relative(this.growthDirection); - i = Math.min(i + 1, 25); + i = Math.min(i + 1, getMaxGrowthAge()); // Purpur } } @@ -142,4 +142,6 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements protected GrowingPlantHeadBlock getHeadBlock() { return this; } + + public abstract int getMaxGrowthAge(); // Purpur } diff --git a/src/main/java/net/minecraft/world/level/block/HayBlock.java b/src/main/java/net/minecraft/world/level/block/HayBlock.java index ef364aa171a48482a45bc18cfe730ec20c3f7be6..74971d90506aa253d5ee821b5390fb2551a3a393 100644 --- a/src/main/java/net/minecraft/world/level/block/HayBlock.java +++ b/src/main/java/net/minecraft/world/level/block/HayBlock.java @@ -23,6 +23,6 @@ public class HayBlock extends RotatedPillarBlock { @Override public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { - entity.causeFallDamage(fallDistance, 0.2F, world.damageSources().fall()); + super.fallOn(world, state, pos, entity, fallDistance); // Purpur } } diff --git a/src/main/java/net/minecraft/world/level/block/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java index 9c8c1df5187daefb1c8098b4d4a0976c71a7bbfd..cf4c1097d54c84b309ab02e9892d1b9e39d57490 100644 --- a/src/main/java/net/minecraft/world/level/block/IceBlock.java +++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java @@ -41,7 +41,7 @@ public class IceBlock extends HalfTransparentBlock { public void afterDestroy(Level world, BlockPos pos, ItemStack tool) { // Paper end - Improve Block#breakNaturally API 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; } @@ -69,7 +69,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 4dbacee1930bc3955ce431e1d32353588b47afc9..a53c6ab63fd10ac94292836120152f54f23c1e13 100644 --- a/src/main/java/net/minecraft/world/level/block/KelpBlock.java +++ b/src/main/java/net/minecraft/world/level/block/KelpBlock.java @@ -72,4 +72,11 @@ public class KelpBlock extends GrowingPlantHeadBlock implements LiquidBlockConta 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 9b3dcf1a4d4cece92a629506d341f6bfe79d13d0..0ed39daf88a98f7fa887fb45d4f7c0348a607551 100644 --- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java +++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java @@ -139,7 +139,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 - Configurable speed for water flowing over lava } @@ -167,7 +167,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)); } @@ -176,7 +176,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 - Configurable speed for water flowing over lava } diff --git a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java index 746c211b575ca588deadbbcd5c55b614e8660ba8..2f38bac9efc224084505e802546623260830b6d4 100644 --- a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java +++ b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java @@ -29,7 +29,7 @@ public class MagmaBlock extends Block { @Override public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) { - if (!entity.isSteppingCarefully() && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) { + if ((!entity.isSteppingCarefully() || world.purpurConfig.magmaBlockDamageWhenSneaking) && entity instanceof LivingEntity && (world.purpurConfig.magmaBlockDamageWithFrostWalker || !EnchantmentHelper.hasFrostWalker((LivingEntity) entity))) { // Purpur entity.hurt(world.damageSources().hotFloor().directBlock(world, pos), 1.0F); // CraftBukkit } diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java index 1b5cc5d6aa0b4313da980ce175c54145852d0db0..f7b724696151b73343feac1ce406fca9377f2508 100644 --- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java +++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java @@ -60,7 +60,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(); } @@ -92,6 +92,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 - Add EntityInsideBlockEvent 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 0fc333f240d6918e841a9221be42973839408802..13eb4dffd60ea7902d620f484df5e3f2c4688402 100644 --- a/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java +++ b/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java @@ -16,7 +16,7 @@ import net.minecraft.world.level.block.state.properties.IntegerProperty; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; -public class NetherWartBlock extends BushBlock { +public class NetherWartBlock extends BushBlock implements BonemealableBlock { // Purpur public static final MapCodec CODEC = simpleCodec(NetherWartBlock::new); public static final int MAX_AGE = 3; @@ -68,4 +68,32 @@ public class NetherWartBlock extends BushBlock { protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(NetherWartBlock.AGE); } + + // Purpur start + @Override + public void playerDestroy(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand, boolean includeDrops, boolean dropExp) { + if (world.purpurConfig.hoeReplantsNetherWarts && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) { + super.playerDestroyAndReplant(world, player, pos, state, blockEntity, itemInHand, Items.NETHER_WART); + } else { + super.playerDestroy(world, player, pos, state, blockEntity, itemInHand, includeDrops, dropExp); + } + } + + @Override + public boolean isValidBonemealTarget(final net.minecraft.world.level.LevelReader world, final BlockPos pos, final BlockState state) { + return ((net.minecraft.world.level.Level) world).purpurConfig.netherWartAffectedByBonemeal && state.getValue(NetherWartBlock.AGE) < 3; + } + + @Override + public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + int i = Math.min(3, state.getValue(NetherWartBlock.AGE) + 1); + state = state.setValue(NetherWartBlock.AGE, i); + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state, 2); // CraftBukkit + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java index a541dc3a6e373b30fff0abf5305e77854c190f10..ac88cb949808752a340637ffcb9d7256b7d09478 100644 --- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java +++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java @@ -94,7 +94,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 713352b68f82d4c4a19a712d5207de0f99456713..d056e80c98973e9ba64adc5a8554acc8a5f3eac9 100644 --- a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java @@ -71,6 +71,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 5835872df922b859a31b44e3723c67097f21a641..ecb595ddf21b593175c27d59fd9587e7f2d56517 100644 --- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java +++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java @@ -189,7 +189,7 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate @VisibleForTesting public static void maybeTransferFluid(BlockState state, ServerLevel world, BlockPos pos, float dripChance) { - if (dripChance <= 0.17578125F || dripChance <= 0.05859375F) { + if (dripChance <= world.purpurConfig.cauldronDripstoneWaterFillChance || dripChance <= world.purpurConfig.cauldronDripstoneLavaFillChance) { // Purpur if (PointedDripstoneBlock.isStalactiteStartPos(state, world, pos)) { Optional optional = PointedDripstoneBlock.getFluidAboveStalactite(world, pos, state); @@ -198,13 +198,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 0dfcac8cfcbb09fe04486bff60119f7985714454..2dbf71e421156093c8bc387941eae991f5e6c957 100644 --- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java +++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java @@ -80,7 +80,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 b84c48902ef24fdae17578a304e6c93dc20c5dce..e03125281767845564c48c98c3e6b6bbd269ade1 100644 --- a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java +++ b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java @@ -30,7 +30,7 @@ public class PoweredRailBlock extends BaseRailBlock { } protected boolean findPoweredRailSignal(Level world, BlockPos pos, BlockState state, boolean flag, int distance) { - if (distance >= 8) { + if (distance >= world.purpurConfig.railActivationRange) { // Purpur return false; } else { int j = pos.getX(); diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java index 088262f306755a9cb785c7a0cf0a9c66ed0965a8..000429c15117b1bcb6c60c2e692ffb3178144f51 100644 --- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java +++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java @@ -148,7 +148,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 - add exploded state + 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 - add exploded state // Purpur } public static boolean canSetSpawn(Level world) { diff --git a/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java b/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java index 09c61eb5ba129e9630a756b452ef6aa61745c533..837c8399b2f490d98ca556e66018bfd471cf05bf 100644 --- a/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java @@ -137,7 +137,7 @@ public class SculkShriekerBlock extends BaseEntityBlock implements SimpleWaterlo @Nullable @Override public BlockState getStateForPlacement(BlockPlaceContext ctx) { - return (BlockState) this.defaultBlockState().setValue(SculkShriekerBlock.WATERLOGGED, ctx.getLevel().getFluidState(ctx.getClickedPos()).getType() == Fluids.WATER); + return (BlockState) this.defaultBlockState().setValue(SculkShriekerBlock.WATERLOGGED, ctx.getLevel().getFluidState(ctx.getClickedPos()).getType() == Fluids.WATER).setValue(SculkShriekerBlock.CAN_SUMMON, ctx.getLevel().purpurConfig.sculkShriekerCanSummonDefault); // Purpur } @Override diff --git a/src/main/java/net/minecraft/world/level/block/SlabBlock.java b/src/main/java/net/minecraft/world/level/block/SlabBlock.java index adc7eba4bb5e8f7c507a16cdbd7497338a7658cf..512c528e0f2f3aa0da0253698a0189415329265a 100644 --- a/src/main/java/net/minecraft/world/level/block/SlabBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SlabBlock.java @@ -138,4 +138,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 a3da9536c3a3ad33d1c795673bdd7b05d6534054..9b057f3967aae5d0ca621b19d1212db91aaaee22 100644 --- a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java @@ -88,6 +88,12 @@ public class SnowLayerBlock extends Block { 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 e8b1c44da90f60cde20cda65aba2aa1e30f89d25..1ac38424a44aa2225b9bd3fa0fbbe61b7b24875c 100644 --- a/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java @@ -42,6 +42,60 @@ public class SpawnerBlock extends BaseEntityBlock { return createTickerHelper(type, BlockEntityType.MOB_SPAWNER, world.isClientSide ? SpawnerBlockEntity::clientTick : SpawnerBlockEntity::serverTick); } + // Purpur start + @Override + public void playerDestroy(Level level, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack stack, boolean includeDrops, boolean dropExp) { + if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.drop.spawners") && isSilkTouch(level, stack)) { + java.util.Optional> type = net.minecraft.world.entity.EntityType.by(((SpawnerBlockEntity) blockEntity).getSpawner().nextSpawnData.getEntityToSpawn()); + + net.minecraft.world.entity.EntityType entityType = type.orElse(null); + final net.kyori.adventure.text.Component mobName = io.papermc.paper.adventure.PaperAdventure.asAdventure(entityType == null ? Component.empty() : entityType.getDescription()); + net.minecraft.nbt.CompoundTag display = new net.minecraft.nbt.CompoundTag(); + net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag(); + net.minecraft.nbt.CompoundTag blockEntityTag = blockEntity.getUpdateTag(); + blockEntityTag.remove("Delay"); // remove this tag to allow stacking duplicate spawners + tag.put("BlockEntityTag", blockEntityTag); + + String name = level.purpurConfig.silkTouchSpawnerName; + if (name != null && !name.isEmpty() && !name.equals("Monster Spawner")) { + net.kyori.adventure.text.Component displayName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName)); + if (name.startsWith("")) { + displayName = displayName.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); + } + display.put("Name", net.minecraft.nbt.StringTag.valueOf(io.papermc.paper.adventure.PaperAdventure.asJsonString(displayName, java.util.Locale.ROOT))); + tag.put("display", display); + } + + List lore = level.purpurConfig.silkTouchSpawnerLore; + if (lore != null && !lore.isEmpty()) { + net.minecraft.nbt.ListTag list = new net.minecraft.nbt.ListTag(); + for (String line : lore) { + net.kyori.adventure.text.Component lineComponent = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(line, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName)); + if (line.startsWith("")) { + lineComponent = lineComponent.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); + } + list.add(net.minecraft.nbt.StringTag.valueOf(io.papermc.paper.adventure.PaperAdventure.asJsonString(lineComponent, java.util.Locale.ROOT))); + } + display.put("Lore", list); + tag.put("display", display); + } + + ItemStack item = new ItemStack(Blocks.SPAWNER.asItem()); + if (entityType != null) { + tag.putDouble("HideFlags", ItemStack.TooltipPart.ADDITIONAL.getMask()); // hides the "Interact with Spawn Egg" tooltip + item.setTag(tag); + } + + popResource(level, pos, item); + } + super.playerDestroy(level, player, pos, state, blockEntity, stack, includeDrops, dropExp); + } + + private boolean isSilkTouch(Level level, ItemStack stack) { + return stack != null && level.purpurConfig.silkTouchTools.contains(stack.getItem()) && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.SILK_TOUCH, stack) >= level.purpurConfig.minimumSilkTouchSpawnerRequire; + } + // Purpur end + @Override public void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { super.spawnAfterBreak(state, world, pos, tool, dropExperience); @@ -50,6 +104,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 8f3cca228f8ec1ea9379fa43af4baa7b18012dd2..7e87b4299979c9e46abb582da7a8e54a36e8dfc5 100644 --- a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java @@ -58,7 +58,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; @@ -77,7 +77,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(); @@ -92,6 +92,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 77ae7b8ab24935d046cce1f8f7f476310876f9df..579abfbe791052507b15c14d8c81e39b2fdd6520 100644 --- a/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java +++ b/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java @@ -99,4 +99,14 @@ 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) { + entity.hurt(entity.damageSources().stonecutter().directBlock(level, pos), level.purpurConfig.stonecutterDamage); + } + super.stepOn(level, pos, state, entity); + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java index 04957d461d0e968d443737068aaeec1d0bce78b2..7a283fbe4663cb321739f8e42ade4039d84e462b 100644 --- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java @@ -20,7 +20,7 @@ import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.VoxelShape; -public class SugarCaneBlock extends Block { +public class SugarCaneBlock extends Block implements BonemealableBlock { // Purpur public static final MapCodec CODEC = simpleCodec(SugarCaneBlock::new); public static final IntegerProperty AGE = BlockStateProperties.AGE_15; @@ -113,4 +113,34 @@ public class SugarCaneBlock extends Block { protected void createBlockStateDefinition(StateDefinition.Builder builder) { builder.add(SugarCaneBlock.AGE); } + + // Purpur start + @Override + public boolean isValidBonemealTarget(final LevelReader world, final BlockPos pos, final BlockState state) { + if (!((net.minecraft.world.level.Level) world).purpurConfig.sugarCanAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false; + + int reedHeight = 0; + while (world.getBlockState(pos.below(reedHeight)).is(this)) { + reedHeight++; + } + + return reedHeight < ((net.minecraft.world.level.Level) world).paperConfig().maxGrowthHeight.reeds; + } + + @Override + public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) { + return true; + } + + @Override + public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { + int reedHeight = 0; + while (world.getBlockState(pos.below(reedHeight)).is(this)) { + reedHeight++; + } + for (int i = 0; i <= world.paperConfig().maxGrowthHeight.reeds - reedHeight; i++) { + world.setBlockAndUpdate(pos.above(i), state.setValue(SugarCaneBlock.AGE, 0)); + } + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java index b4646e26965e0f1f26c5019e7c6a13fdf22bdb47..9a76665c6369b4106d152370dc3d2f5645cb02b1 100644 --- a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java +++ b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java @@ -169,7 +169,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 @@ -202,6 +202,31 @@ public class TurtleEggBlock extends Block { } private boolean canDestroyEgg(Level world, Entity entity) { - return !(entity instanceof Turtle) && !(entity instanceof Bat) ? (!(entity instanceof LivingEntity) ? false : entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) : false; + // Purpur start + if (entity instanceof Turtle || entity instanceof Bat) { + return false; + } + if (world.purpurConfig.turtleEggsBreakFromExpOrbs && entity instanceof net.minecraft.world.entity.ExperienceOrb) { + return true; + } + if (world.purpurConfig.turtleEggsBreakFromItems && entity instanceof net.minecraft.world.entity.item.ItemEntity) { + return true; + } + if (world.purpurConfig.turtleEggsBreakFromMinecarts && entity instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) { + return true; + } + if (!(entity instanceof LivingEntity)) { + return false; + } + if (world.purpurConfig.turtleEggsTramplingFeatherFalling) { + java.util.Iterator armor = entity.getArmorSlots().iterator(); + return !armor.hasNext() || net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FALL_PROTECTION, armor.next()) < (int) entity.fallDistance; + } + if (entity instanceof Player) { + return true; + } + + return world.purpurConfig.turtleEggsBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); + // Purpur end } } diff --git a/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java b/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java index b4f75d034fbaea8ca68c1b5c5715773e0b8a9036..d05939c4b2ae42a09ee9e97431e83babfc8ed76c 100644 --- a/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java +++ b/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java @@ -34,4 +34,11 @@ public class TwistingVinesBlock extends GrowingPlantHeadBlock { protected boolean canGrowInto(BlockState state) { return NetherVines.isValidGrowthState(state); } + + // Purpur start + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.twistingVinesMaxGrowthAge; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java index 98e62d2cd3c106753c3be4a217e9107397d388ab..6a1fd51423c17cd1498cb313fa52314daaca0a29 100644 --- a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java +++ b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java @@ -34,4 +34,11 @@ public class WeepingVinesBlock extends GrowingPlantHeadBlock { protected boolean canGrowInto(BlockState state) { return NetherVines.isValidGrowthState(state); } + + // Purpur start + @Override + public int getMaxGrowthAge() { + return org.purpurmc.purpur.PurpurConfig.weepingVinesMaxGrowthAge; + } + // Purpur end } diff --git a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java index fb180f0bcd20e51d41cfc924029c0b23d3d26258..688d161cd6725f494366c23668ebd6ff709b1587 100644 --- a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java +++ b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java @@ -76,6 +76,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 a6d2de3b8231168c7c38b4a83e99b2836c00af7c..7e560a28608a1016fb87cdf23d7c6a1e41ffbdf8 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java @@ -46,6 +46,7 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.AbstractFurnaceBlock; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.Vec3; // CraftBukkit start import org.bukkit.craftbukkit.block.CraftBlock; @@ -210,6 +211,22 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit // Paper end - cache burn durations } + // Purpur start + public static void addFuel(ItemStack itemStack, Integer burnTime) { + Map map = Maps.newLinkedHashMap(); + map.putAll(getFuel()); + map.put(itemStack.getItem(), burnTime); + cachedBurnDurations = com.google.common.collect.ImmutableMap.copyOf(map); + } + + public static void removeFuel(ItemStack itemStack) { + Map map = Maps.newLinkedHashMap(); + map.putAll(getFuel()); + map.remove(itemStack.getItem()); + cachedBurnDurations = com.google.common.collect.ImmutableMap.copyOf(map); + } + // Purpur End + // CraftBukkit start - add fields and methods private int maxStack = MAX_STACK; public List transaction = new java.util.ArrayList(); @@ -332,6 +349,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(); @@ -417,6 +449,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 RecipeHolder recipe, NonNullList slots, int count) { diff --git a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java index 416aa989ebb18a8741cc9d605a1180ab830f6643..e38a0adf5463c48311ad08b8d2e5b5c2d989a3b5 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java @@ -67,7 +67,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { public BarrelBlockEntity(BlockPos pos, BlockState state) { super(BlockEntityType.BARREL, pos, state); - this.items = NonNullList.withSize(27, ItemStack.EMPTY); + // Purpur start + this.items = NonNullList.withSize(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { + case 6 -> 54; + case 5 -> 45; + case 4 -> 36; + case 2 -> 18; + case 1 -> 9; + default -> 27; + }, ItemStack.EMPTY); + // Purpur end this.openersCounter = new ContainerOpenersCounter() { @Override protected void onOpen(Level world, BlockPos pos, BlockState state) { @@ -118,7 +127,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { @Override public int getContainerSize() { - return 27; + // Purpur start + return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { + case 6 -> 54; + case 5 -> 45; + case 4 -> 36; + case 2 -> 18; + case 1 -> 9; + default -> 27; + }; + // Purpur end } @Override @@ -138,7 +156,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { @Override protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { - return ChestMenu.threeRows(syncId, playerInventory, this); + // Purpur start + return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { + case 6 -> ChestMenu.sixRows(syncId, playerInventory, this); + case 5 -> ChestMenu.fiveRows(syncId, playerInventory, this); + case 4 -> ChestMenu.fourRows(syncId, playerInventory, this); + case 2 -> ChestMenu.twoRows(syncId, playerInventory, this); + case 1 -> ChestMenu.oneRow(syncId, playerInventory, this); + default -> ChestMenu.threeRows(syncId, playerInventory, this); + }; + // Purpur end } @Override diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java index 4b81b0180dfc96fc6a88646838a886ca5b5d301b..2c2e819fb8d92e2de0b4981333219ed529138520 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 @@ -88,6 +88,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; @@ -164,6 +174,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; @@ -197,6 +208,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; @@ -216,7 +230,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))) { // Purpur BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - Custom beacon ranges BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); } diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java index 55b0e2bf98a285cdcd30e40d94192b7a1802efd8..7810d763841825f68c60b7471026d71d89726bf0 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 @@ -47,7 +47,7 @@ public class BeehiveBlockEntity extends BlockEntity { private final List stored = Lists.newArrayList(); @Nullable public BlockPos savedFlowerPos; - public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold + public int maxBees = org.purpurmc.purpur.PurpurConfig.beeInsideBeeHive; // CraftBukkit - allow setting max amount of bees a hive can hold // Purpur public BeehiveBlockEntity(BlockPos pos, BlockState state) { super(BlockEntityType.BEEHIVE, pos, state); @@ -134,6 +134,22 @@ public class BeehiveBlockEntity extends BlockEntity { return list; } + // Purpur start + public List releaseBee(BlockState iblockdata, BeeData data, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, boolean force) { + List list = Lists.newArrayList(); + + BeehiveBlockEntity.releaseBee(this.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); } @@ -143,6 +159,12 @@ public class BeehiveBlockEntity extends BlockEntity { return this.stored.size(); } + // Purpur start + public List getStored() { + return stored; + } + // Purpur end + // Paper start - Add EntityBlockStorage clearEntities public void clearBees() { this.stored.clear(); @@ -207,7 +229,7 @@ public class BeehiveBlockEntity extends BlockEntity { } private static boolean releaseBee(Level world, BlockPos blockposition, BlockState iblockdata, BeehiveBlockEntity.BeeData tileentitybeehive_hivebee, @Nullable List list, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, @Nullable BlockPos blockposition1, boolean force) { - if (!force && (world.isNight() || world.isRaining()) && tileentitybeehive_releasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) { + if (!force && ((world.isNight() && !world.purpurConfig.beeCanWorkAtNight) || (world.isRaining() && !world.purpurConfig.beeCanWorkInRain)) && tileentitybeehive_releasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) { // Purpur // CraftBukkit end return false; } else { @@ -429,9 +451,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 - Fix bees aging inside hives; 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 9ea74d37cd951e0dc76d20ed8234b5871035566c..07a83e06e9cb6e30c600445809e49a3fd23a391a 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java @@ -6,6 +6,8 @@ import net.minecraft.CrashReportCategory; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.resources.ResourceLocation; @@ -73,10 +75,27 @@ public abstract class BlockEntity { if (persistentDataTag instanceof CompoundTag) { this.persistentDataContainer.putAll((CompoundTag) persistentDataTag); } + // Purpur start + if (nbt.contains("Purpur.persistentDisplayName")) { + this.persistentDisplayName = nbt.getString("Purpur.persistentDisplayName"); + } + if (nbt.contains("Purpur.persistentLore")) { + this.persistentLore = nbt.getList("Purpur.persistentLore", 8); + } + // Purpur end } // CraftBukkit end - protected void saveAdditional(CompoundTag nbt) {} + protected void saveAdditional(CompoundTag nbt) { + // Purpur start + if (this.persistentDisplayName != null) { + nbt.put("Purpur.persistentDisplayName", StringTag.valueOf(this.persistentDisplayName)); + } + if (this.persistentLore != null) { + nbt.put("Purpur.persistentLore", this.persistentLore); + } + // Purpur end + } public final CompoundTag saveWithFullMetadata() { CompoundTag nbttagcompound = this.saveWithoutMetadata(); @@ -186,10 +205,24 @@ public abstract class BlockEntity { @Nullable public Packet getUpdatePacket() { + // Purpur start + if (this instanceof net.minecraft.world.Nameable nameable && nameable.hasCustomName()) { + CompoundTag nbt = this.saveWithoutMetadata(); + nbt.remove("Items"); + return net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket.create(this, $ -> nbt); + } + // Purpur end return null; } public CompoundTag getUpdateTag() { + // Purpur start + if (this instanceof net.minecraft.world.Nameable nameable && nameable.hasCustomName()) { + CompoundTag nbt = this.saveWithoutMetadata(); + nbt.remove("Items"); + return nbt; + } + // Purpur end return new CompoundTag(); } @@ -262,4 +295,25 @@ public abstract class BlockEntity { return tag; } // Paper end - Sanitize sent data + + // 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 37e0b762b86e74f607a4541ecb7b24ad7a591d0e..7e3edd41f3a39ef14382e18b20af21e63ce0677b 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 @@ -167,7 +167,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) { @@ -187,7 +187,7 @@ public class ConduitBlockEntity extends BlockEntity { private static void applyEffects(Level world, BlockPos pos, List activatingBlocks) { int i = activatingBlocks.size(); - int j = i / 7 * 16; // Paper - Conduit API; diff on change + int j = i / 7 * world.purpurConfig.conduitDistance; // Paper - Conduit API; diff on change // Purpur int k = pos.getX(); int l = pos.getY(); int i1 = pos.getZ(); @@ -218,20 +218,20 @@ public class ConduitBlockEntity extends BlockEntity { blockEntity.destroyTarget = ConduitBlockEntity.findDestroyTarget(world, pos, blockEntity.destroyTargetUUID); blockEntity.destroyTargetUUID = null; } else if (blockEntity.destroyTarget == null) { - List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos), (entityliving1) -> { + List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos, world), (entityliving1) -> { // Purpur return entityliving1 instanceof Enemy && entityliving1.isInWaterOrRain(); }); if (!list1.isEmpty()) { blockEntity.destroyTarget = (LivingEntity) list1.get(world.random.nextInt(list1.size())); } - } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), 8.0D)) { + } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), world.purpurConfig.conduitDamageDistance)) { // Purpur blockEntity.destroyTarget = null; } if (blockEntity.destroyTarget != null) { // CraftBukkit start - if (blockEntity.destroyTarget.hurt(world.damageSources().magic().directBlock(world, pos), 4.0F)) { // CraftBukkit + if (blockEntity.destroyTarget.hurt(world.damageSources().magic().directBlock(world, pos), world.purpurConfig.conduitDamageAmount)) { // CraftBukkit // Purpur world.playSound(null, blockEntity.destroyTarget.getX(), blockEntity.destroyTarget.getY(), blockEntity.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F); } // CraftBukkit end @@ -256,16 +256,22 @@ public class ConduitBlockEntity extends BlockEntity { } private static AABB getDestroyRangeAABB(BlockPos pos) { + // Purpur start + return getDestroyRangeAABB(pos, null); + } + + private static AABB getDestroyRangeAABB(BlockPos pos, Level level) { + // Purpur end int i = pos.getX(); int j = pos.getY(); int k = pos.getZ(); - return (new AABB((double) i, (double) j, (double) k, (double) (i + 1), (double) (j + 1), (double) (k + 1))).inflate(8.0D); + return (new AABB((double) i, (double) j, (double) k, (double) (i + 1), (double) (j + 1), (double) (k + 1))).inflate(level == null ? 8.0D : level.purpurConfig.conduitDamageDistance); // Purpur } @Nullable private static LivingEntity findDestroyTarget(Level world, BlockPos pos, UUID uuid) { - List list = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos), (entityliving) -> { + List list = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos, world), (entityliving) -> { // Purpur return entityliving.getUUID().equals(uuid); }); diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java index 65e1381bb2d10bd212463feb602c60f8fdb9ade1..b7370e64fd0d50e8725d7d5afc30af2e8bc8455d 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java @@ -24,6 +24,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable public float tRot; private static final RandomSource RANDOM = RandomSource.create(); private Component name; + private int lapis = 0; // Purpur public EnchantmentTableBlockEntity(BlockPos pos, BlockState state) { super(BlockEntityType.ENCHANTING_TABLE, pos, state); @@ -35,6 +36,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable if (this.hasCustomName()) { nbt.putString("CustomName", Component.Serializer.toJson(this.name)); } + nbt.putInt("Purpur.Lapis", this.lapis); // Purpur } @@ -44,6 +46,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable if (nbt.contains("CustomName", 8)) { this.name = io.papermc.paper.util.MCUtil.getBaseComponentFromNbt("CustomName", nbt); // Paper - Catch ParseException } + this.lapis = nbt.getInt("Purpur.Lapis"); // Purpur } @@ -117,4 +120,14 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable public Component getCustomName() { return this.name; } + + // Purpur start + public int getLapis() { + return this.lapis; + } + + public void setLapis(int lapis) { + this.lapis = lapis; + } + // Purpur } diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java index 927c7ea03560be0c86884cec70ee8e408e66cb07..93764bf849ad8e427bf119f6ff3dbcbcc4c2415e 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 @@ -200,16 +200,31 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C return this.setText((SignText) textChanger.apply(signtext), front); } + // Purpur start + private Component translateColors(org.bukkit.entity.Player player, String line, Style style) { + if (level.purpurConfig.signAllowColors) { + if (player.hasPermission("purpur.sign.color")) line = line.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1"); + if (player.hasPermission("purpur.sign.style")) line = line.replaceAll("(?i)&([l-or])", "\u00a7$1"); + if (player.hasPermission("purpur.sign.magic")) line = line.replaceAll("(?i)&([kr])", "\u00a7$1"); + + return io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(line)); + } else { + return Component.literal(line).setStyle(style); + } + } + // Purpur end + private SignText setMessages(net.minecraft.world.entity.player.Player entityhuman, List list, SignText signtext, boolean front) { // CraftBukkit SignText originalText = signtext; // CraftBukkit for (int i = 0; i < list.size(); ++i) { FilteredText filteredtext = (FilteredText) list.get(i); Style chatmodifier = signtext.getMessage(i, entityhuman.isTextFilteringEnabled()).getStyle(); + org.bukkit.entity.Player player = (org.bukkit.craftbukkit.entity.CraftPlayer) entityhuman.getBukkitEntity(); // Purpur if (entityhuman.isTextFilteringEnabled()) { - signtext = signtext.setMessage(i, Component.literal(net.minecraft.SharedConstants.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only + signtext = signtext.setMessage(i, translateColors(player, net.minecraft.SharedConstants.filterText(filteredtext.filteredOrEmpty()), chatmodifier)); // Paper - filter sign text to chat only // Purpur } else { - signtext = signtext.setMessage(i, Component.literal(net.minecraft.SharedConstants.filterText(filteredtext.raw())).setStyle(chatmodifier), Component.literal(net.minecraft.SharedConstants.filterText(filteredtext.filteredOrEmpty())).setStyle(chatmodifier)); // Paper - filter sign text to chat only + signtext = signtext.setMessage(i, translateColors(player, net.minecraft.SharedConstants.filterText(filteredtext.raw()), chatmodifier), translateColors(player, net.minecraft.SharedConstants.filterText(filteredtext.filteredOrEmpty()), chatmodifier)); // Paper - filter sign text to chat only // Purpur } } @@ -349,6 +364,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 0d3242be8fe0201e9179a78bd122a2669c42fbc5..f5e9b99459c3e7d28272f0c2499794f9710f57ce 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 @@ -179,6 +179,14 @@ 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.level().galeConfig().gameplayMechanics.fixes.checkCanChangeDimensionsBeforeUseEndGateway && world.galeConfig().gameplayMechanics.fixes.checkCanChangeDimensionsBeforeUseEndGateway && !entity.canChangeDimensions()) return; // Gale - Purpur - end gateway should check if entity can use portal + // 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 2892e586146cbc560f0bcf4b9af6d0575cb0a82e..d38d8fc7ef22fb68e867cc29dab1171c9aa6ac35 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 @@ -84,7 +84,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; @@ -92,7 +92,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/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java index 23dc37ff1f92951817864963bf93220d5aae91bb..af563ffb67c43b50555951fe1b5e4320d429a7f1 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java @@ -112,6 +112,7 @@ public class EntityStorage implements EntityPersistentStorage { ListTag listTag = new ListTag(); final java.util.Map, Integer> savedEntityCounts = new java.util.HashMap<>(); // Paper - Entity load/save limit per chunk entities.forEach((entity) -> { // diff here: use entities parameter + if (!entity.canSaveToDisk()) return; // Purpur // Paper start - Entity load/save limit per chunk 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 3380125f68b865977ac627579615820405bc98df..8a28952257fd6c01b0f2a4d6dffbd91c1cd1d534 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 - Ability to control player's insomnia and phantoms - if (world.getSkyDarken() < 5 && world.dimensionType().hasSkyLight()) { + if (world.getSkyDarken() < world.purpurConfig.phantomSpawnMinSkyDarkness && world.dimensionType().hasSkyLight()) { // Purpur return 0; } else { int i = 0; @@ -61,10 +61,10 @@ public class PhantomSpawner implements CustomSpawner { if (!entityplayer.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityplayer.isCreative())) { // Paper - Add phantom creative and insomniac controls BlockPos blockposition = entityplayer.blockPosition(); - if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) { + if (!world.dimensionType().hasSkyLight() || (!world.purpurConfig.phantomSpawnOnlyAboveSeaLevel || blockposition.getY() >= world.getSeaLevel()) && (!world.purpurConfig.phantomSpawnOnlyWithVisibleSky || world.canSeeSky(blockposition))) { // Purpur DifficultyInstance difficultydamagescaler = world.getCurrentDifficultyAt(blockposition); - if (difficultydamagescaler.isHarderThan(randomsource.nextFloat() * 3.0F)) { + if (difficultydamagescaler.isHarderThan(randomsource.nextFloat() * (float) world.purpurConfig.phantomSpawnLocalDifficultyChance)) { // Purpur ServerStatsCounter serverstatisticmanager = entityplayer.getStats(); int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); boolean flag2 = true; @@ -84,7 +84,7 @@ public class PhantomSpawner implements CustomSpawner { if (NaturalSpawner.isValidEmptySpawnBlock(world, blockposition1, iblockdata, fluid, EntityType.PHANTOM)) { SpawnGroupData groupdataentity = null; - int k = 1 + randomsource.nextInt(difficultydamagescaler.getDifficulty().getId() + 1); + int k = world.purpurConfig.phantomSpawnMinPerAttempt + world.random.nextInt((world.purpurConfig.phantomSpawnMaxPerAttempt < 0 ? difficultydamagescaler.getDifficulty().getId() : world.purpurConfig.phantomSpawnMaxPerAttempt - world.purpurConfig.phantomSpawnMinPerAttempt) + 1); // Purpur for (int l = 0; l < k; ++l) { // Paper start - PhantomPreSpawnEvent diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java index 05273fa3b59d67858abffcb1ff18a6ff5885c316..6b27719bed1963183015207b21bfd4d6cc84e020 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(); @@ -336,6 +336,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 3bb4a9a1a6249e8ba2de237f801210e7f4fd5825..2d492d849ff73a738dfbcb16507feb89bf19a962 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 cde93efaecd42b9081405638af3ba91cb5e184c9..c664fd77dd865037293a3e86699fd1fa6b703882 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 - Add BlockBreakBlockEvent @Override protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { diff --git a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java index 70c7c735fdd0c1b66243915622e47eab8a57e7cc..996bd26e95e766ce4ca7e61d060881feb9dd4a89 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; @@ -471,7 +471,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { return BlockPathTypes.BLOCKED; } else { // Paper end - Do not load chunks during pathfinding - 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; } @@ -504,7 +504,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 912cee9ec45876f831ca230b59a1be3b48ce6aa5..46910a3bdacc9df1835e16b300f9e107744d2660 100644 --- a/src/main/java/net/minecraft/world/level/portal/PortalShape.java +++ b/src/main/java/net/minecraft/world/level/portal/PortalShape.java @@ -33,7 +33,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 45269115e63cfc3bd7dc740a5694e2cc7c35bcb1..e1498d496aa01c433b6fa198608e33916eadecf3 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 @@ -67,6 +67,7 @@ public class MapItemSavedData extends SavedData { private final Map frameMarkers = Maps.newHashMap(); private int trackedDecorationCount; private org.bukkit.craftbukkit.map.RenderData vanillaRender = new org.bukkit.craftbukkit.map.RenderData(); // Paper + public boolean isExplorerMap; // Purpur // CraftBukkit start public final CraftMapView mapView; diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java index 3fb1e558c3510243c94981211f9a0e5e0ef1895b..e5177e5ffcac360f935f2139db4554c6586b551e 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 @@ -57,6 +57,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 67d595f75e0c3bffdb27b85b25ccd1f0bf1427d5..4d0c524af87d05dbd4a923ad6c40e1f26149e921 100644 --- a/src/main/java/net/minecraft/world/phys/AABB.java +++ b/src/main/java/net/minecraft/world/phys/AABB.java @@ -374,4 +374,10 @@ public class AABB { public static AABB ofSize(Vec3 center, double dx, double dy, double dz) { return new AABB(center.x - dx / 2.0D, center.y - dy / 2.0D, center.z - dz / 2.0D, center.x + dx / 2.0D, center.y + dy / 2.0D, center.z + dz / 2.0D); } + + // Purpur - tuinity added method + public final AABB offsetY(double dy) { + return new AABB(this.minX, this.minY + dy, this.minZ, this.maxX, this.maxY + dy, this.maxZ); + } + // Purpur } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java index 4a875bce9563f3b9351ebecde9b0eb1287beb50e..d3fd9a8752e501f45c154e28d8ffd49b01514979 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java @@ -335,14 +335,26 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa @Override public Location getLocation() { + // Purpur start + if (this.isOnline()) { + return this.getPlayer().getLocation(); + } + // Purpur end + CompoundTag data = this.getData(); if (data == null) { return null; } - if (data.contains("Pos") && data.contains("Rotation")) { - ListTag position = (ListTag) data.get("Pos"); - ListTag rotation = (ListTag) data.get("Rotation"); + // Purpur start - OfflinePlayer API + //if (data.contains("Pos") && data.contains("Rotation")) { + ListTag position = data.getList("Pos", net.minecraft.nbt.Tag.TAG_DOUBLE); + ListTag rotation = data.getList("Rotation", net.minecraft.nbt.Tag.TAG_FLOAT); + + if (position.isEmpty() && rotation.isEmpty()) { + return null; + } + // Purpur end - OfflinePlayer API UUID uuid = new UUID(data.getLong("WorldUUIDMost"), data.getLong("WorldUUIDLeast")); @@ -353,9 +365,9 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa rotation.getFloat(0), rotation.getFloat(1) ); - } + //} // Purpur - OfflinePlayer API - return null; + //return null; // Purpur - OfflinePlayer API } @Override @@ -598,4 +610,191 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa manager.save(); } } + + // Purpur start - OfflinePlayer API + @Override + public boolean getAllowFlight() { + if (this.isOnline()) { + return this.getPlayer().getAllowFlight(); + } else { + CompoundTag data = this.getData(); + if (data == null) return false; + if (!data.contains("abilities")) return false; + CompoundTag abilities = data.getCompound("abilities"); + return abilities.getByte("mayfly") == (byte) 1; + } + } + + @Override + public void setAllowFlight(boolean flight) { + if (this.isOnline()) { + this.getPlayer().setAllowFlight(flight); + } else { + CompoundTag data = this.getData(); + if (data == null) return; + if (!data.contains("abilities")) return; + CompoundTag abilities = data.getCompound("abilities"); + abilities.putByte("mayfly", (byte) (flight ? 1 : 0)); + data.put("abilities", abilities); + save(data); + } + } + + @Override + public boolean isFlying() { + if (this.isOnline()) { + return this.isFlying(); + } else { + CompoundTag data = this.getData(); + if (data == null) return false; + if (!data.contains("abilities")) return false; + CompoundTag abilities = data.getCompound("abilities"); + return abilities.getByte("flying") == (byte) 1; + } + } + + @Override + public void setFlying(boolean value) { + if (this.isOnline()) { + this.getPlayer().setFlying(value); + } else { + CompoundTag data = this.getData(); + if (data == null) return; + if (!data.contains("abilities")) return; + CompoundTag abilities = data.getCompound("abilities"); + abilities.putByte("mayfly", (byte) (value ? 1 : 0)); + data.put("abilities", abilities); + save(data); + } + } + + @Override + public void setFlySpeed(float value) throws IllegalArgumentException { + if (value < -1f || value > 1f) throw new IllegalArgumentException("FlySpeed needs to be between -1 and 1"); + if (this.isOnline()) { + this.getPlayer().setFlySpeed(value); + } else { + CompoundTag data = this.getData(); + if (data == null) return; + if (!data.contains("abilities")) return; + CompoundTag abilities = data.getCompound("abilities"); + abilities.putFloat("flySpeed", value); + data.put("abilities", abilities); + save(data); + } + } + + @Override + public float getFlySpeed() { + if (this.isOnline()) { + return this.getPlayer().getFlySpeed(); + } else { + CompoundTag data = this.getData(); + if (data == null) return 0; + if (!data.contains("abilities")) return 0; + CompoundTag abilities = data.getCompound("abilities"); + return abilities.getFloat("flySpeed"); + } + } + + @Override + public void setWalkSpeed(float value) throws IllegalArgumentException { + if (value < -1f || value > 1f) throw new IllegalArgumentException("WalkSpeed needs to be between -1 and 1"); + if (this.isOnline()) { + this.getPlayer().setWalkSpeed(value); + } else { + CompoundTag data = this.getData(); + if (data == null) return; + if (!data.contains("abilities")) return; + CompoundTag abilities = data.getCompound("abilities"); + abilities.putFloat("walkSpeed", value); + data.put("abilities", abilities); + save(data); + } + } + + @Override + public float getWalkSpeed() { + if (this.isOnline()) { + return this.getPlayer().getWalkSpeed(); + } else { + CompoundTag data = this.getData(); + if (data == null) return 0; + if (!data.contains("abilities")) return 0; + CompoundTag abilities = data.getCompound("abilities"); + return abilities.getFloat("walkSpeed"); + } + } + + @Override + public boolean teleportOffline(Location destination) { + if (this.isOnline()) { + return this.getPlayer().teleport(destination); + } else { + return setLocation(destination); + } + } + + @Override + public boolean teleportOffline(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause){ + if (this.isOnline()) { + return this.getPlayer().teleport(destination, cause); + } else { + return setLocation(destination); + } + } + + @Override + public java.util.concurrent.CompletableFuture teleportOfflineAsync(Location destination) { + if (this.isOnline()) { + return this.getPlayer().teleportAsync(destination); + } else { + return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination)); + } + } + + @Override + public java.util.concurrent.CompletableFuture teleportOfflineAsync(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { + if (this.isOnline()) { + return this.getPlayer().teleportAsync(destination, cause); + } else { + return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination)); + } + } + + private boolean setLocation(Location location) { + CompoundTag data = this.getData(); + if (data == null) return false; + data.putLong("WorldUUIDMost", location.getWorld().getUID().getMostSignificantBits()); + data.putLong("WorldUUIDLeast", location.getWorld().getUID().getLeastSignificantBits()); + net.minecraft.nbt.ListTag position = new net.minecraft.nbt.ListTag(); + position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getX())); + position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getY())); + position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getZ())); + data.put("Pos", position); + net.minecraft.nbt.ListTag rotation = new net.minecraft.nbt.ListTag(); + rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getYaw())); + rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getPitch())); + data.put("Rotation", rotation); + save(data); + return true; + } + + /** + * Safely replaces player's .dat file with provided CompoundTag + * @param compoundTag + */ + private void save(CompoundTag compoundTag) { + File playerDir = server.console.playerDataStorage.getPlayerDir(); + try { + File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir); + net.minecraft.nbt.NbtIo.writeCompressed(compoundTag, tempFile.toPath()); + File playerDataFile = new File(playerDir, this.getUniqueId()+".dat"); + File playerDataFileOld = new File(playerDir, this.getUniqueId()+".dat_old"); + net.minecraft.Util.safeReplaceFile(playerDataFile.toPath(), tempFile.toPath(), playerDataFileOld.toPath()); + } catch (java.io.IOException e) { + e.printStackTrace(); + } + } + // Purpur end - OfflinePlayer API } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 8ecf6ffa93610261d27fafadfff016a2d6047c90..f4fb23ebdd35e23b022bdd78421dec6d86342260 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -404,6 +404,20 @@ public final class CraftServer implements Server { this.serverTickManager = new CraftServerTickManager(console.tickRateManager()); Bukkit.setServer(this); + // Purpur start + org.purpurmc.purpur.language.Language.setLanguage(new org.purpurmc.purpur.language.Language() { + private net.minecraft.locale.Language language = net.minecraft.locale.Language.getInstance(); + @Override + public boolean has(@org.jetbrains.annotations.NotNull String key) { + return language.has(key); + } + + @Override + public @org.jetbrains.annotations.NotNull String getOrDefault(@org.jetbrains.annotations.NotNull String key) { + return language.getOrDefault(key); + } + }); + // Purpur end CraftRegistry.setMinecraftRegistry(console.registryAccess()); @@ -1059,6 +1073,7 @@ public final class CraftServer implements Server { org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot this.console.paperConfigurations.reloadConfigs(this.console); this.console.galeConfigurations.reloadConfigs(this.console); // Gale - Gale configuration + 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)) @@ -1074,6 +1089,7 @@ public final class CraftServer implements Server { } } world.spigotConfig.init(); // Spigot + world.purpurConfig.init(); // Purpur } Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper @@ -1089,6 +1105,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"); @@ -1590,6 +1607,55 @@ public final class CraftServer implements Server { return true; } + // Purpur Start + @Override + public void addFuel(org.bukkit.Material material, int burnTime) { + Preconditions.checkArgument(burnTime > 0, "BurnTime must be greater than 0"); + net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity.addFuel(net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)), burnTime); + } + + @Override + public void removeFuel(org.bukkit.Material material) { + net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity.removeFuel(net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material))); + } + + @Override + public void sendBlockHighlight(Location location, int duration) { + sendBlockHighlight(location, duration, "", 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, int argb) { + sendBlockHighlight(location, duration, "", argb); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text) { + sendBlockHighlight(location, duration, text, 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, int argb) { + this.worlds.forEach((name, world) -> world.sendBlockHighlight(location, duration, text, argb)); + } + + @Override + public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { + sendBlockHighlight(location, duration, "", color, transparency); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { + if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); + sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); + } + + @Override + public void clearBlockHighlights() { + this.worlds.forEach((name, world) -> clearBlockHighlights()); + } + // Purpur End + @Override public List getRecipesFor(ItemStack result) { Preconditions.checkArgument(result != null, "ItemStack cannot be null"); @@ -3045,6 +3111,18 @@ public final class CraftServer implements Server { } // Gale end - Gale configuration - API + // Purpur start + @Override + public YamlConfiguration getPurpurConfig() { + return org.purpurmc.purpur.PurpurConfig.config; + } + + @Override + public java.util.Properties getServerProperties() { + return getProperties().properties; + } + // Purpur end + @Override public void restart() { org.spigotmc.RestartCommand.restart(); @@ -3331,4 +3409,15 @@ public final class CraftServer implements Server { } // Gale end - YAPFA - last tick time - API + // Purpur start + @Override + public String getServerName() { + return this.getProperties().serverName; + } + + @Override + public boolean isLagging() { + return getServer().lagging; + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index c9d276124bd14aafcd1a29991081b7668fe636b6..593119cefb6664f2b0b03b684103440b27696e37 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -2388,6 +2388,48 @@ public class CraftWorld extends CraftRegionAccessor implements World { return (this.getHandle().getDragonFight() == null) ? null : new CraftDragonBattle(this.getHandle().getDragonFight()); } + // Purpur start + public float getLocalDifficultyAt(Location location) { + return getHandle().getCurrentDifficultyAt(io.papermc.paper.util.MCUtil.toBlockPosition(location)).getEffectiveDifficulty(); + } + + @Override + public void sendBlockHighlight(Location location, int duration) { + sendBlockHighlight(location, duration, "", 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, int argb) { + sendBlockHighlight(location, duration, "", argb); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text) { + sendBlockHighlight(location, duration, text, 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, int argb) { + net.minecraft.network.protocol.game.DebugPackets.sendGameTestAddMarker(getHandle(), io.papermc.paper.util.MCUtil.toBlockPosition(location), text, argb, duration); + } + + @Override + public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { + sendBlockHighlight(location, duration, "", color, transparency); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { + if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); + sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); + } + + @Override + public void clearBlockHighlights() { + net.minecraft.network.protocol.game.DebugPackets.sendGameTestClearPacket(getHandle()); + } + // Purpur end + @Override public Collection getStructures(int x, int z) { return this.getStructures(x, z, struct -> true); diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java index 4ef9e98891a85e4b81f7d3f011bee5ad0eaee4f9..94a998b877e3cfff0477cbef4400ca4113e7681d 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -195,6 +195,14 @@ 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"); + // Purpur end + // Paper start acceptsAll(asList("server-name"), "Name of the server") .withRequiredArg() @@ -314,7 +322,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 2e51fab98d95c93d2095f7be6dbb5d5474158bfb..32285c8e0f42897793759fba85a1e8658750c843 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java @@ -16,8 +16,15 @@ import org.bukkit.entity.Bee; public class CraftBeehive extends CraftBlockEntityState implements Beehive { + private final List> storage = new ArrayList<>(); // Purpur + public CraftBeehive(World world, BeehiveBlockEntity tileEntity) { super(world, tileEntity); + // Purpur start - load bees to be able to modify them individually + for(BeehiveBlockEntity.BeeData data : tileEntity.getStored()) { + storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(data, this)); + } + // Purpur end } protected CraftBeehive(CraftBeehive state) { @@ -75,15 +82,54 @@ public class CraftBeehive extends CraftBlockEntityState impl bees.add((Bee) bee.getBukkitEntity()); } } - + storage.clear(); // Purpur return bees; } + // Purpur start + @Override + public Bee releaseEntity(org.purpurmc.purpur.entity.StoredEntity entity) { + ensureNoWorldGeneration(); + + if(!getEntities().contains(entity)) { + return null; + } + + if(isPlaced()) { + BeehiveBlockEntity beehive = ((BeehiveBlockEntity) this.getTileEntityFromWorld()); + BeehiveBlockEntity.BeeData data = ((org.purpurmc.purpur.entity.PurpurStoredBee) entity).getHandle(); + + List list = beehive.releaseBee(getHandle(), data, BeeReleaseStatus.BEE_RELEASED, true); + + if (list.size() == 1) { + storage.remove(entity); + + return (Bee) list.get(0).getBukkitEntity(); + } + } + + return null; + } + + @Override + public List> getEntities() { + return new ArrayList<>(storage); + } + // Purpur end + @Override public void addEntity(Bee entity) { Preconditions.checkArgument(entity != null, "Entity must not be null"); - this.getSnapshot().addOccupant(((CraftBee) entity).getHandle(), false); + int length = this.getSnapshot().getStored().size(); // Purpur + getSnapshot().addOccupant(((CraftBee) entity).getHandle(), false); + + // Purpur start - check if new bee was added, and if yes, add to stored bees + List s = this.getSnapshot().getStored(); + if(length < s.size()) { + storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(s.get(s.size() - 1), this)); + } + // Purpur end } @Override @@ -95,6 +141,7 @@ public class CraftBeehive extends CraftBlockEntityState impl @Override public void clearEntities() { getSnapshot().clearBees(); + storage.clear(); // Purpur } // Paper end } diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java index f0b0348e105fb27c829ec29e638433c57bfd5f64..57ce4b7c5fcfe7a88928cd4124f29af39e117ed9 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java @@ -29,7 +29,7 @@ public class CraftConduit extends CraftBlockEntityState impl @Override public int getRange() { requirePlaced(); - return this.getTileEntity().effectBlocks.size() / 7 * 16; + return this.getTileEntity().effectBlocks.size() / 7 * this.world.getHandle().purpurConfig.conduitDistance; // Purpur } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java index 4e56018b64d11f76c8da43fd8f85c6de72204e36..aa8212432825db65cf485cd93f734ccd9eefcb5a 100644 --- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java +++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java @@ -21,7 +21,12 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co @Override public void sendMessage(String message) { - this.sendRawMessage(message); + // Purpur start + String[] parts = message.split("\n"); + for (String part : parts) { + this.sendRawMessage(part); + } + // Purpur end } @Override @@ -91,7 +96,7 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co // Paper start @Override public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) { - this.sendRawMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); + this.sendMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); // Purpur } @Override diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java index 5b7579395e61684592758f408d61cffe57f8b21d..015ea2ecac493c88f45d6ad66c2ef346252b4ec8 100644 --- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java @@ -71,6 +71,8 @@ public class CraftEnchantment extends Enchantment implements Handleable EnchantmentTarget.TRIDENT; case CROSSBOW -> EnchantmentTarget.CROSSBOW; case VANISHABLE -> EnchantmentTarget.VANISHABLE; + case BOW_AND_CROSSBOW -> EnchantmentTarget.BOW_AND_CROSSBOW; // Purpur + case WEAPON_AND_SHEARS -> EnchantmentTarget.WEAPON_AND_SHEARS; // Purpur }; } 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 0b5a31477e3b76833fb97a455842316193663c8e..897f9f5d25c674f167530810c0b94bca83c3c3cc 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java @@ -84,6 +84,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { this.entityType = CraftEntityType.minecraftToBukkit(entity.getType()); } + @Override + public boolean isInDaylight() { + return getHandle().isSunBurnTick(); + } + public static CraftEntity getEntity(CraftServer server, T entity) { Preconditions.checkArgument(entity != null, "Unknown entity"); @@ -253,6 +258,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; } @@ -1228,4 +1237,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 acbb64010cd59668aa1bcb52ff1220789cadb1d3..6c2e91b986099027d83aba85f14ae69cbc358348 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 63cae1a2e95d8da17c45c4404a8dd0ca6a413c39..966587c2788b5c93be83259ddc962a89cde7cbaa 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java @@ -27,4 +27,17 @@ public class CraftIronGolem extends CraftGolem implements IronGolem { public void setPlayerCreated(boolean playerCreated) { this.getHandle().setPlayerCreated(playerCreated); } + + // Purpur start + @Override + @org.jetbrains.annotations.Nullable + public java.util.UUID getSummoner() { + return getHandle().getSummoner(); + } + + @Override + public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { + getHandle().setSummoner(summoner); + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java index 30d62ee4d5cd2ddacb8783b5bbbf475d592b3e02..9b1dd59df1f7faea2fc74d82d003bd442fd52248 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java @@ -151,4 +151,46 @@ public class CraftItem extends CraftEntity implements Item { public String toString() { return "CraftItem"; } + + // Purpur start + @Override + public void setImmuneToCactus(boolean immuneToCactus) { + this.getHandle().immuneToCactus = immuneToCactus; + } + + @Override + public boolean isImmuneToCactus() { + return this.getHandle().immuneToCactus; + } + + @Override + public void setImmuneToExplosion(boolean immuneToExplosion) { + this.getHandle().immuneToExplosion = immuneToExplosion; + } + + @Override + public boolean isImmuneToExplosion() { + return this.getHandle().immuneToExplosion; + } + + @Override + public void setImmuneToFire(boolean immuneToFire) { + this.getHandle().immuneToFire = immuneToFire; + } + + @Override + public boolean isImmuneToFire() { + return this.getHandle().immuneToFire; + } + + @Override + public void setImmuneToLightning(boolean immuneToLightning) { + this.getHandle().immuneToLightning = immuneToLightning; + } + + @Override + public boolean isImmuneToLightning() { + return this.getHandle().immuneToLightning; + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java index 3f952d1409118535f325f614f1a6507c40efa4d6..fcb2687c7183114ecd36d4a190969f148f71ea8e 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java @@ -490,7 +490,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 @@ -1156,4 +1156,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 616d2e479d91673695ade0db151a0099b568904f..988cc1290200de629a4c24cc67a03e69c2fcc727 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -488,10 +488,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public void setPlayerListName(String name) { + // Purpur start + setPlayerListName(name, false); + } + public void setPlayerListName(String name, boolean useMM) { + // Purpur end if (name == null) { name = this.getName(); } - this.getHandle().listName = name.equals(this.getName()) ? null : CraftChatMessage.fromStringOrNull(name); + this.getHandle().listName = name.equals(this.getName()) ? null : useMM ? io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name)) : CraftChatMessage.fromStringOrNull(name); // Purpur for (ServerPlayer player : (List) this.server.getHandle().players) { if (player.getBukkitEntity().canSee(this)) { player.connection.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, this.getHandle())); @@ -1351,6 +1356,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; } @@ -2661,6 +2670,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return this.getHandle().getAbilities().walkingSpeed * 2f; } + // Purpur start - OfflinePlayer API + @Override + public boolean teleportOffline(@NotNull Location destination) { + return this.teleport(destination); + } + + @Override + public boolean teleportOffline(Location destination, PlayerTeleportEvent.TeleportCause cause) { + return this.teleport(destination, cause); + } + + @Override + public java.util.concurrent.CompletableFuture teleportOfflineAsync(@NotNull Location destination) { + return this.teleportAsync(destination); + } + + @Override + public java.util.concurrent.CompletableFuture teleportOfflineAsync(@NotNull Location destination, PlayerTeleportEvent.TeleportCause cause) { + return this.teleportAsync(destination, cause); + } + // Purpur end - OfflinePlayer API + private void validateSpeed(float value) { Preconditions.checkArgument(value <= 1f && value >= -1f, "Speed value (%s) need to be between -1f and 1f", value); } @@ -3427,4 +3458,70 @@ public class CraftPlayer extends CraftHumanEntity implements Player { public void setSendViewDistance(final int viewDistance) { this.getHandle().setSendViewDistance(viewDistance); } + + // Purpur start + @Override + public boolean usesPurpurClient() { + return getHandle().purpurClient; + } + + @Override + public boolean isAfk() { + return getHandle().isAfk(); + } + + @Override + public void setAfk(boolean setAfk) { + getHandle().setAfk(setAfk); + } + + @Override + public void resetIdleTimer() { + getHandle().resetLastActionTime(); + } + + @Override + public void sendBlockHighlight(Location location, int duration) { + sendBlockHighlight(location, duration, "", 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, int argb) { + sendBlockHighlight(location, duration, "", argb); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text) { + sendBlockHighlight(location, duration, text, 0x6400FF00); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, int argb) { + if (this.getHandle().connection == null) return; + this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket(new net.minecraft.network.protocol.common.custom.GameTestAddMarkerDebugPayload(io.papermc.paper.util.MCUtil.toBlockPosition(location), argb, text, duration))); + } + + @Override + public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { + sendBlockHighlight(location, duration, "", color, transparency); + } + + @Override + public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { + if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); + sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); + } + + @Override + public void clearBlockHighlights() { + if (this.getHandle().connection == null) return; + this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket(new net.minecraft.network.protocol.common.custom.GameTestClearMarkersDebugPayload())); + } + + @Override + public void sendDeathScreen(net.kyori.adventure.text.Component message) { + if (this.getHandle().connection == null) return; + this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket(getEntityId(), io.papermc.paper.adventure.PaperAdventure.asVanilla(message))); + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java index 4ce2373ff71c3c1b8951646e057587a3ab09e145..4f7f6cf6ca24406570d2d29dc63dc89401119961 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java @@ -28,4 +28,17 @@ public class CraftSnowman extends CraftGolem implements Snowman, com.destroystok public String toString() { return "CraftSnowman"; } + + // Purpur start + @Override + @org.jetbrains.annotations.Nullable + public java.util.UUID getSummoner() { + return getHandle().getSummoner(); + } + + @Override + public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { + getHandle().setSummoner(summoner); + } + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java index 6c15d40979fd3e3d246a447c432b321fbf29ada3..6ace76a829c88e2e747dbbcce0a6582c615fc56d 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java @@ -252,4 +252,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 7a8ce6956db56061af93ba9761f5d1057a90bc49..6d286b23806666f7b00ac88c5922144649f8a041 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 38b6d2c377800134de592a780b737b45c8096a11..449acd9dc983be1cd51208bc8f8d843d2ddaa923 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 4c2e8129481384a143384d327e14320023735b1a..94be4f367f4c6cb2386d0e59d0417f29fca1cec7 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -592,6 +592,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; } @@ -1125,7 +1134,7 @@ public class CraftEventFactory { return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), entity, DamageCause.LAVA, bukkitDamageSource, modifiers, modifierFunctions, cancelled); } else if (source.getDirectBlock() != null) { DamageCause cause; - if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL)) { + if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL) || source.isStonecutter()) { // Purpur cause = DamageCause.CONTACT; } else if (source.is(DamageTypes.HOT_FLOOR)) { cause = DamageCause.HOT_FLOOR; @@ -1183,6 +1192,7 @@ public class CraftEventFactory { EntityDamageEvent event; if (damager != null) { event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, critical); + damager.processClick(InteractionHand.MAIN_HAND); // Purpur } else { event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions); } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java index 977b77547f7ba62cef3640cf8d4f1c8e7cded53a..beae43e9b6fe447e7515d878ac175f461968768a 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java @@ -184,8 +184,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 af1ae3dacb628da23f7d2988c6e76d3fb2d64103..4ee2d501f882279b48edb4b8bf0824587c276bf6 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java @@ -84,7 +84,7 @@ public class CraftInventory implements Inventory { @Override public void setContents(ItemStack[] items) { - Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize()); + // Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize()); // Purpur for (int i = 0; i < this.getSize(); i++) { if (i >= items.length) { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java index 9ee14589d63bbfc0880f2eee5e924fe946ee0035..0a5841fa26698e60bdeadbb58b9343fe1ff08a28 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)"); this.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/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java index 71aac5d4cf29cea9daa378fc8ac584750de4d1ca..3e0372d3572431bdc417779888bcdcdd672c1531 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java @@ -596,4 +596,17 @@ public final class CraftItemFactory implements ItemFactory { return CraftItemStack.asCraftMirror(enchanted); } // Paper end - enchantWithLevels API + + // Purpur start + @Override + public @org.jetbrains.annotations.NotNull java.util.List getHoverLines(@org.jetbrains.annotations.NotNull ItemStack itemStack, boolean advanced) { + return io.papermc.paper.adventure.PaperAdventure.asAdventure( + CraftItemStack.asNMSCopy(itemStack).getTooltipLines( + null, + advanced ? net.minecraft.world.item.TooltipFlag.ADVANCED + : net.minecraft.world.item.TooltipFlag.NORMAL + ) + ); + } + // 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 bac3a5c378054481e1a5abaec1f83afde5d64ac1..f1050bf2b9efc54a894426b08989d44566acd875 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java @@ -45,6 +45,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 @@ -97,7 +98,13 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { boolean ambient = effect.getBoolean(CraftMetaPotion.AMBIENT.NBT); boolean particles = effect.contains(CraftMetaPotion.SHOW_PARTICLES.NBT, CraftMagicNumbers.NBT.TAG_BYTE) ? effect.getBoolean(CraftMetaPotion.SHOW_PARTICLES.NBT) : true; boolean icon = effect.contains(CraftMetaPotion.SHOW_ICON.NBT, CraftMagicNumbers.NBT.TAG_BYTE) ? effect.getBoolean(CraftMetaPotion.SHOW_ICON.NBT) : particles; - this.customEffects.add(new PotionEffect(type, duration, amp, ambient, particles, icon)); + // Purpur start + NamespacedKey key = null; + if (tag.contains(CraftMetaPotion.KEY.NBT)) { + key = NamespacedKey.fromString(effect.getString(CraftMetaPotion.KEY.NBT)); + } + this.customEffects.add(new PotionEffect(type, duration, amp, ambient, particles, icon, key)); + // Purpur end } } } @@ -150,6 +157,11 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { effectData.putBoolean(CraftMetaPotion.AMBIENT.NBT, effect.isAmbient()); effectData.putBoolean(CraftMetaPotion.SHOW_PARTICLES.NBT, effect.hasParticles()); effectData.putBoolean(CraftMetaPotion.SHOW_ICON.NBT, effect.hasIcon()); + // Purpur start + if (effect.hasKey()) { + effectData.putString(CraftMetaPotion.KEY.NBT, effect.getKey().toString()); + } + // Purpur end effectList.add(effectData); } } @@ -225,7 +237,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 6ba29875d78ede4aa7978ff689e588f7fed11528..4afec4387971612f62b825e9e56c2ead7424a7c2 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java @@ -29,6 +29,7 @@ public interface CraftRecipe extends Recipe { } else if (bukkit instanceof RecipeChoice.ExactChoice) { stack = new Ingredient(((RecipeChoice.ExactChoice) bukkit).getChoices().stream().map((mat) -> new net.minecraft.world.item.crafting.Ingredient.ItemValue(CraftItemStack.asNMSCopy(mat)))); stack.exact = true; + stack.predicate = ((RecipeChoice.ExactChoice) bukkit).getPredicate(); // Purpur } else { throw new IllegalArgumentException("Unknown recipe stack instance " + bukkit); } diff --git a/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java index 15e9dd8844f893de5e8372b847c9e8295d6f69ca..b4b105c0190502328d5aeb680dd8e67c2875618f 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 e938255fcc5db0c289d3e132175a541187e4a748..f7a747ea73a80c97d863e0fd3772a0c333aef3c8 100644 --- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java +++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java @@ -74,7 +74,7 @@ public class CraftPotionUtil { public static MobEffectInstance fromBukkit(PotionEffect effect) { MobEffect type = CraftPotionEffectType.bukkitToMinecraft(effect.getType()); // Paper - Note: do not copy over the hidden effect, as this method is only used for applying to entities which we do not want to convert over. - return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon()); // Paper + return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon(), effect.getKey()); // Paper // Purpur - add key } public static PotionEffect toBukkit(MobEffectInstance effect) { @@ -83,7 +83,7 @@ public class CraftPotionUtil { int duration = effect.getDuration(); boolean ambient = effect.isAmbient(); boolean particles = effect.isVisible(); - return new PotionEffect(type, duration, amp, ambient, particles, effect.showIcon(), effect.hiddenEffect == null ? null : toBukkit(effect.hiddenEffect)); // Paper + return new PotionEffect(type, duration, amp, ambient, particles, effect.showIcon(), effect.hiddenEffect == null ? null : toBukkit(effect.hiddenEffect), effect.getKey()); // Paper // Purpur - add key } public static boolean equals(MobEffect mobEffect, PotionEffectType type) { diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java index dd95b3bfe59f2bb635afe92317288efcd2986326..11f43f44f359ce57d3a8f3322e58b9f5dfdaf00a 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..0c8bcfbe35f9133a32a31163e5bf2f04786c77a0 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java @@ -0,0 +1,646 @@ +package org.purpurmc.purpur; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import com.mojang.datafixers.util.Pair; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.EntityDimensions; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.food.FoodProperties; +import net.minecraft.world.food.Foods; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; +import org.purpurmc.purpur.command.PurpurCommand; +import org.purpurmc.purpur.task.TPSBarTask; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; + +@SuppressWarnings("unused") +public class PurpurConfig { + private static final String HEADER = "This is the main configuration file for Purpur.\n" + + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n" + + "with caution, and make sure you know what each option does before configuring.\n" + + "\n" + + "If you need help with the configuration or have any questions related to Purpur,\n" + + "join us in our Discord guild.\n" + + "\n" + + "Website: https://purpurmc.org \n" + + "Docs: https://purpurmc.org/docs \n"; + private static File CONFIG_FILE; + public static YamlConfiguration config; + + private static Map commands; + + public static int version; + static boolean verbose; + + public static void init(File configFile) { + CONFIG_FILE = configFile; + config = new YamlConfiguration(); + try { + config.load(CONFIG_FILE); + } catch (IOException ignore) { + } catch (InvalidConfigurationException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Could not load purpur.yml, please correct your syntax errors", ex); + throw Throwables.propagate(ex); + } + config.options().header(HEADER); + config.options().copyDefaults(true); + verbose = getBoolean("verbose", false); + + commands = new HashMap<>(); + commands.put("purpur", new PurpurCommand("purpur")); + + version = getInt("config-version", 34); + set("config-version", 34); + + readConfig(PurpurConfig.class, null); + + Blocks.rebuildCache(); + } + + protected static void log(String s) { + if (verbose) { + log(Level.INFO, s); + } + } + + protected static void log(Level level, String s) { + Bukkit.getLogger().log(level, s); + } + + public static void registerCommands() { + for (Map.Entry entry : commands.entrySet()) { + MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Purpur", entry.getValue()); + } + } + + static void readConfig(Class clazz, Object instance) { + for (Method method : clazz.getDeclaredMethods()) { + if (Modifier.isPrivate(method.getModifiers())) { + if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) { + try { + method.setAccessible(true); + method.invoke(instance); + } catch (InvocationTargetException ex) { + throw Throwables.propagate(ex.getCause()); + } catch (Exception ex) { + Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex); + } + } + } + } + + try { + config.save(CONFIG_FILE); + } catch (IOException ex) { + Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex); + } + } + + private static void set(String path, Object val) { + config.addDefault(path, val); + config.set(path, val); + } + + private static String getString(String path, String def) { + config.addDefault(path, def); + return config.getString(path, config.getString(path)); + } + + private static boolean getBoolean(String path, boolean def) { + config.addDefault(path, def); + return config.getBoolean(path, config.getBoolean(path)); + } + + private static double getDouble(String path, double def) { + config.addDefault(path, def); + return config.getDouble(path, config.getDouble(path)); + } + + private static int getInt(String path, int def) { + config.addDefault(path, def); + return config.getInt(path, config.getInt(path)); + } + + private static List getList(String path, T def) { + config.addDefault(path, def); + return config.getList(path, config.getList(path)); + } + + static Map getMap(String path, Map def) { + if (def != null && config.getConfigurationSection(path) == null) { + config.addDefault(path, def); + return def; + } + return toMap(config.getConfigurationSection(path)); + } + + private static Map toMap(ConfigurationSection section) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + if (section != null) { + for (String key : section.getKeys(false)) { + Object obj = section.get(key); + if (obj != null) { + builder.put(key, obj instanceof ConfigurationSection val ? toMap(val) : obj); + } + } + } + return builder.build(); + } + + public static String cannotRideMob = "You cannot mount that mob"; + public static String afkBroadcastAway = "%s is now AFK"; + public static String afkBroadcastBack = "%s is no longer AFK"; + public static boolean afkBroadcastUseDisplayName = false; + public static String afkTabListPrefix = "[AFK] "; + public static String afkTabListSuffix = ""; + public static String creditsCommandOutput = "%s has been shown the end credits"; + public static String demoCommandOutput = "%s has been shown the demo screen"; + public static String pingCommandOutput = "%s's ping is %sms"; + public static String ramCommandOutput = "Ram Usage: / ()"; + public static String rambarCommandOutput = "Rambar toggled for "; + public static String tpsbarCommandOutput = "Tpsbar toggled for "; + public static String dontRunWithScissors = "Don't run with scissors!"; + public static String uptimeCommandOutput = "Server uptime is "; + public static String unverifiedUsername = "default"; + public static String sleepSkippingNight = "default"; + public static String sleepingPlayersPercent = "default"; + public static String sleepNotPossible = "default"; + private static void messages() { + cannotRideMob = getString("settings.messages.cannot-ride-mob", cannotRideMob); + afkBroadcastAway = getString("settings.messages.afk-broadcast-away", afkBroadcastAway); + afkBroadcastBack = getString("settings.messages.afk-broadcast-back", afkBroadcastBack); + afkBroadcastUseDisplayName = getBoolean("settings.messages.afk-broadcast-use-display-name", afkBroadcastUseDisplayName); + afkTabListPrefix = MiniMessage.miniMessage().serialize(MiniMessage.miniMessage().deserialize(getString("settings.messages.afk-tab-list-prefix", afkTabListPrefix))); + afkTabListSuffix = MiniMessage.miniMessage().serialize(MiniMessage.miniMessage().deserialize(getString("settings.messages.afk-tab-list-suffix", afkTabListSuffix))); + creditsCommandOutput = getString("settings.messages.credits-command-output", creditsCommandOutput); + demoCommandOutput = getString("settings.messages.demo-command-output", demoCommandOutput); + pingCommandOutput = getString("settings.messages.ping-command-output", pingCommandOutput); + ramCommandOutput = getString("settings.messages.ram-command-output", ramCommandOutput); + rambarCommandOutput = getString("settings.messages.rambar-command-output", rambarCommandOutput); + tpsbarCommandOutput = getString("settings.messages.tpsbar-command-output", tpsbarCommandOutput); + dontRunWithScissors = getString("settings.messages.dont-run-with-scissors", dontRunWithScissors); + uptimeCommandOutput = getString("settings.messages.uptime-command-output", uptimeCommandOutput); + unverifiedUsername = getString("settings.messages.unverified-username", unverifiedUsername); + sleepSkippingNight = getString("settings.messages.sleep-skipping-night", sleepSkippingNight); + sleepingPlayersPercent = getString("settings.messages.sleeping-players-percent", sleepingPlayersPercent); + sleepNotPossible = getString("settings.messages.sleep-not-possible", sleepNotPossible); + } + + public static String deathMsgRunWithScissors = " slipped and fell on their shears"; + public static String deathMsgStonecutter = " has sawed themself in half"; + private static void deathMessages() { + deathMsgRunWithScissors = getString("settings.messages.death-message.run-with-scissors", deathMsgRunWithScissors); + deathMsgStonecutter = getString("settings.messages.death-message.stonecutter", deathMsgStonecutter); + } + + public static boolean advancementOnlyBroadcastToAffectedPlayer = false; + public static boolean deathMessageOnlyBroadcastToAffectedPlayer = false; + private static void broadcastSettings() { + if (version < 13) { + boolean oldValue = getBoolean("settings.advancement.only-broadcast-to-affected-player", false); + set("settings.broadcasts.advancement.only-broadcast-to-affected-player", oldValue); + set("settings.advancement.only-broadcast-to-affected-player", null); + } + advancementOnlyBroadcastToAffectedPlayer = getBoolean("settings.broadcasts.advancement.only-broadcast-to-affected-player", advancementOnlyBroadcastToAffectedPlayer); + deathMessageOnlyBroadcastToAffectedPlayer = getBoolean("settings.broadcasts.death.only-broadcast-to-affected-player", deathMessageOnlyBroadcastToAffectedPlayer); + } + + public static String serverModName = "Purpur"; + private static void serverModName() { + serverModName = getString("settings.server-mod-name", serverModName); + } + + public static double laggingThreshold = 19.0D; + private static void tickLoopSettings() { + laggingThreshold = getDouble("settings.lagging-threshold", laggingThreshold); + } + + public static boolean disableGiveCommandDrops = false; + private static void disableGiveCommandDrops() { + disableGiveCommandDrops = getBoolean("settings.disable-give-dropping", disableGiveCommandDrops); + } + + public static String commandRamBarTitle = "Ram: / ()"; + public static BossBar.Overlay commandRamBarProgressOverlay = BossBar.Overlay.NOTCHED_20; + public static BossBar.Color commandRamBarProgressColorGood = BossBar.Color.GREEN; + public static BossBar.Color commandRamBarProgressColorMedium = BossBar.Color.YELLOW; + public static BossBar.Color commandRamBarProgressColorLow = BossBar.Color.RED; + public static String commandRamBarTextColorGood = ""; + public static String commandRamBarTextColorMedium = ""; + public static String commandRamBarTextColorLow = ""; + public static int commandRamBarTickInterval = 20; + public static String commandTPSBarTitle = "TPS: MSPT: Ping: ms"; + public static BossBar.Overlay commandTPSBarProgressOverlay = BossBar.Overlay.NOTCHED_20; + public static TPSBarTask.FillMode commandTPSBarProgressFillMode = TPSBarTask.FillMode.MSPT; + public static BossBar.Color commandTPSBarProgressColorGood = BossBar.Color.GREEN; + public static BossBar.Color commandTPSBarProgressColorMedium = BossBar.Color.YELLOW; + public static BossBar.Color commandTPSBarProgressColorLow = BossBar.Color.RED; + public static String commandTPSBarTextColorGood = ""; + public static String commandTPSBarTextColorMedium = ""; + public static String commandTPSBarTextColorLow = ""; + public static int commandTPSBarTickInterval = 20; + public static String commandCompassBarTitle = "S \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 W \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 N \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 E \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 S \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 W \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 N \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 E \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 "; + public static BossBar.Overlay commandCompassBarProgressOverlay = BossBar.Overlay.PROGRESS; + public static BossBar.Color commandCompassBarProgressColor = BossBar.Color.BLUE; + public static float commandCompassBarProgressPercent = 1.0F; + public static int commandCompassBarTickInterval = 5; + public static boolean commandGamemodeRequiresPermission = false; + public static boolean hideHiddenPlayersFromEntitySelector = false; + public static String uptimeFormat = ""; + public static String uptimeDay = "%02d day, "; + public static String uptimeDays = "%02d days, "; + public static String uptimeHour = "%02d hour, "; + public static String uptimeHours = "%02d hours, "; + public static String uptimeMinute = "%02d minute, and "; + public static String uptimeMinutes = "%02d minutes, and "; + public static String uptimeSecond = "%02d second"; + public static String uptimeSeconds = "%02d seconds"; + private static void commandSettings() { + commandRamBarTitle = getString("settings.command.rambar.title", commandRamBarTitle); + commandRamBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.rambar.overlay", commandRamBarProgressOverlay.name())); + commandRamBarProgressColorGood = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.good", commandRamBarProgressColorGood.name())); + commandRamBarProgressColorMedium = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.medium", commandRamBarProgressColorMedium.name())); + commandRamBarProgressColorLow = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.low", commandRamBarProgressColorLow.name())); + commandRamBarTextColorGood = getString("settings.command.rambar.text-color.good", commandRamBarTextColorGood); + commandRamBarTextColorMedium = getString("settings.command.rambar.text-color.medium", commandRamBarTextColorMedium); + commandRamBarTextColorLow = getString("settings.command.rambar.text-color.low", commandRamBarTextColorLow); + commandRamBarTickInterval = getInt("settings.command.rambar.tick-interval", commandRamBarTickInterval); + + commandTPSBarTitle = getString("settings.command.tpsbar.title", commandTPSBarTitle); + commandTPSBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.tpsbar.overlay", commandTPSBarProgressOverlay.name())); + commandTPSBarProgressFillMode = TPSBarTask.FillMode.valueOf(getString("settings.command.tpsbar.fill-mode", commandTPSBarProgressFillMode.name())); + commandTPSBarProgressColorGood = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.good", commandTPSBarProgressColorGood.name())); + commandTPSBarProgressColorMedium = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.medium", commandTPSBarProgressColorMedium.name())); + commandTPSBarProgressColorLow = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.low", commandTPSBarProgressColorLow.name())); + commandTPSBarTextColorGood = getString("settings.command.tpsbar.text-color.good", commandTPSBarTextColorGood); + commandTPSBarTextColorMedium = getString("settings.command.tpsbar.text-color.medium", commandTPSBarTextColorMedium); + commandTPSBarTextColorLow = getString("settings.command.tpsbar.text-color.low", commandTPSBarTextColorLow); + commandTPSBarTickInterval = getInt("settings.command.tpsbar.tick-interval", commandTPSBarTickInterval); + + commandCompassBarTitle = getString("settings.command.compass.title", commandCompassBarTitle); + commandCompassBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.compass.overlay", commandCompassBarProgressOverlay.name())); + commandCompassBarProgressColor = BossBar.Color.valueOf(getString("settings.command.compass.progress-color", commandCompassBarProgressColor.name())); + commandCompassBarProgressPercent = (float) getDouble("settings.command.compass.percent", commandCompassBarProgressPercent); + commandCompassBarTickInterval = getInt("settings.command.compass.tick-interval", commandCompassBarTickInterval); + + commandGamemodeRequiresPermission = getBoolean("settings.command.gamemode.requires-specific-permission", commandGamemodeRequiresPermission); + hideHiddenPlayersFromEntitySelector = getBoolean("settings.command.hide-hidden-players-from-entity-selector", hideHiddenPlayersFromEntitySelector); + uptimeFormat = getString("settings.command.uptime.format", uptimeFormat); + uptimeDay = getString("settings.command.uptime.day", uptimeDay); + uptimeDays = getString("settings.command.uptime.days", uptimeDays); + uptimeHour = getString("settings.command.uptime.hour", uptimeHour); + uptimeHours = getString("settings.command.uptime.hours", uptimeHours); + uptimeMinute = getString("settings.command.uptime.minute", uptimeMinute); + uptimeMinutes = getString("settings.command.uptime.minutes", uptimeMinutes); + uptimeSecond = getString("settings.command.uptime.second", uptimeSecond); + uptimeSeconds = getString("settings.command.uptime.seconds", uptimeSeconds); + } + + public static int barrelRows = 3; + public static boolean enderChestSixRows = false; + public static boolean enderChestPermissionRows = false; + public static boolean cryingObsidianValidForPortalFrame = false; + public static int beeInsideBeeHive = 3; + public static boolean anvilCumulativeCost = true; + public static int lightningRodRange = 128; + public static Set grindstoneIgnoredEnchants = new HashSet<>(); + public static boolean grindstoneRemoveAttributes = false; + public static boolean grindstoneRemoveDisplay = false; + public static int caveVinesMaxGrowthAge = 25; + public static int kelpMaxGrowthAge = 25; + public static int twistingVinesMaxGrowthAge = 25; + public static int weepingVinesMaxGrowthAge = 25; + private static void blockSettings() { + if (version < 3) { + boolean oldValue = getBoolean("settings.barrel.packed-barrels", true); + set("settings.blocks.barrel.six-rows", oldValue); + set("settings.packed-barrels", null); + oldValue = getBoolean("settings.large-ender-chests", true); + set("settings.blocks.ender_chest.six-rows", oldValue); + set("settings.large-ender-chests", null); + } + if (version < 20) { + boolean oldValue = getBoolean("settings.blocks.barrel.six-rows", false); + set("settings.blocks.barrel.rows", oldValue ? 6 : 3); + set("settings.blocks.barrel.six-rows", null); + } + barrelRows = getInt("settings.blocks.barrel.rows", barrelRows); + if (barrelRows < 1 || barrelRows > 6) { + Bukkit.getLogger().severe("settings.blocks.barrel.rows must be 1-6, resetting to default"); + barrelRows = 3; + } + org.bukkit.event.inventory.InventoryType.BARREL.setDefaultSize(switch (barrelRows) { + case 6 -> 54; + case 5 -> 45; + case 4 -> 36; + case 2 -> 18; + case 1 -> 9; + default -> 27; + }); + enderChestSixRows = getBoolean("settings.blocks.ender_chest.six-rows", enderChestSixRows); + org.bukkit.event.inventory.InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27); + enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); + cryingObsidianValidForPortalFrame = getBoolean("settings.blocks.crying_obsidian.valid-for-portal-frame", cryingObsidianValidForPortalFrame); + beeInsideBeeHive = getInt("settings.blocks.beehive.max-bees-inside", beeInsideBeeHive); + anvilCumulativeCost = getBoolean("settings.blocks.anvil.cumulative-cost", anvilCumulativeCost); + lightningRodRange = getInt("settings.blocks.lightning_rod.range", lightningRodRange); + ArrayList defaultCurses = new ArrayList<>(){{ + add("minecraft:binding_curse"); + add("minecraft:vanishing_curse"); + }}; + if (version < 24 && !getBoolean("settings.blocks.grindstone.ignore-curses", true)) { + defaultCurses.clear(); + } + getList("settings.blocks.grindstone.ignored-enchants", defaultCurses).forEach(key -> { + Enchantment enchantment = BuiltInRegistries.ENCHANTMENT.get(new ResourceLocation(key.toString())); + grindstoneIgnoredEnchants.add(enchantment); + }); + grindstoneRemoveAttributes = getBoolean("settings.blocks.grindstone.remove-attributes", grindstoneRemoveAttributes); + grindstoneRemoveDisplay = getBoolean("settings.blocks.grindstone.remove-name-and-lore", grindstoneRemoveDisplay); + caveVinesMaxGrowthAge = getInt("settings.blocks.cave_vines.max-growth-age", caveVinesMaxGrowthAge); + if (caveVinesMaxGrowthAge > 25) { + caveVinesMaxGrowthAge = 25; + log(Level.WARNING, "blocks.cave_vines.max-growth-age is set to above maximum allowed value of 25"); + log(Level.WARNING, "Using value of 25 to prevent issues"); + } + kelpMaxGrowthAge = getInt("settings.blocks.kelp.max-growth-age", kelpMaxGrowthAge); + if (kelpMaxGrowthAge > 25) { + kelpMaxGrowthAge = 25; + log(Level.WARNING, "blocks.kelp.max-growth-age is set to above maximum allowed value of 25"); + log(Level.WARNING, "Using value of 25 to prevent issues"); + } + twistingVinesMaxGrowthAge = getInt("settings.blocks.twisting_vines.max-growth-age", twistingVinesMaxGrowthAge); + if (twistingVinesMaxGrowthAge > 25) { + twistingVinesMaxGrowthAge = 25; + log(Level.WARNING, "blocks.twisting_vines.max-growth-age is set to above maximum allowed value of 25"); + log(Level.WARNING, "Using value of 25 to prevent issues"); + } + weepingVinesMaxGrowthAge = getInt("settings.blocks.weeping_vines.max-growth-age", weepingVinesMaxGrowthAge); + if (weepingVinesMaxGrowthAge > 25) { + weepingVinesMaxGrowthAge = 25; + log(Level.WARNING, "blocks.weeping_vines.max-growth-age is set to above maximum allowed value of 25"); + log(Level.WARNING, "Using value of 25 to prevent issues"); + } + } + + public static boolean allowInfinityMending = false; + public static boolean allowCrossbowInfinity = false; + public static boolean allowShearsLooting = false; + public static boolean allowUnsafeEnchants = false; + public static boolean allowInapplicableEnchants = true; + public static boolean allowIncompatibleEnchants = true; + public static boolean allowHigherEnchantsLevels = true; + public static boolean allowUnsafeEnchantCommand = false; + public static boolean replaceIncompatibleEnchants = false; + public static boolean clampEnchantLevels = true; + private static void enchantmentSettings() { + if (version < 5) { + boolean oldValue = getBoolean("settings.enchantment.allow-infinite-and-mending-together", false); + set("settings.enchantment.allow-infinity-and-mending-together", oldValue); + set("settings.enchantment.allow-infinite-and-mending-together", null); + } + if (version < 30) { + boolean oldValue = getBoolean("settings.enchantment.allow-unsafe-enchants", false); + set("settings.enchantment.anvil.allow-unsafe-enchants", oldValue); + set("settings.enchantment.anvil.allow-inapplicable-enchants", true); + set("settings.enchantment.anvil.allow-incompatible-enchants", true); + set("settings.enchantment.anvil.allow-higher-enchants-levels", true); + set("settings.enchantment.allow-unsafe-enchants", null); + } + allowInfinityMending = getBoolean("settings.enchantment.allow-infinity-and-mending-together", allowInfinityMending); + allowCrossbowInfinity = getBoolean("settings.enchantment.allow-infinity-on-crossbow", allowCrossbowInfinity); + allowShearsLooting = getBoolean("settings.enchantment.allow-looting-on-shears", allowShearsLooting); + allowUnsafeEnchants = getBoolean("settings.enchantment.anvil.allow-unsafe-enchants", allowUnsafeEnchants); + allowInapplicableEnchants = getBoolean("settings.enchantment.anvil.allow-inapplicable-enchants", allowInapplicableEnchants); + allowIncompatibleEnchants = getBoolean("settings.enchantment.anvil.allow-incompatible-enchants", allowIncompatibleEnchants); + allowHigherEnchantsLevels = getBoolean("settings.enchantment.anvil.allow-higher-enchants-levels", allowHigherEnchantsLevels); + allowUnsafeEnchantCommand = getBoolean("settings.enchantment.allow-unsafe-enchant-command", allowUnsafeEnchants); // allowUnsafeEnchants as default for backwards compatability + replaceIncompatibleEnchants = getBoolean("settings.enchantment.anvil.replace-incompatible-enchants", replaceIncompatibleEnchants); + clampEnchantLevels = getBoolean("settings.enchantment.clamp-levels", clampEnchantLevels); + } + + public static boolean endermanShortHeight = false; + private static void entitySettings() { + endermanShortHeight = getBoolean("settings.entity.enderman.short-height", endermanShortHeight); + if (endermanShortHeight) EntityType.ENDERMAN.setDimensions(EntityDimensions.scalable(0.6F, 1.9F)); + } + + public static boolean allowWaterPlacementInTheEnd = true; + private static void allowWaterPlacementInEnd() { + allowWaterPlacementInTheEnd = getBoolean("settings.allow-water-placement-in-the-end", allowWaterPlacementInTheEnd); + } + + public static boolean beeCountPayload = false; + private static void beeCountPayload() { + beeCountPayload = getBoolean("settings.bee-count-payload", beeCountPayload); + } + + public static boolean tpsCatchup = true; + private static void tpsCatchup() { + tpsCatchup = getBoolean("settings.tps-catchup", tpsCatchup); + } + + public static boolean useUPnP = false; + public static boolean maxJoinsPerSecond = false; + public static boolean kickForOutOfOrderChat = true; + private static void networkSettings() { + useUPnP = getBoolean("settings.network.upnp-port-forwarding", useUPnP); + maxJoinsPerSecond = getBoolean("settings.network.max-joins-per-second", maxJoinsPerSecond); + kickForOutOfOrderChat = getBoolean("settings.network.kick-for-out-of-order-chat", kickForOutOfOrderChat); + } + + public static java.util.regex.Pattern usernameValidCharactersPattern; + private static void usernameValidationSettings() { + String defaultPattern = "^[a-zA-Z0-9_.]*$"; + String setPattern = getString("settings.username-valid-characters", defaultPattern); + usernameValidCharactersPattern = java.util.regex.Pattern.compile(setPattern == null || setPattern.isBlank() ? defaultPattern : setPattern); + } + + private static void foodSettings() { + ConfigurationSection properties = config.getConfigurationSection("settings.food-properties"); + if (properties == null) { + config.addDefault("settings.food-properties", new HashMap<>()); + return; + } + + Map> effectDefaults = new HashMap<>(); + Map EFFECT_DEFAULT = Map.of( + "chance", 0.0F, + "duration", 0, + "amplifier", 0, + "ambient", false, + "visible", true, + "show-icon", true + ); + + properties.getKeys(false).forEach(foodKey -> { + FoodProperties food = Foods.ALL_PROPERTIES.get(foodKey); + if (food == null) { + PurpurConfig.log(Level.SEVERE, "Invalid food property: " + foodKey); + return; + } + FoodProperties foodDefaults = Foods.DEFAULT_PROPERTIES.get(foodKey); + food.setNutrition(properties.getInt(foodKey + ".nutrition", foodDefaults.getNutrition())); + food.setSaturationModifier((float) properties.getDouble(foodKey + ".saturation-modifier", foodDefaults.getSaturationModifier())); + food.setIsMeat(properties.getBoolean(foodKey + ".is-meat", foodDefaults.isMeat())); + food.setCanAlwaysEat(properties.getBoolean(foodKey + ".can-always-eat", foodDefaults.canAlwaysEat())); + food.setFastFood(properties.getBoolean(foodKey + ".fast-food", foodDefaults.isFastFood())); + ConfigurationSection effects = properties.getConfigurationSection(foodKey + ".effects"); + if (effects != null) { + effectDefaults.clear(); + foodDefaults.getEffects().forEach(pair -> { + MobEffectInstance effect = pair.getFirst(); + effectDefaults.put(effect.getEffect(), Map.of( + "chance", pair.getSecond(), + "duration", effect.getDuration(), + "amplifier", effect.getAmplifier(), + "ambient", effect.isAmbient(), + "visible", effect.isVisible(), + "show-icon", effect.showIcon() + )); + }); + effects.getKeys(false).forEach(effectKey -> { + MobEffect effect = BuiltInRegistries.MOB_EFFECT.get(new ResourceLocation(effectKey)); + if (effect == null) { + PurpurConfig.log(Level.SEVERE, "Invalid food property effect for " + foodKey + ": " + effectKey); + return; + } + + Map effectDefault = effectDefaults.get(effect); + if (effectDefault == null) { + effectDefault = EFFECT_DEFAULT; + } + + food.getEffects().removeIf(pair -> pair.getFirst().getEffect() == effect); + float chance = (float) effects.getDouble(effectKey + ".chance", ((Float) effectDefault.get("chance")).doubleValue()); + int duration = effects.getInt(effectKey + ".duration", (int) effectDefault.get("duration")); + if (chance <= 0.0F || duration < 0) { + return; + } + int amplifier = effects.getInt(effectKey + ".amplifier", (int) effectDefault.get("amplifier")); + boolean ambient = effects.getBoolean(effectKey + ".ambient", (boolean) effectDefault.get("ambient")); + boolean visible = effects.getBoolean(effectKey + ".visible", (boolean) effectDefault.get("visible")); + boolean showIcon = effects.getBoolean(effectKey + ".show-icon", (boolean) effectDefault.get("show-icon")); + food.getEffects().add(Pair.of(new MobEffectInstance(effect, duration, amplifier, ambient, visible, showIcon), chance)); + }); + } + }); + } + + public static boolean fixNetworkSerializedItemsInCreative = false; + private static void fixNetworkSerializedCreativeItems() { + fixNetworkSerializedItemsInCreative = getBoolean("settings.fix-network-serialized-items-in-creative", fixNetworkSerializedItemsInCreative); + } + + public static boolean fixProjectileLootingTransfer = false; + private static void fixProjectileLootingTransfer() { + fixProjectileLootingTransfer = getBoolean("settings.fix-projectile-looting-transfer", fixProjectileLootingTransfer); + } + + public static boolean clampAttributes = true; + private static void clampAttributes() { + clampAttributes = getBoolean("settings.clamp-attributes", clampAttributes); + } + + public static boolean limitArmor = true; + private static void limitArmor() { + limitArmor = getBoolean("settings.limit-armor", limitArmor); + } + + private static void blastResistanceSettings() { + getMap("settings.blast-resistance-overrides", Collections.emptyMap()).forEach((blockId, value) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { + log(Level.SEVERE, "Invalid block for `settings.blast-resistance-overrides`: " + blockId); + return; + } + if (!(value instanceof Number blastResistance)) { + log(Level.SEVERE, "Invalid blast resistance for `settings.blast-resistance-overrides." + blockId + "`: " + value); + return; + } + block.explosionResistance = blastResistance.floatValue(); + }); + } + private static void blockFallMultiplierSettings() { + getMap("settings.block-fall-multipliers", Map.ofEntries( + Map.entry("minecraft:hay_block", Map.of("damage", 0.2F)), + Map.entry("minecraft:white_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:light_gray_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:gray_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:black_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:brown_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:pink_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:red_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:orange_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:yellow_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:green_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:lime_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:cyan_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:light_blue_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:blue_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:purple_bed", Map.of("distance", 0.5F)), + Map.entry("minecraft:magenta_bed", Map.of("distance", 0.5F)) + )).forEach((blockId, value) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { + log(Level.SEVERE, "Invalid block for `settings.block-fall-multipliers`: " + blockId); + return; + } + if (!(value instanceof Map map)) { + log(Level.SEVERE, "Invalid fall multiplier for `settings.block-fall-multipliers." + blockId + "`: " + value + + ", expected a map with keys `damage` and `distance` to floats."); + return; + } + Object rawFallDamageMultiplier = map.get("damage"); + if (rawFallDamageMultiplier == null) rawFallDamageMultiplier = 1F; + if (!(rawFallDamageMultiplier instanceof Number fallDamageMultiplier)) { + log(Level.SEVERE, "Invalid multiplier for `settings.block-fall-multipliers." + blockId + ".damage`: " + map.get("damage")); + return; + } + Object rawFallDistanceMultiplier = map.get("distance"); + if (rawFallDistanceMultiplier == null) rawFallDistanceMultiplier = 1F; + if (!(rawFallDistanceMultiplier instanceof Number fallDistanceMultiplier)) { + log(Level.SEVERE, "Invalid multiplier for `settings.block-fall-multipliers." + blockId + ".distance`: " + map.get("distance")); + return; + } + block.fallDamageMultiplier = fallDamageMultiplier.floatValue(); + block.fallDistanceMultiplier = fallDistanceMultiplier.floatValue(); + }); + } + + public static boolean playerDeathsAlwaysShowItem = false; + private static void playerDeathsAlwaysShowItem() { + playerDeathsAlwaysShowItem = getBoolean("settings.player-deaths-always-show-item", playerDeathsAlwaysShowItem); + } + + public static boolean registerMinecraftDebugCommands = false; + private static void registerMinecraftDebugCommands() { + registerMinecraftDebugCommands = getBoolean("settings.register-minecraft-debug-commands", registerMinecraftDebugCommands); + } +} 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..7459ca7b724634a191498bf05e345c7e97fbe901 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java @@ -0,0 +1,3285 @@ +package org.purpurmc.purpur; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.properties.Tilt; +import org.purpurmc.purpur.tool.Flattenable; +import org.purpurmc.purpur.tool.Strippable; +import org.purpurmc.purpur.tool.Tillable; +import org.purpurmc.purpur.tool.Waxable; +import org.purpurmc.purpur.tool.Weatherable; +import org.apache.commons.lang.BooleanUtils; +import org.bukkit.ChatColor; +import org.bukkit.World; +import org.bukkit.configuration.ConfigurationSection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; +import java.util.logging.Level; +import static org.purpurmc.purpur.PurpurConfig.log; + +@SuppressWarnings("unused") +public class PurpurWorldConfig { + + private final String worldName; + private final World.Environment environment; + + public PurpurWorldConfig(String worldName, World.Environment environment) { + this.worldName = worldName; + this.environment = environment; + init(); + } + + public void init() { + log("-------- World Settings For [" + worldName + "] --------"); + PurpurConfig.readConfig(PurpurWorldConfig.class, this); + } + + private void set(String path, Object val) { + PurpurConfig.config.addDefault("world-settings.default." + path, val); + PurpurConfig.config.set("world-settings.default." + path, val); + if (PurpurConfig.config.get("world-settings." + worldName + "." + path) != null) { + PurpurConfig.config.addDefault("world-settings." + worldName + "." + path, val); + PurpurConfig.config.set("world-settings." + worldName + "." + path, val); + } + } + + private ConfigurationSection getConfigurationSection(String path) { + ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + "." + path); + return section != null ? section : PurpurConfig.config.getConfigurationSection("world-settings.default." + path); + } + + private String getString(String path, String def) { + PurpurConfig.config.addDefault("world-settings.default." + path, def); + return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path)); + } + + private boolean getBoolean(String path, boolean def) { + PurpurConfig.config.addDefault("world-settings.default." + path, def); + return PurpurConfig.config.getBoolean("world-settings." + worldName + "." + path, PurpurConfig.config.getBoolean("world-settings.default." + path)); + } + + private boolean getBoolean(String path, Predicate predicate) { + String val = getString(path, "default").toLowerCase(); + Boolean bool = BooleanUtils.toBooleanObject(val, "true", "false", "default"); + return predicate.test(bool); + } + + private double getDouble(String path, double def) { + PurpurConfig.config.addDefault("world-settings.default." + path, def); + return PurpurConfig.config.getDouble("world-settings." + worldName + "." + path, PurpurConfig.config.getDouble("world-settings.default." + path)); + } + + private int getInt(String path, int def) { + PurpurConfig.config.addDefault("world-settings.default." + path, def); + return PurpurConfig.config.getInt("world-settings." + worldName + "." + path, PurpurConfig.config.getInt("world-settings.default." + path)); + } + + private List getList(String path, T def) { + PurpurConfig.config.addDefault("world-settings.default." + path, def); + return PurpurConfig.config.getList("world-settings." + worldName + "." + path, PurpurConfig.config.getList("world-settings.default." + path)); + } + + private Map getMap(String path, Map def) { + final Map fallback = PurpurConfig.getMap("world-settings.default." + path, def); + final Map value = PurpurConfig.getMap("world-settings." + worldName + "." + path, null); + return value.isEmpty() ? fallback : value; + } + + public float armorstandStepHeight = 0.0F; + public boolean armorstandSetNameVisible = true; + public boolean armorstandFixNametags = false; + public boolean armorstandMovement = true; + public boolean armorstandWaterMovement = true; + public boolean armorstandWaterFence = true; + public boolean armorstandPlaceWithArms = false; + private void armorstandSettings() { + armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight); + armorstandSetNameVisible = getBoolean("gameplay-mechanics.armorstand.set-name-visible-when-placing-with-custom-name", armorstandSetNameVisible); + armorstandFixNametags = getBoolean("gameplay-mechanics.armorstand.fix-nametags", armorstandFixNametags); + armorstandMovement = getBoolean("gameplay-mechanics.armorstand.can-movement-tick", armorstandMovement); + armorstandWaterMovement = getBoolean("gameplay-mechanics.armorstand.can-move-in-water", armorstandWaterMovement); + armorstandWaterFence = getBoolean("gameplay-mechanics.armorstand.can-move-in-water-over-fence", armorstandWaterFence); + armorstandPlaceWithArms = getBoolean("gameplay-mechanics.armorstand.place-with-arms-visible", armorstandPlaceWithArms); + } + + public boolean arrowMovementResetsDespawnCounter = true; + private void arrowSettings() { + arrowMovementResetsDespawnCounter = getBoolean("gameplay-mechanics.arrow.movement-resets-despawn-counter", arrowMovementResetsDespawnCounter); + } + + public boolean useBetterMending = false; + public double mendingMultiplier = 1.0; + public boolean alwaysTameInCreative = false; + public boolean boatEjectPlayersOnLand = false; + public boolean boatsDoFallDamage = false; + public boolean disableDropsOnCrammingDeath = false; + public boolean entitiesCanUsePortals = true; + public boolean entitiesPickUpLootBypassMobGriefing = false; + public boolean fireballsBypassMobGriefing = false; + public boolean imposeTeleportRestrictionsOnGateways = false; + public boolean milkCuresBadOmen = true; + public boolean milkClearsBeneficialEffects = true; + public boolean noteBlockIgnoreAbove = false; + public boolean persistentDroppableEntityDisplayNames = true; + public boolean persistentTileEntityDisplayNames = false; + public boolean projectilesBypassMobGriefing = false; + public boolean tickFluids = true; + public double mobsBlindnessMultiplier = 1; + public double tridentLoyaltyVoidReturnHeight = 0.0D; + public double voidDamageHeight = -64.0D; + public double voidDamageDealt = 4.0D; + public int raidCooldownSeconds = 0; + public int animalBreedingCooldownSeconds = 0; + public boolean mobsIgnoreRails = false; + public boolean rainStopsAfterSleep = true; + public boolean thunderStopsAfterSleep = true; + public int mobLastHurtByPlayerTime = 100; + public boolean disableOxidationProximityPenalty = false; + 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); + disableOxidationProximityPenalty = getBoolean("gameplay-mechanics.disable-oxidation-proximity-penalty", disableOxidationProximityPenalty); + } + + public int daytimeTicks = 12000; + public int nighttimeTicks = 12000; + private void daytimeCycleSettings() { + daytimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.daytime", daytimeTicks); + nighttimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.nighttime", nighttimeTicks); + } + + public int drowningAirTicks = 300; + public int drowningDamageInterval = 20; + public double damageFromDrowning = 2.0F; + private void drowningSettings() { + drowningAirTicks = getInt("gameplay-mechanics.drowning.air-ticks", drowningAirTicks); + drowningDamageInterval = getInt("gameplay-mechanics.drowning.ticks-per-damage", drowningDamageInterval); + damageFromDrowning = getDouble("gameplay-mechanics.drowning.damage-from-drowning", damageFromDrowning); + } + + public int elytraDamagePerSecond = 1; + public double elytraDamageMultiplyBySpeed = 0; + public boolean elytraIgnoreUnbreaking = false; + public int elytraDamagePerFireworkBoost = 0; + public int elytraDamagePerTridentBoost = 0; + public boolean elytraKineticDamage = true; + private void elytraSettings() { + elytraDamagePerSecond = getInt("gameplay-mechanics.elytra.damage-per-second", elytraDamagePerSecond); + elytraDamageMultiplyBySpeed = getDouble("gameplay-mechanics.elytra.damage-multiplied-by-speed", elytraDamageMultiplyBySpeed); + elytraIgnoreUnbreaking = getBoolean("gameplay-mechanics.elytra.ignore-unbreaking", elytraIgnoreUnbreaking); + elytraDamagePerFireworkBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.firework", elytraDamagePerFireworkBoost); + elytraDamagePerTridentBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.trident", elytraDamagePerTridentBoost); + elytraKineticDamage = getBoolean("gameplay-mechanics.elytra.kinetic-damage", elytraKineticDamage); + } + + public int entityLifeSpan = 0; + public float entityLeftHandedChance = 0.05f; + public boolean entitySharedRandom = true; + private void entitySettings() { + entityLifeSpan = getInt("gameplay-mechanics.entity-lifespan", entityLifeSpan); + entityLeftHandedChance = (float) getDouble("gameplay-mechanics.entity-left-handed-chance", entityLeftHandedChance); + entitySharedRandom = getBoolean("settings.entity.shared-random", entitySharedRandom); + } + + public boolean explosionClampRadius = true; + private void explosionSettings() { + explosionClampRadius = getBoolean("gameplay-mechanics.clamp-explosion-radius", explosionClampRadius); + } + + public boolean infinityWorksWithoutArrows = false; + public boolean infinityWorksWithNormalArrows = true; + public boolean infinityWorksWithSpectralArrows = false; + public boolean infinityWorksWithTippedArrows = false; + private void infinityArrowsSettings() { + infinityWorksWithoutArrows = getBoolean("gameplay-mechanics.infinity-bow.works-without-arrows", infinityWorksWithoutArrows); + infinityWorksWithNormalArrows = getBoolean("gameplay-mechanics.infinity-bow.normal-arrows", infinityWorksWithNormalArrows); + infinityWorksWithSpectralArrows = getBoolean("gameplay-mechanics.infinity-bow.spectral-arrows", infinityWorksWithSpectralArrows); + infinityWorksWithTippedArrows = getBoolean("gameplay-mechanics.infinity-bow.tipped-arrows", infinityWorksWithTippedArrows); + } + + public List itemImmuneToCactus = new ArrayList<>(); + public List itemImmuneToExplosion = new ArrayList<>(); + public List itemImmuneToFire = new ArrayList<>(); + public List itemImmuneToLightning = new ArrayList<>(); + public boolean dontRunWithScissors = false; + public boolean ignoreScissorsInWater = false; + public boolean ignoreScissorsInLava = false; + public double scissorsRunningDamage = 1D; + public float enderPearlDamage = 5.0F; + public int enderPearlCooldown = 20; + public int enderPearlCooldownCreative = 20; + public float enderPearlEndermiteChance = 0.05F; + public int glowBerriesEatGlowDuration = 0; + public boolean shulkerBoxItemDropContentsWhenDestroyed = true; + public boolean compassItemShowsBossBar = false; + public boolean snowballExtinguishesFire = false; + public boolean snowballExtinguishesCandles = false; + public boolean snowballExtinguishesCampfires = false; + private void itemSettings() { + itemImmuneToCactus.clear(); + getList("gameplay-mechanics.item.immune.cactus", new ArrayList<>()).forEach(key -> { + if (key.toString().equals("*")) { + BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToCactus.add(item)); + return; + } + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); + if (item != Items.AIR) itemImmuneToCactus.add(item); + }); + itemImmuneToExplosion.clear(); + getList("gameplay-mechanics.item.immune.explosion", new ArrayList<>()).forEach(key -> { + if (key.toString().equals("*")) { + BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToExplosion.add(item)); + return; + } + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); + if (item != Items.AIR) itemImmuneToExplosion.add(item); + }); + itemImmuneToFire.clear(); + getList("gameplay-mechanics.item.immune.fire", new ArrayList<>()).forEach(key -> { + if (key.toString().equals("*")) { + BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToFire.add(item)); + return; + } + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); + if (item != Items.AIR) itemImmuneToFire.add(item); + }); + itemImmuneToLightning.clear(); + getList("gameplay-mechanics.item.immune.lightning", new ArrayList<>()).forEach(key -> { + if (key.toString().equals("*")) { + BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToLightning.add(item)); + return; + } + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); + if (item != Items.AIR) itemImmuneToLightning.add(item); + }); + dontRunWithScissors = getBoolean("gameplay-mechanics.item.shears.damage-if-sprinting", dontRunWithScissors); + ignoreScissorsInWater = getBoolean("gameplay-mechanics.item.shears.ignore-in-water", ignoreScissorsInWater); + ignoreScissorsInLava = getBoolean("gameplay-mechanics.item.shears.ignore-in-lava", ignoreScissorsInLava); + scissorsRunningDamage = getDouble("gameplay-mechanics.item.shears.sprinting-damage", scissorsRunningDamage); + enderPearlDamage = (float) getDouble("gameplay-mechanics.item.ender-pearl.damage", enderPearlDamage); + enderPearlCooldown = getInt("gameplay-mechanics.item.ender-pearl.cooldown", enderPearlCooldown); + enderPearlCooldownCreative = getInt("gameplay-mechanics.item.ender-pearl.creative-cooldown", enderPearlCooldownCreative); + enderPearlEndermiteChance = (float) getDouble("gameplay-mechanics.item.ender-pearl.endermite-spawn-chance", enderPearlEndermiteChance); + glowBerriesEatGlowDuration = getInt("gameplay-mechanics.item.glow_berries.eat-glow-duration", glowBerriesEatGlowDuration); + shulkerBoxItemDropContentsWhenDestroyed = getBoolean("gameplay-mechanics.item.shulker_box.drop-contents-when-destroyed", shulkerBoxItemDropContentsWhenDestroyed); + compassItemShowsBossBar = getBoolean("gameplay-mechanics.item.compass.holding-shows-bossbar", compassItemShowsBossBar); + snowballExtinguishesFire = getBoolean("gameplay-mechanics.item.snowball.extinguish.fire", snowballExtinguishesFire); + snowballExtinguishesCandles = getBoolean("gameplay-mechanics.item.snowball.extinguish.candles", snowballExtinguishesCandles); + snowballExtinguishesCampfires = getBoolean("gameplay-mechanics.item.snowball.extinguish.campfires", snowballExtinguishesCampfires); + } + + public double minecartMaxSpeed = 0.4D; + public boolean minecartPlaceAnywhere = false; + public boolean minecartControllable = false; + public float minecartControllableStepHeight = 1.0F; + public double minecartControllableHopBoost = 0.5D; + public boolean minecartControllableFallDamage = true; + public double minecartControllableBaseSpeed = 0.1D; + public Map minecartControllableBlockSpeeds = new HashMap<>(); + public double poweredRailBoostModifier = 0.06; + private void minecartSettings() { + if (PurpurConfig.version < 12) { + boolean oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.place-anywhere", minecartPlaceAnywhere); + set("gameplay-mechanics.controllable-minecarts.place-anywhere", null); + set("gameplay-mechanics.minecart.place-anywhere", oldBool); + oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.enabled", minecartControllable); + set("gameplay-mechanics.controllable-minecarts.enabled", null); + set("gameplay-mechanics.minecart.controllable.enabled", oldBool); + double oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.step-height", minecartControllableStepHeight); + set("gameplay-mechanics.controllable-minecarts.step-height", null); + set("gameplay-mechanics.minecart.controllable.step-height", oldDouble); + oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.hop-boost", minecartControllableHopBoost); + set("gameplay-mechanics.controllable-minecarts.hop-boost", null); + set("gameplay-mechanics.minecart.controllable.hop-boost", oldDouble); + oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.fall-damage", minecartControllableFallDamage); + set("gameplay-mechanics.controllable-minecarts.fall-damage", null); + set("gameplay-mechanics.minecart.controllable.fall-damage", oldBool); + oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.base-speed", minecartControllableBaseSpeed); + set("gameplay-mechanics.controllable-minecarts.base-speed", null); + set("gameplay-mechanics.minecart.controllable.base-speed", oldDouble); + ConfigurationSection section = getConfigurationSection("gameplay-mechanics.controllable-minecarts.block-speed"); + if (section != null) { + for (String key : section.getKeys(false)) { + if ("grass-block".equals(key)) key = "grass_block"; // oopsie + oldDouble = section.getDouble(key, minecartControllableBaseSpeed); + set("gameplay-mechanics.controllable-minecarts.block-speed." + key, null); + set("gameplay-mechanics.minecart.controllable.block-speed." + key, oldDouble); + } + set("gameplay-mechanics.controllable-minecarts.block-speed", null); + } + set("gameplay-mechanics.controllable-minecarts", null); + } + + minecartMaxSpeed = getDouble("gameplay-mechanics.minecart.max-speed", minecartMaxSpeed); + minecartPlaceAnywhere = getBoolean("gameplay-mechanics.minecart.place-anywhere", minecartPlaceAnywhere); + minecartControllable = getBoolean("gameplay-mechanics.minecart.controllable.enabled", minecartControllable); + minecartControllableStepHeight = (float) getDouble("gameplay-mechanics.minecart.controllable.step-height", minecartControllableStepHeight); + minecartControllableHopBoost = getDouble("gameplay-mechanics.minecart.controllable.hop-boost", minecartControllableHopBoost); + minecartControllableFallDamage = getBoolean("gameplay-mechanics.minecart.controllable.fall-damage", minecartControllableFallDamage); + minecartControllableBaseSpeed = getDouble("gameplay-mechanics.minecart.controllable.base-speed", minecartControllableBaseSpeed); + ConfigurationSection section = getConfigurationSection("gameplay-mechanics.minecart.controllable.block-speed"); + if (section != null) { + for (String key : section.getKeys(false)) { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key)); + if (block != Blocks.AIR) { + minecartControllableBlockSpeeds.put(block, section.getDouble(key, minecartControllableBaseSpeed)); + } + } + } else { + set("gameplay-mechanics.minecart.controllable.block-speed.grass_block", 0.3D); + set("gameplay-mechanics.minecart.controllable.block-speed.stone", 0.5D); + } + poweredRailBoostModifier = getDouble("gameplay-mechanics.minecart.powered-rail.boost-modifier", poweredRailBoostModifier); + } + + public float entityHealthRegenAmount = 1.0F; + public float entityMinimalHealthPoison = 1.0F; + public float entityPoisonDegenerationAmount = 1.0F; + public float entityWitherDegenerationAmount = 1.0F; + public float humanHungerExhaustionAmount = 0.005F; + public float humanSaturationRegenAmount = 1.0F; + private void mobEffectSettings() { + entityHealthRegenAmount = (float) getDouble("gameplay-mechanics.mob-effects.health-regen-amount", entityHealthRegenAmount); + entityMinimalHealthPoison = (float) getDouble("gameplay-mechanics.mob-effects.minimal-health-poison-amount", entityMinimalHealthPoison); + entityPoisonDegenerationAmount = (float) getDouble("gameplay-mechanics.mob-effects.poison-degeneration-amount", entityPoisonDegenerationAmount); + entityWitherDegenerationAmount = (float) getDouble("gameplay-mechanics.mob-effects.wither-degeneration-amount", entityWitherDegenerationAmount); + humanHungerExhaustionAmount = (float) getDouble("gameplay-mechanics.mob-effects.hunger-exhaustion-amount", humanHungerExhaustionAmount); + humanSaturationRegenAmount = (float) getDouble("gameplay-mechanics.mob-effects.saturation-regen-amount", humanSaturationRegenAmount); + } + + public boolean catSpawning; + public boolean patrolSpawning; + public boolean phantomSpawning; + public boolean villagerTraderSpawning; + public boolean villageSiegeSpawning; + public boolean mobSpawningIgnoreCreativePlayers = false; + private void mobSpawnerSettings() { + // values of "default" or null will default to true only if the world environment is normal (aka overworld) + Predicate predicate = (bool) -> (bool != null && bool) || (bool == null && environment == World.Environment.NORMAL); + catSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-cats", predicate); + patrolSpawning = getBoolean("gameplay-mechanics.mob-spawning.raid-patrols", predicate); + phantomSpawning = getBoolean("gameplay-mechanics.mob-spawning.phantoms", predicate); + villagerTraderSpawning = getBoolean("gameplay-mechanics.mob-spawning.wandering-traders", predicate); + villageSiegeSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-sieges", predicate); + mobSpawningIgnoreCreativePlayers = getBoolean("gameplay-mechanics.mob-spawning.ignore-creative-players", mobSpawningIgnoreCreativePlayers); + } + + public boolean disableObserverClocks = false; + private void observerSettings() { + disableObserverClocks = getBoolean("blocks.observer.disable-clock", disableObserverClocks); + } + + public int playerNetheriteFireResistanceDuration = 0; + public int playerNetheriteFireResistanceAmplifier = 0; + public boolean playerNetheriteFireResistanceAmbient = false; + public boolean playerNetheriteFireResistanceShowParticles = false; + public boolean playerNetheriteFireResistanceShowIcon = true; + private void playerNetheriteFireResistance() { + playerNetheriteFireResistanceDuration = getInt("gameplay-mechanics.player.netherite-fire-resistance.duration", playerNetheriteFireResistanceDuration); + playerNetheriteFireResistanceAmplifier = getInt("gameplay-mechanics.player.netherite-fire-resistance.amplifier", playerNetheriteFireResistanceAmplifier); + playerNetheriteFireResistanceAmbient = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.ambient", playerNetheriteFireResistanceAmbient); + playerNetheriteFireResistanceShowParticles = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-particles", playerNetheriteFireResistanceShowParticles); + playerNetheriteFireResistanceShowIcon = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-icon", playerNetheriteFireResistanceShowIcon); + } + + public boolean idleTimeoutKick = true; + public boolean idleTimeoutTickNearbyEntities = true; + public boolean idleTimeoutCountAsSleeping = false; + public boolean idleTimeoutUpdateTabList = false; + public boolean idleTimeoutTargetPlayer = true; + public String playerDeathExpDropEquation = "expLevel * 7"; + public int playerDeathExpDropMax = 100; + public boolean teleportIfOutsideBorder = false; + public boolean teleportOnNetherCeilingDamage = false; + public boolean totemOfUndyingWorksInInventory = false; + public boolean playerFixStuckPortal = false; + public boolean creativeOnePunch = false; + public boolean playerSleepNearMonsters = false; + public boolean playersSkipNight = true; + public double playerCriticalDamageMultiplier = 1.5D; + public int playerBurpDelay = 10; + public boolean playerBurpWhenFull = false; + public boolean playerRidableInWater = false; + public boolean playerRemoveBindingWithWeakness = false; + public int shiftRightClickRepairsMendingPoints = 0; + public int playerExpPickupDelay = 2; + public boolean playerVoidTrading = false; + private void playerSettings() { + if (PurpurConfig.version < 19) { + boolean oldVal = getBoolean("gameplay-mechanics.player.idle-timeout.mods-target", idleTimeoutTargetPlayer); + set("gameplay-mechanics.player.idle-timeout.mods-target", null); + set("gameplay-mechanics.player.idle-timeout.mobs-target", oldVal); + } + idleTimeoutKick = System.getenv("PURPUR_FORCE_IDLE_KICK") == null ? getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick) : Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")); + idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); + idleTimeoutCountAsSleeping = getBoolean("gameplay-mechanics.player.idle-timeout.count-as-sleeping", idleTimeoutCountAsSleeping); + idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList); + idleTimeoutTargetPlayer = getBoolean("gameplay-mechanics.player.idle-timeout.mobs-target", idleTimeoutTargetPlayer); + playerDeathExpDropEquation = getString("gameplay-mechanics.player.exp-dropped-on-death.equation", playerDeathExpDropEquation); + playerDeathExpDropMax = getInt("gameplay-mechanics.player.exp-dropped-on-death.maximum", playerDeathExpDropMax); + teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder); + teleportOnNetherCeilingDamage = getBoolean("gameplay-mechanics.player.teleport-on-nether-ceiling-damage", teleportOnNetherCeilingDamage); + totemOfUndyingWorksInInventory = getBoolean("gameplay-mechanics.player.totem-of-undying-works-in-inventory", totemOfUndyingWorksInInventory); + playerFixStuckPortal = getBoolean("gameplay-mechanics.player.fix-stuck-in-portal", playerFixStuckPortal); + creativeOnePunch = getBoolean("gameplay-mechanics.player.one-punch-in-creative", creativeOnePunch); + playerSleepNearMonsters = getBoolean("gameplay-mechanics.player.sleep-ignore-nearby-mobs", playerSleepNearMonsters); + playersSkipNight = getBoolean("gameplay-mechanics.player.can-skip-night", playersSkipNight); + playerCriticalDamageMultiplier = getDouble("gameplay-mechanics.player.critical-damage-multiplier", playerCriticalDamageMultiplier); + playerBurpDelay = getInt("gameplay-mechanics.player.burp-delay", playerBurpDelay); + playerBurpWhenFull = getBoolean("gameplay-mechanics.player.burp-when-full", playerBurpWhenFull); + playerRidableInWater = getBoolean("gameplay-mechanics.player.ridable-in-water", playerRidableInWater); + playerRemoveBindingWithWeakness = getBoolean("gameplay-mechanics.player.curse-of-binding.remove-with-weakness", playerRemoveBindingWithWeakness); + shiftRightClickRepairsMendingPoints = getInt("gameplay-mechanics.player.shift-right-click-repairs-mending-points", shiftRightClickRepairsMendingPoints); + playerExpPickupDelay = getInt("gameplay-mechanics.player.exp-pickup-delay-ticks", playerExpPickupDelay); + playerVoidTrading = getBoolean("gameplay-mechanics.player.allow-void-trading", playerVoidTrading); + } + + private static boolean projectileDespawnRateSettingsMigrated = false; + private void projectileDespawnRateSettings() { + if (PurpurConfig.version < 28 && !projectileDespawnRateSettingsMigrated) { + migrateProjectileDespawnRateSettings(EntityType.DRAGON_FIREBALL); + migrateProjectileDespawnRateSettings(EntityType.EGG); + migrateProjectileDespawnRateSettings(EntityType.ENDER_PEARL); + migrateProjectileDespawnRateSettings(EntityType.EXPERIENCE_BOTTLE); + migrateProjectileDespawnRateSettings(EntityType.FIREWORK_ROCKET); + migrateProjectileDespawnRateSettings(EntityType.FISHING_BOBBER); + migrateProjectileDespawnRateSettings(EntityType.FIREBALL); + migrateProjectileDespawnRateSettings(EntityType.LLAMA_SPIT); + migrateProjectileDespawnRateSettings(EntityType.POTION); + migrateProjectileDespawnRateSettings(EntityType.SHULKER_BULLET); + migrateProjectileDespawnRateSettings(EntityType.SMALL_FIREBALL); + migrateProjectileDespawnRateSettings(EntityType.SNOWBALL); + migrateProjectileDespawnRateSettings(EntityType.WITHER_SKULL); + //PufferfishConfig.save(); + set("gameplay-mechanics.projectile-despawn-rates", null); + // pufferfish's entity_timeout is a global config + // we only want to migrate values from the + // default world (first world loaded) + projectileDespawnRateSettingsMigrated = true; + } + } + private void migrateProjectileDespawnRateSettings(EntityType type) { + //String pufferName = "entity_timeouts." + type.id.toUpperCase(Locale.ROOT); + //int value = getInt("gameplay-mechanics.projectile-despawn-rates." + type.id, -1); + //if (value != -1 && PufferfishConfig.getRawInt(pufferName, -1) == -1) { + // PufferfishConfig.setInt(pufferName, value); + // type.ttl = value; + //} + } + + public double bowProjectileOffset = 1.0D; + public double crossbowProjectileOffset = 1.0D; + public double eggProjectileOffset = 1.0D; + public double enderPearlProjectileOffset = 1.0D; + public double throwablePotionProjectileOffset = 1.0D; + public double tridentProjectileOffset = 1.0D; + public double snowballProjectileOffset = 1.0D; + private void projectileOffsetSettings() { + bowProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.bow", bowProjectileOffset); + crossbowProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.crossbow", crossbowProjectileOffset); + eggProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.egg", eggProjectileOffset); + enderPearlProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.ender-pearl", enderPearlProjectileOffset); + throwablePotionProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.throwable-potion", throwablePotionProjectileOffset); + tridentProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.trident", tridentProjectileOffset); + snowballProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.snowball", snowballProjectileOffset); + } + + public int snowballDamage = -1; + private void snowballSettings() { + snowballDamage = getInt("gameplay-mechanics.projectile-damage.snowball", snowballDamage); + } + + public boolean silkTouchEnabled = false; + public String silkTouchSpawnerName = "Monster Spawner"; + public List silkTouchSpawnerLore = new ArrayList<>(); + public List silkTouchTools = new ArrayList<>(); + public int minimumSilkTouchSpawnerRequire = 1; + private void silkTouchSettings() { + if (PurpurConfig.version < 21) { + String oldName = getString("gameplay-mechanics.silk-touch.spawner-name", silkTouchSpawnerName); + set("gameplay-mechanics.silk-touch.spawner-name", "" + ChatColor.toMM(oldName.replace("{mob}", ""))); + List list = new ArrayList<>(); + getList("gameplay-mechanics.silk-touch.spawner-lore", List.of("Spawns a ")) + .forEach(line -> list.add("" + ChatColor.toMM(line.toString().replace("{mob}", "")))); + set("gameplay-mechanics.silk-touch.spawner-lore", list); + } + silkTouchEnabled = getBoolean("gameplay-mechanics.silk-touch.enabled", silkTouchEnabled); + silkTouchSpawnerName = getString("gameplay-mechanics.silk-touch.spawner-name", silkTouchSpawnerName); + minimumSilkTouchSpawnerRequire = getInt("gameplay-mechanics.silk-touch.minimal-level", minimumSilkTouchSpawnerRequire); + silkTouchSpawnerLore.clear(); + getList("gameplay-mechanics.silk-touch.spawner-lore", List.of("Spawns a ")) + .forEach(line -> silkTouchSpawnerLore.add(line.toString())); + silkTouchTools.clear(); + getList("gameplay-mechanics.silk-touch.tools", List.of( + "minecraft:iron_pickaxe", + "minecraft:golden_pickaxe", + "minecraft:diamond_pickaxe", + "minecraft:netherite_pickaxe" + )).forEach(key -> { + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); + if (item != Items.AIR) silkTouchTools.add(item); + }); + } + + public Map axeStrippables = new HashMap<>(); + public Map axeWaxables = new HashMap<>(); + public Map axeWeatherables = new HashMap<>(); + public Map hoeTillables = new HashMap<>(); + public Map shovelFlattenables = new HashMap<>(); + public boolean hoeReplantsCrops = false; + public boolean hoeReplantsNetherWarts = false; + private void toolSettings() { + axeStrippables.clear(); + axeWaxables.clear(); + axeWeatherables.clear(); + hoeTillables.clear(); + shovelFlattenables.clear(); + if (PurpurConfig.version < 18) { + ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + ".tools.hoe.tilling"); + if (section != null) { + PurpurConfig.config.set("world-settings." + worldName + ".tools.hoe.tillables", section); + PurpurConfig.config.set("world-settings." + worldName + ".tools.hoe.tilling", null); + } + section = PurpurConfig.config.getConfigurationSection("world-settings.default.tools.hoe.tilling"); + if (section != null) { + PurpurConfig.config.set("world-settings.default.tools.hoe.tillables", section); + PurpurConfig.config.set("world-settings.default.tools.hoe.tilling", null); + } + } + if (PurpurConfig.version < 29) { + PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:mangrove_log", Map.of("into", "minecraft:stripped_mangrove_log", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:mangrove_wood", Map.of("into", "minecraft:stripped_mangrove_wood", "drops", new HashMap())); + } + if (PurpurConfig.version < 32) { + PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:cherry_log", Map.of("into", "minecraft:stripped_cherry_log", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap())); + } + if (PurpurConfig.version < 33) { + getList("gameplay-mechanics.shovel-turns-block-to-grass-path", new ArrayList(){{ + add("minecraft:coarse_dirt"); + add("minecraft:dirt"); + add("minecraft:grass_block"); + add("minecraft:mycelium"); + add("minecraft:podzol"); + add("minecraft:rooted_dirt"); + }}).forEach(key -> { + PurpurConfig.config.set("world-settings.default.tools.shovel.flattenables." + key.toString(), Map.of("into", "minecraft:dirt_path", "drops", new HashMap())); + }); + set("gameplay-mechanics.shovel-turns-block-to-grass-path", null); + } + if (PurpurConfig.version < 34) { + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_chiseled_copper", Map.of("into", "minecraft:oxidized_chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_door", Map.of("into", "minecraft:oxidized_copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_trapdoor", Map.of("into", "minecraft:oxidized_copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_grate", Map.of("into", "minecraft:oxidized_copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_exposed_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_weathered_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.waxables.minecraft:waxed_oxidized_copper_bulb", Map.of("into", "minecraft:oxidized_copper_bulb", "drops", new HashMap())); + + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:exposed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:weathered_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())); + PurpurConfig.config.set("world-settings.default.tools.axe.weatherables.minecraft:oxidized_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap())); + } + getMap("tools.axe.strippables", Map.ofEntries( + Map.entry("minecraft:oak_wood", Map.of("into", "minecraft:stripped_oak_wood", "drops", new HashMap())), + Map.entry("minecraft:oak_log", Map.of("into", "minecraft:stripped_oak_log", "drops", new HashMap())), + Map.entry("minecraft:dark_oak_wood", Map.of("into", "minecraft:stripped_dark_oak_wood", "drops", new HashMap())), + Map.entry("minecraft:dark_oak_log", Map.of("into", "minecraft:stripped_dark_oak_log", "drops", new HashMap())), + Map.entry("minecraft:acacia_wood", Map.of("into", "minecraft:stripped_acacia_wood", "drops", new HashMap())), + Map.entry("minecraft:acacia_log", Map.of("into", "minecraft:stripped_acacia_log", "drops", new HashMap())), + Map.entry("minecraft:birch_wood", Map.of("into", "minecraft:stripped_birch_wood", "drops", new HashMap())), + Map.entry("minecraft:birch_log", Map.of("into", "minecraft:stripped_birch_log", "drops", new HashMap())), + Map.entry("minecraft:jungle_wood", Map.of("into", "minecraft:stripped_jungle_wood", "drops", new HashMap())), + Map.entry("minecraft:jungle_log", Map.of("into", "minecraft:stripped_jungle_log", "drops", new HashMap())), + Map.entry("minecraft:spruce_wood", Map.of("into", "minecraft:stripped_spruce_wood", "drops", new HashMap())), + Map.entry("minecraft:spruce_log", Map.of("into", "minecraft:stripped_spruce_log", "drops", new HashMap())), + Map.entry("minecraft:mangrove_wood", Map.of("into", "minecraft:stripped_mangrove_wood", "drops", new HashMap())), + Map.entry("minecraft:mangrove_log", Map.of("into", "minecraft:stripped_mangrove_log", "drops", new HashMap())), + Map.entry("minecraft:cherry_log", Map.of("into", "minecraft:stripped_cherry_log", "drops", new HashMap())), + Map.entry("minecraft:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap())), + Map.entry("minecraft:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap())), + Map.entry("minecraft:warped_stem", Map.of("into", "minecraft:stripped_warped_stem", "drops", new HashMap())), + Map.entry("minecraft:warped_hyphae", Map.of("into", "minecraft:stripped_warped_hyphae", "drops", new HashMap())), + Map.entry("minecraft:crimson_stem", Map.of("into", "minecraft:stripped_crimson_stem", "drops", new HashMap())), + Map.entry("minecraft:crimson_hyphae", Map.of("into", "minecraft:stripped_crimson_hyphae", "drops", new HashMap()))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.strippables." + blockId + "`"); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.strippables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.strippables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + axeStrippables.put(block, new Strippable(into, drops)); + }); + getMap("tools.axe.waxables", Map.ofEntries( + Map.entry("minecraft:waxed_copper_block", Map.of("into", "minecraft:copper_block", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_copper", Map.of("into", "minecraft:exposed_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_copper", Map.of("into", "minecraft:weathered_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_copper", Map.of("into", "minecraft:oxidized_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_cut_copper", Map.of("into", "minecraft:cut_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_cut_copper", Map.of("into", "minecraft:exposed_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_cut_copper", Map.of("into", "minecraft:weathered_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_cut_copper", Map.of("into", "minecraft:oxidized_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_cut_copper_slab", Map.of("into", "minecraft:cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_cut_copper_slab", Map.of("into", "minecraft:exposed_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_cut_copper_slab", Map.of("into", "minecraft:weathered_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_cut_copper_slab", Map.of("into", "minecraft:oxidized_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:waxed_cut_copper_stairs", Map.of("into", "minecraft:cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_cut_copper_stairs", Map.of("into", "minecraft:exposed_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_cut_copper_stairs", Map.of("into", "minecraft:oxidized_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:waxed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_chiseled_copper", Map.of("into", "minecraft:oxidized_chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:waxed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_copper_door", Map.of("into", "minecraft:oxidized_copper_door", "drops", new HashMap())), + Map.entry("minecraft:waxed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_copper_trapdoor", Map.of("into", "minecraft:oxidized_copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:waxed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_copper_grate", Map.of("into", "minecraft:oxidized_copper_grate", "drops", new HashMap())), + Map.entry("minecraft:waxed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap())), + Map.entry("minecraft:waxed_exposed_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())), + Map.entry("minecraft:waxed_weathered_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap())), + Map.entry("minecraft:waxed_oxidized_copper_bulb", Map.of("into", "minecraft:oxidized_copper_bulb", "drops", new HashMap()))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.waxables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.waxables." + blockId + "`"); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.waxables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.waxables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.waxables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + axeWaxables.put(block, new Waxable(into, drops)); + }); + getMap("tools.axe.weatherables", Map.ofEntries( + Map.entry("minecraft:exposed_copper", Map.of("into", "minecraft:copper_block", "drops", new HashMap())), + Map.entry("minecraft:weathered_copper", Map.of("into", "minecraft:exposed_copper", "drops", new HashMap())), + Map.entry("minecraft:oxidized_copper", Map.of("into", "minecraft:weathered_copper", "drops", new HashMap())), + Map.entry("minecraft:exposed_cut_copper", Map.of("into", "minecraft:cut_copper", "drops", new HashMap())), + Map.entry("minecraft:weathered_cut_copper", Map.of("into", "minecraft:exposed_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:oxidized_cut_copper", Map.of("into", "minecraft:weathered_cut_copper", "drops", new HashMap())), + Map.entry("minecraft:exposed_chiseled_copper", Map.of("into", "minecraft:chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:weathered_chiseled_copper", Map.of("into", "minecraft:exposed_chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:oxidized_chiseled_copper", Map.of("into", "minecraft:weathered_chiseled_copper", "drops", new HashMap())), + Map.entry("minecraft:exposed_cut_copper_slab", Map.of("into", "minecraft:cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:weathered_cut_copper_slab", Map.of("into", "minecraft:exposed_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:oxidized_cut_copper_slab", Map.of("into", "minecraft:weathered_cut_copper_slab", "drops", new HashMap())), + Map.entry("minecraft:exposed_cut_copper_stairs", Map.of("into", "minecraft:cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:weathered_cut_copper_stairs", Map.of("into", "minecraft:exposed_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:oxidized_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap())), + Map.entry("minecraft:exposed_copper_door", Map.of("into", "minecraft:copper_door", "drops", new HashMap())), + Map.entry("minecraft:weathered_copper_door", Map.of("into", "minecraft:exposed_copper_door", "drops", new HashMap())), + Map.entry("minecraft:oxidized_copper_door", Map.of("into", "minecraft:weathered_copper_door", "drops", new HashMap())), + Map.entry("minecraft:exposed_copper_trapdoor", Map.of("into", "minecraft:copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:weathered_copper_trapdoor", Map.of("into", "minecraft:exposed_copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:oxidized_copper_trapdoor", Map.of("into", "minecraft:weathered_copper_trapdoor", "drops", new HashMap())), + Map.entry("minecraft:exposed_copper_grate", Map.of("into", "minecraft:copper_grate", "drops", new HashMap())), + Map.entry("minecraft:weathered_copper_grate", Map.of("into", "minecraft:exposed_copper_grate", "drops", new HashMap())), + Map.entry("minecraft:oxidized_copper_grate", Map.of("into", "minecraft:weathered_copper_grate", "drops", new HashMap())), + Map.entry("minecraft:exposed_copper_bulb", Map.of("into", "minecraft:copper_bulb", "drops", new HashMap())), + Map.entry("minecraft:weathered_copper_bulb", Map.of("into", "minecraft:exposed_copper_bulb", "drops", new HashMap())), + Map.entry("minecraft:oxidized_copper_bulb", Map.of("into", "minecraft:weathered_copper_bulb", "drops", new HashMap()))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.weatherables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.weatherables." + blockId + "`"); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.weatherables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.weatherables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.weatherables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + axeWeatherables.put(block, new Weatherable(into, drops)); + }); + getMap("tools.hoe.tillables", Map.ofEntries( + Map.entry("minecraft:grass_block", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap())), + Map.entry("minecraft:dirt_path", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap())), + Map.entry("minecraft:dirt", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap())), + Map.entry("minecraft:coarse_dirt", Map.of("condition", "air_above", "into", "minecraft:dirt", "drops", new HashMap())), + Map.entry("minecraft:rooted_dirt", Map.of("condition", "always", "into", "minecraft:dirt", "drops", Map.of("minecraft:hanging_roots", 1.0D)))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.hoe.tillables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.hoe.tillables." + blockId + "`"); return; } + String conditionId = (String) map.get("condition"); + Tillable.Condition condition = Tillable.Condition.get(conditionId); + if (condition == null) { PurpurConfig.log(Level.SEVERE, "Invalid condition for `tools.hoe.tillables." + blockId + ".condition`: " + conditionId); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.hoe.tillables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.hoe.tillables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.hoe.tillables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + hoeTillables.put(block, new Tillable(condition, into, drops)); + }); + getMap("tools.shovel.flattenables", Map.ofEntries( + Map.entry("minecraft:grass_block", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), + Map.entry("minecraft:dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), + Map.entry("minecraft:podzol", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), + Map.entry("minecraft:coarse_dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), + Map.entry("minecraft:mycelium", Map.of("into", "minecraft:dirt_path", "drops", new HashMap())), + Map.entry("minecraft:rooted_dirt", Map.of("into", "minecraft:dirt_path", "drops", new HashMap()))) + ).forEach((blockId, obj) -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); + if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.shovel.flattenables`: " + blockId); return; } + if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.shovel.flattenables." + blockId + "`"); return; } + String intoId = (String) map.get("into"); + Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); + if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.shovel.flattenables." + blockId + ".into`: " + intoId); return; } + Object dropsObj = map.get("drops"); + if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.shovel.flattenables." + blockId + ".drops`"); return; } + Map drops = new HashMap<>(); + dropsMap.forEach((itemId, chance) -> { + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); + if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.shovel.flattenables." + blockId + ".drops`: " + itemId); return; } + drops.put(item, (double) chance); + }); + shovelFlattenables.put(block, new Flattenable(into, drops)); + }); + hoeReplantsCrops = getBoolean("tools.hoe.replant-crops", hoeReplantsCrops); + hoeReplantsNetherWarts = getBoolean("tools.hoe.replant-nether-warts", hoeReplantsNetherWarts); + } + + public boolean anvilAllowColors = false; + public boolean anvilColorsUseMiniMessage; + public int anvilRepairIngotsAmount = 0; + public int anvilDamageObsidianAmount = 0; + private void anvilSettings() { + anvilAllowColors = getBoolean("blocks.anvil.allow-colors", anvilAllowColors); + anvilColorsUseMiniMessage = getBoolean("blocks.anvil.use-mini-message", anvilColorsUseMiniMessage); + anvilRepairIngotsAmount = getInt("blocks.anvil.iron-ingots-used-for-repair", anvilRepairIngotsAmount); + anvilDamageObsidianAmount = getInt("blocks.anvil.obsidian-used-for-damage", anvilDamageObsidianAmount); + } + + public double azaleaGrowthChance = 0.0D; + private void azaleaSettings() { + azaleaGrowthChance = getDouble("blocks.azalea.growth-chance", azaleaGrowthChance); + } + + public int beaconLevelOne = 20; + public int beaconLevelTwo = 30; + public int beaconLevelThree = 40; + public int beaconLevelFour = 50; + public boolean beaconAllowEffectsWithTintedGlass = false; + private void beaconSettings() { + beaconLevelOne = getInt("blocks.beacon.effect-range.level-1", beaconLevelOne); + beaconLevelTwo = getInt("blocks.beacon.effect-range.level-2", beaconLevelTwo); + beaconLevelThree = getInt("blocks.beacon.effect-range.level-3", beaconLevelThree); + beaconLevelFour = getInt("blocks.beacon.effect-range.level-4", beaconLevelFour); + beaconAllowEffectsWithTintedGlass = getBoolean("blocks.beacon.allow-effects-with-tinted-glass", beaconAllowEffectsWithTintedGlass); + } + + public boolean bedExplode = true; + public boolean bedExplodeOnVillagerSleep = false; + public double bedExplosionPower = 5.0D; + public boolean bedExplosionFire = true; + public net.minecraft.world.level.Level.ExplosionInteraction bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + private void bedSettings() { + if (PurpurConfig.version < 31) { + if ("DESTROY".equals(getString("blocks.bed.explosion-effect", bedExplosionEffect.name()))) { + set("blocks.bed.explosion-effect", "BLOCK"); + } + } + bedExplode = getBoolean("blocks.bed.explode", bedExplode); + bedExplodeOnVillagerSleep = getBoolean("blocks.bed.explode-on-villager-sleep", bedExplodeOnVillagerSleep); + bedExplosionPower = getDouble("blocks.bed.explosion-power", bedExplosionPower); + bedExplosionFire = getBoolean("blocks.bed.explosion-fire", bedExplosionFire); + try { + bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.bed.explosion-effect", bedExplosionEffect.name())); + } catch (IllegalArgumentException e) { + log(Level.SEVERE, "Unknown value for `blocks.bed.explosion-effect`! Using default of `BLOCK`"); + bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + } + } + + public Map bigDripleafTiltDelay = new HashMap<>(); + private void bigDripleafSettings() { + bigDripleafTiltDelay.clear(); + getMap("blocks.big_dripleaf.tilt-delay", Map.ofEntries( + Map.entry("UNSTABLE", 10), + Map.entry("PARTIAL", 10), + Map.entry("FULL", 100)) + ).forEach((tilt, delay) -> { + try { + bigDripleafTiltDelay.put(Tilt.valueOf(tilt), (int) delay); + } catch (IllegalArgumentException e) { + PurpurConfig.log(Level.SEVERE, "Invalid big_dripleaf tilt key: " + tilt); + } + }); + } + + public boolean cactusBreaksFromSolidNeighbors = true; + public boolean cactusAffectedByBonemeal = false; + private void cactusSettings() { + cactusBreaksFromSolidNeighbors = getBoolean("blocks.cactus.breaks-from-solid-neighbors", cactusBreaksFromSolidNeighbors); + cactusAffectedByBonemeal = getBoolean("blocks.cactus.affected-by-bonemeal", cactusAffectedByBonemeal); + } + + public boolean sugarCanAffectedByBonemeal = false; + private void sugarCaneSettings() { + sugarCanAffectedByBonemeal = getBoolean("blocks.sugar_cane.affected-by-bonemeal", sugarCanAffectedByBonemeal); + } + + public boolean netherWartAffectedByBonemeal = false; + private void netherWartSettings() { + netherWartAffectedByBonemeal = getBoolean("blocks.nether_wart.affected-by-bonemeal", netherWartAffectedByBonemeal); + } + + public boolean campFireLitWhenPlaced = true; + private void campFireSettings() { + campFireLitWhenPlaced = getBoolean("blocks.campfire.lit-when-placed", campFireLitWhenPlaced); + } + + public boolean chestOpenWithBlockOnTop = false; + private void chestSettings() { + chestOpenWithBlockOnTop = getBoolean("blocks.chest.open-with-solid-block-on-top", chestOpenWithBlockOnTop); + } + + public boolean composterBulkProcess = false; + private void composterSettings() { + composterBulkProcess = getBoolean("blocks.composter.sneak-to-bulk-process", composterBulkProcess); + } + + public boolean coralDieOutsideWater = true; + private void coralSettings() { + coralDieOutsideWater = getBoolean("blocks.coral.die-outside-water", coralDieOutsideWater); + } + + public boolean dispenserApplyCursedArmor = true; + public boolean dispenserPlaceAnvils = false; + private void dispenserSettings() { + dispenserApplyCursedArmor = getBoolean("blocks.dispenser.apply-cursed-to-armor-slots", dispenserApplyCursedArmor); + dispenserPlaceAnvils = getBoolean("blocks.dispenser.place-anvils", dispenserPlaceAnvils); + } + + public List doorRequiresRedstone = new ArrayList<>(); + private void doorSettings() { + getList("blocks.door.requires-redstone", new ArrayList()).forEach(key -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key.toString())); + if (!block.defaultBlockState().isAir()) { + doorRequiresRedstone.add(block); + } + }); + } + + public boolean dragonEggTeleport = true; + private void dragonEggSettings() { + dragonEggTeleport = getBoolean("blocks.dragon_egg.teleport", dragonEggTeleport); + } + + public boolean baselessEndCrystalExplode = true; + public double baselessEndCrystalExplosionPower = 6.0D; + public boolean baselessEndCrystalExplosionFire = false; + public net.minecraft.world.level.Level.ExplosionInteraction baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + public boolean basedEndCrystalExplode = true; + public double basedEndCrystalExplosionPower = 6.0D; + public boolean basedEndCrystalExplosionFire = false; + public net.minecraft.world.level.Level.ExplosionInteraction basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + public int endCrystalCramming = 0; + public boolean endCrystalPlaceAnywhere = false; + private void endCrystalSettings() { + if (PurpurConfig.version < 31) { + if ("DESTROY".equals(getString("blocks.end-crystal.baseless.explosion-effect", baselessEndCrystalExplosionEffect.name()))) { + set("blocks.end-crystal.baseless.explosion-effect", "BLOCK"); + } + if ("DESTROY".equals(getString("blocks.end-crystal.base.explosion-effect", basedEndCrystalExplosionEffect.name()))) { + set("blocks.end-crystal.base.explosion-effect", "BLOCK"); + } + } + baselessEndCrystalExplode = getBoolean("blocks.end-crystal.baseless.explode", baselessEndCrystalExplode); + baselessEndCrystalExplosionPower = getDouble("blocks.end-crystal.baseless.explosion-power", baselessEndCrystalExplosionPower); + baselessEndCrystalExplosionFire = getBoolean("blocks.end-crystal.baseless.explosion-fire", baselessEndCrystalExplosionFire); + try { + baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.end-crystal.baseless.explosion-effect", baselessEndCrystalExplosionEffect.name())); + } catch (IllegalArgumentException e) { + log(Level.SEVERE, "Unknown value for `blocks.end-crystal.baseless.explosion-effect`! Using default of `BLOCK`"); + baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + } + basedEndCrystalExplode = getBoolean("blocks.end-crystal.base.explode", basedEndCrystalExplode); + basedEndCrystalExplosionPower = getDouble("blocks.end-crystal.base.explosion-power", basedEndCrystalExplosionPower); + basedEndCrystalExplosionFire = getBoolean("blocks.end-crystal.base.explosion-fire", basedEndCrystalExplosionFire); + try { + basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.end-crystal.base.explosion-effect", basedEndCrystalExplosionEffect.name())); + } catch (IllegalArgumentException e) { + log(Level.SEVERE, "Unknown value for `blocks.end-crystal.base.explosion-effect`! Using default of `BLOCK`"); + basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; + } + endCrystalCramming = getInt("blocks.end-crystal.cramming-amount", endCrystalCramming); + endCrystalPlaceAnywhere = getBoolean("gameplay-mechanics.item.end-crystal.place-anywhere", endCrystalPlaceAnywhere); + } + + public boolean farmlandBypassMobGriefing = false; + public boolean farmlandGetsMoistFromBelow = false; + public boolean farmlandAlpha = false; + public boolean farmlandTramplingDisabled = false; + public boolean farmlandTramplingOnlyPlayers = false; + public boolean farmlandTramplingFeatherFalling = false; + public double farmlandTrampleHeight = -1D; + private void farmlandSettings() { + farmlandBypassMobGriefing = getBoolean("blocks.farmland.bypass-mob-griefing", farmlandBypassMobGriefing); + farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); + farmlandAlpha = getBoolean("blocks.farmland.use-alpha-farmland", farmlandAlpha); + farmlandTramplingDisabled = getBoolean("blocks.farmland.disable-trampling", farmlandTramplingDisabled); + farmlandTramplingOnlyPlayers = getBoolean("blocks.farmland.only-players-trample", farmlandTramplingOnlyPlayers); + farmlandTramplingFeatherFalling = getBoolean("blocks.farmland.feather-fall-distance-affects-trampling", farmlandTramplingFeatherFalling); + farmlandTrampleHeight = getDouble("blocks.farmland.trample-height", farmlandTrampleHeight); + } + + public double floweringAzaleaGrowthChance = 0.0D; + private void floweringAzaleaSettings() { + floweringAzaleaGrowthChance = getDouble("blocks.flowering_azalea.growth-chance", floweringAzaleaGrowthChance); + } + + public boolean furnaceUseLavaFromUnderneath = false; + private void furnaceSettings() { + if (PurpurConfig.version < 17) { + furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.infinite-fuel", furnaceUseLavaFromUnderneath); + boolean oldValue = getBoolean("blocks.furnace.infinite-fuel", furnaceUseLavaFromUnderneath); + set("blocks.furnace.infinite-fuel", null); + set("blocks.furnace.use-lava-from-underneath", oldValue); + } + furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.use-lava-from-underneath", furnaceUseLavaFromUnderneath); + } + + public boolean 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 sculkShriekerCanSummonDefault = false; + private void sculkShriekerSettings() { + sculkShriekerCanSummonDefault = getBoolean("blocks.sculk_shrieker.can-summon-default", sculkShriekerCanSummonDefault); + } + + public boolean signAllowColors = false; + private void signSettings() { + signAllowColors = getBoolean("blocks.sign.allow-colors", signAllowColors); + } + + public boolean slabHalfBreak = false; + private void slabSettings() { + slabHalfBreak = getBoolean("blocks.slab.break-individual-slabs-when-sneaking", slabHalfBreak); + } + + public boolean spawnerDeactivateByRedstone = false; + private void spawnerSettings() { + spawnerDeactivateByRedstone = getBoolean("blocks.spawner.deactivate-by-redstone", spawnerDeactivateByRedstone); + } + + public int spongeAbsorptionArea = 65; + public int spongeAbsorptionRadius = 6; + public boolean spongeAbsorbsLava = false; + public boolean spongeAbsorbsWaterFromMud = false; + private void spongeSettings() { + spongeAbsorptionArea = getInt("blocks.sponge.absorption.area", spongeAbsorptionArea); + spongeAbsorptionRadius = getInt("blocks.sponge.absorption.radius", spongeAbsorptionRadius); + spongeAbsorbsLava = getBoolean("blocks.sponge.absorbs-lava", spongeAbsorbsLava); + spongeAbsorbsWaterFromMud = getBoolean("blocks.sponge.absorbs-water-from-mud", spongeAbsorbsWaterFromMud); + } + + public float stonecutterDamage = 0.0F; + private void stonecutterSettings() { + stonecutterDamage = (float) getDouble("blocks.stonecutter.damage", stonecutterDamage); + } + + public boolean turtleEggsBreakFromExpOrbs = false; + public boolean turtleEggsBreakFromItems = false; + public boolean turtleEggsBreakFromMinecarts = false; + public boolean turtleEggsBypassMobGriefing = false; + public int turtleEggsRandomTickCrackChance = 500; + public boolean turtleEggsTramplingFeatherFalling = false; + private void turtleEggSettings() { + turtleEggsBreakFromExpOrbs = getBoolean("blocks.turtle_egg.break-from-exp-orbs", turtleEggsBreakFromExpOrbs); + turtleEggsBreakFromItems = getBoolean("blocks.turtle_egg.break-from-items", turtleEggsBreakFromItems); + turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); + turtleEggsBypassMobGriefing = getBoolean("blocks.turtle_egg.bypass-mob-griefing", turtleEggsBypassMobGriefing); + turtleEggsRandomTickCrackChance = getInt("blocks.turtle_egg.random-tick-crack-chance", turtleEggsRandomTickCrackChance); + turtleEggsTramplingFeatherFalling = getBoolean("blocks.turtle_egg.feather-fall-distance-affects-trampling", turtleEggsTramplingFeatherFalling); + } + + public int waterInfiniteRequiredSources = 2; + private void waterSources() { + waterInfiniteRequiredSources = getInt("blocks.water.infinite-required-sources", waterInfiniteRequiredSources); + } + + public boolean babiesAreRidable = true; + public boolean untamedTamablesAreRidable = true; + public boolean useNightVisionWhenRiding = false; + public boolean useDismountsUnderwaterTag = true; + private void ridableSettings() { + babiesAreRidable = getBoolean("ridable-settings.babies-are-ridable", babiesAreRidable); + untamedTamablesAreRidable = getBoolean("ridable-settings.untamed-tamables-are-ridable", untamedTamablesAreRidable); + useNightVisionWhenRiding = getBoolean("ridable-settings.use-night-vision", useNightVisionWhenRiding); + useDismountsUnderwaterTag = getBoolean("ridable-settings.use-dismounts-underwater-tag", useDismountsUnderwaterTag); + } + + public boolean allayRidable = false; + public boolean allayRidableInWater = true; + public boolean allayControllable = true; + public List allayRespectNBT = new ArrayList<>(); + private void allaySettings() { + allayRidable = getBoolean("mobs.allay.ridable", allayRidable); + allayRidableInWater = getBoolean("mobs.allay.ridable-in-water", allayRidableInWater); + allayControllable = getBoolean("mobs.allay.controllable", allayControllable); + allayRespectNBT.clear(); + getList("mobs.allay.respect-nbt", new ArrayList<>()).forEach(key -> allayRespectNBT.add(key.toString())); + } + + public boolean axolotlRidable = false; + public boolean axolotlControllable = true; + public double axolotlMaxHealth = 14.0D; + public int axolotlBreedingTicks = 6000; + public boolean axolotlTakeDamageFromWater = false; + public boolean axolotlAlwaysDropExp = false; + private void axolotlSettings() { + axolotlRidable = getBoolean("mobs.axolotl.ridable", axolotlRidable); + axolotlControllable = getBoolean("mobs.axolotl.controllable", axolotlControllable); + axolotlMaxHealth = getDouble("mobs.axolotl.attributes.max_health", axolotlMaxHealth); + axolotlBreedingTicks = getInt("mobs.axolotl.breeding-delay-ticks", axolotlBreedingTicks); + axolotlTakeDamageFromWater = getBoolean("mobs.axolotl.takes-damage-from-water", axolotlTakeDamageFromWater); + axolotlAlwaysDropExp = getBoolean("mobs.axolotl.always-drop-exp", axolotlAlwaysDropExp); + } + + public boolean batRidable = false; + public boolean batRidableInWater = true; + public boolean batControllable = true; + public double batMaxY = 320D; + public double batMaxHealth = 6.0D; + public double batFollowRange = 16.0D; + public double batKnockbackResistance = 0.0D; + public double batMovementSpeed = 0.6D; + public double batFlyingSpeed = 0.6D; + public double batArmor = 0.0D; + public double batArmorToughness = 0.0D; + public double batAttackKnockback = 0.0D; + public boolean batTakeDamageFromWater = false; + public boolean batAlwaysDropExp = false; + private void batSettings() { + batRidable = getBoolean("mobs.bat.ridable", batRidable); + batRidableInWater = getBoolean("mobs.bat.ridable-in-water", batRidableInWater); + batControllable = getBoolean("mobs.bat.controllable", batControllable); + batMaxY = getDouble("mobs.bat.ridable-max-y", batMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.bat.attributes.max-health", batMaxHealth); + set("mobs.bat.attributes.max-health", null); + set("mobs.bat.attributes.max_health", oldValue); + } + batMaxHealth = getDouble("mobs.bat.attributes.max_health", batMaxHealth); + batFollowRange = getDouble("mobs.bat.attributes.follow_range", batFollowRange); + batKnockbackResistance = getDouble("mobs.bat.attributes.knockback_resistance", batKnockbackResistance); + batMovementSpeed = getDouble("mobs.bat.attributes.movement_speed", batMovementSpeed); + batFlyingSpeed = getDouble("mobs.bat.attributes.flying_speed", batFlyingSpeed); + batArmor = getDouble("mobs.bat.attributes.armor", batArmor); + batArmorToughness = getDouble("mobs.bat.attributes.armor_toughness", batArmorToughness); + batAttackKnockback = getDouble("mobs.bat.attributes.attack_knockback", batAttackKnockback); + batTakeDamageFromWater = getBoolean("mobs.bat.takes-damage-from-water", batTakeDamageFromWater); + batAlwaysDropExp = getBoolean("mobs.bat.always-drop-exp", batAlwaysDropExp); + } + + public boolean beeRidable = false; + public boolean beeRidableInWater = true; + public boolean beeControllable = true; + public double beeMaxY = 320D; + public double beeMaxHealth = 10.0D; + public int beeBreedingTicks = 6000; + public boolean beeTakeDamageFromWater = false; + public boolean beeCanWorkAtNight = false; + public boolean beeCanWorkInRain = false; + public boolean beeAlwaysDropExp = false; + public boolean beeDiesAfterSting = true; + private void beeSettings() { + beeRidable = getBoolean("mobs.bee.ridable", beeRidable); + beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater); + beeControllable = getBoolean("mobs.bee.controllable", beeControllable); + beeMaxY = getDouble("mobs.bee.ridable-max-y", beeMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.bee.attributes.max-health", beeMaxHealth); + set("mobs.bee.attributes.max-health", null); + set("mobs.bee.attributes.max_health", oldValue); + } + beeMaxHealth = getDouble("mobs.bee.attributes.max_health", beeMaxHealth); + beeBreedingTicks = getInt("mobs.bee.breeding-delay-ticks", beeBreedingTicks); + beeTakeDamageFromWater = getBoolean("mobs.bee.takes-damage-from-water", beeTakeDamageFromWater); + beeCanWorkAtNight = getBoolean("mobs.bee.can-work-at-night", beeCanWorkAtNight); + beeCanWorkInRain = getBoolean("mobs.bee.can-work-in-rain", beeCanWorkInRain); + beeAlwaysDropExp = getBoolean("mobs.bee.always-drop-exp", beeAlwaysDropExp); + beeDiesAfterSting = getBoolean("mobs.bee.dies-after-sting", beeDiesAfterSting); + } + + public boolean blazeRidable = false; + public boolean blazeRidableInWater = true; + public boolean blazeControllable = true; + public double blazeMaxY = 320D; + public double blazeMaxHealth = 20.0D; + public boolean blazeTakeDamageFromWater = true; + public boolean blazeAlwaysDropExp = false; + private void blazeSettings() { + blazeRidable = getBoolean("mobs.blaze.ridable", blazeRidable); + blazeRidableInWater = getBoolean("mobs.blaze.ridable-in-water", blazeRidableInWater); + blazeControllable = getBoolean("mobs.blaze.controllable", blazeControllable); + blazeMaxY = getDouble("mobs.blaze.ridable-max-y", blazeMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.blaze.attributes.max-health", blazeMaxHealth); + set("mobs.blaze.attributes.max-health", null); + set("mobs.blaze.attributes.max_health", oldValue); + } + blazeMaxHealth = getDouble("mobs.blaze.attributes.max_health", blazeMaxHealth); + blazeTakeDamageFromWater = getBoolean("mobs.blaze.takes-damage-from-water", blazeTakeDamageFromWater); + blazeAlwaysDropExp = getBoolean("mobs.blaze.always-drop-exp", blazeAlwaysDropExp); + } + + public boolean camelRidableInWater = false; + public double camelMaxHealthMin = 32.0D; + public double camelMaxHealthMax = 32.0D; + public double camelJumpStrengthMin = 0.42D; + public double camelJumpStrengthMax = 0.42D; + public double camelMovementSpeedMin = 0.09D; + public double camelMovementSpeedMax = 0.09D; + public int camelBreedingTicks = 6000; + private void camelSettings() { + camelRidableInWater = getBoolean("mobs.camel.ridable-in-water", camelRidableInWater); + camelMaxHealthMin = getDouble("mobs.camel.attributes.max_health.min", camelMaxHealthMin); + camelMaxHealthMax = getDouble("mobs.camel.attributes.max_health.max", camelMaxHealthMax); + camelJumpStrengthMin = getDouble("mobs.camel.attributes.jump_strength.min", camelJumpStrengthMin); + camelJumpStrengthMax = getDouble("mobs.camel.attributes.jump_strength.max", camelJumpStrengthMax); + camelMovementSpeedMin = getDouble("mobs.camel.attributes.movement_speed.min", camelMovementSpeedMin); + camelMovementSpeedMax = getDouble("mobs.camel.attributes.movement_speed.max", camelMovementSpeedMax); + camelBreedingTicks = getInt("mobs.camel.breeding-delay-ticks", camelBreedingTicks); + } + + public boolean catRidable = false; + public boolean catRidableInWater = true; + public boolean catControllable = true; + public double catMaxHealth = 10.0D; + public int catSpawnDelay = 1200; + public int catSpawnSwampHutScanRange = 16; + public int catSpawnVillageScanRange = 48; + public int catBreedingTicks = 6000; + public DyeColor catDefaultCollarColor = DyeColor.RED; + public boolean catTakeDamageFromWater = false; + public boolean catAlwaysDropExp = false; + private void catSettings() { + catRidable = getBoolean("mobs.cat.ridable", catRidable); + catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater); + catControllable = getBoolean("mobs.cat.controllable", catControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.cat.attributes.max-health", catMaxHealth); + set("mobs.cat.attributes.max-health", null); + set("mobs.cat.attributes.max_health", oldValue); + } + catMaxHealth = getDouble("mobs.cat.attributes.max_health", catMaxHealth); + catSpawnDelay = getInt("mobs.cat.spawn-delay", catSpawnDelay); + catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange); + catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange); + catBreedingTicks = getInt("mobs.cat.breeding-delay-ticks", catBreedingTicks); + try { + catDefaultCollarColor = DyeColor.valueOf(getString("mobs.cat.default-collar-color", catDefaultCollarColor.name())); + } catch (IllegalArgumentException ignore) { + catDefaultCollarColor = DyeColor.RED; + } + catTakeDamageFromWater = getBoolean("mobs.cat.takes-damage-from-water", catTakeDamageFromWater); + catAlwaysDropExp = getBoolean("mobs.cat.always-drop-exp", catAlwaysDropExp); + } + + public boolean caveSpiderRidable = false; + public boolean caveSpiderRidableInWater = true; + public boolean caveSpiderControllable = true; + public double caveSpiderMaxHealth = 12.0D; + public boolean caveSpiderTakeDamageFromWater = false; + public boolean caveSpiderAlwaysDropExp = false; + private void caveSpiderSettings() { + caveSpiderRidable = getBoolean("mobs.cave_spider.ridable", caveSpiderRidable); + caveSpiderRidableInWater = getBoolean("mobs.cave_spider.ridable-in-water", caveSpiderRidableInWater); + caveSpiderControllable = getBoolean("mobs.cave_spider.controllable", caveSpiderControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.cave_spider.attributes.max-health", caveSpiderMaxHealth); + set("mobs.cave_spider.attributes.max-health", null); + set("mobs.cave_spider.attributes.max_health", oldValue); + } + caveSpiderMaxHealth = getDouble("mobs.cave_spider.attributes.max_health", caveSpiderMaxHealth); + caveSpiderTakeDamageFromWater = getBoolean("mobs.cave_spider.takes-damage-from-water", caveSpiderTakeDamageFromWater); + caveSpiderAlwaysDropExp = getBoolean("mobs.cave_spider.always-drop-exp", caveSpiderAlwaysDropExp); + } + + public boolean chickenRidable = false; + public boolean chickenRidableInWater = false; + public boolean chickenControllable = true; + public double chickenMaxHealth = 4.0D; + public boolean chickenRetaliate = false; + public int chickenBreedingTicks = 6000; + public boolean chickenTakeDamageFromWater = false; + public boolean chickenAlwaysDropExp = false; + private void chickenSettings() { + chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable); + chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater); + chickenControllable = getBoolean("mobs.chicken.controllable", chickenControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.chicken.attributes.max-health", chickenMaxHealth); + set("mobs.chicken.attributes.max-health", null); + set("mobs.chicken.attributes.max_health", oldValue); + } + chickenMaxHealth = getDouble("mobs.chicken.attributes.max_health", chickenMaxHealth); + chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate); + chickenBreedingTicks = getInt("mobs.chicken.breeding-delay-ticks", chickenBreedingTicks); + chickenTakeDamageFromWater = getBoolean("mobs.chicken.takes-damage-from-water", chickenTakeDamageFromWater); + chickenAlwaysDropExp = getBoolean("mobs.chicken.always-drop-exp", chickenAlwaysDropExp); + } + + public boolean codRidable = false; + public boolean codControllable = true; + public double codMaxHealth = 3.0D; + public boolean codTakeDamageFromWater = false; + public boolean codAlwaysDropExp = false; + private void codSettings() { + codRidable = getBoolean("mobs.cod.ridable", codRidable); + codControllable = getBoolean("mobs.cod.controllable", codControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.cod.attributes.max-health", codMaxHealth); + set("mobs.cod.attributes.max-health", null); + set("mobs.cod.attributes.max_health", oldValue); + } + codMaxHealth = getDouble("mobs.cod.attributes.max_health", codMaxHealth); + codTakeDamageFromWater = getBoolean("mobs.cod.takes-damage-from-water", codTakeDamageFromWater); + codAlwaysDropExp = getBoolean("mobs.cod.always-drop-exp", codAlwaysDropExp); + } + + public boolean cowRidable = false; + public boolean cowRidableInWater = true; + public boolean cowControllable = true; + public double cowMaxHealth = 10.0D; + public int cowFeedMushrooms = 0; + public int cowBreedingTicks = 6000; + public boolean cowTakeDamageFromWater = false; + public double cowNaturallyAggressiveToPlayersChance = 0.0D; + public double cowNaturallyAggressiveToPlayersDamage = 2.0D; + public boolean cowAlwaysDropExp = false; + private void cowSettings() { + if (PurpurConfig.version < 22) { + double oldValue = getDouble("mobs.cow.naturally-aggressive-to-players-chance", cowNaturallyAggressiveToPlayersChance); + set("mobs.cow.naturally-aggressive-to-players-chance", null); + set("mobs.cow.naturally-aggressive-to-players.chance", oldValue); + } + cowRidable = getBoolean("mobs.cow.ridable", cowRidable); + cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater); + cowControllable = getBoolean("mobs.cow.controllable", cowControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.cow.attributes.max-health", cowMaxHealth); + set("mobs.cow.attributes.max-health", null); + set("mobs.cow.attributes.max_health", oldValue); + } + cowMaxHealth = getDouble("mobs.cow.attributes.max_health", cowMaxHealth); + cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms); + cowBreedingTicks = getInt("mobs.cow.breeding-delay-ticks", cowBreedingTicks); + cowTakeDamageFromWater = getBoolean("mobs.cow.takes-damage-from-water", cowTakeDamageFromWater); + cowNaturallyAggressiveToPlayersChance = getDouble("mobs.cow.naturally-aggressive-to-players.chance", cowNaturallyAggressiveToPlayersChance); + cowNaturallyAggressiveToPlayersDamage = getDouble("mobs.cow.naturally-aggressive-to-players.damage", cowNaturallyAggressiveToPlayersDamage); + cowAlwaysDropExp = getBoolean("mobs.cow.always-drop-exp", cowAlwaysDropExp); + } + + public boolean creeperRidable = false; + public boolean creeperRidableInWater = true; + public boolean creeperControllable = true; + public double creeperMaxHealth = 20.0D; + public double creeperChargedChance = 0.0D; + public boolean creeperAllowGriefing = true; + public boolean creeperBypassMobGriefing = false; + public boolean creeperTakeDamageFromWater = false; + public boolean creeperExplodeWhenKilled = false; + public boolean creeperHealthRadius = false; + public boolean creeperAlwaysDropExp = false; + public double creeperHeadVisibilityPercent = 0.5D; + public boolean creeperEncircleTarget = false; + private void creeperSettings() { + creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable); + creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater); + creeperControllable = getBoolean("mobs.creeper.controllable", creeperControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.creeper.attributes.max-health", creeperMaxHealth); + set("mobs.creeper.attributes.max-health", null); + set("mobs.creeper.attributes.max_health", oldValue); + } + creeperMaxHealth = getDouble("mobs.creeper.attributes.max_health", creeperMaxHealth); + creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); + creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing); + creeperBypassMobGriefing = getBoolean("mobs.creeper.bypass-mob-griefing", creeperBypassMobGriefing); + creeperTakeDamageFromWater = getBoolean("mobs.creeper.takes-damage-from-water", creeperTakeDamageFromWater); + creeperExplodeWhenKilled = getBoolean("mobs.creeper.explode-when-killed", creeperExplodeWhenKilled); + creeperHealthRadius = getBoolean("mobs.creeper.health-impacts-explosion", creeperHealthRadius); + creeperAlwaysDropExp = getBoolean("mobs.creeper.always-drop-exp", creeperAlwaysDropExp); + creeperHeadVisibilityPercent = getDouble("mobs.creeper.head-visibility-percent", creeperHeadVisibilityPercent); + creeperEncircleTarget = getBoolean("mobs.creeper.encircle-target", creeperEncircleTarget); + } + + public boolean dolphinRidable = false; + public boolean dolphinControllable = true; + public int dolphinSpitCooldown = 20; + public float dolphinSpitSpeed = 1.0F; + public float dolphinSpitDamage = 2.0F; + public double dolphinMaxHealth = 10.0D; + public boolean dolphinDisableTreasureSearching = false; + public boolean dolphinTakeDamageFromWater = false; + public double dolphinNaturallyAggressiveToPlayersChance = 0.0D; + public boolean dolphinAlwaysDropExp = false; + private void dolphinSettings() { + dolphinRidable = getBoolean("mobs.dolphin.ridable", dolphinRidable); + dolphinControllable = getBoolean("mobs.dolphin.controllable", dolphinControllable); + dolphinSpitCooldown = getInt("mobs.dolphin.spit.cooldown", dolphinSpitCooldown); + dolphinSpitSpeed = (float) getDouble("mobs.dolphin.spit.speed", dolphinSpitSpeed); + dolphinSpitDamage = (float) getDouble("mobs.dolphin.spit.damage", dolphinSpitDamage); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.dolphin.attributes.max-health", dolphinMaxHealth); + set("mobs.dolphin.attributes.max-health", null); + set("mobs.dolphin.attributes.max_health", oldValue); + } + dolphinMaxHealth = getDouble("mobs.dolphin.attributes.max_health", dolphinMaxHealth); + dolphinDisableTreasureSearching = getBoolean("mobs.dolphin.disable-treasure-searching", dolphinDisableTreasureSearching); + dolphinTakeDamageFromWater = getBoolean("mobs.dolphin.takes-damage-from-water", dolphinTakeDamageFromWater); + dolphinNaturallyAggressiveToPlayersChance = getDouble("mobs.dolphin.naturally-aggressive-to-players-chance", dolphinNaturallyAggressiveToPlayersChance); + dolphinAlwaysDropExp = getBoolean("mobs.dolphin.always-drop-exp", dolphinAlwaysDropExp); + } + + public boolean donkeyRidableInWater = false; + public double donkeyMaxHealthMin = 15.0D; + public double donkeyMaxHealthMax = 30.0D; + public double donkeyJumpStrengthMin = 0.5D; + public double donkeyJumpStrengthMax = 0.5D; + public double donkeyMovementSpeedMin = 0.175D; + public double donkeyMovementSpeedMax = 0.175D; + public int donkeyBreedingTicks = 6000; + public boolean donkeyTakeDamageFromWater = false; + public boolean donkeyAlwaysDropExp = false; + private void donkeySettings() { + donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.donkey.attributes.max-health.min", donkeyMaxHealthMin); + double oldMax = getDouble("mobs.donkey.attributes.max-health.max", donkeyMaxHealthMax); + set("mobs.donkey.attributes.max-health", null); + set("mobs.donkey.attributes.max_health.min", oldMin); + set("mobs.donkey.attributes.max_health.max", oldMax); + } + donkeyMaxHealthMin = getDouble("mobs.donkey.attributes.max_health.min", donkeyMaxHealthMin); + donkeyMaxHealthMax = getDouble("mobs.donkey.attributes.max_health.max", donkeyMaxHealthMax); + donkeyJumpStrengthMin = getDouble("mobs.donkey.attributes.jump_strength.min", donkeyJumpStrengthMin); + donkeyJumpStrengthMax = getDouble("mobs.donkey.attributes.jump_strength.max", donkeyJumpStrengthMax); + donkeyMovementSpeedMin = getDouble("mobs.donkey.attributes.movement_speed.min", donkeyMovementSpeedMin); + donkeyMovementSpeedMax = getDouble("mobs.donkey.attributes.movement_speed.max", donkeyMovementSpeedMax); + donkeyBreedingTicks = getInt("mobs.donkey.breeding-delay-ticks", donkeyBreedingTicks); + donkeyTakeDamageFromWater = getBoolean("mobs.donkey.takes-damage-from-water", donkeyTakeDamageFromWater); + donkeyAlwaysDropExp = getBoolean("mobs.donkey.always-drop-exp", donkeyAlwaysDropExp); + } + + public boolean drownedRidable = false; + public boolean drownedRidableInWater = true; + public boolean drownedControllable = true; + public double drownedMaxHealth = 20.0D; + public double drownedSpawnReinforcements = 0.1D; + public boolean drownedJockeyOnlyBaby = true; + public double drownedJockeyChance = 0.05D; + public boolean drownedJockeyTryExistingChickens = true; + public boolean drownedTakeDamageFromWater = false; + public boolean drownedBreakDoors = false; + public boolean drownedAlwaysDropExp = false; + private void drownedSettings() { + drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable); + drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater); + drownedControllable = getBoolean("mobs.drowned.controllable", drownedControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.drowned.attributes.max-health", drownedMaxHealth); + set("mobs.drowned.attributes.max-health", null); + set("mobs.drowned.attributes.max_health", oldValue); + } + drownedMaxHealth = getDouble("mobs.drowned.attributes.max_health", drownedMaxHealth); + drownedSpawnReinforcements = getDouble("mobs.drowned.attributes.spawn_reinforcements", drownedSpawnReinforcements); + drownedJockeyOnlyBaby = getBoolean("mobs.drowned.jockey.only-babies", drownedJockeyOnlyBaby); + drownedJockeyChance = getDouble("mobs.drowned.jockey.chance", drownedJockeyChance); + drownedJockeyTryExistingChickens = getBoolean("mobs.drowned.jockey.try-existing-chickens", drownedJockeyTryExistingChickens); + drownedTakeDamageFromWater = getBoolean("mobs.drowned.takes-damage-from-water", drownedTakeDamageFromWater); + drownedBreakDoors = getBoolean("mobs.drowned.can-break-doors", drownedBreakDoors); + drownedAlwaysDropExp = getBoolean("mobs.drowned.always-drop-exp", drownedAlwaysDropExp); + } + + public boolean elderGuardianRidable = false; + public boolean elderGuardianControllable = true; + public double elderGuardianMaxHealth = 80.0D; + public boolean elderGuardianTakeDamageFromWater = false; + public boolean elderGuardianAlwaysDropExp = false; + private void elderGuardianSettings() { + elderGuardianRidable = getBoolean("mobs.elder_guardian.ridable", elderGuardianRidable); + elderGuardianControllable = getBoolean("mobs.elder_guardian.controllable", elderGuardianControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.elder_guardian.attributes.max-health", elderGuardianMaxHealth); + set("mobs.elder_guardian.attributes.max-health", null); + set("mobs.elder_guardian.attributes.max_health", oldValue); + } + elderGuardianMaxHealth = getDouble("mobs.elder_guardian.attributes.max_health", elderGuardianMaxHealth); + elderGuardianTakeDamageFromWater = getBoolean("mobs.elder_guardian.takes-damage-from-water", elderGuardianTakeDamageFromWater); + elderGuardianAlwaysDropExp = getBoolean("mobs.elder_guardian.always-drop-exp", elderGuardianAlwaysDropExp); + } + + public boolean enchantmentTableLapisPersists = false; + private void enchantmentTableSettings() { + enchantmentTableLapisPersists = getBoolean("blocks.enchantment-table.lapis-persists", enchantmentTableLapisPersists); + } + + public boolean enderDragonRidable = false; + public boolean enderDragonRidableInWater = true; + public boolean enderDragonControllable = true; + public double enderDragonMaxY = 320D; + public double enderDragonMaxHealth = 200.0D; + public boolean enderDragonAlwaysDropsFullExp = false; + public boolean enderDragonBypassMobGriefing = false; + public boolean enderDragonTakeDamageFromWater = false; + public boolean enderDragonCanRideVehicles = false; + private void enderDragonSettings() { + enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable); + enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater); + enderDragonControllable = getBoolean("mobs.ender_dragon.controllable", enderDragonControllable); + enderDragonMaxY = getDouble("mobs.ender_dragon.ridable-max-y", enderDragonMaxY); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.ender_dragon.max-health", enderDragonMaxHealth); + set("mobs.ender_dragon.max-health", null); + set("mobs.ender_dragon.attributes.max_health", oldValue); + } else if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.ender_dragon.attributes.max-health", enderDragonMaxHealth); + set("mobs.ender_dragon.attributes.max-health", null); + set("mobs.ender_dragon.attributes.max_health", oldValue); + } + enderDragonMaxHealth = getDouble("mobs.ender_dragon.attributes.max_health", enderDragonMaxHealth); + enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp); + enderDragonBypassMobGriefing = getBoolean("mobs.ender_dragon.bypass-mob-griefing", enderDragonBypassMobGriefing); + enderDragonTakeDamageFromWater = getBoolean("mobs.ender_dragon.takes-damage-from-water", enderDragonTakeDamageFromWater); + enderDragonCanRideVehicles = getBoolean("mobs.ender_dragon.can-ride-vehicles", enderDragonCanRideVehicles); + } + + public boolean endermanRidable = false; + public boolean endermanRidableInWater = true; + public boolean endermanControllable = true; + public double endermanMaxHealth = 40.0D; + public boolean endermanAllowGriefing = true; + public boolean endermanDespawnEvenWithBlock = false; + public boolean endermanBypassMobGriefing = false; + public boolean endermanTakeDamageFromWater = true; + public boolean endermanAggroEndermites = true; + public boolean endermanAggroEndermitesOnlyIfPlayerSpawned = false; + public boolean endermanIgnorePlayerDragonHead = false; + public boolean endermanDisableStareAggro = false; + public boolean endermanIgnoreProjectiles = false; + public boolean endermanAlwaysDropExp = false; + private void endermanSettings() { + endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); + endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); + endermanControllable = getBoolean("mobs.enderman.controllable", endermanControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.enderman.attributes.max-health", endermanMaxHealth); + set("mobs.enderman.attributes.max-health", null); + set("mobs.enderman.attributes.max_health", oldValue); + } + if (PurpurConfig.version < 15) { + // remove old option + set("mobs.enderman.aggressive-towards-spawned-endermites", null); + } + endermanMaxHealth = getDouble("mobs.enderman.attributes.max_health", endermanMaxHealth); + endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing); + endermanDespawnEvenWithBlock = getBoolean("mobs.enderman.can-despawn-with-held-block", endermanDespawnEvenWithBlock); + endermanBypassMobGriefing = getBoolean("mobs.enderman.bypass-mob-griefing", endermanBypassMobGriefing); + endermanTakeDamageFromWater = getBoolean("mobs.enderman.takes-damage-from-water", endermanTakeDamageFromWater); + endermanAggroEndermites = getBoolean("mobs.enderman.aggressive-towards-endermites", endermanAggroEndermites); + endermanAggroEndermitesOnlyIfPlayerSpawned = getBoolean("mobs.enderman.aggressive-towards-endermites-only-spawned-by-player-thrown-ender-pearls", endermanAggroEndermitesOnlyIfPlayerSpawned); + endermanIgnorePlayerDragonHead = getBoolean("mobs.enderman.ignore-players-wearing-dragon-head", endermanIgnorePlayerDragonHead); + endermanDisableStareAggro = getBoolean("mobs.enderman.disable-player-stare-aggression", endermanDisableStareAggro); + endermanIgnoreProjectiles = getBoolean("mobs.enderman.ignore-projectiles", endermanIgnoreProjectiles); + endermanAlwaysDropExp = getBoolean("mobs.enderman.always-drop-exp", endermanAlwaysDropExp); + } + + public boolean endermiteRidable = false; + public boolean endermiteRidableInWater = true; + public boolean endermiteControllable = true; + public double endermiteMaxHealth = 8.0D; + public boolean endermiteTakeDamageFromWater = false; + public boolean endermiteAlwaysDropExp = false; + private void endermiteSettings() { + endermiteRidable = getBoolean("mobs.endermite.ridable", endermiteRidable); + endermiteRidableInWater = getBoolean("mobs.endermite.ridable-in-water", endermiteRidableInWater); + endermiteControllable = getBoolean("mobs.endermite.controllable", endermiteControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.endermite.attributes.max-health", endermiteMaxHealth); + set("mobs.endermite.attributes.max-health", null); + set("mobs.endermite.attributes.max_health", oldValue); + } + endermiteMaxHealth = getDouble("mobs.endermite.attributes.max_health", endermiteMaxHealth); + endermiteTakeDamageFromWater = getBoolean("mobs.endermite.takes-damage-from-water", endermiteTakeDamageFromWater); + endermiteAlwaysDropExp = getBoolean("mobs.endermite.always-drop-exp", endermiteAlwaysDropExp); + } + + public boolean evokerRidable = false; + public boolean evokerRidableInWater = true; + public boolean evokerControllable = true; + public double evokerMaxHealth = 24.0D; + public boolean evokerBypassMobGriefing = false; + public boolean evokerTakeDamageFromWater = false; + public boolean evokerAlwaysDropExp = false; + private void evokerSettings() { + evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable); + evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater); + evokerControllable = getBoolean("mobs.evoker.controllable", evokerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.evoker.attributes.max-health", evokerMaxHealth); + set("mobs.evoker.attributes.max-health", null); + set("mobs.evoker.attributes.max_health", oldValue); + } + evokerMaxHealth = getDouble("mobs.evoker.attributes.max_health", evokerMaxHealth); + evokerBypassMobGriefing = getBoolean("mobs.evoker.bypass-mob-griefing", evokerBypassMobGriefing); + evokerTakeDamageFromWater = getBoolean("mobs.evoker.takes-damage-from-water", evokerTakeDamageFromWater); + evokerAlwaysDropExp = getBoolean("mobs.evoker.always-drop-exp", evokerAlwaysDropExp); + } + + public boolean foxRidable = false; + public boolean foxRidableInWater = true; + public boolean foxControllable = true; + public double foxMaxHealth = 10.0D; + public boolean foxTypeChangesWithTulips = false; + public int foxBreedingTicks = 6000; + public boolean foxBypassMobGriefing = false; + public boolean foxTakeDamageFromWater = false; + public boolean foxAlwaysDropExp = false; + private void foxSettings() { + foxRidable = getBoolean("mobs.fox.ridable", foxRidable); + foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater); + foxControllable = getBoolean("mobs.fox.controllable", foxControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.fox.attributes.max-health", foxMaxHealth); + set("mobs.fox.attributes.max-health", null); + set("mobs.fox.attributes.max_health", oldValue); + } + foxMaxHealth = getDouble("mobs.fox.attributes.max_health", foxMaxHealth); + foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); + foxBreedingTicks = getInt("mobs.fox.breeding-delay-ticks", foxBreedingTicks); + foxBypassMobGriefing = getBoolean("mobs.fox.bypass-mob-griefing", foxBypassMobGriefing); + foxTakeDamageFromWater = getBoolean("mobs.fox.takes-damage-from-water", foxTakeDamageFromWater); + foxAlwaysDropExp = getBoolean("mobs.fox.always-drop-exp", foxAlwaysDropExp); + } + + public boolean frogRidable = false; + public boolean frogRidableInWater = true; + public boolean frogControllable = true; + public float frogRidableJumpHeight = 0.65F; + public int frogBreedingTicks = 6000; + private void frogSettings() { + frogRidable = getBoolean("mobs.frog.ridable", frogRidable); + frogRidableInWater = getBoolean("mobs.frog.ridable-in-water", frogRidableInWater); + frogControllable = getBoolean("mobs.frog.controllable", frogControllable); + frogRidableJumpHeight = (float) getDouble("mobs.frog.ridable-jump-height", frogRidableJumpHeight); + frogBreedingTicks = getInt("mobs.frog.breeding-delay-ticks", frogBreedingTicks); + } + + public boolean ghastRidable = false; + public boolean ghastRidableInWater = true; + public boolean ghastControllable = true; + public double ghastMaxY = 320D; + public double ghastMaxHealth = 10.0D; + public boolean ghastTakeDamageFromWater = false; + public boolean ghastAlwaysDropExp = false; + private void ghastSettings() { + ghastRidable = getBoolean("mobs.ghast.ridable", ghastRidable); + ghastRidableInWater = getBoolean("mobs.ghast.ridable-in-water", ghastRidableInWater); + ghastControllable = getBoolean("mobs.ghast.controllable", ghastControllable); + ghastMaxY = getDouble("mobs.ghast.ridable-max-y", ghastMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.ghast.attributes.max-health", ghastMaxHealth); + set("mobs.ghast.attributes.max-health", null); + set("mobs.ghast.attributes.max_health", oldValue); + } + ghastMaxHealth = getDouble("mobs.ghast.attributes.max_health", ghastMaxHealth); + ghastTakeDamageFromWater = getBoolean("mobs.ghast.takes-damage-from-water", ghastTakeDamageFromWater); + ghastAlwaysDropExp = getBoolean("mobs.ghast.always-drop-exp", ghastAlwaysDropExp); + } + + public boolean giantRidable = false; + public boolean giantRidableInWater = true; + public boolean giantControllable = true; + public double giantMovementSpeed = 0.5D; + public double giantAttackDamage = 50.0D; + public double giantMaxHealth = 100.0D; + public float giantStepHeight = 2.0F; + public float giantJumpHeight = 1.0F; + public boolean giantHaveAI = false; + public boolean giantHaveHostileAI = false; + public boolean giantTakeDamageFromWater = false; + public boolean giantAlwaysDropExp = false; + private void giantSettings() { + giantRidable = getBoolean("mobs.giant.ridable", giantRidable); + giantRidableInWater = getBoolean("mobs.giant.ridable-in-water", giantRidableInWater); + giantControllable = getBoolean("mobs.giant.controllable", giantControllable); + giantMovementSpeed = getDouble("mobs.giant.movement-speed", giantMovementSpeed); + giantAttackDamage = getDouble("mobs.giant.attack-damage", giantAttackDamage); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.giant.max-health", giantMaxHealth); + set("mobs.giant.max-health", null); + set("mobs.giant.attributes.max_health", oldValue); + } else if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.giant.attributes.max-health", giantMaxHealth); + set("mobs.giant.attributes.max-health", null); + set("mobs.giant.attributes.max_health", oldValue); + } + giantMaxHealth = getDouble("mobs.giant.attributes.max_health", giantMaxHealth); + giantStepHeight = (float) getDouble("mobs.giant.step-height", giantStepHeight); + giantJumpHeight = (float) getDouble("mobs.giant.jump-height", giantJumpHeight); + giantHaveAI = getBoolean("mobs.giant.have-ai", giantHaveAI); + giantHaveHostileAI = getBoolean("mobs.giant.have-hostile-ai", giantHaveHostileAI); + giantTakeDamageFromWater = getBoolean("mobs.giant.takes-damage-from-water", giantTakeDamageFromWater); + giantAlwaysDropExp = getBoolean("mobs.giant.always-drop-exp", giantAlwaysDropExp); + } + + public boolean glowSquidRidable = false; + public boolean glowSquidControllable = true; + public double glowSquidMaxHealth = 10.0D; + public boolean glowSquidsCanFly = false; + public boolean glowSquidTakeDamageFromWater = false; + public boolean glowSquidAlwaysDropExp = false; + private void glowSquidSettings() { + glowSquidRidable = getBoolean("mobs.glow_squid.ridable", glowSquidRidable); + glowSquidControllable = getBoolean("mobs.glow_squid.controllable", glowSquidControllable); + glowSquidMaxHealth = getDouble("mobs.glow_squid.attributes.max_health", glowSquidMaxHealth); + glowSquidsCanFly = getBoolean("mobs.glow_squid.can-fly", glowSquidsCanFly); + glowSquidTakeDamageFromWater = getBoolean("mobs.glow_squid.takes-damage-from-water", glowSquidTakeDamageFromWater); + glowSquidAlwaysDropExp = getBoolean("mobs.glow_squid.always-drop-exp", glowSquidAlwaysDropExp); + } + + public boolean goatRidable = false; + public boolean goatRidableInWater = true; + public boolean goatControllable = true; + public double goatMaxHealth = 10.0D; + public int goatBreedingTicks = 6000; + public boolean goatTakeDamageFromWater = false; + public boolean goatAlwaysDropExp = false; + private void goatSettings() { + goatRidable = getBoolean("mobs.goat.ridable", goatRidable); + goatRidableInWater = getBoolean("mobs.goat.ridable-in-water", goatRidableInWater); + goatControllable = getBoolean("mobs.goat.controllable", goatControllable); + goatMaxHealth = getDouble("mobs.goat.attributes.max_health", goatMaxHealth); + goatBreedingTicks = getInt("mobs.goat.breeding-delay-ticks", goatBreedingTicks); + goatTakeDamageFromWater = getBoolean("mobs.goat.takes-damage-from-water", goatTakeDamageFromWater); + goatAlwaysDropExp = getBoolean("mobs.goat.always-drop-exp", goatAlwaysDropExp); + } + + public boolean guardianRidable = false; + public boolean guardianControllable = true; + public double guardianMaxHealth = 30.0D; + public boolean guardianTakeDamageFromWater = false; + public boolean guardianAlwaysDropExp = false; + private void guardianSettings() { + guardianRidable = getBoolean("mobs.guardian.ridable", guardianRidable); + guardianControllable = getBoolean("mobs.guardian.controllable", guardianControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.guardian.attributes.max-health", guardianMaxHealth); + set("mobs.guardian.attributes.max-health", null); + set("mobs.guardian.attributes.max_health", oldValue); + } + guardianMaxHealth = getDouble("mobs.guardian.attributes.max_health", guardianMaxHealth); + guardianTakeDamageFromWater = getBoolean("mobs.guardian.takes-damage-from-water", guardianTakeDamageFromWater); + guardianAlwaysDropExp = getBoolean("mobs.guardian.always-drop-exp", guardianAlwaysDropExp); + } + + public boolean hoglinRidable = false; + public boolean hoglinRidableInWater = true; + public boolean hoglinControllable = true; + public double hoglinMaxHealth = 40.0D; + public int hoglinBreedingTicks = 6000; + public boolean hoglinTakeDamageFromWater = false; + public boolean hoglinAlwaysDropExp = false; + private void hoglinSettings() { + hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable); + hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater); + hoglinControllable = getBoolean("mobs.hoglin.controllable", hoglinControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.hoglin.attributes.max-health", hoglinMaxHealth); + set("mobs.hoglin.attributes.max-health", null); + set("mobs.hoglin.attributes.max_health", oldValue); + } + hoglinMaxHealth = getDouble("mobs.hoglin.attributes.max_health", hoglinMaxHealth); + hoglinBreedingTicks = getInt("mobs.hoglin.breeding-delay-ticks", hoglinBreedingTicks); + hoglinTakeDamageFromWater = getBoolean("mobs.hoglin.takes-damage-from-water", hoglinTakeDamageFromWater); + hoglinAlwaysDropExp = getBoolean("mobs.hoglin.always-drop-exp", hoglinAlwaysDropExp); + } + + public boolean horseRidableInWater = false; + public double horseMaxHealthMin = 15.0D; + public double horseMaxHealthMax = 30.0D; + public double horseJumpStrengthMin = 0.4D; + public double horseJumpStrengthMax = 1.0D; + public double horseMovementSpeedMin = 0.1125D; + public double horseMovementSpeedMax = 0.3375D; + public int horseBreedingTicks = 6000; + public boolean horseTakeDamageFromWater = false; + public boolean horseAlwaysDropExp = false; + private void horseSettings() { + horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.horse.attributes.max-health.min", horseMaxHealthMin); + double oldMax = getDouble("mobs.horse.attributes.max-health.max", horseMaxHealthMax); + set("mobs.horse.attributes.max-health", null); + set("mobs.horse.attributes.max_health.min", oldMin); + set("mobs.horse.attributes.max_health.max", oldMax); + } + horseMaxHealthMin = getDouble("mobs.horse.attributes.max_health.min", horseMaxHealthMin); + horseMaxHealthMax = getDouble("mobs.horse.attributes.max_health.max", horseMaxHealthMax); + horseJumpStrengthMin = getDouble("mobs.horse.attributes.jump_strength.min", horseJumpStrengthMin); + horseJumpStrengthMax = getDouble("mobs.horse.attributes.jump_strength.max", horseJumpStrengthMax); + horseMovementSpeedMin = getDouble("mobs.horse.attributes.movement_speed.min", horseMovementSpeedMin); + horseMovementSpeedMax = getDouble("mobs.horse.attributes.movement_speed.max", horseMovementSpeedMax); + horseBreedingTicks = getInt("mobs.horse.breeding-delay-ticks", horseBreedingTicks); + horseTakeDamageFromWater = getBoolean("mobs.horse.takes-damage-from-water", horseTakeDamageFromWater); + horseAlwaysDropExp = getBoolean("mobs.horse.always-drop-exp", horseAlwaysDropExp); + } + + public boolean huskRidable = false; + public boolean huskRidableInWater = true; + public boolean huskControllable = true; + public double huskMaxHealth = 20.0D; + public double huskSpawnReinforcements = 0.1D; + public boolean huskJockeyOnlyBaby = true; + public double huskJockeyChance = 0.05D; + public boolean huskJockeyTryExistingChickens = true; + public boolean huskTakeDamageFromWater = false; + public boolean huskAlwaysDropExp = false; + private void huskSettings() { + huskRidable = getBoolean("mobs.husk.ridable", huskRidable); + huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater); + huskControllable = getBoolean("mobs.husk.controllable", huskControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.husk.attributes.max-health", huskMaxHealth); + set("mobs.husk.attributes.max-health", null); + set("mobs.husk.attributes.max_health", oldValue); + } + huskMaxHealth = getDouble("mobs.husk.attributes.max_health", huskMaxHealth); + huskSpawnReinforcements = getDouble("mobs.husk.attributes.spawn_reinforcements", huskSpawnReinforcements); + huskJockeyOnlyBaby = getBoolean("mobs.husk.jockey.only-babies", huskJockeyOnlyBaby); + huskJockeyChance = getDouble("mobs.husk.jockey.chance", huskJockeyChance); + huskJockeyTryExistingChickens = getBoolean("mobs.husk.jockey.try-existing-chickens", huskJockeyTryExistingChickens); + huskTakeDamageFromWater = getBoolean("mobs.husk.takes-damage-from-water", huskTakeDamageFromWater); + huskAlwaysDropExp = getBoolean("mobs.husk.always-drop-exp", huskAlwaysDropExp); + } + + public boolean illusionerRidable = false; + public boolean illusionerRidableInWater = true; + public boolean illusionerControllable = true; + public double illusionerMovementSpeed = 0.5D; + public double illusionerFollowRange = 18.0D; + public double illusionerMaxHealth = 32.0D; + public boolean illusionerTakeDamageFromWater = false; + public boolean illusionerAlwaysDropExp = false; + private void illusionerSettings() { + illusionerRidable = getBoolean("mobs.illusioner.ridable", illusionerRidable); + illusionerRidableInWater = getBoolean("mobs.illusioner.ridable-in-water", illusionerRidableInWater); + illusionerControllable = getBoolean("mobs.illusioner.controllable", illusionerControllable); + illusionerMovementSpeed = getDouble("mobs.illusioner.movement-speed", illusionerMovementSpeed); + illusionerFollowRange = getDouble("mobs.illusioner.follow-range", illusionerFollowRange); + if (PurpurConfig.version < 8) { + double oldValue = getDouble("mobs.illusioner.max-health", illusionerMaxHealth); + set("mobs.illusioner.max-health", null); + set("mobs.illusioner.attributes.max_health", oldValue); + } else if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth); + set("mobs.illusioner.attributes.max-health", null); + set("mobs.illusioner.attributes.max_health", oldValue); + } + illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max_health", illusionerMaxHealth); + illusionerTakeDamageFromWater = getBoolean("mobs.illusioner.takes-damage-from-water", illusionerTakeDamageFromWater); + illusionerAlwaysDropExp = getBoolean("mobs.illusioner.always-drop-exp", illusionerAlwaysDropExp); + } + + public boolean ironGolemRidable = false; + public boolean ironGolemRidableInWater = true; + public boolean ironGolemControllable = true; + public boolean ironGolemCanSwim = false; + public double ironGolemMaxHealth = 100.0D; + public boolean ironGolemTakeDamageFromWater = false; + public boolean ironGolemPoppyCalm = false; + public boolean ironGolemHealCalm = false; + public boolean ironGolemAlwaysDropExp = false; + private void ironGolemSettings() { + ironGolemRidable = getBoolean("mobs.iron_golem.ridable", ironGolemRidable); + ironGolemRidableInWater = getBoolean("mobs.iron_golem.ridable-in-water", ironGolemRidableInWater); + ironGolemControllable = getBoolean("mobs.iron_golem.controllable", ironGolemControllable); + ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.iron_golem.attributes.max-health", ironGolemMaxHealth); + set("mobs.iron_golem.attributes.max-health", null); + set("mobs.iron_golem.attributes.max_health", oldValue); + } + ironGolemMaxHealth = getDouble("mobs.iron_golem.attributes.max_health", ironGolemMaxHealth); + ironGolemTakeDamageFromWater = getBoolean("mobs.iron_golem.takes-damage-from-water", ironGolemTakeDamageFromWater); + ironGolemPoppyCalm = getBoolean("mobs.iron_golem.poppy-calms-anger", ironGolemPoppyCalm); + ironGolemHealCalm = getBoolean("mobs.iron_golem.healing-calms-anger", ironGolemHealCalm); + ironGolemAlwaysDropExp = getBoolean("mobs.iron_golem.always-drop-exp", ironGolemAlwaysDropExp); + } + + public boolean llamaRidable = false; + public boolean llamaRidableInWater = false; + public boolean llamaControllable = true; + public double llamaMaxHealthMin = 15.0D; + public double llamaMaxHealthMax = 30.0D; + public double llamaJumpStrengthMin = 0.5D; + public double llamaJumpStrengthMax = 0.5D; + public double llamaMovementSpeedMin = 0.175D; + public double llamaMovementSpeedMax = 0.175D; + public int llamaBreedingTicks = 6000; + public boolean llamaTakeDamageFromWater = false; + public boolean llamaJoinCaravans = true; + public boolean llamaAlwaysDropExp = false; + private void llamaSettings() { + llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable); + llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater); + llamaControllable = getBoolean("mobs.llama.controllable", llamaControllable); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.llama.attributes.max-health.min", llamaMaxHealthMin); + double oldMax = getDouble("mobs.llama.attributes.max-health.max", llamaMaxHealthMax); + set("mobs.llama.attributes.max-health", null); + set("mobs.llama.attributes.max_health.min", oldMin); + set("mobs.llama.attributes.max_health.max", oldMax); + } + llamaMaxHealthMin = getDouble("mobs.llama.attributes.max_health.min", llamaMaxHealthMin); + llamaMaxHealthMax = getDouble("mobs.llama.attributes.max_health.max", llamaMaxHealthMax); + llamaJumpStrengthMin = getDouble("mobs.llama.attributes.jump_strength.min", llamaJumpStrengthMin); + llamaJumpStrengthMax = getDouble("mobs.llama.attributes.jump_strength.max", llamaJumpStrengthMax); + llamaMovementSpeedMin = getDouble("mobs.llama.attributes.movement_speed.min", llamaMovementSpeedMin); + llamaMovementSpeedMax = getDouble("mobs.llama.attributes.movement_speed.max", llamaMovementSpeedMax); + llamaBreedingTicks = getInt("mobs.llama.breeding-delay-ticks", llamaBreedingTicks); + llamaTakeDamageFromWater = getBoolean("mobs.llama.takes-damage-from-water", llamaTakeDamageFromWater); + llamaJoinCaravans = getBoolean("mobs.llama.join-caravans", llamaJoinCaravans); + llamaAlwaysDropExp = getBoolean("mobs.llama.always-drop-exp", llamaAlwaysDropExp); + } + + public boolean magmaCubeRidable = false; + public boolean magmaCubeRidableInWater = true; + public boolean magmaCubeControllable = true; + public String magmaCubeMaxHealth = "size * size"; + public String magmaCubeAttackDamage = "size"; + public Map magmaCubeMaxHealthCache = new HashMap<>(); + public Map magmaCubeAttackDamageCache = new HashMap<>(); + public boolean magmaCubeTakeDamageFromWater = false; + public boolean magmaCubeAlwaysDropExp = false; + private void magmaCubeSettings() { + magmaCubeRidable = getBoolean("mobs.magma_cube.ridable", magmaCubeRidable); + magmaCubeRidableInWater = getBoolean("mobs.magma_cube.ridable-in-water", magmaCubeRidableInWater); + magmaCubeControllable = getBoolean("mobs.magma_cube.controllable", magmaCubeControllable); + if (PurpurConfig.version < 10) { + String oldValue = getString("mobs.magma_cube.attributes.max-health", magmaCubeMaxHealth); + set("mobs.magma_cube.attributes.max-health", null); + set("mobs.magma_cube.attributes.max_health", oldValue); + } + magmaCubeMaxHealth = getString("mobs.magma_cube.attributes.max_health", magmaCubeMaxHealth); + magmaCubeAttackDamage = getString("mobs.magma_cube.attributes.attack_damage", magmaCubeAttackDamage); + magmaCubeMaxHealthCache.clear(); + magmaCubeAttackDamageCache.clear(); + magmaCubeTakeDamageFromWater = getBoolean("mobs.magma_cube.takes-damage-from-water", magmaCubeTakeDamageFromWater); + magmaCubeAlwaysDropExp = getBoolean("mobs.magma_cube.always-drop-exp", magmaCubeAlwaysDropExp); + } + + public boolean mooshroomRidable = false; + public boolean mooshroomRidableInWater = true; + public boolean mooshroomControllable = true; + public double mooshroomMaxHealth = 10.0D; + public int mooshroomBreedingTicks = 6000; + public boolean mooshroomTakeDamageFromWater = false; + public boolean mooshroomAlwaysDropExp = false; + private void mooshroomSettings() { + mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable); + mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater); + mooshroomControllable = getBoolean("mobs.mooshroom.controllable", mooshroomControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.mooshroom.attributes.max-health", mooshroomMaxHealth); + set("mobs.mooshroom.attributes.max-health", null); + set("mobs.mooshroom.attributes.max_health", oldValue); + } + mooshroomMaxHealth = getDouble("mobs.mooshroom.attributes.max_health", mooshroomMaxHealth); + mooshroomBreedingTicks = getInt("mobs.mooshroom.breeding-delay-ticks", mooshroomBreedingTicks); + mooshroomTakeDamageFromWater = getBoolean("mobs.mooshroom.takes-damage-from-water", mooshroomTakeDamageFromWater); + mooshroomAlwaysDropExp = getBoolean("mobs.mooshroom.always-drop-exp", mooshroomAlwaysDropExp); + } + + public boolean muleRidableInWater = false; + public double muleMaxHealthMin = 15.0D; + public double muleMaxHealthMax = 30.0D; + public double muleJumpStrengthMin = 0.5D; + public double muleJumpStrengthMax = 0.5D; + public double muleMovementSpeedMin = 0.175D; + public double muleMovementSpeedMax = 0.175D; + public int muleBreedingTicks = 6000; + public boolean muleTakeDamageFromWater = false; + public boolean muleAlwaysDropExp = false; + private void muleSettings() { + muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater); + if (PurpurConfig.version < 10) { + double oldMin = getDouble("mobs.mule.attributes.max-health.min", muleMaxHealthMin); + double oldMax = getDouble("mobs.mule.attributes.max-health.max", muleMaxHealthMax); + set("mobs.mule.attributes.max-health", null); + set("mobs.mule.attributes.max_health.min", oldMin); + set("mobs.mule.attributes.max_health.max", oldMax); + } + muleMaxHealthMin = getDouble("mobs.mule.attributes.max_health.min", muleMaxHealthMin); + muleMaxHealthMax = getDouble("mobs.mule.attributes.max_health.max", muleMaxHealthMax); + muleJumpStrengthMin = getDouble("mobs.mule.attributes.jump_strength.min", muleJumpStrengthMin); + muleJumpStrengthMax = getDouble("mobs.mule.attributes.jump_strength.max", muleJumpStrengthMax); + muleMovementSpeedMin = getDouble("mobs.mule.attributes.movement_speed.min", muleMovementSpeedMin); + muleMovementSpeedMax = getDouble("mobs.mule.attributes.movement_speed.max", muleMovementSpeedMax); + muleBreedingTicks = getInt("mobs.mule.breeding-delay-ticks", muleBreedingTicks); + muleTakeDamageFromWater = getBoolean("mobs.mule.takes-damage-from-water", muleTakeDamageFromWater); + muleAlwaysDropExp = getBoolean("mobs.mule.always-drop-exp", muleAlwaysDropExp); + } + + public boolean ocelotRidable = false; + public boolean ocelotRidableInWater = true; + public boolean ocelotControllable = true; + public double ocelotMaxHealth = 10.0D; + public int ocelotBreedingTicks = 6000; + public boolean ocelotTakeDamageFromWater = false; + public boolean ocelotAlwaysDropExp = false; + public boolean ocelotSpawnUnderSeaLevel = false; + private void ocelotSettings() { + ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable); + ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater); + ocelotControllable = getBoolean("mobs.ocelot.controllable", ocelotControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.ocelot.attributes.max-health", ocelotMaxHealth); + set("mobs.ocelot.attributes.max-health", null); + set("mobs.ocelot.attributes.max_health", oldValue); + } + ocelotMaxHealth = getDouble("mobs.ocelot.attributes.max_health", ocelotMaxHealth); + ocelotBreedingTicks = getInt("mobs.ocelot.breeding-delay-ticks", ocelotBreedingTicks); + ocelotTakeDamageFromWater = getBoolean("mobs.ocelot.takes-damage-from-water", ocelotTakeDamageFromWater); + ocelotAlwaysDropExp = getBoolean("mobs.ocelot.always-drop-exp", ocelotAlwaysDropExp); + ocelotSpawnUnderSeaLevel = getBoolean("mobs.ocelot.spawn-below-sea-level", ocelotSpawnUnderSeaLevel); + } + + public boolean pandaRidable = false; + public boolean pandaRidableInWater = true; + public boolean pandaControllable = true; + public double pandaMaxHealth = 20.0D; + public int pandaBreedingTicks = 6000; + public boolean pandaTakeDamageFromWater = false; + public boolean pandaAlwaysDropExp = false; + private void pandaSettings() { + pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable); + pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater); + pandaControllable = getBoolean("mobs.panda.controllable", pandaControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.panda.attributes.max-health", pandaMaxHealth); + set("mobs.panda.attributes.max-health", null); + set("mobs.panda.attributes.max_health", oldValue); + } + pandaMaxHealth = getDouble("mobs.panda.attributes.max_health", pandaMaxHealth); + pandaBreedingTicks = getInt("mobs.panda.breeding-delay-ticks", pandaBreedingTicks); + pandaTakeDamageFromWater = getBoolean("mobs.panda.takes-damage-from-water", pandaTakeDamageFromWater); + pandaAlwaysDropExp = getBoolean("mobs.panda.always-drop-exp", pandaAlwaysDropExp); + } + + public boolean parrotRidable = false; + public boolean parrotRidableInWater = true; + public boolean parrotControllable = true; + public double parrotMaxY = 320D; + public double parrotMaxHealth = 6.0D; + public boolean parrotTakeDamageFromWater = false; + public boolean parrotBreedable = false; + public boolean parrotAlwaysDropExp = false; + private void parrotSettings() { + parrotRidable = getBoolean("mobs.parrot.ridable", parrotRidable); + parrotRidableInWater = getBoolean("mobs.parrot.ridable-in-water", parrotRidableInWater); + parrotControllable = getBoolean("mobs.parrot.controllable", parrotControllable); + parrotMaxY = getDouble("mobs.parrot.ridable-max-y", parrotMaxY); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.parrot.attributes.max-health", parrotMaxHealth); + set("mobs.parrot.attributes.max-health", null); + set("mobs.parrot.attributes.max_health", oldValue); + } + parrotMaxHealth = getDouble("mobs.parrot.attributes.max_health", parrotMaxHealth); + parrotTakeDamageFromWater = getBoolean("mobs.parrot.takes-damage-from-water", parrotTakeDamageFromWater); + parrotBreedable = getBoolean("mobs.parrot.can-breed", parrotBreedable); + parrotAlwaysDropExp = getBoolean("mobs.parrot.always-drop-exp", parrotAlwaysDropExp); + } + + public boolean phantomRidable = false; + public boolean phantomRidableInWater = true; + public boolean phantomControllable = true; + public double phantomMaxY = 320D; + public float phantomFlameDamage = 1.0F; + public int phantomFlameFireTime = 8; + public boolean phantomAllowGriefing = false; + public String phantomMaxHealth = "20.0"; + public String phantomAttackDamage = "6 + size"; + public Map phantomMaxHealthCache = new HashMap<>(); + public Map phantomAttackDamageCache = new HashMap<>(); + public double phantomAttackedByCrystalRadius = 0.0D; + public float phantomAttackedByCrystalDamage = 1.0F; + public double phantomOrbitCrystalRadius = 0.0D; + public int phantomSpawnMinSkyDarkness = 5; + public boolean phantomSpawnOnlyAboveSeaLevel = true; + public boolean phantomSpawnOnlyWithVisibleSky = true; + public double phantomSpawnLocalDifficultyChance = 3.0D; + public int phantomSpawnMinPerAttempt = 1; + public int phantomSpawnMaxPerAttempt = -1; + public int phantomBurnInLight = 0; + public boolean phantomIgnorePlayersWithTorch = false; + public boolean phantomBurnInDaylight = true; + public boolean phantomFlamesOnSwoop = false; + public boolean phantomTakeDamageFromWater = false; + public boolean phantomAlwaysDropExp = false; + public int phantomMinSize = 0; + public int phantomMaxSize = 0; + private void phantomSettings() { + phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable); + phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater); + phantomControllable = getBoolean("mobs.phantom.controllable", phantomControllable); + phantomMaxY = getDouble("mobs.phantom.ridable-max-y", phantomMaxY); + phantomFlameDamage = (float) getDouble("mobs.phantom.flames.damage", phantomFlameDamage); + phantomFlameFireTime = getInt("mobs.phantom.flames.fire-time", phantomFlameFireTime); + phantomAllowGriefing = getBoolean("mobs.phantom.allow-griefing", phantomAllowGriefing); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.phantom.attributes.max-health", Double.parseDouble(phantomMaxHealth)); + set("mobs.phantom.attributes.max-health", null); + set("mobs.phantom.attributes.max_health", String.valueOf(oldValue)); + } + if (PurpurConfig.version < 25) { + double oldValue = getDouble("mobs.phantom.attributes.max_health", Double.parseDouble(phantomMaxHealth)); + set("mobs.phantom.attributes.max_health", String.valueOf(oldValue)); + } + phantomMaxHealth = getString("mobs.phantom.attributes.max_health", phantomMaxHealth); + phantomAttackDamage = getString("mobs.phantom.attributes.attack_damage", phantomAttackDamage); + phantomMaxHealthCache.clear(); + phantomAttackDamageCache.clear(); + phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius); + phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage); + phantomOrbitCrystalRadius = getDouble("mobs.phantom.orbit-crystal-radius", phantomOrbitCrystalRadius); + phantomSpawnMinSkyDarkness = getInt("mobs.phantom.spawn.min-sky-darkness", phantomSpawnMinSkyDarkness); + phantomSpawnOnlyAboveSeaLevel = getBoolean("mobs.phantom.spawn.only-above-sea-level", phantomSpawnOnlyAboveSeaLevel); + phantomSpawnOnlyWithVisibleSky = getBoolean("mobs.phantom.spawn.only-with-visible-sky", phantomSpawnOnlyWithVisibleSky); + phantomSpawnLocalDifficultyChance = getDouble("mobs.phantom.spawn.local-difficulty-chance", phantomSpawnLocalDifficultyChance); + phantomSpawnMinPerAttempt = getInt("mobs.phantom.spawn.per-attempt.min", phantomSpawnMinPerAttempt); + phantomSpawnMaxPerAttempt = getInt("mobs.phantom.spawn.per-attempt.max", phantomSpawnMaxPerAttempt); + phantomBurnInLight = getInt("mobs.phantom.burn-in-light", phantomBurnInLight); + phantomBurnInDaylight = getBoolean("mobs.phantom.burn-in-daylight", phantomBurnInDaylight); + phantomIgnorePlayersWithTorch = getBoolean("mobs.phantom.ignore-players-with-torch", phantomIgnorePlayersWithTorch); + phantomFlamesOnSwoop = getBoolean("mobs.phantom.flames-on-swoop", phantomFlamesOnSwoop); + phantomTakeDamageFromWater = getBoolean("mobs.phantom.takes-damage-from-water", phantomTakeDamageFromWater); + phantomAlwaysDropExp = getBoolean("mobs.phantom.always-drop-exp", phantomAlwaysDropExp); + phantomMinSize = Mth.clamp(getInt("mobs.phantom.size.min", phantomMinSize), 0, 64); + phantomMaxSize = Mth.clamp(getInt("mobs.phantom.size.max", phantomMaxSize), 0, 64); + if (phantomMinSize > phantomMaxSize) { + phantomMinSize = phantomMinSize ^ phantomMaxSize; + phantomMaxSize = phantomMinSize ^ phantomMaxSize; + phantomMinSize = phantomMinSize ^ phantomMaxSize; + } + } + + public boolean pigRidable = false; + public boolean pigRidableInWater = false; + public boolean pigControllable = true; + public double pigMaxHealth = 10.0D; + public boolean pigGiveSaddleBack = false; + public int pigBreedingTicks = 6000; + public boolean pigTakeDamageFromWater = false; + public boolean pigAlwaysDropExp = false; + private void pigSettings() { + pigRidable = getBoolean("mobs.pig.ridable", pigRidable); + pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater); + pigControllable = getBoolean("mobs.pig.controllable", pigControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.pig.attributes.max-health", pigMaxHealth); + set("mobs.pig.attributes.max-health", null); + set("mobs.pig.attributes.max_health", oldValue); + } + pigMaxHealth = getDouble("mobs.pig.attributes.max_health", pigMaxHealth); + pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack); + pigBreedingTicks = getInt("mobs.pig.breeding-delay-ticks", pigBreedingTicks); + pigTakeDamageFromWater = getBoolean("mobs.pig.takes-damage-from-water", pigTakeDamageFromWater); + pigAlwaysDropExp = getBoolean("mobs.pig.always-drop-exp", pigAlwaysDropExp); + } + + public boolean piglinRidable = false; + public boolean piglinRidableInWater = true; + public boolean piglinControllable = true; + public double piglinMaxHealth = 16.0D; + public boolean piglinBypassMobGriefing = false; + public boolean piglinTakeDamageFromWater = false; + public int piglinPortalSpawnModifier = 2000; + public boolean piglinAlwaysDropExp = false; + public double piglinHeadVisibilityPercent = 0.5D; + public boolean piglinIgnoresArmorWithGoldTrim = false; + private void piglinSettings() { + piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable); + piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater); + piglinControllable = getBoolean("mobs.piglin.controllable", piglinControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.piglin.attributes.max-health", piglinMaxHealth); + set("mobs.piglin.attributes.max-health", null); + set("mobs.piglin.attributes.max_health", oldValue); + } + piglinMaxHealth = getDouble("mobs.piglin.attributes.max_health", piglinMaxHealth); + piglinBypassMobGriefing = getBoolean("mobs.piglin.bypass-mob-griefing", piglinBypassMobGriefing); + piglinTakeDamageFromWater = getBoolean("mobs.piglin.takes-damage-from-water", piglinTakeDamageFromWater); + piglinPortalSpawnModifier = getInt("mobs.piglin.portal-spawn-modifier", piglinPortalSpawnModifier); + piglinAlwaysDropExp = getBoolean("mobs.piglin.always-drop-exp", piglinAlwaysDropExp); + piglinHeadVisibilityPercent = getDouble("mobs.piglin.head-visibility-percent", piglinHeadVisibilityPercent); + piglinIgnoresArmorWithGoldTrim = getBoolean("mobs.piglin.ignores-armor-with-gold-trim", piglinIgnoresArmorWithGoldTrim); + } + + public boolean piglinBruteRidable = false; + public boolean piglinBruteRidableInWater = true; + public boolean piglinBruteControllable = true; + public double piglinBruteMaxHealth = 50.0D; + public boolean piglinBruteTakeDamageFromWater = false; + public boolean piglinBruteAlwaysDropExp = false; + private void piglinBruteSettings() { + piglinBruteRidable = getBoolean("mobs.piglin_brute.ridable", piglinBruteRidable); + piglinBruteRidableInWater = getBoolean("mobs.piglin_brute.ridable-in-water", piglinBruteRidableInWater); + piglinBruteControllable = getBoolean("mobs.piglin_brute.controllable", piglinBruteControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.piglin_brute.attributes.max-health", piglinBruteMaxHealth); + set("mobs.piglin_brute.attributes.max-health", null); + set("mobs.piglin_brute.attributes.max_health", oldValue); + } + piglinBruteMaxHealth = getDouble("mobs.piglin_brute.attributes.max_health", piglinBruteMaxHealth); + piglinBruteTakeDamageFromWater = getBoolean("mobs.piglin_brute.takes-damage-from-water", piglinBruteTakeDamageFromWater); + piglinBruteAlwaysDropExp = getBoolean("mobs.piglin_brute.always-drop-exp", piglinBruteAlwaysDropExp); + } + + public boolean pillagerRidable = false; + public boolean pillagerRidableInWater = true; + public boolean pillagerControllable = true; + public double pillagerMaxHealth = 24.0D; + public boolean pillagerBypassMobGriefing = false; + public boolean pillagerTakeDamageFromWater = false; + public boolean pillagerAlwaysDropExp = false; + private void pillagerSettings() { + pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable); + pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater); + pillagerControllable = getBoolean("mobs.pillager.controllable", pillagerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.pillager.attributes.max-health", pillagerMaxHealth); + set("mobs.pillager.attributes.max-health", null); + set("mobs.pillager.attributes.max_health", oldValue); + } + pillagerMaxHealth = getDouble("mobs.pillager.attributes.max_health", pillagerMaxHealth); + pillagerBypassMobGriefing = getBoolean("mobs.pillager.bypass-mob-griefing", pillagerBypassMobGriefing); + pillagerTakeDamageFromWater = getBoolean("mobs.pillager.takes-damage-from-water", pillagerTakeDamageFromWater); + pillagerAlwaysDropExp = getBoolean("mobs.pillager.always-drop-exp", pillagerAlwaysDropExp); + } + + public boolean polarBearRidable = false; + public boolean polarBearRidableInWater = true; + public boolean polarBearControllable = true; + public double polarBearMaxHealth = 30.0D; + public String polarBearBreedableItemString = ""; + public Item polarBearBreedableItem = null; + public int polarBearBreedingTicks = 6000; + public boolean polarBearTakeDamageFromWater = false; + public boolean polarBearAlwaysDropExp = false; + private void polarBearSettings() { + polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable); + polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater); + polarBearControllable = getBoolean("mobs.polar_bear.controllable", polarBearControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.polar_bear.attributes.max-health", polarBearMaxHealth); + set("mobs.polar_bear.attributes.max-health", null); + set("mobs.polar_bear.attributes.max_health", oldValue); + } + polarBearMaxHealth = getDouble("mobs.polar_bear.attributes.max_health", polarBearMaxHealth); + polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString); + Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(polarBearBreedableItemString)); + if (item != Items.AIR) polarBearBreedableItem = item; + polarBearBreedingTicks = getInt("mobs.polar_bear.breeding-delay-ticks", polarBearBreedingTicks); + polarBearTakeDamageFromWater = getBoolean("mobs.polar_bear.takes-damage-from-water", polarBearTakeDamageFromWater); + polarBearAlwaysDropExp = getBoolean("mobs.polar_bear.always-drop-exp", polarBearAlwaysDropExp); + } + + public boolean pufferfishRidable = false; + public boolean pufferfishControllable = true; + public double pufferfishMaxHealth = 3.0D; + public boolean pufferfishTakeDamageFromWater = false; + public boolean pufferfishAlwaysDropExp = false; + private void pufferfishSettings() { + pufferfishRidable = getBoolean("mobs.pufferfish.ridable", pufferfishRidable); + pufferfishControllable = getBoolean("mobs.pufferfish.controllable", pufferfishControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.pufferfish.attributes.max-health", pufferfishMaxHealth); + set("mobs.pufferfish.attributes.max-health", null); + set("mobs.pufferfish.attributes.max_health", oldValue); + } + pufferfishMaxHealth = getDouble("mobs.pufferfish.attributes.max_health", pufferfishMaxHealth); + pufferfishTakeDamageFromWater = getBoolean("mobs.pufferfish.takes-damage-from-water", pufferfishTakeDamageFromWater); + pufferfishAlwaysDropExp = getBoolean("mobs.pufferfish.always-drop-exp", pufferfishAlwaysDropExp); + } + + public boolean rabbitRidable = false; + public boolean rabbitRidableInWater = true; + public boolean rabbitControllable = true; + public double rabbitMaxHealth = 3.0D; + public double rabbitNaturalToast = 0.0D; + public double rabbitNaturalKiller = 0.0D; + public int rabbitBreedingTicks = 6000; + public boolean rabbitBypassMobGriefing = false; + public boolean rabbitTakeDamageFromWater = false; + public boolean rabbitAlwaysDropExp = false; + private void rabbitSettings() { + rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable); + rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater); + rabbitControllable = getBoolean("mobs.rabbit.controllable", rabbitControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.rabbit.attributes.max-health", rabbitMaxHealth); + set("mobs.rabbit.attributes.max-health", null); + set("mobs.rabbit.attributes.max_health", oldValue); + } + rabbitMaxHealth = getDouble("mobs.rabbit.attributes.max_health", rabbitMaxHealth); + rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast); + rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller); + rabbitBreedingTicks = getInt("mobs.rabbit.breeding-delay-ticks", rabbitBreedingTicks); + rabbitBypassMobGriefing = getBoolean("mobs.rabbit.bypass-mob-griefing", rabbitBypassMobGriefing); + rabbitTakeDamageFromWater = getBoolean("mobs.rabbit.takes-damage-from-water", rabbitTakeDamageFromWater); + rabbitAlwaysDropExp = getBoolean("mobs.rabbit.always-drop-exp", rabbitAlwaysDropExp); + } + + public boolean ravagerRidable = false; + public boolean ravagerRidableInWater = false; + public boolean ravagerControllable = true; + public double ravagerMaxHealth = 100.0D; + public boolean ravagerBypassMobGriefing = false; + public boolean ravagerTakeDamageFromWater = false; + public List ravagerGriefableBlocks = new ArrayList<>(); + public boolean ravagerAlwaysDropExp = false; + private void ravagerSettings() { + ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable); + ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater); + ravagerControllable = getBoolean("mobs.ravager.controllable", ravagerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.ravager.attributes.max-health", ravagerMaxHealth); + set("mobs.ravager.attributes.max-health", null); + set("mobs.ravager.attributes.max_health", oldValue); + } + ravagerMaxHealth = getDouble("mobs.ravager.attributes.max_health", ravagerMaxHealth); + ravagerBypassMobGriefing = getBoolean("mobs.ravager.bypass-mob-griefing", ravagerBypassMobGriefing); + ravagerTakeDamageFromWater = getBoolean("mobs.ravager.takes-damage-from-water", ravagerTakeDamageFromWater); + getList("mobs.ravager.griefable-blocks", new ArrayList(){{ + add("minecraft:oak_leaves"); + add("minecraft:spruce_leaves"); + add("minecraft:birch_leaves"); + add("minecraft:jungle_leaves"); + add("minecraft:acacia_leaves"); + add("minecraft:dark_oak_leaves"); + add("minecraft:beetroots"); + add("minecraft:carrots"); + add("minecraft:potatoes"); + add("minecraft:wheat"); + }}).forEach(key -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key.toString())); + if (!block.defaultBlockState().isAir()) { + ravagerGriefableBlocks.add(block); + } + }); + ravagerAlwaysDropExp = getBoolean("mobs.ravager.always-drop-exp", ravagerAlwaysDropExp); + } + + public boolean salmonRidable = false; + public boolean salmonControllable = true; + public double salmonMaxHealth = 3.0D; + public boolean salmonTakeDamageFromWater = false; + public boolean salmonAlwaysDropExp = false; + private void salmonSettings() { + salmonRidable = getBoolean("mobs.salmon.ridable", salmonRidable); + salmonControllable = getBoolean("mobs.salmon.controllable", salmonControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.salmon.attributes.max-health", salmonMaxHealth); + set("mobs.salmon.attributes.max-health", null); + set("mobs.salmon.attributes.max_health", oldValue); + } + salmonMaxHealth = getDouble("mobs.salmon.attributes.max_health", salmonMaxHealth); + salmonTakeDamageFromWater = getBoolean("mobs.salmon.takes-damage-from-water", salmonTakeDamageFromWater); + salmonAlwaysDropExp = getBoolean("mobs.salmon.always-drop-exp", salmonAlwaysDropExp); + } + + public boolean sheepRidable = false; + public boolean sheepRidableInWater = true; + public boolean sheepControllable = true; + public double sheepMaxHealth = 8.0D; + public int sheepBreedingTicks = 6000; + public boolean sheepBypassMobGriefing = false; + public boolean sheepTakeDamageFromWater = false; + public boolean sheepAlwaysDropExp = false; + 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); + } + + public boolean shulkerRidable = false; + public boolean shulkerRidableInWater = true; + public boolean shulkerControllable = true; + public double shulkerMaxHealth = 30.0D; + public boolean shulkerTakeDamageFromWater = false; + public float shulkerSpawnFromBulletBaseChance = 1.0F; + public boolean shulkerSpawnFromBulletRequireOpenLid = true; + public double shulkerSpawnFromBulletNearbyRange = 8.0D; + public String shulkerSpawnFromBulletNearbyEquation = "(nearby - 1) / 5.0"; + public boolean shulkerSpawnFromBulletRandomColor = false; + public boolean shulkerChangeColorWithDye = false; + public boolean shulkerAlwaysDropExp = false; + private void shulkerSettings() { + shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable); + shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater); + shulkerControllable = getBoolean("mobs.shulker.controllable", shulkerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.shulker.attributes.max-health", shulkerMaxHealth); + set("mobs.shulker.attributes.max-health", null); + set("mobs.shulker.attributes.max_health", oldValue); + } + shulkerMaxHealth = getDouble("mobs.shulker.attributes.max_health", shulkerMaxHealth); + shulkerTakeDamageFromWater = getBoolean("mobs.shulker.takes-damage-from-water", shulkerTakeDamageFromWater); + shulkerSpawnFromBulletBaseChance = (float) getDouble("mobs.shulker.spawn-from-bullet.base-chance", shulkerSpawnFromBulletBaseChance); + shulkerSpawnFromBulletRequireOpenLid = getBoolean("mobs.shulker.spawn-from-bullet.require-open-lid", shulkerSpawnFromBulletRequireOpenLid); + shulkerSpawnFromBulletNearbyRange = getDouble("mobs.shulker.spawn-from-bullet.nearby-range", shulkerSpawnFromBulletNearbyRange); + shulkerSpawnFromBulletNearbyEquation = getString("mobs.shulker.spawn-from-bullet.nearby-equation", shulkerSpawnFromBulletNearbyEquation); + shulkerSpawnFromBulletRandomColor = getBoolean("mobs.shulker.spawn-from-bullet.random-color", shulkerSpawnFromBulletRandomColor); + shulkerChangeColorWithDye = getBoolean("mobs.shulker.change-color-with-dye", shulkerChangeColorWithDye); + shulkerAlwaysDropExp = getBoolean("mobs.shulker.always-drop-exp", shulkerAlwaysDropExp); + } + + public boolean silverfishRidable = false; + public boolean silverfishRidableInWater = true; + public boolean silverfishControllable = true; + public double silverfishMaxHealth = 8.0D; + public boolean silverfishBypassMobGriefing = false; + public boolean silverfishTakeDamageFromWater = false; + public boolean silverfishAlwaysDropExp = false; + private void silverfishSettings() { + silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable); + silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater); + silverfishControllable = getBoolean("mobs.silverfish.controllable", silverfishControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.silverfish.attributes.max-health", silverfishMaxHealth); + set("mobs.silverfish.attributes.max-health", null); + set("mobs.silverfish.attributes.max_health", oldValue); + } + silverfishMaxHealth = getDouble("mobs.silverfish.attributes.max_health", silverfishMaxHealth); + silverfishBypassMobGriefing = getBoolean("mobs.silverfish.bypass-mob-griefing", silverfishBypassMobGriefing); + silverfishTakeDamageFromWater = getBoolean("mobs.silverfish.takes-damage-from-water", silverfishTakeDamageFromWater); + silverfishAlwaysDropExp = getBoolean("mobs.silverfish.always-drop-exp", silverfishAlwaysDropExp); + } + + public boolean skeletonRidable = false; + public boolean skeletonRidableInWater = true; + public boolean skeletonControllable = true; + public double skeletonMaxHealth = 20.0D; + public boolean skeletonTakeDamageFromWater = false; + public boolean skeletonAlwaysDropExp = false; + public double skeletonHeadVisibilityPercent = 0.5D; + public int skeletonFeedWitherRoses = 0; + public String skeletonBowAccuracy = "14 - difficulty * 4"; + public Map skeletonBowAccuracyMap = new HashMap<>(); + private void skeletonSettings() { + skeletonRidable = getBoolean("mobs.skeleton.ridable", skeletonRidable); + skeletonRidableInWater = getBoolean("mobs.skeleton.ridable-in-water", skeletonRidableInWater); + skeletonControllable = getBoolean("mobs.skeleton.controllable", skeletonControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.skeleton.attributes.max-health", skeletonMaxHealth); + set("mobs.skeleton.attributes.max-health", null); + set("mobs.skeleton.attributes.max_health", oldValue); + } + skeletonMaxHealth = getDouble("mobs.skeleton.attributes.max_health", skeletonMaxHealth); + skeletonTakeDamageFromWater = getBoolean("mobs.skeleton.takes-damage-from-water", skeletonTakeDamageFromWater); + skeletonAlwaysDropExp = getBoolean("mobs.skeleton.always-drop-exp", skeletonAlwaysDropExp); + skeletonHeadVisibilityPercent = getDouble("mobs.skeleton.head-visibility-percent", skeletonHeadVisibilityPercent); + skeletonFeedWitherRoses = getInt("mobs.skeleton.feed-wither-roses", skeletonFeedWitherRoses); + final String defaultSkeletonBowAccuracy = skeletonBowAccuracy; + skeletonBowAccuracy = getString("mobs.skeleton.bow-accuracy", skeletonBowAccuracy); + for (int i = 1; i < 4; i++) { + final float divergence; + try { + divergence = ((Number) Entity.scriptEngine.eval("let difficulty = " + i + "; " + skeletonBowAccuracy)).floatValue(); + } catch (javax.script.ScriptException e) { + e.printStackTrace(); + break; + } + skeletonBowAccuracyMap.put(i, divergence); + } + } + + public boolean skeletonHorseRidable = false; + public boolean skeletonHorseRidableInWater = true; + public boolean skeletonHorseCanSwim = false; + public double skeletonHorseMaxHealthMin = 15.0D; + public double skeletonHorseMaxHealthMax = 15.0D; + public double skeletonHorseJumpStrengthMin = 0.4D; + public double skeletonHorseJumpStrengthMax = 1.0D; + public double skeletonHorseMovementSpeedMin = 0.2D; + public double skeletonHorseMovementSpeedMax = 0.2D; + public boolean skeletonHorseTakeDamageFromWater = false; + public boolean skeletonHorseAlwaysDropExp = false; + private void skeletonHorseSettings() { + skeletonHorseRidable = getBoolean("mobs.skeleton_horse.ridable", skeletonHorseRidable); + skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater); + skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.skeleton_horse.attributes.max-health", skeletonHorseMaxHealthMin); + set("mobs.skeleton_horse.attributes.max-health", null); + set("mobs.skeleton_horse.attributes.max_health.min", oldValue); + set("mobs.skeleton_horse.attributes.max_health.max", oldValue); + } + skeletonHorseMaxHealthMin = getDouble("mobs.skeleton_horse.attributes.max_health.min", skeletonHorseMaxHealthMin); + skeletonHorseMaxHealthMax = getDouble("mobs.skeleton_horse.attributes.max_health.max", skeletonHorseMaxHealthMax); + skeletonHorseJumpStrengthMin = getDouble("mobs.skeleton_horse.attributes.jump_strength.min", skeletonHorseJumpStrengthMin); + skeletonHorseJumpStrengthMax = getDouble("mobs.skeleton_horse.attributes.jump_strength.max", skeletonHorseJumpStrengthMax); + skeletonHorseMovementSpeedMin = getDouble("mobs.skeleton_horse.attributes.movement_speed.min", skeletonHorseMovementSpeedMin); + skeletonHorseMovementSpeedMax = getDouble("mobs.skeleton_horse.attributes.movement_speed.max", skeletonHorseMovementSpeedMax); + skeletonHorseTakeDamageFromWater = getBoolean("mobs.skeleton_horse.takes-damage-from-water", skeletonHorseTakeDamageFromWater); + skeletonHorseAlwaysDropExp = getBoolean("mobs.skeleton_horse.always-drop-exp", skeletonHorseAlwaysDropExp); + } + + public boolean slimeRidable = false; + public boolean slimeRidableInWater = true; + public boolean slimeControllable = true; + public String slimeMaxHealth = "size * size"; + public String slimeAttackDamage = "size"; + public Map slimeMaxHealthCache = new HashMap<>(); + public Map slimeAttackDamageCache = new HashMap<>(); + public boolean slimeTakeDamageFromWater = false; + public boolean slimeAlwaysDropExp = false; + private void slimeSettings() { + slimeRidable = getBoolean("mobs.slime.ridable", slimeRidable); + slimeRidableInWater = getBoolean("mobs.slime.ridable-in-water", slimeRidableInWater); + slimeControllable = getBoolean("mobs.slime.controllable", slimeControllable); + if (PurpurConfig.version < 10) { + String oldValue = getString("mobs.slime.attributes.max-health", slimeMaxHealth); + set("mobs.slime.attributes.max-health", null); + set("mobs.slime.attributes.max_health", oldValue); + } + slimeMaxHealth = getString("mobs.slime.attributes.max_health", slimeMaxHealth); + slimeAttackDamage = getString("mobs.slime.attributes.attack_damage", slimeAttackDamage); + slimeMaxHealthCache.clear(); + slimeAttackDamageCache.clear(); + slimeTakeDamageFromWater = getBoolean("mobs.slime.takes-damage-from-water", slimeTakeDamageFromWater); + slimeAlwaysDropExp = getBoolean("mobs.slime.always-drop-exp", slimeAlwaysDropExp); + } + + public boolean snowGolemRidable = false; + public boolean snowGolemRidableInWater = true; + public boolean snowGolemControllable = true; + public boolean snowGolemLeaveTrailWhenRidden = false; + public double snowGolemMaxHealth = 4.0D; + public 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); + 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 villagerLobotomizeWaitUntilTradeLocked = false; + 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); + villagerLobotomizeWaitUntilTradeLocked = getBoolean("mobs.villager.lobotomize.wait-until-trade-locked", villagerLobotomizeWaitUntilTradeLocked); + villagerDisplayTradeItem = getBoolean("mobs.villager.display-trade-item", villagerDisplayTradeItem); + villagerSpawnIronGolemRadius = getInt("mobs.villager.spawn-iron-golem.radius", villagerSpawnIronGolemRadius); + villagerSpawnIronGolemLimit = getInt("mobs.villager.spawn-iron-golem.limit", villagerSpawnIronGolemLimit); + } + + 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 zombieHorseRidable = false; + public boolean zombieHorseRidableInWater = false; + public boolean zombieHorseCanSwim = false; + public double zombieHorseMaxHealthMin = 15.0D; + public double zombieHorseMaxHealthMax = 15.0D; + public double zombieHorseJumpStrengthMin = 0.4D; + public double zombieHorseJumpStrengthMax = 1.0D; + public double zombieHorseMovementSpeedMin = 0.2D; + public double zombieHorseMovementSpeedMax = 0.2D; + public double zombieHorseSpawnChance = 0.0D; + public boolean zombieHorseTakeDamageFromWater = false; + public boolean zombieHorseAlwaysDropExp = false; + private void zombieHorseSettings() { + zombieHorseRidable = getBoolean("mobs.zombie_horse.ridable", zombieHorseRidable); + zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater); + zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zombie_horse.attributes.max-health", zombieHorseMaxHealthMin); + set("mobs.zombie_horse.attributes.max-health", null); + set("mobs.zombie_horse.attributes.max_health.min", oldValue); + set("mobs.zombie_horse.attributes.max_health.max", oldValue); + } + zombieHorseMaxHealthMin = getDouble("mobs.zombie_horse.attributes.max_health.min", zombieHorseMaxHealthMin); + zombieHorseMaxHealthMax = getDouble("mobs.zombie_horse.attributes.max_health.max", zombieHorseMaxHealthMax); + zombieHorseJumpStrengthMin = getDouble("mobs.zombie_horse.attributes.jump_strength.min", zombieHorseJumpStrengthMin); + zombieHorseJumpStrengthMax = getDouble("mobs.zombie_horse.attributes.jump_strength.max", zombieHorseJumpStrengthMax); + zombieHorseMovementSpeedMin = getDouble("mobs.zombie_horse.attributes.movement_speed.min", zombieHorseMovementSpeedMin); + zombieHorseMovementSpeedMax = getDouble("mobs.zombie_horse.attributes.movement_speed.max", zombieHorseMovementSpeedMax); + zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance); + zombieHorseTakeDamageFromWater = getBoolean("mobs.zombie_horse.takes-damage-from-water", zombieHorseTakeDamageFromWater); + zombieHorseAlwaysDropExp = getBoolean("mobs.zombie_horse.always-drop-exp", zombieHorseAlwaysDropExp); + } + + public boolean zombieVillagerRidable = false; + public boolean zombieVillagerRidableInWater = true; + public boolean zombieVillagerControllable = true; + public double zombieVillagerMaxHealth = 20.0D; + public double zombieVillagerSpawnReinforcements = 0.1D; + public boolean zombieVillagerJockeyOnlyBaby = true; + public double zombieVillagerJockeyChance = 0.05D; + public boolean zombieVillagerJockeyTryExistingChickens = true; + public boolean zombieVillagerTakeDamageFromWater = false; + public int zombieVillagerCuringTimeMin = 3600; + public int zombieVillagerCuringTimeMax = 6000; + public boolean zombieVillagerCureEnabled = true; + public boolean zombieVillagerAlwaysDropExp = false; + private void zombieVillagerSettings() { + zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable); + zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater); + zombieVillagerControllable = getBoolean("mobs.zombie_villager.controllable", zombieVillagerControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zombie_villager.attributes.max-health", zombieVillagerMaxHealth); + set("mobs.zombie_villager.attributes.max-health", null); + set("mobs.zombie_villager.attributes.max_health", oldValue); + } + zombieVillagerMaxHealth = getDouble("mobs.zombie_villager.attributes.max_health", zombieVillagerMaxHealth); + zombieVillagerSpawnReinforcements = getDouble("mobs.zombie_villager.attributes.spawn_reinforcements", zombieVillagerSpawnReinforcements); + zombieVillagerJockeyOnlyBaby = getBoolean("mobs.zombie_villager.jockey.only-babies", zombieVillagerJockeyOnlyBaby); + zombieVillagerJockeyChance = getDouble("mobs.zombie_villager.jockey.chance", zombieVillagerJockeyChance); + zombieVillagerJockeyTryExistingChickens = getBoolean("mobs.zombie_villager.jockey.try-existing-chickens", zombieVillagerJockeyTryExistingChickens); + zombieVillagerTakeDamageFromWater = getBoolean("mobs.zombie_villager.takes-damage-from-water", zombieVillagerTakeDamageFromWater); + zombieVillagerCuringTimeMin = getInt("mobs.zombie_villager.curing_time.min", zombieVillagerCuringTimeMin); + zombieVillagerCuringTimeMax = getInt("mobs.zombie_villager.curing_time.max", zombieVillagerCuringTimeMax); + zombieVillagerCureEnabled = getBoolean("mobs.zombie_villager.cure.enabled", zombieVillagerCureEnabled); + zombieVillagerAlwaysDropExp = getBoolean("mobs.zombie_villager.always-drop-exp", zombieVillagerAlwaysDropExp); + } + + public boolean zombifiedPiglinRidable = false; + public boolean zombifiedPiglinRidableInWater = true; + public boolean zombifiedPiglinControllable = true; + public double zombifiedPiglinMaxHealth = 20.0D; + public double zombifiedPiglinSpawnReinforcements = 0.0D; + public boolean zombifiedPiglinJockeyOnlyBaby = true; + public double zombifiedPiglinJockeyChance = 0.05D; + public boolean zombifiedPiglinJockeyTryExistingChickens = true; + public boolean zombifiedPiglinCountAsPlayerKillWhenAngry = true; + public boolean zombifiedPiglinTakeDamageFromWater = false; + public boolean zombifiedPiglinAlwaysDropExp = false; + private void zombifiedPiglinSettings() { + zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable); + zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater); + zombifiedPiglinControllable = getBoolean("mobs.zombified_piglin.controllable", zombifiedPiglinControllable); + if (PurpurConfig.version < 10) { + double oldValue = getDouble("mobs.zombified_piglin.attributes.max-health", zombifiedPiglinMaxHealth); + set("mobs.zombified_piglin.attributes.max-health", null); + set("mobs.zombified_piglin.attributes.max_health", oldValue); + } + zombifiedPiglinMaxHealth = getDouble("mobs.zombified_piglin.attributes.max_health", zombifiedPiglinMaxHealth); + zombifiedPiglinSpawnReinforcements = getDouble("mobs.zombified_piglin.attributes.spawn_reinforcements", zombifiedPiglinSpawnReinforcements); + zombifiedPiglinJockeyOnlyBaby = getBoolean("mobs.zombified_piglin.jockey.only-babies", zombifiedPiglinJockeyOnlyBaby); + zombifiedPiglinJockeyChance = getDouble("mobs.zombified_piglin.jockey.chance", zombifiedPiglinJockeyChance); + zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens); + zombifiedPiglinCountAsPlayerKillWhenAngry = getBoolean("mobs.zombified_piglin.count-as-player-kill-when-angry", zombifiedPiglinCountAsPlayerKillWhenAngry); + zombifiedPiglinTakeDamageFromWater = getBoolean("mobs.zombified_piglin.takes-damage-from-water", zombifiedPiglinTakeDamageFromWater); + zombifiedPiglinAlwaysDropExp = getBoolean("mobs.zombified_piglin.always-drop-exp", zombifiedPiglinAlwaysDropExp); + } + + public float hungerStarvationDamage = 1.0F; + private void hungerSettings() { + hungerStarvationDamage = (float) getDouble("hunger.starvation-damage", hungerStarvationDamage); + } + + public int conduitDistance = 16; + public double conduitDamageDistance = 8; + public float conduitDamageAmount = 4; + public Block[] conduitBlocks; + private void conduitSettings() { + conduitDistance = getInt("blocks.conduit.effect-distance", conduitDistance); + conduitDamageDistance = getDouble("blocks.conduit.mob-damage.distance", conduitDamageDistance); + conduitDamageAmount = (float) getDouble("blocks.conduit.mob-damage.damage-amount", conduitDamageAmount); + List conduitBlockList = new ArrayList<>(); + getList("blocks.conduit.valid-ring-blocks", new ArrayList(){{ + add("minecraft:prismarine"); + add("minecraft:prismarine_bricks"); + add("minecraft:sea_lantern"); + add("minecraft:dark_prismarine"); + }}).forEach(key -> { + Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key.toString())); + if (!block.defaultBlockState().isAir()) { + conduitBlockList.add(block); + } + }); + conduitBlocks = conduitBlockList.toArray(Block[]::new); + } + + public float cauldronRainChance = 0.05F; + public float cauldronPowderSnowChance = 0.1F; + public float cauldronDripstoneWaterFillChance = 0.17578125F; + public float cauldronDripstoneLavaFillChance = 0.05859375F; + private void cauldronSettings() { + cauldronRainChance = (float) getDouble("blocks.cauldron.fill-chances.rain", cauldronRainChance); + cauldronPowderSnowChance = (float) getDouble("blocks.cauldron.fill-chances.powder-snow", cauldronPowderSnowChance); + cauldronDripstoneWaterFillChance = (float) getDouble("blocks.cauldron.fill-chances.dripstone-water", cauldronDripstoneWaterFillChance); + cauldronDripstoneLavaFillChance = (float) getDouble("blocks.cauldron.fill-chances.dripstone-lava", cauldronDripstoneLavaFillChance); + } + + public float shearsCanDefuseTntChance = 0.00F; + public boolean shearsCanDefuseTnt = false; + private void shearsCanDefuseTntSettings() { + shearsCanDefuseTntChance = (float) getDouble("gameplay-mechanics.item.shears.defuse-tnt-chance", 0.00D); + shearsCanDefuseTnt = shearsCanDefuseTntChance > 0.00F; + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/CompassCommand.java b/src/main/java/org/purpurmc/purpur/command/CompassCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..79b8490832d2a0cc7846ddcb091cb6bcac74ea45 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/CompassCommand.java @@ -0,0 +1,27 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.task.CompassTask; + +public class CompassCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("compass") + .requires(listener -> listener.hasPermission(2, "bukkit.command.compass")) + .executes(context -> { + ServerPlayer player = context.getSource().getPlayerOrException(); + CompassTask task = CompassTask.instance(); + if (player.compassBar()) { + task.removePlayer(player.getBukkitEntity()); + player.compassBar(false); + } else { + task.addPlayer(player.getBukkitEntity()); + player.compassBar(true); + } + return 1; + }) + ); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/CreditsCommand.java b/src/main/java/org/purpurmc/purpur/command/CreditsCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..40d2fab4a9728ac90c36e30c130f3116b7025d11 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/CreditsCommand.java @@ -0,0 +1,35 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.network.protocol.game.ClientboundGameEventPacket; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; + +import java.util.Collection; +import java.util.Collections; + +public class CreditsCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("credits") + .requires((listener) -> listener.hasPermission(2, "bukkit.command.credits")) + .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.credits.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + ClientboundGameEventPacket packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1F); + player.connection.send(packet); + String output = String.format(PurpurConfig.creditsCommandOutput, player.getGameProfile().getName()); + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/DemoCommand.java b/src/main/java/org/purpurmc/purpur/command/DemoCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..235f3cd89f675b70a6152a00534608c0902f19fd --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/DemoCommand.java @@ -0,0 +1,35 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.network.protocol.game.ClientboundGameEventPacket; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; + +import java.util.Collection; +import java.util.Collections; + +public class DemoCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("demo") + .requires((listener) -> listener.hasPermission(2, "bukkit.command.demo")) + .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.demo.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + ClientboundGameEventPacket packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.DEMO_EVENT, 0); + player.connection.send(packet); + String output = String.format(PurpurConfig.demoCommandOutput, player.getGameProfile().getName()); + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/PingCommand.java b/src/main/java/org/purpurmc/purpur/command/PingCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..f202b98a194604e39798fdb8e417c6d2835f71c8 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/PingCommand.java @@ -0,0 +1,33 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; +import org.bukkit.craftbukkit.util.CraftChatMessage; + +import java.util.Collection; +import java.util.Collections; + +public class PingCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("ping") + .requires((listener) -> listener.hasPermission(2, "bukkit.command.ping")) + .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.ping.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + String output = String.format(PurpurConfig.pingCommandOutput, player.getGameProfile().getName(), player.connection.latency()); + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..2621e54879e9ab0029a875f1d09eee67878b90d5 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java @@ -0,0 +1,66 @@ +package org.purpurmc.purpur.command; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import org.purpurmc.purpur.PurpurConfig; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class PurpurCommand extends Command { + public PurpurCommand(String name) { + super(name); + this.description = "Purpur related commands"; + this.usageMessage = "/purpur [reload | version]"; + this.setPermission("bukkit.command.purpur"); + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { + if (args.length == 1) { + return Stream.of("reload", "version") + .filter(arg -> arg.startsWith(args[0].toLowerCase())) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if (!testPermission(sender)) return true; + + if (args.length != 1) { + sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); + return false; + } + + if (args[0].equalsIgnoreCase("reload")) { + Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues."); + Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server."); + + MinecraftServer console = MinecraftServer.getServer(); + PurpurConfig.init((File) console.options.valueOf("purpur-settings")); + for (ServerLevel level : console.getAllLevels()) { + level.purpurConfig.init(); + level.resetBreedingCooldowns(); + } + console.server.reloadCount++; + + Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Purpur config reload complete."); + } else if (args[0].equalsIgnoreCase("version")) { + Command verCmd = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version"); + if (verCmd != null) { + return verCmd.execute(sender, commandLabel, new String[0]); + } + } + + return true; + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java b/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..2852c07adb080c34905f5d1b19efed8ea47eecc6 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java @@ -0,0 +1,44 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; +import org.purpurmc.purpur.task.RamBarTask; + +import java.util.Collection; +import java.util.Collections; + +public class RamBarCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("rambar") + .requires(listener -> listener.hasPermission(2, "bukkit.command.rambar")) + .executes(context -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.rambar.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + boolean result = RamBarTask.instance().togglePlayer(player.getBukkitEntity()); + player.ramBar(result); + + Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.rambarCommandOutput, + Placeholder.component("onoff", Component.translatable(result ? "options.on" : "options.off") + .color(result ? NamedTextColor.GREEN : NamedTextColor.RED)), + Placeholder.parsed("target", player.getGameProfile().getName())); + + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/RamCommand.java b/src/main/java/org/purpurmc/purpur/command/RamCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..992f8dfc628c7485e335191e1308cdfd4eedfbe8 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/RamCommand.java @@ -0,0 +1,30 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import org.purpurmc.purpur.PurpurConfig; +import org.purpurmc.purpur.task.RamBarTask; + +public class RamCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("ram") + .requires(listener -> listener.hasPermission(2, "bukkit.command.ram")) + .executes(context -> { + CommandSourceStack sender = context.getSource(); + RamBarTask ramBar = RamBarTask.instance(); + sender.sendSuccess(() -> PaperAdventure.asVanilla(MiniMessage.miniMessage().deserialize(PurpurConfig.ramCommandOutput, + Placeholder.component("allocated", ramBar.format(ramBar.getAllocated())), + Placeholder.component("used", ramBar.format(ramBar.getUsed())), + Placeholder.component("xmx", ramBar.format(ramBar.getXmx())), + Placeholder.component("xms", ramBar.format(ramBar.getXms())), + Placeholder.unparsed("percent", ((int) (ramBar.getPercent() * 100)) + "%") + )), false); + return 1; + }) + ); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/TPSBarCommand.java b/src/main/java/org/purpurmc/purpur/command/TPSBarCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..d8f9b044107ff7c29a83eb5378aa9f5465ba1995 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/TPSBarCommand.java @@ -0,0 +1,44 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.server.level.ServerPlayer; +import org.purpurmc.purpur.PurpurConfig; +import org.purpurmc.purpur.task.TPSBarTask; + +import java.util.Collection; +import java.util.Collections; + +public class TPSBarCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("tpsbar") + .requires(listener -> listener.hasPermission(2, "bukkit.command.tpsbar")) + .executes(context -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) + .then(Commands.argument("targets", EntityArgument.players()) + .requires(listener -> listener.hasPermission(2, "bukkit.command.tpsbar.other")) + .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) + ) + ); + } + + private static int execute(CommandSourceStack sender, Collection targets) { + for (ServerPlayer player : targets) { + boolean result = TPSBarTask.instance().togglePlayer(player.getBukkitEntity()); + player.tpsBar(result); + + Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.tpsbarCommandOutput, + Placeholder.component("onoff", Component.translatable(result ? "options.on" : "options.off") + .color(result ? NamedTextColor.GREEN : NamedTextColor.RED)), + Placeholder.parsed("target", player.getGameProfile().getName())); + + sender.sendSuccess(output, false); + } + return targets.size(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/command/UptimeCommand.java b/src/main/java/org/purpurmc/purpur/command/UptimeCommand.java new file mode 100644 index 0000000000000000000000000000000000000000..4bb475099bcf8f05d5f1474e7fbf29c57c2c40cd --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/command/UptimeCommand.java @@ -0,0 +1,55 @@ +package org.purpurmc.purpur.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.server.MinecraftServer; +import org.purpurmc.purpur.PurpurConfig; + +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +public class UptimeCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("uptime") + .requires((listener) -> listener.hasPermission(2, "bukkit.command.uptime")) + .executes((context) -> execute(context.getSource())) + ); + } + + private static int execute(CommandSourceStack sender) { + Data data = new Data(); + + data.format = PurpurConfig.uptimeFormat; + data.hide = true; + data.millis = System.currentTimeMillis() - MinecraftServer.startTimeMillis; + + process(data, "", PurpurConfig.uptimeDay, PurpurConfig.uptimeDays, TimeUnit.DAYS, TimeUnit.MILLISECONDS::toDays); + process(data, "", PurpurConfig.uptimeHour, PurpurConfig.uptimeHours, TimeUnit.HOURS, TimeUnit.MILLISECONDS::toHours); + process(data, "", PurpurConfig.uptimeMinute, PurpurConfig.uptimeMinutes, TimeUnit.MINUTES, TimeUnit.MILLISECONDS::toMinutes); + data.hide = false; // never hide seconds + process(data, "", PurpurConfig.uptimeSecond, PurpurConfig.uptimeSeconds, TimeUnit.SECONDS, TimeUnit.MILLISECONDS::toSeconds); + + Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.uptimeCommandOutput, Placeholder.unparsed("uptime", data.format)); + sender.sendSuccess(output, false); + return 1; + } + + private static void process(Data data, String replace, String singular, String plural, TimeUnit unit, Function func) { + if (data.format.contains(replace)) { + long val = func.apply(data.millis); + if (data.hide) data.hide = val == 0; + if (!data.hide) data.millis -= unit.toMillis(val); + data.format = data.format.replace(replace, data.hide ? "" : String.format(val == 1 ? singular : plural, val)); + } + } + + private static class Data { + String format; + boolean hide; + long millis; + } +} diff --git a/src/main/java/org/purpurmc/purpur/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..3e846f38902024875d1961b16a60c50201df309d --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java @@ -0,0 +1,107 @@ +package org.purpurmc.purpur.entity; + +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.animal.Dolphin; +import net.minecraft.world.entity.projectile.LlamaSpit; +import net.minecraft.world.entity.projectile.ProjectileUtil; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import org.bukkit.event.entity.EntityRemoveEvent; + +public class DolphinSpit extends LlamaSpit { + public LivingEntity dolphin; + public int ticksLived; + + public DolphinSpit(EntityType type, Level world) { + super(type, world); + } + + public DolphinSpit(Level world, Dolphin dolphin) { + this(EntityType.LLAMA_SPIT, world); + setOwner(dolphin.getRider() != null ? dolphin.getRider() : dolphin); + this.dolphin = dolphin; + this.setPos( + dolphin.getX() - (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(dolphin.yBodyRot * 0.017453292F), + dolphin.getEyeY() - 0.10000000149011612D, + dolphin.getZ() + (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(dolphin.yBodyRot * 0.017453292F)); + } + + // Purpur start + @Override + public boolean canSaveToDisk() { + return false; + } + // Purpur end + + public void tick() { + super_tick(); + + Vec3 mot = this.getDeltaMovement(); + HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); + + this.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(EntityRemoveEvent.Cause.DISCARD); + } else { + this.setDeltaMovement(mot.scale(0.99D)); + if (!this.isNoGravity()) { + this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); + } + + this.setPos(x, y, z); + } + } + + @Override + public void shoot(double x, double y, double z, float speed, float inaccuracy) { + setDeltaMovement(new Vec3(x, y, z).normalize().add( + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) + .scale(speed)); + } + + @Override + protected void onHitEntity(EntityHitResult entityHitResult) { + Entity shooter = this.getOwner(); + if (shooter instanceof LivingEntity) { + entityHitResult.getEntity().hurt(entityHitResult.getEntity().damageSources().mobProjectile(this, (LivingEntity) shooter), level().purpurConfig.dolphinSpitDamage); + } + } + + @Override + protected void onHitBlock(BlockHitResult blockHitResult) { + if (this.hitCancelled) { + return; + } + BlockState state = this.level().getBlockState(blockHitResult.getBlockPos()); + state.onProjectileHit(this.level(), state, blockHitResult, this); + this.discard(EntityRemoveEvent.Cause.DISCARD); + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java new file mode 100644 index 0000000000000000000000000000000000000000..11825590af9346c61d5d15e5ef446b3c77b81b54 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java @@ -0,0 +1,121 @@ +package org.purpurmc.purpur.entity; + +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.decoration.ArmorStand; +import net.minecraft.world.entity.monster.Phantom; +import net.minecraft.world.entity.projectile.LlamaSpit; +import net.minecraft.world.entity.projectile.ProjectileUtil; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.EntityHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; + +public class PhantomFlames extends LlamaSpit { + public Phantom phantom; + public int ticksLived; + public boolean canGrief = false; + + public PhantomFlames(EntityType type, Level world) { + super(type, world); + } + + public PhantomFlames(Level world, Phantom phantom) { + this(EntityType.LLAMA_SPIT, world); + setOwner(phantom.getRider() != null ? phantom.getRider() : phantom); + this.phantom = phantom; + this.setPos( + phantom.getX() - (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(phantom.yBodyRot * 0.017453292F), + phantom.getEyeY() - 0.10000000149011612D, + phantom.getZ() + (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(phantom.yBodyRot * 0.017453292F)); + } + + // Purpur start + @Override + public boolean canSaveToDisk() { + return false; + } + // Purpur end + + public void tick() { + super_tick(); + + Vec3 mot = this.getDeltaMovement(); + HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity); + + this.preOnHit(hitResult); + + double x = this.getX() + mot.x; + double y = this.getY() + mot.y; + double z = this.getZ() + mot.z; + + this.updateRotation(); + + Vec3 motDouble = mot.scale(2.0); + for (int i = 0; i < 5; i++) { + ((ServerLevel) level()).sendParticles(null, ParticleTypes.FLAME, + getX() + random.nextFloat() / 2 - 0.25F, + getY() + random.nextFloat() / 2 - 0.25F, + getZ() + random.nextFloat() / 2 - 0.25F, + 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D, true); + } + + if (++ticksLived > 20) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + } else if (this.level().getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + } else if (this.isInWaterOrBubble()) { + this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + } else { + this.setDeltaMovement(mot.scale(0.99D)); + if (!this.isNoGravity()) { + this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); + } + + this.setPos(x, y, z); + } + } + + @Override + public void shoot(double x, double y, double z, float speed, float inaccuracy) { + setDeltaMovement(new Vec3(x, y, z).normalize().add( + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, + random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) + .scale(speed)); + } + + @Override + protected void onHitEntity(EntityHitResult entityHitResult) { + 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(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD); + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java b/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java new file mode 100644 index 0000000000000000000000000000000000000000..8efca1d91188ac4db911a8eb0fa9ea2cc3c48e28 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/PurpurStoredBee.java @@ -0,0 +1,102 @@ +package org.purpurmc.purpur.entity; + +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.Component; +import net.minecraft.nbt.Tag; +import net.minecraft.world.level.block.entity.BeehiveBlockEntity; +import org.bukkit.block.EntityBlockStorage; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry; +import org.bukkit.entity.Bee; +import org.bukkit.entity.EntityType; +import org.bukkit.persistence.PersistentDataContainer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Locale; + +public class PurpurStoredBee implements StoredEntity { + private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); + + private final EntityBlockStorage blockStorage; + private final BeehiveBlockEntity.BeeData handle; + private final CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(PurpurStoredBee.DATA_TYPE_REGISTRY); + + private Component customName; + + public PurpurStoredBee(BeehiveBlockEntity.BeeData data, EntityBlockStorage blockStorage) { + this.handle = data; + this.blockStorage = blockStorage; + + this.customName = handle.entityData.contains("CustomName", Tag.TAG_STRING) + ? PaperAdventure.asAdventure(net.minecraft.network.chat.Component.Serializer.fromJson(handle.entityData.getString("CustomName"))) + : null; + + if(handle.entityData.contains("BukkitValues", Tag.TAG_COMPOUND)) { + this.persistentDataContainer.putAll(handle.entityData.getCompound("BukkitValues")); + } + } + + public BeehiveBlockEntity.BeeData getHandle() { + return handle; + } + + @Override + public @Nullable Component customName() { + return customName; + } + + @Override + public void customName(@Nullable Component customName) { + this.customName = customName; + } + + @Override + public @Nullable String getCustomName() { + return PaperAdventure.asPlain(customName, Locale.US); + } + + @Override + public void setCustomName(@Nullable String name) { + customName(name != null ? Component.text(name) : null); + } + + @Override + public @NotNull PersistentDataContainer getPersistentDataContainer() { + return persistentDataContainer; + } + + @Override + public boolean hasBeenReleased() { + return !blockStorage.getEntities().contains(this); + } + + @Override + public @Nullable Bee release() { + return blockStorage.releaseEntity(this); + } + + @Override + public @Nullable EntityBlockStorage getBlockStorage() { + if(hasBeenReleased()) { + return null; + } + + return blockStorage; + } + + @Override + public @NotNull EntityType getType() { + return EntityType.BEE; + } + + @Override + public void update() { + handle.entityData.put("BukkitValues", this.persistentDataContainer.toTagCompound()); + if(customName == null) { + handle.entityData.remove("CustomName"); + } else { + handle.entityData.putString("CustomName", net.minecraft.network.chat.Component.Serializer.toJson(PaperAdventure.asVanilla(customName))); + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java new file mode 100644 index 0000000000000000000000000000000000000000..8babdaddd8b33278aea0369dbbeeb445abe45016 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java @@ -0,0 +1,20 @@ +package org.purpurmc.purpur.entity.ai; + +import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.goal.Goal; + +import java.util.EnumSet; + +public class HasRider extends Goal { + public final Mob entity; + + public HasRider(Mob entity) { + this.entity = entity; + setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.TARGET, Flag.UNKNOWN_BEHAVIOR)); + } + + @Override + public boolean canUse() { + return entity.getRider() != null && entity.isControllable(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java new file mode 100644 index 0000000000000000000000000000000000000000..432f4f3d82af2f19820890b68d33189a9f2c69f9 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java @@ -0,0 +1,17 @@ +package org.purpurmc.purpur.entity.ai; + +import net.minecraft.world.entity.animal.horse.AbstractHorse; + +public class HorseHasRider extends HasRider { + public final AbstractHorse horse; + + public HorseHasRider(AbstractHorse entity) { + super(entity); + this.horse = entity; + } + + @Override + public boolean canUse() { + return super.canUse() && horse.isSaddled(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java new file mode 100644 index 0000000000000000000000000000000000000000..18a95e043cbffa65eeaaf65ff7695e5dc939820c --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java @@ -0,0 +1,17 @@ +package org.purpurmc.purpur.entity.ai; + +import net.minecraft.world.entity.animal.horse.Llama; + +public class LlamaHasRider extends HasRider { + public final Llama llama; + + public LlamaHasRider(Llama entity) { + super(entity); + this.llama = entity; + } + + @Override + public boolean canUse() { + return super.canUse() && llama.isSaddled() && llama.isControllable(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java b/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java new file mode 100644 index 0000000000000000000000000000000000000000..9660716f4162a4441c6e1b0baddef8f5086566c5 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java @@ -0,0 +1,91 @@ +package org.purpurmc.purpur.entity.ai; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.ai.goal.Goal; +import net.minecraft.world.entity.animal.IronGolem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Blocks; + +import java.util.EnumSet; +import java.util.UUID; + +public class ReceiveFlower extends Goal { + private final IronGolem irongolem; + private ServerPlayer target; + private int cooldown; + + public ReceiveFlower(IronGolem entity) { + this.irongolem = entity; + setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK)); + } + + @Override + public boolean canUse() { + if (this.irongolem.getOfferFlowerTick() > 0) { + return false; + } + if (!this.irongolem.isAngry()) { + return false; + } + UUID uuid = this.irongolem.getPersistentAngerTarget(); + if (uuid == null) { + return false; + } + Entity target = ((ServerLevel) this.irongolem.level()).getEntity(uuid); + if (!(target instanceof ServerPlayer player)) { + return false; + } + InteractionHand hand = getPoppyHand(player); + if (hand == null) { + return false; + } + removeFlower(player, hand); + this.target = player; + return true; + } + + @Override + public boolean canContinueToUse() { + return this.cooldown > 0; + } + + @Override + public void start() { + this.cooldown = 100; + this.irongolem.stopBeingAngry(); + this.irongolem.offerFlower(true); + } + + @Override + public void stop() { + this.irongolem.offerFlower(false); + this.target = null; + } + + @Override + public void tick() { + this.irongolem.getLookControl().setLookAt(this.target, 30.0F, 30.0F); + --this.cooldown; + } + + private InteractionHand getPoppyHand(ServerPlayer player) { + if (isPoppy(player.getMainHandItem())) { + return InteractionHand.MAIN_HAND; + } + if (isPoppy(player.getOffhandItem())) { + return InteractionHand.OFF_HAND; + } + return null; + } + + private void removeFlower(ServerPlayer player, InteractionHand hand) { + player.setItemInHand(hand, ItemStack.EMPTY); + } + + private boolean isPoppy(ItemStack item) { + return item.getItem() == Blocks.POPPY.asItem(); + } +} diff --git a/src/main/java/org/purpurmc/purpur/gui/GUIColor.java b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java new file mode 100644 index 0000000000000000000000000000000000000000..0f2e7e0b81620c8581949bd5f0bdb567cd93c17e --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java @@ -0,0 +1,54 @@ +package org.purpurmc.purpur.gui; + +import net.md_5.bungee.api.ChatColor; + +import java.awt.Color; +import java.util.HashMap; +import java.util.Map; + +public enum GUIColor { + BLACK(ChatColor.BLACK, new Color(0x000000)), + DARK_BLUE(ChatColor.DARK_BLUE, new Color(0x0000AA)), + DARK_GREEN(ChatColor.DARK_GREEN, new Color(0x00AA00)), + DARK_AQUA(ChatColor.DARK_AQUA, new Color(0x009999)), + DARK_RED(ChatColor.DARK_RED, new Color(0xAA0000)), + DARK_PURPLE(ChatColor.DARK_PURPLE, new Color(0xAA00AA)), + GOLD(ChatColor.GOLD, new Color(0xBB8800)), + GRAY(ChatColor.GRAY, new Color(0x888888)), + DARK_GRAY(ChatColor.DARK_GRAY, new Color(0x444444)), + BLUE(ChatColor.BLUE, new Color(0x5555FF)), + GREEN(ChatColor.GREEN, new Color(0x55FF55)), + AQUA(ChatColor.AQUA, new Color(0x55DDDD)), + RED(ChatColor.RED, new Color(0xFF5555)), + LIGHT_PURPLE(ChatColor.LIGHT_PURPLE, new Color(0xFF55FF)), + YELLOW(ChatColor.YELLOW, new Color(0xFFBB00)), + WHITE(ChatColor.WHITE, new Color(0xBBBBBB)); + + private final ChatColor chat; + private final Color color; + + private static final Map BY_CHAT = new HashMap<>(); + + GUIColor(ChatColor chat, Color color) { + this.chat = chat; + this.color = color; + } + + public Color getColor() { + return color; + } + + public String getCode() { + return chat.toString(); + } + + public static GUIColor getColor(ChatColor chat) { + return BY_CHAT.get(chat); + } + + static { + for (GUIColor color : values()) { + BY_CHAT.put(color.chat, color); + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java new file mode 100644 index 0000000000000000000000000000000000000000..33e89b4c00fa8318506b36cbe49fe4e412e0a9a1 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java @@ -0,0 +1,78 @@ +package org.purpurmc.purpur.gui; + +import com.google.common.collect.Sets; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; + +import javax.swing.JTextPane; +import javax.swing.Timer; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyleContext; +import java.util.Set; + +public class JColorTextPane extends JTextPane { + private static final GUIColor DEFAULT_COLOR = GUIColor.BLACK; + + public void append(String msg) { + // TODO: update to use adventure instead + BaseComponent[] components = TextComponent.fromLegacyText(DEFAULT_COLOR.getCode() + msg, ChatColor.BLACK); + for (BaseComponent component : components) { + String text = component.toPlainText(); + if (text == null || text.isEmpty()) { + continue; + } + + GUIColor guiColor = GUIColor.getColor(component.getColor()); + + StyleContext context = StyleContext.getDefaultStyleContext(); + AttributeSet attr = context.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, guiColor.getColor()); + attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Bold, component.isBold() || guiColor != DEFAULT_COLOR); + attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Italic, component.isItalic()); + attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Underline, component.isUnderlined()); + attr = context.addAttribute(attr, StyleConstants.CharacterConstants.StrikeThrough, component.isStrikethrough()); + //attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Blink, component.isObfuscated()); // no such thing as Blink, sadly + + try { + int pos = getDocument().getLength(); + getDocument().insertString(pos, text, attr); + + if (component.isObfuscated()) { + // dirty hack to blink some text + Blink blink = new Blink(pos, text.length(), attr, context.addAttribute(attr, StyleConstants.Foreground, getBackground())); + BLINKS.add(blink); + } + } catch (BadLocationException ignore) { + } + } + } + + private static final Set BLINKS = Sets.newHashSet(); + private static boolean SYNC_BLINK; + + static { + new Timer(500, e -> { + SYNC_BLINK = !SYNC_BLINK; + BLINKS.forEach(Blink::blink); + }).start(); + } + + public class Blink { + private final int start, length; + private final AttributeSet attr1, attr2; + + private Blink(int start, int length, AttributeSet attr1, AttributeSet attr2) { + this.start = start; + this.length = length; + this.attr1 = attr1; + this.attr2 = attr2; + } + + private void blink() { + getStyledDocument().setCharacterAttributes(start, length, SYNC_BLINK ? attr1 : attr2, true); + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java new file mode 100644 index 0000000000000000000000000000000000000000..7f526883495b3222746de3d0442e9e4fb5107036 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java @@ -0,0 +1,26 @@ +package org.purpurmc.purpur.item; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemNameBlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import org.bukkit.event.entity.EntityPotionEffectEvent; + +public class GlowBerryItem extends ItemNameBlockItem { + public GlowBerryItem(Block block, Properties settings) { + super(block, settings); + } + + @Override + public ItemStack finishUsingItem(ItemStack stack, Level world, LivingEntity user) { + ItemStack result = super.finishUsingItem(stack, world, user); + if (world.purpurConfig.glowBerriesEatGlowDuration > 0 && user instanceof ServerPlayer player) { + player.addEffect(new MobEffectInstance(MobEffects.GLOWING, world.purpurConfig.glowBerriesEatGlowDuration), EntityPotionEffectEvent.Cause.FOOD); + } + return result; + } +} diff --git a/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java new file mode 100644 index 0000000000000000000000000000000000000000..d6cc7e434cb2bacc00e4cad9e1f4be7fcf5d0bee --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java @@ -0,0 +1,38 @@ +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)); + } else if (tag.contains("BlockEntityTag")) { + spawner.load(tag.getCompound("BlockEntityTag")); + } + } + } + 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..15e760d5c0465b24969df3e25bf8409faab8b62e --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java @@ -0,0 +1,96 @@ +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.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +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(); + + // targeted block info max range specified in client at net.minecraft.client.gui.hud.DebugHud#render + if (!pos.getCenter().closerThan(serverPlayer.position(), 20)) return; // Targeted Block info max range is 20 + if (serverPlayer.level().getChunkIfLoaded(pos) == null) return; + + BlockEntity blockEntity = serverPlayer.level().getBlockEntity(pos); + if (!(blockEntity instanceof BeehiveBlockEntity beehive)) { + return; + } + + ByteArrayDataOutput out = out(); + + out.writeInt(beehive.getOccupantCount()); + out.writeLong(packedPos); + + FriendlyByteBuf byteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(out.toByteArray())); + serverPlayer.connection.send(new ClientboundCustomPayloadPacket(new CustomPacketPayload() { + @Override + public void write(final FriendlyByteBuf buf) { + buf.writeBytes(byteBuf.copy()); + } + + @Override + public ResourceLocation id() { + return BEEHIVE_S2C; + } + })); + } + + @SuppressWarnings("UnstableApiUsage") + private static ByteArrayDataOutput out() { + return ByteStreams.newDataOutput(); + } + + @SuppressWarnings("UnstableApiUsage") + private static ByteArrayDataInput in(byte[] bytes) { + return ByteStreams.newDataInput(bytes); + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/BossBarTask.java b/src/main/java/org/purpurmc/purpur/task/BossBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..114f273dd7f8b8a3c02f0651f6944859b33a65d4 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/BossBarTask.java @@ -0,0 +1,121 @@ +package org.purpurmc.purpur.task; + +import net.kyori.adventure.bossbar.BossBar; +import net.minecraft.server.level.ServerPlayer; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; + +public abstract class BossBarTask extends BukkitRunnable { + private final Map bossbars = new HashMap<>(); + private boolean started; + + abstract BossBar createBossBar(); + + abstract void updateBossBar(BossBar bossbar, Player player); + + @Override + public void run() { + Iterator> iter = bossbars.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + Player player = Bukkit.getPlayer(entry.getKey()); + if (player == null) { + iter.remove(); + continue; + } + updateBossBar(entry.getValue(), player); + } + } + + @Override + public void cancel() { + super.cancel(); + new HashSet<>(this.bossbars.keySet()).forEach(uuid -> { + Player player = Bukkit.getPlayer(uuid); + if (player != null) { + removePlayer(player); + } + }); + this.bossbars.clear(); + } + + public boolean removePlayer(Player player) { + BossBar bossbar = this.bossbars.remove(player.getUniqueId()); + if (bossbar != null) { + player.hideBossBar(bossbar); + return true; + } + return false; + } + + public void addPlayer(Player player) { + removePlayer(player); + BossBar bossbar = createBossBar(); + this.bossbars.put(player.getUniqueId(), bossbar); + this.updateBossBar(bossbar, player); + player.showBossBar(bossbar); + } + + public boolean hasPlayer(UUID uuid) { + return this.bossbars.containsKey(uuid); + } + + public boolean togglePlayer(Player player) { + if (removePlayer(player)) { + return false; + } + addPlayer(player); + return true; + } + + public void start() { + stop(); + this.runTaskTimerAsynchronously(new MinecraftInternalPlugin(), 1, 1); + started = true; + } + + public void stop() { + if (started) { + cancel(); + } + } + + public static void startAll() { + RamBarTask.instance().start(); + TPSBarTask.instance().start(); + CompassTask.instance().start(); + } + + public static void stopAll() { + RamBarTask.instance().stop(); + TPSBarTask.instance().stop(); + CompassTask.instance().stop(); + } + + public static void addToAll(ServerPlayer player) { + Player bukkit = player.getBukkitEntity(); + if (player.ramBar()) { + RamBarTask.instance().addPlayer(bukkit); + } + if (player.tpsBar()) { + TPSBarTask.instance().addPlayer(bukkit); + } + if (player.compassBar()) { + CompassTask.instance().addPlayer(bukkit); + } + } + + public static void removeFromAll(Player player) { + RamBarTask.instance().removePlayer(player); + TPSBarTask.instance().removePlayer(player); + CompassTask.instance().removePlayer(player); + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/CompassTask.java b/src/main/java/org/purpurmc/purpur/task/CompassTask.java new file mode 100644 index 0000000000000000000000000000000000000000..bece7eefc8ba8822b433835526251d2fb916c025 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/CompassTask.java @@ -0,0 +1,68 @@ +package org.purpurmc.purpur.task; + +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.item.Items; +import org.bukkit.entity.Player; +import org.purpurmc.purpur.PurpurConfig; + +public class CompassTask extends BossBarTask { + private static CompassTask instance; + + private int tick = 0; + + public static CompassTask instance() { + if (instance == null) { + instance = new CompassTask(); + } + return instance; + } + + @Override + public void run() { + if (++tick < PurpurConfig.commandCompassBarTickInterval) { + return; + } + tick = 0; + + MinecraftServer.getServer().getAllLevels().forEach((level) -> { + if (level.purpurConfig.compassItemShowsBossBar) { + level.players().forEach(player -> { + if (!player.compassBar()) { + if (player.getMainHandItem().getItem() != Items.COMPASS && player.getOffhandItem().getItem() != Items.COMPASS) { + removePlayer(player.getBukkitEntity()); + } else if (!hasPlayer(player.getUUID())) { + addPlayer(player.getBukkitEntity()); + } + } + }); + } + }); + + super.run(); + } + + @Override + BossBar createBossBar() { + return BossBar.bossBar(Component.text(""), PurpurConfig.commandCompassBarProgressPercent, PurpurConfig.commandCompassBarProgressColor, PurpurConfig.commandCompassBarProgressOverlay); + } + + @Override + void updateBossBar(BossBar bossbar, Player player) { + float yaw = player.getLocation().getYaw(); + int length = PurpurConfig.commandCompassBarTitle.length(); + int pos = (int) ((normalize(yaw) * (length / 720F)) + (length / 2F)); + bossbar.name(Component.text(PurpurConfig.commandCompassBarTitle.substring(pos - 25, pos + 25))); + } + + private float normalize(float yaw) { + while (yaw < -180.0F) { + yaw += 360.0F; + } + while (yaw > 180.0F) { + yaw -= 360.0F; + } + return yaw; + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/RamBarTask.java b/src/main/java/org/purpurmc/purpur/task/RamBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..8e98c0ae73e2c40002a72b5d0d246ffa0c3ab38f --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/RamBarTask.java @@ -0,0 +1,117 @@ +package org.purpurmc.purpur.task; + +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import org.bukkit.entity.Player; +import org.purpurmc.purpur.PurpurConfig; + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryUsage; + +public class RamBarTask extends BossBarTask { + private static RamBarTask instance; + private long allocated = 0L; + private long used = 0L; + private long xmx = 0L; + private long xms = 0L; + private float percent = 0F; + private int tick = 0; + + public static RamBarTask instance() { + if (instance == null) { + instance = new RamBarTask(); + } + return instance; + } + + @Override + BossBar createBossBar() { + return BossBar.bossBar(Component.text(""), 0.0F, instance().getBossBarColor(), PurpurConfig.commandRamBarProgressOverlay); + } + + @Override + void updateBossBar(BossBar bossbar, Player player) { + bossbar.progress(getBossBarProgress()); + bossbar.color(getBossBarColor()); + bossbar.name(MiniMessage.miniMessage().deserialize(PurpurConfig.commandRamBarTitle, + Placeholder.component("allocated", format(this.allocated)), + Placeholder.component("used", format(this.used)), + Placeholder.component("xmx", format(this.xmx)), + Placeholder.component("xms", format(this.xms)), + Placeholder.unparsed("percent", ((int) (this.percent * 100)) + "%") + )); + } + + @Override + public void run() { + if (++this.tick < PurpurConfig.commandRamBarTickInterval) { + return; + } + this.tick = 0; + + MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); + + this.allocated = heap.getCommitted(); + this.used = heap.getUsed(); + this.xmx = heap.getMax(); + this.xms = heap.getInit(); + this.percent = Math.max(Math.min((float) this.used / this.xmx, 1.0F), 0.0F); + + super.run(); + } + + private float getBossBarProgress() { + return this.percent; + } + + private BossBar.Color getBossBarColor() { + if (this.percent < 0.5F) { + return PurpurConfig.commandRamBarProgressColorGood; + } else if (this.percent < 0.75F) { + return PurpurConfig.commandRamBarProgressColorMedium; + } else { + return PurpurConfig.commandRamBarProgressColorLow; + } + } + + public Component format(long v) { + String color; + if (this.percent < 0.60F) { + color = PurpurConfig.commandRamBarTextColorGood; + } else if (this.percent < 0.85F) { + color = PurpurConfig.commandRamBarTextColorMedium; + } else { + color = PurpurConfig.commandRamBarTextColorLow; + } + String value; + if (v < 1024) { + value = v + "B"; + } else { + int z = (63 - Long.numberOfLeadingZeros(v)) / 10; + value = String.format("%.1f%s", (double) v / (1L << (z * 10)), "BKMGTPE".charAt(z)); + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.unparsed("text", value)); + } + + public long getAllocated() { + return this.allocated; + } + + public long getUsed() { + return this.used; + } + + public long getXmx() { + return this.xmx; + } + + public long getXms() { + return this.xms; + } + + public float getPercent() { + return this.percent; + } +} diff --git a/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java b/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java new file mode 100644 index 0000000000000000000000000000000000000000..8769993e7ca59da309087051a3cd38fc562c15d1 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java @@ -0,0 +1,142 @@ +package org.purpurmc.purpur.task; + +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import org.purpurmc.purpur.PurpurConfig; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +public class TPSBarTask extends BossBarTask { + private static TPSBarTask instance; + private double tps = 20.0D; + private double mspt = 0.0D; + private int tick = 0; + + public static TPSBarTask instance() { + if (instance == null) { + instance = new TPSBarTask(); + } + return instance; + } + + @Override + BossBar createBossBar() { + return BossBar.bossBar(Component.text(""), 0.0F, instance().getBossBarColor(), PurpurConfig.commandTPSBarProgressOverlay); + } + + @Override + void updateBossBar(BossBar bossbar, Player player) { + bossbar.progress(getBossBarProgress()); + bossbar.color(getBossBarColor()); + bossbar.name(MiniMessage.miniMessage().deserialize(PurpurConfig.commandTPSBarTitle, + Placeholder.component("tps", getTPSColor()), + Placeholder.component("mspt", getMSPTColor()), + Placeholder.component("ping", getPingColor(player.getPing())) + )); + } + + @Override + public void run() { + if (++tick < PurpurConfig.commandTPSBarTickInterval) { + return; + } + tick = 0; + + this.tps = Math.max(Math.min(Bukkit.getTPS()[0], 20.0D), 0.0D); + this.mspt = Bukkit.getAverageTickTime(); + + super.run(); + } + + private float getBossBarProgress() { + if (PurpurConfig.commandTPSBarProgressFillMode == FillMode.MSPT) { + return Math.max(Math.min((float) mspt / 50.0F, 1.0F), 0.0F); + } else { + return Math.max(Math.min((float) tps / 20.0F, 1.0F), 0.0F); + } + } + + private BossBar.Color getBossBarColor() { + if (isGood(PurpurConfig.commandTPSBarProgressFillMode)) { + return PurpurConfig.commandTPSBarProgressColorGood; + } else if (isMedium(PurpurConfig.commandTPSBarProgressFillMode)) { + return PurpurConfig.commandTPSBarProgressColorMedium; + } else { + return PurpurConfig.commandTPSBarProgressColorLow; + } + } + + private boolean isGood(FillMode mode) { + return isGood(mode, 0); + } + + private boolean isGood(FillMode mode, int ping) { + if (mode == FillMode.MSPT) { + return mspt < 40; + } else if (mode == FillMode.TPS) { + return tps >= 19; + } else if (mode == FillMode.PING) { + return ping < 100; + } else { + return false; + } + } + + private boolean isMedium(FillMode mode) { + return isMedium(mode, 0); + } + + private boolean isMedium(FillMode mode, int ping) { + if (mode == FillMode.MSPT) { + return mspt < 50; + } else if (mode == FillMode.TPS) { + return tps >= 15; + } else if (mode == FillMode.PING) { + return ping < 200; + } else { + return false; + } + } + + private Component getTPSColor() { + String color; + if (isGood(FillMode.TPS)) { + color = PurpurConfig.commandTPSBarTextColorGood; + } else if (isMedium(FillMode.TPS)) { + color = PurpurConfig.commandTPSBarTextColorMedium; + } else { + color = PurpurConfig.commandTPSBarTextColorLow; + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%.2f", tps))); + } + + private Component getMSPTColor() { + String color; + if (isGood(FillMode.MSPT)) { + color = PurpurConfig.commandTPSBarTextColorGood; + } else if (isMedium(FillMode.MSPT)) { + color = PurpurConfig.commandTPSBarTextColorMedium; + } else { + color = PurpurConfig.commandTPSBarTextColorLow; + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%.2f", mspt))); + } + + private Component getPingColor(int ping) { + String color; + if (isGood(FillMode.PING, ping)) { + color = PurpurConfig.commandTPSBarTextColorGood; + } else if (isMedium(FillMode.PING, ping)) { + color = PurpurConfig.commandTPSBarTextColorMedium; + } else { + color = PurpurConfig.commandTPSBarTextColorLow; + } + return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%s", ping))); + } + + public enum FillMode { + TPS, MSPT, PING + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Actionable.java b/src/main/java/org/purpurmc/purpur/tool/Actionable.java new file mode 100644 index 0000000000000000000000000000000000000000..e18c37f06730da9d3055d5215e813b1477c1e70e --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Actionable.java @@ -0,0 +1,24 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Map; + +public abstract class Actionable { + private final Block into; + private final Map drops; + + public Actionable(Block into, Map drops) { + this.into = into; + this.drops = drops; + } + + public Block into() { + return into; + } + + public Map drops() { + return drops; + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Flattenable.java b/src/main/java/org/purpurmc/purpur/tool/Flattenable.java new file mode 100644 index 0000000000000000000000000000000000000000..345d4ee4ff0b78bd1050959711a4f5d16a5e8aee --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Flattenable.java @@ -0,0 +1,12 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Map; + +public class Flattenable extends Actionable { + public Flattenable(Block into, Map drops) { + super(into, drops); + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Strippable.java b/src/main/java/org/purpurmc/purpur/tool/Strippable.java new file mode 100644 index 0000000000000000000000000000000000000000..bf5402214f41af9c09bd6c5c4f45d330516d742e --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Strippable.java @@ -0,0 +1,12 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Map; + +public class Strippable extends Actionable { + public Strippable(Block into, Map drops) { + super(into, drops); + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Tillable.java b/src/main/java/org/purpurmc/purpur/tool/Tillable.java new file mode 100644 index 0000000000000000000000000000000000000000..715f6dd44480347eebced43c11bc364e05727498 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Tillable.java @@ -0,0 +1,50 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.HoeItem; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.block.Block; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; + +public class Tillable extends Actionable { + private final Condition condition; + + public Tillable(Condition condition, Block into, Map drops) { + super(into, drops); + this.condition = condition; + } + + public Condition condition() { + return condition; + } + + public enum Condition { + AIR_ABOVE(HoeItem::onlyIfAirAbove), + ALWAYS((useOnContext) -> true); + + private final Predicate predicate; + + Condition(Predicate predicate) { + this.predicate = predicate; + } + + public Predicate predicate() { + return predicate; + } + + private static final Map BY_NAME = new HashMap<>(); + + static { + for (Condition condition : values()) { + BY_NAME.put(condition.name(), condition); + } + } + + public static Condition get(String name) { + return BY_NAME.get(name.toUpperCase(java.util.Locale.ROOT)); + } + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Waxable.java b/src/main/java/org/purpurmc/purpur/tool/Waxable.java new file mode 100644 index 0000000000000000000000000000000000000000..64adb13b29b6757dcf227a55588da70ecabe083f --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Waxable.java @@ -0,0 +1,12 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Map; + +public class Waxable extends Actionable { + public Waxable(Block into, Map drops) { + super(into, drops); + } +} diff --git a/src/main/java/org/purpurmc/purpur/tool/Weatherable.java b/src/main/java/org/purpurmc/purpur/tool/Weatherable.java new file mode 100644 index 0000000000000000000000000000000000000000..b7586f494528f30eb0da82420d3bcf5b83a1a902 --- /dev/null +++ b/src/main/java/org/purpurmc/purpur/tool/Weatherable.java @@ -0,0 +1,12 @@ +package org.purpurmc.purpur.tool; + +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; + +import java.util.Map; + +public class Weatherable extends Actionable { + public Weatherable(Block into, Map drops) { + super(into, drops); + } +} diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java index 0735af396aa89b6ea7d5565d91f070fac90f7da1..c6c58c398b2ce4741301afaa04d2ba2dbee475d4 100644 --- a/src/main/java/org/spigotmc/ActivationRange.java +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -15,6 +15,7 @@ import net.minecraft.world.entity.ambient.AmbientCreature; import net.minecraft.world.entity.animal.Animal; import net.minecraft.world.entity.animal.Bee; import net.minecraft.world.entity.animal.Sheep; +import net.minecraft.world.entity.animal.Squid; import net.minecraft.world.entity.animal.WaterAnimal; import net.minecraft.world.entity.animal.horse.Llama; import net.minecraft.world.entity.boss.EnderDragonPart; @@ -217,6 +218,7 @@ public class ActivationRange continue; } + if (!player.level().purpurConfig.idleTimeoutTickNearbyEntities && player.isAfk()) continue; // Purpur // Paper start int worldHeight = world.getHeight(); ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, worldHeight, maxRange ); @@ -414,6 +416,7 @@ public class ActivationRange */ public static boolean checkIfActive(Entity entity) { + if (entity.level().purpurConfig.squidImmuneToEAR && entity instanceof Squid) return true; // Purpur // Never safe to skip fireworks or entities not yet added to chunk if ( entity instanceof FireworkRocketEntity ) { return true; diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 675cd61221e807aadf28322b46c3daa1370241b5..0769f5c4711a3b7f59489e611ed01ad8367e5db1 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -2,7 +2,16 @@ - + + + + + + + + + diff --git a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java index afeb4271fffb7546209f1e651214065187c88302..81bc3af856b8af019fd13e1da1f7cccd526b7cf0 100644 --- a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java +++ b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java @@ -45,6 +45,7 @@ public class MinecraftCommandPermissionsTest extends AbstractTestingBase { Set foundPerms = new HashSet<>(); for (CommandNode child : root.getChildren()) { final String vanillaPerm = VanillaCommandWrapper.getPermission(child); + if (TO_SKIP.contains(vanillaPerm)) continue; // Purpur if (!perms.contains(vanillaPerm)) { missing.add("Missing permission for " + child.getName() + " (" + vanillaPerm + ") command"); } else { @@ -57,6 +58,25 @@ public class MinecraftCommandPermissionsTest extends AbstractTestingBase { } private static final List TO_SKIP = List.of( + // Purpur start + "minecraft.command.compass", + "minecraft.command.credits", + "minecraft.command.demo", + "minecraft.command.ping", + "minecraft.command.ram", + "minecraft.command.rambar", + "minecraft.command.tpsbar", + "minecraft.command.uptime", + "minecraft.command.debug", + "minecraft.command.gamemode.adventure", + "minecraft.command.gamemode.adventure.other", + "minecraft.command.gamemode.creative", + "minecraft.command.gamemode.creative.other", + "minecraft.command.gamemode.spectator", + "minecraft.command.gamemode.spectator.other", + "minecraft.command.gamemode.survival", + "minecraft.command.gamemode.survival.other", + // Purpur end "minecraft.command.selector" ); diff --git a/src/test/java/org/bukkit/potion/PotionTest.java b/src/test/java/org/bukkit/potion/PotionTest.java index 8963d93e99bdaf719fa160c11dd5af6a1d86f9a4..d852d8b14f5000415cbb4f06601059b3934b7efc 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.craftbukkit.potion.CraftPotionEffectType; import org.bukkit.support.AbstractTestingBase; import org.junit.jupiter.api.Test; @@ -46,4 +47,27 @@ public class PotionTest extends AbstractTestingBase { assertEquals(bukkit, byName, "Same type not returned by name " + key); } } + + // Purpur start + @Test + public void testNamespacedKey() { + NamespacedKey key = new NamespacedKey("testnamespace", "testkey"); + PotionEffect namedSpacedEffect = new PotionEffect(PotionEffectType.DOLPHINS_GRACE, 20, 0, true, true, true, key); + assertNotNull(namedSpacedEffect.getKey()); + assertTrue(namedSpacedEffect.hasKey()); + assertFalse(namedSpacedEffect.withKey(null).hasKey()); + + PotionEffect effect = new PotionEffect(PotionEffectType.DOLPHINS_GRACE, 20, 0, true, true, true); + assertNull(effect.getKey()); + assertFalse(effect.hasKey()); + assertTrue(namedSpacedEffect.withKey(key).hasKey()); + + Map s1 = namedSpacedEffect.serialize(); + Map s2 = effect.serialize(); + assertTrue(s1.containsKey("namespacedKey")); + assertFalse(s2.containsKey("namespacedKey")); + assertNotNull(new PotionEffect(s1).getKey()); + assertNull(new PotionEffect(s2).getKey()); + } + // Purpur end }