Fix player join
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# Lobby Systems v1.5.3 — Fabric 26.2
|
||||
# Lobby Systems v1.5.5 — Fabric 26.2
|
||||
|
||||
Server-side lobby routing untuk Minecraft 26.2/Fabric. v1.5.3 memastikan dimension gamemode dipaksa segera setelah teleport, termasuk join/reconnect ke Lobby, dan menambahkan mode Git-only pada script compile bila JAR versi yang sama sudah pernah dibuild.
|
||||
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.
|
||||
|
||||
**v1.5.3 bugfix/workflow:** gamemode Lobby kini langsung diterapkan setelah join teleport sehingga gamemode lama dari player data tidak tertinggal. `compile.bat` / `compile.sh` juga mendeteksi JAR versi yang sama dan menawarkan Compile ulang atau Git-only tanpa build ulang.
|
||||
**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.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.
|
||||
|
||||
@@ -43,7 +43,7 @@ chmod +x compile.sh release.sh
|
||||
```text
|
||||
============================================
|
||||
Lobby Systems - Fabric 26.2 Build
|
||||
Version: 1.5.3
|
||||
Version: 1.5.5
|
||||
Namespace: id.dartokloning.lobbysystems
|
||||
Mod ID: dartokloning
|
||||
============================================
|
||||
@@ -52,7 +52,7 @@ chmod +x compile.sh release.sh
|
||||
Output utama:
|
||||
|
||||
```text
|
||||
build/libs/lobby-systems-1.5.3.jar
|
||||
build/libs/lobby-systems-1.5.5.jar
|
||||
```
|
||||
|
||||
`*-sources.jar` hanya source archive dan tidak perlu dipasang ke server.
|
||||
@@ -83,14 +83,14 @@ State penting Lobby Systems memakai separator agar mudah terlihat di server cons
|
||||
```text
|
||||
============================================
|
||||
Lobby Systems - READY
|
||||
Version: 1.5.3
|
||||
Version: 1.5.5
|
||||
Minecraft: 26.2
|
||||
Config directory: config/lobby
|
||||
WorldEdit: available
|
||||
============================================
|
||||
```
|
||||
|
||||
## Struktur konfigurasi v1.5.3
|
||||
## Struktur konfigurasi v1.5.5
|
||||
|
||||
```text
|
||||
config/
|
||||
@@ -401,9 +401,13 @@ Setelah portal aktif, `portalCooldownTicks` mencegah portal terpicu berulang saa
|
||||
## Flow server
|
||||
|
||||
```text
|
||||
JOIN
|
||||
PLAYER DATA LOADED
|
||||
↓
|
||||
Lobby fixed spawn
|
||||
Lobby Systems pre-login placement
|
||||
↓
|
||||
Initial world sent to client = Lobby
|
||||
↓
|
||||
Lobby fixed spawn + configured gamemode
|
||||
↓
|
||||
Player berjalan ke portal
|
||||
↓
|
||||
|
||||
+1
-1
@@ -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.3
|
||||
mod_version=1.5.5
|
||||
maven_group=id.dartokloning
|
||||
archives_base_name=lobby-systems
|
||||
|
||||
@@ -23,7 +23,9 @@ import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
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;
|
||||
|
||||
@@ -131,24 +133,25 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
|
||||
ServerPlayConnectionEvents.JOIN.register((listener, sender, server) -> {
|
||||
ServerPlayer player = listener.getPlayer();
|
||||
server.execute(() -> {
|
||||
POSITIONS.captureCurrent(player);
|
||||
TELEPORTS.scheduleJoinToLobby(player);
|
||||
|
||||
// Initial dimension/position sudah dipindahkan ke Lobby oleh PlayerListMixin
|
||||
// sebelum paket login/world awal dikirim. Di titik JOIN ini kita hanya
|
||||
// sinkronkan state runtime dan memberi enforcement singkat sebagai proteksi
|
||||
// terhadap mod lain yang mengubah gamemode tepat setelah login.
|
||||
if (CONFIG.get().manageDimensionGamemode) {
|
||||
// Terapkan segera pada state player yang sudah dimuat, lalu pertahankan
|
||||
// stabilization window agar state login yang diterapkan terlambat oleh
|
||||
// vanilla/mod lain tidak mengalahkan konfigurasi Lobby Systems.
|
||||
DIMENSION_GAMEMODES.applyForPlayer(player, true);
|
||||
DIMENSION_GAMEMODES.beginJoinEnforcement(player.getUUID());
|
||||
}
|
||||
SESSIONS.onJoin(player.getUUID(), lobbyKey());
|
||||
|
||||
LobbyLog.state(
|
||||
"PLAYER JOIN",
|
||||
"Player: " + player.getName().getString(),
|
||||
"UUID: " + player.getUUID(),
|
||||
"Lobby teleport: scheduled"
|
||||
"Initial dimension: " + player.level().dimension().identifier(),
|
||||
"Lobby placement: pre-login / immediate"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
ServerPlayConnectionEvents.DISCONNECT.register((listener, server) -> {
|
||||
ServerPlayer player = listener.getPlayer();
|
||||
@@ -166,11 +169,7 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
});
|
||||
|
||||
ServerTickEvents.END_SERVER_TICK.register(server -> {
|
||||
TELEPORTS.tick(server, (player, minecraftServer) -> {
|
||||
if (teleportToLobby(player, minecraftServer, false)) {
|
||||
SESSIONS.onJoin(player.getUUID(), lobbyKey());
|
||||
}
|
||||
});
|
||||
TELEPORTS.tick();
|
||||
|
||||
if (CONFIG.get().manageDimensionGamemode) {
|
||||
DIMENSION_GAMEMODES.tick(server);
|
||||
@@ -940,6 +939,68 @@ public final class LobbySystemsMod implements ModInitializer {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dipanggil dari PlayerListMixin pada HEAD placeNewPlayer, yaitu setelah player data
|
||||
* sudah dimuat tetapi sebelum PlayerList mengirim paket login/world awal ke client.
|
||||
* Ini membuat Lobby menjadi initial world yang benar-benar dilihat client, bukan
|
||||
* teleport tertunda setelah player sempat muncul di posisi/dimension terakhir.
|
||||
*/
|
||||
public static void preparePlayerForInitialLogin(ServerPlayer player) {
|
||||
MinecraftServer server = player.level().getServer();
|
||||
|
||||
final ResourceKey<Level> key;
|
||||
try {
|
||||
key = lobbyKey();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Cannot prepare login lobby: invalid lobbyDimension {}", CONFIG.get().lobbyDimension, e);
|
||||
return;
|
||||
}
|
||||
|
||||
ServerLevel target = server.getLevel(key);
|
||||
if (target == null) {
|
||||
LOGGER.error("Cannot prepare login lobby: dimension {} is not loaded/found.", CONFIG.get().lobbyDimension);
|
||||
return;
|
||||
}
|
||||
|
||||
// Simpan posisi terakhir gameplay SEBELUM level player diganti ke Lobby.
|
||||
POSITIONS.captureCurrent(player);
|
||||
|
||||
LobbyConfig c = CONFIG.get();
|
||||
ResourceKey<Level> previousDimension = player.level().dimension();
|
||||
|
||||
// Tidak memakai ServerPlayer#teleport di fase ini. Player belum ditempatkan ke
|
||||
// world aktif; kita langsung menentukan level + posisi yang akan dipakai oleh
|
||||
// PlayerList ketika membangun ClientboundLoginPacket dan initial world state.
|
||||
player.setServerLevel(target);
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
LobbyLog.state(
|
||||
"PLAYER PRE-LOGIN PLACEMENT",
|
||||
"Player: " + player.getName().getString(),
|
||||
"Loaded from: " + previousDimension.identifier(),
|
||||
"Initial world sent to client: " + target.dimension().identifier(),
|
||||
"Position: [" + String.format("%.2f", c.lobbyX) + ", "
|
||||
+ String.format("%.2f", c.lobbyY) + ", "
|
||||
+ String.format("%.2f", c.lobbyZ) + "]",
|
||||
"Gamemode: " + (c.manageDimensionGamemode
|
||||
? String.valueOf(DIMENSION_GAMEMODES.get(c.lobbyDimension))
|
||||
: "unmanaged")
|
||||
);
|
||||
}
|
||||
|
||||
static ServerLevel findLevelForManager(MinecraftServer server, String dimensionId) {
|
||||
return findLevel(server, dimensionId);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package id.dartokloning.lobbysystems;
|
||||
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.Relative;
|
||||
@@ -17,39 +16,22 @@ import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Menangani teleport lintas dimensi melalui TeleportTransition vanilla.
|
||||
* Juga memberi lock singkat agar request teleport tidak bertumpuk ketika
|
||||
* client masih menyelesaikan pergantian dimension/chunk.
|
||||
* Login player tidak memakai coordinator ini: initial placement ke Lobby dilakukan
|
||||
* sebelum PlayerList mengirim world awal ke client.
|
||||
*/
|
||||
public final class TeleportCoordinator {
|
||||
private static final int TELEPORT_LOCK_TICKS = 40; // 2 detik
|
||||
private static final int JOIN_DELAY_TICKS = 20; // 1 detik setelah JOIN
|
||||
|
||||
private final Map<UUID, Long> lockedUntilTick = new HashMap<>();
|
||||
private final Map<UUID, PendingLobbyTeleport> pendingJoinTeleports = new HashMap<>();
|
||||
private long currentTick;
|
||||
|
||||
public void scheduleJoinToLobby(ServerPlayer player) {
|
||||
pendingJoinTeleports.put(player.getUUID(), new PendingLobbyTeleport(currentTick + JOIN_DELAY_TICKS));
|
||||
}
|
||||
|
||||
public void onDisconnect(UUID uuid) {
|
||||
lockedUntilTick.remove(uuid);
|
||||
pendingJoinTeleports.remove(uuid);
|
||||
}
|
||||
|
||||
public void tick(MinecraftServer server, LobbyTeleportHandler lobbyHandler) {
|
||||
public void tick() {
|
||||
currentTick++;
|
||||
lockedUntilTick.entrySet().removeIf(entry -> entry.getValue() <= currentTick);
|
||||
|
||||
pendingJoinTeleports.entrySet().removeIf(entry -> {
|
||||
if (entry.getValue().executeAtTick > currentTick) return false;
|
||||
|
||||
ServerPlayer player = server.getPlayerList().getPlayer(entry.getKey());
|
||||
if (player != null) {
|
||||
lobbyHandler.teleport(player, server);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isLocked(ServerPlayer player) {
|
||||
@@ -66,7 +48,6 @@ public final class TeleportCoordinator {
|
||||
ResourceKey<Level> from = player.level().dimension();
|
||||
ResourceKey<Level> to = target.dimension();
|
||||
|
||||
// Paksa destination chunk tersedia sebelum transition dikirim ke client.
|
||||
int chunkX = ((int) Math.floor(x)) >> 4;
|
||||
int chunkZ = ((int) Math.floor(z)) >> 4;
|
||||
target.getChunk(chunkX, chunkZ);
|
||||
@@ -108,11 +89,4 @@ public final class TeleportCoordinator {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface LobbyTeleportHandler {
|
||||
void teleport(ServerPlayer player, MinecraftServer server);
|
||||
}
|
||||
|
||||
private record PendingLobbyTeleport(long executeAtTick) {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package id.dartokloning.lobbysystems.mixin;
|
||||
|
||||
import id.dartokloning.lobbysystems.LobbySystemsMod;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.CommonListenerCookie;
|
||||
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.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Menempatkan player ke Lobby sebelum PlayerList mengirim paket login/world awal.
|
||||
* Dengan begitu client tidak sempat terlebih dahulu masuk ke dimension/posisi terakhir
|
||||
* kemudian baru menerima teleport tertunda.
|
||||
*/
|
||||
@Mixin(PlayerList.class)
|
||||
public abstract class PlayerListMixin {
|
||||
@Inject(method = "placeNewPlayer", at = @At("HEAD"))
|
||||
private void lobbySystems$prepareLobbyBeforeInitialPlacement(
|
||||
Connection connection,
|
||||
ServerPlayer player,
|
||||
CommonListenerCookie cookie,
|
||||
CallbackInfo ci
|
||||
) {
|
||||
LobbySystemsMod.preparePlayerForInitialLogin(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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);
|
||||
}
|
||||
@@ -3,7 +3,9 @@
|
||||
"package": "id.dartokloning.lobbysystems.mixin",
|
||||
"compatibilityLevel": "JAVA_25",
|
||||
"mixins": [
|
||||
"ServerLevelGameRulesMixin"
|
||||
"ServerLevelGameRulesMixin",
|
||||
"PlayerListMixin",
|
||||
"ServerPlayerGameModeAccessor"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
||||
Reference in New Issue
Block a user