43/103 rejected minecraft source files applied

(idk what i was counting in the previous commit...)
This commit is contained in:
granny
2026-03-11 21:06:08 -07:00
parent 5eb960544c
commit 1f72458912
71 changed files with 1302 additions and 1656 deletions

View File

@@ -0,0 +1,53 @@
--- a/net/minecraft/commands/CommandSourceStack.java
+++ b/net/minecraft/commands/CommandSourceStack.java
@@ -430,6 +_,19 @@
}
// CraftBukkit end
+ // Purpur start - Gamemode extra permissions
+ public boolean testPermission(net.minecraft.server.permissions.Permission permission, String bukkitPermission) {
+ if (hasPermission(permission, bukkitPermission)) {
+ return true;
+ }
+ net.kyori.adventure.text.Component permissionMessage = getLevel().getServer().server.permissionMessage();
+ if (!permissionMessage.equals(net.kyori.adventure.text.Component.empty())) {
+ sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(permissionMessage.replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("<permission>").replacement(bukkitPermission).build())));
+ }
+ return false;
+ }
+ // Purpur end - Gamemode extra permissions
+
public Vec3 getPosition() {
return this.worldPosition;
}
@@ -512,6 +_,30 @@
}
}
}
+
+ // Purpur start - Purpur config files
+ public void sendSuccess(@Nullable String message) {
+ sendSuccess(message, false);
+ }
+
+ public void sendSuccess(@Nullable String message, boolean broadcastToOps) {
+ if (message == null) {
+ return;
+ }
+ sendSuccess(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), broadcastToOps);
+ }
+
+ public void sendSuccess(net.kyori.adventure.text.@Nullable Component message) {
+ sendSuccess(message, false);
+ }
+
+ public void sendSuccess(net.kyori.adventure.text.@Nullable Component message, boolean broadcastToOps) {
+ if (message == null) {
+ return;
+ }
+ sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(message), broadcastToOps);
+ }
+ // Purpur end - Purpur config files
public void sendSuccess(final Supplier<Component> messageSupplier, final boolean broadcast) {
boolean shouldSendSystemMessage = this.source.acceptsSuccess() && !this.silent;

View File

@@ -0,0 +1,48 @@
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -268,11 +_,11 @@
JfrCommand.register(this.dispatcher);
}
- if (SharedConstants.DEBUG_CHASE_COMMAND) {
+ if (org.purpurmc.purpur.PurpurConfig.registerMinecraftDisabledCommands || SharedConstants.DEBUG_CHASE_COMMAND) { // Purpur - register disabled minecraft commands
ChaseCommand.register(this.dispatcher);
}
- if (SharedConstants.DEBUG_DEV_COMMANDS || SharedConstants.IS_RUNNING_IN_IDE) {
+ if (org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands || SharedConstants.DEBUG_DEV_COMMANDS || SharedConstants.IS_RUNNING_IN_IDE) { // Purpur - register minecraft debug commands
RaidCommand.register(this.dispatcher, context);
DebugPathCommand.register(this.dispatcher);
DebugMobSpawningCommand.register(this.dispatcher);
@@ -300,6 +_,14 @@
StopCommand.register(this.dispatcher);
TransferCommand.register(this.dispatcher);
WhitelistCommand.register(this.dispatcher);
+ org.purpurmc.purpur.command.CreditsCommand.register(this.dispatcher); // Purpur - Add credits command
+ org.purpurmc.purpur.command.DemoCommand.register(this.dispatcher); // Purpur - Add demo command
+ org.purpurmc.purpur.command.PingCommand.register(this.dispatcher); // Purpur - Add ping command
+ org.purpurmc.purpur.command.UptimeCommand.register(this.dispatcher); // Purpur - Add uptime command
+ org.purpurmc.purpur.command.TPSBarCommand.register(this.dispatcher); // Purpur - Implement TPSBar
+ org.purpurmc.purpur.command.CompassCommand.register(this.dispatcher); // Purpur - Add compass command
+ org.purpurmc.purpur.command.RamBarCommand.register(this.dispatcher); // Purpur - Add rambar command
+ org.purpurmc.purpur.command.RamCommand.register(this.dispatcher); // Purpur - Add ram command
}
if (commandSelection.includeIntegrated) {
@@ -522,6 +_,7 @@
private void runSync(ServerPlayer player, java.util.Collection<String> bukkit, RootCommandNode<CommandSourceStack> root) {
// Paper end - Perf: Async command map building
new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent<CommandSourceStack>(player.getBukkitEntity(), (RootCommandNode) root, true).callEvent(); // Paper - Brigadier API
+ if (org.bukkit.event.player.PlayerCommandSendEvent.getHandlerList().getRegisteredListeners().length > 0) { // Purpur - Skip events if there's no listeners
org.bukkit.event.player.PlayerCommandSendEvent event = new org.bukkit.event.player.PlayerCommandSendEvent(player.getBukkitEntity(), new java.util.LinkedHashSet<>(bukkit));
event.getPlayer().getServer().getPluginManager().callEvent(event);
@@ -532,6 +_,8 @@
}
}
// CraftBukkit end
+ } // Purpur - Skip events if there's no listeners
+
player.connection.send(new ClientboundCommandsPacket(root, COMMAND_NODE_INSPECTOR));
}

View File

@@ -0,0 +1,45 @@
--- a/net/minecraft/commands/arguments/selector/EntitySelector.java
+++ b/net/minecraft/commands/arguments/selector/EntitySelector.java
@@ -206,26 +_,27 @@
this.checkPermissions(sender);
if (this.playerName != null) {
ServerPlayer result = sender.getServer().getPlayerList().getPlayerByName(this.playerName);
- return result == null ? List.of() : List.of(result);
+ return result == null || !canSee(sender, result) ? List.of() : List.of(result); // Purpur - Hide hidden players from entity selector
} else if (this.entityUUID != null) {
ServerPlayer result = sender.getServer().getPlayerList().getPlayer(this.entityUUID);
- return result == null ? List.of() : List.of(result);
+ return result == null || !canSee(sender, result) ? List.of() : List.of(result); // Purpur - Hide hidden players from entity selector
} else {
Vec3 pos = this.position.apply(sender.getPosition());
AABB absoluteAabb = this.getAbsoluteAabb(pos);
Predicate<Entity> predicate = this.getPredicate(pos, absoluteAabb, null);
if (this.currentEntity) {
- return sender.getEntity() instanceof ServerPlayer player && predicate.test(player) ? List.of(player) : List.of();
+ return sender.getEntity() instanceof ServerPlayer player && predicate.test(player) && canSee(sender, player) ? List.of(player) : List.of(); // Purpur - Hide hidden players from entity selector
} else {
int limit = this.getResultLimit();
List<ServerPlayer> result;
if (this.isWorldLimited()) {
result = sender.getLevel().getPlayers(predicate, limit);
+ result.removeIf(entityplayer3 -> !canSee(sender, entityplayer3)); // Purpur - Hide hidden players from entity selector
} else {
result = new ObjectArrayList<>();
for (ServerPlayer player : sender.getServer().getPlayerList().getPlayers()) {
- if (predicate.test(player)) {
+ if (predicate.test(player) && canSee(sender, player)) { // Purpur - Hide hidden players from entity selector
result.add(player);
if (result.size() >= limit) {
return result;
@@ -283,4 +_,10 @@
public static Component joinNames(final List<? extends Entity> entities) {
return ComponentUtils.formatList(entities, Entity::getDisplayName);
}
+
+ // Purpur start - Hide hidden players from entity selector
+ private boolean canSee(CommandSourceStack sender, ServerPlayer target) {
+ return !org.purpurmc.purpur.PurpurConfig.hideHiddenPlayersFromEntitySelector || !(sender.getEntity() instanceof ServerPlayer player) || player.getBukkitEntity().canSee(target.getBukkitEntity());
+ }
+ // Purpur end - Hide hidden players from entity selector
}

View File

@@ -0,0 +1,15 @@
--- a/net/minecraft/core/BlockPos.java
+++ b/net/minecraft/core/BlockPos.java
@@ -58,6 +_,12 @@
private static final int X_OFFSET = PACKED_Y_LENGTH + PACKED_HORIZONTAL_LENGTH;
public static final int MAX_HORIZONTAL_COORDINATE = (1 << PACKED_HORIZONTAL_LENGTH) / 2 - 1;
+ // Purpur start - Ridables
+ public BlockPos(net.minecraft.world.entity.Entity entity) {
+ super(entity.getBlockX(), entity.getBlockY(), entity.getBlockZ());
+ }
+ // Purpur end - Ridables
+
public BlockPos(final int x, final int y, final int z) {
super(x, y, z);
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/EquipmentDispenseItemBehavior.java
@@ -31,7 +_,7 @@
return false;
} else {
LivingEntity target = entities.getFirst();
- EquipmentSlot slot = target.getEquipmentSlotForItem(dispensed);
+ EquipmentSlot slot = source.level().purpurConfig.dispenserApplyCursedArmor ? target.getEquipmentSlotForItem(dispensed) : target.getEquipmentSlotForDispenserItem(dispensed); if (slot == null) return false; // Purpur - Dispenser curse of binding protection
ItemStack equip = dispensed.copyWithCount(1); // Paper - shrink below and single item in event
// CraftBukkit start
net.minecraft.world.level.Level world = source.level();

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/gametest/framework/TestCommand.java
+++ b/net/minecraft/gametest/framework/TestCommand.java
@@ -424,7 +_,7 @@
)
)
);
- if (SharedConstants.IS_RUNNING_IN_IDE) {
+ if (org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands || SharedConstants.IS_RUNNING_IN_IDE) { // Purpur - register minecraft debug commands
testCommand = testCommand.then(
Commands.literal("export")
.then(

View File

@@ -0,0 +1,16 @@
--- a/net/minecraft/server/Main.java
+++ b/net/minecraft/server/Main.java
@@ -104,6 +_,13 @@
JvmProfiler.INSTANCE.start(Environment.SERVER);
}
+ // Purpur start - Add toggle for enchant level clamping - load config files early
+ org.bukkit.configuration.file.YamlConfiguration purpurConfiguration = io.papermc.paper.configuration.PaperConfigurations.loadLegacyConfigFile((File) options.valueOf("purpur-settings"));
+ org.purpurmc.purpur.PurpurConfig.clampEnchantLevels = purpurConfiguration.getBoolean("settings.enchantment.clamp-levels", true);
+ org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands = purpurConfiguration.getBoolean("settings.register-minecraft-debug-commands"); // Purpur - register minecraft debug commands
+ org.purpurmc.purpur.PurpurConfig.registerMinecraftDisabledCommands = purpurConfiguration.getBoolean("settings.register-minecraft-disabled-commands"); // Purpur - register disabled minecraft commands
+ // Purpur end - Add toggle for enchant level clamping - load config files early
+
io.papermc.paper.plugin.PluginInitializerManager.load(options); // Paper
Bootstrap.bootStrap();
Bootstrap.validate();

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/server/PlayerAdvancements.java
+++ b/net/minecraft/server/PlayerAdvancements.java
@@ -148,6 +_,7 @@
AdvancementHolder advancement = manager.get(id);
if (advancement == null) {
if (!id.getNamespace().equals(Identifier.DEFAULT_NAMESPACE)) return; // CraftBukkit
+ if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressIgnoredAdvancementWarnings) // Purpur - Logger settings (suppressing pointless logs)
LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", id, this.playerSavePath);
} else {
this.startProgress(advancement, progress);
@@ -195,6 +_,7 @@
holder.value().display().ifPresent(display -> {
// Paper start - Add Adventure message to PlayerAdvancementDoneEvent
if (event.message() != null && this.player.level().getGameRules().get(GameRules.SHOW_ADVANCEMENT_MESSAGES)) {
+ if (org.purpurmc.purpur.PurpurConfig.advancementOnlyBroadcastToAffectedPlayer) this.player.sendMessage(message); else // Purpur - Configurable broadcast settings
this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false);
// Paper end
}

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/server/commands/EnchantCommand.java
+++ b/net/minecraft/server/commands/EnchantCommand.java
@@ -67,7 +_,7 @@
final CommandSourceStack source, final Collection<? extends Entity> targets, final Holder<Enchantment> enchantmentHolder, final int level
) throws CommandSyntaxException {
Enchantment enchantment = enchantmentHolder.value();
- if (level > enchantment.getMaxLevel()) {
+ if (!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && level > enchantment.getMaxLevel()) { // Purpur - Config to allow unsafe enchants
throw ERROR_LEVEL_TOO_HIGH.create(level, enchantment.getMaxLevel());
} else {
int success = 0;
@@ -77,7 +_,7 @@
ItemStack item = target.getMainHandItem();
if (!item.isEmpty()) {
if (enchantment.canEnchant(item)
- && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(item).keySet(), enchantmentHolder)) {
+ && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(item).keySet(), enchantmentHolder) || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && !mainHandItem.hasEnchantment(enchantment))) { // Purpur - Config to allow unsafe enchants
item.enchant(enchantmentHolder, level);
success++;
} else if (targets.size() == 1) {

View File

@@ -0,0 +1,21 @@
--- a/net/minecraft/server/commands/GameModeCommand.java
+++ b/net/minecraft/server/commands/GameModeCommand.java
@@ -47,6 +_,18 @@
}
private static int setMode(final CommandContext<CommandSourceStack> context, final Collection<ServerPlayer> players, final GameType type) {
+ // Purpur start - Gamemode extra permissions
+ if (org.purpurmc.purpur.PurpurConfig.commandGamemodeRequiresPermission) {
+ String gamemode = type.getName();
+ CommandSourceStack sender = context.getSource();
+ if (!sender.testPermission(Permissions.COMMANDS_GAMEMASTER, "minecraft.command.gamemode." + gamemode)) {
+ return 0;
+ }
+ if (sender.getEntity() instanceof ServerPlayer player && (players.size() > 1 || !players.contains(player)) && !sender.testPermission(Permissions.COMMANDS_GAMEMASTER, "minecraft.command.gamemode." + gamemode + ".other")) {
+ return 0;
+ }
+ }
+ // Purpur end - Gamemode extra permissions
int count = 0;
for (ServerPlayer player : players) {

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/server/commands/GiveCommand.java
+++ b/net/minecraft/server/commands/GiveCommand.java
@@ -61,6 +_,7 @@
remaining -= size;
ItemStack copyToDrop = prototypeItemStack.copyWithCount(size);
boolean added = player.getInventory().add(copyToDrop);
+ if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) continue; // Purpur - add config option for toggling give command dropping
if (added && copyToDrop.isEmpty()) {
ItemEntity drop = player.drop(prototypeItemStack.copy(), false, false, false, null); // Paper - do not fire PlayerDropItemEvent for /give command
if (drop != null) {

View File

@@ -0,0 +1,135 @@
--- a/net/minecraft/server/gui/MinecraftServerGui.java
+++ b/net/minecraft/server/gui/MinecraftServerGui.java
@@ -40,6 +_,11 @@
private Thread logAppenderThread;
private final Collection<Runnable> finalizers = Lists.newArrayList();
private final AtomicBoolean isClosing = new AtomicBoolean();
+ // Purpur start - GUI Improvements
+ private final CommandHistory history = new CommandHistory();
+ private String currentCommand = "";
+ private int historyIndex = 0;
+ // Purpur end - GUI Improvements
public static MinecraftServerGui showFrameFor(final DedicatedServer server) {
try {
@@ -47,7 +_,7 @@
} catch (Exception var3) {
}
- final JFrame frame = new JFrame("Minecraft server");
+ final JFrame frame = new JFrame("Purpur Minecraft server"); // Purpur - Improve GUI
final MinecraftServerGui gui = new MinecraftServerGui(server);
frame.setDefaultCloseOperation(2);
frame.add(gui);
@@ -55,7 +_,7 @@
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// Paper start - Improve ServerGUI
- frame.setName("Minecraft server");
+ frame.setName("Purpur Minecraft server"); // Purpur - Improve GUI
try {
frame.setIconImage(javax.imageio.ImageIO.read(java.util.Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png"))));
} catch (java.io.IOException ignore) {
@@ -65,7 +_,7 @@
@Override
public void windowClosing(final WindowEvent event) {
if (!gui.isClosing.getAndSet(true)) {
- frame.setTitle("Minecraft server - shutting down!");
+ frame.setTitle("Purpur Minecraft server - shutting down!"); // Purpur - Improve GUI
server.halt(true);
gui.runFinalizers();
}
@@ -113,7 +_,7 @@
private JComponent buildChatPanel() {
JPanel panel = new JPanel(new BorderLayout());
- JTextArea chatArea = new JTextArea();
+ org.purpurmc.purpur.gui.JColorTextPane chatArea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur - GUI Improvements
JScrollPane scrollPane = new JScrollPane(chatArea, 22, 30);
chatArea.setEditable(false);
chatArea.setFont(MONOSPACED);
@@ -122,10 +_,43 @@
String text = chatField.getText().trim();
if (!text.isEmpty()) {
this.server.handleConsoleInput(text, this.server.createCommandSourceStack());
+ // Purpur start - GUI Improvements
+ history.add(text);
+ historyIndex = -1;
+ // Purpur end - GUI Improvements
}
chatField.setText("");
});
+ // Purpur start - GUI Improvements
+ chatField.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up");
+ chatField.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down");
+ chatField.getActionMap().put("up", new javax.swing.AbstractAction() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
+ if (historyIndex < 0) {
+ currentCommand = chatField.getText();
+ }
+ if (historyIndex < history.size() - 1) {
+ chatField.setText(history.get(++historyIndex));
+ }
+ }
+ });
+ chatField.getActionMap().put("down", new javax.swing.AbstractAction() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
+ if (historyIndex >= 0) {
+ if (historyIndex == 0) {
+ --historyIndex;
+ chatField.setText(currentCommand);
+ } else {
+ --historyIndex;
+ chatField.setText(history.get(historyIndex));
+ }
+ }
+ }
+ });
+ // Purpur end - GUI Improvements
chatArea.addFocusListener(new FocusAdapter() {
{
Objects.requireNonNull(MinecraftServerGui.this);
@@ -164,7 +_,7 @@
}
private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper
- public void print(final JTextArea console, final JScrollPane scrollPane, final String line) {
+ public void print(final org.purpurmc.purpur.gui.JColorTextPane console, final JScrollPane scrollPane, final String line) {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(() -> this.print(console, scrollPane, line));
} else {
@@ -175,16 +_,29 @@
shouldScroll = scrollBar.getValue() + scrollBar.getSize().getHeight() + MONOSPACED.getSize() * 4 > scrollBar.getMaximum();
}
- try {
+ /*try { // Purpur - GUI Improvements
document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(line).replaceAll(""), null); // CraftBukkit
} catch (BadLocationException var8) {
- }
+ }*/ // Purpur - GUI Improvements
+ console.append(line); // Purpur - GUI Improvements
if (shouldScroll) {
scrollBar.setValue(Integer.MAX_VALUE);
}
}
}
+
+ // Purpur start - GUI Improvements
+ public static class CommandHistory extends java.util.LinkedList<String> {
+ @Override
+ public boolean add(String command) {
+ if (size() > 1000) {
+ remove();
+ }
+ return super.offerFirst(command);
+ }
+ }
+ // Purpur end - GUI Improvements
// Paper start - Add onboarding message for initial server start
private JComponent buildOnboardingPanel() {

View File

@@ -0,0 +1,283 @@
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -447,6 +_,9 @@
public boolean isRealPlayer; // Paper
public com.destroystokyo.paper.event.entity.@Nullable PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent
public org.bukkit.event.player.PlayerQuitEvent.@Nullable QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event
+ private boolean tpsBar = false; // Purpur - Implement TPSBar
+ private boolean compassBar = false; // Purpur - Add compass command
+ private boolean ramBar = false; // Purpur - Implement rambar commands
public ServerPlayer(final MinecraftServer server, final ServerLevel level, final GameProfile gameProfile, final ClientInformation clientInformation) {
super(level, gameProfile);
@@ -490,6 +_,9 @@
this.respawnConfig = input.read("respawn", ServerPlayer.RespawnConfig.CODEC).orElse(null);
this.spawnExtraParticlesOnFall = input.getBooleanOr("spawn_extra_particles_on_fall", false);
this.raidOmenPosition = input.read("raid_omen_position", BlockPos.CODEC).orElse(null);
+ this.tpsBar = input.getBooleanOr("Purpur.TPSBar", false); // Purpur - Implement TPSBar
+ this.compassBar = input.getBooleanOr("Purpur.CompassBar", false); // Purpur - Add compass command
+ this.ramBar = input.getBooleanOr("Purpur.RamBar", false); // Purpur - Implement rambar command
// Paper start - Expand PlayerGameModeChangeEvent
this.loadGameTypes(input);
}
@@ -531,6 +_,9 @@
output.store("ShoulderEntityRight", CompoundTag.CODEC, this.getShoulderEntityRight());
}
this.getBukkitEntity().setExtraData(output); // CraftBukkit
+ output.putBoolean("Purpur.TPSBar", this.tpsBar); // Purpur - Implement TPSBar
+ output.putBoolean("Purpur.CompassBar", this.compassBar); // Purpur - Add compass command
+ output.putBoolean("Purpur.RamBar", this.ramBar); // Purpur - Add rambar command
}
private void saveParentVehicle(final ValueOutput playerOutput) {
@@ -1166,6 +_,7 @@
// Paper - moved up to sendClientboundPlayerCombatKillPacket()
sendClientboundPlayerCombatKillPacket(event.getShowDeathMessages(), deathScreenMessage); // Paper - Expand PlayerDeathEvent
Team team = this.getTeam();
+ if (org.purpurmc.purpur.PurpurConfig.deathMessageOnlyBroadcastToAffectedPlayer) this.sendSystemMessage(deathMessage); else // Purpur - Configurable broadcast settings
if (team == null || team.getDeathMessageVisibility() == Team.Visibility.ALWAYS) {
this.server.getPlayerList().broadcastSystemMessage(deathMessage, false);
} else if (team.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) {
@@ -1274,6 +_,13 @@
if (this.isInvulnerableTo(level, source)) {
return false;
} else {
+ // Purpur start - Add boat fall damage config
+ if (source.is(net.minecraft.tags.DamageTypeTags.IS_FALL)) {
+ if (getRootVehicle() instanceof net.minecraft.world.entity.vehicle.boat.Boat && !level().purpurConfig.boatsDoFallDamage) {
+ return false;
+ }
+ }
+ // Purpur end - Add boat fall damage config
Entity entity = source.getEntity();
if (!( // Paper - split the if statement. If below statement is false, hurtServer would not have been evaluated. Return false.
!(entity instanceof Player playerx && !this.canHarmPlayer(playerx))
@@ -1531,6 +_,7 @@
profiler.pop();
profiler.push("placing");
+ this.portalPos = org.bukkit.craftbukkit.util.CraftLocation.toBlockPosition(exit); // Purpur - Fix stuck in portals
this.setServerLevel(newLevel);
this.connection.internalTeleport(PositionMoveRotation.of(transition), transition.relatives()); // CraftBukkit - use internal teleport without event
this.connection.resetPosition();
@@ -1647,7 +_,7 @@
),
monster -> monster.isPreventingPlayerRest(this.level(), this)
);
- if (!monsters.isEmpty()) {
+ if (!this.level().purpurConfig.playerSleepNearMonsters && !monsters.isEmpty()) { // Purpur - Config to ignore nearby mobs when sleeping
return Either.left(Player.BedSleepingProblem.NOT_SAFE);
}
}
@@ -1687,8 +_,19 @@
CriteriaTriggers.SLEPT_IN_BED.trigger(this);
});
if (!this.level().canSleepThroughNights()) {
- this.sendOverlayMessage(Component.translatable("sleep.not_possible"));
+ // Purpur start - Customizable sleeping actionbar messages
+ Component clientMessage;
+ if (org.purpurmc.purpur.PurpurConfig.sleepNotPossible.isBlank()) {
+ clientMessage = null;
+ } else if (!org.purpurmc.purpur.PurpurConfig.sleepNotPossible.equalsIgnoreCase("default")) {
+ clientMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepNotPossible));
+ } else {
+ clientMessage = Component.translatable("sleep.not_possible");
}
+ if (clientMessage != null) {
+ this.sendOverlayMessage(clientMessage);
+ }// Purpur end - Customizable sleeping actionbar messages
+ }
this.level().updateSleepingPlayerList();
return result;
@@ -1782,6 +_,7 @@
@Override
public void openTextEdit(final SignBlockEntity sign, final boolean isFrontText) {
+ if (level().purpurConfig.signAllowColors) this.connection.send(sign.getTranslatedUpdatePacket(textFilteringEnabled, isFrontText)); // Purpur - Signs allow color codes
this.connection.send(new ClientboundBlockUpdatePacket(this.level(), sign.getBlockPos()));
this.connection.send(new ClientboundOpenSignEditorPacket(sign.getBlockPos(), isFrontText));
}
@@ -2125,6 +_,26 @@
this.lastSentExp = -1; // CraftBukkit - Added to reset
}
+ // Purpur start - Component related conveniences
+ public void sendActionBarMessage(@Nullable String message) {
+ if (message != null && !message.isEmpty()) {
+ sendActionBarMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message));
+ }
+ }
+
+ public void sendActionBarMessage(net.kyori.adventure.text.@Nullable Component message) {
+ if (message != null) {
+ sendActionBarMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message));
+ }
+ }
+
+ public void sendActionBarMessage(@Nullable Component message) {
+ if (message != null) {
+ sendOverlayMessage(message);
+ }
+ }
+ // Purpur end - Component related conveniences
+
@Override
public void completeUsingItem() {
if (!this.useItem.isEmpty() && this.isUsingItem()) {
@@ -2364,6 +_,20 @@
);
}
+ // Purpur start - Component related conveniences
+ public void sendMiniMessage(@Nullable String message) {
+ if (message != null && !message.isEmpty()) {
+ this.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message));
+ }
+ }
+
+ public void sendMessage(net.kyori.adventure.text.@Nullable Component message) {
+ if (message != null) {
+ this.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message));
+ }
+ }
+ // Purpur end - Component related conveniences
+
@Override
public void sendSystemMessage(final Component message) {
this.sendSystemMessage(message, false);
@@ -2511,7 +_,67 @@
public void resetLastActionTime() {
this.lastActionTime = Util.getMillis();
- }
+ this.setAfk(false); // Purpur - AFK API
+ }
+
+ // Purpur start - AFK API
+ private boolean isAfk = false;
+
+ @Override
+ public void setAfk(boolean afk) {
+ if (this.isAfk == afk) {
+ return;
+ }
+
+ String msg = afk ? org.purpurmc.purpur.PurpurConfig.afkBroadcastAway : org.purpurmc.purpur.PurpurConfig.afkBroadcastBack;
+
+ org.purpurmc.purpur.event.PlayerAFKEvent event = new org.purpurmc.purpur.event.PlayerAFKEvent(this.getBukkitEntity(), afk, this.level().purpurConfig.idleTimeoutKick, msg, !org.bukkit.Bukkit.isPrimaryThread());
+ if (!event.callEvent() || event.shouldKick()) {
+ return;
+ }
+
+ this.isAfk = afk;
+
+ if (!afk) {
+ resetLastActionTime();
+ }
+
+ msg = event.getBroadcastMsg();
+ if (msg != null && !msg.isEmpty()) {
+ String playerName = this.getGameProfile().name();
+ if (org.purpurmc.purpur.PurpurConfig.afkBroadcastUseDisplayName) {
+ net.kyori.adventure.text.Component playerDisplayNameComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(this.getBukkitEntity().getDisplayName());
+ playerName = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText().serialize(playerDisplayNameComponent);
+ }
+ server.getPlayerList().broadcastMiniMessage(String.format(msg, playerName), false);
+ }
+
+ if (this.level().purpurConfig.idleTimeoutUpdateTabList) {
+ String scoreboardName = getScoreboardName();
+ String playerListName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().serialize(getBukkitEntity().playerListName());
+ String[] split = playerListName.split(scoreboardName);
+ String prefix = (split.length > 0 ? split[0] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix, "");
+ String suffix = (split.length > 1 ? split[1] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, "");
+ if (afk) {
+ getBukkitEntity().setPlayerListName(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix + prefix + scoreboardName + suffix + org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, true);
+ } else {
+ getBukkitEntity().setPlayerListName(prefix + scoreboardName + suffix, true);
+ }
+ }
+
+ ((ServerLevel) this.level()).updateSleepingPlayerList();
+ }
+
+ @Override
+ public boolean isAfk() {
+ return this.isAfk;
+ }
+
+ @Override
+ public boolean canBeCollidedWith(Entity entity) {
+ return !this.isAfk() && super.canBeCollidedWith(entity);
+ }
+ // Purpur end - AFK API
public ServerStatsCounter getStats() {
return this.stats;
@@ -3136,4 +_,65 @@
return (org.bukkit.craftbukkit.entity.CraftPlayer) super.getBukkitEntity();
}
// CraftBukkit end
+
+ // Purpur start - Add option to teleport to spawn if outside world border
+ public void teleport(org.bukkit.Location to) {
+ this.ejectPassengers();
+ this.stopRiding(true);
+
+ if (this.isSleeping()) {
+ this.stopSleepInBed(true, false);
+ }
+
+ if (this.containerMenu != this.inventoryMenu) {
+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT);
+ }
+
+ ServerLevel toLevel = ((org.bukkit.craftbukkit.CraftWorld) to.getWorld()).getHandle();
+ if (this.level() == toLevel) {
+ this.connection.internalTeleport(to);
+ } else {
+ this.teleport(new TeleportTransition(
+ toLevel,
+ org.bukkit.craftbukkit.util.CraftLocation.toVec3(to),
+ Vec3.ZERO,
+ to.getYaw(),
+ to.getPitch(),
+ net.minecraft.world.entity.Relative.ALL,
+ TeleportTransition.DO_NOTHING,
+ org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN
+ ));
+ }
+ }
+ // Purpur end - Add option to teleport to spawn if outside world border
+
+ // Purpur start - Implement TPSBar
+ public boolean tpsBar() {
+ return this.tpsBar;
+ }
+
+ public void tpsBar(boolean tpsBar) {
+ this.tpsBar = tpsBar;
+ }
+ // Purpur end - Implement TPSBar
+
+ // Purpur start - Add compass command
+ public boolean compassBar() {
+ return this.compassBar;
+ }
+
+ public void compassBar(boolean compassBar) {
+ this.compassBar = compassBar;
+ }
+ // Purpur end - Add compass command
+
+ // Purpur start - Add rambar command
+ public boolean ramBar() {
+ return this.ramBar;
+ }
+
+ public void ramBar(boolean ramBar) {
+ this.ramBar = ramBar;
+ }
+ // Purpur end - Add rambar command
}

View File

@@ -0,0 +1,46 @@
--- a/net/minecraft/server/level/ServerPlayerGameMode.java
+++ b/net/minecraft/server/level/ServerPlayerGameMode.java
@@ -364,6 +_,7 @@
}
return false;
}
+ if (this.player.level().purpurConfig.slabHalfBreak && this.player.isShiftKeyDown() && blockState.getBlock() instanceof net.minecraft.world.level.block.SlabBlock && ((net.minecraft.world.level.block.SlabBlock) blockState.getBlock()).halfBreak(blockState, pos, this.player)) return true; // Purpur - Break individual slabs when sneaking
}
// CraftBukkit end
@@ -494,6 +_,7 @@
public InteractionResult useItemOn(
final ServerPlayer player, final Level level, final ItemStack itemStack, final InteractionHand hand, final BlockHitResult hitResult
) {
+ if (shiftClickMended(itemStack)) return InteractionResult.SUCCESS; // Purpur - Shift right click to use exp for mending
BlockPos pos = hitResult.getBlockPos();
BlockState state = level.getBlockState(pos);
boolean cancelledBlock = false;
@@ -542,7 +_,7 @@
boolean haveSomethingInOurHands = !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty();
boolean suppressUsingBlock = player.isSecondaryUseActive() && haveSomethingInOurHands;
ItemStack usedItemStack = itemStack.copy();
- if (!suppressUsingBlock) {
+ if (!suppressUsingBlock || (player.level().purpurConfig.composterBulkProcess && state.is(net.minecraft.world.level.block.Blocks.COMPOSTER))) { // Purpur - Sneak to bulk process composter
InteractionResult itemUse = state.useItemOn(player.getItemInHand(hand), level, player, hand, hitResult);
if (itemUse.consumesAction()) {
CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, pos, usedItemStack);
@@ -588,4 +_,18 @@
public void setLevel(final ServerLevel newLevel) {
this.level = newLevel;
}
+
+ // Purpur start - Shift right click to use exp for mending
+ public boolean shiftClickMended(ItemStack itemstack) {
+ if (this.player.level().purpurConfig.shiftRightClickRepairsMendingPoints > 0 && this.player.isShiftKeyDown() && this.player.getBukkitEntity().hasPermission("purpur.mending_shift_click")) {
+ int points = Math.min(this.player.totalExperience, this.player.level().purpurConfig.shiftRightClickRepairsMendingPoints);
+ if (points > 0 && itemstack.isDamaged() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MENDING, itemstack) > 0) {
+ this.player.giveExperiencePoints(itemstack.getDamageValue() == 1 ? -2 : -points);
+ this.player.level().addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(this.player.level(), this.player.getX(), this.player.getY(), this.player.getZ(), itemstack.getDamageValue() == 1 ? 1 : points, org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN, this.player, this.player));
+ return true;
+ }
+ }
+ return false;
+ }
+ // Purpur end - Shift right click to use exp for mending
}

