move file patches to rejected directory

This commit is contained in:
granny
2025-03-14 21:03:05 -07:00
parent 907406a11b
commit e2a920d63c
263 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
--- a/io/papermc/paper/entity/activation/ActivationRange.java
+++ b/io/papermc/paper/entity/activation/ActivationRange.java
@@ -141,6 +_,8 @@
continue;
}
+ if (!player.level().purpurConfig.idleTimeoutTickNearbyEntities && player.isAfk()) continue; // Purpur - AFK API
+
final int worldHeight = world.getHeight();
ActivationRange.maxBB = player.getBoundingBox().inflate(maxRange, worldHeight, maxRange);
ActivationType.MISC.boundingBox = player.getBoundingBox().inflate(miscActivationRange, worldHeight, miscActivationRange);
@@ -282,6 +_,7 @@
* @return
*/
public static boolean checkIfActive(final Entity entity) {
+ if (entity.level().purpurConfig.squidImmuneToEAR && entity instanceof net.minecraft.world.entity.animal.Squid) return true; // Purpur - Squid EAR immunity
// Never safe to skip fireworks or item gravity
if (entity instanceof FireworkRocketEntity || (entity instanceof ItemEntity && (entity.tickCount + entity.getId()) % 4 == 0)) { // Needed for item gravity, see ItemEntity tick
return true;

View File

@@ -0,0 +1,28 @@
--- a/net/minecraft/CrashReport.java
+++ b/net/minecraft/CrashReport.java
@@ -30,6 +_,7 @@
private boolean trackingStackTrace = true;
private StackTraceElement[] uncategorizedStackTrace = new StackTraceElement[0];
private final SystemReport systemReport = new SystemReport();
+ private List<String> extraInfo = List.of("", "DO NOT REPORT THIS TO PAPER! REPORT TO PURPUR INSTEAD!", ""); // Purpur - Rebrand
public CrashReport(String title, Throwable exception) {
io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(exception); // Paper
@@ -130,7 +_,7 @@
}
public String getFriendlyReport(ReportType type) {
- return this.getFriendlyReport(type, List.of());
+ return this.getFriendlyReport(type, extraInfo); // Purpur - Rebrand
}
@Nullable
@@ -161,7 +_,7 @@
}
public boolean saveToFile(Path path, ReportType type) {
- return this.saveToFile(path, type, List.of());
+ return this.saveToFile(path, type, extraInfo); // Purpur - Rebrand
}
public SystemReport getSystemReport() {

View File

@@ -0,0 +1,53 @@
--- a/net/minecraft/commands/CommandSourceStack.java
+++ b/net/minecraft/commands/CommandSourceStack.java
@@ -455,6 +_,19 @@
}
// CraftBukkit end
+ // Purpur start - Gamemode extra permissions
+ public boolean testPermission(int i, String bukkitPermission) {
+ if (hasPermission(i, bukkitPermission)) {
+ return true;
+ }
+ net.kyori.adventure.text.Component permissionMessage = getLevel().getServer().server.permissionMessage();
+ if (!permissionMessage.equals(net.kyori.adventure.text.Component.empty())) {
+ sendFailure(io.papermc.paper.adventure.PaperAdventure.asVanilla(permissionMessage.replaceText(net.kyori.adventure.text.TextReplacementConfig.builder().matchLiteral("<permission>").replacement(bukkitPermission).build())));
+ }
+ return false;
+ }
+ // Purpur end - Gamemode extra permissions
+
public Vec3 getPosition() {
return this.worldPosition;
}
@@ -540,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(@Nullable net.kyori.adventure.text.Component message) {
+ sendSuccess(message, false);
+ }
+
+ public void sendSuccess(@Nullable net.kyori.adventure.text.Component message, boolean broadcastToOps) {
+ if (message == null) {
+ return;
+ }
+ sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(message), broadcastToOps);
+ }
+ // Purpur end - Purpur config files
public void sendSuccess(Supplier<Component> messageSupplier, boolean allowLogging) {
boolean flag = this.source.acceptsSuccess() && !this.silent;

View File

@@ -0,0 +1,44 @@
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -221,8 +_,8 @@
JfrCommand.register(this.dispatcher);
}
- if (SharedConstants.IS_RUNNING_IN_IDE) {
- TestCommand.register(this.dispatcher);
+ if (org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands || SharedConstants.IS_RUNNING_IN_IDE) { // Purpur - register minecraft debug commands
+ if (!org.purpurmc.purpur.PurpurConfig.registerMinecraftDebugCommands) TestCommand.register(this.dispatcher); // Purpur - register minecraft debug commands
RaidCommand.register(this.dispatcher, context);
DebugPathCommand.register(this.dispatcher);
DebugMobSpawningCommand.register(this.dispatcher);
@@ -250,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 (selection.includeIntegrated) {
@@ -503,6 +_,7 @@
private void runSync(ServerPlayer player, java.util.Collection<String> bukkit, RootCommandNode<SharedSuggestionProvider> rootCommandNode) {
// Paper end - Perf: Async command map building
new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent<CommandSourceStack>(player.getBukkitEntity(), (RootCommandNode) rootCommandNode, 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);
@@ -513,6 +_,7 @@
}
}
// CraftBukkit end
+ } // Purpur - Skip events if there's no listeners
player.connection.send(new ClientboundCommandsPacket(rootCommandNode));
}

View File

