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:
38
src/main/java/net/respawnbackoff/DeathLockSnapshot.java
Normal file
38
src/main/java/net/respawnbackoff/DeathLockSnapshot.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package net.respawnbackoff;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
public record DeathLockSnapshot(
|
||||
ResourceKey<Level> dimension,
|
||||
double x,
|
||||
double y,
|
||||
double z,
|
||||
float yaw,
|
||||
float pitch
|
||||
) {
|
||||
public static final Codec<DeathLockSnapshot> CODEC = RecordCodecBuilder.create(instance -> instance.group(
|
||||
ResourceKey.codec(Registries.DIMENSION).fieldOf("dimension").forGetter(DeathLockSnapshot::dimension),
|
||||
Codec.DOUBLE.fieldOf("x").forGetter(DeathLockSnapshot::x),
|
||||
Codec.DOUBLE.fieldOf("y").forGetter(DeathLockSnapshot::y),
|
||||
Codec.DOUBLE.fieldOf("z").forGetter(DeathLockSnapshot::z),
|
||||
Codec.FLOAT.fieldOf("yaw").forGetter(DeathLockSnapshot::yaw),
|
||||
Codec.FLOAT.fieldOf("pitch").forGetter(DeathLockSnapshot::pitch)
|
||||
).apply(instance, DeathLockSnapshot::new));
|
||||
|
||||
public static DeathLockSnapshot from(ServerPlayer player) {
|
||||
return new DeathLockSnapshot(
|
||||
player.level().dimension(),
|
||||
player.getX(),
|
||||
player.getY(),
|
||||
player.getZ(),
|
||||
player.getYRot(),
|
||||
player.getXRot()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package net.respawnbackoff;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
|
||||
@@ -7,15 +9,17 @@ public record RespawnBackoffData(
|
||||
int exponent,
|
||||
long lastEpochDay,
|
||||
long pendingDurationMs,
|
||||
long cooldownEndEpochMs
|
||||
long cooldownEndEpochMs,
|
||||
Optional<DeathLockSnapshot> deathLock
|
||||
) {
|
||||
public static final RespawnBackoffData DEFAULT = new RespawnBackoffData(0, 0L, 0L, 0L);
|
||||
public static final RespawnBackoffData DEFAULT = new RespawnBackoffData(0, 0L, 0L, 0L, Optional.empty());
|
||||
|
||||
public static final Codec<RespawnBackoffData> CODEC = RecordCodecBuilder.create(instance -> instance.group(
|
||||
Codec.INT.fieldOf("exponent").forGetter(RespawnBackoffData::exponent),
|
||||
Codec.LONG.fieldOf("last_epoch_day").forGetter(RespawnBackoffData::lastEpochDay),
|
||||
Codec.LONG.fieldOf("pending_duration_ms").forGetter(RespawnBackoffData::pendingDurationMs),
|
||||
Codec.LONG.fieldOf("cooldown_end_ms").forGetter(RespawnBackoffData::cooldownEndEpochMs)
|
||||
Codec.LONG.fieldOf("cooldown_end_ms").forGetter(RespawnBackoffData::cooldownEndEpochMs),
|
||||
DeathLockSnapshot.CODEC.optionalFieldOf("death_lock").forGetter(RespawnBackoffData::deathLock)
|
||||
).apply(instance, RespawnBackoffData::new));
|
||||
|
||||
public boolean hasActiveCooldown(long nowMs) {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user