fix gamemode player join

This commit is contained in:
Darto KLoning
2026-09-07 07:51:30 +07:00
parent dda9d76ec0
commit 964af6c856
6 changed files with 56 additions and 56 deletions
+4 -4
View File
@@ -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. **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 ```text
config/ config/
+1 -1
View File
@@ -5,6 +5,6 @@ minecraft_version=26.2
loader_version=0.19.3 loader_version=0.19.3
loom_version=1.17-SNAPSHOT loom_version=1.17-SNAPSHOT
fabric_api_version=0.156.0+26.2 fabric_api_version=0.156.0+26.2
mod_version=1.5.5 mod_version=1.5.6
maven_group=id.dartokloning maven_group=id.dartokloning
archives_base_name=lobby-systems archives_base_name=lobby-systems
@@ -30,10 +30,11 @@ public final class DimensionGameModeManager {
private final Map<String, String> configured = new LinkedHashMap<>(); private final Map<String, String> configured = new LinkedHashMap<>();
private final Map<UUID, String> lastDimension = new ConcurrentHashMap<>(); private final Map<UUID, String> lastDimension = new ConcurrentHashMap<>();
private final Map<UUID, Integer> joinEnforcementTicks = new ConcurrentHashMap<>(); private final Map<UUID, Integer> joinSyncTicks = new ConcurrentHashMap<>();
private static final int JOIN_ENFORCEMENT_DURATION_TICKS = 100; // 5 detik // Koreksi post-login pada +1, +5, dan +20 tick. JOIN sendiri melakukan
private static final int JOIN_ENFORCEMENT_INTERVAL_TICKS = 5; // 0.25 detik // apply immediate (stage +0) sebelum antrean ini dimulai.
private static final int JOIN_SYNC_FINAL_TICK = 20;
public boolean load() { public boolean load() {
if (!Files.exists(PATH)) { if (!Files.exists(PATH)) {
@@ -88,28 +89,55 @@ public final class DimensionGameModeManager {
applyConfiguredMode(player, dimension, true); applyConfiguredMode(player, dimension, true);
} }
Integer remaining = joinEnforcementTicks.get(uuid); Integer elapsed = joinSyncTicks.get(uuid);
if (remaining != null) { if (elapsed != null) {
if (remaining <= 0) { int next = elapsed + 1;
joinEnforcementTicks.remove(uuid); if (next == 1 || next == 5 || next == JOIN_SYNC_FINAL_TICK) {
} else { applyJoinSyncStage(player, dimension, next);
if (remaining % JOIN_ENFORCEMENT_INTERVAL_TICKS == 0) {
applyConfiguredMode(player, dimension, true);
} }
joinEnforcementTicks.put(uuid, remaining - 1); if (next >= JOIN_SYNC_FINAL_TICK) {
joinSyncTicks.remove(uuid);
} else {
joinSyncTicks.put(uuid, next);
} }
} }
} }
} }
/** /**
* Selama beberapa detik pertama setelah login, gamemode dikoreksi berulang. * Menjadwalkan koreksi gamemode singkat sesudah JOIN. Initial world placement
* Ini sengaja dibuat sebagai stabilization window karena vanilla player-data * tetap immediate; hanya gamemode yang dikoreksi pada +1, +5, dan +20 tick.
* maupun mod lain dapat menerapkan state player sedikit lebih lambat dari event JOIN.
* Setelah window selesai, player tidak terus-menerus dipaksa kecuali berpindah dimension.
*/ */
public void beginJoinEnforcement(UUID uuid) { 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) { public void onDisconnect(UUID uuid) {
lastDimension.remove(uuid); lastDimension.remove(uuid);
joinEnforcementTicks.remove(uuid); joinSyncTicks.remove(uuid);
} }
public String get(String dimension) { public String get(String dimension) {
@@ -25,7 +25,6 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.GameType; import net.minecraft.world.level.GameType;
import net.minecraft.world.level.gamerules.GameRule; import net.minecraft.world.level.gamerules.GameRule;
import id.dartokloning.lobbysystems.mixin.ServerPlayerGameModeAccessor;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -975,17 +974,11 @@ public final class LobbySystemsMod implements ModInitializer {
player.gameMode.setLevel(target); player.gameMode.setLevel(target);
player.snapTo(c.lobbyX, c.lobbyY, c.lobbyZ, c.lobbyYaw, c.lobbyPitch); player.snapTo(c.lobbyX, c.lobbyY, c.lobbyZ, c.lobbyYaw, c.lobbyPitch);
if (c.manageDimensionGamemode) { // Jangan mengubah gamemode internal pada fase pre-login. Vanilla masih
String configuredMode = DIMENSION_GAMEMODES.get(c.lobbyDimension); // memulihkan gamemode tersimpan selama lifecycle login. Jika kita mengubah
GameType gameType = DimensionGameModeManager.parseMode(configuredMode); // field internal di sini, server dapat menganggap mode sudah benar sehingga
if (gameType != null && player.gameMode.getGameModeForPlayer() != gameType) { // ServerPlayer#setGameMode() sesudah JOIN tidak mengirim update ke client.
GameType previousMode = player.gameMode.getGameModeForPlayer(); // Gamemode Lobby disinkronkan setelah JOIN oleh DimensionGameModeManager.
ServerPlayerGameModeAccessor accessor = (ServerPlayerGameModeAccessor) player.gameMode;
accessor.lobbySystems$setPreviousGameModeForPlayer(previousMode);
accessor.lobbySystems$setGameModeForPlayer(gameType);
gameType.updatePlayerAbilities(player.getAbilities());
}
}
LobbyLog.state( LobbyLog.state(
"PLAYER PRE-LOGIN PLACEMENT", "PLAYER PRE-LOGIN PLACEMENT",
@@ -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);
}
+1 -2
View File
@@ -4,8 +4,7 @@
"compatibilityLevel": "JAVA_25", "compatibilityLevel": "JAVA_25",
"mixins": [ "mixins": [
"ServerLevelGameRulesMixin", "ServerLevelGameRulesMixin",
"PlayerListMixin", "PlayerListMixin"
"ServerPlayerGameModeAccessor"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1