View File

@@ -0,0 +1,233 @@
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -354,6 +_,20 @@
}
// Paper end - configuration phase API
+ // Purpur start - AFK API
+ private final com.google.common.cache.LoadingCache<org.bukkit.craftbukkit.entity.CraftPlayer, Boolean> kickPermissionCache = com.google.common.cache.CacheBuilder.newBuilder()
+ .maximumSize(1000)
+ .expireAfterWrite(1, java.util.concurrent.TimeUnit.MINUTES)
+ .build(
+ new com.google.common.cache.CacheLoader<>() {
+ @Override
+ public Boolean load(org.bukkit.craftbukkit.entity.CraftPlayer player) {
+ return player.hasPermission("purpur.bypassIdleKick");
+ }
+ }
+ );
+ // Purpur end - AFK API
+
@Override
public void tick() {
if (this.isDisconnected()) return; // Paper
@@ -372,6 +_,12 @@
&& this.server.playerIdleTimeout() > 0
&& Util.getMillis() - this.player.getLastActionTime() > TimeUnit.MINUTES.toMillis(this.server.playerIdleTimeout())
&& !this.player.wonGame) {
+ // Purpur start - AFK API
+ this.player.setAfk(true);
+ if (!this.player.level().purpurConfig.idleTimeoutKick || (!Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")) && kickPermissionCache.getUnchecked(this.player.getBukkitEntity()))) {
+ return;
+ }
+ // Purpur end - AFK API
this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause
}
}
@@ -682,6 +_,8 @@
this.lastYaw = to.getYaw();
this.lastPitch = to.getPitch();
+ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur - AFK API
+
Location oldTo = to.clone();
PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
this.cserver.getPluginManager().callEvent(event);
@@ -738,6 +_,7 @@
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
if (packet.getId() == this.awaitingTeleport) {
if (this.awaitingPositionFromClient == null) {
+ ServerGamePacketListenerImpl.LOGGER.warn("Disconnected on accept teleport packet. Was not expecting position data from client at this time"); // Purpur - Add more logger output for invalid movement kicks
this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
return;
}
@@ -1306,6 +_,10 @@
final int maxBookPageSize = pageMax.intValue();
final double multiplier = Math.clamp(io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier, 0.3D, 1D);
long byteAllowed = maxBookPageSize;
+ // Purpur start - PlayerBookTooLargeEvent
+ int slot = packet.slot();
+ ItemStack itemstack = Inventory.isHotbarSlot(slot) || slot == Inventory.SLOT_OFFHAND ? this.player.getInventory().getItem(slot) : ItemStack.EMPTY;
+ // Purpur end - PlayerBookTooLargeEvent
for (final String page : pageList) {
final int byteLength = page.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
byteTotal += byteLength;
@@ -1330,7 +_,8 @@
}
if (byteTotal > byteAllowed) {
- ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size());
+ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send too large of a book. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size());
+ org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur - PlayerBookTooLargeEvent
this.disconnectAsync(Component.literal("Book too large!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause // Paper - add proper async disconnect
return;
}
@@ -1349,31 +_,45 @@
Optional<String> title = packet.title();
title.ifPresent(contents::add);
contents.addAll(packet.pages());
+ // Purpur start - Allow color codes in books
+ boolean hasEditPerm = getCraftPlayer().hasPermission("purpur.book.color.edit");
+ boolean hasSignPerm = hasEditPerm || getCraftPlayer().hasPermission("purpur.book.color.sign");
+ // Purpur end - Allow color codes in books
Consumer<List<FilteredText>> handler = title.isPresent()
- ? filteredContents -> this.signBook(filteredContents.get(0), filteredContents.subList(1, filteredContents.size()), slot)
- : filteredContents -> this.updateBookContents(filteredContents, slot);
+ ? filteredContents -> this.signBook(filteredContents.get(0), filteredContents.subList(1, filteredContents.size()), slot, hasSignPerm) // Purpur - Allow color codes in books
+ : filteredContents -> this.updateBookContents(filteredContents, slot, hasEditPerm); // Purpur - Allow color codes in books
this.filterTextPacket(contents).thenAcceptAsync(handler, this.server);
}
}
private void updateBookContents(final List<FilteredText> contents, final int slot) {
+ // Purpur start - Allow color codes in books
+ updateBookContents(contents, slot, false);
+ }
+ private void updateBookContents(List<FilteredText> contents, int slot, boolean hasPerm) {
+ // Purpur end - Allow color codes in books
// CraftBukkit start
ItemStack handItem = this.player.getInventory().getItem(slot);
ItemStack carried = handItem.copy();
// CraftBukkit end
if (carried.has(DataComponents.WRITABLE_BOOK_CONTENT)) {
- List<Filterable<String>> pages = contents.stream().map(this::filterableFromOutgoing).toList();
+ List<Filterable<String>> pages = contents.stream().map(filteredText -> filterableFromOutgoing(filteredText).map(s -> color(s, hasPerm))).toList(); // Purpur - Allow color codes in books
carried.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(pages));
this.player.getInventory().setItem(slot, CraftEventFactory.handleEditBookEvent(this.player, slot, handItem, carried)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent)
}
}
private void signBook(final FilteredText title, final List<FilteredText> contents, final int slot) {
+ // Purpur start - Allow color codes in books
+ signBook(title, contents, slot, false);
+ }
+ private void signBook(final FilteredText title, final List<FilteredText> contents, final int slot, boolean hasPerm) {
+ // Purpur end - Allow color codes in books
ItemStack carried = this.player.getInventory().getItem(slot);
if (carried.has(DataComponents.WRITABLE_BOOK_CONTENT)) {
ItemStack writtenBook = carried.transmuteCopy(Items.WRITTEN_BOOK);
writtenBook.remove(DataComponents.WRITABLE_BOOK_CONTENT);
- List<Filterable<Component>> pages = contents.stream().map(page -> this.filterableFromOutgoing(page).<Component>map(Component::literal)).toList();
+ List<Filterable<Component>> pages = contents.stream().map(page -> this.filterableFromOutgoing(page).map(s -> hexColor(s, hasPerm))).toList(); // Purpur - Allow color codes in books
writtenBook.set(
DataComponents.WRITTEN_BOOK_CONTENT, new WrittenBookContent(this.filterableFromOutgoing(title), this.player.getPlainTextName(), 0, pages, true)
);
@@ -1386,6 +_,16 @@
return this.player.isTextFilteringEnabled() ? Filterable.passThrough(text.filteredOrEmpty()) : Filterable.from(text);
}
+ // Purpur start - Allow color codes in books
+ private Component hexColor(String str, boolean hasPerm) {
+ return hasPerm ? PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().deserialize(str)) : Component.literal(str);
+ }
+
+ private String color(String str, boolean hasPerm) {
+ return hasPerm ? org.bukkit.ChatColor.color(str, false) : str;
+ }
+ // Purpur end - Allow color codes in books
+
@Override
public void handleEntityTagQuery(final ServerboundEntityTagQueryPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
@@ -1425,7 +_,15 @@
@Override
public void handleMovePlayer(final ServerboundMovePlayerPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
- if (containsInvalidValues(packet.getX(0.0), packet.getY(0.0), packet.getZ(0.0), packet.getYRot(0.0F), packet.getXRot(0.0F))) {
+ // Purpur start - Add more logger output for invalid movement kicks
+ boolean invalidX = Double.isNaN(packet.getX(0.0));
+ boolean invalidY = Double.isNaN(packet.getY(0.0));
+ boolean invalidZ = Double.isNaN(packet.getZ(0.0));
+ boolean invalidYaw = !Floats.isFinite(packet.getYRot(0.0F));
+ boolean invalidPitch = !Floats.isFinite(packet.getXRot(0.0F));
+ if (invalidX || invalidY || invalidZ || invalidYaw || invalidPitch) {
+ ServerGamePacketListenerImpl.LOGGER.warn(String.format("Disconnected on move player packet. Invalid data: x=%b, y=%b, z=%b, yaw=%b, pitch=%b", invalidX, invalidY, invalidZ, invalidYaw, invalidPitch));
+ // Purpur end - Add more logger output for invalid movement kicks
this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
} else {
ServerLevel level = this.player.level();
@@ -1609,7 +_,7 @@
movedWrongly = true;
if (event.getLogWarning())
// Paper end
- LOGGER.warn("{} moved wrongly!", this.player.getPlainTextName());
+ LOGGER.warn("{} moved wrongly!, ({})", this.player.getPlainTextName(), verticalDelta); // Purpur - AFK API
} // Paper
}
@@ -1664,6 +_,8 @@
this.lastYaw = to.getYaw();
this.lastPitch = to.getPitch();
+ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetLastActionTime(); // Purpur - AFK API
+
Location oldTo = to.clone();
PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
this.cserver.getPluginManager().callEvent(event);
@@ -1719,6 +_,13 @@
this.player.tryResetCurrentImpulseContext();
}
+ // Purpur start - Dont run with scissors!
+ if (this.player.level().purpurConfig.dontRunWithScissors && this.player.isSprinting() && !(this.player.level().purpurConfig.ignoreScissorsInWater && this.player.isInWater()) && !(this.player.level().purpurConfig.ignoreScissorsInLava && this.player.isInLava()) && (isScissors(this.player.getItemInHand(InteractionHand.MAIN_HAND)) || isScissors(this.player.getItemInHand(InteractionHand.OFF_HAND))) && (int) (Math.random() * 10) == 0) {
+ this.player.hurtServer(this.player.level(), this.player.damageSources().scissors(), (float) this.player.level().purpurConfig.scissorsRunningDamage);
+ if (!org.purpurmc.purpur.PurpurConfig.dontRunWithScissors.isBlank()) this.player.sendActionBarMessage(org.purpurmc.purpur.PurpurConfig.dontRunWithScissors);
+ }
+ // Purpur end - Dont run with scissors!
+
this.player.checkMovementStatistics(this.player.getX() - startX, this.player.getY() - startY, this.player.getZ() - startZ);
this.lastGoodX = this.player.getX();
this.lastGoodY = this.player.getY();
@@ -1739,6 +_,17 @@
}
}
+ // Purpur start - Dont run with scissors!
+ public boolean isScissors(ItemStack stack) {
+ if (!stack.is(Items.SHEARS)) return false;
+
+ Identifier itemModelReference = stack.get(net.minecraft.core.component.DataComponents.ITEM_MODEL);
+ if (itemModelReference != null && itemModelReference.equals(this.player.level().purpurConfig.dontRunWithScissorsItemModelReference)) return true;
+
+ return stack.getOrDefault(DataComponents.CUSTOM_MODEL_DATA, net.minecraft.world.item.component.CustomModelData.EMPTY).equals(net.minecraft.world.item.component.CustomModelData.EMPTY);
+ }
+ // Purpur end - Dont run with scissors!
+
private boolean shouldCheckPlayerMovement(final boolean isFallFlying) {
if (this.isSingleplayerOwner()) {
return false;
@@ -2155,6 +_,7 @@
boolean cancelled;
if (hitResult == null || hitResult.getType() != HitResult.Type.BLOCK) {
+ if (this.player.gameMode.shiftClickMended(itemStack)) return; // Purpur - Shift right click to use exp for mending
org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemStack, hand);
cancelled = event.useItemInHand() == Event.Result.DENY;
} else {
@@ -2800,6 +_,7 @@
if (target != null && level.getWorldBorder().isWithinBounds(target.blockPosition())) {
AABB targetBounds = target.getBoundingBox();
if (this.player.isWithinAttackRange(this.player.getMainHandItem(), targetBounds, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0))) { // Paper - configurable lenience value for interact range
+ if (target instanceof net.minecraft.world.entity.Mob mob) mob.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan
if (!(target instanceof ItemEntity)
&& !(target instanceof ExperienceOrb)
&& (target != this.player || this.player.isSpectator()) // CraftBukkit
@@ -3590,7 +_,7 @@
@Override
public void handleChangeGameMode(final ServerboundChangeGameModePacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level());
- if (!GameModeCommand.PERMISSION_CHECK.check(this.player.permissions())) {
+ if (!GameModeCommand.PERMISSION_CHECK.check(this.player.permissions()) && !player.getBukkitEntity().hasPermission("purpur.debug.f3n")) { // Purpur - Add permission for F3+N debug
LOGGER.warn(
"Player {} tried to change game mode to {} without required permissions",
this.player.getGameProfile().name(),

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -284,7 +_,7 @@
ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!");
ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(name)); // Spigot
} else {
- ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username"));
+ ServerLoginPacketListenerImpl.this.disconnect(org.purpurmc.purpur.PurpurConfig.unverifiedUsername.equals("default") ? Component.translatable("multiplayer.disconnect.unverified_username") : io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.unverifiedUsername))); // Purpur - Config for unverified username message
ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", name);
}
} catch (AuthenticationUnavailableException var4) {

View File

@@ -0,0 +1,56 @@
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -313,6 +_,7 @@
scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam);
}
// Paper end - Configurable player collision
+ org.purpurmc.purpur.task.BossBarTask.addToAll(player); // Purpur - Implement TPSBar
// CraftBukkit start - moved down
LOGGER.info(
"{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", // CraftBukkit - add world name
@@ -436,6 +_,7 @@
}
public net.kyori.adventure.text.@Nullable Component remove(final ServerPlayer player, final net.kyori.adventure.text.Component leaveMessage) {
// Paper end - Fix kick event leave message not being sent
+ org.purpurmc.purpur.task.BossBarTask.removeFromAll(player.getBukkitEntity()); // Purpur - Implement TPSBar
ServerLevel level = player.level();
player.awardStat(Stats.LEAVE_GAME);
// CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it
@@ -771,6 +_,20 @@
}
}
+ // Purpur start - Component related conveniences
+ public void broadcastMiniMessage(@Nullable String message, boolean overlay) {
+ if (message != null && !message.isEmpty()) {
+ this.broadcastMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), overlay);
+ }
+ }
+
+ public void broadcastMessage(net.kyori.adventure.text.@Nullable Component message, boolean overlay) {
+ if (message != null) {
+ this.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), overlay);
+ }
+ }
+ // Purpur end - Component related conveniences
+
public void broadcastAll(final Packet<?> packet, final ResourceKey<Level> dimension) {
for (ServerPlayer player : this.players) {
if (player.level().dimension() == dimension) {
@@ -864,6 +_,7 @@
case ADMINS -> EntityEvent.PERMISSION_LEVEL_ADMINS;
case OWNERS -> EntityEvent.PERMISSION_LEVEL_OWNERS;
};
+ if (eventId < EntityEvent.PERMISSION_LEVEL_OWNERS && player.getBukkitEntity().hasPermission("purpur.debug.f3n")) eventId = EntityEvent.PERMISSION_LEVEL_OWNERS; // Purpur - Add permission for F3+N debug
player.connection.send(new ClientboundEntityEventPacket(player, eventId));
}
@@ -875,7 +_,7 @@
// Paper start - whitelist verify event / login event
public LoginResult canBypassFullServerLogin(final NameAndId nameAndId, final LoginResult currentResult) {
- final boolean shouldKick = this.players.size() >= this.getMaxPlayers() && !this.canBypassPlayerLimit(nameAndId);
+ final boolean shouldKick = this.players.size() >= this.getMaxPlayers() && !(/*player.hasPermission("purpur.joinfullserver") || */this.canBypassPlayerLimit(nameAndId)); // Purpur - Allow player join full server by permission TODO: this hasn't worked for a while, so comment it out until we can reliably check perms of the player joining
final io.papermc.paper.event.player.PlayerServerFullCheckEvent fullCheckEvent = new io.papermc.paper.event.player.PlayerServerFullCheckEvent(
new com.destroystokyo.paper.profile.CraftPlayerProfile(nameAndId),
io.papermc.paper.adventure.PaperAdventure.asAdventure(currentResult.message),

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/server/players/SleepStatus.java
+++ b/net/minecraft/server/players/SleepStatus.java
@@ -15,7 +_,7 @@
public boolean areEnoughDeepSleeping(final int sleepPercentageNeeded, final List<ServerPlayer> players) {
// CraftBukkit start
- int deepSleepers = (int)players.stream().filter(player -> player.isSleepingLongEnough() || player.fauxSleeping).count();
+ int deepSleepers = (int)players.stream().filter(player -> player.isSleepingLongEnough() || player.fauxSleeping || (player.level().purpurConfig.idleTimeoutCountAsSleeping && player.isAfk())).count(); // Purpur - AFK API
boolean anyDeepSleep = players.stream().anyMatch(Player::isSleepingLongEnough);
return anyDeepSleep && deepSleepers >= this.sleepersNeeded(sleepPercentageNeeded);
// CraftBukkit end
@@ -43,7 +_,7 @@
for (ServerPlayer player : players) {
if (!player.isSpectator()) {
this.activePlayers++;
- if (player.isSleeping() || player.fauxSleeping) { // CraftBukkit
+ if (player.isSleeping() || player.fauxSleeping || (player.level().purpurConfig.idleTimeoutCountAsSleeping && player.isAfk())) { // CraftBukkit // Purpur - AFK API
this.sleepingPlayers++;
}
// CraftBukkit start

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/stats/ServerRecipeBook.java
+++ b/net/minecraft/stats/ServerRecipeBook.java
@@ -110,6 +_,7 @@
) {
for (ResourceKey<Recipe<?>> recipe : recipes) {
if (!validator.test(recipe)) {
+ if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressUnrecognizedRecipeErrors) // Purpur - Logger settings (suppressing pointless logs)
LOGGER.error("Tried to load unrecognized recipe: {} removed now.", recipe);
} else {
recipeAddingMethod.accept(recipe);

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/world/damagesource/CombatRules.java
+++ b/net/minecraft/world/damagesource/CombatRules.java
@@ -17,7 +_,7 @@
final LivingEntity victim, final float damage, final DamageSource source, final float totalArmor, final float armorToughness
) {
float toughness = 2.0F + armorToughness / 4.0F;
- float realArmor = Mth.clamp(totalArmor - damage / toughness, totalArmor * 0.2F, 20.0F);
+ float realArmor = Mth.clamp(totalArmor - damage / toughness, totalArmor * 0.2F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur - Add attribute clamping and armor limit config
float armorFraction = realArmor / 25.0F;
ItemStack weaponItem = source.getWeaponItem();
float modifiedArmorFraction;
@@ -32,7 +_,7 @@
}
public static float getDamageAfterMagicAbsorb(final float damage, final float totalMagicArmor) {
- float realArmor = Mth.clamp(totalMagicArmor, 0.0F, 20.0F);
+ float realArmor = Mth.clamp(totalMagicArmor, 0.0F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur - Add attribute clamping and armor limit config
return damage * (1.0F - realArmor / 25.0F);
}
}

View File

@@ -0,0 +1,27 @@
--- a/net/minecraft/world/damagesource/CombatTracker.java
+++ b/net/minecraft/world/damagesource/CombatTracker.java
@@ -66,7 +_,7 @@
final Entity attackerEntity, final Component attackerName, final String messageWithItem, final String messageWithoutItem
) {
ItemStack attackerItem = attackerEntity instanceof LivingEntity livingEntity ? livingEntity.getMainHandItem() : ItemStack.EMPTY;
- return !attackerItem.isEmpty() && attackerItem.has(DataComponents.CUSTOM_NAME)
+ return !attackerItem.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || attackerItem.has(DataComponents.CUSTOM_NAME)) // Purpur - always show item in player death messages
? Component.translatable(messageWithItem, this.mob.getDisplayName(), attackerName, attackerItem.getDisplayName())
: Component.translatable(messageWithoutItem, this.mob.getDisplayName(), attackerName);
}
@@ -109,6 +_,15 @@
Component link = ComponentUtils.wrapInSquareBrackets(Component.translatable(deathMsg + ".link")).withStyle(INTENTIONAL_GAME_DESIGN_STYLE);
return Component.translatable(deathMsg + ".message", this.mob.getDisplayName(), link);
} else {
+ // Purpur start - Dont run with scissors!
+ if (killingSource.isScissors()) {
+ return killingSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgRunWithScissors, this.mob);
+ // Purpur start - Stonecutter damage
+ } else if (killingSource.isStonecutter()) {
+ return killingSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgStonecutter, this.mob);
+ // Purpur end - Stonecutter damage
+ }
+ // Purpur end - Dont run with scissors!
return killingSource.getLocalizedDeathMessage(this.mob);
}
}

View File

@@ -0,0 +1,73 @@
--- a/net/minecraft/world/damagesource/DamageSource.java
+++ b/net/minecraft/world/damagesource/DamageSource.java
@@ -24,6 +_,8 @@
private org.bukkit.block.@Nullable Block eventBlockDamager; // Relevant block set. damageSourcePosition is only used for bad respawn point explosion or custom damage
private org.bukkit.block.@Nullable BlockState fromBlockSnapshot; // Captured block snapshot when the eventBlockDamager is not relevant (e.g. for bad respawn point explosions the block is already removed)
private boolean critical; // Supports arrows and sweeping damage
+ private boolean scissors = false; // Purpur - Dont run with scissors!
+ private boolean stonecutter = false; // Purpur - Stonecutter damage
public DamageSource knownCause(final org.bukkit.event.entity.EntityDamageEvent.DamageCause cause) {
final DamageSource damageSource = this.copy();
@@ -35,6 +_,30 @@
return this.knownCause;
}
+ // Purpur start - Dont run with scissors!
+ public DamageSource scissors() {
+ this.knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.SUICIDE);
+ this.scissors = true;
+ return this;
+ }
+
+ public boolean isScissors() {
+ return this.scissors;
+ }
+ // Purpur end - Dont run with scissors!
+
+ // Purpur start - - Stonecutter damage
+ public DamageSource stonecutter() {
+ this.knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.CONTACT);
+ this.stonecutter = true;
+ return this;
+ }
+
+ public boolean isStonecutter() {
+ return this.stonecutter;
+ }
+ // Purpur end - Stonecutter damage
+
@Nullable
public Entity eventEntityDamager() {
return this.eventEntityDamager;
@@ -94,6 +_,8 @@
damageSource.eventBlockDamager = this.eventBlockDamager;
damageSource.fromBlockSnapshot = this.fromBlockSnapshot;
damageSource.critical = this.critical;
+ damageSource.scissors = this.isScissors(); // Purpur - Dont run with scissors!
+ damageSource.stonecutter = this.isStonecutter(); // Purpur - Stonecutter damage
return damageSource;
}
// CraftBukkit end
@@ -159,11 +_,20 @@
} else {
Component name = this.causingEntity == null ? this.directEntity.getDisplayName() : this.causingEntity.getDisplayName();
ItemStack held = this.causingEntity instanceof LivingEntity livingEntity ? livingEntity.getMainHandItem() : ItemStack.EMPTY;
- return !held.isEmpty() && held.has(DataComponents.CUSTOM_NAME)
+ return !held.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || held.has(DataComponents.CUSTOM_NAME)) // Purpur - always show item in player death messages
? Component.translatable(deathMsg + ".item", victim.getDisplayName(), name, held.getDisplayName())
: Component.translatable(deathMsg, victim.getDisplayName(), name);
}
}
+
+ // Purpur start - Component related conveniences
+ public Component getLocalizedDeathMessage(String str, LivingEntity entity) {
+ net.kyori.adventure.text.Component name = io.papermc.paper.adventure.PaperAdventure.asAdventure(entity.getDisplayName());
+ net.kyori.adventure.text.minimessage.tag.resolver.TagResolver template = net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("player", name);
+ net.kyori.adventure.text.Component component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(str, template);
+ return io.papermc.paper.adventure.PaperAdventure.asVanilla(component);
+ }
+ // Purpur end - Component related conveniences
public String getMsgId() {
return this.type().msgId();

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/effect/HungerMobEffect.java
+++ b/net/minecraft/world/effect/HungerMobEffect.java
@@ -12,7 +_,7 @@
@Override
public boolean applyEffectTick(final ServerLevel serverLevel, final LivingEntity mob, final int amplification) {
if (mob instanceof Player player) {
- player.causeFoodExhaustion(0.005F * (amplification + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent
+ player.causeFoodExhaustion(mob.level().purpurConfig.humanHungerExhaustionAmount * (amplification + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent // Purpur - Config MobEffect by world
}
return true;

View File

@@ -0,0 +1,13 @@
--- a/net/minecraft/world/effect/PoisonMobEffect.java
+++ b/net/minecraft/world/effect/PoisonMobEffect.java
@@ -12,8 +_,8 @@
@Override
public boolean applyEffectTick(final ServerLevel level, final LivingEntity mob, final int amplification) {
- if (mob.getHealth() > 1.0F) {
- mob.hurtServer(level, mob.damageSources().magic().knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.POISON), 1.0F); // CraftBukkit
+ if (mob.getHealth() > mob.level().purpurConfig.entityMinimalHealthPoison) { // Purpur
+ mob.hurtServer(level, mob.damageSources().magic().knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.POISON), mob.level().purpurConfig.entityPoisonDegenerationAmount); // CraftBukkit // Purpur - Config MobEffect by world
}
return true;

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/effect/RegenerationMobEffect.java
+++ b/net/minecraft/world/effect/RegenerationMobEffect.java
@@ -11,7 +_,7 @@
@Override
public boolean applyEffectTick(final ServerLevel level, final LivingEntity mob, final int amplification) {
if (mob.getHealth() < mob.getMaxHealth()) {
- mob.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit
+ mob.heal(mob.level().purpurConfig.entityHealthRegenAmount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit // Purpur - Config MobEffect by world
}
return true;

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/effect/WitherMobEffect.java
+++ b/net/minecraft/world/effect/WitherMobEffect.java
@@ -12,7 +_,7 @@
@Override
public boolean applyEffectTick(final ServerLevel level, final LivingEntity mob, final int amplification) {
- mob.hurtServer(level, mob.damageSources().wither(), 1.0F);
+ mob.hurtServer(level, mob.damageSources().wither(), mob.level().purpurConfig.entityWitherDegenerationAmount); // Purpur - Config MobEffect by world
return true;
}

View File

@@ -1,6 +1,33 @@
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -380,7 +_,7 @@
@@ -168,6 +_,7 @@
SlotProvider,
DebugValueSource,
TypedInstance<EntityType<?>> {
+ public static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur - Configurable entity base attributes
// CraftBukkit start
private static final int CURRENT_LEVEL = 2;
static boolean isLevelAtLeast(ValueInput input, int level) {
@@ -341,8 +_,9 @@
public double xOld;
public double yOld;
public double zOld;
+ public float maxUpStep; // Purpur - Add option to set armorstand step height
public boolean noPhysics;
- public final RandomSource random = SHARED_RANDOM; // Paper - Share random for entities to make them more random
+ public final RandomSource random; // Paper - Share random for entities to make them more random // Add toggle for RNG manipulation
public int tickCount;
private int remainingFireTicks;
private final EntityFluidInteraction fluidInteraction = new EntityFluidInteraction(Set.of(FluidTags.WATER, FluidTags.LAVA));
@@ -374,13 +_,13 @@
public @Nullable PortalProcessor portalProcess;
public int portalCooldown;
private boolean invulnerable;
- protected UUID uuid = Mth.createInsecureUUID(this.random);
- protected String stringUUID = this.uuid.toString();
+ protected UUID uuid; // Purpur - Add toggle for RNG manipulation
+ protected String stringUUID; // Purpur - Add toggle for RNG manipulation
private boolean hasGlowingTag;
private final Set<String> tags = new io.papermc.paper.util.SizeLimitedSet<>(new it.unimi.dsi.fastutil.objects.ObjectOpenHashSet<>(), MAX_ENTITY_TAG_COUNT); // Paper - fully limit tag size - replace set impl
private final double[] pistonDeltas = new double[]{0.0, 0.0, 0.0};
private long pistonDeltasGameTime;
@@ -9,3 +36,141 @@
private float eyeHeight;
public boolean isInPowderSnow;
public boolean wasInPowderSnow;
@@ -426,6 +_,7 @@
private final int despawnTime; // Paper - entity despawn time limit
public int totalEntityAge; // Paper - age-like counter for all entities
public final io.papermc.paper.entity.activation.ActivationType activationType = io.papermc.paper.entity.activation.ActivationType.activationTypeFor(this); // Paper - EAR 2/tracking ranges
+ public @Nullable Boolean immuneToFire = null; // Purpur - Fire immune API
// CraftBukkit end
// Paper start
@@ -434,10 +_,22 @@
}
// Paper end
+ // Purpur start - Add canSaveToDisk to Entity
+ public boolean canSaveToDisk() {
+ return true;
+ }
+ // Purpur end - Add canSaveToDisk to Entity
+
public Entity(final EntityType<?> type, final Level level) {
this.type = type;
this.level = level;
this.dimensions = type.getDimensions();
+ this.maxAirTicks = level == null ? Entity.TOTAL_AIR_SUPPLY : this.level.purpurConfig.drowningAirTicks; // Purpur - Drowning Settings
+ // Purpur start - Add toggle for RNG manipulation
+ this.random = level == null || level.purpurConfig.entitySharedRandom ? SHARED_RANDOM : RandomSource.create();
+ this.uuid = Mth.createInsecureUUID(this.random);
+ this.stringUUID = this.uuid.toString();
+ // Purpur end - Add toggle for RNG manipulation
this.position = Vec3.ZERO;
this.blockPosition = BlockPos.ZERO;
this.chunkPosition = ChunkPos.ZERO;
@@ -828,6 +_,7 @@
&& this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v)
&& (!(this instanceof Player player) || !player.getAbilities().invulnerable))) {
// Paper end - Configurable nether ceiling damage
+ if (this.level.purpurConfig.teleportOnNetherCeilingDamage && this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER && this instanceof ServerPlayer player) player.teleport(org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.level.levelData.getRespawnData().pos(), this.level)); else // Purpur - Add option to teleport to spawn on nether ceiling damage
this.onBelowWorld();
}
}
@@ -1826,7 +_,7 @@
}
public boolean fireImmune() {
- return this.getType().fireImmune();
+ return this.immuneToFire != null ? immuneToFire : this.getType().fireImmune(); // Purpur - add fire immune API
}
public boolean causeFallDamage(final double fallDistance, final float damageModifier, final DamageSource damageSource) {
@@ -2412,7 +_,7 @@
output.putBoolean("Bukkit.invisible", this.persistentInvisibility);
}
// SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
- if (this.maxAirTicks != this.getDefaultMaxAirSupply()) {
+ if (this.maxAirTicks != this.getDefaultMaxAirSupply() && this.getDefaultMaxAirSupply() != this.level().purpurConfig.drowningAirTicks) { // Purpur - Drowning Settings
output.putInt("Bukkit.MaxAirSupply", this.getMaxAirSupply());
}
output.putInt("Spigot.ticksLived", this.totalEntityAge); // Paper
@@ -2499,6 +_,11 @@
output.putBoolean("Paper.FreezeLock", true);
}
// Paper end
+ // Purpur start - Fire immune API
+ if (immuneToFire != null) {
+ output.putBoolean("Purpur.FireImmune", immuneToFire);
+ }
+ // Purpur end - Fire immune API
} catch (Throwable var7) {
CrashReport report = CrashReport.forThrowable(var7, "Saving entity NBT");
CrashReportCategory category = report.addCategory("Entity being saved");
@@ -2621,6 +_,9 @@
}
freezeLocked = input.getBooleanOr("Paper.FreezeLock", false);
// Paper end
+
+ immuneToFire = input.read("Purpur.FireImmune", com.mojang.serialization.Codec.BOOL).orElse(null); // Purpur - Fire immune API
+
} catch (Throwable var7) {
CrashReport report = CrashReport.forThrowable(var7, "Loading entity NBT");
CrashReportCategory category = report.addCategory("Entity being loaded");
@@ -2800,6 +_,7 @@
if (this.isAlive() && this instanceof Leashable leashablex) {
if (leashablex.getLeashHolder() == player) {
if (!this.level().isClientSide()) {
+ if (hand == InteractionHand.OFF_HAND && (level().purpurConfig.villagerCanBeLeashed || level().purpurConfig.wanderingTraderCanBeLeashed) && this instanceof net.minecraft.world.entity.npc.villager.AbstractVillager) return InteractionResult.CONSUME; // Purpur - Allow leashing villagers
// Paper start - EntityUnleashEvent
if (!org.bukkit.craftbukkit.event.CraftEventFactory.handlePlayerUnleashEntityEvent(
leashablex, player, hand, !player.hasInfiniteMaterials(), true
@@ -3230,15 +_,18 @@
return Vec3.directionFromRotation(this.getRotationVector());
}
+ public BlockPos portalPos = BlockPos.ZERO; // Purpur - Fix stuck in portals
public void setAsInsidePortal(final Portal portal, final BlockPos pos) {
if (this.isOnPortalCooldown()) {
+ if (!(level().purpurConfig.playerFixStuckPortal && this instanceof Player && !pos.equals(this.portalPos))) // Purpur - Fix stuck in portals
this.setPortalCooldown();
- } else {
+ } else if (this.level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer) { // Purpur - Entities can use portals
if (this.portalProcess == null || !this.portalProcess.isSamePortal(portal)) {
this.portalProcess = new PortalProcessor(portal, pos.immutable());
} else if (!this.portalProcess.isInsidePortalThisTick()) {
this.portalProcess.updateEntryPosition(pos.immutable());
this.portalProcess.setAsInsidePortalThisTick(true);
+ this.portalPos = BlockPos.ZERO; // Purpur - Fix stuck in portals
}
}
}
@@ -3967,7 +_,7 @@
}
public boolean canUsePortal(final boolean ignorePassenger) {
- return (ignorePassenger || !this.isPassenger()) && this.isAlive();
+ return (ignorePassenger || !this.isPassenger()) && this.isAlive() && (this.level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer); // Purpur - Entities can use portals
}
public boolean canTeleport(final Level from, final Level to) {
@@ -4515,6 +_,12 @@
return Mth.lerp(partial, this.yRotO, this.yRot);
}
+ // Purpur start - Stop squids floating on top of water
+ public AABB getAxisForFluidCheck() {
+ return this.getBoundingBox().deflate(0.001D);
+ }
+ // Purpur end - Stop squids floating on top of water
+
public boolean touchingUnloadedChunk() {
AABB box = this.getBoundingBox().inflate(1.0);
int x0 = Mth.floor(box.minX);
@@ -4809,7 +_,7 @@
}
public float maxUpStep() {
- return 0.0F;
+ return maxUpStep; // Purpur - Add option to set armorstand step height
}
public void onExplosionHit(final @Nullable Entity explosionCausedBy) {

View File

@@ -9,3 +9,45 @@
private final float spawnDimensionsScale;
private final FeatureFlagSet requiredFeatures;
private final boolean allowedInPeaceful;
@@ -1238,6 +_,16 @@
return register(vanillaEntityId(vanillaId), builder);
}
+ // Purpur start - PlayerSetSpawnerTypeWithEggEvent
+ public static EntityType<?> getFromBukkitType(org.bukkit.entity.EntityType bukkitType) {
+ return getFromKey(Identifier.parse(bukkitType.getKey().toString()));
+ }
+
+ public static EntityType<?> getFromKey(Identifier location) {
+ return BuiltInRegistries.ENTITY_TYPE.getValue(location);
+ }
+ // Purpur end - PlayerSetSpawnerTypeWithEggEvent
+
public static Identifier getKey(final EntityType<?> type) {
return BuiltInRegistries.ENTITY_TYPE.getKey(type);
}
@@ -1495,6 +_,16 @@
return this.category;
}
+ // Purpur start - PlayerSetSpawnerTypeWithEggEvent
+ public String getName() {
+ return BuiltInRegistries.ENTITY_TYPE.getKey(this).getPath();
+ }
+
+ public String getTranslatedName() {
+ return getDescription().getString();
+ }
+ // Purpur end - PlayerSetSpawnerTypeWithEggEvent
+
public String getDescriptionId() {
return this.descriptionId;
}
@@ -1556,6 +_,7 @@
// Paper start - Add logging for debugging entity tags with invalid ids
() -> {
LOGGER.warn("Skipping Entity with id {}", input.getStringOr("id", "[invalid]"));
+ LOGGER.warn("Location: {} {}", level.getWorld().getName(), input.read("Pos", net.minecraft.world.phys.Vec3.CODEC).orElse(net.minecraft.world.phys.Vec3.ZERO)); // Purpur - log skipped entity's position
if ((DEBUG_ENTITIES_WITH_INVALID_IDS || level.getCraftServer().getServer().isDebugging()) && input instanceof TagValueInput tagInput) {
LOGGER.warn("Skipped entity tag: {}", tagInput.input);
}

View File

@@ -0,0 +1,21 @@
--- a/net/minecraft/world/entity/ExperienceOrb.java
+++ b/net/minecraft/world/entity/ExperienceOrb.java
@@ -352,7 +_,7 @@
public void playerTouch(final Player player) {
if (player instanceof ServerPlayer serverPlayer) {
if (player.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent
- player.takeXpDelay = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerXpCooldownEvent(player, 2, org.bukkit.event.player.PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entity.takeXpDelay = 2;
+ player.takeXpDelay = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerXpCooldownEvent(player, this.level().purpurConfig.playerExpPickupDelay, org.bukkit.event.player.PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entity.takeXpDelay = 2; // Purpur - Configurable player pickup exp delay
player.take(this, 1);
int remaining = this.repairPlayerItems(serverPlayer, this.getValue());
if (remaining > 0) {
@@ -368,7 +_,8 @@
}
private int repairPlayerItems(final ServerPlayer player, final int amount) {
- Optional<EnchantedItemInUse> selected = EnchantmentHelper.getRandomItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged);
+ Optional<EnchantedItemInUse> selected = level().purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedItemWith(EnchantmentEffectComponents.REPAIR_WITH_XP, player) :EnchantmentHelper.getRandomItemWith(// Purpur - Add option to mend the most damaged equipment first
+ EnchantmentEffectComponents.REPAIR_WITH_XP, player, ItemStack::isDamaged);
if (selected.isPresent()) {
ItemStack itemStack = selected.get().itemStack();
int toRepairFromXpAmount = EnchantmentHelper.modifyDurabilityToRepairFromXp(player.level(), itemStack, amount);

View File

@@ -1,5 +1,180 @@
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -440,6 +_,12 @@
if (dist < 0.0) {
double damagePerBlock = level.getWorldBorder().getDamagePerBlock();
if (damagePerBlock > 0.0) {
+ // Purpur start - Add option to teleport to spawn if outside world border
+ if (this.level().purpurConfig.teleportIfOutsideBorder && this instanceof ServerPlayer serverPlayer) {
+ serverPlayer.teleport(org.bukkit.craftbukkit.util.CraftLocation.toBukkit(this.level().levelData.getRespawnData().pos(), this.level()));
+ return;
+ }
+ // Purpur end - Add option to teleport to spawn if outside world border
this.hurtServer(level, this.damageSources().outOfBorder(), Math.max(1, Mth.floor(-dist * damagePerBlock)));
}
}
@@ -455,7 +_,7 @@
if (this.shouldTakeDrowningDamage()) {
this.setAirSupply(0);
level.broadcastEntityEvent(this, EntityEvent.DROWN_PARTICLES);
- this.hurtServer(level, this.damageSources().drown(), 2.0F);
+ this.hurtServer(level, this.damageSources().drown(), (float) this.level().purpurConfig.damageFromDrowning); // Purpur - Drowning Settings
}
} else if (this.getAirSupply() < this.getMaxAirSupply() && MobEffectUtil.shouldEffectsRefillAirsupply(this)) {
this.setAirSupply(this.increaseAirSupply(this.getAirSupply()));
@@ -515,7 +_,7 @@
}
protected boolean shouldTakeDrowningDamage() {
- return this.getAirSupply() <= -20;
+ return this.getAirSupply() <= -this.level().purpurConfig.drowningDamageInterval; // Purpur - Drowning Settings
}
@Override
@@ -1045,15 +_,33 @@
}
if (targetingEntity != null) {
- ItemStack itemStack = this.getItemBySlot(EquipmentSlot.HEAD);
- if (targetingEntity.is(EntityType.SKELETON) && itemStack.is(Items.SKELETON_SKULL)
- || targetingEntity.is(EntityType.ZOMBIE) && itemStack.is(Items.ZOMBIE_HEAD)
- || targetingEntity.is(EntityType.PIGLIN) && itemStack.is(Items.PIGLIN_HEAD)
- || targetingEntity.is(EntityType.PIGLIN_BRUTE) && itemStack.is(Items.PIGLIN_HEAD)
- || targetingEntity.is(EntityType.CREEPER) && itemStack.is(Items.CREEPER_HEAD)) {
- visibilityPercent *= 0.5;
- }
- }
+ ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.HEAD);
+ // Purpur start - Mob head visibility percent
+ if (targetingEntity.is(EntityType.SKELETON) && itemBySlot.is(Items.SKELETON_SKULL)) {
+ visibilityPercent *= targetingEntity.level().purpurConfig.skeletonHeadVisibilityPercent;
+ }
+ else if (targetingEntity.is(EntityType.ZOMBIE) && itemBySlot.is(Items.ZOMBIE_HEAD)) {
+ visibilityPercent *= targetingEntity.level().purpurConfig.zombieHeadVisibilityPercent;
+ }
+ else if ((targetingEntity.is(EntityType.PIGLIN) || targetingEntity.is(EntityType.PIGLIN_BRUTE)) && itemBySlot.is(Items.PIGLIN_HEAD)) {
+ visibilityPercent *= targetingEntity.level().purpurConfig.piglinHeadVisibilityPercent;
+ }
+ else if (targetingEntity.is(EntityType.CREEPER) && itemBySlot.is(Items.CREEPER_HEAD)) {
+ visibilityPercent *= targetingEntity.level().purpurConfig.creeperHeadVisibilityPercent;
+ }
+ // Purpur end - Mob head visibility percent
+ }
+
+ // Purpur start - Configurable mob blindness
+ if (targetingEntity instanceof LivingEntity entityliving) {
+ if (entityliving.hasEffect(MobEffects.BLINDNESS)) {
+ int amplifier = entityliving.getEffect(MobEffects.BLINDNESS).getAmplifier();
+ for (int i = 0; i < amplifier; i++) {
+ d *= this.level().purpurConfig.mobsBlindnessMultiplier;
+ }
+ }
+ }
+ // Purpur end - Configurable mob blindness
return visibilityPercent;
}
@@ -1100,6 +_,7 @@
Iterator<MobEffectInstance> iterator = this.activeEffects.values().iterator();
while (iterator.hasNext()) {
MobEffectInstance effect = iterator.next();
+ if (cause == EntityPotionEffectEvent.Cause.MILK && !this.level().purpurConfig.milkClearsBeneficialEffects && effect.getEffect().value().isBeneficial()) continue; // Purpur - Milk Keeps Beneficial Effects
EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED);
if (event.isCancelled()) {
continue;
@@ -1431,6 +_,24 @@
this.stopSleeping();
}
+ // Purpur start - One Punch Man!
+ if (damageSource.getEntity() instanceof net.minecraft.world.entity.player.Player player && damageSource.getEntity().level().purpurConfig.creativeOnePunch && !damageSource.is(DamageTypeTags.IS_PROJECTILE)) {
+ if (player.isCreative()) {
+ org.apache.commons.lang3.mutable.MutableDouble attackDamage = new org.apache.commons.lang3.mutable.MutableDouble();
+ player.getMainHandItem().forEachModifier(EquipmentSlot.MAINHAND, (attributeHolder, attributeModifier) -> {
+ if (attributeModifier.operation() == AttributeModifier.Operation.ADD_VALUE) {
+ attackDamage.addAndGet(attributeModifier.amount());
+ }
+ });
+
+ if (attackDamage.doubleValue() == 0.0D) {
+ // One punch!
+ amount = 9999F;
+ }
+ }
+ }
+ // Purpur end - One Punch Man!
+
this.noActionTime = 0;
if (damage < 0.0F) {
damage = 0.0F;
@@ -1692,10 +_,10 @@
protected @Nullable Player resolvePlayerResponsibleForDamage(final DamageSource source) {
Entity sourceEntity = source.getEntity();
if (sourceEntity instanceof Player playerSource) {
- this.setLastHurtByPlayer(playerSource, 100);
+ this.setLastHurtByPlayer(playerSource, this.level().purpurConfig.mobLastHurtByPlayerTime); // Purpur - Config for mob last hurt by player time
} else if (sourceEntity instanceof Wolf wolf && wolf.isTame()) {
if (wolf.getOwnerReference() != null) {
- this.setLastHurtByPlayer(wolf.getOwnerReference().getUUID(), 100);
+ this.setLastHurtByPlayer(wolf.getOwnerReference().getUUID(), this.level().purpurConfig.mobLastHurtByPlayerTime); // Purpur - Config for mob last hurt by player time
} else {
this.lastHurtByPlayer = null;
this.lastHurtByPlayerMemoryTime = 0;
@@ -1746,6 +_,30 @@
}
}
+ // Purpur start - Totems work in inventory
+ if (level().purpurConfig.totemOfUndyingWorksInInventory && this instanceof ServerPlayer player && (itemStack == null || itemStack.getItem() != Items.TOTEM_OF_UNDYING) && player.getBukkitEntity().hasPermission("purpur.inventory_totem")) {
+ for (ItemStack item : player.getInventory().getNonEquipmentItems()) {
+ if (item.getItem() == Items.TOTEM_OF_UNDYING) {
+ itemInHand = item;
+ itemStack = item.copy();
+ break;
+ }
+ }
+ }
+ // Purpur end - Totems work in inventory
+
+ // Purpur start - Totems work in inventory
+ if (level().purpurConfig.totemOfUndyingWorksInInventory && this instanceof ServerPlayer player && (itemStack == null || itemStack.getItem() != Items.TOTEM_OF_UNDYING) && player.getBukkitEntity().hasPermission("purpur.inventory_totem")) {
+ for (ItemStack item : player.getInventory().getNonEquipmentItems()) {
+ if (item.getItem() == Items.TOTEM_OF_UNDYING) {
+ itemInHand = item;
+ itemStack = item.copy();
+ break;
+ }
+ }
+ }
+ // Purpur end - Totems work in inventory
+
final org.bukkit.inventory.EquipmentSlot handSlot = (hand != null) ? org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand) : null;
final EntityResurrectEvent event = new EntityResurrectEvent((org.bukkit.entity.LivingEntity) this.getBukkitEntity(), handSlot);
event.setCancelled(protectionItem == null);
@@ -1920,6 +_,7 @@
boolean playerKilled = this.lastHurtByPlayerMemoryTime > 0;
this.dropEquipment(level); // CraftBukkit - from below
if (this.shouldDropLoot(level)) {
+ if (!(source.is(net.minecraft.world.damagesource.DamageTypes.CRAMMING) && level().purpurConfig.disableDropsOnCrammingDeath)) { // Purpur - Disable loot drops on death by cramming
this.dropFromLootTable(level, source, playerKilled);
// Paper start
final boolean prev = this.clearEquipmentSlots;
@@ -1928,6 +_,7 @@
// Paper end
this.dropCustomDeathLoot(level, source, playerKilled);
this.clearEquipmentSlots = prev; // Paper
+ } // Purpur - Disable loot drops on death by cramming
}
// CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment
@@ -3269,6 +_,7 @@
float dmg = (float)(diff * 10.0 - 3.0);
if (dmg > 0.0F) {
this.playSound(this.getFallDamageSound((int)dmg), 1.0F, 1.0F);
+ if (level().purpurConfig.elytraKineticDamage) // Purpur - Toggle for kinetic damage
this.hurt(this.damageSources().flyIntoWall(), dmg);
}
}
@@ -3852,7 +_,7 @@
}
}
@@ -9,3 +184,16 @@
if (!this.onGround() && !this.isPassenger() && !this.hasEffect(MobEffects.LEVITATION)) {
for (EquipmentSlot slot : EquipmentSlot.VALUES) {
if (canGlideUsing(this.getItemBySlot(slot), slot)) {
@@ -4740,6 +_,12 @@
? slot == EquipmentSlot.MAINHAND && this.canUseSlot(EquipmentSlot.MAINHAND)
: slot == equippable.slot() && this.canUseSlot(equippable.slot()) && equippable.canBeEquippedBy(this.typeHolder());
}
+
+ // Purpur start - Dispenser curse of binding protection
+ public @Nullable EquipmentSlot getEquipmentSlotForDispenserItem(ItemStack itemstack) {
+ return EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.BINDING_CURSE, itemstack) > 0 ? null : this.getEquipmentSlotForItem(itemstack);
+ }
+ // Purpur end - Dispenser curse of binding protection
private static SlotAccess createEquipmentSlotAccess(final LivingEntity entity, final EquipmentSlot equipmentSlot) {
return equipmentSlot != EquipmentSlot.HEAD && equipmentSlot != EquipmentSlot.MAINHAND && equipmentSlot != EquipmentSlot.OFFHAND

View File

@@ -0,0 +1,80 @@
--- a/net/minecraft/world/entity/Mob.java
+++ b/net/minecraft/world/entity/Mob.java
@@ -150,6 +_,7 @@
private int homeRadius = -1;
public boolean aware = true; // CraftBukkit
public net.kyori.adventure.util.TriState despawnInPeacefulOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - allow changing despawnInPeaceful
+ public int ticksSinceLastInteraction; // Purpur - Entity lifespan
protected Mob(final EntityType<? extends Mob> type, final Level level) {
super(type, level);
@@ -285,6 +_,7 @@
target = null;
}
}
+ if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan
this.target = this.asValidTarget(target);
return true;
// CraftBukkit end
@@ -328,7 +_,27 @@
}
profiler.pop();
- }
+ incrementTicksSinceLastInteraction(); // Purpur - Entity lifespan
+ }
+
+ // Purpur start - Entity lifespan
+ private void incrementTicksSinceLastInteraction() {
+ ++this.ticksSinceLastInteraction;
+ if (getRider() != null) {
+ this.ticksSinceLastInteraction = 0;
+ return;
+ }
+ if (this.level().purpurConfig.entityLifeSpan <= 0) {
+ return; // feature disabled
+ }
+ if (!this.removeWhenFarAway(0) || isPersistenceRequired() || requiresCustomPersistence() || hasCustomName()) {
+ return; // mob persistent
+ }
+ if (this.ticksSinceLastInteraction > this.level().purpurConfig.entityLifeSpan) {
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+ }
+ }
+ // Purpur end - Entity lifespan
@Override
protected void playHurtSound(final DamageSource source) {
@@ -432,6 +_,7 @@
output.putString("Paper.DespawnInPeacefulOverride", this.despawnInPeacefulOverride.name());
}
// Paper end - allow changing despawnInPeaceful
+ output.putInt("Purpur.ticksSinceLastInteraction", this.ticksSinceLastInteraction); // Purpur - Entity lifespan
}
@Override
@@ -459,6 +_,7 @@
this.lootTableSeed = input.getLongOr("DeathLootTableSeed", 0L);
this.setNoAi(input.getBooleanOr("NoAI", false));
this.aware = input.getBooleanOr("Bukkit.Aware", true); // CraftBukkit
+ this.ticksSinceLastInteraction = input.getIntOr("Purpur.ticksSinceLastInteraction", 0); // Purpur- Entity lifespan
// Paper start - allow changing despawnInPeaceful
this.despawnInPeacefulOverride = readDespawnInPeacefulOverride(input);
}
@@ -1237,7 +_,7 @@
);
}
- this.setLeftHanded(random.nextFloat() < 0.05F);
+ this.setLeftHanded(random.nextFloat() < level.getLevel().purpurConfig.entityLeftHandedChance); // Purpur - Changeable Mob Left Handed Chance
return groupData;
}
@@ -1598,6 +_,7 @@
}
this.postPiercingAttack();
+ if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan
return wasHurt;
}