@@ -0,0 +1,45 @@
--- a/net/minecraft/commands/arguments/selector/EntitySelector.java
+++ b/net/minecraft/commands/arguments/selector/EntitySelector.java
@@ -192,26 +_,27 @@
this.checkPermissions(source);
if (this.playerName != null) {
ServerPlayer playerByName = source.getServer().getPlayerList().getPlayerByName(this.playerName);
- return playerByName == null ? List.of() : List.of(playerByName);
+ return playerByName == null || !canSee(source, playerByName) ? List.of() : List.of(playerByName); // Purpur - Hide hidden players from entity selector
} else if (this.entityUUID != null) {
ServerPlayer playerByName = source.getServer().getPlayerList().getPlayer(this.entityUUID);
- return playerByName == null ? List.of() : List.of(playerByName);
+ return playerByName == null || !canSee(source, playerByName) ? List.of() : List.of(playerByName); // Purpur - Hide hidden players from entity selector
} else {
Vec3 vec3 = this.position.apply(source.getPosition());
AABB absoluteAabb = this.getAbsoluteAabb(vec3);
Predicate<Entity> predicate = this.getPredicate(vec3, absoluteAabb, null);
if (this.currentEntity) {
- return source.getEntity() instanceof ServerPlayer serverPlayer && predicate.test(serverPlayer) ? List.of(serverPlayer) : List.of();
+ return source.getEntity() instanceof ServerPlayer serverPlayer && predicate.test(serverPlayer) && canSee(source, serverPlayer) ? List.of(serverPlayer) : List.of(); // Purpur - Hide hidden players from entity selector
} else {
int resultLimit = this.getResultLimit();
List<ServerPlayer> players;
if (this.isWorldLimited()) {
players = source.getLevel().getPlayers(predicate, resultLimit);
+ players.removeIf(entityplayer3 -> !canSee(source, entityplayer3)); // Purpur - Hide hidden players from entity selector
} else {
players = new ObjectArrayList<>();
for (ServerPlayer serverPlayer1 : source.getServer().getPlayerList().getPlayers()) {
- if (predicate.test(serverPlayer1)) {
+ if (predicate.test(serverPlayer1) && canSee(source, serverPlayer1)) { // Purpur - Hide hidden players from entity selector
players.add(serverPlayer1);
if (players.size() >= resultLimit) {
return players;
@@ -270,4 +_,10 @@
public static Component joinNames(List<? extends Entity> names) {
return ComponentUtils.formatList(names, 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
@@ -63,6 +_,12 @@
public static final int MAX_HORIZONTAL_COORDINATE = 33554431;
// Paper end - Optimize Bit Operations by inlining
+ // Purpur start - Ridables
+ public BlockPos(net.minecraft.world.entity.Entity entity) {
+ super(entity.getBlockX(), entity.getBlockY(), entity.getBlockZ());
+ }
+ // Purpur end - Ridables
+
public BlockPos(int x, int y, int z) {
super(x, y, z);
}

View File

@@ -0,0 +1,25 @@
--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
+++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
@@ -892,5 +_,22 @@
DispenserBlock.registerBehavior(Items.TNT_MINECART, new MinecartDispenseItemBehavior(EntityType.TNT_MINECART));
DispenserBlock.registerBehavior(Items.HOPPER_MINECART, new MinecartDispenseItemBehavior(EntityType.HOPPER_MINECART));
DispenserBlock.registerBehavior(Items.COMMAND_BLOCK_MINECART, new MinecartDispenseItemBehavior(EntityType.COMMAND_BLOCK_MINECART));
+ // Purpur start - Dispensers place anvils option
+ DispenserBlock.registerBehavior(Items.ANVIL, (new OptionalDispenseItemBehavior() {
+ @Override
+ public ItemStack execute(BlockSource dispenser, ItemStack stack) {
+ net.minecraft.world.level.Level level = dispenser.level();
+ if (!level.purpurConfig.dispenserPlaceAnvils) return super.execute(dispenser, stack);
+ Direction facing = dispenser.blockEntity().getBlockState().getValue(DispenserBlock.FACING);
+ BlockPos pos = dispenser.pos().relative(facing);
+ BlockState state = level.getBlockState(pos);
+ if (state.isAir()) {
+ level.setBlockAndUpdate(pos, Blocks.ANVIL.defaultBlockState().setValue(net.minecraft.world.level.block.AnvilBlock.FACING, facing.getAxis() == Direction.Axis.Y ? Direction.NORTH : facing.getClockWise()));
+ stack.shrink(1);
+ }
+ return stack;
+ }
+ }));
+ // Purpur end - Dispensers place anvils option
}
}

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 livingEntity = entitiesOfClass.getFirst();
- EquipmentSlot equipmentSlotForItem = livingEntity.getEquipmentSlotForItem(item);
+ EquipmentSlot equipmentSlotForItem = blockSource.level().purpurConfig.dispenserApplyCursedArmor ? livingEntity.getEquipmentSlotForItem(item) : livingEntity.getEquipmentSlotForDispenserItem(item); if (equipmentSlotForItem == null) return false; // Purpur - Dispenser curse of binding protection
ItemStack itemStack = item.copyWithCount(1); // Paper - shrink below and single item in event
// CraftBukkit start
net.minecraft.world.level.Level world = blockSource.level();

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/gametest/framework/GameTestHelper.java
+++ b/net/minecraft/gametest/framework/GameTestHelper.java
@@ -279,6 +_,8 @@
return gameType.isCreative();
}
+ public void setAfk(final boolean afk) {} // Purpur - AFK API
+
@Override
public boolean isLocalPlayer() {
return true;

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -588,11 +_,20 @@
private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world
private static int joinAttemptsThisTick; // Paper - Buffer joins to world
private static int currTick; // Paper - Buffer joins to world
+ private static int tickSecond; // Purpur - Max joins per second
public void tick() {
this.flushQueue();
// Paper start - Buffer joins to world
if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) {
Connection.currTick = net.minecraft.server.MinecraftServer.currentTick;
+ // Purpur start - Max joins per second
+ if (org.purpurmc.purpur.PurpurConfig.maxJoinsPerSecond) {
+ if (++Connection.tickSecond > 20) {
+ Connection.tickSecond = 0;
+ Connection.joinAttemptsThisTick = 0;
+ }
+ } else
+ // Purpur end - Max joins per second
Connection.joinAttemptsThisTick = 0;
}
// Paper end - Buffer joins to world

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/network/chat/SignedMessageChain.java
+++ b/net/minecraft/network/chat/SignedMessageChain.java
@@ -45,7 +_,7 @@
SignedMessageLink signedMessageLink = SignedMessageChain.this.nextLink;
if (signedMessageLink == null) {
throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.CHAIN_BROKEN);
- } else if (body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) {
+ } else if (org.purpurmc.purpur.PurpurConfig.kickForOutOfOrderChat && body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) { // Purpur - Option to disable kick for out of order chat
this.setChainBroken();
throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT, org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event causes
} else {

View File

@@ -0,0 +1,15 @@
--- a/net/minecraft/server/Main.java
+++ b/net/minecraft/server/Main.java
@@ -108,6 +_,12 @@
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) optionSet.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
+ // Purpur end - Add toggle for enchant level clamping - load config files early
+
io.papermc.paper.plugin.PluginInitializerManager.load(optionSet); // Paper
Bootstrap.bootStrap();
Bootstrap.validate();

View File

@@ -0,0 +1,133 @@
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
@@ -284,6 +_,7 @@
public joptsimple.OptionSet options;
public org.bukkit.command.ConsoleCommandSender console;
public static int currentTick; // Paper - improve tick loop
+ public static final long startTimeMillis = System.currentTimeMillis(); // Purpur - Add uptime command
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
public int autosavePeriod;
// Paper - don't store the vanilla dispatcher
@@ -294,7 +_,7 @@
public static final int TICK_TIME = 1000000000 / MinecraftServer.TPS;
private static final int SAMPLE_INTERVAL = 20; // Paper - improve server tick loop
@Deprecated(forRemoval = true) // Paper
- public final double[] recentTps = new double[3];
+ public final double[] recentTps = new double[4]; // Purpur - Add 5 second tps average in /tps
// Spigot end
public volatile boolean hasFullyShutdown; // Paper - Improved watchdog support
public volatile boolean abnormalExit; // Paper - Improved watchdog support
@@ -302,7 +_,9 @@
public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
+ public boolean lagging = false; // Purpur - Lagging threshold
public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
+ protected boolean upnp = false; // Purpur - UPnP Port Forwarding
public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
@@ -1001,6 +_,15 @@
LOGGER.info("Stopping server");
Commands.COMMAND_SENDING_POOL.shutdownNow(); // Paper - Perf: Async command map building; Shutdown and don't bother finishing
+ // Purpur start - UPnP Port Forwarding
+ if (upnp) {
+ if (dev.omega24.upnp4j.UPnP4J.close(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) {
+ LOGGER.info("[UPnP] Port {} closed", this.getPort());
+ } else {
+ LOGGER.error("[UPnP] Failed to close port {}", this.getPort());
+ }
+ }
+ // Purpur end - UPnP Port Forwarding
// CraftBukkit start
if (this.server != null) {
this.server.spark.disable(); // Paper - spark
@@ -1093,6 +_,8 @@
this.safeShutdown(waitForServer, false);
}
public void safeShutdown(boolean waitForServer, boolean isRestarting) {
+ org.purpurmc.purpur.task.BossBarTask.stopAll(); // Purpur - Implement TPSBar
+ org.purpurmc.purpur.task.BeehiveTask.instance().unregister(); // Purpur - Give bee counts in beehives to Purpur clients
this.isRestarting = isRestarting;
this.hasLoggedStop = true; // Paper - Debugging
if (isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
@@ -1112,6 +_,7 @@
private static final long MAX_CATCHUP_BUFFER = TICK_TIME * TPS * 60L;
private long lastTick = 0;
private long catchupTime = 0;
+ public final RollingAverage tps5s = new RollingAverage(5); // Purpur - Add 5 second tps average in /tps
public final RollingAverage tps1 = new RollingAverage(60);
public final RollingAverage tps5 = new RollingAverage(60 * 5);
public final RollingAverage tps15 = new RollingAverage(60 * 15);
@@ -1197,6 +_,16 @@
}
// Paper end - Add onboarding message for initial server start
+ // Purpur start - config for startup commands
+ if (!Boolean.getBoolean("Purpur.IReallyDontWantStartupCommands") && !org.purpurmc.purpur.PurpurConfig.startupCommands.isEmpty()) {
+ LOGGER.info("Purpur: Running startup commands specified in purpur.yml.");
+ for (final String startupCommand : org.purpurmc.purpur.PurpurConfig.startupCommands) {
+ LOGGER.info("Purpur: Running the following command: \"{}\"", startupCommand);
+ ((net.minecraft.server.dedicated.DedicatedServer) this).handleConsoleInput(startupCommand, this.createCommandSourceStack());
+ }
+ }
+ // Purpur end - config for startup commands
+
while (this.running) {
long l;
if (!this.isPaused() && this.tickRateManager.isSprinting() && this.tickRateManager.checkShouldSprintThisTick()) {
@@ -1221,14 +_,19 @@
if (++MinecraftServer.currentTick % MinecraftServer.SAMPLE_INTERVAL == 0) {
final long diff = currentTime - tickSection;
final java.math.BigDecimal currentTps = TPS_BASE.divide(new java.math.BigDecimal(diff), 30, java.math.RoundingMode.HALF_UP);
+ tps5s.add(currentTps, diff); // Purpur - Add 5 second tps average in /tps
tps1.add(currentTps, diff);
tps5.add(currentTps, diff);
tps15.add(currentTps, diff);
// Backwards compat with bad plugins
- this.recentTps[0] = tps1.getAverage();
- this.recentTps[1] = tps5.getAverage();
- this.recentTps[2] = tps15.getAverage();
+ // Purpur start - Add 5 second tps average in /tps
+ this.recentTps[0] = tps5s.getAverage();
+ this.recentTps[1] = tps1.getAverage();
+ this.recentTps[2] = tps5.getAverage();
+ this.recentTps[3] = tps15.getAverage();
+ // Purpur end - Add 5 second tps average in /tps
+ lagging = recentTps[0] < org.purpurmc.purpur.PurpurConfig.laggingThreshold; // Purpur - Lagging threshold
tickSection = currentTime;
}
// Paper end - further improve server tick loop
@@ -1260,6 +_,12 @@
profilerFiller.popPush("nextTickWait");
this.mayHaveDelayedTasks = true;
this.delayedTasksMaxNextTickTimeNanos = Math.max(Util.getNanos() + l, this.nextTickTimeNanos);
+ // Purpur start - Configurable TPS Catchup
+ if (!org.purpurmc.purpur.PurpurConfig.tpsCatchup /*|| !gg.pufferfish.pufferfish.PufferfishConfig.tpsCatchup*/) { // Purpur - Configurable TPS Catchup
+ this.nextTickTimeNanos = currentTime + l;
+ this.delayedTasksMaxNextTickTimeNanos = nextTickTimeNanos;
+ }
+ // Purpur end - Configurable TPS Catchup
this.startMeasuringTaskExecutionTime();
this.waitUntilNextTick();
this.finishMeasuringTaskExecutionTime();
@@ -1690,7 +_,7 @@
long worldTime = level.getGameTime();
final ClientboundSetTimePacket worldPacket = new ClientboundSetTimePacket(worldTime, dayTime, doDaylight);
for (Player entityhuman : level.players()) {
- if (!(entityhuman instanceof ServerPlayer) || (tickCount + entityhuman.getId()) % 20 != 0) {
+ if (!(entityhuman instanceof ServerPlayer) || (!level.isForceTime() && (tickCount + entityhuman.getId()) % 20 != 0)) { // Purpur - Configurable daylight cycle
continue;
}
ServerPlayer entityplayer = (ServerPlayer) entityhuman;
@@ -1855,7 +_,7 @@
@DontObfuscate
public String getServerModName() {
- return io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); // Paper
+ return org.purpurmc.purpur.PurpurConfig.serverModName; // Paper // Purpur - Configurable server mod name
}
public SystemReport fillSystemReport(SystemReport systemReport) {

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/server/PlayerAdvancements.java
+++ b/net/minecraft/server/PlayerAdvancements.java
@@ -148,6 +_,7 @@
AdvancementHolder advancementHolder = advancementManager.get(path);
if (advancementHolder == null) {
if (!path.getNamespace().equals(ResourceLocation.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?", path, this.playerSavePath);
} else {
this.startProgress(advancementHolder, progress);
@@ -195,6 +_,7 @@
advancement.value().display().ifPresent(displayInfo -> {
// Paper start - Add Adventure message to PlayerAdvancementDoneEvent
if (event.message() != null && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
+ 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
@@ -70,7 +_,7 @@
private static int enchant(CommandSourceStack source, Collection<? extends Entity> targets, Holder<Enchantment> enchantment, int level) throws CommandSyntaxException {
Enchantment enchantment1 = enchantment.value();
- if (level > enchantment1.getMaxLevel()) {
+ if (!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && level > enchantment1.getMaxLevel()) { // Purpur - Config to allow unsafe enchants
throw ERROR_LEVEL_TOO_HIGH.create(level, enchantment1.getMaxLevel());
} else {
int i = 0;
@@ -81,7 +_,7 @@
ItemStack mainHandItem = livingEntity.getMainHandItem();
if (!mainHandItem.isEmpty()) {
if (enchantment1.canEnchant(mainHandItem)
- && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(mainHandItem).keySet(), enchantment)) {
+ && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantmentsForCrafting(mainHandItem).keySet(), enchantment) || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && !mainHandItem.hasEnchantment(enchantment))) { // Purpur - Config to allow unsafe enchants
mainHandItem.enchant(enchantment, level);
i++;
} 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
@@ -51,6 +_,18 @@
}
private static int setMode(CommandContext<CommandSourceStack> source, Collection<ServerPlayer> players, GameType gameType) {
+ // Purpur start - Gamemode extra permissions
+ if (org.purpurmc.purpur.PurpurConfig.commandGamemodeRequiresPermission) {
+ String gamemode = gameType.getName();
+ CommandSourceStack sender = source.getSource();
+ if (!sender.testPermission(2, "minecraft.command.gamemode." + gamemode)) {
+ return 0;
+ }
+ if (sender.getEntity() instanceof ServerPlayer player && (players.size() > 1 || !players.contains(player)) && !sender.testPermission(2, "minecraft.command.gamemode." + gamemode + ".other")) {
+ return 0;
+ }
+ }
+ // Purpur end - Gamemode extra permissions
int i = 0;
for (ServerPlayer serverPlayer : players) {

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/server/commands/GiveCommand.java
+++ b/net/minecraft/server/commands/GiveCommand.java
@@ -66,6 +_,7 @@
i1 -= min;
ItemStack itemStack1 = item.createItemStack(min, false);
boolean flag = serverPlayer.getInventory().add(itemStack1);
+ if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) continue; // Purpur - add config option for toggling give command dropping
if (flag && itemStack1.isEmpty()) {
ItemEntity itemEntity = serverPlayer.drop(itemStack, false, false, false); // CraftBukkit - SPIGOT-2942: Add boolean to call event
if (itemEntity != null) {

View File

@@ -0,0 +1,66 @@
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -106,6 +_,7 @@
// CraftBukkit start
if (!org.bukkit.craftbukkit.Main.useConsole) return;
// Paper start - Use TerminalConsoleAppender
+ if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - GUI Improvements - has no GUI or has console (did not double-click)
new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start();
/*
jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader;
@@ -224,6 +_,15 @@
io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark
com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
+ /*// Purpur start - Purpur config files // Purpur - Configurable void damage height and damage
+ try {
+ org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings"));
+ } catch (Exception e) {
+ DedicatedServer.LOGGER.error("Unable to load server configuration", e);
+ return false;
+ }
+ org.purpurmc.purpur.PurpurConfig.registerCommands();
+ */// Purpur end - Purpur config files // Purpur - Configurable void damage height and damage
com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
this.setPvpAllowed(properties.pvp);
@@ -271,6 +_,30 @@
if (true) throw new IllegalStateException("Failed to bind to port", var10); // Paper - Propagate failed to bind to port error
return false;
}
+ // Purpur start - UPnP Port Forwarding
+ if (org.purpurmc.purpur.PurpurConfig.useUPnP) {
+ LOGGER.info("[UPnP] Attempting to start UPnP port forwarding service...");
+ if (dev.omega24.upnp4j.UPnP4J.isUPnPAvailable()) {
+ if (dev.omega24.upnp4j.UPnP4J.isOpen(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) {
+ this.upnp = false;
+ LOGGER.info("[UPnP] Port {} is already open", this.getPort());
+ } else if (dev.omega24.upnp4j.UPnP4J.open(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) {
+ this.upnp = true;
+ LOGGER.info("[UPnP] Successfully opened port {}", this.getPort());
+ } else {
+ this.upnp = false;
+ LOGGER.info("[UPnP] Failed to open port {}", this.getPort());
+ }
+
+ if (upnp) {
+ LOGGER.info("[UPnP] {}:{}", dev.omega24.upnp4j.UPnP4J.getExternalIP(), this.getPort());
+ }
+ } else {
+ this.upnp = false;
+ LOGGER.error("[UPnP] Service is unavailable");
+ }
+ }
+ // Purpur end - UPnP Port Forwarding
// CraftBukkit start
// this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up
@@ -350,6 +_,8 @@
LOGGER.info("JMX monitoring enabled");
}
+ org.purpurmc.purpur.task.BossBarTask.startAll(); // Purpur - Implement TPSBar
+ if (org.purpurmc.purpur.PurpurConfig.beeCountPayload) org.purpurmc.purpur.task.BeehiveTask.instance().register(); // Purpur - Give bee counts in beehives to Purpur clients
return true;
}
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/server/dedicated/DedicatedServerProperties.java
+++ b/net/minecraft/server/dedicated/DedicatedServerProperties.java
@@ -49,6 +_,7 @@
public final boolean onlineMode = this.get("online-mode", true);
public final boolean preventProxyConnections = this.get("prevent-proxy-connections", false);
public final String serverIp = this.get("server-ip", "");
+ public final String serverName = this.get("server-name", "Unknown Server"); // Purpur - Bring back server name
public final boolean pvp = this.get("pvp", true);
public final boolean allowFlight = this.get("allow-flight", false);
public final String motd = this.get("motd", "A Minecraft Server");

View File

@@ -0,0 +1,135 @@
--- a/net/minecraft/server/gui/MinecraftServerGui.java
+++ b/net/minecraft/server/gui/MinecraftServerGui.java
@@ -39,6 +_,11 @@
private Thread logAppenderThread;
private final Collection<Runnable> finalizers = Lists.newArrayList();
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 {
@@ -46,7 +_,7 @@
} catch (Exception var3) {
}
- final JFrame jFrame = new JFrame("Minecraft server");
+ final JFrame jFrame = new JFrame("Purpur Minecraft server"); // Purpur - Improve GUI
final MinecraftServerGui minecraftServerGui = new MinecraftServerGui(server);
jFrame.setDefaultCloseOperation(2);
jFrame.add(minecraftServerGui);
@@ -54,7 +_,7 @@
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);
// Paper start - Improve ServerGUI
- jFrame.setName("Minecraft server");
+ jFrame.setName("Purpur Minecraft server"); // Purpur - Improve GUI
try {
jFrame.setIconImage(javax.imageio.ImageIO.read(java.util.Objects.requireNonNull(MinecraftServerGui.class.getClassLoader().getResourceAsStream("logo.png"))));
} catch (java.io.IOException ignore) {
@@ -64,7 +_,7 @@
@Override
public void windowClosing(WindowEvent event) {
if (!minecraftServerGui.isClosing.getAndSet(true)) {
- jFrame.setTitle("Minecraft server - shutting down!");
+ jFrame.setTitle("Purpur Minecraft server - shutting down!"); // Purpur - Improve GUI
server.halt(true);
minecraftServerGui.runFinalizers();
}
@@ -112,7 +_,7 @@
private JComponent buildChatPanel() {
JPanel jPanel = new JPanel(new BorderLayout());
- JTextArea jTextArea = new JTextArea();
+ org.purpurmc.purpur.gui.JColorTextPane jTextArea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur - GUI Improvements
JScrollPane jScrollPane = new JScrollPane(jTextArea, 22, 30);
jTextArea.setEditable(false);
jTextArea.setFont(MONOSPACED);
@@ -121,10 +_,43 @@
String trimmed = jTextField.getText().trim();
if (!trimmed.isEmpty()) {
this.server.handleConsoleInput(trimmed, this.server.createCommandSourceStack());
+ // Purpur start - GUI Improvements
+ history.add(trimmed);
+ historyIndex = -1;
+ // Purpur end - GUI Improvements
}
jTextField.setText("");
});
+ // Purpur start - GUI Improvements
+ jTextField.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up");
+ jTextField.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down");
+ jTextField.getActionMap().put("up", new javax.swing.AbstractAction() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
+ if (historyIndex < 0) {
+ currentCommand = jTextField.getText();
+ }
+ if (historyIndex < history.size() - 1) {
+ jTextField.setText(history.get(++historyIndex));
+ }
+ }
+ });
+ jTextField.getActionMap().put("down", new javax.swing.AbstractAction() {
+ @Override
+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
+ if (historyIndex >= 0) {
+ if (historyIndex == 0) {
+ --historyIndex;
+ jTextField.setText(currentCommand);
+ } else {
+ --historyIndex;
+ jTextField.setText(history.get(historyIndex));
+ }
+ }
+ }
+ });
+ // Purpur end - GUI Improvements
jTextArea.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent event) {
@@ -159,7 +_,7 @@
}
private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper
- public void print(JTextArea textArea, JScrollPane scrollPane, String line) {
+ public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String line) { // Purpur - GUI Improvements
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(() -> this.print(textArea, scrollPane, line));
} else {
@@ -170,16 +_,29 @@
flag = verticalScrollBar.getValue() + verticalScrollBar.getSize().getHeight() + MONOSPACED.getSize() * 4 > verticalScrollBar.getMaximum();
}
- try {
+ /*try { // Purpur - GUI Improvements
document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(line).replaceAll(""), null); // CraftBukkit
} catch (BadLocationException var8) {
- }
+ }*/ // Purpur - GUI Improvements
+ textArea.append(line); // Purpur - GUI Improvements
if (flag) {
verticalScrollBar.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,11 @@
--- a/net/minecraft/server/gui/StatsComponent.java
+++ b/net/minecraft/server/gui/StatsComponent.java
@@ -43,7 +_,7 @@
}
this.msgs[0] = "Memory use: " + l / 1024L / 1024L + " mb (" + Runtime.getRuntime().freeMemory() * 100L / Runtime.getRuntime().maxMemory() + "% free)";
this.msgs[1] = "Avg tick: " + DECIMAL_FORMAT.format((double)this.server.getAverageTickTimeNanos() / TimeUtil.NANOSECONDS_PER_MILLISECOND) + " ms";
- this.msgs[2] = "TPS from last 1m, 5m, 15m: " + String.join(", ", tpsAvg);
+ this.msgs[2] = "TPS from last 5s, 1m, 5m, 15m: " + String.join(", ", tpsAvg); // Purpur - Add 5 second tps average in /tps
// Paper end - Improve ServerGUI
this.values[this.vp++ & 0xFF] = (int)(l * 100L / Runtime.getRuntime().maxMemory());
this.repaint();

View File

@@ -0,0 +1,172 @@
--- a/net/minecraft/server/level/ServerLevel.java
+++ b/net/minecraft/server/level/ServerLevel.java
@@ -207,6 +_,8 @@
private final StructureManager structureManager;
private final StructureCheck structureCheck;
private final boolean tickTime;
+ private double preciseTime; // Purpur - Configurable daylight cycle
+ private boolean forceTime; // Purpur - Configurable daylight cycle
private final RandomSequences randomSequences;
// CraftBukkit start
@@ -595,7 +_,24 @@
// CraftBukkit end
this.tickTime = tickTime;
this.server = server;
- this.customSpawners = customSpawners;
+ // Purpur start - Allow toggling special MobSpawners per world
+ this.customSpawners = new ArrayList<>();
+ if (purpurConfig.phantomSpawning) {
+ this.customSpawners.add(new net.minecraft.world.level.levelgen.PhantomSpawner());
+ }
+ if (purpurConfig.patrolSpawning) {
+ this.customSpawners.add(new net.minecraft.world.level.levelgen.PatrolSpawner());
+ }
+ if (purpurConfig.catSpawning) {
+ this.customSpawners.add(new net.minecraft.world.entity.npc.CatSpawner());
+ }
+ if (purpurConfig.villageSiegeSpawning) {
+ this.customSpawners.add(new net.minecraft.world.entity.ai.village.VillageSiege());
+ }
+ if (purpurConfig.villagerTraderSpawning) {
+ this.customSpawners.add(new net.minecraft.world.entity.npc.WanderingTraderSpawner(serverLevelData));
+ }
+ // Purpur end - Allow toggling special MobSpawners per world
this.serverLevelData = serverLevelData;
ChunkGenerator chunkGenerator = levelStem.generator();
// CraftBukkit start
@@ -681,6 +_,7 @@
this.chunkDataController = new ca.spottedleaf.moonrise.patches.chunk_system.io.datacontroller.ChunkDataController((ServerLevel)(Object)this, this.chunkTaskScheduler);
// Paper end - rewrite chunk system
this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
+ this.preciseTime = this.serverLevelData.getDayTime(); // Purpur - Configurable daylight cycle
}
// Paper start
@@ -727,7 +_,7 @@
}
int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
- if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) {
+ if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) { // Purpur - Config for skipping night
// Paper start - create time skip event - move up calculations
final long newDayTime = this.levelData.getDayTime() + 24000L;
org.bukkit.event.world.TimeSkipEvent event = new org.bukkit.event.world.TimeSkipEvent(
@@ -846,6 +_,13 @@
this.serverLevelData.getScheduledEvents().tick(this.server, l);
Profiler.get().pop();
if (this.serverLevelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
+ // Purpur start - Configurable daylight cycle
+ int incrementTicks = isDay() ? this.purpurConfig.daytimeTicks : this.purpurConfig.nighttimeTicks;
+ if (incrementTicks != 12000) {
+ this.preciseTime += 12000 / (double) incrementTicks;
+ this.setDayTime(this.preciseTime);
+ } else
+ // Purpur end - Configurable daylight cycle
this.setDayTime(this.levelData.getDayTime() + 1L);
}
}
@@ -853,7 +_,21 @@
public void setDayTime(long time) {
this.serverLevelData.setDayTime(time);
- }
+ // Purpur start - Configurable daylight cycle
+ this.preciseTime = time;
+ this.forceTime = false;
+ }
+ public void setDayTime(double i) {
+ this.serverLevelData.setDayTime((long) i);
+ this.forceTime = true;
+ // Purpur end - Configurable daylight cycle
+ }
+
+ // Purpur start - Configurable daylight cycle
+ public boolean isForceTime() {
+ return this.forceTime;
+ }
+ // Purpur end - Configurable daylight cycle
public void tickCustomSpawners(boolean spawnEnemies, boolean spawnFriendlies) {
for (CustomSpawner customSpawner : this.customSpawners) {
@@ -934,9 +_,17 @@
&& this.random.nextDouble() < currentDifficultyAt.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01) // Paper - Configurable spawn chances for skeleton horses
&& !this.getBlockState(blockPos.below()).is(Blocks.LIGHTNING_ROD);
if (flag) {
- SkeletonHorse skeletonHorse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT);
+ // Purpur start - Special mobs naturally spawn
+ net.minecraft.world.entity.animal.horse.AbstractHorse skeletonHorse;
+ if (purpurConfig.zombieHorseSpawnChance > 0D && random.nextDouble() <= purpurConfig.zombieHorseSpawnChance) {
+ skeletonHorse = EntityType.ZOMBIE_HORSE.create(this, EntitySpawnReason.EVENT);
+ } else {
+ skeletonHorse = EntityType.SKELETON_HORSE.create(this, EntitySpawnReason.EVENT);
+ if (skeletonHorse != null) ((SkeletonHorse) skeletonHorse).setTrap(true);
+ }
+ // Purpur end - Special mobs naturally spawn
if (skeletonHorse != null) {
- skeletonHorse.setTrap(true);
+ //skeletonHorse.setTrap(true); // Purpur - Special mobs naturally spawn - moved up
skeletonHorse.setAge(0);
skeletonHorse.setPos(blockPos.getX(), blockPos.getY(), blockPos.getZ());
this.addFreshEntity(skeletonHorse, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
@@ -1009,7 +_,7 @@
pointOfInterestType -> pointOfInterestType.is(PoiTypes.LIGHTNING_ROD),
blockPos -> blockPos.getY() == this.getHeight(Heightmap.Types.WORLD_SURFACE, blockPos.getX(), blockPos.getZ()) - 1,
pos,
- 128,
+ org.purpurmc.purpur.PurpurConfig.lightningRodRange, // Purpur - Make lightning rod range configurable
PoiManager.Occupancy.ANY
);
return optional.map(blockPos -> blockPos.above(1));
@@ -1057,8 +_,26 @@
int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
Component component;
if (this.sleepStatus.areEnoughSleeping(_int)) {
+ // Purpur start - Customizable sleeping actionbar messages
+ if (org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.isBlank()) {
+ return;
+ }
+ if (!org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.equalsIgnoreCase("default")) {
+ component = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepSkippingNight));
+ } else
+ // Purpur end - Customizable sleeping actionbar messages
component = Component.translatable("sleep.skipping_night");
} else {
+ // Purpur start - Customizable sleeping actionbar messages
+ if (org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.isBlank()) {
+ return;
+ }
+ if (!org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.equalsIgnoreCase("default")) {
+ component = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent,
+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("count", Integer.toString(this.sleepStatus.amountSleeping())),
+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("total", Integer.toString(this.sleepStatus.sleepersNeeded(_int)))));
+ } else
+ // Purpur end - Customizable sleeping actionbar messages
component = Component.translatable("sleep.players_sleeping", this.sleepStatus.amountSleeping(), this.sleepStatus.sleepersNeeded(_int));
}
@@ -1191,6 +_,7 @@
@VisibleForTesting
public void resetWeatherCycle() {
// CraftBukkit start
+ if (this.purpurConfig.rainStopsAfterSleep) // Purpur - Option for if rain and thunder should stop on sleep
this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents
// If we stop due to everyone sleeping we should reset the weather duration to some other random value.
// Not that everyone ever manages to get the whole server to sleep at the same time....
@@ -1198,6 +_,7 @@
this.serverLevelData.setRainTime(0);
}
// CraftBukkit end
+ if (this.purpurConfig.thunderStopsAfterSleep) // Purpur - Option for if rain and thunder should stop on sleep
this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents
// CraftBukkit start
// If we stop due to everyone sleeping we should reset the weather duration to some other random value.
@@ -2679,7 +_,7 @@
// Spigot start
if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message
// Paper start - Fix merchant inventory not closing on entity removal
- if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) {
+ if (!entity.level().purpurConfig.playerVoidTrading && entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { // Purpur - Allow void trading
merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED);
}
// Paper end - Fix merchant inventory not closing on entity removal

View File

@@ -0,0 +1,276 @@
--- a/net/minecraft/server/level/ServerPlayer.java
+++ b/net/minecraft/server/level/ServerPlayer.java
@@ -400,6 +_,10 @@
public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent
public @Nullable String clientBrandName = null; // Paper - Brand support
public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event
+ public boolean purpurClient = false; // Purpur - Purpur client support
+ private boolean tpsBar = false; // Purpur - Implement TPSBar
+ private boolean compassBar = false; // Purpur - Add compass command
+ private boolean ramBar = false; // Purpur - Implement rambar commands
// Paper start - rewrite chunk system
private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader;
@@ -568,6 +_,10 @@
if (tag != null) {
BlockPos.CODEC.parse(NbtOps.INSTANCE, tag).resultOrPartial(LOGGER::error).ifPresent(pos -> this.raidOmenPosition = pos);
}
+
+ if (compound.contains("Purpur.TPSBar")) { this.tpsBar = compound.getBoolean("Purpur.TPSBar"); } // Purpur - Implement TPSBar
+ if (compound.contains("Purpur.CompassBar")) { this.compassBar = compound.getBoolean("Purpur.CompassBar"); } // Purpur - Add compass command
+ if (compound.contains("Purpur.RamBar")) { this.ramBar = compound.getBoolean("Purpur.RamBar"); } // Purpur - Implement rambar command
}
@Override
@@ -612,6 +_,9 @@
}
this.saveEnderPearls(compound);
+ compound.putBoolean("Purpur.TPSBar", this.tpsBar); // Purpur - Implement TPSBar
+ compound.putBoolean("Purpur.CompassBar", this.compassBar); // Purpur - Add compass command
+ compound.putBoolean("Purpur.RamBar", this.ramBar); // Purpur - Add rambar command
}
private void saveParentVehicle(CompoundTag tag) {
@@ -1131,6 +_,7 @@
)
);
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) {
@@ -1224,6 +_,13 @@
if (this.isInvulnerableTo(level, damageSource)) {
return false;
} else {
+ // Purpur start - Add boat fall damage config
+ if (damageSource.is(net.minecraft.tags.DamageTypeTags.IS_FALL)) {
+ if (getRootVehicle() instanceof net.minecraft.world.entity.vehicle.Boat && !level().purpurConfig.boatsDoFallDamage) {
+ return false;
+ }
+ }
+ // Purpur end - Add boat fall damage config
Entity entity = damageSource.getEntity();
if (!( // Paper - split the if statement. If below statement is false, hurtServer would not have been evaluated. Return false.
!(entity instanceof Player player && !this.canHarmPlayer(player))
@@ -1453,6 +_,7 @@
serverLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
this.unsetRemoved();
// CraftBukkit end
+ this.portalPos = io.papermc.paper.util.MCUtil.toBlockPosition(exit); // Purpur - Fix stuck in portals
this.setServerLevel(level);
this.connection.internalTeleport(PositionMoveRotation.of(teleportTransition), teleportTransition.relatives()); // CraftBukkit - use internal teleport without event
this.connection.resetPosition();
@@ -1571,7 +_,7 @@
new AABB(vec3.x() - 8.0, vec3.y() - 5.0, vec3.z() - 8.0, vec3.x() + 8.0, vec3.y() + 5.0, vec3.z() + 8.0),
monster -> monster.isPreventingPlayerRest(this.serverLevel(), this)
);
- if (!entitiesOfClass.isEmpty()) {
+ if (!this.level().purpurConfig.playerSleepNearMonsters && !entitiesOfClass.isEmpty()) { // Purpur - Config to ignore nearby mobs when sleeping
return Either.left(Player.BedSleepingProblem.NOT_SAFE);
}
}
@@ -1608,7 +_,19 @@
CriteriaTriggers.SLEPT_IN_BED.trigger(this);
});
if (!this.serverLevel().canSleepThroughNights()) {
- this.displayClientMessage(Component.translatable("sleep.not_possible"), true);
+ // 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.displayClientMessage(clientMessage, true);
+ }
+ // Purpur end - Customizable sleeping actionbar messages
}
((ServerLevel)this.level()).updateSleepingPlayerList();
@@ -1716,6 +_,7 @@
@Override
public void openTextEdit(SignBlockEntity signEntity, boolean isFrontText) {
+ if (level().purpurConfig.signAllowColors) this.connection.send(signEntity.getTranslatedUpdatePacket(textFilteringEnabled, isFrontText)); // Purpur - Signs allow color codes
this.connection.send(new ClientboundBlockUpdatePacket(this.level(), signEntity.getBlockPos()));
this.connection.send(new ClientboundOpenSignEditorPacket(signEntity.getBlockPos(), isFrontText));
}
@@ -2021,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(@Nullable net.kyori.adventure.text.Component message) {
+ if (message != null) {
+ sendActionBarMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message));
+ }
+ }
+
+ public void sendActionBarMessage(@Nullable Component message) {
+ if (message != null) {
+ displayClientMessage(message, true);
+ }
+ }
+ // Purpur end - Component related conveniences
+
@Override
public void displayClientMessage(Component chatComponent, boolean actionBar) {
this.sendSystemMessage(chatComponent, actionBar);
@@ -2242,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(@Nullable net.kyori.adventure.text.Component message) {
+ if (message != null) {
+ this.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message));
+ }
+ }
+ // Purpur end - Component related conveniences
+
public void sendSystemMessage(Component mesage) {
this.sendSystemMessage(mesage, false);
}
@@ -2380,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().getName();
+ if (org.purpurmc.purpur.PurpurConfig.afkBroadcastUseDisplayName) {
+ net.kyori.adventure.text.Component playerDisplayNameComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(this.getBukkitEntity().getDisplayName());
+ playerName = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText().serialize(playerDisplayNameComponent);
+ }
+ server.getPlayerList().broadcastMiniMessage(String.format(msg, playerName), false);
+ }
+
+ if (this.level().purpurConfig.idleTimeoutUpdateTabList) {
+ String scoreboardName = getScoreboardName();
+ String playerListName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().serialize(getBukkitEntity().playerListName());
+ String[] split = playerListName.split(scoreboardName);
+ String prefix = (split.length > 0 ? split[0] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix, "");
+ String suffix = (split.length > 1 ? split[1] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, "");
+ if (afk) {
+ getBukkitEntity().setPlayerListName(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix + prefix + scoreboardName + suffix + org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, true);
+ } else {
+ getBukkitEntity().setPlayerListName(prefix + scoreboardName + suffix, true);
+ }
+ }
+
+ ((ServerLevel) this.level()).updateSleepingPlayerList();
+ }
+
+ @Override
+ public boolean isAfk() {
+ return this.isAfk;
+ }
+
+ @Override
+ public boolean canBeCollidedWith() {
+ return !this.isAfk() && super.canBeCollidedWith();
+ }
+ // Purpur end - AFK API
public ServerStatsCounter getStats() {
return this.stats;
@@ -3085,4 +_,56 @@
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.teleport(to);
+ } else {
+ this.server.getPlayerList().respawn(this, true, RemovalReason.KILLED, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH, to);
+ }
+ }
+ // 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
@@ -351,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
@@ -464,6 +_,7 @@
public InteractionHand interactHand;
public ItemStack interactItemStack;
public InteractionResult useItemOn(ServerPlayer player, Level level, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) {
+ if (shiftClickMended(stack)) return InteractionResult.SUCCESS; // Purpur - Shift right click to use exp for mending
BlockPos blockPos = hitResult.getBlockPos();
BlockState blockState = level.getBlockState(blockPos);
boolean cancelledBlock = false;
@@ -506,7 +_,7 @@
boolean flag = !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty();
boolean flag1 = player.isSecondaryUseActive() && flag;
ItemStack itemStack = stack.copy();
- if (!flag1) {
+ if (!flag1 || (player.level().purpurConfig.composterBulkProcess && blockState.is(net.minecraft.world.level.block.Blocks.COMPOSTER))) { // Purpur - Sneak to bulk process composter
InteractionResult interactionResult = blockState.useItemOn(player.getItemInHand(hand), level, player, hand, hitResult);
if (interactionResult.consumesAction()) {
CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, blockPos, itemStack);
@@ -552,4 +_,18 @@
public void setLevel(ServerLevel serverLevel) {
this.level = serverLevel;
}
+
+ // 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(-points);
+ this.player.level().addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(this.player.level(), this.player.getX(), this.player.getY(), this.player.getZ(), points, org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN, this.player, this.player));
+ return true;
+ }
+ }
+ return false;
+ }
+ // Purpur end - Shift right click to use exp for mending
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/server/level/WorldGenRegion.java
+++ b/net/minecraft/server/level/WorldGenRegion.java
@@ -312,6 +_,7 @@
return true;
} else {
// Paper start - Buffer OOB setBlock calls
+ if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressSetBlockFarChunk) // Purpur - Logger settings (suppressing pointless logs)
if (!hasSetFarWarned) {
Util.logAndPauseIfInIde(
"Detected setBlock in a far chunk ["

View File

@@ -0,0 +1,72 @@
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -41,6 +_,7 @@
private long keepAliveChallenge;
private long closedListenerTime;
private boolean closed = false;
+ private it.unimi.dsi.fastutil.longs.LongList keepAlives = new it.unimi.dsi.fastutil.longs.LongArrayList(); // Purpur - Alternative Keepalive Handling
private int latency;
private volatile boolean suspendFlushingOnServerThread = false;
// CraftBukkit start
@@ -51,6 +_,7 @@
public final java.util.Map<java.util.UUID, net.kyori.adventure.resource.ResourcePackCallback> packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks
private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit
protected static final net.minecraft.resources.ResourceLocation MINECRAFT_BRAND = net.minecraft.resources.ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support
+ protected static final net.minecraft.resources.ResourceLocation PURPUR_CLIENT = net.minecraft.resources.ResourceLocation.fromNamespaceAndPath("purpur", "client"); // Purpur - Purpur client support
public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, net.minecraft.server.level.ServerPlayer player) { // CraftBukkit
this.server = server;
@@ -118,6 +_,16 @@
@Override
public void handleKeepAlive(ServerboundKeepAlivePacket packet) {
+ // Purpur start - Alternative Keepalive Handling
+ if (org.purpurmc.purpur.PurpurConfig.useAlternateKeepAlive) {
+ if (this.keepAlivePending && !keepAlives.isEmpty() && keepAlives.contains(packet.getId())) {
+ int ping = (int) (Util.getMillis() - packet.getId());
+ this.latency = (this.latency * 3 + ping) / 4;
+ this.keepAlivePending = false;
+ keepAlives.clear(); // we got a valid response, lets roll with it and forget the rest
+ }
+ } else
+ // Purpur end - Alternative Keepalive Handling
if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) {
int i = (int)(Util.getMillis() - this.keepAliveTime);
this.latency = (this.latency * 3 + i) / 4;
@@ -159,6 +_,13 @@
ServerGamePacketListenerImpl.LOGGER.error("Couldn't register custom payload", ex);
this.disconnect(Component.literal("Invalid payload REGISTER!"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
}
+ // Purpur start - Purpur client support
+ } else if (identifier.equals(PURPUR_CLIENT)) {
+ try {
+ player.purpurClient = true;
+ } catch (Exception ignore) {
+ }
+ // Purpur end - Purpur client support
} else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) {
try {
String channels = payload.toString(com.google.common.base.Charsets.UTF_8);
@@ -238,6 +_,22 @@
// Paper start - give clients a longer time to respond to pings as per pre 1.12.2 timings
// This should effectively place the keepalive handling back to "as it was" before 1.12.2
final long elapsedTime = millis - this.keepAliveTime;
+
+ // Purpur start - Alternative Keepalive Handling
+ if (org.purpurmc.purpur.PurpurConfig.useAlternateKeepAlive) {
+ if (elapsedTime >= 1000L) { // 1 second
+ if (this.keepAlivePending && !this.processedDisconnect && keepAlives.size() * 1000L >= KEEPALIVE_LIMIT) {
+ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT);
+ } else if (this.checkIfClosed(millis)) {
+ this.keepAlivePending = true;
+ this.keepAliveTime = millis; // hijack this field for 1 second intervals
+ this.keepAlives.add(millis); // currentTime is ID
+ this.send(new ClientboundKeepAlivePacket(millis));
+ }
+ }
+ } else
+ // Purpur end - Alternative Keepalive Handling
+
if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // use vanilla's 15000L between keep alive packets
if (this.keepAlivePending) {
if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected

View File

@@ -0,0 +1,224 @@
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -328,6 +_,20 @@
this.tickEndEvent = new io.papermc.paper.event.packet.ClientTickEndEvent(player.getBukkitEntity()); // Paper - add client tick end event
}
+ // 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.ackBlockChangesUpTo > -1) {
@@ -386,6 +_,12 @@
if (this.player.getLastActionTime() > 0L
&& this.server.getPlayerIdleTimeout() > 0
&& Util.getMillis() - this.player.getLastActionTime() > this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits
+ // 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.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854
this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause
}
@@ -631,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);
@@ -711,6 +_,7 @@
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
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;
}
@@ -1179,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;
@@ -1203,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;
}
@@ -1222,31 +_,45 @@
Optional<String> optional = packet.title();
optional.ifPresent(list::add);
list.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>> consumer = optional.isPresent()
- ? texts -> this.signBook(texts.get(0), texts.subList(1, texts.size()), slot)
- : texts -> this.updateBookContents(texts, slot);
+ ? texts -> this.signBook(texts.get(0), texts.subList(1, texts.size()), slot, hasSignPerm) // Purpur - Allow color codes in books
+ : texts -> this.updateBookContents(texts, slot, hasEditPerm); // Purpur - Allow color codes in books
this.filterTextPacket(list).thenAcceptAsync(consumer, this.server);
}
}
private void updateBookContents(List<FilteredText> pages, int index) {
+ // Purpur start - Allow color codes in books
+ updateBookContents(pages, index, false);
+ }
+ private void updateBookContents(List<FilteredText> pages, int index, boolean hasPerm) {
+ // Purpur end - Allow color codes in books
// CraftBukkit start
ItemStack handItem = this.player.getInventory().getItem(index);
ItemStack item = handItem.copy();
// CraftBukkit end
if (item.has(DataComponents.WRITABLE_BOOK_CONTENT)) {
- List<Filterable<String>> list = pages.stream().map(this::filterableFromOutgoing).toList();
+ List<Filterable<String>> list = pages.stream().map(filteredText -> filterableFromOutgoing(filteredText).map(s -> color(s, hasPerm))).toList(); // Purpur - Allow color codes in books
item.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list));
this.player.getInventory().setItem(index, CraftEventFactory.handleEditBookEvent(this.player, index, handItem, item)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent)
}
}
private void signBook(FilteredText title, List<FilteredText> pages, int index) {
+ // Purpur start - Allow color codes in books
+ signBook(title, pages, index, false);
+ }
+ private void signBook(FilteredText title, List<FilteredText> pages, int index, boolean hasPerm) {
+ // Purpur end - Allow color codes in books
ItemStack item = this.player.getInventory().getItem(index);
if (item.has(DataComponents.WRITABLE_BOOK_CONTENT)) {
ItemStack itemStack = item.transmuteCopy(Items.WRITTEN_BOOK);
itemStack.remove(DataComponents.WRITABLE_BOOK_CONTENT);
- List<Filterable<Component>> list = pages.stream().map(filteredText -> this.filterableFromOutgoing(filteredText).<Component>map(Component::literal)).toList();
+ List<Filterable<Component>> list = pages.stream().map((filteredText) -> this.filterableFromOutgoing(filteredText).map(s -> hexColor(s, hasPerm))).toList(); // Purpur - Allow color codes in books
itemStack.set(
DataComponents.WRITTEN_BOOK_CONTENT,
new WrittenBookContent(this.filterableFromOutgoing(title), this.player.getName().getString(), 0, list, true)
@@ -1260,6 +_,16 @@
return this.player.isTextFilteringEnabled() ? Filterable.passThrough(filteredText.filteredOrEmpty()) : Filterable.from(filteredText);
}
+ // 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(ServerboundEntityTagQueryPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@@ -1295,7 +_,15 @@
@Override
public void handleMovePlayer(ServerboundMovePlayerPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
- 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 serverLevel = this.player.serverLevel();
@@ -1470,7 +_,7 @@
movedWrongly = true;
if (event.getLogWarning())
// Paper end
- LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
+ LOGGER.warn("{} moved wrongly!, ({})", this.player.getName().getString(), verticalDelta); // Purpur - AFK API
} // Paper
}
@@ -1536,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);
@@ -1592,6 +_,13 @@
this.player.tryResetCurrentImpulseContext();
}
+ // Purpur start - Dont run with scissors!
+ if (this.player.serverLevel().purpurConfig.dontRunWithScissors && this.player.isSprinting() && !(this.player.serverLevel().purpurConfig.ignoreScissorsInWater && this.player.isInWater()) && !(this.player.serverLevel().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.serverLevel(), this.player.damageSources().scissors(), (float) this.player.serverLevel().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() - x, this.player.getY() - y, this.player.getZ() - z);
this.lastGoodX = this.player.getX();
this.lastGoodY = this.player.getY();
@@ -1640,6 +_,17 @@
}
}
+ // Purpur start - Dont run with scissors!
+ public boolean isScissors(ItemStack stack) {
+ if (!stack.is(Items.SHEARS)) return false;
+
+ ResourceLocation itemModelReference = stack.get(net.minecraft.core.component.DataComponents.ITEM_MODEL);
+ if (itemModelReference != null && itemModelReference.equals(this.player.serverLevel().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!
+
// Paper start - optimise out extra getCubes
private boolean hasNewCollision(final ServerLevel level, final Entity entity, final AABB oldBox, final AABB newBox) {
final List<AABB> collisionsBB = new java.util.ArrayList<>();
@@ -2010,6 +_,7 @@
boolean cancelled;
if (hitResult == null || hitResult.getType() != HitResult.Type.BLOCK) {
+ if (this.player.gameMode.shiftClickMended(itemInHand)) return; // Purpur - Shift right click to use exp for mending
org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemInHand, hand);
cancelled = event.useItemInHand() == Event.Result.DENY;
} else {
@@ -2751,6 +_,7 @@
AABB boundingBox = target.getBoundingBox();
if (this.player.canInteractWithEntity(boundingBox, 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
packet.dispatch(
new ServerboundInteractPacket.Handler() {
private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction entityInteraction, PlayerInteractEntityEvent event) { // CraftBukkit

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -307,7 +_,7 @@
ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!");
ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(string1)); // 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", string1);
}
} catch (AuthenticationUnavailableException var4) {

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
@@ -37,6 +_,7 @@
} else {
this.hasRequestedStatus = true;
// this.connection.send(new ClientboundStatusResponsePacket(this.status)); // Paper
+ if (net.minecraft.server.MinecraftServer.getServer().getStatus().version().isEmpty()) return; // Purpur - Fix 'outdated server' showing in ping before server fully boots - do not respond to pings before we know the protocol version
com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(net.minecraft.server.MinecraftServer.getServer(), this.connection); // Paper - handle status request
}
}

View File

@@ -0,0 +1,56 @@
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -396,6 +_,7 @@
scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam);
}
// Paper end - Configurable player collision
+ org.purpurmc.purpur.task.BossBarTask.addToAll(player); // Purpur - Implement TPSBar
PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), loggableAddress, player.getId(), serverLevel.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ());
// Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead
if (player.isDeadOrDying()) {
@@ -501,6 +_,7 @@
}
public net.kyori.adventure.text.Component remove(ServerPlayer player, 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 serverLevel = player.serverLevel();
player.awardStat(Stats.LEAVE_GAME);
// CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it
@@ -665,7 +_,7 @@
// return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile)
// ? Component.translatable("multiplayer.disconnect.server_full")
// : null;
- if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile)) {
+ if (this.players.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameProfile))) { // Purpur - Allow player join full server by permission
event.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure
}
}
@@ -919,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(@Nullable net.kyori.adventure.text.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(Packet<?> packet, ResourceKey<Level> dimension) {
for (ServerPlayer serverPlayer : this.players) {
if (serverPlayer.level().dimension() == dimension) {
@@ -1002,6 +_,7 @@
} else {
b = (byte)(24 + permLevel);
}
+ if (b < 28 && player.getBukkitEntity().hasPermission("purpur.debug.f3n")) b = 28; // Purpur - Add permission for F3+N debug
player.connection.send(new ClientboundEntityEventPacket(player, b));
}

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(int requiredSleepPercentage, List<ServerPlayer> sleepingPlayers) {
// CraftBukkit start
- int i = (int) sleepingPlayers.stream().filter(player -> player.isSleepingLongEnough() || player.fauxSleeping).count();
+ int i = (int) sleepingPlayers.stream().filter(player -> player.isSleepingLongEnough() || player.fauxSleeping || (player.level().purpurConfig.idleTimeoutCountAsSleeping && player.isAfk())).count(); // Purpur - AFK API
boolean anyDeepSleep = sleepingPlayers.stream().anyMatch(Player::isSleepingLongEnough);
return anyDeepSleep && i >= this.sleepersNeeded(requiredSleepPercentage);
// CraftBukkit end
@@ -43,7 +_,7 @@
for (ServerPlayer serverPlayer : players) {
if (!serverPlayer.isSpectator()) {
this.activePlayers++;
- if (serverPlayer.isSleeping() || serverPlayer.fauxSleeping) { // CraftBukkit
+ if (serverPlayer.isSleeping() || serverPlayer.fauxSleeping || (serverPlayer.level().purpurConfig.idleTimeoutCountAsSleeping && serverPlayer.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
@@ -138,6 +_,7 @@
try {
ResourceKey<Recipe<?>> resourceKey = ResourceKey.create(Registries.RECIPE, ResourceLocation.parse(string));
if (!isRecognized.test(resourceKey)) {
+ if (!org.purpurmc.purpur.PurpurConfig.loggerSuppressUnrecognizedRecipeErrors) // Purpur - Logger settings (suppressing pointless logs)
LOGGER.error("Tried to load unrecognized recipe: {} removed now.", resourceKey);
} else {
output.accept(resourceKey);

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/util/StringUtil.java
+++ b/net/minecraft/util/StringUtil.java
@@ -87,6 +_,7 @@
// Paper start - Username validation
public static boolean isReasonablePlayerName(final String name) {
+ if (true) return org.purpurmc.purpur.PurpurConfig.usernameValidCharactersPattern.matcher(name).matches(); // Purpur - Configurable valid characters for usernames
if (name.isEmpty() || name.length() > 16) {
return false;
}

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/world/damagesource/CombatRules.java
+++ b/net/minecraft/world/damagesource/CombatRules.java
@@ -15,7 +_,7 @@
public static float getDamageAfterAbsorb(LivingEntity entity, float damage, DamageSource damageSource, float armorValue, float armorToughness) {
float f = 2.0F + armorToughness / 4.0F;
- float f1 = Mth.clamp(armorValue - damage / f, armorValue * 0.2F, 20.0F);
+ float f1 = Mth.clamp(armorValue - damage / f, armorValue * 0.2F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur - Add attribute clamping and armor limit config
float f2 = f1 / 25.0F;
ItemStack weaponItem = damageSource.getWeaponItem();
float f3;
@@ -30,7 +_,7 @@
}
public static float getDamageAfterMagicAbsorb(float damage, float enchantModifiers) {
- float f = Mth.clamp(enchantModifiers, 0.0F, 20.0F);
+ float f = Mth.clamp(enchantModifiers, 0.0F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur - Add attribute clamping and armor limit config
return damage * (1.0F - f / 25.0F);
}
}

View File

@@ -0,0 +1,27 @@
--- a/net/minecraft/world/damagesource/CombatTracker.java
+++ b/net/minecraft/world/damagesource/CombatTracker.java
@@ -54,7 +_,7 @@
private Component getMessageForAssistedFall(Entity entity, Component entityDisplayName, String hasWeaponTranslationKey, String noWeaponTranslationKey) {
ItemStack itemStack = entity instanceof LivingEntity livingEntity ? livingEntity.getMainHandItem() : ItemStack.EMPTY;
- return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME)
+ return !itemStack.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemStack.has(DataComponents.CUSTOM_NAME)) // Purpur - always show item in player death messages
? Component.translatable(hasWeaponTranslationKey, this.mob.getDisplayName(), entityDisplayName, itemStack.getDisplayName())
: Component.translatable(noWeaponTranslationKey, this.mob.getDisplayName(), entityDisplayName);
}
@@ -98,6 +_,15 @@
Component component = ComponentUtils.wrapInSquareBrackets(Component.translatable(string + ".link")).withStyle(INTENTIONAL_GAME_DESIGN_STYLE);
return Component.translatable(string + ".message", this.mob.getDisplayName(), component);
} else {
+ // Purpur start - Dont run with scissors!
+ if (damageSource.isScissors()) {
+ return damageSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgRunWithScissors, this.mob);
+ // Purpur start - Stonecutter damage
+ } else if (damageSource.isStonecutter()) {
+ return damageSource.getLocalizedDeathMessage(org.purpurmc.purpur.PurpurConfig.deathMsgStonecutter, this.mob);
+ // Purpur end - Stonecutter damage
+ }
+ // Purpur end - Dont run with scissors!
return damageSource.getLocalizedDeathMessage(this.mob);
}
}

View File

@@ -0,0 +1,73 @@
--- a/net/minecraft/world/damagesource/DamageSource.java
+++ b/net/minecraft/world/damagesource/DamageSource.java
@@ -30,6 +_,8 @@
@Nullable
private org.bukkit.block.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();
@@ -42,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;
@@ -103,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
@@ -169,11 +_,20 @@
} else {
Component component = this.causingEntity == null ? this.directEntity.getDisplayName() : this.causingEntity.getDisplayName();
ItemStack itemStack = this.causingEntity instanceof LivingEntity livingEntity1 ? livingEntity1.getMainHandItem() : ItemStack.EMPTY;
- return !itemStack.isEmpty() && itemStack.has(DataComponents.CUSTOM_NAME)
+ return !itemStack.isEmpty() && (org.purpurmc.purpur.PurpurConfig.playerDeathsAlwaysShowItem || itemStack.has(DataComponents.CUSTOM_NAME)) // Purpur - always show item in player death messages
? Component.translatable(string + ".item", livingEntity.getDisplayName(), component, itemStack.getDisplayName())
: Component.translatable(string, livingEntity.getDisplayName(), component);
}
}
+
+ // 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,39 @@
--- a/net/minecraft/world/damagesource/DamageSources.java
+++ b/net/minecraft/world/damagesource/DamageSources.java
@@ -42,6 +_,8 @@
private final DamageSource stalagmite;
private final DamageSource outsideBorder;
private final DamageSource genericKill;
+ private final DamageSource scissors; // Purpur - Dont run with scissors!
+ private final DamageSource stonecutter; // Purpur - Stonecutter damage
public DamageSources(RegistryAccess registry) {
this.damageTypes = registry.lookupOrThrow(Registries.DAMAGE_TYPE);
@@ -70,6 +_,8 @@
this.stalagmite = this.source(DamageTypes.STALAGMITE);
this.outsideBorder = this.source(DamageTypes.OUTSIDE_BORDER);
this.genericKill = this.source(DamageTypes.GENERIC_KILL);
+ this.scissors = this.source(DamageTypes.MAGIC).scissors(); // Purpur - Dont run with scissors!
+ this.stonecutter = this.source(DamageTypes.MAGIC).stonecutter(); // Purpur - Stonecutter damage
}
private DamageSource source(ResourceKey<DamageType> damageTypeKey) {
@@ -83,6 +_,18 @@
private DamageSource source(ResourceKey<DamageType> damageTypeKey, @Nullable Entity causingEntity, @Nullable Entity directEntity) {
return new DamageSource(this.damageTypes.getOrThrow(damageTypeKey), causingEntity, directEntity);
}
+
+ // Purpur start - Dont run with scissor
+ public DamageSource scissors() {
+ return this.scissors;
+ }
+ // Purpur end - Dont run with scissors!
+
+ // Purpur start - Stonecutter damage
+ public DamageSource stonecutter() {
+ return this.stonecutter;
+ }
+ // Purpur end - Stonecutter damage
public DamageSource inFire() {
return this.inFire;

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(ServerLevel level, LivingEntity entity, int amplifier) {
if (entity instanceof Player player) {
- player.causeFoodExhaustion(0.005F * (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent
+ player.causeFoodExhaustion(entity.level().purpurConfig.humanHungerExhaustionAmount * (amplifier + 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(ServerLevel level, LivingEntity entity, int amplifier) {
- if (entity.getHealth() > 1.0F) {
- entity.hurtServer(level, entity.damageSources().magic().knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.POISON), 1.0F); // CraftBukkit
+ if (entity.getHealth() > entity.level().purpurConfig.entityMinimalHealthPoison) { // Purpur
+ entity.hurtServer(level, entity.damageSources().magic().knownCause(org.bukkit.event.entity.EntityDamageEvent.DamageCause.POISON), entity.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(ServerLevel level, LivingEntity entity, int amplifier) {
if (entity.getHealth() < entity.getMaxHealth()) {
- entity.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit
+ entity.heal(entity.level().purpurConfig.entityHealthRegenAmount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit // Purpur - Config MobEffect by world
}
return true;

View File

@@ -0,0 +1,12 @@
--- a/net/minecraft/world/effect/SaturationMobEffect.java
+++ b/net/minecraft/world/effect/SaturationMobEffect.java
@@ -16,7 +_,8 @@
int oldFoodLevel = player.getFoodData().foodLevel;
org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, amplifier + 1 + oldFoodLevel);
if (!event.isCancelled()) {
- player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F);
+ if (player.level().purpurConfig.playerBurpWhenFull && event.getFoodLevel() == 20 && oldFoodLevel < 20) player.burpDelay = player.level().purpurConfig.playerBurpDelay; // Purpur - Burp delay
+ player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, entity.level().purpurConfig.humanSaturationRegenAmount); // Purpur - Config MobEffect by world
}
((org.bukkit.craftbukkit.entity.CraftPlayer) player.getBukkitEntity()).sendHealthUpdate();

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(ServerLevel level, LivingEntity entity, int amplifier) {
- entity.hurtServer(level, entity.damageSources().wither(), 1.0F);
+ entity.hurtServer(level, entity.damageSources().wither(), entity.level().purpurConfig.entityWitherDegenerationAmount); // Purpur - Config MobEffect by world
return true;
}

View File

@@ -0,0 +1,186 @@
--- a/net/minecraft/world/entity/Entity.java
+++ b/net/minecraft/world/entity/Entity.java
@@ -136,7 +_,7 @@
import org.slf4j.Logger;
public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity { // Paper - rewrite chunk system // Paper - optimise entity tracker
-
+ 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;
public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation
@@ -253,9 +_,10 @@
public double xOld;
public double yOld;
public double zOld;
+ public float maxUpStep; // Purpur - Add option to set armorstand step height
public boolean noPhysics;
private boolean wasOnFire;
- 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 = -this.getFireImmuneTicks();
public boolean wasTouchingWater;
@@ -289,8 +_,8 @@
public 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};
@@ -343,6 +_,7 @@
public long activatedTick = Integer.MIN_VALUE;
public boolean isTemporarilyActive;
public long activatedImmunityTick = Integer.MIN_VALUE;
+ public @Nullable Boolean immuneToFire = null; // Purpur - Fire immune API
public void inactiveTick() {
}
@@ -523,10 +_,21 @@
}
// Paper end - optimise entity tracker
+ // Purpur start - Add canSaveToDisk to Entity
+ public boolean canSaveToDisk() {
+ return true;
+ }
+ // Purpur end - Add canSaveToDisk to Entity
+
public Entity(EntityType<?> entityType, Level level) {
this.type = entityType;
this.level = level;
this.dimensions = entityType.getDimensions();
+ // 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;
@@ -905,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(io.papermc.paper.util.MCUtil.toLocation(this.level, this.level.getSharedSpawnPos())); else // Purpur - Add option to teleport to spawn on nether ceiling damage
this.onBelowWorld();
}
}
@@ -1827,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(float fallDistance, float multiplier, DamageSource source) {
@@ -1896,7 +_,7 @@
return this.isInWater() || flag;
}
- public void updateInWaterStateAndDoWaterCurrentPushing() {
+ public void updateInWaterStateAndDoWaterCurrentPushing() { // Purpur - Movement options for armor stands - package-private -> public - TODO: use AT file
if (this.getVehicle() instanceof AbstractBoat abstractBoat && !abstractBoat.isUnderWater()) {
this.wasTouchingWater = false;
} else if (this.updateFluidHeightAndDoFluidPushing(FluidTags.WATER, 0.014)) {
@@ -2522,6 +_,13 @@
compound.putBoolean("Paper.FreezeLock", true);
}
// Paper end
+
+ // Purpur start - Fire immune API
+ if (immuneToFire != null) {
+ compound.putBoolean("Purpur.FireImmune", immuneToFire);
+ }
+ // Purpur end - Fire immune API
+
return compound;
} catch (Throwable var9) {
CrashReport crashReport = CrashReport.forThrowable(var9, "Saving entity NBT");
@@ -2671,6 +_,13 @@
freezeLocked = compound.getBoolean("Paper.FreezeLock");
}
// Paper end
+
+ // Purpur start - Fire immune API
+ if (compound.contains("Purpur.FireImmune")) {
+ immuneToFire = compound.getBoolean("Purpur.FireImmune");
+ }
+ // Purpur end - Fire immune API
+
} catch (Throwable var17) {
CrashReport crashReport = CrashReport.forThrowable(var17, "Loading entity NBT");
CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being loaded");
@@ -2917,6 +_,7 @@
if (this.isAlive() && this instanceof Leashable leashable) {
if (leashable.getLeashHolder() == player) {
if (!this.level().isClientSide()) {
+ if (hand == InteractionHand.OFF_HAND && (level().purpurConfig.villagerCanBeLeashed || level().purpurConfig.wanderingTraderCanBeLeashed) && this instanceof net.minecraft.world.entity.npc.AbstractVillager) return InteractionResult.CONSUME; // Purpur - Allow leashing villagers
// CraftBukkit start - fire PlayerUnleashEntityEvent
// Paper start - Expand EntityUnleashEvent
org.bukkit.event.player.PlayerUnleashEntityEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.hasInfiniteMaterials());
@@ -3242,15 +_,18 @@
return Vec3.directionFromRotation(this.getRotationVector());
}
+ public BlockPos portalPos = BlockPos.ZERO; // Purpur - Fix stuck in portals
public void setAsInsidePortal(Portal portal, 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
}
}
}
@@ -3455,7 +_,7 @@
}
public int getMaxAirSupply() {
- return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+ return this.level == null? this.maxAirTicks : this.level().purpurConfig.drowningAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() // Purpur - Drowning Settings
}
public int getAirSupply() {
@@ -3950,7 +_,7 @@
// CraftBukkit end
public boolean canUsePortal(boolean allowPassengers) {
- return (allowPassengers || !this.isPassenger()) && this.isAlive();
+ return (allowPassengers || !this.isPassenger()) && this.isAlive() && (this.level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer); // Purpur - Entities can use portals
}
public boolean canTeleport(Level fromLevel, Level toLevel) {
@@ -4482,6 +_,12 @@
return Mth.lerp(partialTick, 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
+
// Paper start - optimise collisions
public boolean updateFluidHeightAndDoFluidPushing(final TagKey<Fluid> fluid, final double flowScale) {
if (this.touchingUnloadedChunk()) {
@@ -4880,7 +_,7 @@
}
public float maxUpStep() {
- return 0.0F;
+ return maxUpStep; // Purpur - Add option to set armorstand step height
}
public void onExplosionHit(@Nullable Entity entity) {

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/EntitySelector.java
+++ b/net/minecraft/world/entity/EntitySelector.java
@@ -28,6 +_,8 @@
return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks;
};
// Paper end - Ability to control player's insomnia and phantoms
+ public static Predicate<Player> notAfk = (player) -> !player.isAfk(); // Purpur - AFK API
+
// Paper start - Affects Spawning API
public static final Predicate<Entity> PLAYER_AFFECTS_SPAWNING = (entity) -> {
return !entity.isSpectator() && entity.isAlive() && entity instanceof Player player && player.affectsSpawning;

View File

@@ -0,0 +1,52 @@
--- a/net/minecraft/world/entity/EntityType.java
+++ b/net/minecraft/world/entity/EntityType.java
@@ -1083,6 +_,16 @@
return register(vanillaEntityId(key), builder);
}
+ // Purpur start - PlayerSetSpawnerTypeWithEggEvent
+ public static EntityType<?> getFromBukkitType(org.bukkit.entity.EntityType bukkitType) {
+ return getFromKey(ResourceLocation.parse(bukkitType.getKey().toString()));
+ }
+
+ public static EntityType<?> getFromKey(ResourceLocation location) {
+ return BuiltInRegistries.ENTITY_TYPE.getValue(location);
+ }
+ // Purpur end - PlayerSetSpawnerTypeWithEggEvent
+
public static ResourceLocation getKey(EntityType<?> entityType) {
return BuiltInRegistries.ENTITY_TYPE.getKey(entityType);
}
@@ -1312,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;
}
@@ -1370,7 +_,14 @@
entity.load(tag);
},
// Paper end - Don't fire sync event during generation
- () -> LOGGER.warn("Skipping Entity with id {}", tag.getString("id"))
+ // Purpur start - log skipped entity's position
+ () -> {LOGGER.warn("Skipping Entity with id {}", tag.getString("id"));
+ try {
+ ListTag pos = tag.getList("Pos", 6);
+ EntityType.LOGGER.warn("Location: {} {},{},{}", level.getWorld().getName(), pos.getDouble(0), pos.getDouble(1), pos.getDouble(2));
+ } catch (Throwable ignore) {}
+ }
+ // Purpur end - log skipped entity's position
);
}

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/world/entity/ExperienceOrb.java
+++ b/net/minecraft/world/entity/ExperienceOrb.java
@@ -323,7 +_,7 @@
public void playerTouch(Player entity) {
if (entity instanceof ServerPlayer serverPlayer) {
if (entity.takeXpDelay == 0 && new com.destroystokyo.paper.event.player.PlayerPickupExperienceEvent(serverPlayer.getBukkitEntity(), (org.bukkit.entity.ExperienceOrb) this.getBukkitEntity()).callEvent()) { // Paper - PlayerPickupExperienceEvent
- entity.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(entity, 2, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2;
+ entity.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(entity, this.level().purpurConfig.playerExpPickupDelay, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2; // Purpur - Configurable player pickup exp delay
entity.take(this, 1);
int i = this.repairPlayerItems(serverPlayer, this.value);
if (i > 0) {
@@ -339,7 +_,7 @@
}
private int repairPlayerItems(ServerPlayer player, int value) {
- Optional<EnchantedItemInUse> randomItemWith = EnchantmentHelper.getRandomItemWith(
+ Optional<EnchantedItemInUse> randomItemWith = 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 (randomItemWith.isPresent()) {

View File

@@ -0,0 +1,16 @@
--- a/net/minecraft/world/entity/GlowSquid.java
+++ b/net/minecraft/world/entity/GlowSquid.java
@@ -25,6 +_,13 @@
super(entityType, level);
}
+ // Purpur start - Flying squids! Oh my!
+ @Override
+ public boolean canFly() {
+ return this.level().purpurConfig.glowSquidsCanFly;
+ }
+ // Purpur end - Flying squids! Oh my!
+
@Override
protected ParticleOptions getInkParticle() {
return ParticleTypes.GLOW_SQUID_INK;

View File

@@ -0,0 +1,177 @@
--- a/net/minecraft/world/entity/LivingEntity.java
+++ b/net/minecraft/world/entity/LivingEntity.java
@@ -459,6 +_,12 @@
if (d < 0.0) {
double damagePerBlock = this.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(io.papermc.paper.util.MCUtil.toLocation(this.level(), this.level().getSharedSpawnPos()));
+ return;
+ }
+ // Purpur end - Add option to teleport to spawn if outside world border
this.hurtServer(serverLevel1, this.damageSources().outOfBorder(), Math.max(1, Mth.floor(-d * damagePerBlock)));
}
}
@@ -472,7 +_,7 @@
&& (!flag || !((Player)this).getAbilities().invulnerable);
if (flag1) {
this.setAirSupply(this.decreaseAirSupply(this.getAirSupply()));
- if (this.getAirSupply() == -20) {
+ if (this.getAirSupply() == -this.level().purpurConfig.drowningDamageInterval) { // Purpur - Drowning Settings
this.setAirSupply(0);
Vec3 deltaMovement = this.getDeltaMovement();
@@ -492,7 +_,7 @@
);
}
- this.hurt(this.damageSources().drown(), 2.0F);
+ this.hurt(this.damageSources().drown(), (float) this.level().purpurConfig.damageFromDrowning); // Purpur - Drowning Settings
}
} else if (this.getAirSupply() < this.getMaxAirSupply()) {
this.setAirSupply(this.increaseAirSupply(this.getAirSupply()));
@@ -1009,14 +_,32 @@
if (lookingEntity != null) {
ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.HEAD);
EntityType<?> type = lookingEntity.getType();
- if (type == EntityType.SKELETON && itemBySlot.is(Items.SKELETON_SKULL)
- || type == EntityType.ZOMBIE && itemBySlot.is(Items.ZOMBIE_HEAD)
- || type == EntityType.PIGLIN && itemBySlot.is(Items.PIGLIN_HEAD)
- || type == EntityType.PIGLIN_BRUTE && itemBySlot.is(Items.PIGLIN_HEAD)
- || type == EntityType.CREEPER && itemBySlot.is(Items.CREEPER_HEAD)) {
- d *= 0.5;
- }
- }
+ // Purpur start - Mob head visibility percent
+ if (type == EntityType.SKELETON && itemBySlot.is(Items.SKELETON_SKULL)) {
+ d *= lookingEntity.level().purpurConfig.skeletonHeadVisibilityPercent;
+ }
+ else if (type == EntityType.ZOMBIE && itemBySlot.is(Items.ZOMBIE_HEAD)) {
+ d *= lookingEntity.level().purpurConfig.zombieHeadVisibilityPercent;
+ }
+ else if ((type == EntityType.PIGLIN || type == EntityType.PIGLIN_BRUTE) && itemBySlot.is(Items.PIGLIN_HEAD)) {
+ d *= lookingEntity.level().purpurConfig.piglinHeadVisibilityPercent;
+ }
+ else if (type == EntityType.CREEPER && itemBySlot.is(Items.CREEPER_HEAD)) {
+ d *= lookingEntity.level().purpurConfig.creeperHeadVisibilityPercent;
+ }
+ // Purpur end - Mob head visibility percent
+ }
+
+ // Purpur start - Configurable mob blindness
+ if (lookingEntity 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 d;
}
@@ -1063,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;
@@ -1372,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 (amount < 0.0F) {
amount = 0.0F;
@@ -1536,11 +_,11 @@
protected Player resolvePlayerResponsibleForDamage(DamageSource damageSource) {
Entity entity = damageSource.getEntity();
if (entity instanceof Player player) {
- this.lastHurtByPlayerTime = 100;
+ this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur - Config for mob last hurt by player time
this.lastHurtByPlayer = player;
return player;
} else if (entity instanceof Wolf wolf && wolf.isTame()) {
- this.lastHurtByPlayerTime = 100;
+ this.lastHurtByPlayerTime = this.level().purpurConfig.mobLastHurtByPlayerTime; // Purpur - Config for mob last hurt by player time
if (wolf.getOwner() instanceof Player player1) {
this.lastHurtByPlayer = player1;
} else {
@@ -1594,6 +_,18 @@
}
}
+ // 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().items) {
+ 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(itemStack == null);
@@ -1790,6 +_,7 @@
boolean flag = this.lastHurtByPlayerTime > 0;
this.dropEquipment(level); // CraftBukkit - from below
if (this.shouldDropLoot() && level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+ if (!(damageSource.is(net.minecraft.world.damagesource.DamageTypes.CRAMMING) && level().purpurConfig.disableDropsOnCrammingDeath)) { // Purpur - Disable loot drops on death by cramming
this.dropFromLootTable(level, damageSource, flag);
// Paper start
final boolean prev = this.clearEquipmentSlots;
@@ -1798,6 +_,7 @@
// Paper end
this.dropCustomDeathLoot(level, damageSource, flag);
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
@@ -2996,6 +_,7 @@
float f = (float)(d * 10.0 - 3.0);
if (f > 0.0F) {
this.playSound(this.getFallDamageSound((int)f), 1.0F, 1.0F);
+ if (level().purpurConfig.elytraKineticDamage) // Purpur - Toggle for kinetic damage
this.hurt(this.damageSources().flyIntoWall(), f);
}
}
@@ -4423,6 +_,12 @@
? slot == EquipmentSlot.MAINHAND && this.canUseSlot(EquipmentSlot.MAINHAND)
: slot == equippable.slot() && this.canUseSlot(equippable.slot()) && equippable.canBeEquippedBy(this.getType());
}
+
+ // 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(LivingEntity entity, EquipmentSlot slot) {
return slot != EquipmentSlot.HEAD && slot != EquipmentSlot.MAINHAND && slot != EquipmentSlot.OFFHAND

View File

@@ -0,0 +1,84 @@
--- a/net/minecraft/world/entity/Mob.java
+++ b/net/minecraft/world/entity/Mob.java
@@ -145,6 +_,7 @@
private BlockPos restrictCenter = BlockPos.ZERO;
private float restrictRadius = -1.0F;
public boolean aware = true; // CraftBukkit
+ public int ticksSinceLastInteraction; // Purpur - Entity lifespan
protected Mob(EntityType<? extends Mob> entityType, Level level) {
super(entityType, level);
@@ -294,6 +_,7 @@
target = null;
}
}
+ if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan
this.target = target;
return true;
// CraftBukkit end
@@ -337,7 +_,27 @@
}
profilerFiller.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(DamageSource source) {
@@ -486,6 +_,7 @@
compound.putBoolean("NoAI", this.isNoAi());
}
compound.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit
+ compound.putInt("Purpur.ticksSinceLastInteraction", this.ticksSinceLastInteraction); // Purpur - Entity lifespan
}
@Override
@@ -568,6 +_,11 @@
this.aware = compound.getBoolean("Bukkit.Aware");
}
// CraftBukkit end
+ // Purpur start - Entity lifespan
+ if (compound.contains("Purpur.ticksSinceLastInteraction")) {
+ this.ticksSinceLastInteraction = compound.getInt("Purpur.ticksSinceLastInteraction");
+ }
+ // Purpur end - Entity lifespan
}
@Override
@@ -1282,7 +_,7 @@
);
}
- this.setLeftHanded(random.nextFloat() < 0.05F);
+ this.setLeftHanded(random.nextFloat() < level.getLevel().purpurConfig.entityLeftHandedChance); // Purpur - Changeable Mob Left Handed Chance
return spawnGroupData;
}
@@ -1621,6 +_,7 @@
this.playAttackSound();
}
+ if (target instanceof net.minecraft.server.level.ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur - Entity lifespan
return flag;
}

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
+++ b/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
@@ -29,6 +_,7 @@
@Override
public double sanitizeValue(double value) {
+ if (!org.purpurmc.purpur.PurpurConfig.clampAttributes) return Double.isNaN(value) ? this.minValue : value; // Purpur - Add attribute clamping and armor limit config
return Double.isNaN(value) ? this.minValue : Mth.clamp(value, this.minValue, this.maxValue);
}
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
+++ b/net/minecraft/world/entity/ai/behavior/AcquirePoi.java
@@ -86,7 +_,7 @@
};
// Paper start - optimise POI access
final java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>();
- io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, acquirablePois, predicate1, mob.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes);
+ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, acquirablePois, predicate1, mob.blockPosition(), level.purpurConfig.villagerAcquirePoiSearchRadius, level.purpurConfig.villagerAcquirePoiSearchRadius*level.purpurConfig.villagerAcquirePoiSearchRadius, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); // Purpur - Configurable villager search radius
final Set<Pair<Holder<PoiType>, BlockPos>> set = new java.util.HashSet<>(poiposes.size());
for (final Pair<Holder<PoiType>, BlockPos> poiPose : poiposes) {
if (predicate.test(level, poiPose.getSecond())) {

View File

@@ -0,0 +1,29 @@
--- a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
+++ b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
@@ -55,7 +_,7 @@
Node nextNode = path.getNextNode();
BlockPos blockPos = previousNode.asBlockPos();
BlockState blockState = level.getBlockState(blockPos);
- if (blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) {
+ if (blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)&& !DoorBlock.requiresRedstone(entity.level(), blockState, blockPos)) { // Purpur - Option to make doors require redstone
DoorBlock doorBlock = (DoorBlock)blockState.getBlock();
if (!doorBlock.isOpen(blockState)) {
// CraftBukkit start - entities opening doors
@@ -72,7 +_,7 @@
BlockPos blockPos1 = nextNode.asBlockPos();
BlockState blockState1 = level.getBlockState(blockPos1);
- if (blockState1.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) {
+ if (blockState1.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock) && !DoorBlock.requiresRedstone(entity.level(), blockState1, blockPos1)) { // Purpur - Option to make doors require redstone
DoorBlock doorBlock1 = (DoorBlock)blockState1.getBlock();
if (!doorBlock1.isOpen(blockState1)) {
// CraftBukkit start - entities opening doors
@@ -118,7 +_,7 @@
iterator.remove();
} else {
BlockState blockState = level.getBlockState(blockPos);
- if (!blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock)) {
+ if (!blockState.is(BlockTags.MOB_INTERACTABLE_DOORS, state -> state.getBlock() instanceof DoorBlock) || DoorBlock.requiresRedstone(entity.level(), blockState, blockPos)) { // Purpur - Option to make doors require redstone
iterator.remove();
} else {
DoorBlock doorBlock = (DoorBlock)blockState.getBlock();

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
+++ b/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
@@ -46,6 +_,7 @@
@Override
public boolean canStillUse(ServerLevel level, Villager entity, long gameTime) {
+ if (!entity.level().purpurConfig.villagerDisplayTradeItem) return false; // Purpur - Option for villager display trade item
return this.checkExtraStartConditions(level, entity)
&& this.lookTime > 0
&& entity.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).isPresent();

View File

@@ -0,0 +1,18 @@
--- a/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
+++ b/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
@@ -22,6 +_,7 @@
@Override
public boolean canUse() {
+ if (!this.llama.level().purpurConfig.llamaJoinCaravans || !this.llama.shouldJoinCaravan) return false; // Purpur - Llama API // Purpur - Config to disable Llama caravans
if (!this.llama.isLeashed() && !this.llama.inCaravan()) {
List<Entity> entities = this.llama.level().getEntities(this.llama, this.llama.getBoundingBox().inflate(9.0, 4.0, 9.0), entity1 -> {
EntityType<?> type = entity1.getType();
@@ -71,6 +_,7 @@
@Override
public boolean canContinueToUse() {
+ if (!this.llama.shouldJoinCaravan) return false; // Purpur - Llama API
if (this.llama.inCaravan() && this.llama.getCaravanHead().isAlive() && this.firstIsLeashed(this.llama, 0)) {
double d = this.llama.distanceToSqr(this.llama.getCaravanHead());
if (d > 676.0) {

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java
+++ b/net/minecraft/world/entity/ai/goal/RangedBowAttackGoal.java
@@ -116,9 +_,9 @@
}
this.mob.lookAt(target, 30.0F, 30.0F);
- } else {
+ } //else { // Purpur - MC-121706 - Fix mobs not looking up and down when strafing
this.mob.getLookControl().setLookAt(target, 30.0F, 30.0F);
- }
+ //} // Purpur - MC-121706 - Fix mobs not looking up and down when strafing
if (this.mob.isUsingItem()) {
if (!hasLineOfSight && this.seeTime < -60) {

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
+++ b/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
@@ -58,7 +_,7 @@
if (firstPassenger instanceof Player player) {
int temper = this.horse.getTemper();
int maxTemper = this.horse.getMaxTemper();
- if (maxTemper > 0 && this.horse.getRandom().nextInt(maxTemper) < temper && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this.horse, ((org.bukkit.craftbukkit.entity.CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent
+ if (((this.horse.level().purpurConfig.alwaysTameInCreative && player.hasInfiniteMaterials()) || (maxTemper > 0 && this.horse.getRandom().nextInt(maxTemper) < temper)) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this.horse, ((org.bukkit.craftbukkit.entity.CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent // Purpur - Config to always tame in Creative
this.horse.tameWithName(player);
return;
}

View File

@@ -0,0 +1,17 @@
--- a/net/minecraft/world/entity/ai/goal/SwellGoal.java
+++ b/net/minecraft/world/entity/ai/goal/SwellGoal.java
@@ -55,6 +_,14 @@
this.creeper.setSwellDir(-1);
} else {
this.creeper.setSwellDir(1);
+ // Purpur start - option to allow creeper to encircle target when fusing
+ if (this.creeper.level().purpurConfig.creeperEncircleTarget) {
+ net.minecraft.world.phys.Vec3 relative = this.creeper.position().subtract(this.target.position());
+ relative = relative.yRot((float) Math.PI / 3).normalize().multiply(2, 2, 2);
+ net.minecraft.world.phys.Vec3 destination = this.target.position().add(relative);
+ this.creeper.getNavigation().moveTo(destination.x, destination.y, destination.z, 1);
+ }
+ // Purpur end - option to allow creeper to encircle target when fusing
}
}
}

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
+++ b/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java
@@ -56,7 +_,7 @@
// Paper start - optimise POI access
java.util.List<Pair<Holder<PoiType>, BlockPos>> poiposes = new java.util.ArrayList<>();
// don't ask me why it's unbounded. ask mojang.
- io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes);
+ io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, type -> type.is(PoiTypes.HOME), predicate, entity.blockPosition(), level.purpurConfig.villagerNearestBedSensorSearchRadius, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); // Purpur - Configurable villager search radius
Path path = AcquirePoi.findPathToPois(entity, new java.util.HashSet<>(poiposes));
// Paper end - optimise POI access
if (path != null && path.canReach()) {

View File

@@ -0,0 +1,13 @@
--- a/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
+++ b/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
@@ -64,6 +_,10 @@
return false;
} else if (this.selector != null && !this.selector.test(target, level)) {
return false;
+ // Purpur start - AFK API
+ } else if (!level.purpurConfig.idleTimeoutTargetPlayer && target instanceof net.minecraft.server.level.ServerPlayer player && player.isAfk()) {
+ return false;
+ // Purpur end - AFK API
} else {
if (entity == null) {
if (this.isCombat && (!target.canBeSeenAsEnemy() || level.getDifficulty() == Difficulty.PEACEFUL)) {

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/world/entity/ambient/Bat.java
+++ b/net/minecraft/world/entity/ambient/Bat.java
@@ -231,7 +_,7 @@
} else {
int maxLocalRawBrightness = level.getMaxLocalRawBrightness(pos);
int i = 4;
- if (isHalloween()) {
+ if (Bat.isHalloweenSeason(level.getMinecraftWorld())) { // Purpur - Halloween options and optimizations
i = 7;
} else if (randomSource.nextBoolean()) {
return false;
@@ -243,6 +_,11 @@
}
}
+ // Pufferfish start - only check for spooky season once an hour
+ //private static boolean isSpookySeason = false;
+ //private static final int ONE_HOUR = 20 * 60 * 60;
+ //private static int lastSpookyCheck = -ONE_HOUR;
+ public static boolean isHalloweenSeason(Level level) { return level.purpurConfig.forceHalloweenSeason || isHalloween(); } // Purpur - Halloween options and optimizations
private static boolean isHalloween() {
LocalDate localDate = LocalDate.now();
int i = localDate.get(ChronoField.DAY_OF_MONTH);

View File

@@ -0,0 +1,34 @@
--- a/net/minecraft/world/entity/animal/Animal.java
+++ b/net/minecraft/world/entity/animal/Animal.java
@@ -142,7 +_,7 @@
ItemStack itemInHand = player.getItemInHand(hand);
if (this.isFood(itemInHand)) {
int age = this.getAge();
- if (!this.level().isClientSide && age == 0 && this.canFallInLove()) {
+ if (!this.level().isClientSide && age == 0 && this.canFallInLove() && (this.level().purpurConfig.animalBreedingCooldownSeconds <= 0 || !this.level().hasBreedingCooldown(player.getUUID(), this.getClass()))) { // Purpur - Add adjustable breeding cooldown to config
final ItemStack breedCopy = itemInHand.copy(); // Paper - Fix EntityBreedEvent copying
this.usePlayerItem(player, hand, itemInHand);
this.setInLove(player, breedCopy); // Paper - Fix EntityBreedEvent copying
@@ -239,10 +_,20 @@
public void spawnChildFromBreeding(ServerLevel level, Animal mate) {
AgeableMob breedOffspring = this.getBreedOffspring(level, mate);
if (breedOffspring != null) {
- breedOffspring.setBaby(true);
- breedOffspring.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
+ //breedOffspring.setBaby(true); // Purpur - Add adjustable breeding cooldown to config - moved down
+ //breedOffspring.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); // Purpur - Add adjustable breeding cooldown to config - moved down
// CraftBukkit start - call EntityBreedEvent
ServerPlayer breeder = Optional.ofNullable(this.getLoveCause()).or(() -> Optional.ofNullable(mate.getLoveCause())).orElse(null);
+ // Purpur start - Add adjustable breeding cooldown to config
+ if (breeder != null && level.purpurConfig.animalBreedingCooldownSeconds > 0) {
+ if (level.hasBreedingCooldown(breeder.getUUID(), this.getClass())) {
+ return;
+ }
+ level.addBreedingCooldown(breeder.getUUID(), this.getClass());
+ }
+ breedOffspring.setBaby(true);
+ breedOffspring.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
+ // Purpur end - Add adjustable breeding cooldown to config
int experience = this.getRandom().nextInt(7) + 1;
org.bukkit.event.entity.EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(breedOffspring, this, mate, breeder, this.breedItem, experience);
if (entityBreedEvent.isCancelled()) {

View File

@@ -0,0 +1,61 @@
--- a/net/minecraft/world/entity/animal/Bee.java
+++ b/net/minecraft/world/entity/animal/Bee.java
@@ -163,7 +_,7 @@
// Paper end - Fix MC-167279
this.lookControl = new Bee.BeeLookControl(this);
this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F);
- this.setPathfindingMalus(PathType.WATER, -1.0F);
+ if (this.level().purpurConfig.beeCanInstantlyStartDrowning) this.setPathfindingMalus(PathType.WATER, -1.0F); // Purpur - bee can instantly start drowning in water option
this.setPathfindingMalus(PathType.WATER_BORDER, 16.0F);
this.setPathfindingMalus(PathType.COCOA, -1.0F);
this.setPathfindingMalus(PathType.FENCE, -1.0F);
@@ -365,7 +_,7 @@
}
public static boolean isNightOrRaining(Level level) {
- return level.dimensionType().hasSkyLight() && (level.isNight() || level.isRaining());
+ return level.dimensionType().hasSkyLight() && (level.isNight() && !level.purpurConfig.beeCanWorkAtNight || level.isRaining() && !level.purpurConfig.beeCanWorkInRain); // Purpur - Bee can work when raining or at night
}
public void setStayOutOfHiveCountdown(int stayOutOfHiveCountdown) {
@@ -388,7 +_,7 @@
@Override
protected void customServerAiStep(ServerLevel level) {
boolean hasStung = this.hasStung();
- if (this.isInWaterOrBubble()) {
+ if (this.level().purpurConfig.beeCanInstantlyStartDrowning && this.isInWaterOrBubble()) { // Purpur - bee can instantly start drowning in water option
this.underWaterTicks++;
} else {
this.underWaterTicks = 0;
@@ -398,6 +_,7 @@
this.hurtServer(level, this.damageSources().drown(), 1.0F);
}
+ if (hasStung && !this.level().purpurConfig.beeDiesAfterSting) setHasStung(false); else // Purpur - Stop bees from dying after stinging
if (hasStung) {
this.timeSinceSting++;
if (this.timeSinceSting % 5 == 0 && this.random.nextInt(Mth.clamp(1200 - this.timeSinceSting, 1, 1200)) == 0) {
@@ -1136,6 +_,7 @@
Bee.this.savedFlowerPos = optional.get();
Bee.this.navigation
.moveTo(Bee.this.savedFlowerPos.getX() + 0.5, Bee.this.savedFlowerPos.getY() + 0.5, Bee.this.savedFlowerPos.getZ() + 0.5, 1.2F);
+ new org.purpurmc.purpur.event.entity.BeeFoundFlowerEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos)).callEvent(); // Purpur - Bee API
return true;
} else {
Bee.this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(Bee.this.random, 20, 60);
@@ -1182,6 +_,7 @@
this.pollinating = false;
Bee.this.navigation.stop();
Bee.this.remainingCooldownBeforeLocatingNewFlower = 200;
+ new org.purpurmc.purpur.event.entity.BeeStopPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), Bee.this.savedFlowerPos == null ? null : io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos), Bee.this.hasNectar()).callEvent(); // Purpur - Bee API
}
@Override
@@ -1228,6 +_,7 @@
this.setWantedPos();
}
+ if (this.successfulPollinatingTicks == 0) new org.purpurmc.purpur.event.entity.BeeStartedPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level(), Bee.this.savedFlowerPos)).callEvent(); // Purpur - Bee API
this.successfulPollinatingTicks++;
if (Bee.this.random.nextFloat() < 0.05F && this.successfulPollinatingTicks > this.lastSoundPlayedTick + 60) {
this.lastSoundPlayedTick = this.successfulPollinatingTicks;

View File

@@ -0,0 +1,26 @@
--- a/net/minecraft/world/entity/animal/Cat.java
+++ b/net/minecraft/world/entity/animal/Cat.java
@@ -332,6 +_,14 @@
return this.isTame() && otherAnimal instanceof Cat cat && cat.isTame() && super.canMate(otherAnimal);
}
+ // Purpur start - Configurable default collar color
+ @Override
+ public void tame(Player player) {
+ setCollarColor(level().purpurConfig.catDefaultCollarColor);
+ super.tame(player);
+ }
+ // Purpur end - Configurable default collar color
+
@Nullable
@Override
public SpawnGroupData finalizeSpawn(
@@ -438,7 +_,7 @@
}
private void tryToTame(Player player) {
- if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit
+ if (((this.level().purpurConfig.alwaysTameInCreative && player.hasInfiniteMaterials()) || this.random.nextInt(3) == 0) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit // Purpur - Config to always tame in Creative
this.tame(player);
this.setOrderedToSit(true);
this.level().broadcastEntityEvent(this, (byte)7);

View File

@@ -0,0 +1,90 @@
--- a/net/minecraft/world/entity/animal/Cow.java
+++ b/net/minecraft/world/entity/animal/Cow.java
@@ -43,7 +_,7 @@
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new PanicGoal(this, 2.0));
this.goalSelector.addGoal(2, new BreedGoal(this, 1.0));
- this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, itemStack -> itemStack.is(ItemTags.COW_FOOD), false));
+ this.goalSelector.addGoal(3, new TemptGoal(this, 1.25, itemStack -> level().purpurConfig.cowFeedMushrooms > 0 && (itemStack.is(net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem()) || itemStack.is(net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem())) || itemStack.is(ItemTags.COW_FOOD), false)); // Purpur - Cows eat mushrooms
this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
@@ -99,6 +_,10 @@
ItemStack itemStack = ItemUtils.createFilledResult(itemInHand, player, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
player.setItemInHand(hand, itemStack);
return InteractionResult.SUCCESS;
+ // Purpur start - Cows eat mushrooms - feed mushroom to change to mooshroom
+ } else if (level().purpurConfig.cowFeedMushrooms > 0 && this.getType() != EntityType.MOOSHROOM && isMushroom(itemInHand)) {
+ return this.feedMushroom(player, itemInHand);
+ // Purpur end - Cows eat mushrooms
} else {
return super.mobInteract(player, hand);
}
@@ -114,4 +_,67 @@
public EntityDimensions getDefaultDimensions(Pose pose) {
return this.isBaby() ? BABY_DIMENSIONS : super.getDefaultDimensions(pose);
}
+
+ // Purpur start - Cows eat mushrooms - feed mushroom to change to mooshroom
+ private int redMushroomsFed = 0;
+ private int brownMushroomsFed = 0;
+
+ private boolean isMushroom(ItemStack stack) {
+ return stack.getItem() == net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem() || stack.getItem() == net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem();
+ }
+
+ private int incrementFeedCount(ItemStack stack) {
+ if (stack.getItem() == net.minecraft.world.level.block.Blocks.RED_MUSHROOM.asItem()) {
+ return ++redMushroomsFed;
+ } else {
+ return ++brownMushroomsFed;
+ }
+ }
+
+ private InteractionResult feedMushroom(Player player, ItemStack stack) {
+ level().broadcastEntityEvent(this, (byte) 18); // hearts
+ playSound(SoundEvents.COW_MILK, 1.0F, 1.0F);
+ if (incrementFeedCount(stack) < level().purpurConfig.cowFeedMushrooms) {
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(1);
+ }
+ return InteractionResult.CONSUME; // require 5 mushrooms to transform (prevents mushroom duping)
+ }
+ MushroomCow mooshroom = EntityType.MOOSHROOM.create(level(), EntitySpawnReason.CONVERSION);
+ if (mooshroom == null) {
+ return InteractionResult.PASS;
+ }
+ if (stack.getItem() == net.minecraft.world.level.block.Blocks.BROWN_MUSHROOM.asItem()) {
+ mooshroom.setVariant(MushroomCow.Variant.BROWN);
+ } else {
+ mooshroom.setVariant(MushroomCow.Variant.RED);
+ }
+ mooshroom.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
+ mooshroom.setHealth(this.getHealth());
+ mooshroom.setAge(getAge());
+ mooshroom.copyPosition(this);
+ mooshroom.setYBodyRot(this.yBodyRot);
+ mooshroom.setYHeadRot(this.getYHeadRot());
+ mooshroom.yRotO = this.yRotO;
+ mooshroom.xRotO = this.xRotO;
+ if (this.hasCustomName()) {
+ mooshroom.setCustomName(this.getCustomName());
+ }
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTransformEvent(this, mooshroom, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) {
+ return InteractionResult.PASS;
+ }
+ this.level().addFreshEntity(mooshroom);
+ this.remove(RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(1);
+ }
+ for (int i = 0; i < 15; ++i) {
+ ((ServerLevel) level()).sendParticlesSource(((ServerLevel) level()).players(), null, net.minecraft.core.particles.ParticleTypes.HAPPY_VILLAGER,
+ false, true,
+ getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1,
+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0);
+ }
+ return InteractionResult.SUCCESS;
+ }
+ // Purpur end - Cows eat mushrooms
}

View File

@@ -0,0 +1,47 @@
--- a/net/minecraft/world/entity/animal/Dolphin.java
+++ b/net/minecraft/world/entity/animal/Dolphin.java
@@ -71,6 +_,7 @@
private static final int TOTAL_MOISTNESS_LEVEL = 2400;
public static final Predicate<ItemEntity> ALLOWED_ITEMS = itemEntity -> !itemEntity.hasPickUpDelay() && itemEntity.isAlive() && itemEntity.isInWater();
public static final float BABY_SCALE = 0.65F;
+ private boolean isNaturallyAggressiveToPlayers; // Purpur - Dolphins naturally aggressive to players chance
public Dolphin(EntityType<? extends Dolphin> entityType, Level level) {
super(entityType, level);
@@ -87,6 +_,7 @@
this.setAirSupply(this.getMaxAirSupply());
this.setXRot(0.0F);
SpawnGroupData spawnGroupData1 = Objects.requireNonNullElseGet(spawnGroupData, () -> new AgeableMob.AgeableMobGroupData(0.1F));
+ this.isNaturallyAggressiveToPlayers = level.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= level.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance; // Purpur - Dolphins naturally aggressive to players chance
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData1);
}
@@ -169,17 +_,19 @@
protected void registerGoals() {
this.goalSelector.addGoal(0, new BreathAirGoal(this));
this.goalSelector.addGoal(0, new TryFindWaterGoal(this));
+ this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur - Dolphins naturally aggressive to players chance
this.goalSelector.addGoal(1, new Dolphin.DolphinSwimToTreasureGoal(this));
this.goalSelector.addGoal(2, new Dolphin.DolphinSwimWithPlayerGoal(this, 4.0));
this.goalSelector.addGoal(4, new RandomSwimmingGoal(this, 1.0, 10));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(5, new DolphinJumpGoal(this, 10));
- this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2F, true));
+ //this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2F, true)); // Purpur - moved up - Dolphins naturally aggressive to players chance
this.goalSelector.addGoal(8, new Dolphin.PlayWithItemsGoal());
this.goalSelector.addGoal(8, new FollowBoatGoal(this));
this.goalSelector.addGoal(9, new AvoidEntityGoal<>(this, Guardian.class, 8.0F, 1.0, 1.0));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Guardian.class).setAlertOthers());
+ this.targetSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (ignored, ignored2) -> isNaturallyAggressiveToPlayers)); // Purpur - Dolphins naturally aggressive to players chance
}
public static AttributeSupplier.Builder createAttributes() {
@@ -412,6 +_,7 @@
@Override
public boolean canUse() {
+ if (this.dolphin.level().purpurConfig.dolphinDisableTreasureSearching) return false; // Purpur - Add option to disable dolphin treasure searching
return this.dolphin.gotFish() && this.dolphin.getAirSupply() >= 100;
}

View File

@@ -0,0 +1,52 @@
--- a/net/minecraft/world/entity/animal/Fox.java
+++ b/net/minecraft/world/entity/animal/Fox.java
@@ -335,6 +_,11 @@
}
private void setTargetGoals() {
+ // Purpur start - Tulips change fox type - do not add duplicate goals
+ this.targetSelector.removeGoal(this.landTargetGoal);
+ this.targetSelector.removeGoal(this.turtleEggTargetGoal);
+ this.targetSelector.removeGoal(this.fishTargetGoal);
+ // Purpur end - Tulips change fox type
if (this.getVariant() == Fox.Variant.RED) {
this.targetSelector.addGoal(4, this.landTargetGoal);
this.targetSelector.addGoal(4, this.turtleEggTargetGoal);
@@ -364,6 +_,7 @@
@Override
public void setVariant(Fox.Variant variant) {
this.entityData.set(DATA_TYPE_ID, variant.getId());
+ this.setTargetGoals(); // Purpur - Tulips change fox type - fix API bug not updating pathfinders on type change
}
List<UUID> getTrustedUUIDs() {
@@ -684,6 +_,29 @@
return slot == EquipmentSlot.MAINHAND;
}
// Paper end
+
+ // Purpur start - Tulips change fox type
+ @Override
+ public net.minecraft.world.InteractionResult mobInteract(Player player, net.minecraft.world.InteractionHand hand) {
+ if (level().purpurConfig.foxTypeChangesWithTulips) {
+ ItemStack itemstack = player.getItemInHand(hand);
+ if (getVariant() == Variant.RED && itemstack.getItem() == Items.WHITE_TULIP) {
+ setVariant(Variant.SNOW);
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
+ return net.minecraft.world.InteractionResult.SUCCESS;
+ } else if (getVariant() == Variant.SNOW && itemstack.getItem() == Items.ORANGE_TULIP) {
+ setVariant(Variant.RED);
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
+ return net.minecraft.world.InteractionResult.SUCCESS;
+ }
+ }
+ return super.mobInteract(player, hand);
+ }
+ // Purpur end - Tulips change fox type
@Override
// Paper start - Cancellable death event

View File

@@ -0,0 +1,53 @@
--- a/net/minecraft/world/entity/animal/IronGolem.java
+++ b/net/minecraft/world/entity/animal/IronGolem.java
@@ -56,13 +_,26 @@
private int remainingPersistentAngerTime;
@Nullable
private UUID persistentAngerTarget;
+ @Nullable private UUID summoner; // Purpur - Summoner API
public IronGolem(EntityType<? extends IronGolem> entityType, Level level) {
super(entityType, level);
}
+ // Purpur start - Summoner API
+ @Nullable
+ public UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(@Nullable UUID summoner) {
+ this.summoner = summoner;
+ }
+ // Purpur end - Summoner API
+
@Override
protected void registerGoals() {
+ if (this.level().purpurConfig.ironGolemPoppyCalm) this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.ReceiveFlower(this)); // Purpur - Iron golem calm anger options
this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0, true));
this.goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9, 32.0F));
this.goalSelector.addGoal(2, new MoveBackToVillageGoal(this, 0.6, false));
@@ -140,6 +_,7 @@
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.putBoolean("PlayerCreated", this.isPlayerCreated());
+ if (getSummoner() != null) compound.putUUID("Purpur.Summoner", getSummoner()); // Purpur - Summoner API
this.addPersistentAngerSaveData(compound);
}
@@ -147,6 +_,7 @@
public void readAdditionalSaveData(CompoundTag compound) {
super.readAdditionalSaveData(compound);
this.setPlayerCreated(compound.getBoolean("PlayerCreated"));
+ if (compound.contains("Purpur.Summoner")) setSummoner(compound.getUUID("Purpur.Summoner")); // Purpur - Summoner API
this.readPersistentAngerSaveData(this.level(), compound);
}
@@ -266,6 +_,7 @@
float f = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F;
this.playSound(SoundEvents.IRON_GOLEM_REPAIR, 1.0F, f);
itemInHand.consume(1, player);
+ if (this.level().purpurConfig.ironGolemHealCalm && isAngry() && getHealth() == getMaxHealth()) stopBeingAngry(); // Purpur - Iron golem calm anger options
return InteractionResult.SUCCESS;
}
}

View File

@@ -0,0 +1,16 @@
--- a/net/minecraft/world/entity/animal/MushroomCow.java
+++ b/net/minecraft/world/entity/animal/MushroomCow.java
@@ -192,6 +_,13 @@
level.playSound(null, this, SoundEvents.MOOSHROOM_SHEAR, soundSource, 1.0F, 1.0F);
this.convertTo(EntityType.COW, ConversionParams.single(this, false, false), mob -> {
level.sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(0.5), this.getZ(), 1, 0.0, 0.0, 0.0, 0.0);
+ // Purpur start - Fix cow rotation when shearing mooshroom
+ mob.copyPosition(this);
+ mob.yBodyRot = this.yBodyRot;
+ mob.setYHeadRot(this.getYHeadRot());
+ mob.yRotO = this.yRotO;
+ mob.xRotO = this.xRotO;
+ // Purpur end - Fix cow rotation when shearing mooshroom
// Paper start - custom shear drops; moved drop generation to separate method
drops.forEach(drop -> {
this.spawnAtLocation(level, new ItemEntity(this.level(), this.getX(), this.getY(1.0), this.getZ(), drop));

View File

@@ -0,0 +1,11 @@
--- a/net/minecraft/world/entity/animal/Ocelot.java
+++ b/net/minecraft/world/entity/animal/Ocelot.java
@@ -232,7 +_,7 @@
public boolean checkSpawnObstruction(LevelReader level) {
if (level.isUnobstructed(this) && !level.containsAnyLiquid(this.getBoundingBox())) {
BlockPos blockPos = this.blockPosition();
- if (blockPos.getY() < level.getSeaLevel()) {
+ if (!level().purpurConfig.ocelotSpawnUnderSeaLevel && blockPos.getY() < level.getSeaLevel()) { // Purpur - Option Ocelot Spawn Under Sea Level
return false;
}

View File

@@ -0,0 +1,52 @@
--- a/net/minecraft/world/entity/animal/Parrot.java
+++ b/net/minecraft/world/entity/animal/Parrot.java
@@ -152,6 +_,7 @@
protected void registerGoals() {
this.goalSelector.addGoal(0, new TamableAnimal.TamableAnimalPanicGoal(1.25));
this.goalSelector.addGoal(0, new FloatGoal(this));
+ if (this.level().purpurConfig.parrotBreedable) this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.BreedGoal(this, 1.0D)); // Purpur - Breedable parrots
this.goalSelector.addGoal(1, new LookAtPlayerGoal(this, Player.class, 8.0F));
this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
this.goalSelector.addGoal(2, new FollowOwnerGoal(this, 1.0, 5.0F, 1.0F));
@@ -257,7 +_,7 @@
}
if (!this.level().isClientSide) {
- if (this.random.nextInt(10) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit
+ if (((this.level().purpurConfig.alwaysTameInCreative && player.hasInfiniteMaterials()) || this.random.nextInt(10) == 0) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit // Purpur - Config to always tame in Creative
this.tame(player);
this.level().broadcastEntityEvent(this, (byte)7);
} else {
@@ -265,6 +_,7 @@
}
}
+ if (this.level().purpurConfig.parrotBreedable) return super.mobInteract(player, hand); // Purpur - Breedable parrots
return InteractionResult.SUCCESS;
} else if (!itemInHand.is(ItemTags.PARROT_POISONOUS_FOOD)) {
if (!this.isFlying() && this.isTame() && this.isOwnedBy(player)) {
@@ -289,7 +_,7 @@
@Override
public boolean isFood(ItemStack stack) {
- return false;
+ return this.level().purpurConfig.parrotBreedable && stack.is(ItemTags.PARROT_FOOD); // Purpur - Breedable parrots
}
public static boolean checkParrotSpawnRules(
@@ -304,13 +_,13 @@
@Override
public boolean canMate(Animal otherAnimal) {
- return false;
+ return super.canMate(otherAnimal); // Purpur - Breedable parrots
}
@Nullable
@Override
public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
- return null;
+ return level.purpurConfig.parrotBreedable ? EntityType.PARROT.create(level, EntitySpawnReason.BREEDING) : null; // Purpur - Breedable parrots
}
@Nullable

View File

@@ -0,0 +1,22 @@
--- a/net/minecraft/world/entity/animal/Pig.java
+++ b/net/minecraft/world/entity/animal/Pig.java
@@ -132,6 +_,19 @@
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
boolean isFood = this.isFood(player.getItemInHand(hand));
+ // Purpur start - Pigs give saddle back
+ if (level().purpurConfig.pigGiveSaddleBack && player.isSecondaryUseActive() && !isFood && isSaddled() && !isVehicle()) {
+ this.steering.setSaddle(false);
+ if (!player.getAbilities().instabuild) {
+ ItemStack saddle = new ItemStack(Items.SADDLE);
+ if (!player.getInventory().add(saddle)) {
+ player.drop(saddle, false);
+ }
+ }
+ return InteractionResult.SUCCESS;
+ }
+ // Purpur end - Pigs give saddle back
+
if (!isFood && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) {
if (!this.level().isClientSide) {
player.startRiding(this);

View File

@@ -0,0 +1,54 @@
--- a/net/minecraft/world/entity/animal/PolarBear.java
+++ b/net/minecraft/world/entity/animal/PolarBear.java
@@ -64,6 +_,29 @@
super(entityType, level);
}
+ // Purpur start - Breedable Polar Bears
+ public boolean canMate(Animal other) {
+ if (other == this) {
+ return false;
+ } else if (this.isStanding()) {
+ return false;
+ } else if (this.getTarget() != null) {
+ return false;
+ } else if (!(other instanceof PolarBear)) {
+ return false;
+ } else {
+ PolarBear bear = (PolarBear) other;
+ if (bear.isStanding()) {
+ return false;
+ }
+ if (bear.getTarget() != null) {
+ return false;
+ }
+ return this.isInLove() && bear.isInLove();
+ }
+ }
+ // Purpur end - Breedable Polar Bears
+
@Nullable
@Override
public AgeableMob getBreedOffspring(ServerLevel level, AgeableMob otherParent) {
@@ -72,7 +_,7 @@
@Override
public boolean isFood(ItemStack stack) {
- return false;
+ return level().purpurConfig.polarBearBreedableItem != null && stack.getItem() == level().purpurConfig.polarBearBreedableItem; // Purpur - Breedable Polar Bears
}
@Override
@@ -81,6 +_,12 @@
this.goalSelector.addGoal(0, new FloatGoal(this));
this.goalSelector.addGoal(1, new PolarBear.PolarBearMeleeAttackGoal());
this.goalSelector.addGoal(1, new PanicGoal(this, 2.0, mob -> mob.isBaby() ? DamageTypeTags.PANIC_CAUSES : DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES));
+ // Purpur start - Breedable Polar Bears
+ if (level().purpurConfig.polarBearBreedableItem != null) {
+ this.goalSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.BreedGoal(this, 1.0D));
+ this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, net.minecraft.world.item.crafting.Ingredient.of(level().purpurConfig.polarBearBreedableItem), false));
+ }
+ // Purpur end - Breedable Polar Bears
this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25));
this.goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));

View File

@@ -0,0 +1,26 @@
--- a/net/minecraft/world/entity/animal/Rabbit.java
+++ b/net/minecraft/world/entity/animal/Rabbit.java
@@ -376,10 +_,23 @@
}
this.setVariant(randomRabbitVariant);
+
+ // Purpur start - Special mobs naturally spawn
+ if (randomRabbitVariant != Variant.EVIL && level.getLevel().purpurConfig.rabbitNaturalToast > 0D && random.nextDouble() <= level.getLevel().purpurConfig.rabbitNaturalToast) {
+ setCustomName(Component.translatable("Toast"));
+ }
+ // Purpur end - Special mobs naturally spawn
+
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
}
private static Rabbit.Variant getRandomRabbitVariant(LevelAccessor level, BlockPos pos) {
+ // Purpur start - Special mobs naturally spawn
+ Level world = level.getMinecraftWorld();
+ if (world.purpurConfig.rabbitNaturalKiller > 0D && world.getRandom().nextDouble() <= world.purpurConfig.rabbitNaturalKiller) {
+ return Rabbit.Variant.EVIL;
+ }
+ // Purpur end - Special mobs naturally spawn
Holder<Biome> biome = level.getBiome(pos);
int randomInt = level.getRandom().nextInt(100);
if (biome.is(BiomeTags.SPAWNS_WHITE_RABBITS)) {

View File

@@ -0,0 +1,63 @@
--- a/net/minecraft/world/entity/animal/SnowGolem.java
+++ b/net/minecraft/world/entity/animal/SnowGolem.java
@@ -44,15 +_,27 @@
public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackMob {
private static final EntityDataAccessor<Byte> DATA_PUMPKIN_ID = SynchedEntityData.defineId(SnowGolem.class, EntityDataSerializers.BYTE);
private static final byte PUMPKIN_FLAG = 16;
+ @Nullable private java.util.UUID summoner; // Purpur - Summoner API
public SnowGolem(EntityType<? extends SnowGolem> entityType, Level level) {
super(entityType, level);
}
+ // Purpur start - Summoner API
+ @Nullable
+ public java.util.UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(@Nullable java.util.UUID summoner) {
+ this.summoner = summoner;
+ }
+ // Purpur end - Summoner API
+
@Override
protected void registerGoals() {
- this.goalSelector.addGoal(1, new RangedAttackGoal(this, 1.25, 20, 10.0F));
- this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0, 1.0000001E-5F));
+ this.goalSelector.addGoal(1, new RangedAttackGoal(this, level().purpurConfig.snowGolemAttackDistance, level().purpurConfig.snowGolemSnowBallMin, level().purpurConfig.snowGolemSnowBallMax, level().purpurConfig.snowGolemSnowBallModifier)); // Purpur - Snow Golem rate of fire config
+ this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D, 1.0000001E-5F));
this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F));
this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Mob.class, 10, true, false, (entity, level) -> entity instanceof Enemy));
@@ -72,6 +_,7 @@
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.putBoolean("Pumpkin", this.hasPumpkin());
+ if (getSummoner() != null) compound.putUUID("Purpur.Summoner", getSummoner()); // Purpur - Summoner API
}
@Override
@@ -80,6 +_,7 @@
if (compound.contains("Pumpkin")) {
this.setPumpkin(compound.getBoolean("Pumpkin"));
}
+ if (compound.contains("Purpur.Summoner")) setSummoner(compound.getUUID("Purpur.Summoner")); // Purpur - Summoner API
}
@Override
@@ -153,6 +_,14 @@
}
return InteractionResult.SUCCESS;
+ // Purpur start - Snowman drop and put back pumpkin
+ } else if (level().purpurConfig.snowGolemPutPumpkinBack && !hasPumpkin() && itemInHand.getItem() == Blocks.CARVED_PUMPKIN.asItem()) {
+ setPumpkin(true);
+ if (!player.getAbilities().instabuild) {
+ itemInHand.shrink(1);
+ }
+ return InteractionResult.SUCCESS;
+ // Purpur end - Snowman drop and put back pumpkin
} else {
return InteractionResult.PASS;
}

View File

@@ -0,0 +1,50 @@
--- a/net/minecraft/world/entity/animal/Squid.java
+++ b/net/minecraft/world/entity/animal/Squid.java
@@ -46,10 +_,29 @@
public Squid(EntityType<? extends Squid> entityType, Level level) {
super(entityType, level);
- //this.random.setSeed(this.getId()); // Paper - Share random for entities to make them more random
+ if (!level.purpurConfig.entitySharedRandom) this.random.setSeed(this.getId()); // Paper - Share random for entities to make them more random // Purpur - Add toggle for RNG manipulation
this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F;
}
+ // Purpur start - Stop squids floating on top of water
+ @Override
+ public net.minecraft.world.phys.AABB getAxisForFluidCheck() {
+ // Stops squids from floating just over the water
+ return super.getAxisForFluidCheck().offsetY(level().purpurConfig.squidOffsetWaterCheck);
+ }
+ // Purpur end - Stop squids floating on top of water
+
+ // Purpur start - Flying squids! Oh my!
+ public boolean canFly() {
+ return this.level().purpurConfig.squidsCanFly;
+ }
+
+ @Override
+ public boolean isInWater() {
+ return this.wasTouchingWater || canFly();
+ }
+ // Purpur end - Flying squids! Oh my!
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(0, new Squid.SquidRandomMovementGoal(this));
@@ -127,6 +_,7 @@
}
if (this.isInWaterOrBubble()) {
+ if (canFly()) setNoGravity(!wasTouchingWater); // Purpur - Flying squids! Oh my!
if (this.tentacleMovement < (float) Math.PI) {
float f = this.tentacleMovement / (float) Math.PI;
this.tentacleAngle = Mth.sin(f * f * (float) Math.PI) * (float) Math.PI * 0.25F;
@@ -310,7 +_,7 @@
int noActionTime = this.squid.getNoActionTime();
if (noActionTime > 100) {
this.squid.movementVector = Vec3.ZERO;
- } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.wasTouchingWater || !this.squid.hasMovementVector()) {
+ } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.isInWater() || !this.squid.hasMovementVector()) { // Purpur - Flying squids! Oh my!
float f = this.squid.getRandom().nextFloat() * (float) (Math.PI * 2);
this.squid.movementVector = new Vec3(Mth.cos(f) * 0.2F, -0.1F + this.squid.getRandom().nextFloat() * 0.2F, Mth.sin(f) * 0.2F);
}

