rcc-server/src/main/java/cc/reconnected/server/trackers/AfkTracker.java

203 lines
7.2 KiB
Java

package cc.reconnected.server.trackers;
import cc.reconnected.server.RccServer;
import cc.reconnected.server.data.StateSaverAndLoader;
import cc.reconnected.server.database.PlayerData;
import cc.reconnected.server.events.PlayerActivityEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.player.*;
import net.fabricmc.fabric.api.message.v1.ServerMessageEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.TypedActionResult;
import java.util.HashMap;
import java.util.UUID;
public class AfkTracker {
private static final int cycleDelay = 1;
private static final int absentTimeTrigger = RccServer.CONFIG.afkTimeTrigger() * 20; // seconds * 20 ticks
private final HashMap<UUID, PlayerState> playerStates = new HashMap<>();
public AfkTracker() {
ServerTickEvents.END_SERVER_TICK.register(server -> {
if (server.getTicks() % cycleDelay == 0) {
updatePlayers(server);
}
});
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
final var player = handler.getPlayer();
playerStates.put(player.getUuid(), new PlayerState(player, server.getTicks()));
});
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
updatePlayerActiveTime(handler.getPlayer(), server.getTicks());
playerStates.remove(handler.getPlayer().getUuid());
// sync to LP
//var activeTime = String.valueOf(getActiveTime(handler.getPlayer()));
//var playerData = PlayerData.getPlayer(handler.getPlayer());
//playerData.set(PlayerData.KEYS.activeTime, activeTime).join();
});
AttackBlockCallback.EVENT.register((player, world, hand, pos, direction) -> {
resetAfkState((ServerPlayerEntity) player, world.getServer());
return ActionResult.PASS;
});
AttackEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
resetAfkState((ServerPlayerEntity) player, world.getServer());
return ActionResult.PASS;
});
UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> {
resetAfkState((ServerPlayerEntity) player, world.getServer());
return ActionResult.PASS;
});
UseEntityCallback.EVENT.register((player, world, hand, entity, hitResult) -> {
resetAfkState((ServerPlayerEntity) player, world.getServer());
return ActionResult.PASS;
});
UseItemCallback.EVENT.register((player, world, hand) -> {
resetAfkState((ServerPlayerEntity) player, world.getServer());
return TypedActionResult.pass(player.getStackInHand(hand));
});
ServerMessageEvents.ALLOW_CHAT_MESSAGE.register((message, sender, params) -> {
resetAfkState(sender, sender.getServer());
return true;
});
ServerMessageEvents.ALLOW_COMMAND_MESSAGE.register((message, source, params) -> {
if (!source.isExecutedByPlayer())
return true;
resetAfkState(source.getPlayer(), source.getServer());
return true;
});
}
private void updatePlayer(ServerPlayerEntity player, MinecraftServer server) {
var currentTick = server.getTicks();
var playerState = playerStates.computeIfAbsent(player.getUuid(), uuid -> new PlayerState(player, currentTick));
var oldPosition = playerState.position;
var newPosition = new PlayerPosition(player);
if (!oldPosition.equals(newPosition)) {
playerState.position = newPosition;
resetAfkState(player, server);
return;
}
if (playerState.isAfk)
return;
if ((playerState.lastUpdate + absentTimeTrigger) <= currentTick) {
// player is afk after 5 mins
updatePlayerActiveTime(player, currentTick);
playerState.isAfk = true;
PlayerActivityEvents.AFK.invoker().onAfk(player, server);
}
}
private void updatePlayerActiveTime(ServerPlayerEntity player, int currentTick) {
var playerState = playerStates.get(player.getUuid());
if(!playerState.isAfk) {
var worldPlayerData = StateSaverAndLoader.getPlayerState(player);
var interval = currentTick - playerState.activeStart;
worldPlayerData.activeTime += interval / 20;
}
}
private void updatePlayers(MinecraftServer server) {
var players = server.getPlayerManager().getPlayerList();
players.forEach(player -> {
updatePlayer(player, server);
});
}
private void resetAfkState(ServerPlayerEntity player, MinecraftServer server) {
var playerState = playerStates.get(player.getUuid());
playerState.lastUpdate = server.getTicks();
if (playerState.isAfk) {
playerState.isAfk = false;
playerState.activeStart = server.getTicks();
PlayerActivityEvents.AFK_RETURN.invoker().onAfkReturn(player, server);
}
}
public static class PlayerPosition {
public String dimension;
public double x;
public double y;
public double z;
public float yaw;
public float pitch;
public boolean equals(PlayerPosition obj) {
return x == obj.x && y == obj.y && z == obj.z
&& yaw == obj.yaw && pitch == obj.pitch
&& dimension.equals(obj.dimension);
}
public PlayerPosition(ServerPlayerEntity player) {
dimension = player.getWorld().getRegistryKey().getValue().toString();
x = player.getX();
y = player.getY();
z = player.getZ();
yaw = player.getYaw();
pitch = player.getPitch();
}
}
public static class PlayerState {
public PlayerPosition position;
public int lastUpdate;
public boolean isAfk;
public int activeStart;
public PlayerState(ServerPlayerEntity player, int lastUpdate) {
this.position = new PlayerPosition(player);
this.lastUpdate = lastUpdate;
this.isAfk = false;
this.activeStart = lastUpdate;
}
}
public boolean isPlayerAfk(UUID playerUuid) {
if (!playerStates.containsKey(playerUuid)) {
return false;
}
return playerStates.get(playerUuid).isAfk;
}
public void setPlayerAfk(ServerPlayerEntity player, boolean afk) {
if (!playerStates.containsKey(player.getUuid())) {
return;
}
var server = player.getWorld().getServer();
if (afk) {
playerStates.get(player.getUuid()).lastUpdate = -absentTimeTrigger - 20; // just to be sure
} else {
resetAfkState(player, server);
}
updatePlayer(player, server);
}
public int getActiveTime(ServerPlayerEntity player) {
var worldPlayerData = StateSaverAndLoader.getPlayerState(player);
return worldPlayerData.activeTime;
}
}