View File

@@ -0,0 +1,41 @@
--- a/net/minecraft/world/entity/npc/CatSpawner.java
+++ b/net/minecraft/world/entity/npc/CatSpawner.java
@@ -23,7 +_,7 @@
public void tick(final ServerLevel level, final boolean spawnEnemies) {
this.nextTick--;
if (this.nextTick <= 0) {
- this.nextTick = 1200;
+ this.nextTick = level.purpurConfig.catSpawnDelay; // Purpur - Cat spawning options
Player player = level.getRandomPlayer();
if (player != null) {
RandomSource random = level.getRandom();
@@ -45,9 +_,12 @@
}
private void spawnInVillage(final ServerLevel serverLevel, final BlockPos spawnPos) {
- int radius = 48;
- if (serverLevel.getPoiManager().getCountInRange(p -> p.is(PoiTypes.HOME), spawnPos, 48, PoiManager.Occupancy.IS_OCCUPIED) > 4L) {
- List<Cat> cats = serverLevel.getEntitiesOfClass(Cat.class, new AABB(spawnPos).inflate(48.0, 8.0, 48.0));
+ // Purpur start - Cat spawning options
+ int radius = serverLevel.purpurConfig.catSpawnVillageScanRange;
+ if (radius <= 0) return;
+ if (serverLevel.getPoiManager().getCountInRange(holder -> holder.is(PoiTypes.HOME), spawnPos, radius, PoiManager.Occupancy.IS_OCCUPIED) > 4L) {
+ List<Cat> cats = serverLevel.getEntitiesOfClass(Cat.class, new AABB(spawnPos).inflate(radius, 8.0, radius));
+ // Purpur end - Cat spawning options
if (cats.size() < 5) {
this.spawnCat(spawnPos, serverLevel, false);
}
@@ -55,8 +_,11 @@
}
private void spawnInHut(final ServerLevel level, final BlockPos spawnPos) {
- int radius = 16;
- List<Cat> cats = level.getEntitiesOfClass(Cat.class, new AABB(spawnPos).inflate(16.0, 8.0, 16.0));
+ // Purpur start - Cat spawning options
+ int radius = level.purpurConfig.catSpawnSwampHutScanRange;
+ if (radius <= 0) return;
+ List<Cat> cats = level.getEntitiesOfClass(Cat.class, new AABB(spawnPos).inflate(radius, 8.0, radius));
+ // Purpur end - Cat spawning options
if (cats.isEmpty()) {
this.spawnCat(spawnPos, level, true);
}

View File

@@ -0,0 +1,36 @@
--- a/net/minecraft/world/entity/npc/wanderingtrader/WanderingTrader.java
+++ b/net/minecraft/world/entity/npc/wanderingtrader/WanderingTrader.java
@@ -61,6 +_,13 @@
super(type, level);
}
+ // Purpur start - Allow leashing villagers
+ @Override
+ public boolean canBeLeashed() {
+ return level().purpurConfig.wanderingTraderCanBeLeashed;
+ }
+ // Purpur end - Allow leashing villagers
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new FloatGoal(this));
@@ -78,7 +_,7 @@
.addGoal(
0,
new UseItemGoal<>(
- this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, e -> this.canDrinkMilk && this.level().isBrightOutside() && e.isInvisible() // Paper - Add more WanderingTrader API
+ this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, e -> level().purpurConfig.milkClearsBeneficialEffects && this.canDrinkMilk && this.level().isBrightOutside() && e.isInvisible() // Paper - Add more WanderingTrader API // Purpur - Milk Keeps Beneficial Effects
)
);
this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this));
@@ -121,8 +_,10 @@
return InteractionResult.CONSUME;
}
+ if (this.level().purpurConfig.wanderingTraderAllowTrading) { // Purpur - Add config for villager trading
this.setTradingPlayer(player);
this.openTradingScreen(player, this.getDisplayName(), 1);
+ } // Purpur - Add config for villager trading
}
return InteractionResult.SUCCESS;

