Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/better afk #2

Merged
merged 3 commits into from
Mar 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions src/main/java/xyz/nkomarn/harbor/listener/AfkListeners.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package xyz.nkomarn.harbor.listener;

import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.scheduler.BukkitRunnable;
import xyz.nkomarn.harbor.util.PlayerManager;

import java.util.ArrayDeque;
import java.util.Queue;

public final class AfkListeners extends BukkitRunnable implements Listener {

private final PlayerManager playerManager;
private double checksToMake = 0;
private final Queue<AfkPlayer> players = new ArrayDeque<>();

public AfkListeners(PlayerManager playerManager) {
this.playerManager = playerManager;
}

@EventHandler(ignoreCancelled = true)
public void onChat(AsyncPlayerChatEvent event) {
playerManager.updateActivity(event.getPlayer());
}

@EventHandler(ignoreCancelled = true)
public void onCommand(PlayerCommandPreprocessEvent event) {
playerManager.updateActivity(event.getPlayer());
}

@EventHandler(ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
playerManager.updateActivity((Player) event.getWhoClicked());
}

@EventHandler
public void onJoin(PlayerJoinEvent event) {
players.add(new AfkPlayer(event.getPlayer()));
}

@EventHandler
public void onLeave(PlayerQuitEvent event) {
players.remove(new AfkPlayer(event.getPlayer()));
}

@Override
public void run() {
if (players.isEmpty()) return;

// We want every player to get a check every 20 ticks.
checksToMake += players.size() / 20.0;
long start = System.currentTimeMillis();

// We assume that a tick should take 50 ms at max.
// To leave some space for other plugins we only take 20 ms of a tick.
while (System.currentTimeMillis() - start < 20 && checksToMake > 0) {
AfkPlayer afkPlayer = players.poll();
if (afkPlayer.changed()) {
playerManager.updateActivity(afkPlayer.player);
}
players.add(afkPlayer);
checksToMake--;
}
}

private static class AfkPlayer {
private final Player player;
private int hash;

public AfkPlayer(Player player) {
this.player = player;
}

/**
* Check if the player changed its position since the last check
* @return true if the position changed
*/
private boolean changed() {
int hash = player.getLocation().hashCode();
boolean changed = hash != this.hash;
this.hash = hash;
return changed;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AfkPlayer afkPlayer = (AfkPlayer) o;
return player.getUniqueId().equals(afkPlayer.player.getUniqueId());
}

@Override
public int hashCode() {
return player.getUniqueId().hashCode();
}
}
}
9 changes: 5 additions & 4 deletions src/main/java/xyz/nkomarn/harbor/listener/BedListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
import xyz.nkomarn.harbor.util.Messages;
import xyz.nkomarn.harbor.util.PlayerManager;

import java.util.concurrent.TimeUnit;
import java.time.Instant;
import java.time.temporal.ChronoUnit;

