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;