Skip to content

Cooperative pause

When a host pauses, the engine freezes vanilla ticking and locks players in place. It cannot stop your mod’s own round loop. Your tick handler is your code, running on your schedule, and the engine has no way to reach into it.

So unless you gate it, a paused match still counts down. The host pauses to scrub back twenty seconds, and while they are deciding, the round timer runs out and the game ends underneath them.

static void tick(MinecraftServer server, MyLobby lobby) {
if (RewindRecorder.isFrozen(lobby.level())) return; // paused or scrubbing
...
}

That is the whole fix. isFrozen covers both states that should stop your logic: paused, and mid-scrub.

If your game has more to do than stop, for example suspending a background task or muting an ambient loop, use the events instead:

GamePausedEvent.EVENT.register((level, recording) -> myScheduler.suspend());
GameResumedEvent.EVENT.register((level, recording) -> myScheduler.resume());

Worth being precise about, because the two get conflated and the failure mode is subtle.

Gating on isFrozen stops your logic advancing while the world is frozen. It does nothing to make your state travel backwards when the host actually scrubs. A game with the gate and no snapshot slice pauses correctly and then, on commit, keeps playing from a scoreboard that belongs to a moment the world no longer remembers.

Most games want both. The gate stops time; the slice rewinds it.

isFrozen is a map lookup against the level, and RewindRecorder.anyFrozen() is a single volatile read if you want a cheaper early-out for a hot path shared across levels. Neither is worth caching or avoiding; call it per tick.