Skip to content

Safe teleport

Calling ServerPlayer.teleport directly across dimensions from inside a tick handler hits at least two known races: a stuck removed flag from a prior transition, and a spectator-to-non-spectator desync when gamemode changes happen in the same tick as the move. SafeTeleport queues the move to the next tick, clears the removed flag via an accessor mixin, and exposes a post-arrival callback so you can change gamemode or wipe inventory after the player has landed.

  • Any cross-dimension move from a tick handler, command, or event.
  • When you need to run something (gamemode swap, heal, hand items) only after the player arrives.
  • Inside lobby / phase transitions where the player may have been a spectator a moment ago.
package me.zlex.conduit.teleport;
public final class SafeTeleport {
public static void teleport(ServerPlayer player, ServerLevel target, Vec3 pos);
public static void teleport(ServerPlayer player, ServerLevel target, Vec3 pos,
float yaw, float pitch);
public static void teleport(ServerPlayer player, ServerLevel target, Vec3 pos,
float yaw, float pitch,
@Nullable Consumer<ServerPlayer> afterTeleport);
}

The teleport is enqueued and dispatched on END_SERVER_TICK. The optional afterTeleport runs in the destination dimension after the player is fully relocated.

import me.zlex.conduit.teleport.SafeTeleport;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.phys.Vec3;
public final class GamePhases {
/** Player won the round. Move them to the celebration platform and put
* them in adventure so they can't break the trophy block. */
public static void teleportWinnerToPlatform(ServerPlayer winner, ServerLevel celebration) {
Vec3 podium = new Vec3(0.5, 65, 0.5);
SafeTeleport.teleport(winner, celebration, podium, 0f, 0f, p -> {
p.setGameMode(GameType.ADVENTURE);
p.heal(p.getMaxHealth());
p.getFoodData().setFoodLevel(20);
});
}
}
  • Never change gamemode in the same tick as a cross-dim teleport. Use afterTeleport.
  • MinecraftServer.execute(Runnable) runs synchronously when called from the server thread — that’s why this class keeps its own queue.
  • The post-arrival hook runs the same tick as the teleport, after relocation. Anything you defer further than that, schedule yourself.