diff --git a/src/main/java/io/lampnet/travelerssuitcase/TravelersSuitcase.java b/src/main/java/io/lampnet/travelerssuitcase/TravelersSuitcase.java index 41b1faa..10e86cd 100644 --- a/src/main/java/io/lampnet/travelerssuitcase/TravelersSuitcase.java +++ b/src/main/java/io/lampnet/travelerssuitcase/TravelersSuitcase.java @@ -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; @@ -120,6 +121,14 @@ public class TravelersSuitcase { @SubscribeEvent 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") @@ -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 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 repositionEntity) { diff --git a/src/main/java/io/lampnet/travelerssuitcase/block/PocketPortalBlock.java b/src/main/java/io/lampnet/travelerssuitcase/block/PocketPortalBlock.java index 08b540b..e39dd4c 100644 --- a/src/main/java/io/lampnet/travelerssuitcase/block/PocketPortalBlock.java +++ b/src/main/java/io/lampnet/travelerssuitcase/block/PocketPortalBlock.java @@ -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 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 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 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"); - } - } - } + } \ No newline at end of file diff --git a/src/main/java/io/lampnet/travelerssuitcase/block/SuitcaseBlock.java b/src/main/java/io/lampnet/travelerssuitcase/block/SuitcaseBlock.java index 1c55792..da16d71 100644 --- a/src/main/java/io/lampnet/travelerssuitcase/block/SuitcaseBlock.java +++ b/src/main/java/io/lampnet/travelerssuitcase/block/SuitcaseBlock.java @@ -45,6 +45,7 @@ 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.item.DyeColor; @@ -64,10 +65,7 @@ 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); - private static final int PORTAL_SEARCH_RADIUS = 50; - private static final int PORTAL_SEARCH_Y_MIN = -20; - private static final int PORTAL_SEARCH_Y_MAX = 40; - private static final double PORTAL_TELEPORT_Y_OFFSET = -3.0; + public SuitcaseBlock(BlockBehaviour.Properties properties) { super(properties); @@ -230,19 +228,40 @@ public class SuitcaseBlock extends BaseEntityBlock { PlayerEntryData ped = PlayerEntryData.get(targetWorld); - // Find the portal block position in the pocket dimension - BlockPos portalPos = findPortalBlockInDimension(targetWorld); - if (portalPos != null) { - // Update the entry position to match the portal block location - Vec3 portalLocation = new Vec3(portalPos.getX() + 0.5, portalPos.getY() + PORTAL_TELEPORT_Y_OFFSET, portalPos.getZ() + 0.5); - ped.setEntry(portalLocation, 0f, 0f); + // 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 yaw = ped.getEntryYaw(); + 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 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)); @@ -257,23 +276,7 @@ public class SuitcaseBlock extends BaseEntityBlock { } } - private BlockPos findPortalBlockInDimension(ServerLevel targetWorld) { - // Search around the structure placement area (0, 64, 0) for portal blocks - BlockPos structureCenter = new BlockPos(0, 64, 0); - - for (int x = -PORTAL_SEARCH_RADIUS; x <= PORTAL_SEARCH_RADIUS; x++) { - for (int y = PORTAL_SEARCH_Y_MIN; y <= PORTAL_SEARCH_Y_MAX; y++) { - for (int z = -PORTAL_SEARCH_RADIUS; z <= PORTAL_SEARCH_RADIUS; z++) { - BlockPos checkPos = structureCenter.offset(x, y, z); - if (targetWorld.getBlockState(checkPos).getBlock() instanceof PocketPortalBlock) { - return checkPos; - } - } - } - } - - return null; - } + @Override public @NotNull VoxelShape getShape(BlockState state, @NotNull BlockGetter world, @NotNull BlockPos pos, @NotNull CollisionContext context) { diff --git a/src/main/java/io/lampnet/travelerssuitcase/block/entity/SuitcaseBlockEntity.java b/src/main/java/io/lampnet/travelerssuitcase/block/entity/SuitcaseBlockEntity.java index d47c147..1b68773 100644 --- a/src/main/java/io/lampnet/travelerssuitcase/block/entity/SuitcaseBlockEntity.java +++ b/src/main/java/io/lampnet/travelerssuitcase/block/entity/SuitcaseBlockEntity.java @@ -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(); @@ -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; } diff --git a/src/main/java/io/lampnet/travelerssuitcase/data/PlayerEntryData.java b/src/main/java/io/lampnet/travelerssuitcase/data/PlayerEntryData.java index d2057f2..1ba5e79 100644 --- a/src/main/java/io/lampnet/travelerssuitcase/data/PlayerEntryData.java +++ b/src/main/java/io/lampnet/travelerssuitcase/data/PlayerEntryData.java @@ -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; } } \ No newline at end of file