View File

@@ -0,0 +1,12 @@
--- a/net/minecraft/world/entity/animal/WaterAnimal.java
+++ b/net/minecraft/world/entity/animal/WaterAnimal.java
@@ -74,8 +_,7 @@
seaLevel = level.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(seaLevel);
i = level.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(i);
// Paper end - Make water animal spawn height configurable
- return pos.getY() >= i
- && pos.getY() <= seaLevel
+ return ((spawnReason == EntitySpawnReason.SPAWNER && level.getMinecraftWorld().purpurConfig.spawnerFixMC238526) || (pos.getY() >= i && pos.getY() <= seaLevel)) // Purpur - MC-238526 - Fix spawner not spawning water animals correctly
&& level.getFluidState(pos.below()).is(FluidTags.WATER)
&& level.getBlockState(pos.above()).is(Blocks.WATER);
}

View File

@@ -0,0 +1,168 @@
--- a/net/minecraft/world/entity/animal/Wolf.java
+++ b/net/minecraft/world/entity/animal/Wolf.java
@@ -94,6 +_,37 @@
EntityType<?> type = entity.getType();
return type == EntityType.SHEEP || type == EntityType.RABBIT || type == EntityType.FOX;
};
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ private boolean isRabid = false;
+ private static final TargetingConditions.Selector RABID_PREDICATE = (entity, ignored) -> entity instanceof net.minecraft.server.level.ServerPlayer || entity instanceof net.minecraft.world.entity.Mob;
+ private final net.minecraft.world.entity.ai.goal.Goal PATHFINDER_VANILLA = new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR);
+ private final net.minecraft.world.entity.ai.goal.Goal PATHFINDER_RABID = new NonTameRandomTargetGoal<>(this, LivingEntity.class, false, RABID_PREDICATE);
+ private static final class AvoidRabidWolfGoal extends AvoidEntityGoal<Wolf> {
+ private final Wolf wolf;
+
+ public AvoidRabidWolfGoal(Wolf wolf, float distance, double minSpeed, double maxSpeed) {
+ super(wolf, Wolf.class, distance, minSpeed, maxSpeed);
+ this.wolf = wolf;
+ }
+
+ @Override
+ public boolean canUse() {
+ return super.canUse() && !this.wolf.isRabid() && this.toAvoid != null && this.toAvoid.isRabid(); // wolves which are not rabid run away from rabid wolves
+ }
+
+ @Override
+ public void start() {
+ this.wolf.setTarget(null);
+ super.start();
+ }
+
+ @Override
+ public void tick() {
+ this.wolf.setTarget(null);
+ super.tick();
+ }
+ }
+ // Purpur end - Configurable chance for wolves to spawn rabid
private static final float START_HEALTH = 8.0F;
private static final float TAME_HEALTH = 40.0F;
private static final float ARMOR_REPAIR_UNIT = 0.125F;
@@ -115,12 +_,47 @@
this.setPathfindingMalus(PathType.DANGER_POWDER_SNOW, -1.0F);
}
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ public boolean isRabid() {
+ return this.isRabid;
+ }
+
+ public void setRabid(boolean isRabid) {
+ this.isRabid = isRabid;
+ updatePathfinders(true);
+ }
+
+ public void updatePathfinders(boolean modifyEffects) {
+ this.targetSelector.removeGoal(PATHFINDER_VANILLA);
+ this.targetSelector.removeGoal(PATHFINDER_RABID);
+ if (this.isRabid) {
+ setOwnerUUID(null);
+ setTame(false, true);
+ this.targetSelector.addGoal(5, PATHFINDER_RABID);
+ if (modifyEffects) this.addEffect(new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.CONFUSION, 1200));
+ } else {
+ this.targetSelector.addGoal(5, PATHFINDER_VANILLA);
+ this.stopBeingAngry();
+ if (modifyEffects) this.removeEffect(net.minecraft.world.effect.MobEffects.CONFUSION);
+ }
+ }
+ // Purpur end - Configurable chance for wolves to spawn rabid
+
+ // Purpur start - Configurable default collar color
+ @Override
+ public void tame(Player player) {
+ setCollarColor(level().purpurConfig.wolfDefaultCollarColor);
+ super.tame(player);
+ }
+ // Purpur end - Configurable default collar color
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
this.goalSelector.addGoal(1, new TamableAnimal.TamableAnimalPanicGoal(1.5, DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES));
this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
this.goalSelector.addGoal(3, new Wolf.WolfAvoidEntityGoal<>(this, Llama.class, 24.0F, 1.5, 1.5));
+ this.goalSelector.addGoal(3, new AvoidRabidWolfGoal(this, 24.0F, 1.5D, 1.5D)); // Purpur - Configurable chance for wolves to spawn rabid
this.goalSelector.addGoal(4, new LeapAtTargetGoal(this, 0.4F));
this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0, true));
this.goalSelector.addGoal(6, new FollowOwnerGoal(this, 1.0, 10.0F, 2.0F));
@@ -133,7 +_,7 @@
this.targetSelector.addGoal(2, new OwnerHurtTargetGoal(this));
this.targetSelector.addGoal(3, new HurtByTargetGoal(this).setAlertOthers());
this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
- this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR));
+ // this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR)); // Purpur - Configurable chance for wolves to spawn rabid - moved to updatePathfinders()
this.targetSelector.addGoal(6, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR));
this.targetSelector.addGoal(7, new NearestAttackableTargetGoal<>(this, AbstractSkeleton.class, false));
this.targetSelector.addGoal(8, new ResetUniversalAngerTargetGoal<>(this, true));
@@ -182,6 +_,7 @@
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.putByte("CollarColor", (byte)this.getCollarColor().getId());
+ compound.putBoolean("Purpur.IsRabid", this.isRabid); // Purpur - Configurable chance for wolves to spawn rabid
this.getVariant().unwrapKey().ifPresent(resourceKey -> compound.putString("variant", resourceKey.location().toString()));
this.addPersistentAngerSaveData(compound);
}
@@ -196,6 +_,10 @@
if (compound.contains("CollarColor", 99)) {
this.setCollarColor(DyeColor.byId(compound.getInt("CollarColor")));
}
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ this.isRabid = compound.getBoolean("Purpur.IsRabid");
+ this.updatePathfinders(false);
+ // Purpur end - Configurable chance for wolves to spawn rabid
this.readPersistentAngerSaveData(this.level(), compound);
}
@@ -215,6 +_,10 @@
}
this.setVariant(holder);
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ this.isRabid = level.getLevel().purpurConfig.wolfNaturalRabid > 0.0D && random.nextDouble() <= level.getLevel().purpurConfig.wolfNaturalRabid;
+ this.updatePathfinders(false);
+ // Purpur end - Configurable chance for wolves to spawn rabid
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
}
@@ -263,6 +_,11 @@
public void tick() {
super.tick();
if (this.isAlive()) {
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ if (this.age % 300 == 0 && this.isRabid()) {
+ this.addEffect(new net.minecraft.world.effect.MobEffectInstance(net.minecraft.world.effect.MobEffects.CONFUSION, 400));
+ }
+ // Purpur end - Configurable chance for wolves to spawn rabid
this.interestedAngleO = this.interestedAngle;
if (this.isInterested()) {
this.interestedAngle = this.interestedAngle + (1.0F - this.interestedAngle) * 0.4F;
@@ -481,13 +_,27 @@
itemInHand.consume(1, player);
this.tryToTame(player);
return InteractionResult.SUCCESS_SERVER;
+ // Purpur start - Configurable chance for wolves to spawn rabid
+ } else if (this.level().purpurConfig.wolfMilkCuresRabies && itemInHand.getItem() == Items.MILK_BUCKET && this.isRabid()) {
+ if (!player.isCreative()) {
+ player.setItemInHand(hand, new ItemStack(Items.BUCKET));
+ }
+ this.setRabid(false);
+ for (int i = 0; i < 10; ++i) {
+ ((ServerLevel) level()).sendParticlesSource(((ServerLevel) level()).players(), null, ParticleTypes.HAPPY_VILLAGER,
+ false, true,
+ getX() + random.nextFloat(), getY() + (random.nextFloat() * 1.5), getZ() + random.nextFloat(), 1,
+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0);
+ }
+ return InteractionResult.SUCCESS_SERVER;
+ // Purpur end - Configurable chance for wolves to spawn rabid
}
return super.mobInteract(player, hand);
}
private void tryToTame(Player player) {
- if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit - added event call and isCancelled check.
+ if (((this.level().purpurConfig.alwaysTameInCreative && player.hasInfiniteMaterials()) || this.random.nextInt(3) == 0) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit - added event call and isCancelled check. // Purpur - Config to always tame in Creative
this.tame(player);
this.navigation.stop();
this.setTarget(null);

View File

@@ -0,0 +1,10 @@
--- a/net/minecraft/world/entity/animal/goat/Goat.java
+++ b/net/minecraft/world/entity/animal/goat/Goat.java
@@ -392,6 +_,7 @@
// Paper start - Goat ram API
public void ram(net.minecraft.world.entity.LivingEntity entity) {
+ if(!new org.purpurmc.purpur.event.entity.GoatRamEntityEvent((org.bukkit.entity.Goat) getBukkitEntity(), entity.getBukkitLivingEntity()).callEvent()) return; // Purpur - Added goat ram event
Brain<Goat> brain = this.getBrain();
brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position());
brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS);

View File

@@ -0,0 +1,42 @@
--- a/net/minecraft/world/entity/animal/horse/Llama.java
+++ b/net/minecraft/world/entity/animal/horse/Llama.java
@@ -72,6 +_,7 @@
private Llama caravanHead;
@Nullable
public Llama caravanTail; // Paper
+ public boolean shouldJoinCaravan = true; // Purpur - Llama API
public Llama(EntityType<? extends Llama> entityType, Level level) {
super(entityType, level);
@@ -106,6 +_,7 @@
super.addAdditionalSaveData(compound);
compound.putInt("Variant", this.getVariant().id);
compound.putInt("Strength", this.getStrength());
+ compound.putBoolean("Purpur.ShouldJoinCaravan", shouldJoinCaravan); // Purpur - Llama API
}
@Override
@@ -113,6 +_,7 @@
this.setStrength(compound.getInt("Strength"));
super.readAdditionalSaveData(compound);
this.setVariant(Llama.Variant.byId(compound.getInt("Variant")));
+ if (compound.contains("Purpur.ShouldJoinCaravan")) this.shouldJoinCaravan = compound.getBoolean("Purpur.ShouldJoinCaravan"); // Purpur - Llama API
}
@Override
@@ -386,6 +_,7 @@
public void leaveCaravan() {
if (this.caravanHead != null) {
+ new org.purpurmc.purpur.event.entity.LlamaLeaveCaravanEvent((org.bukkit.entity.Llama) getBukkitEntity()).callEvent(); // Purpur - Llama API
this.caravanHead.caravanTail = null;
}
@@ -393,6 +_,7 @@
}
public void joinCaravan(Llama caravanHead) {
+ if (!this.level().purpurConfig.llamaJoinCaravans || !shouldJoinCaravan || !new org.purpurmc.purpur.event.entity.LlamaJoinCaravanEvent((org.bukkit.entity.Llama) getBukkitEntity(), (org.bukkit.entity.Llama) caravanHead.getBukkitEntity()).callEvent()) return; // Purpur - Llama API // Purpur - Config to disable Llama caravans
this.caravanHead = caravanHead;
this.caravanHead.caravanTail = this;
}

View File

@@ -0,0 +1,56 @@
--- a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
+++ b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
@@ -38,6 +_,24 @@
this.setPos(x, y, z);
}
+ // Purpur start - End crystal explosion options
+ public boolean shouldExplode() {
+ return showsBottom() ? level().purpurConfig.basedEndCrystalExplode : level().purpurConfig.baselessEndCrystalExplode;
+ }
+
+ public float getExplosionPower() {
+ return (float) (showsBottom() ? level().purpurConfig.basedEndCrystalExplosionPower : level().purpurConfig.baselessEndCrystalExplosionPower);
+ }
+
+ public boolean hasExplosionFire() {
+ return showsBottom() ? level().purpurConfig.basedEndCrystalExplosionFire : level().purpurConfig.baselessEndCrystalExplosionFire;
+ }
+
+ public Level.ExplosionInteraction getExplosionEffect() {
+ return showsBottom() ? level().purpurConfig.basedEndCrystalExplosionEffect : level().purpurConfig.baselessEndCrystalExplosionEffect;
+ }
+ // Purpur end - End crystal explosion options
+
@Override
protected Entity.MovementEmission getMovementEmission() {
return Entity.MovementEmission.NONE;
@@ -74,6 +_,8 @@
}
}
// Paper end - Fix invulnerable end crystals
+ if (this.level().purpurConfig.endCrystalCramming > 0 && this.level().getEntitiesOfClass(EndCrystal.class, getBoundingBox()).size() > this.level().purpurConfig.endCrystalCramming) this.hurt(this.damageSources().cramming(), 6.0F); // Purpur - End Crystal Cramming
+
}
@Override
@@ -119,15 +_,17 @@
}
// CraftBukkit end
if (!damageSource.is(DamageTypeTags.IS_EXPLOSION)) {
+ if (shouldExplode()) {// Purpur - End crystal explosion options
DamageSource damageSource1 = damageSource.getEntity() != null ? this.damageSources().explosion(this, damageSource.getEntity()) : null;
// CraftBukkit start
- org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false);
+ org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, getExplosionPower(), hasExplosionFire()); // Purpur - End crystal explosion options
if (event.isCancelled()) {
return false;
}
this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // Paper - add Bukkit remove cause
- level.explode(this, damageSource1, null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK);
+ level.explode(this, damageSource1, null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), getExplosionEffect()); // Purpur - End crystal explosion options
+ } else this.unsetRemoved(); // Purpur - End crystal explosion options
} else {
this.remove(Entity.RemovalReason.KILLED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DEATH); // Paper - add Bukkit remove cause
// CraftBukkit end

View File

@@ -0,0 +1,19 @@
--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
@@ -974,6 +_,7 @@
@Override
protected boolean canRide(Entity entity) {
+ if (this.level().purpurConfig.enderDragonCanRideVehicles) return this.boardingCooldown <= 0; // Purpur - Configs for if Wither/Ender Dragon can ride vehicles
return false;
}
@@ -1009,7 +_,7 @@
boolean flag = worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
int i = 500;
- if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
+ if (this.dragonFight != null && (level().purpurConfig.enderDragonAlwaysDropsFullExp || !this.dragonFight.hasPreviouslyKilledDragon())) { // Purpur - Ender dragon always drop full exp
i = 12000;
}

View File

@@ -0,0 +1,74 @@
--- a/net/minecraft/world/entity/boss/wither/WitherBoss.java
+++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java
@@ -77,6 +_,7 @@
private static final TargetingConditions.Selector LIVING_ENTITY_SELECTOR = (entity, level) -> !entity.getType().is(EntityTypeTags.WITHER_FRIENDS)
&& entity.attackable();
private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0).selector(LIVING_ENTITY_SELECTOR);
+ @Nullable private java.util.UUID summoner; // Purpur - Summoner API
public WitherBoss(EntityType<? extends WitherBoss> entityType, Level level) {
super(entityType, level);
@@ -85,6 +_,17 @@
this.xpReward = 50;
}
+ // Purpur start - Summoner API
+ @Nullable
+ public java.util.UUID getSummoner() {
+ return summoner;
+ }
+
+ public void setSummoner(@Nullable java.util.UUID summoner) {
+ this.summoner = summoner;
+ }
+ // Purpur end - Summoner API
+
@Override
protected PathNavigation createNavigation(Level level) {
FlyingPathNavigation flyingPathNavigation = new FlyingPathNavigation(this, level);
@@ -117,6 +_,7 @@
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.putInt("Invul", this.getInvulnerableTicks());
+ if (getSummoner() != null) compound.putUUID("Purpur.Summoner", getSummoner()); // Purpur - Summoner API
}
@Override
@@ -126,6 +_,7 @@
if (this.hasCustomName()) {
this.bossEvent.setName(this.getDisplayName());
}
+ if (compound.contains("Purpur.Summoner")) setSummoner(compound.getUUID("Purpur.Summoner")); // Purpur - Summoner API
}
@Override
@@ -269,7 +_,7 @@
level.explode(this, this.getX(), this.getEyeY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB);
}
// CraftBukkit end
- if (!this.isSilent()) {
+ if (!this.isSilent() && level.purpurConfig.witherPlaySpawnSound) { // Purpur - Toggle for Wither's spawn sound
// CraftBukkit start - Use relative location for far away sounds
// level.globalLevelEvent(1023, this.blockPosition(), 0);
int viewDistance = level.getCraftServer().getViewDistance() * 16;
@@ -376,8 +_,10 @@
}
}
- if (this.tickCount % 20 == 0) {
- this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
+ // Purpur start - Customizable wither health and healing - customizable heal rate and amount
+ if (this.tickCount % level().purpurConfig.witherHealthRegenDelay == 0) {
+ this.heal(level().purpurConfig.witherHealthRegenAmount, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
+ // Purpur end - Customizable wither health and healing
}
this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
@@ -574,6 +_,7 @@
@Override
protected boolean canRide(Entity entity) {
+ if (this.level().purpurConfig.witherCanRideVehicles) return this.boardingCooldown <= 0; // Purpur - Configs for if Wither/Ender Dragon can ride vehicles
return false;
}

View File

@@ -0,0 +1,43 @@
--- a/net/minecraft/world/entity/decoration/ArmorStand.java
+++ b/net/minecraft/world/entity/decoration/ArmorStand.java
@@ -93,10 +_,13 @@
private boolean noTickPoseDirty = false;
private boolean noTickEquipmentDirty = false;
// Paper end - Allow ArmorStands not to tick
+ public boolean canMovementTick = true; // Purpur - Movement options for armor stands
public ArmorStand(EntityType<? extends ArmorStand> entityType, Level level) {
super(entityType, level);
if (level != null) this.canTick = level.paperConfig().entities.armorStands.tick; // Paper - Allow ArmorStands not to tick
+ if (level != null) this.canMovementTick = level.purpurConfig.armorstandMovement; // Purpur - Movement options for armor stands
+ this.setShowArms(level != null && level.purpurConfig.armorstandPlaceWithArms); // Purpur - Config to show Armor Stand arms on spawn
}
public ArmorStand(Level level, double x, double y, double z) {
@@ -620,6 +_,7 @@
@Override
public void tick() {
+ maxUpStep = level().purpurConfig.armorstandStepHeight; // Purpur - Add option to set armorstand step height
// Paper start - Allow ArmorStands not to tick
if (!this.canTick) {
if (this.noTickPoseDirty) {
@@ -949,4 +_,18 @@
}
}
// Paper end
+
+ // Purpur start - Movement options for armor stands
+ @Override
+ public void updateInWaterStateAndDoWaterCurrentPushing() {
+ if (this.level().purpurConfig.armorstandWaterMovement &&
+ (this.level().purpurConfig.armorstandWaterFence || !(level().getBlockState(blockPosition().below()).getBlock() instanceof net.minecraft.world.level.block.FenceBlock)))
+ super.updateInWaterStateAndDoWaterCurrentPushing();
+ }
+
+ @Override
+ public void aiStep() {
+ if (this.canMovementTick && this.canMove) super.aiStep();
+ }
+ // Purpur end - Movement options for armor stands
}

View File

@@ -0,0 +1,46 @@
--- a/net/minecraft/world/entity/item/ItemEntity.java
+++ b/net/minecraft/world/entity/item/ItemEntity.java
@@ -52,6 +_,12 @@
public boolean canMobPickup = true; // Paper - Item#canEntityPickup
private int despawnRate = -1; // Paper - Alternative item-despawn-rate
public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Friction API
+ // Purpur start - Item entity immunities
+ public boolean immuneToCactus = false;
+ public boolean immuneToExplosion = false;
+ public boolean immuneToFire = false;
+ public boolean immuneToLightning = false;
+ // Purpur end - Item entity immunities
public ItemEntity(EntityType<? extends ItemEntity> entityType, Level level) {
super(entityType, level);
@@ -337,7 +_,16 @@
@Override
public final boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
- if (this.isInvulnerableToBase(damageSource)) {
+ // Purpur start - Item entity immunities
+ if (
+ (immuneToCactus && damageSource.is(net.minecraft.world.damagesource.DamageTypes.CACTUS)) ||
+ (immuneToFire && (damageSource.is(net.minecraft.tags.DamageTypeTags.IS_FIRE) || damageSource.is(net.minecraft.world.damagesource.DamageTypes.ON_FIRE) || damageSource.is(net.minecraft.world.damagesource.DamageTypes.IN_FIRE))) ||
+ (immuneToLightning && damageSource.is(net.minecraft.world.damagesource.DamageTypes.LIGHTNING_BOLT)) ||
+ (immuneToExplosion && damageSource.is(net.minecraft.tags.DamageTypeTags.IS_EXPLOSION))
+ ) {
+ return false;
+ } else if (this.isInvulnerableToBase(damageSource)) {
+ // Purpur end - Item entity immunities
return false;
} else if (!level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && damageSource.getEntity() instanceof Mob) {
return false;
@@ -539,6 +_,12 @@
public void setItem(ItemStack stack) {
this.getEntityData().set(DATA_ITEM, stack);
this.despawnRate = this.level().paperConfig().entities.spawning.altItemDespawnRate.enabled ? this.level().paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), this.level().spigotConfig.itemDespawnRate) : this.level().spigotConfig.itemDespawnRate; // Paper - Alternative item-despawn-rate
+ // Purpur start - Item entity immunities
+ if (level().purpurConfig.itemImmuneToCactus.contains(stack.getItem())) immuneToCactus = true;
+ if (level().purpurConfig.itemImmuneToExplosion.contains(stack.getItem())) immuneToExplosion = true;
+ if (level().purpurConfig.itemImmuneToFire.contains(stack.getItem())) immuneToFire = true;
+ if (level().purpurConfig.itemImmuneToLightning.contains(stack.getItem())) immuneToLightning = true;
+ // level end - Item entity immunities
}
@Override

View File

@@ -0,0 +1,35 @@
--- a/net/minecraft/world/entity/item/PrimedTnt.java
+++ b/net/minecraft/world/entity/item/PrimedTnt.java
@@ -251,4 +_,32 @@
return !this.level().paperConfig().fixes.preventTntFromMovingInWater && super.isPushedByFluid();
}
// Paper end - Option to prevent TNT from moving in water
+
+ // Purpur start - Shears can defuse TNT
+ @Override
+ public net.minecraft.world.InteractionResult interact(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) {
+ Level world = this.level();
+
+ if (world instanceof ServerLevel serverWorld && level().purpurConfig.shearsCanDefuseTnt) {
+ final net.minecraft.world.item.ItemStack inHand = player.getItemInHand(hand);
+
+ if (!inHand.is(net.minecraft.world.item.Items.SHEARS) || !player.getBukkitEntity().hasPermission("purpur.tnt.defuse") ||
+ serverWorld.random.nextFloat() > serverWorld.purpurConfig.shearsCanDefuseTntChance) return net.minecraft.world.InteractionResult.PASS;
+
+ net.minecraft.world.entity.item.ItemEntity tntItem = new net.minecraft.world.entity.item.ItemEntity(serverWorld, getX(), getY(), getZ(),
+ new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.TNT));
+ tntItem.setPickUpDelay(10);
+
+ inHand.hurtAndBreak(1, player, LivingEntity.getSlotForHand(hand));
+ serverWorld.addFreshEntity(tntItem, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CUSTOM);
+
+ this.playSound(net.minecraft.sounds.SoundEvents.SHEEP_SHEAR);
+
+ this.kill(serverWorld);
+ return net.minecraft.world.InteractionResult.SUCCESS;
+ }
+
+ return super.interact(player, hand);
+ }
+ // Purpur end - Shears can defuse TNT
}

View File

@@ -0,0 +1,23 @@
--- a/net/minecraft/world/entity/monster/AbstractSkeleton.java
+++ b/net/minecraft/world/entity/monster/AbstractSkeleton.java
@@ -158,10 +_,7 @@
this.reassessWeaponGoal();
this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || random.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - Add world settings for mobs picking up loot
if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
- LocalDate localDate = LocalDate.now();
- int i = localDate.get(ChronoField.DAY_OF_MONTH);
- int i1 = localDate.get(ChronoField.MONTH_OF_YEAR);
- if (i1 == 10 && i == 31 && random.nextFloat() < 0.25F) {
+ if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(level.getMinecraftWorld()) && this.random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur - Halloween options and optimizations
this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(random.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN));
this.armorDropChances[EquipmentSlot.HEAD.getIndex()] = 0.0F;
}
@@ -217,7 +_,7 @@
if (event.getProjectile() == arrow.getBukkitEntity()) {
// CraftBukkit end
Projectile.spawnProjectileUsingShoot(
- arrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, 14 - serverLevel.getDifficulty().getId() * 4
+ arrow, serverLevel, projectile, d, d1 + squareRoot * 0.2F, d2, 1.6F, serverLevel.purpurConfig.skeletonBowAccuracyMap.getOrDefault(serverLevel.getDifficulty().getId(), (float) (14 - serverLevel.getDifficulty().getId() * 4)) // Purpur - skeleton bow accuracy option
);
} // CraftBukkit
}

View File

@@ -0,0 +1,64 @@
--- a/net/minecraft/world/entity/monster/Creeper.java
+++ b/net/minecraft/world/entity/monster/Creeper.java
@@ -50,6 +_,7 @@
public int explosionRadius = 3;
private int droppedSkulls;
public Entity entityIgniter; // CraftBukkit
+ private boolean exploding = false; // Purpur - Config to make Creepers explode on death
public Creeper(EntityType<? extends Creeper> entityType, Level level) {
super(entityType, level);
@@ -161,6 +_,26 @@
}
}
+ // Purpur start - Special mobs naturally spawn
+ public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.EntitySpawnReason spawnReason, @Nullable net.minecraft.world.entity.SpawnGroupData entityData) {
+ double chance = world.getLevel().purpurConfig.creeperChargedChance;
+ if (chance > 0D && random.nextDouble() <= chance) {
+ setPowered(true);
+ }
+ return super.finalizeSpawn(world, difficulty, spawnReason, entityData);
+ }
+ // Purpur end - Special mobs naturally spawn
+
+ // Purpur start - Config to make Creepers explode on death
+ @Override
+ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(ServerLevel world, DamageSource damageSource) {
+ if (!this.exploding && this.level().purpurConfig.creeperExplodeWhenKilled && damageSource.getEntity() instanceof net.minecraft.server.level.ServerPlayer) {
+ this.explodeCreeper();
+ }
+ return super.dropAllDeathLoot(world, damageSource);
+ }
+ // Purpur end - Config to make Creepers explode on death
+
@Override
protected SoundEvent getHurtSound(DamageSource damageSource) {
return SoundEvents.CREEPER_HURT;
@@ -243,14 +_,16 @@
}
public void explodeCreeper() {
+ this.exploding = true; // Purpur - Config to make Creepers explode on death
if (this.level() instanceof ServerLevel serverLevel) {
float f = this.isPowered() ? 2.0F : 1.0F;
+ float multiplier = serverLevel.purpurConfig.creeperHealthRadius ? this.getHealth() / this.getMaxHealth() : 1; // Purpur - Config for health to impact Creeper explosion radius
// CraftBukkit start
- org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, this.explosionRadius * f, false);
+ org.bukkit.event.entity.ExplosionPrimeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExplosionPrimeEvent(this, (this.explosionRadius * f) * multiplier, false); // Purpur - Config for health to impact Creeper explosion radius
if (!event.isCancelled()) {
// CraftBukkit end
this.dead = true;
- serverLevel.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this)
+ serverLevel.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), serverLevel.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) && level().purpurConfig.creeperAllowGriefing ? Level.ExplosionInteraction.MOB : Level.ExplosionInteraction.NONE); // CraftBukkit // Paper - fix DamageSource API (revert to vanilla, no, just no, don't change this) // Purpur - Add enderman and creeper griefing controls
this.spawnLingeringCloud();
this.triggerOnDeathMobEffects(serverLevel, Entity.RemovalReason.KILLED);
this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.EXPLODE); // CraftBukkit - add Bukkit remove cause
@@ -261,6 +_,7 @@
}
// CraftBukkit end
}
+ this.exploding = false; // Purpur - Config to make Creepers explode on death
}
private void spawnLingeringCloud() {

View File

@@ -0,0 +1,27 @@
--- a/net/minecraft/world/entity/monster/Drowned.java
+++ b/net/minecraft/world/entity/monster/Drowned.java
@@ -82,10 +_,23 @@
this.goalSelector.addGoal(2, new Drowned.DrownedAttackGoal(this, 1.0, false));
this.goalSelector.addGoal(5, new Drowned.DrownedGoToBeachGoal(this, 1.0));
this.goalSelector.addGoal(6, new Drowned.DrownedSwimUpGoal(this, 1.0, this.level().getSeaLevel()));
+ if (level().purpurConfig.drownedBreakDoors) this.goalSelector.addGoal(6, new net.minecraft.world.entity.ai.goal.MoveThroughVillageGoal(this, 1.0D, true, 4, this::canBreakDoors)); // Purpur - Option to make drowned break doors
this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Drowned.class).setAlertOthers(ZombifiedPiglin.class));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entity, level) -> this.okTarget(entity)));
- if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper - Check drowned for villager aggression config
+ // Purpur start - Add option to disable zombie aggressiveness towards villagers
+ if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false) { // Paper - Check drowned for villager aggression config
+ @Override
+ public boolean canUse() {
+ return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canUse();
+ }
+
+ @Override
+ public boolean canContinueToUse() {
+ return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canContinueToUse();
+ }
+ });
+ // Purpur end - Add option to disable zombie aggressiveness towards villagers
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false));
this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR));