public class BedListener implements Listener {

Expand All @@ -37,7 +38,7 @@ public void onBedEnter(PlayerBedEnterEvent event) {
}

Bukkit.getScheduler().runTaskLater(harbor, () -> {
playerManager.setCooldown(player, System.currentTimeMillis());
playerManager.setCooldown(player, Instant.now());
harbor.getMessages().sendWorldChatMessage(event.getBed().getWorld(), messages.prepareMessage(
player, harbor.getConfiguration().getString("messages.chat.player-sleeping"))
);
Expand All @@ -51,7 +52,7 @@ public void onBedLeave(PlayerBedLeaveEvent event) {
}

Bukkit.getScheduler().runTaskLater(harbor, () -> {
playerManager.setCooldown(event.getPlayer(), System.currentTimeMillis());
playerManager.setCooldown(event.getPlayer(), Instant.now());
harbor.getMessages().sendWorldChatMessage(event.getBed().getWorld(), messages.prepareMessage(
event.getPlayer(), harbor.getConfiguration().getString("messages.chat.player-left-bed"))
);
Expand All @@ -74,6 +75,6 @@ private boolean isMessageSilenced(@NotNull Player player) {
}

int cooldown = harbor.getConfiguration().getInteger("messages.chat.message-cooldown");
return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - playerManager.getCooldown(player)) < cooldown;
return playerManager.getCooldown(player).until(Instant.now(), ChronoUnit.MINUTES) < cooldown;
}
}
51 changes: 15 additions & 36 deletions src/main/java/xyz/nkomarn/harbor/util/PlayerManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,23 @@
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.jetbrains.annotations.NotNull;
import xyz.nkomarn.harbor.Harbor;
import xyz.nkomarn.harbor.listener.AfkListeners;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class PlayerManager implements Listener {

private final Harbor harbor;
private final Map<UUID, Long> cooldowns;
private final Map<UUID, Long> playerActivity;
private final Map<UUID, Instant> cooldowns;
private final Map<UUID, Instant> playerActivity;

public PlayerManager(@NotNull Harbor harbor) {
this.harbor = harbor;
Expand All @@ -35,10 +33,11 @@ public PlayerManager(@NotNull Harbor harbor) {
* Gets the last tracked cooldown time for a given player.
*
* @param player The player for which to return cooldown time.
*
* @return The player's last cooldown time.
*/
public long getCooldown(@NotNull Player player) {
return cooldowns.getOrDefault(player.getUniqueId(), 0L);
public Instant getCooldown(@NotNull Player player) {
return cooldowns.getOrDefault(player.getUniqueId(), Instant.MIN);
}

/**
Expand All @@ -47,7 +46,7 @@ public long getCooldown(@NotNull Player player) {
* @param player The player for which to set cooldown.
* @param cooldown The cooldown value.
*/
public void setCooldown(@NotNull Player player, long cooldown) {
public void setCooldown(@NotNull Player player, Instant cooldown) {
cooldowns.put(player.getUniqueId(), cooldown);
}

Expand All @@ -62,6 +61,7 @@ public void clearCooldowns() {
* Checks if a player is considered "AFK" for Harbor's player checks.
*
* @param player The player to check.
*
* @return Whether the player is considered AFK.
*/
public boolean isAfk(@NotNull Player player) {
Expand All @@ -82,7 +82,7 @@ public boolean isAfk(@NotNull Player player) {
return false;
}

long minutes = TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - playerActivity.get(player.getUniqueId()));
long minutes = playerActivity.get(player.getUniqueId()).until(Instant.now(), ChronoUnit.MINUTES);
return minutes >= harbor.getConfiguration().getInteger("afk-detection.timeout");
}

Expand All @@ -92,14 +92,16 @@ public boolean isAfk(@NotNull Player player) {
* @param player The player to update.
*/
public void updateActivity(@NotNull Player player) {
playerActivity.put(player.getUniqueId(), System.currentTimeMillis());
playerActivity.put(player.getUniqueId(), Instant.now());
}

/**
* Registers Harbor's fallback listeners for AFK detection if Essentials is not present.
*/
public void registerFallbackListeners() {
harbor.getServer().getPluginManager().registerEvents(new AfkListeners(), harbor);
AfkListeners afkListeners = new AfkListeners(this);
afkListeners.runTaskTimer(harbor, 1, 1);
harbor.getServer().getPluginManager().registerEvents(afkListeners, harbor);
}

@EventHandler
Expand All @@ -108,27 +110,4 @@ public void onQuit(PlayerQuitEvent event) {
cooldowns.remove(uuid);
playerActivity.remove(uuid);
}

private final class AfkListeners implements Listener {

@EventHandler(ignoreCancelled = true)
public void onChat(AsyncPlayerChatEvent event) {
updateActivity(event.getPlayer());
}

@EventHandler(ignoreCancelled = true)
public void onCommand(PlayerCommandPreprocessEvent event) {
updateActivity(event.getPlayer());
}

@EventHandler(ignoreCancelled = true)
public void onMove(PlayerMoveEvent event) {
updateActivity(event.getPlayer());
}

@EventHandler(ignoreCancelled = true)
public void onInventoryClick(InventoryClickEvent event) {
updateActivity((Player) event.getWhoClicked());
}
}
}