diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index 74d477395..2fb143488 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -332,6 +332,24 @@ limit-villager-iron-golem-spawns * **default**: 5 * **description**: Maximum amount of iron golems villagers can spawn in configured radius +idle-timeout +~~~~~~~~~~~~ +* kick-if-idle + - **default**: true + - **description**: Kick players if they become idle (see server.properties for player-idle-timeout time) + +* tick-nearby-entities + - **default**: false + - **description**: Should entities tick normally when nearby players are afk. False will require at least 1 non-afk player in order to tick. + +* broadcast + * away + - **default**: "&e&o{player} is now AFK" + - **description**: The message to broadcast server-wide when a player goes afk. Set to empty string ("") to disable + * back + - **default**: "&e&o{player} is no longer AFK" + - **description**: The message to broadcast server-wide when a player comes back from being afk. Set to empty string ("") to disable + elytra ~~~~~~ * damage-per-second diff --git a/patches/server/0058-Implement-AFK-options.patch b/patches/server/0058-Implement-AFK-options.patch new file mode 100644 index 000000000..f086052a2 --- /dev/null +++ b/patches/server/0058-Implement-AFK-options.patch @@ -0,0 +1,220 @@ +From 1129007b4b28ee14349f7018ef4de740543895e8 Mon Sep 17 00:00:00 2001 +From: William Blake Galbreath +Date: Thu, 8 Aug 2019 15:29:15 -0500 +Subject: [PATCH] Implement AFK options + +--- + .../java/net/minecraft/server/Entity.java | 1 + + .../net/minecraft/server/EntityHuman.java | 24 +++++++++++++ + .../net/minecraft/server/EntityPlayer.java | 1 + + .../net/minecraft/server/IEntityAccess.java | 34 +++++++------------ + .../net/minecraft/server/IEntitySelector.java | 2 ++ + .../minecraft/server/PlayerConnection.java | 10 ++++++ + .../net/pl3x/purpur/PurpurWorldConfig.java | 12 +++++++ + .../java/org/spigotmc/ActivationRange.java | 1 + + 8 files changed, 63 insertions(+), 22 deletions(-) + +diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java +index 770d21468..2ff5a12d8 100644 +--- a/src/main/java/net/minecraft/server/Entity.java ++++ b/src/main/java/net/minecraft/server/Entity.java +@@ -1396,6 +1396,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke + return MathHelper.c(f * f + f1 * f1 + f2 * f2); + } + ++ public double getDistanceSq(double x, double y, double z) { return e(x, y, z); } // Purpur - OBFHELPER + public double e(double d0, double d1, double d2) { + double d3 = this.locX - d0; + double d4 = this.locY - d1; +diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java +index 2a943f316..c396bf2e2 100644 +--- a/src/main/java/net/minecraft/server/EntityHuman.java ++++ b/src/main/java/net/minecraft/server/EntityHuman.java +@@ -75,6 +75,30 @@ public abstract class EntityHuman extends EntityLiving { + public boolean affectsSpawning = true; + // Paper end + ++ // Purpur start ++ private boolean isAfk = false; ++ ++ public void setAfk(boolean isAfk) { ++ if (!world.purpurConfig.idleTimeoutKick) { // do not broadcast if going to kick ++ if (!world.purpurConfig.idleTimeoutBroadcastAway.isEmpty() && !this.isAfk && isAfk) { ++ getMinecraftServer().server.broadcastMessage(world.purpurConfig.idleTimeoutBroadcastAway.replace("{player}", getName())); ++ } else if (!world.purpurConfig.idleTimeoutBroadcastBack.isEmpty() && this.isAfk && !isAfk) { ++ getMinecraftServer().server.broadcastMessage(world.purpurConfig.idleTimeoutBroadcastBack.replace("{player}", getName())); ++ } ++ } ++ this.isAfk = isAfk; ++ } ++ ++ public boolean isAfk() { ++ return isAfk; ++ } ++ ++ @Override ++ public boolean isCollidable(boolean ignoreClimbing) { ++ return !isAfk && super.isCollidable(ignoreClimbing); ++ } ++ // Purpur end ++ + // CraftBukkit start + public boolean fauxSleeping; + public String spawnWorld = ""; +diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java +index 622899d8f..f7694545e 100644 +--- a/src/main/java/net/minecraft/server/EntityPlayer.java ++++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -1593,6 +1593,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + + public void resetIdleTimer() { + this.cm = SystemUtils.getMonotonicMillis(); ++ setAfk(false); // Purpur + } + + public ServerStatisticManager getStatisticManager() { +diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java +index 9aaa75e95..70fe85279 100644 +--- a/src/main/java/net/minecraft/server/IEntityAccess.java ++++ b/src/main/java/net/minecraft/server/IEntityAccess.java +@@ -116,28 +116,18 @@ public interface IEntityAccess { + return entityhuman; + } + +- default boolean isPlayerNearby(double d0, double d1, double d2, double d3) { +- Iterator iterator = this.getPlayers().iterator(); +- +- double d4; +- +- do { +- EntityHuman entityhuman; +- +- do { +- do { +- if (!iterator.hasNext()) { +- return false; +- } +- +- entityhuman = (EntityHuman) iterator.next(); +- } while (!IEntitySelector.f.test(entityhuman)); +- } while (!IEntitySelector.b.test(entityhuman)); +- +- d4 = entityhuman.e(d0, d1, d2); +- } while (d3 >= 0.0D && d4 >= d3 * d3); +- +- return true; ++ // Purpur start ++ default boolean isPlayerNearby(double x, double y, double z, double distance) { ++ double distanceSq = distance * distance; ++ for (EntityHuman player : getPlayers()) { ++ if (IEntitySelector.notSpectator().test(player) && IEntitySelector.isLivingAlive().test(player) && IEntitySelector.notAfk.test(player)) { ++ if (distance < 0.0D || player.getDistanceSq(x, y, z) < distanceSq) { ++ return true; ++ } ++ } ++ } ++ return false; ++ // Purpur end + } + + @Nullable +diff --git a/src/main/java/net/minecraft/server/IEntitySelector.java b/src/main/java/net/minecraft/server/IEntitySelector.java +index 7ef7fe228..915be1a9a 100644 +--- a/src/main/java/net/minecraft/server/IEntitySelector.java ++++ b/src/main/java/net/minecraft/server/IEntitySelector.java +@@ -7,6 +7,7 @@ import javax.annotation.Nullable; + public final class IEntitySelector { + + public static final Predicate a = Entity::isAlive; ++ public static Predicate isLivingAlive() { return b; } // Purpur - OBFHELPER + public static final Predicate b = EntityLiving::isAlive; + public static final Predicate c = (entity) -> { + return entity.isAlive() && !entity.isVehicle() && !entity.isPassenger(); +@@ -22,6 +23,7 @@ public final class IEntitySelector { + public static final Predicate f = (entity) -> { + return !entity.isSpectator(); + }; ++ public static Predicate notAfk = (player) -> !player.isAfk(); // Purpur + + public static Predicate a(double d0, double d1, double d2, double d3) { + double d4 = d3 * d3; +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index 8aa8a672d..7d4369887 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -274,6 +274,12 @@ public class PlayerConnection implements PacketListenerPlayIn { + } + + if (this.player.F() > 0L && this.minecraftServer.getIdleTimeout() > 0 && SystemUtils.getMonotonicMillis() - this.player.F() > (long) (this.minecraftServer.getIdleTimeout() * 1000 * 60)) { ++ // Purpur start ++ this.player.setAfk(true); ++ if (!this.player.world.purpurConfig.idleTimeoutKick) { ++ return; ++ } ++ // Purpur end + this.player.resetIdleTimer(); // CraftBukkit - SPIGOT-854 + this.disconnect(new ChatMessage("multiplayer.disconnect.idling", new Object[0])); + } +@@ -490,6 +496,8 @@ public class PlayerConnection implements PacketListenerPlayIn { + this.lastYaw = to.getYaw(); + this.lastPitch = to.getPitch(); + ++ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getX() != from.getX() || to.getY() != from.getY() || to.getZ() != from.getZ()) this.player.resetIdleTimer(); // Purpur ++ + // Skip the first time we do this + if (true) { // Spigot - don't skip any move events + Location oldTo = to.clone(); +@@ -1150,6 +1158,8 @@ public class PlayerConnection implements PacketListenerPlayIn { + this.lastYaw = to.getYaw(); + this.lastPitch = to.getPitch(); + ++ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getX() != from.getX() || to.getY() != from.getY() || to.getZ() != from.getZ()) this.player.resetIdleTimer(); // Purpur ++ + // Skip the first time we do this + if (from.getX() != Double.MAX_VALUE) { + Location oldTo = to.clone(); +diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +index b1dcb5f8d..69fac757d 100644 +--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java ++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +@@ -1,6 +1,7 @@ + package net.pl3x.purpur; + + import com.destroystokyo.paper.PaperWorldConfig; ++import org.bukkit.ChatColor; + import org.bukkit.configuration.file.YamlConfiguration; + import org.spigotmc.SpigotWorldConfig; + +@@ -131,6 +132,17 @@ public class PurpurWorldConfig { + limitVillagerIronGolemSpawns = getInt("limit-villager-iron-golem-spawns", limitVillagerIronGolemSpawns); + } + ++ public boolean idleTimeoutKick = true; ++ public boolean idleTimeoutTickNearbyEntities = false; ++ public String idleTimeoutBroadcastAway = "&e&o{player} is now AFK"; ++ public String idleTimeoutBroadcastBack = "&e&o{player} is no longer AFK"; ++ private void playerIdleTimeoutSettings() { ++ idleTimeoutKick = getBoolean("idle-timeout.kick-if-idle", idleTimeoutKick); ++ idleTimeoutTickNearbyEntities = getBoolean("idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); ++ idleTimeoutBroadcastAway = ChatColor.translateAlternateColorCodes('&', getString("idle-timeout.broadcast.away", idleTimeoutBroadcastAway)); ++ idleTimeoutBroadcastBack = ChatColor.translateAlternateColorCodes('&', getString("idle-timeout.broadcast.back", idleTimeoutBroadcastBack)); ++ } ++ + public int elytraDamagePerSecond = 1; + public double elytraDamageMultiplyBySpeed = 0; + public boolean elytraIgnoreUnbreaking = false; +diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java +index 92601c581..185717c80 100644 +--- a/src/main/java/org/spigotmc/ActivationRange.java ++++ b/src/main/java/org/spigotmc/ActivationRange.java +@@ -128,6 +128,7 @@ public class ActivationRange + { + + player.activatedTick = MinecraftServer.currentTick; ++ if (!player.world.purpurConfig.idleTimeoutTickNearbyEntities && player.isAfk()) continue; // Purpur + maxBB = player.getBoundingBox().grow( maxRange, 256, maxRange ); + ActivationType.MISC.boundingBox = player.getBoundingBox().grow( miscActivationRange, 256, miscActivationRange ); + ActivationType.RAIDER.boundingBox = player.getBoundingBox().grow( raiderActivationRange, 256, raiderActivationRange ); +-- +2.20.1 +