Lock dead players at death site; teleport to spawn when cooldown ends

Persist a death-position snapshot in player attachment data, move spectators back each tick if they drift, and on rejoin during an active wait. When the respawn timer completes (or skip command), teleport to bed or world spawn then restore survival. Uses ServerPlayer.teleportTo with empty RelativeMovement set.
This commit is contained in:
2026-04-10 02:03:37 +03:00
parent b9a7ccf4be
commit 2733fb7fe1
3 changed files with 128 additions and 7 deletions

View File

@@ -2,6 +2,9 @@ package net.respawnbackoff;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.attachment.v1.AttachmentRegistry;
@@ -11,8 +14,12 @@ import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.RelativeMovement;
import net.minecraft.world.level.GameType;
public class RespawnBackoffMod implements ModInitializer {
@@ -52,6 +59,7 @@ public class RespawnBackoffMod implements ModInitializer {
if (!player.isSpectator()) {
player.setGameMode(GameType.SPECTATOR);
}
data.deathLock().ifPresent(lock -> teleportToDeathLock(player, lock));
RespawnBackoffNetworking.sendCooldown(player, true, data.cooldownEndEpochMs());
} else {
RespawnBackoffNetworking.sendCooldown(player, false, 0L);
@@ -85,7 +93,13 @@ public class RespawnBackoffMod implements ModInitializer {
long pendingMs = waitMinutes * 60_000L;
int nextExponent = Math.min(exponent + 1, 6);
RespawnBackoffData next = new RespawnBackoffData(nextExponent, today, pendingMs, 0L);
RespawnBackoffData next = new RespawnBackoffData(
nextExponent,
today,
pendingMs,
0L,
Optional.of(DeathLockSnapshot.from(player))
);
player.setAttached(RESPAWN_BACKOFF, next);
}
@@ -101,15 +115,21 @@ public class RespawnBackoffMod implements ModInitializer {
data.exponent(),
data.lastEpochDay(),
0L,
end
end,
data.deathLock()
);
player.setAttached(RESPAWN_BACKOFF, next);
player.setGameMode(GameType.SPECTATOR);
data.deathLock().ifPresent(lock -> teleportToDeathLock(player, lock));
RespawnBackoffNetworking.sendCooldown(player, true, end);
}
private static void tickPlayer(ServerPlayer player, long nowMs) {
RespawnBackoffData data = player.getAttachedOrElse(RESPAWN_BACKOFF, RespawnBackoffData.DEFAULT);
if (data.hasActiveCooldown(nowMs)) {
data.deathLock().ifPresent(lock -> enforceDeathLock(player, lock));
}
if (!data.hasActiveCooldown(nowMs)) {
return;
}
@@ -135,9 +155,11 @@ public class RespawnBackoffMod implements ModInitializer {
data.exponent(),
data.lastEpochDay(),
0L,
0L
0L,
Optional.empty()
);
player.setAttached(RESPAWN_BACKOFF, cleared);
teleportToRespawnPoint(player);
if (player.isSpectator()) {
player.setGameMode(GameType.SURVIVAL);
}
@@ -155,7 +177,8 @@ public class RespawnBackoffMod implements ModInitializer {
0,
today,
0L,
data.cooldownEndEpochMs()
data.cooldownEndEpochMs(),
data.deathLock()
);
player.setAttached(RESPAWN_BACKOFF, next);
}
@@ -163,4 +186,60 @@ public class RespawnBackoffMod implements ModInitializer {
public static boolean isPenaltyActive(ServerPlayer player, long nowMs) {
return player.getAttachedOrElse(RESPAWN_BACKOFF, RespawnBackoffData.DEFAULT).hasActiveCooldown(nowMs);
}
private static final double DEATH_LOCK_EPSILON_SQ = 1.0e-6;
private static void teleportToDeathLock(ServerPlayer player, DeathLockSnapshot lock) {
MinecraftServer server = player.server;
ServerLevel level = server.getLevel(lock.dimension());
if (level == null) {
level = server.overworld();
BlockPos spawn = level.getSharedSpawnPos();
teleportAbsolute(player, level, spawn.getX() + 0.5, spawn.getY(), spawn.getZ() + 0.5, level.getSharedSpawnAngle(), 0.0f);
return;
}
teleportAbsolute(player, level, lock.x(), lock.y(), lock.z(), lock.yaw(), lock.pitch());
}
private static void teleportAbsolute(ServerPlayer player, ServerLevel level, double x, double y, double z, float yaw, float pitch) {
Set<RelativeMovement> relatives = Collections.emptySet();
player.teleportTo(level, x, y, z, relatives, yaw, pitch);
}
private static void enforceDeathLock(ServerPlayer player, DeathLockSnapshot lock) {
if (!player.level().dimension().equals(lock.dimension())) {
teleportToDeathLock(player, lock);
return;
}
double dx = player.getX() - lock.x();
double dy = player.getY() - lock.y();
double dz = player.getZ() - lock.z();
if (dx * dx + dy * dy + dz * dz > DEATH_LOCK_EPSILON_SQ) {
teleportToDeathLock(player, lock);
}
}
private static void teleportToRespawnPoint(ServerPlayer player) {
MinecraftServer server = player.server;
ServerLevel respawnLevel = server.getLevel(player.getRespawnDimension());
if (respawnLevel == null) {
respawnLevel = server.overworld();
}
BlockPos bed = player.getRespawnPosition();
float yaw = player.getRespawnAngle();
if (bed != null) {
teleportAbsolute(player, respawnLevel, bed.getX() + 0.5, bed.getY(), bed.getZ() + 0.5, yaw, 0.0f);
return;
}
BlockPos spawn = respawnLevel.getSharedSpawnPos();
teleportAbsolute(
player,
respawnLevel,
spawn.getX() + 0.5,
spawn.getY(),
spawn.getZ() + 0.5,
respawnLevel.getSharedSpawnAngle(),
0.0f
);
}
}