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/0004-Purpur-Server-Paper-Changes.patch
Dreeam e7b5078dce Updated Upstream (Paper/Gale/Purpur)
Upstream has released updates that appear to apply and compile correctly

Paper Changes:
PaperMC/Paper@742968e0 [ci/skip] Increase outdated build delay to two weeks (#12063)

Gale Changes:
Dreeam-qwq/Gale@57be1e6f Updated Upstream (Paper)

Purpur Changes:
PurpurMC/Purpur@36a18d54 fix(mobs/ravager): add missing blocks to griefable list
PurpurMC/Purpur@fdc28eed Updated Upstream (Paper)
PurpurMC/Purpur@d8aadde1 [ci/skip] Update README.md (#1642)
PurpurMC/Purpur@9680ad8e [ci/skip] clean up unsafe enchants diff
2025-02-06 19:36:44 -05:00

2035 lines
93 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Github Actions <no-reply@github.com>
Date: Thu, 16 Jan 2025 11:21:12 +0000
Subject: [PATCH] Purpur Server Paper Changes
Original license: MIT
Original project: https://github.com/PurpurMC/Purpur
Commit: 9680ad8e578abc81fd92f1bb2b30357eab103057
Patches listed below are removed in this patch, They exists in Gale or Leaf:
* "Rebrand.patch"
* "com/destroystokyo/paper/Metrics.java.patch"
- Rebrand
* "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..ede7cb766079031a2c75f3846aa654e28daa5b76
--- /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);
+ }
+}
\ No newline at end of file
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 6bdc683b5ade408ee27f1d6636b4d60c8c89cb7c..bc6d3898d8784e50a0e2264bbb5bde63add61f4e 100644
--- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
+++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
@@ -136,6 +136,10 @@ public class MobGoalHelper {
static {
// TODO these kinda should be checked on each release, in case obfuscation changes
deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee");
+ // Purpur start - 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 f0fce4113fb07c64adbec029d177c236cbdcbae8..94dc9f97de086e3ab51d1b000eb116cf868a75db 100644
--- a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java
+++ b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java
@@ -78,10 +78,10 @@ public class PaperPluginsCommand extends BukkitCommand {
this.setAliases(Arrays.asList("pl"));
}
- private static <T> List<Component> formatProviders(TreeMap<String, PluginProvider<T>> plugins) {
+ private static <T> List<Component> formatProviders(TreeMap<String, PluginProvider<T>> plugins, @NotNull CommandSender sender) { // Purpur - Improve output of plugins command
List<Component> components = new ArrayList<>(plugins.size());
for (PluginProvider<T> entry : plugins.values()) {
- components.add(formatProvider(entry));
+ components.add(formatProvider(entry, sender)); // Purpur - Improve output of plugins command
}
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 - Improve output of plugins command
TextComponent.Builder builder = Component.text();
if (provider instanceof SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) {
builder.append(LEGACY_PLUGIN_STAR);
@@ -117,13 +117,65 @@ public class PaperPluginsCommand extends BukkitCommand {
String name = provider.getMeta().getName();
Component pluginName = Component.text(name, fromStatus(provider))
- .clickEvent(ClickEvent.runCommand("/version " + name));
+ // Purpur start - Improve output of plugins command
+ .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);
return builder.build();
}
+ // Purpur start - Improve output of plugins command
+ @NotNull
+ private static TextComponent getAuthors(@NotNull final PluginMeta pluginMeta) {
+ TextComponent.Builder builder = Component.text();
+ List<String> authors = pluginMeta.getAuthors();
+
+ for (int i = 0; i < authors.size(); i++) {
+ if (i > 0) {
+ builder.append(Component.text(i < authors.size() - 1 ? ", " : " and ", NamedTextColor.WHITE));
+ }
+
+ builder.append(Component.text(authors.get(i), NamedTextColor.GREEN));
+ }
+
+ return builder.build();
+ }
+ // Purpur end - Improve output of plugins command
+
private static Component asPlainComponents(String strings) {
net.kyori.adventure.text.TextComponent.Builder builder = Component.text();
for (String string : strings.split("\n")) {
@@ -182,24 +234,24 @@ public class PaperPluginsCommand extends BukkitCommand {
}
}
- Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE);
+ //Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE); // Purpur - Improve output of plugins command
//.append(INFO_ICON_START.hoverEvent(SERVER_PLUGIN_INFO)); TODO: Add docs
- sender.sendMessage(infoMessage);
+ //sender.sendMessage(infoMessage); // Purpur - Improve output of plugins command
- if (!paperPlugins.isEmpty()) {
- sender.sendMessage(PAPER_HEADER);
- }
+ //if (!paperPlugins.isEmpty()) { // Purpur - Improve output of plugins command
+ sender.sendMessage(PAPER_HEADER.append(Component.text(" (%s):".formatted(paperPlugins.size())))); // Purpur - Improve output of plugins command
+ //} // Purpur - Improve output of plugins command
- for (Component component : formatProviders(paperPlugins)) {
+ for (Component component : formatProviders(paperPlugins, sender)) { // Purpur - Improve output of plugins command
sender.sendMessage(component);
}
- if (!spigotPlugins.isEmpty()) {
- sender.sendMessage(BUKKIT_HEADER);
- }
+ //if (!spigotPlugins.isEmpty()) { // Purpur - Improve output of plugins command
+ sender.sendMessage(BUKKIT_HEADER.append(Component.text(" (%s):".formatted(spigotPlugins.size())))); // Purpur - Improve output of plugins command
+ //} // Purpur - Improve output of plugins command
- for (Component component : formatProviders(spigotPlugins)) {
+ for (Component component : formatProviders(spigotPlugins, sender)) { // Purpur - Improve output of plugins command
sender.sendMessage(component);
}
diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java
index 352d62385e56d5805510596ec9424e5d14336861..b4d4ad2dc7d719d72c0786791f803fbcf0982d1f 100644
--- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java
+++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java
@@ -267,6 +267,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
for (final NodePath path : RemovedConfigurations.REMOVED_WORLD_PATHS) {
builder.addAction(path, TransformAction.remove());
}
+ org.purpurmc.purpur.configuration.transformation.VoidDamageHeightMigration.apply(builder, contextMap); // Purpur - Configurable void damage height and damage
builder.build().apply(node);
final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder();
diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
index d7c9acaffdcff5e35e026ae90a3e521bab13b074..a426ba82af695426952bb5e04fa721e6ccff2f89 100644
--- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
+++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
@@ -398,6 +398,7 @@ public class WorldConfiguration extends ConfigurationPart {
public boolean useVanillaWorldScoreboardNameColoring = false;
}
+ @Setting(org.purpurmc.purpur.configuration.transformation.VoidDamageHeightMigration.ENVIRONMENT_KEY) // Purpur - Configurable void damage height and damage
public Environment environment;
public class Environment extends ConfigurationPart {
@@ -407,7 +408,9 @@ public class WorldConfiguration extends ConfigurationPart {
public boolean disableExplosionKnockback = false;
public boolean generateFlatBedrock = false;
public FrostedIce frostedIce;
+ @Setting(org.purpurmc.purpur.configuration.transformation.VoidDamageHeightMigration.VOID_DAMAGE_KEY) // Purpur - Configurable void damage height and damage
public DoubleOr.Disabled voidDamageAmount = new DoubleOr.Disabled(OptionalDouble.of(4));
+ @Setting(org.purpurmc.purpur.configuration.transformation.VoidDamageHeightMigration.VOID_DAMAGE_MIN_HEIGHT_OFFSET_KEY) // Purpur - Configurable void damage height and damage
public double voidDamageMinBuildHeightOffset = -64.0;
public class FrostedIce extends ConfigurationPart {
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 94ca0407303c4493ab4928b12ec6ecc75aaca549..f2d87c12dd19210ce7e2147fada5c10191008632 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
@@ -363,14 +363,26 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa
@Override
public Location getLocation() {
+ // Purpur start - OfflinePlayer API
+ if (this.isOnline()) {
+ return this.getPlayer().getLocation();
+ }
+ // Purpur end - OfflinePlayer API
+
CompoundTag data = this.getData();
if (data == null) {
return null;
}
- if (data.contains("Pos") && data.contains("Rotation")) {
- ListTag position = (ListTag) data.get("Pos");
- ListTag rotation = (ListTag) data.get("Rotation");
+ // Purpur start - OfflinePlayer API
+ //if (data.contains("Pos") && data.contains("Rotation")) {
+ ListTag position = data.getList("Pos", net.minecraft.nbt.Tag.TAG_DOUBLE);
+ ListTag rotation = data.getList("Rotation", net.minecraft.nbt.Tag.TAG_FLOAT);
+
+ if (position.isEmpty() && rotation.isEmpty()) {
+ return null;
+ }
+ // Purpur end - OfflinePlayer API
UUID uuid = new UUID(data.getLong("WorldUUIDMost"), data.getLong("WorldUUIDLeast"));
@@ -381,9 +393,9 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa
rotation.getFloat(0),
rotation.getFloat(1)
);
- }
+ //} // Purpur - OfflinePlayer API
- return null;
+ //return null; // Purpur - OfflinePlayer API
}
@Override
@@ -626,4 +638,191 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa
manager.save();
}
}
+
+ // Purpur start - OfflinePlayer API
+ @Override
+ public boolean getAllowFlight() {
+ if (this.isOnline()) {
+ return this.getPlayer().getAllowFlight();
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return false;
+ if (!data.contains("abilities")) return false;
+ CompoundTag abilities = data.getCompound("abilities");
+ return abilities.getByte("mayfly") == (byte) 1;
+ }
+ }
+
+ @Override
+ public void setAllowFlight(boolean flight) {
+ if (this.isOnline()) {
+ this.getPlayer().setAllowFlight(flight);
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return;
+ if (!data.contains("abilities")) return;
+ CompoundTag abilities = data.getCompound("abilities");
+ abilities.putByte("mayfly", (byte) (flight ? 1 : 0));
+ data.put("abilities", abilities);
+ save(data);
+ }
+ }
+
+ @Override
+ public boolean isFlying() {
+ if (this.isOnline()) {
+ return this.isFlying();
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return false;
+ if (!data.contains("abilities")) return false;
+ CompoundTag abilities = data.getCompound("abilities");
+ return abilities.getByte("flying") == (byte) 1;
+ }
+ }
+
+ @Override
+ public void setFlying(boolean value) {
+ if (this.isOnline()) {
+ this.getPlayer().setFlying(value);
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return;
+ if (!data.contains("abilities")) return;
+ CompoundTag abilities = data.getCompound("abilities");
+ abilities.putByte("mayfly", (byte) (value ? 1 : 0));
+ data.put("abilities", abilities);
+ save(data);
+ }
+ }
+
+ @Override
+ public void setFlySpeed(float value) throws IllegalArgumentException {
+ if (value < -1f || value > 1f) throw new IllegalArgumentException("FlySpeed needs to be between -1 and 1");
+ if (this.isOnline()) {
+ this.getPlayer().setFlySpeed(value);
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return;
+ if (!data.contains("abilities")) return;
+ CompoundTag abilities = data.getCompound("abilities");
+ abilities.putFloat("flySpeed", value);
+ data.put("abilities", abilities);
+ save(data);
+ }
+ }
+
+ @Override
+ public float getFlySpeed() {
+ if (this.isOnline()) {
+ return this.getPlayer().getFlySpeed();
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return 0;
+ if (!data.contains("abilities")) return 0;
+ CompoundTag abilities = data.getCompound("abilities");
+ return abilities.getFloat("flySpeed");
+ }
+ }
+
+ @Override
+ public void setWalkSpeed(float value) throws IllegalArgumentException {
+ if (value < -1f || value > 1f) throw new IllegalArgumentException("WalkSpeed needs to be between -1 and 1");
+ if (this.isOnline()) {
+ this.getPlayer().setWalkSpeed(value);
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return;
+ if (!data.contains("abilities")) return;
+ CompoundTag abilities = data.getCompound("abilities");
+ abilities.putFloat("walkSpeed", value);
+ data.put("abilities", abilities);
+ save(data);
+ }
+ }
+
+ @Override
+ public float getWalkSpeed() {
+ if (this.isOnline()) {
+ return this.getPlayer().getWalkSpeed();
+ } else {
+ CompoundTag data = this.getData();
+ if (data == null) return 0;
+ if (!data.contains("abilities")) return 0;
+ CompoundTag abilities = data.getCompound("abilities");
+ return abilities.getFloat("walkSpeed");
+ }
+ }
+
+ @Override
+ public boolean teleportOffline(Location destination) {
+ if (this.isOnline()) {
+ return this.getPlayer().teleport(destination);
+ } else {
+ return setLocation(destination);
+ }
+ }
+
+ @Override
+ public boolean teleportOffline(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause){
+ if (this.isOnline()) {
+ return this.getPlayer().teleport(destination, cause);
+ } else {
+ return setLocation(destination);
+ }
+ }
+
+ @Override
+ public java.util.concurrent.CompletableFuture<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 5ad91693178b5645c98804ab04a56e85a28eb426..60389075b86a7c030e59fbc4c493a4f31012a66e 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -428,6 +428,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());
@@ -1089,6 +1103,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))
@@ -1104,6 +1119,7 @@ public final class CraftServer implements Server {
}
}
world.spigotConfig.init(); // Spigot
+ world.purpurConfig.init(); // Purpur - Purpur config files
}
Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
@@ -1121,6 +1137,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");
@@ -1652,6 +1669,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");
@@ -3069,6 +3140,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() {
org.spigotmc.RestartCommand.restart();
@@ -3361,4 +3444,18 @@ public final class CraftServer implements Server {
return MinecraftServer.lastTickOversleepTime;
}
// Gale end - YAPFA - last tick time - API
+
+ // 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 284b58ece65d9cf6411a79abb748f1dfa52edc92..ba5797e9e518d7a1054bad0c7a1fb10b9eaf32bd 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -2373,6 +2373,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(io.papermc.paper.util.MCUtil.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(), 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 - 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 d0becb56a9911ef4cc55ae8d7c47832f442ad52f..bdce5f9a589fedf8c7a59da9cde56056413dd723 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -176,6 +176,13 @@ public class Main {
.describedAs("Jar file");
// Paper end
+ // 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
// Paper start
acceptsAll(asList("server-name"), "Name of the server")
.withRequiredArg()
@@ -259,7 +266,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 1a2a05160ba51d9c75f1ae6ae61d944d81428722..a86b026f2f420637d125cf697bcd07bf314c98aa 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 tileEntity) {
super(world, tileEntity);
+ // Purpur start - load bees to be able to modify them individually - Stored Bee API
+ for(BeehiveBlockEntity.BeeData data : tileEntity.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.getTileEntityFromWorld());
+ 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 c1759aeb3e6ad0e4eb66cba3da1b120dd1dce812..a663962e5181e89286caa18f537c1f5758b41623 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.getTileEntityFromWorld();
- 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 4e56018b64d11f76c8da43fd8f85c6de72204e36..36cec3ed39807e85013e4e3b98c979d7af37ce58 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
@@ -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 - 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 d657fd2c507a5b215aeab0a5f3e9c2ee892a27c8..3604d92c122b5c8be823098ce7b91e57e976589c 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 - Add back player spawned endermite API
}
@Override
public void setPlayerSpawned(boolean playerSpawned) {
- // Nop
+ getHandle().setPlayerSpawned(playerSpawned); // Purpur - Add back player spawned endermite API
}
// 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 659f0f9c7e45e5174f4736c4878a78d330b1d4ee..0d46585c0128cea7265870de5fe260681c65d7c3 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
@@ -90,6 +90,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");
@@ -249,6 +268,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;
}
@@ -1333,4 +1353,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
}
}
// Paper end - broadcast hurt animation
+
+ // 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 a396157548a5b3c3e86206c35789bb40346c701c..469b99bede578b03a1b711b4404a88bf85d7e7ca 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
@@ -281,6 +281,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 63cae1a2e95d8da17c45c4404a8dd0ca6a413c39..464a3713845548473a357ea66c6147b10ff2cb16 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 - 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 7a3d982b133f8cdaeb936cf40f92565f0f7f6dd0..8b0faa08411ee1f336641b161acd3412c886dc2b 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
@@ -151,4 +151,53 @@ public class CraftItem extends CraftEntity implements Item {
public String toString() {
return "CraftItem";
}
+
+ // 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 4f98d138a275a6c34528b7a5148ef265bc38d6b5..3a9d9b7526b2bf0fbd4e0d7886b3d849a6dcfee9 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
@@ -523,7 +523,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
net.minecraft.server.level.ServerPlayer entityPlayer = killer == null ? null : ((CraftPlayer) killer).getHandle();
getHandle().lastHurtByPlayer = entityPlayer;
getHandle().lastHurtByMob = entityPlayer;
- getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : 100; // 100 value taken from EntityLiving#damageEntity
+ getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : getHandle().level().purpurConfig.mobLastHurtByPlayerTime; // 100 value taken from EntityLiving#damageEntity // Purpur - Config for mob last hurt by player time
}
// Paper end
@@ -1211,4 +1211,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
return this.getHandle().canUseSlot(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot));
}
// Paper end - Expose canUseSlot
+
+ // Purpur start - API for any mob to burn daylight
+ @Override
+ public boolean shouldBurnInDay() {
+ return this.getHandle().shouldBurnInDay();
+ }
+
+ @Override
+ public void setShouldBurnInDay(final boolean shouldBurnInDay) {
+ this.getHandle().setShouldBurnInDay(shouldBurnInDay);
+ }
+ // Purpur end - API for any mob to burn daylight
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
index 351f42842b780d053cd2e5bad9ae299449141b10..054d2c2b93c43faeeaf56f482eb7b9431a6190df 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 - 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 baffa036078bfc38505f9008a54a5abff65704db..d84d716629d93e264bc40c565e9fedd1ce38722c 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -591,10 +591,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)) {
@@ -1433,6 +1438,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;
}
@@ -1454,6 +1460,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;
}
@@ -2752,6 +2759,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
return this.getHandle().getAbilities().walkingSpeed * 2f;
}
+ // Purpur start - OfflinePlayer API
+ @Override
+ public boolean teleportOffline(@NotNull Location destination) {
+ return this.teleport(destination);
+ }
+
+ @Override
+ public boolean teleportOffline(Location destination, PlayerTeleportEvent.TeleportCause cause) {
+ return this.teleport(destination, cause);
+ }
+
+ @Override
+ public java.util.concurrent.CompletableFuture<Boolean> teleportOfflineAsync(@NotNull Location destination) {
+ return this.teleportAsync(destination);
+ }
+
+ @Override
+ public java.util.concurrent.CompletableFuture<Boolean> teleportOfflineAsync(@NotNull Location destination, PlayerTeleportEvent.TeleportCause cause) {
+ return this.teleportAsync(destination, cause);
+ }
+ // Purpur end - OfflinePlayer API
+
private void validateSpeed(float value) {
Preconditions.checkArgument(value <= 1f && value >= -1f, "Speed value (%s) need to be between -1f and 1f", value);
}
@@ -3581,4 +3610,75 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
handle.containerMenu.broadcastChanges();
return new PaperPlayerGiveResult(leftovers.build(), drops.build());
}
+
+ // 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(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()));
+ }
+ // 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 4ce2373ff71c3c1b8951646e057587a3ab09e145..997b8e5059569de4ee8e70127c5d6019ce53afe3 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 - 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 aaddce10e1d41531939d1e7f3d717b458ec1b7ab..d65a3bee4671e9e21769ba03f5e65c7312b23580 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
@@ -373,4 +373,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager {
getHandle().getGossips().gossips.clear();
}
// Paper end
+
+ // 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 7881c6253c1d652c0c0d54a9a8accdf0a1ff0f3e..fe8be71121324f64346174922c7bc7f5d3a9de69 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 - 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 c1b7f1281fbd41e765d2c1881763ca25b20e924d..53c620e717d39ef16f44c9697ac4809ac1fdfa6a 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java
@@ -145,4 +145,15 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf {
return this.getKey().hashCode();
}
}
+ // 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 d7a52220e9525502163f5ee6afbadf2baaae6190..1b7fdbecf9c28732d5196236980e87fa737a0769 100644
--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
@@ -602,6 +602,15 @@ public class CraftEventFactory {
// Paper end
craftServer.getPluginManager().callEvent(event);
+ // Purpur start - Ridables
+ 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 - Ridables
+
return event;
}
@@ -1131,7 +1140,7 @@ public class CraftEventFactory {
return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.LAVA, bukkitDamageSource, modifiers, modifierFunctions, cancelled);
} else if (source.getDirectBlock() != null) {
DamageCause cause;
- if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL)) {
+ if (source.is(DamageTypes.CACTUS) || source.is(DamageTypes.SWEET_BERRY_BUSH) || source.is(DamageTypes.STALAGMITE) || source.is(DamageTypes.FALLING_STALACTITE) || source.is(DamageTypes.FALLING_ANVIL) || source.isStonecutter()) { // Purpur - Stonecutter damage
cause = DamageCause.CONTACT;
} else if (source.is(DamageTypes.HOT_FLOOR)) {
cause = DamageCause.HOT_FLOOR;
@@ -1191,6 +1200,7 @@ public class CraftEventFactory {
EntityDamageEvent event;
if (damager != null) {
event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, critical);
+ damager.processClick(InteractionHand.MAIN_HAND); // Purpur - 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 1ce328bed5cf3d087a3f7dc9236153381d758493..364afc994443f6c64af4f9ebbe210da63e18681c 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
@@ -145,8 +145,19 @@ public class CraftContainer extends AbstractContainerMenu {
case PLAYER:
case CHEST:
case ENDER_CHEST:
+ // Purpur start - 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 c6159c70f7a37b9bffe268b91905ce848d1d2927..8b4f8a475faafe3b8a479160888145c4aa603a27 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 3799973696eabbdc992bee4ff24175fc28ec8d7c..ae68b1d72be45503acc8c7a52b20d95d7b651f06 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
@@ -666,4 +666,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 f86c95a13dff012de5db3e41ac261e9e8d44d9f3..1db0b790d824e419bb5fb6ab1f3003e120f9763b 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/view/CraftAnvilView.java
@@ -75,4 +75,26 @@ public class CraftAnvilView extends CraftInventoryView<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 cf0920e5f84b35647882fb963e9972af4e8427e0..2949cd29c7fa707835044a4b61eae150df56296e 100644
--- a/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java
+++ b/src/main/java/org/bukkit/craftbukkit/map/CraftMapRenderer.java
@@ -49,4 +49,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 b3169c551b8410f5861f9db0543c785439ecba7c..377b6bdc80019f0c824c8fd35a8f228315bafe9f 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/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java b/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java
new file mode 100644
index 0000000000000000000000000000000000000000..a04d23bd98075cd65a24d4de8d18281d1668480f
--- /dev/null
+++ b/src/main/java/org/purpurmc/purpur/configuration/transformation/VoidDamageHeightMigration.java
@@ -0,0 +1,67 @@
+package org.purpurmc.purpur.configuration.transformation;
+
+import io.papermc.paper.configuration.Configurations;
+import io.papermc.paper.configuration.PaperConfigurations;
+import io.papermc.paper.configuration.type.number.DoubleOr;
+import java.util.OptionalDouble;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.purpurmc.purpur.PurpurConfig;
+import org.spongepowered.configurate.ConfigurateException;
+import org.spongepowered.configurate.ConfigurationNode;
+import org.spongepowered.configurate.NodePath;
+import org.spongepowered.configurate.transformation.ConfigurationTransformation;
+import org.spongepowered.configurate.transformation.TransformAction;
+
+import static org.spongepowered.configurate.NodePath.path;
+
+public class VoidDamageHeightMigration implements TransformAction {
+
+ public static boolean HAS_BEEN_REGISTERED = false;
+
+ public static final String ENVIRONMENT_KEY = "environment";
+ public static final String VOID_DAMAGE_KEY = "void-damage-amount";
+ public static final String VOID_DAMAGE_MIN_HEIGHT_OFFSET_KEY = "void-damage-min-build-height-offset";
+ public static final double DEFAULT_VOID_DAMAGE_HEIGHT = -64.0D;
+ public static final double DEFAULT_VOID_DAMAGE = 4.0D;
+
+ private final String worldName;
+
+ private VoidDamageHeightMigration(String worldName) {
+ this.worldName = PaperConfigurations.WORLD_DEFAULTS.equals(worldName) ? "default" : worldName;
+ }
+
+ @Override
+ public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) throws ConfigurateException {
+ String purpurVoidDamageHeightPath = "world-settings." + this.worldName + ".gameplay-mechanics.void-damage-height";
+ ConfigurationNode voidDamageMinHeightOffsetNode = value.node(ENVIRONMENT_KEY, VOID_DAMAGE_MIN_HEIGHT_OFFSET_KEY);
+ if (PurpurConfig.config.contains(purpurVoidDamageHeightPath)) {
+ double purpurVoidDamageHeight = PurpurConfig.config.getDouble(purpurVoidDamageHeightPath);
+ if (purpurVoidDamageHeight != DEFAULT_VOID_DAMAGE_HEIGHT && (voidDamageMinHeightOffsetNode.empty() || voidDamageMinHeightOffsetNode.getDouble() == DEFAULT_VOID_DAMAGE_HEIGHT)) {
+ voidDamageMinHeightOffsetNode.raw(null);
+ voidDamageMinHeightOffsetNode.set(purpurVoidDamageHeight);
+ }
+ PurpurConfig.config.set(purpurVoidDamageHeightPath, null);
+ }
+
+ String purpurVoidDamagePath = "world-settings." + this.worldName + ".gameplay-mechanics.void-damage-dealt";
+ ConfigurationNode voidDamageNode = value.node(ENVIRONMENT_KEY, VOID_DAMAGE_KEY);
+ if (PurpurConfig.config.contains(purpurVoidDamagePath)) {
+ double purpurVoidDamage = PurpurConfig.config.getDouble(purpurVoidDamagePath);
+ if (purpurVoidDamage != DEFAULT_VOID_DAMAGE && (voidDamageNode.empty() || voidDamageNode.getDouble() == DEFAULT_VOID_DAMAGE)) {
+ voidDamageNode.raw(null);
+ voidDamageNode.set(new DoubleOr.Disabled(OptionalDouble.of(purpurVoidDamage)));
+ }
+ PurpurConfig.config.set(purpurVoidDamagePath, null);
+ }
+
+ return null;
+ }
+
+ public static void apply(final ConfigurationTransformation.Builder builder, final Configurations.ContextMap contextMap) {
+ if (PurpurConfig.version < 36) {
+ HAS_BEEN_REGISTERED = true;
+ builder.addAction(path(), new VoidDamageHeightMigration(contextMap.require(Configurations.WORLD_NAME)));
+ }
+ }
+
+}
diff --git a/src/main/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 75ed5050f72c001d6eab117a2c0b352a413548bd..2764d4204d51a3615f27b8bfd7b6675294465155 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"
);