From 31bfd9a7056b705d605669d7b75c0a3fbc7f2896 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 29 Aug 2026 04:06:15 +0000 Subject: [PATCH] =?UTF-8?q?fix(replay):=20=E8=BF=9B=E5=85=A5=E5=9B=9E?= =?UTF-8?q?=E6=94=BE=E5=89=8D=E5=85=88=E9=80=80=E5=87=BA=E5=BD=93=E5=89=8D?= =?UTF-8?q?=E4=B8=96=E7=95=8C=E6=88=96=E6=9C=8D=E5=8A=A1=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit loadWorld(replayWorld) 不会断开原连接,旁观飞行仍会发到真实服务器。 进入回放时先按 Disconnect 路径退出现世/服务器,再加载回放世界。 Co-authored-by: Gao Yu --- .../top/fpsmaster/replay/ReplayPlayer.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/java/top/fpsmaster/replay/ReplayPlayer.java b/src/main/java/top/fpsmaster/replay/ReplayPlayer.java index dee177db..a65f37da 100644 --- a/src/main/java/top/fpsmaster/replay/ReplayPlayer.java +++ b/src/main/java/top/fpsmaster/replay/ReplayPlayer.java @@ -289,11 +289,18 @@ public synchronized void start(File replay) { /** * Builds the world the recording will be poured into. * + *

A live singleplayer or multiplayer session is torn down first. + * {@code loadWorld(WorldClient)} does not disconnect: it leaves the old + * {@code NetworkManager} open, so spectator movement is still sent to the + * real server. + * *

This is what {@code handleJoinGame} does, minus its one Forge call: that resolves the * dimension through the connection's Netty channel, and playback has no channel. */ private void openWorld(ReplayFile.Header header) { Minecraft mc = Minecraft.getMinecraft(); + leaveCurrentSession(mc); + NetworkManager connection = new SilentConnection(); netHandler = new NetHandlerPlayClient(mc, null, connection, recorderProfile); connection.setNetHandler(netHandler); @@ -311,6 +318,29 @@ private void openWorld(ReplayFile.Header header) { mc.displayGuiScreen(null); } + /** + * Leaves the current singleplayer world or multiplayer server the way the in-game Disconnect + * button does, without flashing a menu in between. + * + *

{@code Minecraft.loadWorld} only closes the connection and shuts down the integrated + * server when the argument is {@code null}. Loading a replay world on top of a live one keeps + * the old handler and the live player entity, so flight in the replay is still sent upstream. + */ + private static void leaveCurrentSession(Minecraft mc) { + if (mc.theWorld == null && mc.thePlayer == null && mc.getNetHandler() == null) { + return; + } + ClientLogger.info("replay", "leaving current world/server before playback"); + if (mc.theWorld != null) { + try { + mc.theWorld.sendQuittingDisconnectingPacket(); + } catch (Exception failure) { + ClientLogger.warn("replay -> failed to send quit packet: " + failure.getMessage(), failure); + } + } + mc.loadWorld(null); + } + public synchronized void stop() { if (!active) { return;