diff --git a/README.md b/README.md index 2169db0..0ad7ccf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Lobby Systems v1.8.4 — Fabric 26.2 +# Lobby Systems v1.8.5 — Fabric 26.2 Server-side lobby framework untuk Minecraft Fabric 26.2. Lobby Systems menangani lobby placement, portal antar-dimensi, gamemode/gamerule per dimensi, Starter Kit, penyimpanan posisi player, dan hybrid authentication untuk premium + local/cracked player. @@ -100,12 +100,14 @@ Default: "requireLoginEveryJoin": false ``` -Semantik: +Semantik `premiumAutoLogin` dan `requireLoginEveryJoin`: -- `false`: premium player cukup dengan Mojang authentication dan auto-login; local/cracked player tetap wajib password Lobby Systems pada setiap connection. -- `true`: premium player tetap diverifikasi Mojang, tetapi juga wajib mempunyai dan memasukkan password Lobby Systems setiap connection. Local/cracked player tetap wajib password seperti biasa. +- `premiumAutoLogin=true` + `requireLoginEveryJoin=false`: premium player cukup dengan Mojang authentication dan auto-login. +- `premiumAutoLogin=false`: premium player tetap diverifikasi Mojang, tetapi wajib membuat/memasukkan password Lobby Systems. +- `requireLoginEveryJoin=true`: premium player juga wajib password Lobby Systems setiap connection, terlepas dari auto-login policy. +- Local/cracked player selalu memakai password Lobby Systems setiap connection. -Jadi `true` merupakan lapisan password tambahan untuk premium account, bukan pengganti Mojang authentication. +Password Lobby Systems untuk premium adalah lapisan tambahan; Mojang authentication tetap dipertahankan. ### Server Rules @@ -115,6 +117,8 @@ Rules dipisahkan dari Auth config dan dibuat otomatis sebagai `config/lobby/auth { "enabled": true, "version": 1, + "timeoutSeconds": 60, + "timeoutMessage": "You did not accept the server rules within {seconds} seconds. Please reconnect and try again.", "title": "Server Rules", "introduction": "Please read and accept our rules:", "rules": [ @@ -223,7 +227,7 @@ Console menampilkan `RELOAD START` dan `RELOAD COMPLETE`. Player admin yang menj - Memperbaiki crash `ConcurrentModificationException` ketika authentication timeout men-disconnect player dari dalam server tick. Auth sekarang mengiterasi snapshot player list, bukan live `ArrayList`. - Initial dialog ditunda singkat sampai player benar-benar tersedia sebagai target command; ini menghilangkan kegagalan awal `No player was found`. - Dialog tidak lagi dibuka ulang setiap `reminderIntervalSeconds`, karena itu dapat menghapus password yang sedang diketik. -- Countdown sekarang memakai dua fase independen melalui action bar: Rules memakai `rules.json -> timeoutSeconds`, sedangkan Register/Login memakai `auth/config.json -> authenticationTimeoutSeconds`. Timer Register/Login dimulai ulang dari penuh setelah Rules diterima. +- Rules dan Register/Login memakai timeout independen: Rules memakai `rules.json -> timeoutSeconds`, sedangkan Register/Login memakai `auth/config.json -> authenticationTimeoutSeconds`. Timer Register/Login dimulai ulang dari penuh setelah Rules diterima. - Rules dipindahkan ke `config/lobby/auth/rules.json` dan Rules dialog dibangun dari file tersebut. - `rulesEnabled` dan `rulesVersion` lama dipindahkan menjadi `enabled` dan `version` di `rules.json`. - `/ls reload` juga me-reload `rules.json` dan mengevaluasi ulang acceptance/session player online. @@ -240,8 +244,7 @@ All Rights Reserved. Source dan asset Lobby Systems tidak boleh didistribusikan ## Catatan v1.8.3 - Memperbaiki compile error Minecraft 26.2 karena `ServerPlayer.displayClientMessage(Component, boolean)` sudah tidak tersedia pada mapping/API target ini. -- Countdown authentication sekarang dikirim langsung dengan vanilla `ClientboundSetActionBarTextPacket`, sehingga tetap tampil sebagai action bar tanpa bergantung pada method player yang telah dihapus. -- Warna countdown menggunakan `ChatFormatting`/component styling, bukan legacy section-code di payload action bar. +- Implementasi action-bar countdown yang diperkenalkan sementara pada v1.8.3 kemudian dihentikan pada v1.8.5 karena Dialog bersifat modal dan countdown HUD tidak memberi UX yang konsisten. - Seluruh perubahan v1.8.2 (safe timeout iteration, delayed initial dialog, `rules.json`, dan global `/ls reload`) tetap dipertahankan. @@ -251,6 +254,17 @@ All Rights Reserved. Source dan asset Lobby Systems tidak boleh didistribusikan - `rules.json` memiliki `timeoutSeconds` (default `60`) untuk batas waktu menerima Rules. - Register/Login tetap memakai `authenticationTimeoutSeconds` (default `120`). - Setelah Rules diterima, timer Rules dihentikan dan timer Register/Login dimulai dari awal. -- Countdown action bar memakai label sesuai fase: `Accept the rules within`, `Register within`, atau `Login within`. +- Tidak ada countdown visual pada HUD/action bar; timeout berjalan di server dan hanya muncul sebagai alasan disconnect jika waktunya habis. - Timeout Rules dan timeout authentication memiliki pesan disconnect yang berbeda. - `/ls reload` tetap memuat ulang seluruh konfigurasi Lobby Systems, termasuk `auth/rules.json` dan `auth/config.json`. + + +## v1.8.5 — Auth timeout messages & premium login policy + +- Menghapus countdown visual dari action bar. Dialog Rules/Register/Login tetap bersih tanpa HUD timer yang tertutup layer Dialog. +- Two-stage timeout tetap dipertahankan secara server-side: Rules memakai `rules.json -> timeoutSeconds`, sedangkan Register/Login memakai `auth/config.json -> authenticationTimeoutSeconds`. +- `rules.json` sekarang memiliki `timeoutMessage`. Placeholder `{seconds}` otomatis diganti dengan nilai `timeoutSeconds`. +- `auth/config.json` sekarang memiliki `registrationTimeoutMessage`, `loginTimeoutMessage`, dan fallback `authenticationTimeoutMessage`; placeholder `{seconds}` memakai `authenticationTimeoutSeconds`. +- `premiumAutoLogin=false` sekarang benar-benar mematikan auto-login Lobby Systems untuk premium player. Mojang authentication tetap berjalan, kemudian premium player diarahkan ke Register/Login Dialog dan membutuhkan password Lobby Systems. +- `premiumAutoLogin=true` + `requireLoginEveryJoin=false` tetap merupakan mode premium auto-login. `requireLoginEveryJoin=true` tetap memaksa password Lobby Systems untuk premium player setiap join. +- `/ls reload` memuat ulang policy dan pesan timeout Auth/Rules tanpa restart server. diff --git a/gradle.properties b/gradle.properties index abd5122..f5e9134 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.8.4 +mod_version=1.8.5 maven_group=id.dartokloning archives_base_name=lobby-systems diff --git a/src/main/java/id/dartokloning/lobbysystems/AuthConfig.java b/src/main/java/id/dartokloning/lobbysystems/AuthConfig.java index 7e72f8e..1d6f843 100644 --- a/src/main/java/id/dartokloning/lobbysystems/AuthConfig.java +++ b/src/main/java/id/dartokloning/lobbysystems/AuthConfig.java @@ -2,9 +2,18 @@ package id.dartokloning.lobbysystems; public final class AuthConfig { public boolean enabled = true; + /** + * true: a Mojang-verified premium account is authenticated automatically unless + * requireLoginEveryJoin is enabled. + * false: premium accounts still use Mojang verification, but must also register + * and use a Lobby Systems password. + */ public boolean premiumAutoLogin = true; public boolean protectPremiumUsernames = true; public int authenticationTimeoutSeconds = 120; + public String registrationTimeoutMessage = "Registration timed out after {seconds} seconds. Please reconnect and create your password."; + public String loginTimeoutMessage = "Login timed out after {seconds} seconds. Please reconnect and enter your password."; + public String authenticationTimeoutMessage = "Authentication timed out after {seconds} seconds. Please reconnect and try again."; public int maxLoginAttempts = 3; public int minimumPasswordLength = 6; public int pbkdf2Iterations = 210000; diff --git a/src/main/java/id/dartokloning/lobbysystems/AuthManager.java b/src/main/java/id/dartokloning/lobbysystems/AuthManager.java index 9506951..f36e9ca 100644 --- a/src/main/java/id/dartokloning/lobbysystems/AuthManager.java +++ b/src/main/java/id/dartokloning/lobbysystems/AuthManager.java @@ -4,7 +4,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; @@ -114,14 +113,14 @@ public final class AuthManager { } // false means premium accounts are satisfied by Mojang authentication. - if (s.premium && !config.requireLoginEveryJoin) { + if (s.premium && config.premiumAutoLogin && !config.requireLoginEveryJoin) { authenticate(player); continue; } // true makes the Lobby Systems password an additional layer for premium // accounts, and local accounts always require it on each connection. - if (s.premium && config.requireLoginEveryJoin && s.authenticated) { + if (s.premium && (!config.premiumAutoLogin || config.requireLoginEveryJoin) && s.authenticated) { s.authenticated = false; s.joinedAt = System.currentTimeMillis(); s.attempts = 0; @@ -129,7 +128,6 @@ public final class AuthManager { if (!s.authenticated) { s.stage = isRegistered(player.getName().getString()) ? Stage.LOGIN : Stage.REGISTER; s.joinedAt = System.currentTimeMillis(); - s.lastCountdownSecond = -1L; showCurrentDialog(player, s); } } @@ -137,7 +135,7 @@ public final class AuthManager { /** Called from the login mixin before the player enters play state. */ public LoginRoute resolveLoginRoute(String username) { - if (!config.enabled || !config.premiumAutoLogin) return LoginRoute.PREMIUM; + if (!config.enabled) return LoginRoute.PREMIUM; PremiumResult result = premiumStatus(username); return switch (result) { case PREMIUM -> LoginRoute.PREMIUM; @@ -227,13 +225,18 @@ public final class AuthManager { for (ServerPlayer player : List.copyOf(server.getPlayerList().getPlayers())) { Session s = sessions.get(player.getUUID()); if (s == null || s.authenticated) continue; - int timeoutSeconds = s.stage == Stage.RULES ? Math.max(1, rules.timeoutSeconds) : Math.max(1, config.authenticationTimeoutSeconds); + int timeoutSeconds = s.stage == Stage.RULES + ? Math.max(1, rules.timeoutSeconds) + : Math.max(1, config.authenticationTimeoutSeconds); long remainingMillis = timeoutSeconds * 1000L - (now - s.joinedAt); if (remainingMillis <= 0L) { - String timeoutMessage = s.stage == Stage.RULES - ? "You did not accept the server rules in time. Please reconnect and try again." - : "Authentication timed out. Please reconnect and try again."; - player.connection.disconnect(Component.literal(timeoutMessage)); + String template = switch (s.stage) { + case RULES -> rules.timeoutMessage; + case REGISTER -> config.registrationTimeoutMessage; + case LOGIN -> config.loginTimeoutMessage; + default -> config.authenticationTimeoutMessage; + }; + player.connection.disconnect(Component.literal(formatTimeoutMessage(template, timeoutSeconds))); continue; } // Keep the player at the safe Lobby authentication position. World safety is @@ -249,24 +252,6 @@ public final class AuthManager { showCurrentDialog(player, s); } - // A live countdown is shown in the action bar. Do not reopen the dialog every - // few seconds: reopening destroys text currently typed into password fields. - long remainingSeconds = Math.max(1L, (remainingMillis + 999L) / 1000L); - if (remainingSeconds != s.lastCountdownSecond) { - s.lastCountdownSecond = remainingSeconds; - String countdownLabel = switch (s.stage) { - case RULES -> "Accept the rules within: "; - case REGISTER -> "Register within: "; - case LOGIN -> "Login within: "; - default -> "Time remaining: "; - }; - long minutes = remainingSeconds / 60L; - long seconds = remainingSeconds % 60L; - player.connection.send(new ClientboundSetActionBarTextPacket( - Component.literal(countdownLabel + String.format(Locale.ROOT, "%02d:%02d", minutes, seconds)) - .withStyle(net.minecraft.ChatFormatting.YELLOW) - )); - } } } @@ -293,7 +278,6 @@ public final class AuthManager { // are accepted, start a fresh timer for REGISTER/LOGIN instead of carrying over // time spent reading the rules. s.joinedAt = System.currentTimeMillis(); - s.lastCountdownSecond = -1L; continueAfterRules(player); return 1; } @@ -303,7 +287,7 @@ public final class AuthManager { Session s = sessions.get(player.getUUID()); if (s == null || s.authenticated) return failAndReopen(player, "§eYou are already authenticated.", DIALOG_REGISTER); if (s.stage == Stage.RULES) return failAndReopen(player, "§cPlease accept the server rules first.", DIALOG_RULES); - if (s.premium && !config.requireLoginEveryJoin) return fail(player, "§ePremium accounts use Mojang authentication and do not need a local password."); + if (s.premium && config.premiumAutoLogin && !config.requireLoginEveryJoin) return fail(player, "§ePremium accounts use Mojang authentication and do not need a local password."); String name = player.getName().getString(); if (!s.premium) { @@ -370,7 +354,7 @@ public final class AuthManager { Session s = sessions.get(player.getUUID()); if (s == null) return false; - if (s.premium && !config.requireLoginEveryJoin) { + if (s.premium && config.premiumAutoLogin && !config.requireLoginEveryJoin) { authenticate(player); player.sendSystemMessage(Component.literal("§aLobby Systems: Premium account verified. Auto-login successful.")); return false; @@ -561,6 +545,13 @@ public final class AuthManager { } } + private static String formatTimeoutMessage(String template, int seconds) { + String value = (template == null || template.isBlank()) + ? "Authentication timed out after {seconds} seconds. Please reconnect and try again." + : template; + return value.replace("{seconds}", Integer.toString(seconds)); + } + private static String key(String username) { return username.toLowerCase(Locale.ROOT); } private void saveConfig() throws Exception { @@ -619,7 +610,6 @@ public final class AuthManager { long lastReminder; // retained for binary/source migration; no longer reopens dialogs boolean dialogPending; long dialogReadyAt; - long lastCountdownSecond = -1L; final double x, y, z; final float yaw, pitch; Stage stage = Stage.RULES; diff --git a/src/main/java/id/dartokloning/lobbysystems/RulesConfig.java b/src/main/java/id/dartokloning/lobbysystems/RulesConfig.java index 20bb1a8..b9d15b4 100644 --- a/src/main/java/id/dartokloning/lobbysystems/RulesConfig.java +++ b/src/main/java/id/dartokloning/lobbysystems/RulesConfig.java @@ -7,6 +7,7 @@ public final class RulesConfig { public boolean enabled = true; public int version = 1; public int timeoutSeconds = 60; + public String timeoutMessage = "You did not accept the server rules within {seconds} seconds. Please reconnect and try again."; public String title = "Server Rules"; public String introduction = "Please read and accept our rules:"; public List rules = new ArrayList<>(List.of(