View File

@@ -0,0 +1,61 @@
--- a/net/minecraft/world/entity/monster/EnderMan.java
+++ b/net/minecraft/world/entity/monster/EnderMan.java
@@ -102,7 +_,7 @@
this.goalSelector.addGoal(11, new EnderMan.EndermanTakeBlockGoal(this));
this.targetSelector.addGoal(1, new EnderMan.EndermanLookForPlayerGoal(this, this::isAngryAt));
this.targetSelector.addGoal(2, new HurtByTargetGoal(this));
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, true, false));
+ this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, 10, true, false, (entityliving, ignored) -> entityliving.level().purpurConfig.endermanAggroEndermites && entityliving instanceof Endermite endermite && (!entityliving.level().purpurConfig.endermanAggroEndermitesOnlyIfPlayerSpawned || endermite.isPlayerSpawned()))); // Purpur
this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal<>(this, false));
}
@@ -230,7 +_,7 @@
boolean isBeingStaredBy(Player player) {
// Paper start - EndermanAttackPlayerEvent
- final boolean shouldAttack = isBeingStaredBy0(player);
+ final boolean shouldAttack = !this.level().purpurConfig.endermanDisableStareAggro && isBeingStaredBy0(player); // Purpur - Config to ignore Dragon Head wearers and stare aggro
final com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent event = new com.destroystokyo.paper.event.entity.EndermanAttackPlayerEvent((org.bukkit.entity.Enderman) getBukkitEntity(), (org.bukkit.entity.Player) player.getBukkitEntity());
event.setCancelled(!shouldAttack);
return event.callEvent();
@@ -385,6 +_,7 @@
public boolean hurtServer(ServerLevel level, DamageSource damageSource, float amount) {
if (this.isInvulnerableTo(level, damageSource)) {
return false;
+ } else if (org.purpurmc.purpur.PurpurConfig.endermanShortHeight && damageSource.is(net.minecraft.world.damagesource.DamageTypes.IN_WALL)) { return false; // Purpur - no suffocation damage if short height - Short enderman height
} else {
boolean flag = damageSource.getDirectEntity() instanceof ThrownPotion;
if (!damageSource.is(DamageTypeTags.IS_PROJECTILE) && !flag) {
@@ -397,6 +_,7 @@
} else {
boolean flag1 = flag && this.hurtWithCleanWater(level, damageSource, (ThrownPotion)damageSource.getDirectEntity(), amount);
+ if (!flag1 && level.purpurConfig.endermanIgnoreProjectiles) return super.hurtServer(level, damageSource, amount); // Purpur - Config to disable Enderman teleport on projectile hit
if (this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.INDIRECT)) { // Paper - EndermanEscapeEvent
for (int i = 0; i < 64; i++) {
if (this.teleport()) {
@@ -440,7 +_,7 @@
@Override
public boolean requiresCustomPersistence() {
- return super.requiresCustomPersistence() || this.getCarriedBlock() != null;
+ return super.requiresCustomPersistence() || (!this.level().purpurConfig.endermanDespawnEvenWithBlock && this.getCarriedBlock() != null); // Purpur - Add config for allowing Endermen to despawn even while holding a block
}
static class EndermanFreezeWhenLookedAt extends Goal {
@@ -484,6 +_,7 @@
@Override
public boolean canUse() {
+ if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur - Add enderman and creeper griefing controls
return this.enderman.getCarriedBlock() != null
&& getServerLevel(this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)
&& this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0;
@@ -633,6 +_,7 @@
@Override
public boolean canUse() {
+ if (!enderman.level().purpurConfig.endermanAllowGriefing) return false; // Purpur - Add enderman and creeper griefing controls
return this.enderman.getCarriedBlock() == null
&& getServerLevel(this.enderman).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)
&& this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0;

View File

@@ -0,0 +1,41 @@
--- a/net/minecraft/world/entity/monster/Endermite.java
+++ b/net/minecraft/world/entity/monster/Endermite.java
@@ -28,12 +_,23 @@
public class Endermite extends Monster {
private static final int MAX_LIFE = 2400;
public int life;
+ private boolean isPlayerSpawned; // Purpur - Add back player spawned endermite API
public Endermite(EntityType<? extends Endermite> entityType, Level level) {
super(entityType, level);
this.xpReward = 3;
}
+ // Purpur start - Add back player spawned endermite API
+ public boolean isPlayerSpawned() {
+ return this.isPlayerSpawned;
+ }
+
+ public void setPlayerSpawned(boolean playerSpawned) {
+ this.isPlayerSpawned = playerSpawned;
+ }
+ // Purpur end - Add back player spawned endermite API
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new FloatGoal(this));
@@ -79,12 +_,14 @@
public void readAdditionalSaveData(CompoundTag compound) {
super.readAdditionalSaveData(compound);
this.life = compound.getInt("Lifetime");
+ this.isPlayerSpawned = compound.getBoolean("PlayerSpawned"); // Purpur - Add back player spawned endermite API
}
@Override
public void addAdditionalSaveData(CompoundTag compound) {
super.addAdditionalSaveData(compound);
compound.putInt("Lifetime", this.life);
+ compound.putBoolean("PlayerSpawned", this.isPlayerSpawned); // Purpur - Add back player spawned endermite API
}
@Override

View File

@@ -0,0 +1,17 @@
--- a/net/minecraft/world/entity/monster/Monster.java
+++ b/net/minecraft/world/entity/monster/Monster.java
@@ -88,6 +_,14 @@
}
public static boolean isDarkEnoughToSpawn(ServerLevelAccessor level, BlockPos pos, RandomSource random) {
+ // Purpur start - Config to disable hostile mob spawn on ice
+ if (!level.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce || !level.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce) {
+ net.minecraft.world.level.block.state.BlockState spawnBlock = level.getBlockState(pos.below());
+ if ((!level.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.PACKED_ICE)) || (!level.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.BLUE_ICE))) {
+ return false;
+ }
+ }
+ // Purpur end - Config to disable hostile mob spawn on ice
if (level.getBrightness(LightLayer.SKY, pos) > random.nextInt(32)) {
return false;
} else {

View File

@@ -0,0 +1,15 @@
--- a/net/minecraft/world/entity/monster/Phantom.java
+++ b/net/minecraft/world/entity/monster/Phantom.java
@@ -158,7 +_,11 @@
ServerLevelAccessor level, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData spawnGroupData
) {
this.anchorPoint = this.blockPosition().above(5);
- this.setPhantomSize(0);
+ // Purpur start - Configurable phantom size
+ int min = level.getLevel().purpurConfig.phantomMinSize;
+ int max = level.getLevel().purpurConfig.phantomMaxSize;
+ this.setPhantomSize(min == max ? min : level.getRandom().nextInt(max + 1 - min) + min);
+ // Purpur end - Configurable phantom size
return super.finalizeSpawn(level, difficulty, spawnReason, spawnGroupData);
}

View File

@@ -0,0 +1,19 @@
--- a/net/minecraft/world/entity/monster/Ravager.java
+++ b/net/minecraft/world/entity/monster/Ravager.java
@@ -70,6 +_,7 @@
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
+ if (level().purpurConfig.ravagerAvoidRabbits) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.AvoidEntityGoal<>(this, net.minecraft.world.entity.animal.Rabbit.class, 6.0F, 1.0D, 1.2D)); // Purpur - option to make ravagers afraid of rabbits
this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0, true));
this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.4));
this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
@@ -150,7 +_,7 @@
)) {
BlockState blockState = serverLevel.getBlockState(blockPos);
Block block = blockState.getBlock();
- if (block instanceof LeavesBlock) {
+ if (this.level().purpurConfig.ravagerGriefableBlocks.contains(block)) { // Purpur - Configurable ravager griefable blocks list
// CraftBukkit start
if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockPos, blockState.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
continue;

View File

@@ -0,0 +1,67 @@
--- a/net/minecraft/world/entity/monster/Shulker.java
+++ b/net/minecraft/world/entity/monster/Shulker.java
@@ -50,6 +_,7 @@
import net.minecraft.world.level.ServerLevelAccessor;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.entity.EntityTypeTest;
import net.minecraft.world.level.gameevent.GameEvent;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
@@ -88,6 +_,21 @@
this.lookControl = new Shulker.ShulkerLookControl(this);
}
+ // Purpur start - Shulker change color with dye
+ @Override
+ protected net.minecraft.world.InteractionResult mobInteract(Player player, net.minecraft.world.InteractionHand hand) {
+ net.minecraft.world.item.ItemStack itemstack = player.getItemInHand(hand);
+ if (player.level().purpurConfig.shulkerChangeColorWithDye && itemstack.getItem() instanceof net.minecraft.world.item.DyeItem dye && dye.getDyeColor() != this.getColor()) {
+ this.setVariant(Optional.of(dye.getDyeColor()));
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
+ return net.minecraft.world.InteractionResult.SUCCESS;
+ }
+ return super.mobInteract(player, hand);
+ }
+ // Purpur end - Shulker change color with dye
+
@Override
protected void registerGoals() {
this.goalSelector.addGoal(1, new LookAtPlayerGoal(this, Player.class, 8.0F, 0.02F, true));
@@ -459,11 +_,21 @@
private void hitByShulkerBullet() {
Vec3 vec3 = this.position();
AABB boundingBox = this.getBoundingBox();
- if (!this.isClosed() && this.teleportSomewhere()) {
- int size = this.level().getEntities(EntityType.SHULKER, boundingBox.inflate(8.0), Entity::isAlive).size();
- float f = (size - 1) / 5.0F;
- if (!(this.level().random.nextFloat() < f)) {
+ // Purpur start - Shulker spawn from bullet options
+ if ((!this.level().purpurConfig.shulkerSpawnFromBulletRequireOpenLid || !this.isClosed()) && this.teleportSomewhere()) {
+ float chance = this.level().purpurConfig.shulkerSpawnFromBulletBaseChance;
+ if (!this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation.isBlank()) {
+ int nearby = this.level().getEntities((EntityTypeTest) EntityType.SHULKER, boundingBox.inflate(this.level().purpurConfig.shulkerSpawnFromBulletNearbyRange), Entity::isAlive).size();
+ try {
+ chance -= ((Number) scriptEngine.eval("let nearby = " + nearby + "; " + this.level().purpurConfig.shulkerSpawnFromBulletNearbyEquation)).floatValue();
+ } catch (javax.script.ScriptException e) {
+ e.printStackTrace();
+ chance -= (nearby - 1) / 5.0F;
+ }
+ }
+ if (this.level().random.nextFloat() <= chance) {
Shulker shulker = EntityType.SHULKER.create(this.level(), EntitySpawnReason.BREEDING);
+ // Purpur end - Shulker spawn from bullet options
if (shulker != null) {
shulker.setVariant(this.getVariant());
shulker.moveTo(vec3);
@@ -573,7 +_,7 @@
@Override
public Optional<DyeColor> getVariant() {
- return Optional.ofNullable(this.getColor());
+ return Optional.ofNullable(this.level().purpurConfig.shulkerSpawnFromBulletRandomColor ? DyeColor.random(this.level().random) : this.getColor()); // Purpur - Shulker spawn from bullet options
}
@Nullable

View File

@@ -0,0 +1,67 @@
--- a/net/minecraft/world/entity/monster/Skeleton.java
+++ b/net/minecraft/world/entity/monster/Skeleton.java
@@ -135,4 +_,64 @@
this.spawnAtLocation(level, Items.SKELETON_SKULL);
}
}
+
+ // Purpur start - Skeletons eat wither roses
+ private int witherRosesFed = 0;
+
+ @Override
+ public net.minecraft.world.InteractionResult mobInteract(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) {
+ net.minecraft.world.item.ItemStack stack = player.getItemInHand(hand);
+
+ if (level().purpurConfig.skeletonFeedWitherRoses > 0 && this.getType() != EntityType.WITHER_SKELETON && stack.getItem() == net.minecraft.world.level.block.Blocks.WITHER_ROSE.asItem()) {
+ return this.feedWitherRose(player, stack);
+ }
+
+ return super.mobInteract(player, hand);
+ }
+
+ private net.minecraft.world.InteractionResult feedWitherRose(net.minecraft.world.entity.player.Player player, net.minecraft.world.item.ItemStack stack) {
+ if (++witherRosesFed < level().purpurConfig.skeletonFeedWitherRoses) {
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(1);
+ }
+ return net.minecraft.world.InteractionResult.CONSUME;
+ }
+
+ WitherSkeleton skeleton = EntityType.WITHER_SKELETON.create(level(), net.minecraft.world.entity.EntitySpawnReason.CONVERSION);
+ if (skeleton == null) {
+ return net.minecraft.world.InteractionResult.PASS;
+ }
+
+ skeleton.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
+ skeleton.setHealth(this.getHealth());
+ skeleton.setAggressive(this.isAggressive());
+ skeleton.copyPosition(this);
+ skeleton.setYBodyRot(this.yBodyRot);
+ skeleton.setYHeadRot(this.getYHeadRot());
+ skeleton.yRotO = this.yRotO;
+ skeleton.xRotO = this.xRotO;
+
+ if (this.hasCustomName()) {
+ skeleton.setCustomName(this.getCustomName());
+ }
+
+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTransformEvent(this, skeleton, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) {
+ return net.minecraft.world.InteractionResult.PASS;
+ }
+
+ this.level().addFreshEntity(skeleton);
+ this.remove(RemovalReason.DISCARDED, org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
+ if (!player.getAbilities().instabuild) {
+ stack.shrink(1);
+ }
+
+ for (int i = 0; i < 15; ++i) {
+ ((ServerLevel) level()).sendParticlesSource(((ServerLevel) level()).players(), null, net.minecraft.core.particles.ParticleTypes.HAPPY_VILLAGER,
+ false, true,
+ getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1,
+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0);
+ }
+ return net.minecraft.world.InteractionResult.SUCCESS;
+ }
+ // Purpur end - Skeletons eat wither roses
}

View File

@@ -0,0 +1,22 @@
--- a/net/minecraft/world/entity/monster/Strider.java
+++ b/net/minecraft/world/entity/monster/Strider.java
@@ -414,6 +_,19 @@
@Override
public InteractionResult mobInteract(Player player, InteractionHand hand) {
boolean isFood = this.isFood(player.getItemInHand(hand));
+ // Purpur start
+ if (level().purpurConfig.striderGiveSaddleBack && player.isSecondaryUseActive() && !isFood && isSaddled() && !isVehicle()) {
+ this.steering.setSaddle(false);
+ if (!player.getAbilities().instabuild) {
+ ItemStack saddle = new ItemStack(Items.SADDLE);
+ if (!player.getInventory().add(saddle)) {
+ player.drop(saddle, false);
+ }
+ }
+ return InteractionResult.SUCCESS;
+ }
+ // Purpur end
+
if (!isFood && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) {
if (!this.level().isClientSide) {
player.startRiding(this);

View File

@@ -0,0 +1,14 @@
--- a/net/minecraft/world/entity/monster/Vindicator.java
+++ b/net/minecraft/world/entity/monster/Vindicator.java
@@ -132,6 +_,11 @@
RandomSource random = level.getRandom();
this.populateDefaultEquipmentSlots(random, difficulty);
this.populateDefaultEquipmentEnchantments(level, random, difficulty);
+ // Purpur start - Special mobs naturally spawn
+ if (level().purpurConfig.vindicatorJohnnySpawnChance > 0D && random.nextDouble() <= level().purpurConfig.vindicatorJohnnySpawnChance) {
+ setCustomName(Component.translatable("Johnny"));
+ }
+ // Purpur end - Special mobs naturally spawn
return spawnGroupData1;
}

View File

@@ -0,0 +1,35 @@
--- a/net/minecraft/world/entity/monster/Zombie.java
+++ b/net/minecraft/world/entity/monster/Zombie.java
@@ -114,7 +_,19 @@
this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0));
this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class));
this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
- if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Spigot
+ // Purpur start - Add option to disable zombie aggressiveness towards villagers
+ if (this.level().spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false) { // Spigot
+ @Override
+ public boolean canUse() {
+ return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canUse();
+ }
+
+ @Override
+ public boolean canContinueToUse() {
+ return (level().purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level().getServer().server.isLagging()) && super.canContinueToUse();
+ }
+ });
+ // Purpur end - Add option to disable zombie aggressiveness towards villagers
this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR));
}
@@ -550,10 +_,7 @@
}
if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
- LocalDate localDate = LocalDate.now();
- int i = localDate.get(ChronoField.DAY_OF_MONTH);
- int i1 = localDate.get(ChronoField.MONTH_OF_YEAR);
- if (i1 == 10 && i == 31 && random.nextFloat() < 0.25F) {
+ if (net.minecraft.world.entity.ambient.Bat.isHalloweenSeason(level.getMinecraftWorld()) && this.random.nextFloat() < this.level().purpurConfig.chanceHeadHalloweenOnEntity) { // Purpur - Halloween options and optimizations
this.setItemSlot(EquipmentSlot.HEAD, new ItemStack(random.nextFloat() < 0.1F ? Blocks.JACK_O_LANTERN : Blocks.CARVED_PUMPKIN));
this.armorDropChances[EquipmentSlot.HEAD.getIndex()] = 0.0F;
}

View File

@@ -0,0 +1,15 @@
--- a/net/minecraft/world/entity/monster/ZombieVillager.java
+++ b/net/minecraft/world/entity/monster/ZombieVillager.java
@@ -156,10 +_,10 @@
public InteractionResult mobInteract(Player player, InteractionHand hand) {
ItemStack itemInHand = player.getItemInHand(hand);
if (itemInHand.is(Items.GOLDEN_APPLE)) {
- if (this.hasEffect(MobEffects.WEAKNESS)) {
+ if (this.hasEffect(MobEffects.WEAKNESS) && level().purpurConfig.zombieVillagerCureEnabled) { // Purpur - Add option to disable zombie villagers cure
itemInHand.consume(1, player);
if (!this.level().isClientSide) {
- this.startConverting(player.getUUID(), this.random.nextInt(2401) + 3600);
+ this.startConverting(player.getUUID(), this.random.nextInt(level().purpurConfig.zombieVillagerCuringTimeMax - level().purpurConfig.zombieVillagerCuringTimeMin + 1) + level().purpurConfig.zombieVillagerCuringTimeMin); // Purpur - Customizable Zombie Villager curing times
}
return InteractionResult.SUCCESS_SERVER;

View File

@@ -0,0 +1,20 @@
--- a/net/minecraft/world/entity/monster/ZombifiedPiglin.java
+++ b/net/minecraft/world/entity/monster/ZombifiedPiglin.java
@@ -112,7 +_,7 @@
this.maybeAlertOthers();
}
- if (this.isAngry()) {
+ if (this.isAngry() && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur - Toggle for Zombified Piglin death always counting as player kill when angry
this.lastHurtByPlayerTime = this.tickCount;
}
@@ -163,7 +_,7 @@
this.ticksUntilNextAlert = ALERT_INTERVAL.sample(this.random);
}
- if (livingEntity instanceof Player) {
+ if (livingEntity instanceof Player && this.level().purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur - Toggle for Zombified Piglin death always counting as player kill when angry
this.setLastHurtByPlayer((Player)livingEntity);
}

Some files were not shown because too many files have changed in this diff Show More