From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <blake.galbreath@gmail.com>
Date: Thu, 8 Aug 2019 15:29:15 -0500
Subject: [PATCH] AFK API


diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java
index fd6dbbf619b828c4ef3d00f8dc30d3893007db7b..7e388dcca2d4e78b49d5fc5fce0b962e74839fa3 100644
--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java
@@ -2070,8 +2070,54 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
 
     public void resetIdleTimer() {
         this.ca = SystemUtils.getMonotonicMillis();
+        setAfk(false); // Purpur
     }
 
+    // Purpur start
+    private boolean isAfk = false;
+
+    @Override
+    public void setAfk(boolean setAfk) {
+        if (this.isAfk == setAfk) {
+            return;
+        }
+
+        String msg = setAfk ? net.pl3x.purpur.PurpurConfig.afkBroadcastAway : net.pl3x.purpur.PurpurConfig.afkBroadcastBack;
+
+        net.pl3x.purpur.event.PlayerAFKEvent event = new net.pl3x.purpur.event.PlayerAFKEvent(getBukkitEntity(), setAfk, world.purpurConfig.idleTimeoutKick, msg, !Bukkit.isPrimaryThread());
+        if (!event.callEvent() || event.shouldKick()) {
+            return;
+        }
+
+        this.isAfk = setAfk;
+
+        if (!setAfk) {
+            resetIdleTimer();
+        }
+
+        msg = event.getBroadcastMsg();
+        if (msg != null && !msg.isEmpty()) {
+            server.getPlayerList().sendMessage(org.bukkit.craftbukkit.util.CraftChatMessage.fromStringOrNull(String.format(msg, getProfile().getName())));
+        }
+
+        if (world.purpurConfig.idleTimeoutUpdateTabList) {
+            getBukkitEntity().setPlayerListName((setAfk ? net.pl3x.purpur.PurpurConfig.afkTabListPrefix : "") + getName());
+        }
+
+        ((WorldServer) world).everyoneSleeping();
+    }
+
+    @Override
+    public boolean isAfk() {
+        return isAfk;
+    }
+
+    @Override
+    public boolean isCollidable() {
+        return !isAfk() && super.isCollidable();
+    }
+    // Purpur end
+
     public ServerStatisticManager getStatisticManager() {
         return this.serverStatisticManager;
     }
diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java
index e2bfb1423f6b24514cf7ea364be8bf5f554e0a5f..18d70aff974d756a62a8f9f0e6c52f89ef1fa2c8 100644
--- a/src/main/java/net/minecraft/server/level/WorldServer.java
+++ b/src/main/java/net/minecraft/server/level/WorldServer.java
@@ -1002,7 +1002,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
         // CraftBukkit end
 
         if (this.everyoneSleeping && this.players.stream().noneMatch((entityplayer) -> {
-            return !entityplayer.isSpectator() && !entityplayer.isDeeplySleeping() && !entityplayer.fauxSleeping; // CraftBukkit
+            return !entityplayer.isSpectator() && !entityplayer.isDeeplySleeping() && !entityplayer.fauxSleeping && !(purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk()); // CraftBukkit // Purpur
         })) {
             // CraftBukkit start
             long l = this.worldData.getDayTime() + 24000L;
@@ -1348,7 +1348,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
             while (iterator.hasNext()) {
                 EntityPlayer entityplayer = (EntityPlayer) iterator.next();
 
-                if (entityplayer.isSpectator() || (entityplayer.fauxSleeping && !entityplayer.isSleeping())) { // CraftBukkit
+                if (entityplayer.isSpectator() || (entityplayer.fauxSleeping && !entityplayer.isSleeping()) || (purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk())) { // CraftBukkit // Purpur
                     ++i;
                 } else if (entityplayer.isSleeping()) {
                     ++j;
diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java
index 80b77cdffd64690bb9066b8745ea0ee08d60a7a4..e58bbd4c1b59293ddf58d0f7cf37a629f5cc96d6 100644
--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java
+++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java
@@ -399,6 +399,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"));
         }
@@ -674,6 +680,8 @@ public class PlayerConnection implements PacketListenerPlayIn {
                     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.resetIdleTimer(); // Purpur
+
                     // Skip the first time we do this
                     if (true) { // Spigot - don't skip any move events
                         Location oldTo = to.clone();
@@ -1416,7 +1424,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
 
                             if (!this.player.H() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.playerInteractManager.isCreative() && this.player.playerInteractManager.getGameMode() != EnumGamemode.SPECTATOR) { // Spigot
                                 flag1 = true; // Tuinity - diff on change, this should be moved wrongly
-                                PlayerConnection.LOGGER.warn("{} moved wrongly!", this.player.getDisplayName().getString());
+                                PlayerConnection.LOGGER.warn("{} moved wrongly! ({})", this.player.getDisplayName().getString(), d11); // Purpur
                             }
 
                             this.player.setLocation(d4, d5, d6, f, f1);
@@ -1466,6 +1474,8 @@ public class PlayerConnection implements PacketListenerPlayIn {
                                     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.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/minecraft/world/entity/IEntitySelector.java b/src/main/java/net/minecraft/world/entity/IEntitySelector.java
index f5e32faeb6d937cf90b1f3ea251b5cfc91f2338d..f9908fb7cc27a8947030c2100dccf1dc1a4e24f7 100644
--- a/src/main/java/net/minecraft/world/entity/IEntitySelector.java
+++ b/src/main/java/net/minecraft/world/entity/IEntitySelector.java
@@ -15,6 +15,7 @@ import net.minecraft.world.scores.ScoreboardTeamBase;
 public final class IEntitySelector {
 
     public static final Predicate<Entity> a = Entity::isAlive;
+    public static Predicate<EntityLiving> isLivingAlive() { return b; } // Purpur - OBFHELPER
     public static final Predicate<EntityLiving> b = EntityLiving::isAlive;
     public static final Predicate<Entity> c = (entity) -> {
         return entity.isAlive() && !entity.isVehicle() && !entity.isPassenger();
@@ -35,6 +36,7 @@ public final class IEntitySelector {
         return !entity.isSpectator();
     };
     public static Predicate<EntityHuman> isInsomniac = (player) -> MathHelper.clamp(((EntityPlayer) player).getStatisticManager().getStatisticValue(StatisticList.CUSTOM.get(StatisticList.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= 72000; // Paper
+    public static Predicate<EntityHuman> notAfk = (player) -> !player.isAfk(); // Purpur
 
     // Paper start
     public static final Predicate<Entity> affectsSpawning = (entity) -> {
diff --git a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java
index ad85dda5c50b797904824a08513fbcec042128ea..8f9d0769e7855c3565a34927f3f3741c43e45f35 100644
--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java
+++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java
@@ -182,6 +182,15 @@ public abstract class EntityHuman extends EntityLiving {
     }
     // CraftBukkit end
 
+    // Purpur start
+    public void setAfk(boolean setAfk){
+    }
+
+    public boolean isAfk() {
+        return false;
+    }
+    // Purpur end
+
     public EntityHuman(World world, BlockPosition blockposition, float f, GameProfile gameprofile) {
         super(EntityTypes.PLAYER, world);
         this.bL = ItemStack.b;
diff --git a/src/main/java/net/minecraft/world/level/IEntityAccess.java b/src/main/java/net/minecraft/world/level/IEntityAccess.java
index a7f2304acf8ee0a15d6eae8c42060e003be13ae7..fd56b2f15e570f266a79c25823a3b3530a693510 100644
--- a/src/main/java/net/minecraft/world/level/IEntityAccess.java
+++ b/src/main/java/net/minecraft/world/level/IEntityAccess.java
@@ -183,28 +183,18 @@ public interface IEntityAccess {
     }
     // Paper end
 
-    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.g.test(entityhuman));
-            } while (!IEntitySelector.b.test(entityhuman));
-
-            d4 = entityhuman.h(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.getDistanceSquared(x, y, z) < distanceSq) {
+                    return true;
+                }
+            }
+        }
+        return false;
+        // Purpur end
     }
 
     @Nullable
diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
index 64682d3188c356eec62b414d50e0221fc67de895..3c81d9f1171262d17407232433e3eb0383f424c0 100644
--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java
+++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java
@@ -1,6 +1,7 @@
 package net.pl3x.purpur;
 
 import com.google.common.base.Throwables;
+import net.minecraft.locale.LocaleLanguage;
 import net.minecraft.server.MinecraftServer;
 import net.pl3x.purpur.command.PurpurCommand;
 import org.bukkit.Bukkit;
@@ -129,6 +130,15 @@ public class PurpurConfig {
         return config.getString(path, config.getString(path));
     }
 
+    public static String afkBroadcastAway = "§e§o%s is now AFK";
+    public static String afkBroadcastBack = "§e§o%s is no longer AFK";
+    public static String afkTabListPrefix = "[AFK] ";
+    private static void messages() {
+        afkBroadcastAway = getString("settings.messages.afk-broadcast-away", afkBroadcastAway);
+        afkBroadcastBack = getString("settings.messages.afk-broadcast-back", afkBroadcastBack);
+        afkTabListPrefix = getString("settings.messages.afk-tab-list-prefix", afkTabListPrefix);
+    }
+
     public static String timingsUrl = "https://timings.pl3x.net";
     private static void timingsSettings() {
         timingsUrl = getString("settings.timings.url", timingsUrl);
diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
index 361f7857e461578e90cb71e15027dadaf794cb69..2578a4677d1ee060f687be531e696b7c7be89e84 100644
--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
@@ -56,4 +56,15 @@ public class PurpurWorldConfig {
         PurpurConfig.config.addDefault("world-settings.default." + path, def);
         return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path));
     }
+
+    public boolean idleTimeoutKick = true;
+    public boolean idleTimeoutTickNearbyEntities = true;
+    public boolean idleTimeoutCountAsSleeping = false;
+    public boolean idleTimeoutUpdateTabList = false;
+    private void playerIdleTimeoutSettings() {
+        idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick);
+        idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities);
+        idleTimeoutCountAsSleeping = getBoolean("gameplay-mechanics.player.idle-timeout.count-as-sleeping", idleTimeoutCountAsSleeping);
+        idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList);
+    }
 }
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 45e786565ac988abadffda2e7ba3ff1e2880b786..f4052aaa2235894b996d65c569a083f1a09b59a3 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -2491,4 +2491,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
         return spigot;
     }
     // Spigot end
+
+    // Purpur start
+    @Override
+    public boolean isAfk() {
+        return getHandle().isAfk();
+    }
+
+    @Override
+    public void setAfk(boolean setAfk) {
+        getHandle().setAfk(setAfk);
+    }
+
+    @Override
+    public void resetIdleTimer() {
+        getHandle().resetIdleTimer();
+    }
+    // Purpur end
 }
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
index 8b36ca5062f8e0e8bd58aa506e91704a747de81b..c94cd5a95f28190e88d31b522035fc7c74a2ac33 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -207,6 +207,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 );
