Compare commits

..

2 Commits

Author SHA1 Message Date
57f0f340a9 really fix teleporting issue 2025-08-03 01:10:46 -04:00
881ba1a9a2 fix suvival dupe, fix teleportation issue 2025-08-01 18:54:42 -04:00
6 changed files with 288 additions and 278 deletions

View File

@ -57,6 +57,7 @@ import java.util.HashSet;
import java.util.Set;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.entity.Entity;
import net.minecraft.server.MinecraftServer;
import io.lampnet.travelerssuitcase.world.RuntimeWorldHandle;
@ -121,6 +122,14 @@ public class TravelersSuitcase {
public static void onServerStarting(ServerStartingEvent event) {
SuitcaseRegistryState.onServerStart(event.getServer());
// Load configuration
try {
loadConfig(event.getServer());
LOGGER.info("Configuration loaded successfully");
} catch (IOException e) {
LOGGER.warn("Failed to load configuration, using defaults", e);
}
Path registryFile = event.getServer().getWorldPath(net.minecraft.world.level.storage.LevelResource.ROOT)
.resolve("data")
.resolve(MODID)
@ -196,6 +205,52 @@ public class TravelersSuitcase {
), false);
return 1;
}))
.then(Commands.literal("getPlayerEntry")
.executes(ctx -> {
var src = ctx.getSource();
var world = src.getLevel();
var id = world.dimension().location();
if (!id.getNamespace().equals(MODID)
|| !id.getPath().startsWith("pocket_dimension_")) {
src.sendFailure(Component.literal("§cNot in a pocket dimension"));
return 0;
}
PlayerEntryData data = PlayerEntryData.get(world);
Vec3 pos = data.getEntryPos();
float yaw = data.getEntryYaw();
float pitch = data.getEntryPitch();
src.sendSuccess(() -> Component.literal(
String.format("§aPlayer entry: %.2f, %.2f, %.2f (yaw: %.1f, pitch: %.1f)",
pos.x(), pos.y(), pos.z(), yaw, pitch)
), false);
return 1;
})
)
.then(Commands.literal("getMobEntry")
.executes(ctx -> {
var src = ctx.getSource();
var world = src.getLevel();
var id = world.dimension().location();
if (!id.getNamespace().equals(MODID)
|| !id.getPath().startsWith("pocket_dimension_")) {
src.sendFailure(Component.literal("§cNot in a pocket dimension"));
return 0;
}
MobEntryData data = MobEntryData.get(world);
Vec3 pos = data.getEntryPos();
float yaw = data.getEntryYaw();
float pitch = data.getEntryPitch();
src.sendSuccess(() -> Component.literal(
String.format("§aMob entry: %.2f, %.2f, %.2f (yaw: %.1f, pitch: %.1f)",
pos.x(), pos.y(), pos.z(), yaw, pitch)
), false);
return 1;
})
)
.then(Commands.literal("resetPlayerEntry")
.then(Commands.argument("dimension", StringArgumentType.word())
.executes(ctx -> {
@ -213,7 +268,7 @@ public class TravelersSuitcase {
// Reset player entry to default position
PlayerEntryData playerData = PlayerEntryData.get(targetWorld);
playerData.setEntry(new Vec3(17.5, 97.0, 9.5), 0f, 0f);
playerData.resetToDefault();
src.sendSuccess(() -> Component.literal(
"§aPlayer entry reset for pocket dimension '" + dimSuffix + "'"
@ -222,6 +277,32 @@ public class TravelersSuitcase {
})
)
)
.then(Commands.literal("resetMobEntry")
.then(Commands.argument("dimension", StringArgumentType.word())
.executes(ctx -> {
var src = ctx.getSource();
String dimSuffix = StringArgumentType.getString(ctx, "dimension");
ResourceLocation dimId = ResourceLocation.fromNamespaceAndPath(MODID, "pocket_dimension_" + dimSuffix);
ResourceKey<Level> worldKey = ResourceKey.create(Registries.DIMENSION, dimId);
ServerLevel targetWorld = src.getServer().getLevel(worldKey);
if (targetWorld == null) {
src.sendFailure(Component.literal("§cPocket dimension '" + dimSuffix + "' not found"));
return 0;
}
// Reset mob entry to default position
MobEntryData mobData = MobEntryData.get(targetWorld);
mobData.setEntry(new Vec3(35.5, 85.0, 16.5), 0f, 0f);
src.sendSuccess(() -> Component.literal(
"§aMob entry reset for pocket dimension '" + dimSuffix + "'"
), false);
return 1;
})
)
)
.then(Commands.literal("listDimensions")
.requires(src -> src.hasPermission(2))
.executes(ctx -> {
@ -342,10 +423,120 @@ public class TravelersSuitcase {
return 1;
})
)
)
.then(Commands.literal("config")
.requires(src -> src.hasPermission(2))
.then(Commands.literal("save")
.executes(ctx -> {
var src = ctx.getSource();
try {
saveConfig(src.getServer());
src.sendSuccess(() -> Component.literal("§aConfiguration saved successfully"), false);
return 1;
} catch (Exception e) {
src.sendFailure(Component.literal("§cFailed to save configuration: " + e.getMessage()));
return 0;
}
})
)
.then(Commands.literal("load")
.executes(ctx -> {
var src = ctx.getSource();
try {
loadConfig(src.getServer());
src.sendSuccess(() -> Component.literal("§aConfiguration loaded successfully"), false);
return 1;
} catch (Exception e) {
src.sendFailure(Component.literal("§cFailed to load configuration: " + e.getMessage()));
return 0;
}
})
)
.then(Commands.literal("reload")
.executes(ctx -> {
var src = ctx.getSource();
try {
loadConfig(src.getServer());
src.sendSuccess(() -> Component.literal("§aConfiguration reloaded successfully"), false);
return 1;
} catch (Exception e) {
src.sendFailure(Component.literal("§cFailed to reload configuration: " + e.getMessage()));
return 0;
}
})
)
);
event.getDispatcher().register(builder);
}
private static void saveConfig(MinecraftServer server) throws IOException {
Path configDir = server.getWorldPath(net.minecraft.world.level.storage.LevelResource.ROOT)
.resolve("data")
.resolve(MODID);
Files.createDirectories(configDir);
Path configFile = configDir.resolve("config.properties");
StringBuilder content = new StringBuilder();
content.append("canCaptureHostile=").append(canCaptureHostile).append("\n");
content.append("blacklistedEntities=");
boolean first = true;
for (EntityType<?> entityType : entityBlacklist) {
if (!first) {
content.append(",");
}
content.append(BuiltInRegistries.ENTITY_TYPE.getKey(entityType));
first = false;
}
content.append("\n");
Files.writeString(configFile, content.toString());
}
private static void loadConfig(MinecraftServer server) throws IOException {
Path configFile = server.getWorldPath(net.minecraft.world.level.storage.LevelResource.ROOT)
.resolve("data")
.resolve(MODID)
.resolve("config.properties");
if (!Files.exists(configFile)) {
return;
}
entityBlacklist.clear();
for (String line : Files.readAllLines(configFile)) {
String[] parts = line.split("=", 2);
if (parts.length != 2) continue;
String key = parts[0].trim();
String value = parts[1].trim();
switch (key) {
case "canCaptureHostile":
canCaptureHostile = Boolean.parseBoolean(value);
break;
case "blacklistedEntities":
if (!value.isEmpty()) {
for (String entityId : value.split(",")) {
try {
ResourceLocation id = ResourceLocation.parse(entityId.trim());
EntityType<?> entityType = BuiltInRegistries.ENTITY_TYPE.get(id);
if (entityType != EntityType.PIG || entityId.trim().equals("minecraft:pig")) {
entityBlacklist.add(entityType);
}
} catch (Exception e) {
LOGGER.warn("Failed to parse entity ID from config: " + entityId, e);
}
}
}
break;
}
}
}
@SubscribeEvent
public void onUseEntity(PlayerInteractEvent.EntityInteract event) {
Level world = event.getLevel();
@ -402,8 +593,6 @@ public class TravelersSuitcase {
}
MobEntryData data = MobEntryData.get(targetWorld);
Vec3 dest = data.getEntryPos();
float yaw = data.getEntryYaw();
float pitch = data.getEntryPitch();
mob.changeDimension(targetWorld, new net.minecraftforge.common.util.ITeleporter() {
@Override
public Entity placeEntity(Entity entity, ServerLevel currentWorld, ServerLevel destWorld, float yaw, java.util.function.Function<Boolean, Entity> repositionEntity) {

View File

@ -4,12 +4,7 @@ import io.lampnet.travelerssuitcase.block.entity.SuitcaseBlockEntity;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
@ -17,43 +12,20 @@ import net.minecraft.sounds.SoundSource;
import net.minecraft.network.chat.Component;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.level.Level;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.nbt.Tag;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import net.minecraft.world.level.storage.loot.LootParams;
public class PocketPortalBlock extends Block {
private static final Map<String, PlayerPositionData> LAST_KNOWN_POSITIONS = new HashMap<>();
private static final int SEARCH_RADIUS_CHUNKS = 12;
public static class PlayerPositionData {
public final double x;
public final double y;
public final double z;
public final float yaw;
public final float pitch;
public final long timestamp;
public PlayerPositionData(double x, double y, double z, float yaw, float pitch) {
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.timestamp = System.currentTimeMillis();
}
}
public PocketPortalBlock(BlockBehaviour.Properties properties) {
super(properties);
@ -65,76 +37,9 @@ public class PocketPortalBlock extends Block {
}
public static void storePlayerPosition(ServerPlayer player) {
LAST_KNOWN_POSITIONS.put(
player.getUUID().toString(),
new PlayerPositionData(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot())
);
}
private boolean attemptPlayerInventorySuitcaseTeleport(Level world, ServerLevel overworld, ServerPlayer player, String keystoneName) {
for (ServerPlayer serverPlayer : Objects.requireNonNull(world.getServer()).getPlayerList().getPlayers()) {
if (scanPlayerInventoryForSuitcase(serverPlayer, player, keystoneName, world, overworld)) {
return true;
}
}
return false;
}
private boolean scanPlayerInventoryForSuitcase(ServerPlayer inventoryOwner, ServerPlayer exitingPlayer,
String keystoneName, Level world, ServerLevel overworld) {
for (int i = 0; i < inventoryOwner.getInventory().getContainerSize(); i++) {
ItemStack stack = inventoryOwner.getInventory().getItem(i);
if (isSuitcaseItemWithKeystone(stack, keystoneName)) {
cleanUpSuitcaseItemNbt(stack, exitingPlayer, keystoneName);
inventoryOwner.getInventory().setItem(i, stack);
teleportToPosition(world, exitingPlayer, overworld,
inventoryOwner.getX(), inventoryOwner.getY() + 1.0, inventoryOwner.getZ(),
exitingPlayer.getYRot(), exitingPlayer.getXRot());
return true;
}
}
return false;
}
private boolean isSuitcaseItemWithKeystone(ItemStack stack, String keystoneName) {
if (stack.isEmpty() || !(stack.getItem() instanceof BlockItem) ||
!(((BlockItem) stack.getItem()).getBlock() instanceof SuitcaseBlock)) {
return false;
}
if (!stack.hasTag()) return false;
CompoundTag beTag = BlockItem.getBlockEntityData(stack);
if (beTag == null) return false;
return beTag.contains("BoundKeystone") && keystoneName.equals(beTag.getString("BoundKeystone"));
}
private void cleanUpSuitcaseItemNbt(ItemStack stack, ServerPlayer player, String keystoneName) {
if (!stack.hasTag()) return;
CompoundTag beTag = BlockItem.getBlockEntityData(stack);
if (beTag == null) return;
if (beTag.contains("EnteredPlayers", Tag.TAG_LIST)) {
ListTag playersList = beTag.getList("EnteredPlayers", Tag.TAG_COMPOUND);
ListTag newPlayersList = new ListTag();
boolean playerFound = false;
for (int i = 0; i < playersList.size(); i++) {
CompoundTag playerData = playersList.getCompound(i);
if (!player.getUUID().toString().equals(playerData.getString("UUID"))) {
newPlayersList.add(playerData);
} else {
playerFound = true;
}
}
if (playerFound) {
beTag.put("EnteredPlayers", newPlayersList);
int remainingPlayers = newPlayersList.size();
updateItemLore(stack, remainingPlayers);
}
}
SuitcaseBlockEntity.removeSuitcaseEntry(keystoneName, player.getUUID().toString(), player.getServer());
}
@Override
public void entityInside(@NotNull BlockState state, Level world, @NotNull BlockPos pos, @NotNull Entity entity) {
@ -148,28 +53,9 @@ public class PocketPortalBlock extends Block {
ServerLevel overworld = Objects.requireNonNull(world.getServer()).getLevel(Level.OVERWORLD);
if (overworld == null) return;
boolean teleported = false;
// Method 1: Try to teleport to the original suitcase block entity
teleported = attemptSuitcaseTeleport(world, overworld, player, keystoneName);
// Method 2: Try to find suitcase in a player's inventory (new method)
if (!teleported) {
teleported = attemptPlayerInventorySuitcaseTeleport(world, overworld, player, keystoneName);
}
// Method 3: Try to find the suitcase as an item entity in the world
if (!teleported) {
teleported = attemptSuitcaseItemTeleport(world, overworld, player, keystoneName);
}
// Method 4: Try to use player's last known position
if (!teleported) {
teleported = attemptLastKnownPositionTeleport(world, overworld, player);
}
// Fallback: Take them to spawn
if (!teleported) {
// Primary method: Try to teleport to the suitcase block they entered from
if (!attemptSuitcaseTeleport(world, overworld, player, keystoneName)) {
// Fallback: Take them to spawn if suitcase block cannot be found
player.displayClientMessage(Component.literal("§c...").withStyle(ChatFormatting.RED), false);
teleportToPosition(world, player, overworld,
overworld.getSharedSpawnPos().getX() + 0.5,
@ -177,7 +63,6 @@ public class PocketPortalBlock extends Block {
overworld.getSharedSpawnPos().getZ() + 0.5, 0, 0);
}
SuitcaseBlockEntity.removeSuitcaseEntry(keystoneName, player.getUUID().toString(), world.getServer());
LAST_KNOWN_POSITIONS.remove(player.getUUID().toString());
}
}
}
@ -188,10 +73,11 @@ public class PocketPortalBlock extends Block {
player.fallDistance = 0f;
}
// Method 1: Try to teleport to the original suitcase block entity
// Teleport to the suitcase block coordinates they entered from
private boolean attemptSuitcaseTeleport(Level world, ServerLevel overworld, ServerPlayer player, String keystoneName) {
BlockPos suitcasePos = SuitcaseBlockEntity.findSuitcasePosition(keystoneName, player.getUUID().toString());
if (suitcasePos == null) return false;
ChunkPos suitcaseChunkPos = new ChunkPos(suitcasePos);
overworld.setChunkForced(suitcaseChunkPos.x, suitcaseChunkPos.z, true);
try {
@ -199,7 +85,10 @@ public class PocketPortalBlock extends Block {
if (targetEntity instanceof SuitcaseBlockEntity suitcase) {
SuitcaseBlockEntity.EnteredPlayerData exitData = suitcase.getExitPosition(player.getUUID().toString());
if (exitData != null) {
teleportToPosition(world, player, overworld, exitData.x(), exitData.y(), exitData.z(), exitData.yaw(), player.getXRot());
// Teleport to the suitcase block position (not the original player position)
teleportToPosition(world, player, overworld,
suitcasePos.getX() + 0.5, suitcasePos.getY() + 1.0, suitcasePos.getZ() + 0.5,
exitData.yaw(), player.getXRot());
Objects.requireNonNull(world.getServer()).execute(() -> {
overworld.setChunkForced(suitcaseChunkPos.x, suitcaseChunkPos.z, false);
});
@ -212,118 +101,24 @@ public class PocketPortalBlock extends Block {
return false;
}
// Method 2: Try to find the suitcase as an item entity in the world
private boolean attemptSuitcaseItemTeleport(Level world, ServerLevel overworld, ServerPlayer player, String keystoneName) {
BlockPos searchCenter;
BlockPos suitcasePos = SuitcaseBlockEntity.findSuitcasePosition(keystoneName, player.getUUID().toString());
if (suitcasePos != null) {
searchCenter = suitcasePos;
} else {
PlayerPositionData lastPos = LAST_KNOWN_POSITIONS.get(player.getUUID().toString());
if (lastPos != null) {
searchCenter = new BlockPos((int)lastPos.x, (int)lastPos.y, (int)lastPos.z);
} else {
searchCenter = overworld.getSharedSpawnPos();
}
}
int centerX = searchCenter.getX() >> 4;
int centerZ = searchCenter.getZ() >> 4;
for (int radius = 0; radius <= SEARCH_RADIUS_CHUNKS; radius++) {
for (int x = centerX - radius; x <= centerX + radius; x++) {
for (int z = centerZ - radius; z <= centerZ + radius; z++) {
if (radius > 0 && x > centerX - radius && x < centerX + radius &&
z > centerZ - radius && z < centerZ + radius) {
continue;
}
if (!overworld.hasChunk(x, z)) {
continue;
}
LevelChunk chunk = overworld.getChunk(x, z);
List<ItemEntity> itemEntities = overworld.getEntitiesOfClass(
ItemEntity.class,
new AABB(chunk.getPos().getMinBlockX(), overworld.getMinBuildHeight(), chunk.getPos().getMinBlockZ(),
chunk.getPos().getMaxBlockX(), overworld.getMaxBuildHeight(), chunk.getPos().getMaxBlockZ()),
itemEntity -> {
ItemStack stack = itemEntity.getItem();
CompoundTag beTag = BlockItem.getBlockEntityData(stack);
if (beTag == null) return false;
return beTag.contains("BoundKeystone") &&
keystoneName.equals(beTag.getString("BoundKeystone"));
}
);
if (!itemEntities.isEmpty()) {
ItemEntity suitcaseItemEntity = itemEntities.get(0);
cleanUpSuitcaseItemNbt(suitcaseItemEntity.getItem(), player, keystoneName);
suitcaseItemEntity.setItem(suitcaseItemEntity.getItem());
teleportToPosition(world, player, overworld,
suitcaseItemEntity.getX(), suitcaseItemEntity.getY() + 1.0, suitcaseItemEntity.getZ(),
player.getYRot(), player.getXRot());
return true;
}
}
}
}
return false;
}
// Method 3: Try to use player's last known position
private boolean attemptLastKnownPositionTeleport(Level world, ServerLevel overworld, ServerPlayer player) {
PlayerPositionData lastPos = LAST_KNOWN_POSITIONS.get(player.getUUID().toString());
if (lastPos != null) {
long currentTime = System.currentTimeMillis();
if (currentTime - lastPos.timestamp > 10 * 60 * 1000) {
return false;
}
player.displayClientMessage(Component.literal("§6Returning to your last known position."), false);
teleportToPosition(world, player, overworld,
lastPos.x, lastPos.y, lastPos.z,
lastPos.yaw, lastPos.pitch);
return true;
}
return false;
}
private void teleportToPosition(Level world, ServerPlayer player, ServerLevel targetWorld,
double x, double y, double z, float yaw, float pitch) {
if (!world.isClientSide) {
player.teleportTo(targetWorld, x, y, z, yaw, pitch);
// Use changeDimension instead of teleportTo to avoid "Player moved wrongly" errors
player.changeDimension(targetWorld, new net.minecraftforge.common.util.ITeleporter() {
@Override
public Entity placeEntity(Entity entity, ServerLevel currentWorld, ServerLevel destWorld, float placementYaw, java.util.function.Function<Boolean, Entity> repositionEntity) {
Entity e = repositionEntity.apply(false);
e.teleportTo(x, y, z);
e.setYRot(yaw);
e.setXRot(pitch);
return e;
}
});
}
}
private void updateItemLore(ItemStack stack, int playerCount) {
if (stack.hasTag()) {
assert stack.getTag() != null;
if (stack.getTag().contains("display")) {
CompoundTag display = stack.getTag().getCompound("display");
if (display.contains("Lore", Tag.TAG_LIST)) {
ListTag lore = display.getList("Lore", Tag.TAG_STRING);
ListTag newLore = new ListTag();
for (int i = 0; i < lore.size(); i++) {
String loreStr = lore.getString(i);
if (!loreStr.contains("traveler")) {
newLore.add(lore.get(i));
}
}
if (playerCount > 0) {
Component warningText = Component.literal("§c⚠ Contains " + playerCount + " traveler(s)!")
.withStyle(ChatFormatting.RED);
newLore.add(0, StringTag.valueOf(Component.Serializer.toJson(warningText)));
}
display.put("Lore", newLore);
}
}
}
if (playerCount > 0) {
Component lore = Component.literal(playerCount + " player" + (playerCount > 1 ? "s" : "") + " inside").withStyle(ChatFormatting.GRAY);
CompoundTag displayTag = stack.getOrCreateTagElement("display");
ListTag loreList = new ListTag();
loreList.add(StringTag.valueOf(Component.Serializer.toJson(lore)));
displayTag.put("Lore", loreList);
} else {
if (stack.hasTag() && stack.getTag().contains("display")) {
stack.getTag().getCompound("display").remove("Lore");
}
}
}
}

View File

@ -45,13 +45,11 @@ import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.Containers;
import net.minecraft.world.item.DyeColor;
import net.minecraft.network.protocol.game.ClientboundStopSoundPacket;
import net.minecraft.sounds.SoundSource;
import net.minecraft.sounds.SoundEvents;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -67,6 +65,8 @@ public class SuitcaseBlock extends BaseEntityBlock {
private final static VoxelShape SHAPE_E = Block.box(2, 0, 0, 14, 4, 16);
private final static VoxelShape SHAPE_W = Block.box(2, 0, 0, 14, 4, 16);
public SuitcaseBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any()
@ -119,42 +119,6 @@ public class SuitcaseBlock extends BaseEntityBlock {
@Override
public void onRemove(BlockState state, @NotNull Level world, @NotNull BlockPos pos, BlockState newState, boolean isMoving) {
if (!state.is(newState.getBlock())) {
BlockEntity blockEntity = world.getBlockEntity(pos);
if (blockEntity instanceof SuitcaseBlockEntity suitcase) {
ItemStack itemStack = new ItemStack(this);
String boundKeystone = suitcase.getBoundKeystoneName();
if (boundKeystone != null) {
CompoundTag beNbt = new CompoundTag();
suitcase.saveAdditional(beNbt);
if (!beNbt.isEmpty()) {
BlockItem.setBlockEntityData(itemStack, ModBlockEntities.SUITCASE_BLOCK_ENTITY.get(), beNbt);
}
CompoundTag display = itemStack.getOrCreateTagElement("display");
ListTag lore = new ListTag();
String displayName = boundKeystone.replace("_", " ");
Component boundText;
if (suitcase.isLocked()) {
boundText = Component.literal("Bound to: §k" + displayName)
.withStyle(ChatFormatting.GRAY);
} else {
boundText = Component.literal("Bound to: " + displayName)
.withStyle(ChatFormatting.GRAY);
}
Component lockText = Component.literal(suitcase.isLocked() ? "§cLocked" : "§aUnlocked")
.withStyle(ChatFormatting.GRAY);
if (!suitcase.getEnteredPlayers().isEmpty()) {
Component warningText = Component.literal("§c⚠ Contains " + suitcase.getEnteredPlayers().size() + " Traveler's!")
.withStyle(ChatFormatting.RED);
lore.add(StringTag.valueOf(Component.Serializer.toJson(warningText)));
}
lore.add(StringTag.valueOf(Component.Serializer.toJson(boundText)));
lore.add(StringTag.valueOf(Component.Serializer.toJson(lockText)));
display.put("Lore", lore);
}
Containers.dropItemStack(world, pos.getX(), pos.getY(), pos.getZ(), itemStack);
}
super.onRemove(state, world, pos, newState, isMoving);
}
}
@ -263,12 +227,41 @@ public class SuitcaseBlock extends BaseEntityBlock {
player.fallDistance = 0f;
PlayerEntryData ped = PlayerEntryData.get(targetWorld);
Vec3 dest = ped.getEntryPos();
float yaw = ped.getEntryYaw();
// Use default position (17, 97, 9) unless manually configured
if (!ped.isManuallySet()) {
ped.setEntryAutomatic(new Vec3(17.5, 97.0, 9.5), 0f, 0f);
}
Vec3 dest = ped.getEntryPos();
float entryYaw = ped.getEntryYaw();
float pitch = player.getXRot();
player.teleportTo(targetWorld, dest.x, dest.y, dest.z, yaw, pitch);
// Ensure destination chunk is loaded before teleportation
ChunkPos destChunkPos = new ChunkPos(BlockPos.containing(dest));
targetWorld.setChunkForced(destChunkPos.x, destChunkPos.z, true);
try {
// Use changeDimension instead of teleportTo to avoid "Player moved wrongly" errors
player.changeDimension(targetWorld, new net.minecraftforge.common.util.ITeleporter() {
@Override
public Entity placeEntity(Entity entity, ServerLevel currentWorld, ServerLevel destWorld, float placementYaw, java.util.function.Function<Boolean, Entity> repositionEntity) {
Entity e = repositionEntity.apply(false);
e.teleportTo(dest.x, dest.y, dest.z);
e.setYRot(entryYaw);
e.setXRot(pitch);
return e;
}
});
// Schedule chunk unforcing on next tick to allow proper entity placement
Objects.requireNonNull(world.getServer()).execute(() -> {
targetWorld.setChunkForced(destChunkPos.x, destChunkPos.z, false);
});
} finally {
// Ensure chunk is unforced even if teleportation fails
targetWorld.setChunkForced(destChunkPos.x, destChunkPos.z, false);
}
player.connection.send(new ClientboundStopSoundPacket(null, null));
@ -283,6 +276,8 @@ public class SuitcaseBlock extends BaseEntityBlock {
}
}
@Override
public @NotNull VoxelShape getShape(BlockState state, @NotNull BlockGetter world, @NotNull BlockPos pos, @NotNull CollisionContext context) {
return switch (state.getValue(FACING)) {

View File

@ -1,6 +1,6 @@
package io.lampnet.travelerssuitcase.block.entity;
import io.lampnet.travelerssuitcase.block.PocketPortalBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
@ -96,7 +96,7 @@ public class SuitcaseBlockEntity extends BlockEntity {
);
suitcases.put(player.getUUID().toString(), this.worldPosition);
}
PocketPortalBlock.storePlayerPosition(player);
setChangedAndNotify();
MinecraftServer server = player.getServer();
@ -111,9 +111,9 @@ public class SuitcaseBlockEntity extends BlockEntity {
if (data.uuid.equals(playerUuid)) {
EnteredPlayerData exitData = new EnteredPlayerData(
data.uuid,
this.worldPosition.getX() + 0.5, this.worldPosition.getY() + 1.0, this.worldPosition.getZ() + 0.5,
data.x, data.y, data.z,
data.pitch, data.yaw,
this.worldPosition
data.suitcasePos != null ? data.suitcasePos : this.worldPosition
);
enteredPlayers.remove(i);
setChangedAndNotify();
@ -187,7 +187,13 @@ public class SuitcaseBlockEntity extends BlockEntity {
@Override
public @NotNull CompoundTag getUpdateTag() {
CompoundTag nbt = new CompoundTag();
this.saveAdditional(nbt);
// Only sync essential data to client, not player entry data which could cause position conflicts
if (boundKeystoneName != null) {
nbt.putString("BoundKeystone", boundKeystoneName);
}
nbt.putBoolean("Locked", isLocked);
nbt.putBoolean("DimensionLocked", dimensionLocked);
// Deliberately exclude EnteredPlayers from client sync to prevent coordinate conflicts
return nbt;
}

View File

@ -10,6 +10,7 @@ public class PlayerEntryData extends SavedData {
private Vec3 entryPos = Vec3.ZERO;
private float entryYaw = 0f, entryPitch = 0f;
private boolean isManuallySet = false;
public PlayerEntryData() {
super();
@ -27,6 +28,7 @@ public class PlayerEntryData extends SavedData {
);
data.entryYaw = nbt.getFloat("pyaw");
data.entryPitch = nbt.getFloat("ppitch");
data.isManuallySet = nbt.getBoolean("manually_set");
return data;
}
@ -41,6 +43,7 @@ public class PlayerEntryData extends SavedData {
nbt.putDouble("pz", entryPos.z);
nbt.putFloat( "pyaw", entryYaw);
nbt.putFloat( "ppitch", entryPitch);
nbt.putBoolean("manually_set", isManuallySet);
return nbt;
}
@ -48,10 +51,27 @@ public class PlayerEntryData extends SavedData {
this.entryPos = pos;
this.entryYaw = yaw;
this.entryPitch = pitch;
this.isManuallySet = true;
this.setDirty();
}
public void setEntryAutomatic(Vec3 pos, float yaw, float pitch) {
this.entryPos = pos;
this.entryYaw = yaw;
this.entryPitch = pitch;
this.setDirty();
}
public void resetToDefault() {
this.entryPos = new Vec3(17.5, 97.0, 9.5);
this.entryYaw = 0f;
this.entryPitch = 0f;
this.isManuallySet = false;
this.setDirty();
}
public Vec3 getEntryPos() { return entryPos; }
public float getEntryYaw() { return entryYaw; }
public float getEntryPitch() { return entryPitch; }
public boolean isManuallySet() { return isManuallySet; }
}

View File

@ -21,9 +21,14 @@ public class ServerChunkManagerMixin {
private void onRunDistanceManagerUpdates(CallbackInfoReturnable<Boolean> cir) {
// Only apply special chunk processing logic to RuntimeWorld instances (fantasy dimensions)
if (this.level instanceof RuntimeWorld) {
if (!((FantasyWorldAccess) this.level).fantasy$shouldTick()) {
cir.setReturnValue(false);
FantasyWorldAccess worldAccess = (FantasyWorldAccess) this.level;
// Always allow distance manager updates if chunks are loaded (prevents breaking entity initialization)
// Only block when world is truly empty AND configured not to tick when empty
if (this.level.getChunkSource().getLoadedChunksCount() > 0 || worldAccess.fantasy$shouldTick()) {
// Allow distance manager to run - this is critical for entity systems
return;
}
cir.setReturnValue(false);
}
// Regular worlds (overworld, nether, end) process chunks normally without interference
}