From 4e5682c6b17968c9837c1575befd951f49486a76 Mon Sep 17 00:00:00 2001 From: itqop Date: Sun, 9 Nov 2025 15:54:20 +0300 Subject: [PATCH] feat: first mixins attempt --- .../whitelist/WhitelistEventHandler.java | 28 +++++--------- .../whitelist/mixin/PlayerListMixin.java | 37 +++++++++++++++++++ src/main/resources/whitelist.mixins.json | 13 +++++++ .../templates/META-INF/neoforge.mods.toml | 4 +- 4 files changed, 61 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/itqop/whitelist/mixin/PlayerListMixin.java create mode 100644 src/main/resources/whitelist.mixins.json diff --git a/src/main/java/org/itqop/whitelist/WhitelistEventHandler.java b/src/main/java/org/itqop/whitelist/WhitelistEventHandler.java index 0db477c..1f60b5e 100644 --- a/src/main/java/org/itqop/whitelist/WhitelistEventHandler.java +++ b/src/main/java/org/itqop/whitelist/WhitelistEventHandler.java @@ -25,6 +25,7 @@ public class WhitelistEventHandler { WhitelistCommands.register(event.getDispatcher()); } + // Check whitelist when player logs in @SubscribeEvent(priority = EventPriority.HIGHEST) public static void onPlayerLogin(PlayerEvent.PlayerLoggedInEvent event) { if (!Config.enableWhitelist) return; @@ -47,35 +48,24 @@ public class WhitelistEventHandler { ) .join(); - // If check failed or player not whitelisted, kick immediately + // If check failed or player not whitelisted, disconnect immediately if (!response.isSuccess() || !response.isWhitelisted()) { - kickPlayer(player, createKickMessage(response.isSuccess())); + disconnectPlayer(player, createKickMessage(response.isSuccess())); } } catch (Exception e) { // On any error, deny access LOGGER.error("Whitelist check error for '{}': {}", playerName, e.getMessage()); - kickPlayer(player, Component.literal("Whitelist service error. Please contact administrator.")); + disconnectPlayer(player, Component.literal("Whitelist service error. Please contact administrator.")); } } - private static void kickPlayer(ServerPlayer player, Component message) { - if (player.server == null || player.connection == null) return; + private static void disconnectPlayer(ServerPlayer player, Component message) { + if (player.connection == null) return; - // Send disconnect message with delay to ensure client receives it - // Without delay, rapid reconnects may fail to show the message - player.server.execute(() -> { - if (player.connection != null) { - try { - // Small delay to ensure message packet is sent before connection close - Thread.sleep(100); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - player.connection.disconnect(message); - LOGGER.info("Player '{}' kicked from whitelist", player.getGameProfile().getName()); - } - }); + // Disconnect player - mixin will suppress all join/leave messages + player.connection.disconnect(message); + LOGGER.info("Player '{}' denied access (not in whitelist)", player.getGameProfile().getName()); } private static Component createKickMessage(boolean apiAvailable) { diff --git a/src/main/java/org/itqop/whitelist/mixin/PlayerListMixin.java b/src/main/java/org/itqop/whitelist/mixin/PlayerListMixin.java new file mode 100644 index 0000000..1f70b56 --- /dev/null +++ b/src/main/java/org/itqop/whitelist/mixin/PlayerListMixin.java @@ -0,0 +1,37 @@ +package org.itqop.whitelist.mixin; + +import net.minecraft.network.chat.Component; +import net.minecraft.server.players.PlayerList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(PlayerList.class) +public class PlayerListMixin { + + /** + * Suppress ALL player join/leave messages + * Intercepts ALL calls to broadcastSystemMessage and filters out join/leave messages + */ + @ModifyArg(method = "*", + at = @At(value = "INVOKE", + target = "Lnet/minecraft/server/players/PlayerList;broadcastSystemMessage(Lnet/minecraft/network/chat/Component;Z)V"), + index = 0) + private Component suppressJoinLeaveMessages(Component original) { + String messageString = original.getString(); + + // Check if this is a join or leave message + // Common patterns: "Player joined the game", "Player left the game" + // Russian: "Игрок присоединился к игре", "Игрок покинул игру" + if (messageString.contains("joined the game") || + messageString.contains("left the game") || + messageString.contains("присоединился") || + messageString.contains("покинул")) { + // Return empty component to suppress the message + return Component.empty(); + } + + // For all other messages, return the original + return original; + } +} diff --git a/src/main/resources/whitelist.mixins.json b/src/main/resources/whitelist.mixins.json new file mode 100644 index 0000000..8fc79b1 --- /dev/null +++ b/src/main/resources/whitelist.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.itqop.whitelist.mixin", + "compatibilityLevel": "JAVA_21", + "refmap": "whitelist.refmap.json", + "mixins": [ + "PlayerListMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/main/templates/META-INF/neoforge.mods.toml b/src/main/templates/META-INF/neoforge.mods.toml index 6441e6d..0ee7da4 100644 --- a/src/main/templates/META-INF/neoforge.mods.toml +++ b/src/main/templates/META-INF/neoforge.mods.toml @@ -35,8 +35,8 @@ authors = "${mod_authors}" #optional description = '''${mod_description}''' # The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded. -#[[mixins]] -#config="${mod_id}.mixins.json" +[[mixins]] +config="${mod_id}.mixins.json" # The [[accessTransformers]] block allows you to declare where your AT file is. # If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg