From 964af6c856687f940004fd795d20753d15a48e8c Mon Sep 17 00:00:00 2001 From: Darto KLoning Date: Mon, 7 Sep 2026 07:51:30 +0700 Subject: [PATCH] fix gamemode player join --- README.md | 8 +-- gradle.properties | 2 +- .../DimensionGameModeManager.java | 62 ++++++++++++++----- .../lobbysystems/LobbySystemsMod.java | 17 ++--- .../mixin/ServerPlayerGameModeAccessor.java | 20 ------ src/main/resources/lobby-systems.mixins.json | 3 +- 6 files changed, 56 insertions(+), 56 deletions(-) delete mode 100644 src/main/java/id/dartokloning/lobbysystems/mixin/ServerPlayerGameModeAccessor.java diff --git a/README.md b/README.md index f5a8223..c6796bc 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Lobby Systems v1.5.5 — Fabric 26.2 +# Lobby Systems v1.5.6 — Fabric 26.2 -Server-side lobby routing untuk Minecraft 26.2/Fabric. v1.5.5 mengubah proses login agar Lobby menjadi initial dimension player sebelum world awal dikirim ke client, sehingga tidak ada lagi teleport join yang ditunda atau "persinggahan" di dimension terakhir. +Server-side lobby routing untuk Minecraft 26.2/Fabric. v1.5.6 mengubah proses login agar Lobby menjadi initial dimension player sebelum world awal dikirim ke client, sehingga tidak ada lagi teleport join yang ditunda atau "persinggahan" di dimension terakhir. -**v1.5.5 login placement fix:** player data tetap dibaca untuk menyimpan posisi gameplay terakhir, tetapi sebelum `PlayerList` mengirim initial world state, Lobby Systems mengganti initial level, posisi, yaw/pitch, dan gamemode ke konfigurasi Lobby. Mekanisme scheduled join teleport dari versi sebelumnya telah dihapus. `compile.bat` / `compile.sh` tetap mendeteksi JAR versi yang sama dan menawarkan Compile ulang atau Git-only. +**v1.5.6 login placement fix:** player data tetap dibaca untuk menyimpan posisi gameplay terakhir, tetapi sebelum `PlayerList` mengirim initial world state, Lobby Systems mengganti initial level, posisi, yaw/pitch, dan gamemode ke konfigurasi Lobby. Mekanisme scheduled join teleport dari versi sebelumnya telah dihapus. `compile.bat` / `compile.sh` tetap mendeteksi JAR versi yang sama dan menawarkan Compile ulang atau Git-only. **v1.5.0 feature release:** Portal Visual System menambahkan permukaan swirl ungu memakai block state vanilla `minecraft:nether_portal`, dengan axis X/Z yang ditentukan otomatis dari WorldEdit selection. Frame boleh memakai block apa saja. `manageDimensionGamemode` tetap default `true`, compatibility warning Dimensional Inventories tetap aktif, dan bridge WorldEdit 7.4.x tetap dipakai. @@ -90,7 +90,7 @@ State penting Lobby Systems memakai separator agar mudah terlihat di server cons ============================================ ``` -## Struktur konfigurasi v1.5.5 +## Struktur konfigurasi v1.5.6 ```text config/ diff --git a/gradle.properties b/gradle.properties index 6c8320e..84279fb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,6 +5,6 @@ minecraft_version=26.2 loader_version=0.19.3 loom_version=1.17-SNAPSHOT fabric_api_version=0.156.0+26.2 -mod_version=1.5.5 +mod_version=1.5.6 maven_group=id.dartokloning archives_base_name=lobby-systems diff --git a/src/main/java/id/dartokloning/lobbysystems/DimensionGameModeManager.java b/src/main/java/id/dartokloning/lobbysystems/DimensionGameModeManager.java index 481b541..28f938d 100644 --- a/src/main/java/id/dartokloning/lobbysystems/DimensionGameModeManager.java +++ b/src/main/java/id/dartokloning/lobbysystems/DimensionGameModeManager.java @@ -30,10 +30,11 @@ public final class DimensionGameModeManager { private final Map configured = new LinkedHashMap<>(); private final Map lastDimension = new ConcurrentHashMap<>(); - private final Map joinEnforcementTicks = new ConcurrentHashMap<>(); + private final Map joinSyncTicks = new ConcurrentHashMap<>(); - private static final int JOIN_ENFORCEMENT_DURATION_TICKS = 100; // 5 detik - private static final int JOIN_ENFORCEMENT_INTERVAL_TICKS = 5; // 0.25 detik + // Koreksi post-login pada +1, +5, dan +20 tick. JOIN sendiri melakukan + // apply immediate (stage +0) sebelum antrean ini dimulai. + private static final int JOIN_SYNC_FINAL_TICK = 20; public boolean load() { if (!Files.exists(PATH)) { @@ -88,28 +89,55 @@ public final class DimensionGameModeManager { applyConfiguredMode(player, dimension, true); } - Integer remaining = joinEnforcementTicks.get(uuid); - if (remaining != null) { - if (remaining <= 0) { - joinEnforcementTicks.remove(uuid); + Integer elapsed = joinSyncTicks.get(uuid); + if (elapsed != null) { + int next = elapsed + 1; + if (next == 1 || next == 5 || next == JOIN_SYNC_FINAL_TICK) { + applyJoinSyncStage(player, dimension, next); + } + if (next >= JOIN_SYNC_FINAL_TICK) { + joinSyncTicks.remove(uuid); } else { - if (remaining % JOIN_ENFORCEMENT_INTERVAL_TICKS == 0) { - applyConfiguredMode(player, dimension, true); - } - joinEnforcementTicks.put(uuid, remaining - 1); + joinSyncTicks.put(uuid, next); } } } } /** - * Selama beberapa detik pertama setelah login, gamemode dikoreksi berulang. - * Ini sengaja dibuat sebagai stabilization window karena vanilla player-data - * maupun mod lain dapat menerapkan state player sedikit lebih lambat dari event JOIN. - * Setelah window selesai, player tidak terus-menerus dipaksa kecuali berpindah dimension. + * Menjadwalkan koreksi gamemode singkat sesudah JOIN. Initial world placement + * tetap immediate; hanya gamemode yang dikoreksi pada +1, +5, dan +20 tick. */ public void beginJoinEnforcement(UUID uuid) { - joinEnforcementTicks.put(uuid, JOIN_ENFORCEMENT_DURATION_TICKS); + joinSyncTicks.put(uuid, 0); + } + + private void applyJoinSyncStage(ServerPlayer player, String dimension, int tick) { + String configuredMode = configured.get(normalizeDimension(dimension)); + if (configuredMode == null) return; + + GameType before = player.gameMode.getGameModeForPlayer(); + applyConfiguredMode(player, dimension, false); + GameType after = player.gameMode.getGameModeForPlayer(); + + LobbyLog.state( + "JOIN GAMEMODE SYNC", + "Player: " + player.getName().getString(), + "Dimension: " + dimension, + "Expected: " + configuredMode, + "Before: " + gameTypeName(before), + "After: " + gameTypeName(after), + "Stage: +" + tick + " tick" + (tick == JOIN_SYNC_FINAL_TICK ? " / final" : "") + ); + } + + private static String gameTypeName(GameType type) { + if (type == null) return "unknown"; + if (type == GameType.SURVIVAL) return "survival"; + if (type == GameType.CREATIVE) return "creative"; + if (type == GameType.ADVENTURE) return "adventure"; + if (type == GameType.SPECTATOR) return "spectator"; + return type.toString().toLowerCase(); } /** @@ -126,7 +154,7 @@ public final class DimensionGameModeManager { public void onDisconnect(UUID uuid) { lastDimension.remove(uuid); - joinEnforcementTicks.remove(uuid); + joinSyncTicks.remove(uuid); } public String get(String dimension) { diff --git a/src/main/java/id/dartokloning/lobbysystems/LobbySystemsMod.java b/src/main/java/id/dartokloning/lobbysystems/LobbySystemsMod.java index fc7b128..06cd470 100644 --- a/src/main/java/id/dartokloning/lobbysystems/LobbySystemsMod.java +++ b/src/main/java/id/dartokloning/lobbysystems/LobbySystemsMod.java @@ -25,7 +25,6 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.Level; import net.minecraft.world.level.GameType; import net.minecraft.world.level.gamerules.GameRule; -import id.dartokloning.lobbysystems.mixin.ServerPlayerGameModeAccessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -975,17 +974,11 @@ public final class LobbySystemsMod implements ModInitializer { player.gameMode.setLevel(target); player.snapTo(c.lobbyX, c.lobbyY, c.lobbyZ, c.lobbyYaw, c.lobbyPitch); - if (c.manageDimensionGamemode) { - String configuredMode = DIMENSION_GAMEMODES.get(c.lobbyDimension); - GameType gameType = DimensionGameModeManager.parseMode(configuredMode); - if (gameType != null && player.gameMode.getGameModeForPlayer() != gameType) { - GameType previousMode = player.gameMode.getGameModeForPlayer(); - ServerPlayerGameModeAccessor accessor = (ServerPlayerGameModeAccessor) player.gameMode; - accessor.lobbySystems$setPreviousGameModeForPlayer(previousMode); - accessor.lobbySystems$setGameModeForPlayer(gameType); - gameType.updatePlayerAbilities(player.getAbilities()); - } - } + // Jangan mengubah gamemode internal pada fase pre-login. Vanilla masih + // memulihkan gamemode tersimpan selama lifecycle login. Jika kita mengubah + // field internal di sini, server dapat menganggap mode sudah benar sehingga + // ServerPlayer#setGameMode() sesudah JOIN tidak mengirim update ke client. + // Gamemode Lobby disinkronkan setelah JOIN oleh DimensionGameModeManager. LobbyLog.state( "PLAYER PRE-LOGIN PLACEMENT", diff --git a/src/main/java/id/dartokloning/lobbysystems/mixin/ServerPlayerGameModeAccessor.java b/src/main/java/id/dartokloning/lobbysystems/mixin/ServerPlayerGameModeAccessor.java deleted file mode 100644 index d8d2597..0000000 --- a/src/main/java/id/dartokloning/lobbysystems/mixin/ServerPlayerGameModeAccessor.java +++ /dev/null @@ -1,20 +0,0 @@ -package id.dartokloning.lobbysystems.mixin; - -import net.minecraft.server.level.ServerPlayerGameMode; -import net.minecraft.world.level.GameType; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -/** - * Akses internal gamemode hanya untuk fase pre-login, sebelum connection player siap. - * Kita tidak memanggil ServerPlayer#setGameMode di titik ini karena method tersebut - * dapat mencoba mengirim update network terlalu dini. - */ -@Mixin(ServerPlayerGameMode.class) -public interface ServerPlayerGameModeAccessor { - @Accessor("gameModeForPlayer") - void lobbySystems$setGameModeForPlayer(GameType gameType); - - @Accessor("previousGameModeForPlayer") - void lobbySystems$setPreviousGameModeForPlayer(GameType gameType); -} diff --git a/src/main/resources/lobby-systems.mixins.json b/src/main/resources/lobby-systems.mixins.json index 6cffd1e..e71987b 100644 --- a/src/main/resources/lobby-systems.mixins.json +++ b/src/main/resources/lobby-systems.mixins.json @@ -4,8 +4,7 @@ "compatibilityLevel": "JAVA_25", "mixins": [ "ServerLevelGameRulesMixin", - "PlayerListMixin", - "ServerPlayerGameModeAccessor" + "PlayerListMixin" ], "injectors": { "defaultRequire": 1