Sky fall when respawning in purg, rest works

This commit is contained in:
2026-09-08 00:38:57 +02:00
parent 6d307dbdd6
commit 24242cc895
2 changed files with 143 additions and 63 deletions
@@ -4,7 +4,10 @@ import com.mojang.logging.LogUtils;
import dev.lazysucksat.purgatory.Purgatory;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Position;
import net.minecraft.core.registries.Registries;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
@@ -17,64 +20,46 @@ import net.minecraftforge.fml.common.Mod;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Mod.EventBusSubscriber(modid = Purgatory.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE)
public class DimensionTeleporter {
private record TeleportRequest(ServerPlayer player, ServerLevel targetLevel) {}
public static void escapePurgatory(ServerPlayer player) {
CompoundTag persistentData = player.getPersistentData();
private static final ArrayList<TeleportRequest> teleportQueue = new ArrayList<>();
if (persistentData.contains("PurgatorySavedSpawn")) {
CompoundTag purgatoryData = persistentData.getCompound("PurgatorySavedSpawn");
public static void teleportToDimension(ServerPlayer player, ResourceKey<Level> dimensionKey) {
if (player.level().isClientSide() && player.getServer() != null) {
return;
}
if (purgatoryData.getBoolean("HasSavedBed")) {
BlockPos originalBedPos = new BlockPos(
purgatoryData.getInt("BedX"),
purgatoryData.getInt("BedY"),
purgatoryData.getInt("BedZ")
);
MinecraftServer server = player.getServer();
assert server != null;
ServerLevel targetLevel = server.getLevel(dimensionKey);
ResourceKey<Level> originalDim = ResourceKey.create(
Registries.DIMENSION,
ResourceLocation.parse(purgatoryData.getString("BedDim"))
);
if (targetLevel == null) {
LogUtils.getLogger().atError().log(String.format("Dimension %s does not exist!", dimensionKey.toString()));
return;
}
float angle = purgatoryData.getFloat("BedAngle");
boolean forced = purgatoryData.getBoolean("BedForced");
teleportQueue.add(new TeleportRequest(player, targetLevel));
}
@SubscribeEvent
public static void onServerTick(TickEvent.ServerTickEvent event) {
if (event.phase == TickEvent.Phase.END && !teleportQueue.isEmpty()) {
List<TeleportRequest> toTeleport = new ArrayList<>(teleportQueue);
teleportQueue.clear();
LogUtils.getLogger().atInfo().log("Teleporting players!");
for (TeleportRequest request : toTeleport) {
ServerPlayer player = request.player();
ServerLevel target = request.targetLevel();
double currentScale = player.level().dimensionType().coordinateScale();
double destScale = target.dimensionType().coordinateScale();
double scaleFactor = currentScale / destScale;
double targetX = player.getX() * scaleFactor;
double targetZ = player.getZ() * scaleFactor;
int blockX = (int) targetX;
int blockZ = (int) targetZ;
BlockPos highestPos = target.getHeightmapPos(Heightmap.Types.WORLD_SURFACE, new BlockPos(blockX, 0, blockZ));
int highestY = highestPos.getY();
if (player.isAlive()) {
player.teleportTo(target, targetX, highestY, targetZ, player.getYRot(), player.getXRot());
// Restore original bed/anchor spawn
player.setRespawnPosition(originalDim, originalBedPos, angle, forced, false);
} else {
teleportQueue.add(request);
// Clear custom spawn so they fall back to Overworld world spawn
player.setRespawnPosition(Level.OVERWORLD, null, 0.0F, false, false);
}
// Clean up NBT tag
persistentData.remove("PurgatorySavedSpawn");
}
// Teleport player out back to Overworld or their bed
ServerLevel overworld = Objects.requireNonNull(player.getServer()).getLevel(Level.OVERWORLD);
if (overworld != null) {
player.changeDimension(overworld, new PurgatoryTeleport());
}
}
}
@@ -1,13 +1,14 @@
package dev.lazysucksat.purgatory.dimension;
import com.mojang.logging.LogUtils;
import dev.lazysucksat.purgatory.Purgatory;
import dev.lazysucksat.purgatory.players.PlayerChecks;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.common.MinecraftForge;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
@@ -17,16 +18,110 @@ import net.minecraftforge.fml.common.Mod;
public class PurgatoryEventHandler {
@SubscribeEvent
public static void respawnEventHandler(PlayerEvent.PlayerRespawnEvent respawnEvent) {
LogUtils.getLogger().atInfo().log("Ran respawnEventHandler");
Player player = respawnEvent.getEntity();
if (!PlayerChecks.playerUnlockedPurgatory(player)) {
public static void onPlayerDeath(LivingDeathEvent event) {
if (!(event.getEntity() instanceof ServerPlayer player)) {
return;
}
if (player instanceof ServerPlayer serverPlayer) {
DimensionTeleporter.teleportToDimension(serverPlayer, PurgatoryDim.PURGATORY_KEY);
// Only redirect death if dying outside Purgatory
if (!player.level().dimension().equals(PurgatoryDim.PURGATORY_KEY)) {
// 1. Save original Bed/Anchor info into player persistent NBT
BlockPos bedPos = player.getRespawnPosition();
ResourceKey<Level> bedDim = player.getRespawnDimension();
float bedAngle = player.getRespawnAngle();
boolean bedForced = player.isRespawnForced();
CompoundTag persistentData = player.getPersistentData();
CompoundTag purgatoryData = new CompoundTag();
if (bedPos != null) {
purgatoryData.putInt("BedX", bedPos.getX());
purgatoryData.putInt("BedY", bedPos.getY());
purgatoryData.putInt("BedZ", bedPos.getZ());
purgatoryData.putString("BedDim", bedDim.location().toString());
purgatoryData.putFloat("BedAngle", bedAngle);
purgatoryData.putBoolean("BedForced", bedForced);
purgatoryData.putBoolean("HasSavedBed", true);
} else {
purgatoryData.putBoolean("HasSavedBed", false);
}
ServerLevel purgatoryLevel = player.getServer().getLevel(PurgatoryDim.PURGATORY_KEY);
if (purgatoryLevel == null) return;
double currentScale = player.level().dimensionType().coordinateScale();
double destScale = purgatoryLevel.dimensionType().coordinateScale();
double scaleFactor = currentScale / destScale;
int targetX = (int) (player.getX() * scaleFactor);
int targetZ = (int) (player.getZ() * scaleFactor);
// 2. TRICK: Target the sky limit.
// Vanilla natively accepts Air as a valid forced spawn. This prevents Vanilla from
// rejecting the spawn due to unloaded chunks and stops the "missing bed" error.
int safeSkyY = purgatoryLevel.getMaxBuildHeight() - 1;
BlockPos skySpawn = new BlockPos(targetX, safeSkyY, targetZ);
purgatoryData.putBoolean("NeedsGroundSnap", true);
persistentData.put("PurgatorySavedSpawn", purgatoryData);
// Start loading the chunk early while the player is on the death screen
purgatoryLevel.getChunkSource().addRegionTicket(
net.minecraft.server.level.TicketType.POST_TELEPORT,
new ChunkPos(skySpawn),
3,
player.getId()
);
// Force vanilla to natively respawn them in Purgatory
player.setRespawnPosition(PurgatoryDim.PURGATORY_KEY, skySpawn, player.getYRot(), true, false);
}
}
@SubscribeEvent
public static void onPlayerClone(PlayerEvent.Clone event) {
if (!event.isWasDeath()) {
return;
}
// Preserve Purgatory NBT data across the respawn player instance creation
CompoundTag oldData = event.getOriginal().getPersistentData();
if (oldData.contains("PurgatorySavedSpawn")) {
event.getEntity().getPersistentData().put("PurgatorySavedSpawn", oldData.getCompound("PurgatorySavedSpawn").copy());
}
}
@SubscribeEvent
public static void onPlayerRespawn(PlayerEvent.PlayerRespawnEvent event) {
if (!(event.getEntity() instanceof ServerPlayer player) || event.isEndConquered()) {
return;
}
CompoundTag persistentData = player.getPersistentData();
if (persistentData.contains("PurgatorySavedSpawn")) {
CompoundTag purgatoryData = persistentData.getCompound("PurgatorySavedSpawn");
if (purgatoryData.getBoolean("NeedsGroundSnap") && player.level().dimension().equals(PurgatoryDim.PURGATORY_KEY)) {
// Consume the snap flag so it only happens once
purgatoryData.putBoolean("NeedsGroundSnap", false);
ServerLevel purgatoryLevel = (ServerLevel) player.level();
BlockPos currentPos = player.blockPosition();
// Now that the player is natively inside Purgatory, the chunk is GUARANTEED
// to be fully loaded, meaning getHeightmapPos will return the true surface level.
BlockPos surfacePos = purgatoryLevel.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, currentPos);
// Instantly snap the player down to the surface before the client renders
player.teleportTo(
surfacePos.getX() + 0.5,
surfacePos.getY(),
surfacePos.getZ() + 0.5
);
player.fallDistance = 0.0F; // Prevent fall damage from the snap
}
}
}
}