Add AFK support

This commit is contained in:
Alessandro Proto 2024-10-13 02:46:59 +02:00
parent 99099a345a
commit 4f38f2e5f0
4 changed files with 186 additions and 1 deletions

View file

@ -9,7 +9,7 @@ yarn_mappings=1.20.1+build.10
loader_version=0.16.5 loader_version=0.16.5
# Mod Properties # Mod Properties
mod_version=1.8.5 mod_version=1.9.0
maven_group=cc.reconnected maven_group=cc.reconnected
archives_base_name=rcc-server archives_base_name=rcc-server

View file

@ -2,11 +2,14 @@ package cc.reconnected.server;
import cc.reconnected.server.commands.RccCommand; import cc.reconnected.server.commands.RccCommand;
import cc.reconnected.server.database.PlayerData; import cc.reconnected.server.database.PlayerData;
import cc.reconnected.server.events.PlayerActivityEvents;
import cc.reconnected.server.events.PlayerWelcome; import cc.reconnected.server.events.PlayerWelcome;
import cc.reconnected.server.events.Ready; import cc.reconnected.server.events.Ready;
import cc.reconnected.server.http.ServiceServer; import cc.reconnected.server.http.ServiceServer;
import cc.reconnected.server.trackers.AfkTracker;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
@ -48,6 +51,11 @@ public class RccServer implements ModInitializer {
return luckPerms; return luckPerms;
} }
private AfkTracker afkTracker;
public AfkTracker afkTracker() {
return afkTracker;
}
public static float getTPS() { public static float getTPS() {
return currentTps; return currentTps;
} }
@ -79,6 +87,7 @@ public class RccServer implements ModInitializer {
ServerLifecycleEvents.SERVER_STARTED.register(server -> { ServerLifecycleEvents.SERVER_STARTED.register(server -> {
luckPerms = LuckPermsProvider.get(); luckPerms = LuckPermsProvider.get();
afkTracker = new AfkTracker();
Ready.READY.invoker().ready(server, luckPerms); Ready.READY.invoker().ready(server, luckPerms);
}); });
@ -114,6 +123,7 @@ public class RccServer implements ModInitializer {
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> { ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
currentPlayerCount = server.getCurrentPlayerCount() - 1; currentPlayerCount = server.getCurrentPlayerCount() - 1;
}); });
} }
public void broadcastMessage(MinecraftServer server, Text message) { public void broadcastMessage(MinecraftServer server, Text message) {

View file

@ -0,0 +1,31 @@
package cc.reconnected.server.events;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
public final class PlayerActivityEvents {
public static final Event<PlayerActivityEvents.Afk> AFK = EventFactory.createArrayBacked(PlayerActivityEvents.Afk.class, callbacks -> (handler, server) -> {
for (PlayerActivityEvents.Afk callback : callbacks) {
callback.onAfk(handler, server);
}
});
public static final Event<PlayerActivityEvents.AfkReturn> AFK_RETURN = EventFactory.createArrayBacked(PlayerActivityEvents.AfkReturn.class, callbacks -> (handler, server) -> {
for (PlayerActivityEvents.AfkReturn callback : callbacks) {
callback.onAfkReturn(handler, server);
}
});
@FunctionalInterface
public interface Afk {
void onAfk(ServerPlayerEntity player, MinecraftServer server);
}
@FunctionalInterface
public interface AfkReturn {
void onAfkReturn(ServerPlayerEntity player, MinecraftServer server);
}
}

View file

@ -0,0 +1,144 @@
package cc.reconnected.server.trackers;
import cc.reconnected.server.RccServer;
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.entity.player.PlayerEntity;
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 = 10;
private static final int absentTimeTrigger = 300 * 20; // 5 mins (* 20 ticks)
private final HashMap<UUID, PlayerPosition> playerPositions = new HashMap<>();
private final HashMap<UUID, Integer> playerLastUpdate = new HashMap<>();
private final HashMap<UUID, Boolean> playerAfkStates = 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();
var playerPosition = new PlayerPosition(player);
playerPositions.put(player.getUuid(), playerPosition);
playerLastUpdate.put(player.getUuid(), server.getTicks());
playerAfkStates.put(player.getUuid(), false);
});
ServerPlayConnectionEvents.DISCONNECT.register((handler, server) -> {
playerPositions.remove(handler.getPlayer().getUuid());
playerLastUpdate.remove(handler.getPlayer().getUuid());
playerAfkStates.remove(handler.getPlayer().getUuid());
});
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;
});
}
public void updatePlayers(MinecraftServer server) {
var players = server.getPlayerManager().getPlayerList();
var currentTick = server.getTicks();
players.forEach(player -> {
if (!playerPositions.containsKey(player.getUuid())) {
playerPositions.put(player.getUuid(), new PlayerPosition(player));
return;
}
var oldPosition = playerPositions.get(player.getUuid());
var newPosition = new PlayerPosition(player);
if (!oldPosition.equals(newPosition)) {
playerPositions.put(player.getUuid(), newPosition);
resetAfkState(player, server);
return;
}
if (playerAfkStates.get(player.getUuid())) {
return;
}
if ((playerLastUpdate.get(player.getUuid()) + absentTimeTrigger) <= currentTick) {
// player is afk after 5 mins
playerAfkStates.put(player.getUuid(), true);
PlayerActivityEvents.AFK.invoker().onAfk(player, server);
}
});
}
private void resetAfkState(ServerPlayerEntity player, MinecraftServer server) {
playerLastUpdate.put(player.getUuid(), server.getTicks());
if (playerAfkStates.get(player.getUuid())) {
PlayerActivityEvents.AFK_RETURN.invoker().onAfkReturn(player, server);
playerAfkStates.put(player.getUuid(), false);
}
}
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();
}
}
}