9
0
mirror of https://github.com/Winds-Studio/Leaf.git synced 2025-12-19 15:09:25 +00:00
Files
Leaf/leaf-server/paper-patches/features/0009-Purpur-Server-Paper-Changes.patch
Dreeam 5ef030ab1e [ci skip] Updated Upstream (Paper/Purpur)
Upstream has released updates that appear to apply and compile correctly

Paper Changes:
PaperMC/Paper@39203a65 [ci/skip] Publish PR API and dev bundles (#12672)
PaperMC/Paper@a1b30587 Provide env environment variable and copy spigots sys prop for overriding default repository

Purpur Changes:
PurpurMC/Purpur@b1d412fb Updated Upstream (Paper)
PurpurMC/Purpur@293e28a0 use empty registryaccess where context is not needed, closes #1676
PurpurMC/Purpur@452bb319 port PaperMC/Paper#12654, closes #1665
PurpurMC/Purpur@849bc79c register test subcommands used for debugging, closes #1675
PurpurMC/Purpur@61d7f559 Updated Upstream (Paper)
2025-06-21 11:04:38 +08:00

1883 lines
85 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Github Actions <no-reply@github.com>
Date: Sun, 6 Apr 2025 06:33:24 +0000
Subject: [PATCH] Purpur Server Paper Changes
Original license: MIT
Original project: https://github.com/PurpurMC/Purpur
Commit: 61d7f5590e79909caba55e9f00cefdd51006d015
Patches listed below are removed in this patch, They exists in Gale or Leaf:
* "Rebrand.patch"
* "com/destroystokyo/paper/Metrics.java.patch"
- Purpur config files
* "com/destroystokyo/paper/PaperVersionFetcher.java.patch"
- Rebrand
* "com/destroystokyo/paper/console/PaperConsole.java.patch"
- Rebrand
* "com/destroystokyo/paper/gui/RAMDetails.java.patch"
- Add 5 second tps average in /tps
* "io/papermc/paper/ServerBuildInfoImpl.java.patch"
- Rebrand
* "org/bukkit/craftbukkit/CraftServer.java.patch"
- Add 5 second tps average in /tps
* "org/bukkit/craftbukkit/legacy/CraftLegacy.java.patch"
- Logger settings (suppressing pointless logs)
* "org/bukkit/craftbukkit/scheduler/CraftScheduler.java.patch"
- Rebrand
* "org/bukkit/craftbukkit/util/CraftMagicNumbers.java.patch"
- Rebrand
* "org/bukkit/craftbukkit/util/Versioning.java.patch"
- Rebrand
* "org/spigotmc/TicksPerSecondCommand.java.patch"
- Add 5 second tps average in /tps
* "org/spigotmc/WatchdogThread.java.patch"
- Rebrand
diff --git a/src/log4jPlugins/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java b/src/log4jPlugins/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java
new file mode 100644
index 0000000000000000000000000000000000000000..15a226e3854d731f7724025ea3459c8ace07630c
--- /dev/null
+++ b/src/log4jPlugins/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java
@@ -0,0 +1,85 @@
+package org.purpurmc.purpur.gui.util;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.pattern.ConverterKeys;
+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternFormatter;
+import org.apache.logging.log4j.core.pattern.PatternParser;
+import org.apache.logging.log4j.util.PerformanceSensitive;
+
+import java.util.List;
+
+@Plugin(name = "highlightGUIError", category = PatternConverter.CATEGORY)
+@ConverterKeys({"highlightGUIError"})
+@PerformanceSensitive("allocation")
+public final class HighlightErrorConverter extends LogEventPatternConverter {
+ private static final String ERROR = "\u00A74\u00A7l"; // Bold Red
+ private static final String WARN = "\u00A7e\u00A7l"; // Bold Yellow
+
+ private final List<PatternFormatter> formatters;
+
+ private HighlightErrorConverter(List<PatternFormatter> formatters) {
+ super("highlightGUIError", null);
+ this.formatters = formatters;
+ }
+
+ @Override
+ public void format(LogEvent event, StringBuilder toAppendTo) {
+ Level level = event.getLevel();
+ if (level.isMoreSpecificThan(Level.ERROR)) {
+ format(ERROR, event, toAppendTo);
+ return;
+ } else if (level.isMoreSpecificThan(Level.WARN)) {
+ format(WARN, event, toAppendTo);
+ return;
+ }
+ for (PatternFormatter formatter : formatters) {
+ formatter.format(event, toAppendTo);
+ }
+ }
+
+ private void format(String style, LogEvent event, StringBuilder toAppendTo) {
+ int start = toAppendTo.length();
+ toAppendTo.append(style);
+ int end = toAppendTo.length();
+
+ for (PatternFormatter formatter : formatters) {
+ formatter.format(event, toAppendTo);
+ }
+
+ if (toAppendTo.length() == end) {
+ toAppendTo.setLength(start);
+ }
+ }
+
+ @Override
+ public boolean handlesThrowable() {
+ for (final PatternFormatter formatter : formatters) {
+ if (formatter.handlesThrowable()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static HighlightErrorConverter newInstance(Configuration config, String[] options) {
+ if (options.length != 1) {
+ LOGGER.error("Incorrect number of options on highlightGUIError. Expected 1 received " + options.length);
+ return null;
+ }
+
+ if (options[0] == null) {
+ LOGGER.error("No pattern supplied on highlightGUIError");
+ return null;
+ }
+
+ PatternParser parser = PatternLayout.createPatternParser(config);
+ List<PatternFormatter> formatters = parser.parse(options[0]);
+ return new HighlightErrorConverter(formatters);
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
index a786d7579455c017c8ad79c97b1507b66f8c0800..c26cf541de853d380048119057752e1ccebc89aa 100644
--- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
+++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
@@ -27,6 +27,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 - Add option to disable zombie aggressiveness towards villagers
+ deobfuscationMap.put("zombie_1", "zombie_attack_villager");
+ deobfuscationMap.put("drowned_1", "drowned_attack_villager");
+ // Purpur end - Add option to disable zombie aggressiveness towards villagers
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 41c95f00b4b2bea6d31f85e268c33d7f6184823e..2eaf27ddf0bddb23b8850310c332b202b1d7e69c 100644
--- a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java
+++ b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java
@@ -70,10 +70,10 @@ public class PaperPluginsCommand extends BukkitCommand {
this.setAliases(List.of("pl"));
}
- private static <T> List<Component> formatProviders(final TreeMap<String, PluginProvider<T>> plugins) {
+ private static <T> List<Component> formatProviders(final TreeMap<String, PluginProvider<T>> plugins, CommandSender sender) { // Purpur - Improve output of plugins command
final List<Component> components = new ArrayList<>(plugins.size());
for (final PluginProvider<T> entry : plugins.values()) {
- components.add(formatProvider(entry));
+ components.add(formatProvider(entry, sender)); // Purpur - Improve output of plugins command
}
boolean isFirst = true;
@@ -100,15 +100,49 @@ public class PaperPluginsCommand extends BukkitCommand {
return formattedSubLists;
}
- private static Component formatProvider(final PluginProvider<?> provider) {
+ private static Component formatProvider(final PluginProvider<?> provider, CommandSender sender) { // Purpur - Improve output of plugins command
final TextComponent.Builder builder = Component.text();
if (provider instanceof final SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) {
builder.append(LEGACY_PLUGIN_STAR);
}
final String name = provider.getMeta().getName();
- final Component pluginName = Component.text(name, fromStatus(provider))
- .clickEvent(ClickEvent.runCommand("/version " + name));
+ // Purpur start - Improve output of plugins command
+ Component pluginName = Component.text(name, fromStatus(provider))
+ .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 = pluginName.hoverEvent(hover.build());
+ }
+ // Purpur end - Improve output of plugins command
builder.append(pluginName);
@@ -126,6 +160,23 @@ public class PaperPluginsCommand extends BukkitCommand {
return componentHeader.append(Component.text(":")).build();
}
+ // Purpur start - Improve output of plugins command
+ private static TextComponent getAuthors(final PluginMeta pluginMeta) {
+ TextComponent.Builder builder = Component.text();
+ List<String> authors = pluginMeta.getAuthors();
+
+ for (int i = 0; i < authors.size(); i++) {
+ if (i > 0) {
+ builder.append(Component.text(i < authors.size() - 1 ? ", " : " and ", NamedTextColor.WHITE));
+ }
+
+ builder.append(Component.text(authors.get(i), NamedTextColor.GREEN));
+ }
+
+ return builder.build();
+ }
+ // Purpur end - Improve output of plugins command
+
private static Component asPlainComponents(final String strings) {
final net.kyori.adventure.text.TextComponent.Builder builder = Component.text();
for (final String string : strings.split("\n")) {
@@ -186,25 +237,25 @@ public class PaperPluginsCommand extends BukkitCommand {
final int sizePaperPlugins = paperPlugins.size();
final int sizeSpigotPlugins = spigotPlugins.size();
final int sizePlugins = sizePaperPlugins + sizeSpigotPlugins;
- final boolean hasAllPluginTypes = (sizePaperPlugins > 0 && sizeSpigotPlugins > 0);
+ final boolean hasAllPluginTypes = true; // Purpur - Improve output of plugins command
final Component infoMessage = Component.text().append(INFO_ICON_SERVER_PLUGIN).append(Component.text("Server Plugins (%s):".formatted(sizePlugins), NamedTextColor.WHITE)).build();
sender.sendMessage(infoMessage);
- if (!paperPlugins.isEmpty()) {
+ //if (!paperPlugins.isEmpty()) { // Purpur - Improve output of plugins command
sender.sendMessage(header("Paper Plugins", 0x0288D1, sizePaperPlugins, hasAllPluginTypes));
- }
+ //} // Purpur - Improve output of plugins command
- for (final Component component : formatProviders(paperPlugins)) {
+ for (final Component component : formatProviders(paperPlugins, sender)) { // Purpur - Improve output of plugins command
sender.sendMessage(component);
}
- if (!spigotPlugins.isEmpty()) {
+ //if (!spigotPlugins.isEmpty()) { // Purpur - Improve output of plugins command
sender.sendMessage(header("Bukkit Plugins", 0xED8106, sizeSpigotPlugins, hasAllPluginTypes));
- }
+ //} // Purpur - Improve output of plugins command
- for (final Component component : formatProviders(spigotPlugins)) {
+ for (final Component component : formatProviders(spigotPlugins, sender)) { // Purpur - Improve output of plugins command
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..96a377a07d8396f893954a3ba97fbf88d73faf6d 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 - Enhance SysoutCatcher - prefix not needed
- if (SysoutCatcher.SUPPRESS_NAGS) {
+ if (true || SysoutCatcher.SUPPRESS_NAGS) { // Purpur - Enhance SysoutCatcher - nagging is annoying
return;
}
if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
index 43ae147ae15668d3357ff1f7ae66bcad241d3ff3..ecd9e392e9e026cbeac4c65bfdb7467f971ca92d 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
@@ -336,6 +336,12 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa
@Override
public Location getLocation() {
+ // Purpur start - OfflinePlayer API
+ if (this.isOnline()) {
+ return this.getPlayer().getLocation();
+ }
+ // Purpur end - OfflinePlayer API
+
CompoundTag data = this.getData();
if (data == null) {
return null;
@@ -573,4 +579,183 @@ 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.get("abilities") instanceof CompoundTag abilities)) return false;
+ return abilities.getByteOr("mayfly", (byte) 0) == (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.get("abilities") instanceof CompoundTag abilities)) return;
+ 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.get("abilities") instanceof CompoundTag abilities)) return false;
+ return abilities.getByteOr("flying", (byte) 0) == (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.get("abilities") instanceof CompoundTag abilities)) return;
+ 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.get("abilities") instanceof CompoundTag abilities)) return;
+ 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.get("abilities") instanceof CompoundTag abilities)) return 0;
+ return abilities.getFloatOr("flySpeed", 0);
+ }
+ }
+
+ @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.get("abilities") instanceof CompoundTag abilities)) return;
+ 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.get("abilities") instanceof CompoundTag abilities)) return 0;
+ return abilities.getFloatOr("walkSpeed", 0);
+ }
+ }
+
+ @Override
+ public boolean teleportOffline(Location destination) {
+ if (this.isOnline()) {
+ return this.getPlayer().teleport(destination);
+ } else {
+ return setLocation(destination);
+ }
+ }
+
+ @Override
+ public boolean teleportOffline(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause){
+ if (this.isOnline()) {
+ return this.getPlayer().teleport(destination, cause);
+ } else {
+ return setLocation(destination);
+ }
+ }
+
+ @Override
+ public java.util.concurrent.CompletableFuture<Boolean> teleportOfflineAsync(Location destination) {
+ if (this.isOnline()) {
+ return this.getPlayer().teleportAsync(destination);
+ } else {
+ return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination));
+ }
+ }
+
+ @Override
+ public java.util.concurrent.CompletableFuture<Boolean> teleportOfflineAsync(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
+ if (this.isOnline()) {
+ return this.getPlayer().teleportAsync(destination, cause);
+ } else {
+ return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination));
+ }
+ }
+
+ private boolean setLocation(Location location) {
+ CompoundTag data = this.getData();
+ if (data == null) return false;
+ data.putLong("WorldUUIDMost", location.getWorld().getUID().getMostSignificantBits());
+ data.putLong("WorldUUIDLeast", location.getWorld().getUID().getLeastSignificantBits());
+ net.minecraft.nbt.ListTag position = new net.minecraft.nbt.ListTag();
+ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getX()));
+ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getY()));
+ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getZ()));
+ data.put("Pos", position);
+ net.minecraft.nbt.ListTag rotation = new net.minecraft.nbt.ListTag();
+ rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getYaw()));
+ rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getPitch()));
+ data.put("Rotation", rotation);
+ save(data);
+ return true;
+ }
+
+ /**
+ * Safely replaces player's .dat file with provided CompoundTag
+ * @param compoundTag
+ */
+ private void save(CompoundTag compoundTag) {
+ File playerDir = server.console.playerDataStorage.getPlayerDir();
+ try {
+ File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir);
+ net.minecraft.nbt.NbtIo.writeCompressed(compoundTag, tempFile.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 c92adb939892e1054f7b2d24070f43e6104dda2b..23bd30ad769507e3eeb4e397e57a2c0ac01f8b7a 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -429,6 +429,20 @@ public final class CraftServer implements Server {
this.paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager);
this.pluginManager.paperPluginManager = this.paperPluginManager;
// Paper end
+ // Purpur start - Language API
+ 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 - Language API
CraftRegistry.setMinecraftRegistry(console.registryAccess());
@@ -1074,6 +1088,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 - Purpur config files
for (ServerLevel world : this.console.getAllLevels()) {
// world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty
world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean))
@@ -1089,6 +1104,7 @@ public final class CraftServer implements Server {
}
}
world.spigotConfig.init(); // Spigot
+ world.purpurConfig.init(); // Purpur - Purpur config files
}
Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
@@ -1106,6 +1122,7 @@ public final class CraftServer implements Server {
org.spigotmc.SpigotConfig.registerCommands(); // Spigot
io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper
this.spark.registerCommandBeforePlugins(this); // Paper - spark
+ org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur - Purpur config files
this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions");
@@ -1605,6 +1622,60 @@ public final class CraftServer implements Server {
return true;
}
+ // Purpur start - Added the ability to add combustible items
+ @Override
+ public void addFuel(org.bukkit.Material material, int burnTime) {
+ Preconditions.checkArgument(burnTime > 0, "BurnTime must be greater than 0");
+
+ net.minecraft.world.item.ItemStack itemStack = net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material));
+ MinecraftServer.getServer().fuelValues().values.put(itemStack.getItem(), burnTime);
+ }
+
+ @Override
+ public void removeFuel(org.bukkit.Material material) {
+ net.minecraft.world.item.ItemStack itemStack = net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material));
+ MinecraftServer.getServer().fuelValues().values.keySet().removeIf(itemStack::is);
+ }
+ // Purpur end - Added the ability to add combustible items
+
+ // Purpur start - Debug Marker API
+ @Override
+ public void sendBlockHighlight(Location location, int duration) {
+ sendBlockHighlight(location, duration, "", 0x6400FF00);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, int argb) {
+ sendBlockHighlight(location, duration, "", argb);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text) {
+ sendBlockHighlight(location, duration, text, 0x6400FF00);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text, int argb) {
+ this.worlds.forEach((name, world) -> world.sendBlockHighlight(location, duration, text, argb));
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) {
+ sendBlockHighlight(location, duration, "", color, transparency);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) {
+ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range");
+ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB());
+ }
+
+ @Override
+ public void clearBlockHighlights() {
+ this.worlds.forEach((name, world) -> clearBlockHighlights());
+ }
+ // Purpur end - Debug Marker API
+
@Override
public List<Recipe> getRecipesFor(ItemStack result) {
Preconditions.checkArgument(result != null, "ItemStack cannot be null");
@@ -2976,6 +3047,18 @@ public final class CraftServer implements Server {
}
// Gale end - Gale configuration - API
+ // Purpur start - Purpur config files
+ @Override
+ public YamlConfiguration getPurpurConfig() {
+ return org.purpurmc.purpur.PurpurConfig.config;
+ }
+
+ @Override
+ public java.util.Properties getServerProperties() {
+ return getProperties().properties;
+ }
+ // Purpur end - Purpur config files
+
@Override
public void restart() {
CraftServer.this.restart();
@@ -3215,4 +3298,18 @@ public final class CraftServer implements Server {
public void allowPausing(final Plugin plugin, final boolean value) {
this.console.addPluginAllowingSleep(plugin.getName(), value);
}
+
+ // Purpur start - Bring back server name
+ @Override
+ public String getServerName() {
+ return this.getProperties().serverName;
+ }
+ // Purpur end - Bring back server name
+
+ // Purpur start - Lagging threshold
+ @Override
+ public boolean isLagging() {
+ return getServer().lagging;
+ }
+ // Purpur end - Lagging threshold
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index aae378697b2f2e388d2a5dfaca24c9197b8abf3e..0ea7df63498492e320f98ddec5bdf33b203cc770 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -2352,6 +2352,50 @@ public class CraftWorld extends CraftRegionAccessor implements World {
return (this.getHandle().getDragonFight() == null) ? null : new CraftDragonBattle(this.getHandle().getDragonFight());
}
+ // Purpur start - Add local difficulty api
+ public float getLocalDifficultyAt(Location location) {
+ return getHandle().getCurrentDifficultyAt(org.bukkit.craftbukkit.util.CraftLocation.toBlockPosition(location)).getEffectiveDifficulty();
+ }
+ // Purpur end - Add local difficulty api
+
+ // Purpur start - Debug Marker API
+ @Override
+ public void sendBlockHighlight(Location location, int duration) {
+ sendBlockHighlight(location, duration, "", 0x6400FF00);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, int argb) {
+ sendBlockHighlight(location, duration, "", argb);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text) {
+ sendBlockHighlight(location, duration, text, 0x6400FF00);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text, int argb) {
+ net.minecraft.network.protocol.game.DebugPackets.sendGameTestAddMarker(getHandle(), org.bukkit.craftbukkit.util.CraftLocation.toBlockPosition(location), text, argb, duration);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) {
+ sendBlockHighlight(location, duration, "", color, transparency);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) {
+ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range");
+ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB());
+ }
+
+ @Override
+ public void clearBlockHighlights() {
+ net.minecraft.network.protocol.game.DebugPackets.sendGameTestClearPacket(getHandle());
+ }
+ // Purpur end - Debug Marker API
+
@Override
public Collection<GeneratedStructure> 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 e89c906dc07e52ea8d661e005d8531128ce4d080..4f64d377b56481fe5e1ff82b424fe06cdf8304e8 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -165,6 +165,14 @@ public class Main {
.defaultsTo(new File[] {})
.describedAs("Jar file");
+ // Purpur start - Purpur config files
+ acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings")
+ .withRequiredArg()
+ .ofType(File.class)
+ .defaultsTo(new File("purpur.yml"))
+ .describedAs("Yml file");
+ // Purpur end - Purpur config files
+
acceptsAll(asList("server-name"), "Name of the server")
.withRequiredArg()
.ofType(String.class)
@@ -224,7 +232,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 - Disable outdated build check
Date buildDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(Main.class.getPackage().getImplementationVendor()); // Paper
Calendar deadline = Calendar.getInstance();
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java
index ed4692090a115d3d0a0826b10f77e903058eeec6..c5971aadd443dc0ffa26f1740552cc009d244a3e 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java
@@ -16,8 +16,15 @@ import org.bukkit.entity.Bee;
public class CraftBeehive extends CraftBlockEntityState<BeehiveBlockEntity> implements Beehive {
+ private final List<org.purpurmc.purpur.entity.StoredEntity<Bee>> storage = new ArrayList<>(); // Purpur - Stored Bee API
+
public CraftBeehive(World world, BeehiveBlockEntity blockEntity) {
super(world, blockEntity);
+ // Purpur start - load bees to be able to modify them individually - Stored Bee API
+ for(BeehiveBlockEntity.BeeData data : blockEntity.getStored()) {
+ storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(data, this));
+ }
+ // Purpur end - Stored Bee API
}
protected CraftBeehive(CraftBeehive state, Location location) {
@@ -76,14 +83,54 @@ public class CraftBeehive extends CraftBlockEntityState<BeehiveBlockEntity> impl
}
}
+ storage.clear(); // Purpur - Stored Bee API
return bees;
}
+ // Purpur start - Stored Bee API
+ @Override
+ public Bee releaseEntity(org.purpurmc.purpur.entity.StoredEntity<Bee> entity) {
+ ensureNoWorldGeneration();
+
+ if(!getEntities().contains(entity)) {
+ return null;
+ }
+
+ if(isPlaced()) {
+ BeehiveBlockEntity beehive = ((BeehiveBlockEntity) this.getBlockEntityFromWorld());
+ BeehiveBlockEntity.BeeData data = ((org.purpurmc.purpur.entity.PurpurStoredBee) entity).getHandle();
+
+ List<Entity> 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<org.purpurmc.purpur.entity.StoredEntity<Bee>> getEntities() {
+ return new ArrayList<>(storage);
+ }
+ // Purpur end - Stored Bee API
+
@Override
public void addEntity(Bee entity) {
Preconditions.checkArgument(entity != null, "Entity must not be null");
+ int length = this.getSnapshot().getStored().size(); // Purpur - Stored Bee API
this.getSnapshot().addOccupant(((CraftBee) entity).getHandle());
+
+ // Purpur start - check if new bee was added, and if yes, add to stored bees - Stored Bee API
+ List<BeehiveBlockEntity.BeeData> storedBeeData = this.getSnapshot().getStored();
+ if(length < storedBeeData.size()) {
+ storage.add(new org.purpurmc.purpur.entity.PurpurStoredBee(storedBeeData.getLast(), this));
+ }
+ // Purpur end - Stored Bee API
}
@Override
@@ -100,6 +147,7 @@ public class CraftBeehive extends CraftBlockEntityState<BeehiveBlockEntity> impl
@Override
public void clearEntities() {
getSnapshot().clearBees();
+ storage.clear(); // Purpur - Stored Bee API
}
// Paper end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java
index b7352ab040c979b42ec7efd301bd9d7bfe08129f..79c59d77068709e42b3a908080d147b94fcee209 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftConduit.java
@@ -73,7 +73,7 @@ public class CraftConduit extends CraftBlockEntityState<ConduitBlockEntity> impl
public int getRange() {
this.ensureNoWorldGeneration();
ConduitBlockEntity conduit = (ConduitBlockEntity) this.getBlockEntityFromWorld();
- return (conduit != null) ? ConduitBlockEntity.getRange(conduit.effectBlocks) : 0;
+ return (conduit != null) ? ConduitBlockEntity.getRange(conduit.effectBlocks, this.world.getHandle()) : 0; // Purpur - Conduit behavior configuration
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
index 6e2f931b06d4ef702e0abf609a5f5bba780c5b7a..15ae2653c1809041b7ab7cb67015e82c226e9f77 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
@@ -21,7 +21,12 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co
@Override
public void sendMessage(String message) {
- this.sendRawMessage(message);
+ // Purpur start - Rebrand
+ String[] parts = message.split("\n");
+ for (String part : parts) {
+ this.sendRawMessage(part);
+ }
+ // Purpur end - Rebrand
}
@Override
@@ -88,7 +93,7 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co
@Override
public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) {
- this.sendRawMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message));
+ this.sendMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); // Purpur - Rebrand
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java
index 000c5ea5d39d5ab6e5592012ab1cc7ee547505dc..140da7078217f833f490d88faaf803ab1763115d 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java
@@ -16,12 +16,12 @@ public class CraftEndermite extends CraftMonster implements Endermite {
@Override
public boolean isPlayerSpawned() {
- return false;
+ return getHandle().isPlayerSpawned(); // Purpur - Add back player spawned endermite API
}
@Override
public void setPlayerSpawned(boolean playerSpawned) {
- // Nop
+ getHandle().setPlayerSpawned(playerSpawned); // Purpur - Add back player spawned endermite API
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
index 3b41a37f852c3b494b158e447eade9b030f26556..395951c968e95adffa97e1851d104faf192744fd 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -100,6 +100,25 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
this.entityType = CraftEntityType.minecraftToBukkit(entity.getType());
}
+ // Purpur start - Fire Immunity API
+ @Override
+ public boolean isImmuneToFire() {
+ return getHandle().fireImmune();
+ }
+
+ @Override
+ public void setImmuneToFire(Boolean fireImmune) {
+ getHandle().immuneToFire = fireImmune;
+ }
+ // Purpur end - Fire Immunity API
+
+ // Purpur start - API for any mob to burn daylight
+ @Override
+ public boolean isInDaylight() {
+ return getHandle().isSunBurnTick();
+ }
+ // Purpur end - API for any mob to burn daylight
+
public static <T extends Entity> CraftEntity getEntity(CraftServer server, T entity) {
Preconditions.checkArgument(entity != null, "Unknown entity");
@@ -289,6 +308,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
boolean retainPassengers = flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS);
// Don't allow teleporting between worlds while keeping passengers
if (flagSet.contains(io.papermc.paper.entity.TeleportFlag.EntityState.RETAIN_PASSENGERS) && this.entity.isVehicle() && location.getWorld() != this.getWorld()) {
+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur - Add EntityTeleportHinderedEvent
return false;
}
@@ -1313,4 +1333,26 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
return this.entity.get(io.papermc.paper.datacomponent.PaperDataComponentType.bukkitToMinecraft(type)) != null;
}
+ // Purpur start - Ridables
+ @Override
+ public org.bukkit.entity.Player getRider() {
+ net.minecraft.world.entity.player.Player rider = getHandle().getRider();
+ return rider != null ? (org.bukkit.entity.Player) rider.getBukkitEntity() : null;
+ }
+
+ @Override
+ public boolean hasRider() {
+ return getHandle().getRider() != null;
+ }
+
+ @Override
+ public boolean isRidable() {
+ return getHandle().isRidable();
+ }
+
+ @Override
+ public boolean isRidableInWater() {
+ return !getHandle().dismountsUnderwater();
+ }
+ // Purpur end - Ridables
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
index 934eb5c668b09d871c62a17c8d0916723fa25695..d4e36d6d2e11d43db4c80bf95d0a935f99de576e 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
@@ -295,6 +295,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity {
@Override
public void recalculatePermissions() {
this.perm.recalculatePermissions();
+ getHandle().canPortalInstant = hasPermission("purpur.portal.instant"); // Purpur - Add portal permission bypass
}
@Override
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java
index 2d7be6f2afe32fd7555640989313052d37973e61..5bafbb876e4780ff6ea0408c95007bdfcef8fa89 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java
@@ -22,4 +22,17 @@ public class CraftIronGolem extends CraftGolem implements IronGolem {
public void setPlayerCreated(boolean playerCreated) {
this.getHandle().setPlayerCreated(playerCreated);
}
+
+ // Purpur start - Summoner API
+ @Override
+ @org.jetbrains.annotations.Nullable
+ public java.util.UUID getSummoner() {
+ return getHandle().getSummoner();
+ }
+
+ @Override
+ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) {
+ getHandle().setSummoner(summoner);
+ }
+ // Purpur end - Summoner API
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
index ca656d97a00eef86773be9833fd908283002c885..01759ae999cd158e540f1e282473740758d6cf54 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
@@ -143,4 +143,53 @@ public class CraftItem extends CraftEntity implements Item {
public UUID getThrower() {
return this.getHandle().thrower;
}
+
+ // Purpur start - Item entity immunities
+ @Override
+ public void setImmuneToCactus(boolean immuneToCactus) {
+ this.getHandle().immuneToCactus = immuneToCactus;
+ }
+
+ @Override
+ public boolean isImmuneToCactus() {
+ return this.getHandle().immuneToCactus;
+ }
+
+ @Override
+ public void setImmuneToExplosion(boolean immuneToExplosion) {
+ this.getHandle().immuneToExplosion = immuneToExplosion;
+ }
+
+ @Override
+ public boolean isImmuneToExplosion() {
+ return this.getHandle().immuneToExplosion;
+ }
+
+ // Purpur start - Fire Immunity API
+ @Override
+ public void setImmuneToFire(@org.jetbrains.annotations.Nullable Boolean immuneToFire) {
+ this.getHandle().immuneToFire = (immuneToFire != null && immuneToFire);
+ }
+ // Purpur end - Fire Immunity API
+
+ @Override
+ public void setImmuneToFire(boolean immuneToFire) {
+ this.setImmuneToFire((Boolean) immuneToFire); // Purpur - Fire Immunity API
+ }
+
+ @Override
+ public boolean isImmuneToFire() {
+ return this.getHandle().immuneToFire;
+ }
+
+ @Override
+ public void setImmuneToLightning(boolean immuneToLightning) {
+ this.getHandle().immuneToLightning = immuneToLightning;
+ }
+
+ @Override
+ public boolean isImmuneToLightning() {
+ return this.getHandle().immuneToLightning;
+ }
+ // Purpur end - Item entity immunities
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
index 6b4e74b808b4fb6ac0143d5c82571657b2c49683..fc4fa99a993a017676da2be3cb254399d421bce1 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
@@ -514,7 +514,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
net.minecraft.server.level.ServerPlayer nmsKiller = killer == null ? null : ((CraftPlayer) killer).getHandle();
this.getHandle().setLastHurtByMob(nmsKiller);
if (nmsKiller != null) {
- this.getHandle().setLastHurtByPlayer(nmsKiller, 100); // value taken from LivingEntity#resolvePlayerResponsibleForDamage
+ this.getHandle().setLastHurtByPlayer(nmsKiller, this.getHandle().level().purpurConfig.mobLastHurtByPlayerTime); // value taken from LivingEntity#resolvePlayerResponsibleForDamage // Purpur - Config for mob last hurt by player time
} else {
this.getHandle().lastHurtByPlayer = null;
this.getHandle().lastHurtByPlayerMemoryTime = 0;
@@ -1167,4 +1167,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
public CombatTracker getCombatTracker() {
return this.getHandle().getCombatTracker().paperCombatTracker;
}
+
+ // Purpur start - API for any mob to burn daylight
+ @Override
+ public boolean shouldBurnInDay() {
+ return this.getHandle().shouldBurnInDay();
+ }
+
+ @Override
+ public void setShouldBurnInDay(final boolean shouldBurnInDay) {
+ this.getHandle().setShouldBurnInDay(shouldBurnInDay);
+ }
+ // Purpur end - API for any mob to burn daylight
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
index 02deb9c37250144be9afa0fe55df2a49017a7070..dfb597fcb8d01ed6cf20eae57e260949356275de 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
@@ -86,4 +86,17 @@ public class CraftLlama extends CraftChestedHorse implements Llama, com.destroys
public Llama getCaravanTail() {
return this.getHandle().caravanTail == null ? null : (Llama) this.getHandle().caravanTail.getBukkitEntity();
}
+ // Paper end
+
+ // Purpur start - Llama API
+ @Override
+ public boolean shouldJoinCaravan() {
+ return getHandle().shouldJoinCaravan;
+ }
+
+ @Override
+ public void setShouldJoinCaravan(boolean shouldJoinCaravan) {
+ getHandle().shouldJoinCaravan = shouldJoinCaravan;
+ }
+ // Purpur end - Llama API
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 1db6276ae7a547860cf2c6f85b37ec4d6b33655a..fcc7bc9098e2544588d8b36dd0cff59e6a2563bd 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -614,10 +614,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@Override
public void setPlayerListName(String name) {
+ // Purpur start - AFK API
+ setPlayerListName(name, false);
+ }
+ public void setPlayerListName(String name, boolean useMM) {
+ // Purpur end - AFK API
if (name == null) {
name = this.getName();
}
- this.getHandle().listName = name.equals(this.getName()) ? null : CraftChatMessage.fromStringOrNull(name);
+ this.getHandle().listName = name.equals(this.getName()) ? null : useMM ? io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name)) : CraftChatMessage.fromStringOrNull(name); // Purpur - AFK API
if (this.getHandle().connection == null) return; // Paper - Updates are possible before the player has fully joined
for (ServerPlayer player : (List<ServerPlayer>) this.server.getHandle().players) {
if (player.getBukkitEntity().canSee(this)) {
@@ -1431,6 +1436,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
// Paper start - Teleport passenger API
// Don't allow teleporting between worlds while keeping passengers
if (ignorePassengers && entity.isVehicle() && location.getWorld() != this.getWorld()) {
+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur - Add EntityTeleportHinderedEvent
return false;
}
@@ -1452,6 +1458,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
}
if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API
+ if (!new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) // Purpur - Add EntityTeleportHinderedEvent
return false;
}
@@ -2723,6 +2730,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
return this.getHandle().getAbilities().walkingSpeed * 2f;
}
+ // Purpur start - OfflinePlayer API
+ @Override
+ public boolean teleportOffline(Location destination) {
+ return this.teleport(destination);
+ }
+
+ @Override
+ public boolean teleportOffline(Location destination, PlayerTeleportEvent.TeleportCause cause) {
+ return this.teleport(destination, cause);
+ }
+
+ @Override
+ public java.util.concurrent.CompletableFuture<Boolean> teleportOfflineAsync(Location destination) {
+ return this.teleportAsync(destination);
+ }
+
+ @Override
+ public java.util.concurrent.CompletableFuture<Boolean> teleportOfflineAsync(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);
}
@@ -3576,4 +3605,76 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
public void setDeathScreenScore(final int score) {
getHandle().setScore(score);
}
+
+ // Purpur start - Purpur client support
+ @Override
+ public boolean usesPurpurClient() {
+ return getHandle().purpurClient;
+ }
+ // Purpur end - Purpur client support
+
+ // Purpur start - AFK API
+ @Override
+ public boolean isAfk() {
+ return getHandle().isAfk();
+ }
+
+ @Override
+ public void setAfk(boolean setAfk) {
+ getHandle().setAfk(setAfk);
+ }
+
+ @Override
+ public void resetIdleTimer() {
+ getHandle().resetLastActionTime();
+ }
+ // Purpur end - AFK API
+
+ // Purpur start - Debug Marker API
+ @Override
+ public void sendBlockHighlight(Location location, int duration) {
+ sendBlockHighlight(location, duration, "", 0x6400FF00);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, int argb) {
+ sendBlockHighlight(location, duration, "", argb);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text) {
+ sendBlockHighlight(location, duration, text, 0x6400FF00);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text, int argb) {
+ if (this.getHandle().connection == null) return;
+ this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket(new net.minecraft.network.protocol.common.custom.GameTestAddMarkerDebugPayload(org.bukkit.craftbukkit.util.CraftLocation.toBlockPosition(location), argb, text, duration)));
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) {
+ sendBlockHighlight(location, duration, "", color, transparency);
+ }
+
+ @Override
+ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) {
+ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range");
+ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB());
+ }
+
+ @Override
+ public void clearBlockHighlights() {
+ if (this.getHandle().connection == null) return;
+ this.getHandle().connection.send(new net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket(new net.minecraft.network.protocol.common.custom.GameTestClearMarkersDebugPayload()));
+ }
+ // Purpur end - Debug Marker API
+
+ // Purpur start - Death screen API
+ @Override
+ public void sendDeathScreen(net.kyori.adventure.text.Component message) {
+ if (this.getHandle().connection == null) return;
+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket(getEntityId(), io.papermc.paper.adventure.PaperAdventure.asVanilla(message)));
+ }
+ // Purpur end - Death screen API
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java
index f03f9f94c51ef2f2eb24e4f65ec69b542b1c0a07..af60e3ef00ce1e98708d9963d466e72b6cde53cc 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java
@@ -24,4 +24,17 @@ public class CraftSnowman extends CraftGolem implements Snowman, com.destroystok
public void setDerp(boolean derpMode) {
this.getHandle().setPumpkin(!derpMode);
}
+
+ // Purpur start - Summoner API
+ @Override
+ @org.jetbrains.annotations.Nullable
+ public java.util.UUID getSummoner() {
+ return getHandle().getSummoner();
+ }
+
+ @Override
+ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) {
+ getHandle().setSummoner(summoner);
+ }
+ // Purpur end - Summoner API
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
index 66f0089ee33c36b9d13fb0b87303d33f08021ed1..26f056294e19f509bfd4986b9650e13f92a8feaf 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
@@ -258,4 +258,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager {
public void restock() {
getHandle().restock();
}
+
+ // Purpur start - Lobotomize stuck villagers
+ @Override
+ public boolean isLobotomized() {
+ return getHandle().isLobotomized();
+ }
+ // Purpur end - Lobotomize stuck villagers
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java
index 2e11df97e5c8ff5fbf22ba87946d6018ac8bbbed..83b779eb0adcaf2e6d5658cddc2607f6deea6bb9 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java
@@ -92,4 +92,18 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok
public void enterInvulnerabilityPhase() {
this.getHandle().makeInvulnerable();
}
+ // Paper end
+
+ // Purpur start - Summoner API
+ @Override
+ @org.jetbrains.annotations.Nullable
+ public java.util.UUID getSummoner() {
+ return getHandle().getSummoner();
+ }
+
+ @Override
+ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) {
+ getHandle().setSummoner(summoner);
+ }
+ // Purpur end - Summoner API
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java
index 33866c5a5591150de4c1d8f4c175122db3bd5bd4..5fd656f43985e3972c9f572d5b05bf5c81da2678 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java
@@ -135,4 +135,15 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf {
super(holder);
}
}
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ @Override
+ public boolean isRabid() {
+ return getHandle().isRabid();
+ }
+
+ @Override
+ public void setRabid(boolean isRabid) {
+ getHandle().setRabid(isRabid);
+ }
+ // Purpur end - Configurable chance for wolves to spawn rabid
}
diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
index 9774c9c72a910ccd919b903d92f4068c06d199d7..5300a513a295d472752d31a6e8af48bb64b06704 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -514,6 +514,15 @@ public class CraftEventFactory {
}
craftServer.getPluginManager().callEvent(event);
+ // Purpur start - Ridables
+ if (player != null) {
+ switch (action) {
+ case LEFT_CLICK_BLOCK, LEFT_CLICK_AIR -> player.processClick(InteractionHand.MAIN_HAND);
+ case RIGHT_CLICK_BLOCK, RIGHT_CLICK_AIR -> player.processClick(InteractionHand.OFF_HAND);
+ }
+ }
+ // Purpur end - Ridables
+
return event;
}
@@ -1046,6 +1055,7 @@ public class CraftEventFactory {
EntityDamageEvent event;
if (damager != null) {
event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, critical);
+ damager.processClick(InteractionHand.MAIN_HAND); // Purpur - Ridables
} else {
event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions);
}
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
index c00ddfe41439954fa0fd87c0933f274c8a752eb6..5fde4c71915f55f100cd30406803d6fca5b573dc 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
@@ -150,8 +150,19 @@ public class CraftContainer extends AbstractContainerMenu {
case PLAYER:
case CHEST:
case ENDER_CHEST:
+ // Purpur start - Barrels and enderchests 6 rows
+ this.delegate = new ChestMenu(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? net.minecraft.world.inventory.MenuType.GENERIC_9x6 : net.minecraft.world.inventory.MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9);
+ break;
case BARREL:
- this.delegate = new ChestMenu(net.minecraft.world.inventory.MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9);
+ this.delegate = new ChestMenu(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) {
+ case 6 -> net.minecraft.world.inventory.MenuType.GENERIC_9x6;
+ case 5 -> net.minecraft.world.inventory.MenuType.GENERIC_9x5;
+ case 4 -> net.minecraft.world.inventory.MenuType.GENERIC_9x4;
+ case 2 -> net.minecraft.world.inventory.MenuType.GENERIC_9x2;
+ case 1 -> net.minecraft.world.inventory.MenuType.GENERIC_9x1;
+ default -> net.minecraft.world.inventory.MenuType.GENERIC_9x3;
+ }, windowId, bottom, top, top.getContainerSize() / 9);
+ // Purpur end - Barrels and enderchests 6 rows
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 f850e6cea92edc87ed54cf54488b5ebb606913ed..19180c08f41db939c1a9f0caeb62e5beb1117f69 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 - Barrels and enderchests 6 rows
for (int i = 0; i < this.getSize(); i++) {
if (i >= items.length) {
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java
index 792cb6adf0c7a6335cc5985fce8bed2e0f1149af..5734c5caffda79383ae30df20c3defb51b87f39e 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java
@@ -19,6 +19,10 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn
private int repairCost;
private int repairCostAmount;
private int maximumRepairCost;
+ // Purpur start - Anvil API
+ private boolean bypassCost;
+ private boolean canDoUnsafeEnchants;
+ // Purpur end - Anvil API
public CraftInventoryAnvil(Location location, Container inventory, Container resultInventory) {
super(inventory, resultInventory);
@@ -27,6 +31,10 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn
this.repairCost = CraftInventoryAnvil.DEFAULT_REPAIR_COST;
this.repairCostAmount = CraftInventoryAnvil.DEFAULT_REPAIR_COST_AMOUNT;
this.maximumRepairCost = CraftInventoryAnvil.DEFAULT_MAXIMUM_REPAIR_COST;
+ // Purpur start - Anvil API
+ this.bypassCost = false;
+ this.canDoUnsafeEnchants = false;
+ // Purpur end - Anvil API
}
@Override
@@ -113,4 +121,30 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn
consumer.accept(cav);
}
}
+
+ // Purpur start - Anvil API
+ @Override
+ public boolean canBypassCost() {
+ this.syncWithArbitraryViewValue((cav) -> this.bypassCost = cav.canBypassCost());
+ return this.bypassCost;
+ }
+
+ @Override
+ public void setBypassCost(boolean bypassCost) {
+ this.bypassCost = bypassCost;
+ this.syncViews((cav) -> cav.setBypassCost(bypassCost));
+ }
+
+ @Override
+ public boolean canDoUnsafeEnchants() {
+ this.syncWithArbitraryViewValue((cav) -> this.canDoUnsafeEnchants = cav.canDoUnsafeEnchants());
+ return this.canDoUnsafeEnchants;
+ }
+
+ @Override
+ public void setDoUnsafeEnchants(boolean canDoUnsafeEnchants) {
+ this.canDoUnsafeEnchants = canDoUnsafeEnchants;
+ this.syncViews((cav) -> cav.setDoUnsafeEnchants(canDoUnsafeEnchants));
+ }
+ // Purpur end - Anvil API
}
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
index 4a7b4b603c2c555c320911e618a4392628c2c754..0a3ad330e1d40cbe9e03fc3585ee309fe9f5c930 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
@@ -667,4 +667,285 @@ public final class CraftItemStack extends ItemStack {
}
// Paper end - data component API
+
+ // Purpur start - ItemStack convenience methods
+ @Override
+ public String getDisplayName() {
+ return getItemMeta().getDisplayName();
+ }
+
+ @Override
+ public void setDisplayName(String name) {
+ ItemMeta itemMeta = getItemMeta();
+ itemMeta.setDisplayName(name);
+ setItemMeta(itemMeta);
+ }
+
+ @Override
+ public boolean hasDisplayName() {
+ return hasItemMeta() && getItemMeta().hasDisplayName();
+ }
+
+ @Override
+ public String getLocalizedName() {
+ return getItemMeta().getLocalizedName();
+ }
+
+ @Override
+ public void setLocalizedName(String name) {
+ ItemMeta itemMeta = getItemMeta();
+ itemMeta.setLocalizedName(name);
+ setItemMeta(itemMeta);
+ }
+
+ @Override
+ public boolean hasLocalizedName() {
+ return hasItemMeta() && getItemMeta().hasLocalizedName();
+ }
+
+ @Override
+ public boolean hasLore() {
+ return hasItemMeta() && getItemMeta().hasLore();
+ }
+
+ @Override
+ public boolean hasEnchant(Enchantment ench) {
+ return hasItemMeta() && getItemMeta().hasEnchant(ench);
+ }
+
+ @Override
+ public int getEnchantLevel(Enchantment ench) {
+ return getItemMeta().getEnchantLevel(ench);
+ }
+
+ @Override
+ public Map<Enchantment, Integer> getEnchants() {
+ return getItemMeta().getEnchants();
+ }
+
+ @Override
+ public boolean addEnchant(Enchantment ench, int level, boolean ignoreLevelRestriction) {
+ ItemMeta itemMeta = getItemMeta();
+ boolean result = itemMeta.addEnchant(ench, level, ignoreLevelRestriction);
+ setItemMeta(itemMeta);
+ return result;
+ }
+
+ @Override
+ public boolean removeEnchant(Enchantment ench) {
+ ItemMeta itemMeta = getItemMeta();
+ boolean result = itemMeta.removeEnchant(ench);
+ setItemMeta(itemMeta);
+ return result;
+ }
+
+ @Override
+ public boolean hasEnchants() {
+ return hasItemMeta() && getItemMeta().hasEnchants();
+ }
+
+ @Override
+ public boolean hasConflictingEnchant(Enchantment ench) {
+ return hasItemMeta() && getItemMeta().hasConflictingEnchant(ench);
+ }
+
+ @Override
+ public void setCustomModelData(Integer data) {
+ ItemMeta itemMeta = getItemMeta();
+ itemMeta.setCustomModelData(data);
+ setItemMeta(itemMeta);
+ }
+
+ @Override
+ public int getCustomModelData() {
+ return getItemMeta().getCustomModelData();
+ }
+
+ @Override
+ public boolean hasCustomModelData() {
+ return hasItemMeta() && getItemMeta().hasCustomModelData();
+ }
+
+ @Override
+ public boolean hasBlockData() {
+ return hasItemMeta() && ((org.bukkit.inventory.meta.BlockDataMeta) getItemMeta()).hasBlockData();
+ }
+
+ @Override
+ public org.bukkit.block.data.BlockData getBlockData(Material material) {
+ return ((org.bukkit.inventory.meta.BlockDataMeta) getItemMeta()).getBlockData(material);
+ }
+
+ @Override
+ public void setBlockData(org.bukkit.block.data.BlockData blockData) {
+ ItemMeta itemMeta = getItemMeta();
+ ((org.bukkit.inventory.meta.BlockDataMeta) itemMeta).setBlockData(blockData);
+ setItemMeta(itemMeta);
+ }
+
+ @Override
+ public int getRepairCost() {
+ return ((org.bukkit.inventory.meta.Repairable) getItemMeta()).getRepairCost();
+ }
+
+ @Override
+ public void setRepairCost(int cost) {
+ ItemMeta itemMeta = getItemMeta();
+ ((org.bukkit.inventory.meta.Repairable) itemMeta).setRepairCost(cost);
+ setItemMeta(itemMeta);
+ }
+
+ @Override
+ public boolean hasRepairCost() {
+ return hasItemMeta() && ((org.bukkit.inventory.meta.Repairable) getItemMeta()).hasRepairCost();
+ }
+
+ @Override
+ public boolean isUnbreakable() {
+ return hasItemMeta() && getItemMeta().isUnbreakable();
+ }
+
+ @Override
+ public void setUnbreakable(boolean unbreakable) {
+ ItemMeta itemMeta = getItemMeta();
+ itemMeta.setUnbreakable(unbreakable);
+ setItemMeta(itemMeta);
+ }
+
+ @Override
+ public boolean hasAttributeModifiers() {
+ return hasItemMeta() && getItemMeta().hasAttributeModifiers();
+ }
+
+ @Override
+ public com.google.common.collect.Multimap<org.bukkit.attribute.Attribute, org.bukkit.attribute.AttributeModifier> getAttributeModifiers() {
+ return getItemMeta().getAttributeModifiers();
+ }
+
+ @Override
+ public com.google.common.collect.Multimap<org.bukkit.attribute.Attribute, org.bukkit.attribute.AttributeModifier> getAttributeModifiers(org.bukkit.inventory.EquipmentSlot slot) {
+ return getItemMeta().getAttributeModifiers(slot);
+ }
+
+ @Override
+ public java.util.Collection<org.bukkit.attribute.AttributeModifier> getAttributeModifiers(org.bukkit.attribute.Attribute attribute) {
+ return getItemMeta().getAttributeModifiers(attribute);
+ }
+
+ @Override
+ public boolean addAttributeModifier(org.bukkit.attribute.Attribute attribute, org.bukkit.attribute.AttributeModifier modifier) {
+ ItemMeta itemMeta = getItemMeta();
+ boolean result = itemMeta.addAttributeModifier(attribute, modifier);
+ setItemMeta(itemMeta);
+ return result;
+ }
+
+ @Override
+ public void setAttributeModifiers(com.google.common.collect.Multimap<org.bukkit.attribute.Attribute, org.bukkit.attribute.AttributeModifier> attributeModifiers) {
+ ItemMeta itemMeta = getItemMeta();
+ itemMeta.setAttributeModifiers(attributeModifiers);
+ setItemMeta(itemMeta);
+ }
+
+ @Override
+ public boolean removeAttributeModifier(org.bukkit.attribute.Attribute attribute) {
+ ItemMeta itemMeta = getItemMeta();
+ boolean result = itemMeta.removeAttributeModifier(attribute);
+ setItemMeta(itemMeta);
+ return result;
+ }
+
+ @Override
+ public boolean removeAttributeModifier(org.bukkit.inventory.EquipmentSlot slot) {
+ ItemMeta itemMeta = getItemMeta();
+ boolean result = itemMeta.removeAttributeModifier(slot);
+ setItemMeta(itemMeta);
+ return result;
+ }
+
+ @Override
+ public boolean removeAttributeModifier(org.bukkit.attribute.Attribute attribute, org.bukkit.attribute.AttributeModifier modifier) {
+ ItemMeta itemMeta = getItemMeta();
+ boolean result = itemMeta.removeAttributeModifier(attribute, modifier);
+ setItemMeta(itemMeta);
+ return result;
+ }
+
+ @Override
+ public boolean hasDamage() {
+ return hasItemMeta() && ((org.bukkit.inventory.meta.Damageable) getItemMeta()).hasDamage();
+ }
+
+ @Override
+ public int getDamage() {
+ return ((org.bukkit.inventory.meta.Damageable) getItemMeta()).getDamage();
+ }
+
+ @Override
+ public void setDamage(int damage) {
+ ItemMeta itemMeta = getItemMeta();
+ ((org.bukkit.inventory.meta.Damageable) itemMeta).setDamage(damage);
+ setItemMeta(itemMeta);
+ }
+
+ @Override
+ public void repair() {
+ repair(1);
+ }
+
+ @Override
+ public boolean damage() {
+ return damage(1);
+ }
+
+ @Override
+ public void repair(int amount) {
+ damage(-amount);
+ }
+
+ @Override
+ public boolean damage(int amount) {
+ return damage(amount, false);
+ }
+
+ @Override
+ public boolean damage(int amount, boolean ignoreUnbreaking) {
+ org.bukkit.inventory.meta.Damageable damageable = (org.bukkit.inventory.meta.Damageable) getItemMeta();
+ if (amount > 0) {
+ int unbreaking = getEnchantLevel(Enchantment.UNBREAKING);
+ int reduce = 0;
+ for (int i = 0; unbreaking > 0 && i < amount; ++i) {
+ if (reduceDamage(java.util.concurrent.ThreadLocalRandom.current(), unbreaking)) {
+ ++reduce;
+ }
+ }
+ amount -= reduce;
+ if (amount <= 0) {
+ return isBroke(damageable.getDamage());
+ }
+ }
+ int damage = damageable.getDamage() + amount;
+ damageable.setDamage(damage);
+ setItemMeta((ItemMeta) damageable);
+ return isBroke(damage);
+ }
+
+ private boolean isBroke(int damage) {
+ if (damage > getType().getMaxDurability()) {
+ if (getAmount() > 0) {
+ // ensure it "breaks"
+ setAmount(0);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private boolean reduceDamage(java.util.Random random, int unbreaking) {
+ if (getType().isArmor()) {
+ return random.nextFloat() < 0.6F;
+ }
+ return random.nextInt(unbreaking + 1) > 0;
+ }
+ // Purpur end - ItemStack convenience methods
}
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
index 4864e2016cb1d377425297fd1c52b383632cb59e..953d64d128a53eb9ec2ff55140dde5cb80326044 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
@@ -36,6 +36,7 @@ public interface CraftRecipe extends Recipe {
stack = Ingredient.of(((RecipeChoice.MaterialChoice) bukkit).getChoices().stream().map((mat) -> CraftItemType.bukkitToMinecraft(mat)));
} else if (bukkit instanceof RecipeChoice.ExactChoice) {
stack = Ingredient.ofStacks(((RecipeChoice.ExactChoice) bukkit).getChoices().stream().map((mat) -> CraftItemStack.asNMSCopy(mat)).toList());
+ stack.predicate = ((RecipeChoice.ExactChoice) bukkit).getPredicate(); // Purpur - Add predicate to recipe's ExactChoice ingredient
// Paper start - support "empty" choices - legacy method that spigot might incorrectly call
// Their impl of Ingredient.of() will error, ingredients need at least one entry.
// Callers running into this exception may have passed an incorrect empty() recipe choice to a non-empty slot or
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java b/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java
index 2b3844f5ecc1bfbf3f5dab1c00d06f8be27f39e9..4e535c287137ceedd4d48047b03f4578c33467b0 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java
@@ -73,4 +73,26 @@ public class CraftAnvilView extends CraftInventoryView<AnvilMenu, AnvilInventory
this.setMaximumRepairCost(legacy.getMaximumRepairCost());
}
}
+
+ // Purpur start - Anvil API
+ @Override
+ public boolean canBypassCost() {
+ return this.container.bypassCost;
+ }
+
+ @Override
+ public void setBypassCost(boolean bypassCost) {
+ this.container.bypassCost = bypassCost;
+ }
+
+ @Override
+ public boolean canDoUnsafeEnchants() {
+ return this.container.canDoUnsafeEnchants;
+ }
+
+ @Override
+ public void setDoUnsafeEnchants(boolean canDoUnsafeEnchants) {
+ this.container.canDoUnsafeEnchants = canDoUnsafeEnchants;
+ }
+ // Purpur end - Anvil API
}
diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java b/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java
index db8d8e2a07296d62c3097f02b03319e2e1ba9394..4ba4d52961b4ba61fe2db130ee4368ff358920d4 100644
--- a/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java
+++ b/src/main/java/org/bukkit/craftbukkit/legacy/MaterialRerouting.java
@@ -708,4 +708,32 @@ public class MaterialRerouting {
meta.setCanPlaceOn(materials);
}
// Paper end
+ // Purpur start - Adopt MaterialRerouting
+ // Method added post 1.13, no-op (https://github.com/PurpurMC/Purpur/pull/570)
+ public static void addFuel(Server server, Material material, int burnTime) {
+ server.addFuel(material, burnTime);
+ }
+
+ // Method added post 1.13, no-op (https://github.com/PurpurMC/Purpur/pull/570)
+ public static void removeFuel(Server server, Material material) {
+ server.removeFuel(material);
+ }
+
+ // Method added post 1.13, no-op (https://github.com/PurpurMC/Purpur/pull/570)
+ @RerouteStatic("org/bukkit/Bukkit")
+ public static void addFuel(Material material, int burnTime) {
+ Bukkit.addFuel(material, burnTime);
+ }
+
+ // Method added post 1.13, no-op (https://github.com/PurpurMC/Purpur/pull/570)
+ @RerouteStatic("org/bukkit/Bukkit")
+ public static void removeFuel(Material material) {
+ Bukkit.removeFuel(material);
+ }
+
+ // Method added post 1.13, no-op (https://github.com/PurpurMC/Purpur/commit/607d909efba516893072b782c0393c53d048210e)
+ public static BlockData getBlockData(ItemStack itemStack, Material material) {
+ return itemStack.getBlockData(MaterialRerouting.transformToBlockType(material));
+ }
+ // Purpur end - Adopt MaterialRerouting
}
diff --git a/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java
index a573499fb96cb08d96e0b20602fc423bfd0e2f52..eb9cbc29181ffc83acd556f26f2c0b4f227808ab 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 - Explorer Map API
+ @Override
+ public boolean isExplorerMap() {
+ return this.worldMap.isExplorerMap;
+ }
+ // Purpur end - Explorer Map API
}
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 a38d464b470bacd9280fb4ae69f3600cbd5dbe22..a36b0b535252c06fc426535ad2138e23fa2e47fd 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 - Gamemode extra permissions
+ 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 - Gamemode extra permissions
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/resources/log4j2.xml b/src/main/resources/log4j2.xml
index d2a75850af9c6ad2aca66a5f994f1b587d73eac4..a056aa167887abef9e6d531a9edd2cda433567d2 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -2,7 +2,16 @@
<Configuration status="WARN" shutdownHook="disable">
<Appenders>
<Queue name="ServerGuiConsole">
- <PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />
+ <!-- Purpur start - copied from TerminalConsole -->
+ <PatternLayout>
+ <LoggerNamePatternSelector defaultPattern="%highlightGUIError{[%d{HH:mm:ss} %level]: [%logger] %stripAnsi{%msg}%n%xEx{full}}">
+ <!-- Log root, Minecraft, Mojang and Bukkit loggers without prefix -->
+ <!-- Disable prefix for various plugins that bypass the plugin logger -->
+ <PatternMatch key=",net.minecraft.,Minecraft,com.mojang.,com.sk89q.,ru.tehkode.,Minecraft.AWE"
+ pattern="%highlightGUIError{[%d{HH:mm:ss} %level]: %stripAnsi{%msg}%n%xEx{full}}" />
+ </LoggerNamePatternSelector>
+ </PatternLayout>
+ <!-- Purpur end -->
</Queue>
<TerminalConsole name="TerminalConsole">
<PatternLayout>
diff --git a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java
index fe08e446e86d53cef6eecc33cd484e93adc42871..54fff448fded12cd6110583f0c255f2ca86388da 100644
--- a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java
+++ b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java
@@ -46,6 +46,7 @@ public class MinecraftCommandPermissionsTest {
Set<String> foundPerms = new HashSet<>();
for (CommandNode<CommandSourceStack> child : root.getChildren()) {
final String vanillaPerm = VanillaCommandWrapper.getPermission(child);
+ if (TO_SKIP.contains(vanillaPerm)) continue; // Purpur - Skip junit tests for purpur commands
if (!perms.contains(vanillaPerm)) {
missing.add("Missing permission for " + child.getName() + " (" + vanillaPerm + ") command");
} else {
@@ -58,6 +59,25 @@ public class MinecraftCommandPermissionsTest {
}
private static final List<String> TO_SKIP = List.of(
+ // Purpur start - Skip junit tests for purpur commands
+ "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 - Skip junit tests for purpur commands
"minecraft.command.selector"
);