View File

@@ -1,5 +1,75 @@
--- a/net/minecraft/world/entity/player/Player.java
+++ b/net/minecraft/world/entity/player/Player.java
@@ -174,11 +_,20 @@
public float hurtDir;
public boolean affectsSpawning = true; // Paper - Affects Spawning API
public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage
+ public int burpDelay = 0; // Purpur - Burp delay
+ public boolean canPortalInstant = false; // Purpur - Add portal permission bypass
// CraftBukkit start
public boolean fauxSleeping;
public int oldLevel = -1;
+ // Purpur start - AFK API
+ public abstract void setAfk(boolean afk);
+
+ public boolean isAfk() {
+ return false;
+ }
+ // Purpur end - AFK API
@Override
public org.bukkit.craftbukkit.entity.CraftHumanEntity getBukkitEntity() {
return (org.bukkit.craftbukkit.entity.CraftHumanEntity) super.getBukkitEntity();
@@ -240,6 +_,12 @@
@Override
public void tick() {
+ // Purpur start - Burp delay
+ if (this.burpDelay > 0 && --this.burpDelay == 0) {
+ this.level().playSound(null, getX(), getY(), getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 1.0F, this.level().random.nextFloat() * 0.1F + 0.9F);
+ }
+ // Purpur end - Burp delay
+
this.noPhysics = this.isSpectator();
if (this.isSpectator() || this.isPassenger()) {
this.setOnGround(false);
@@ -297,6 +_,17 @@
this.turtleHelmetTick();
}
+ // Purpur start - Full netherite armor grants fire resistance
+ if (this.level().purpurConfig.playerNetheriteFireResistanceDuration > 0 && this.level().getGameTime() % 20 == 0) {
+ if (this.getItemBySlot(EquipmentSlot.HEAD).is(Items.NETHERITE_HELMET)
+ && this.getItemBySlot(EquipmentSlot.CHEST).is(Items.NETHERITE_CHESTPLATE)
+ && this.getItemBySlot(EquipmentSlot.LEGS).is(Items.NETHERITE_LEGGINGS)
+ && this.getItemBySlot(EquipmentSlot.FEET).is(Items.NETHERITE_BOOTS)) {
+ this.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, this.level().purpurConfig.playerNetheriteFireResistanceDuration, this.level().purpurConfig.playerNetheriteFireResistanceAmplifier, this.level().purpurConfig.playerNetheriteFireResistanceAmbient, this.level().purpurConfig.playerNetheriteFireResistanceShowParticles, this.level().purpurConfig.playerNetheriteFireResistanceShowIcon), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.NETHERITE_ARMOR);
+ }
+ }
+ // Purpur end - Full netherite armor grants fire resistance
+
this.cooldowns.tick();
this.updatePlayerPose();
}
@@ -502,7 +_,7 @@
List<Entity> orbs = Lists.newArrayList();
for (Entity entity : entities) {
- if (entity.is(EntityType.EXPERIENCE_ORB)) {
+ if (entity.is(EntityType.EXPERIENCE_ORB) && entity.level().purpurConfig.playerExpPickupDelay >= 0) { // Purpur - Configurable player pickup exp delay
orbs.add(entity);
} else if (!entity.isRemoved()) {
this.touch(entity);
@@ -1045,7 +_,7 @@
criticalAttack = criticalAttack && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits
if (criticalAttack) {
damageSource = damageSource.critical(); // Paper - critical damage API
- baseDamage *= 1.5F;
+ baseDamage *= (float) this.level().purpurConfig.playerCriticalDamageMultiplier; // Purpur - Add config change multiplier critical damage value
}
float totalDamage = baseDamage + magicBoost;
@@ -1519,7 +_,7 @@
}
@@ -9,3 +79,42 @@
return !this.abilities.flying && super.canGlide();
}
@@ -1747,7 +_,23 @@
@Override
protected int getBaseExperienceReward(final ServerLevel level) {
- return !level.getGameRules().get(GameRules.KEEP_INVENTORY) && !this.isSpectator() ? Math.min(this.experienceLevel * 7, 100) : 0;
+ // Purpur start - Add player death exp control options
+ if (!level.getGameRules().get(GameRules.KEEP_INVENTORY) && !this.isSpectator()) {
+ int toDrop;
+ try {
+ toDrop = Math.round(((Number) scriptEngine.eval("let expLevel = " + experienceLevel + "; " +
+ "let expTotal = " + totalExperience + "; " +
+ "let exp = " + experienceProgress + "; " +
+ level().purpurConfig.playerDeathExpDropEquation)).floatValue());
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ toDrop = experienceLevel * 7;
+ }
+ return Math.min(toDrop, level().purpurConfig.playerDeathExpDropMax);
+ } else {
+ return 0;
+ }
+ // Purpur end - Add player death exp control options
}
@Override
@@ -1790,6 +_,13 @@
public boolean addItem(final ItemStack itemStack) {
return this.inventory.add(itemStack);
}
+
+ // Purpur start - Player ridable in water option
+ @Override
+ public boolean dismountsUnderwater() {
+ return !level().purpurConfig.playerRidableInWater;
+ }
+ // Purpur end - Player ridable in water option
public abstract @Nullable GameType gameMode();

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/projectile/arrow/ThrownTrident.java
+++ b/net/minecraft/world/entity/projectile/arrow/ThrownTrident.java
@@ -72,7 +_,7 @@
Entity currentOwner = this.getOwner();
int loyalty = this.entityData.get(ID_LOYALTY);
- if (loyalty > 0 && (this.dealtDamage || this.isNoPhysics()) && currentOwner != null) {
+ if (loyalty > 0 && (this.dealtDamage || this.isNoPhysics() || (level().purpurConfig.tridentLoyaltyVoidReturnHeight < 0.0D && getY() < level().purpurConfig.tridentLoyaltyVoidReturnHeight)) && currentOwner != null) { // Purpur - Add option to allow loyalty on tridents to work in the void
if (!this.isAcceptibleReturnOwner()) {
if (this.level() instanceof ServerLevel level && this.pickup == AbstractArrow.Pickup.ALLOWED) {
this.spawnAtLocation(level, this.getPickupItem(), 0.1F);

View File

@@ -0,0 +1,43 @@
--- a/net/minecraft/world/entity/projectile/throwableitemprojectile/Snowball.java
+++ b/net/minecraft/world/entity/projectile/throwableitemprojectile/Snowball.java
@@ -56,9 +_,39 @@
protected void onHitEntity(final EntityHitResult hitResult) {
super.onHitEntity(hitResult);
Entity entity = hitResult.getEntity();
- int damage = entity instanceof Blaze ? 3 : 0;
+ int damage = entity.level().purpurConfig.snowballDamage >= 0 ? entity.level().purpurConfig.snowballDamage : entity instanceof Blaze ? 3 : 0; // Purpur - Add configurable snowball damage
entity.hurt(this.damageSources().thrown(this, this.getOwner()), damage);
}
+
+ // Purpur start - options to extinguish fire blocks with snowballs - borrowed and modified code from ThrownPotion#onHitBlock and ThrownPotion#dowseFire
+ @Override
+ protected void onHitBlock(net.minecraft.world.phys.BlockHitResult blockHitResult) {
+ super.onHitBlock(blockHitResult);
+
+ if (!this.level().isClientSide()) {
+ net.minecraft.core.BlockPos pos = blockHitResult.getBlockPos();
+ net.minecraft.core.BlockPos relativePos = pos.relative(blockHitResult.getDirection());
+
+ net.minecraft.world.level.block.state.BlockState blockState = this.level().getBlockState(pos);
+
+ if (this.level().purpurConfig.snowballExtinguishesFire && this.level().getBlockState(relativePos).is(net.minecraft.world.level.block.Blocks.FIRE)) {
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, relativePos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) {
+ this.level().removeBlock(relativePos, false);
+ }
+ } else if (this.level().purpurConfig.snowballExtinguishesCandles && net.minecraft.world.level.block.AbstractCandleBlock.isLit(blockState)) {
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, pos, blockState.setValue(net.minecraft.world.level.block.AbstractCandleBlock.LIT, false))) {
+ net.minecraft.world.level.block.AbstractCandleBlock.extinguish(null, blockState, this.level(), pos);
+ }
+ } else if (this.level().purpurConfig.snowballExtinguishesCampfires && net.minecraft.world.level.block.CampfireBlock.isLitCampfire(blockState)) {
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, pos, blockState.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false))) {
+ this.level().levelEvent(null, 1009, pos, 0);
+ net.minecraft.world.level.block.CampfireBlock.dowse(this.getOwner(), this.level(), pos, blockState);
+ this.level().setBlockAndUpdate(pos, blockState.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false));
+ }
+ }
+ }
+ }
+ // Purpur end - options to extinguish fire blocks with snowballs
@Override
protected void onHit(final HitResult hitResult) {

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
+++ b/net/minecraft/world/entity/projectile/throwableitemprojectile/ThrownEnderpearl.java
@@ -113,9 +_,10 @@
return;
}
// CraftBukkit end
- if (this.random.nextFloat() < 0.05F && level.isSpawningMonsters()) {
+ if (this.random.nextFloat() < level.purpurConfig.enderPearlEndermiteChance && level.isSpawningMonsters()) { // Purpur - Configurable Ender Pearl RNG
Endermite endermite = EntityType.ENDERMITE.create(level, EntitySpawnReason.TRIGGERED);
if (endermite != null) {
+ endermite.setPlayerSpawned(true); // Purpur - Add back player spawned endermite API
endermite.snapTo(preTeleportX, preTeleportY, preTeleportZ, preTeleportYRot, preTeleportXRot); // Paper - spawn endermite at pre teleport position as teleport has been moved up
level.addFreshEntity(endermite, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.ENDER_PEARL); // Paper - add reason
}
@@ -135,7 +_,7 @@
if (newOwner != null) {
newOwner.resetFallDistance();
newOwner.resetCurrentImpulseContext();
- newOwner.hurtServer(player.level(), this.damageSources().enderPearl().eventEntityDamager(this), 5.0F); // CraftBukkit // Paper - fix DamageSource API
+ newOwner.hurtServer(player.level(), this.damageSources().enderPearl().eventEntityDamager(this), this.level().purpurConfig.enderPearlDamage); // CraftBukkit // Paper - fix DamageSource API // Purpur - Configurable Ender Pearl damage
}
this.playSound(level, teleportPos);

View File

@@ -0,0 +1,42 @@
--- a/net/minecraft/world/entity/raid/Raids.java
+++ b/net/minecraft/world/entity/raid/Raids.java
@@ -29,6 +_,7 @@
public class Raids extends SavedData {
private static final Identifier RAID_FILE_ID = Identifier.withDefaultNamespace("raids");
+ public final java.util.Map<java.util.UUID, Integer> playerCooldowns = com.google.common.collect.Maps.newHashMap(); // Purpur - Raid cooldown setting
public static final Codec<Raids> CODEC = RecordCodecBuilder.create(
i -> i.group(
Raids.RaidWithId.CODEC
@@ -75,6 +_,17 @@
public void tick(final ServerLevel level) {
this.tick++;
+ // Purpur start - Raid cooldown setting
+ if (level.purpurConfig.raidCooldownSeconds != 0 && this.tick % 20 == 0) {
+ com.google.common.collect.ImmutableMap.copyOf(playerCooldowns).forEach((uuid, i) -> {
+ if (i < 1) {
+ playerCooldowns.remove(uuid);
+ } else {
+ playerCooldowns.put(uuid, i - 1);
+ }
+ });
+ }
+ // Purpur end - Raid cooldown setting
Iterator<Raid> raidIterator = this.raidMap.values().iterator();
while (raidIterator.hasNext()) {
@@ -137,11 +_,11 @@
// }
if (!raid.isStarted() || (raid.isInProgress() && raid.getRaidOmenLevel() < raid.getMaxRaidOmenLevel())) { // CraftBukkit - fixed a bug with raid: players could add up Bad Omen level even when the raid had finished
- // CraftBukkit start
+ if (level.purpurConfig.raidCooldownSeconds != 0 && playerCooldowns.containsKey(player.getUUID())) return null; // Purpur - Raid cooldown setting// CraftBukkit start
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(level, raid, player)) {
player.removeEffect(net.minecraft.world.effect.MobEffects.RAID_OMEN);
return null;
- }
+ }if (level.purpurConfig.raidCooldownSeconds != 0) playerCooldowns.put(player.getUUID(), serverLevel.purpurConfig.raidCooldownSeconds); // Purpur - Raid cooldown setting
if (!raid.isStarted() && !this.raidMap.containsValue(raid)) {
this.raidMap.put(this.getUniqueId(), raid);

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/entity/vehicle/boat/AbstractBoat.java
+++ b/net/minecraft/world/entity/vehicle/boat/AbstractBoat.java
@@ -431,6 +_,7 @@
float friction = this.getGroundFriction();
if (friction > 0.0F) {
this.landFriction = friction;
+ if (level().purpurConfig.boatEjectPlayersOnLand) ejectPassengers(); // Purpur - Add option for boats to eject players on land
return AbstractBoat.Status.ON_LAND;
} else {
return AbstractBoat.Status.IN_AIR;

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/vehicle/minecart/NewMinecartBehavior.java
+++ b/net/minecraft/world/entity/vehicle/minecart/NewMinecartBehavior.java
@@ -394,7 +_,7 @@
private Vec3 calculateBoostTrackSpeed(final Vec3 deltaMovement, final BlockPos pos, final BlockState state) {
if (state.is(Blocks.POWERED_RAIL) && state.getValue(PoweredRailBlock.POWERED)) {
if (deltaMovement.length() > 0.01) {
- return deltaMovement.normalize().scale(deltaMovement.length() + 0.06);
+ return deltaMovement.normalize().scale(deltaMovement.length() + this.level().purpurConfig.poweredRailBoostModifier); // Purpur - Configurable powered rail boost modifier
} else {
Vec3 powerDirection = this.minecart.getRedstoneDirection(pos);
return powerDirection.lengthSqr() <= 0.0 ? deltaMovement : powerDirection.scale(deltaMovement.length() + 0.2);

View File

@@ -0,0 +1,13 @@
--- a/net/minecraft/world/entity/vehicle/minecart/OldMinecartBehavior.java
+++ b/net/minecraft/world/entity/vehicle/minecart/OldMinecartBehavior.java
@@ -242,8 +_,8 @@
Vec3 vec3 = this.getDeltaMovement();
double speedLength = vec3.horizontalDistance();
if (speedLength > 0.01) {
- double speed = 0.06;
- this.setDeltaMovement(vec3.add(vec3.x / speedLength * 0.06, 0.0, vec3.z / speedLength * 0.06));
+ double speed = level.purpurConfig.poweredRailBoostModifier; // Purpur - Configurable powered rail boost modifier
+ this.setDeltaMovement(vec3.add(vec3.x / speedLength * level.purpurConfig.poweredRailBoostModifier, 0.0, vec3.z / speedLength * level.purpurConfig.poweredRailBoostModifier)); // Purpur - Configurable powered rail boost modifier
} else {
Vec3 deltaMovement = this.getDeltaMovement();
double dx = deltaMovement.x;

View File

@@ -0,0 +1,19 @@
--- a/net/minecraft/world/food/FoodData.java
+++ b/net/minecraft/world/food/FoodData.java
@@ -39,6 +_,7 @@
int oldFoodLevel = this.foodLevel;
org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(serverPlayer, foodProperties.nutrition() + oldFoodLevel, stack);
if (!event.isCancelled()) {
+ if (serverPlayer.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) serverPlayer.burpDelay = serverPlayer.level().purpurConfig.playerBurpDelay; // Purpur - Burp after eating food fills hunger bar completely
this.add(event.getFoodLevel() - oldFoodLevel, foodProperties.saturation());
}
serverPlayer.getBukkitEntity().sendHealthUpdate();
@@ -87,7 +_,7 @@
this.tickTimer++;
if (this.tickTimer >= this.starvationRate) { // CraftBukkit - add regen rate manipulation
if (player.getHealth() > 10.0F || difficulty == Difficulty.HARD || player.getHealth() > 1.0F && difficulty == Difficulty.NORMAL) {
- player.hurtServer(level, player.damageSources().starve(), 1.0F);
+ player.hurtServer(level, player.damageSources().starve(), player.level().purpurConfig.hungerStarvationDamage); // Purpur - Configurable hunger starvation damage
}
this.tickTimer = 0;

View File

@@ -0,0 +1,29 @@
--- a/net/minecraft/world/level/block/piston/PistonStructureResolver.java
+++ b/net/minecraft/world/level/block/piston/PistonStructureResolver.java
@@ -81,7 +_,7 @@
return true;
} else {
int blockCount = 1;
- if (blockCount + this.toPush.size() > 12) {
+ if (blockCount + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - Configurable piston push limit
return false;
} else {
while (isSticky(nextState)) {
@@ -95,7 +_,7 @@
break;
}
- if (++blockCount + this.toPush.size() > 12) {
+ if (++blockCount + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - Configurable piston push limit
return false;
}
}
@@ -139,7 +_,7 @@
return true;
}
- if (this.toPush.size() >= 12) {
+ if (this.toPush.size() >= this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - Configurable piston push limit
return false;
}