Compare commits

..

No commits in common. "57f0f340a912665be5152da549ea0f61d32227c2" and "54c9024615910eb54a5b000ac147b70f9045a719" have entirely different histories.

6 changed files with 278 additions and 288 deletions

View File

@ -57,7 +57,6 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.server.MinecraftServer;
import io.lampnet.travelerssuitcase.world.RuntimeWorldHandle; import io.lampnet.travelerssuitcase.world.RuntimeWorldHandle;
@ -121,14 +120,6 @@ public class TravelersSuitcase {
@SubscribeEvent @SubscribeEvent
public static void onServerStarting(ServerStartingEvent event) { public static void onServerStarting(ServerStartingEvent event) {
SuitcaseRegistryState.onServerStart(event.getServer()); 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) Path registryFile = event.getServer().getWorldPath(net.minecraft.world.level.storage.LevelResource.ROOT)
.resolve("data") .resolve("data")
@ -205,52 +196,6 @@ public class TravelersSuitcase {
), false); ), false);
return 1; 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.literal("resetPlayerEntry")
.then(Commands.argument("dimension", StringArgumentType.word()) .then(Commands.argument("dimension", StringArgumentType.word())
.executes(ctx -> { .executes(ctx -> {
@ -268,7 +213,7 @@ public class TravelersSuitcase {
// Reset player entry to default position // Reset player entry to default position
PlayerEntryData playerData = PlayerEntryData.get(targetWorld); PlayerEntryData playerData = PlayerEntryData.get(targetWorld);
playerData.resetToDefault(); playerData.setEntry(new Vec3(17.5, 97.0, 9.5), 0f, 0f);
src.sendSuccess(() -> Component.literal( src.sendSuccess(() -> Component.literal(
"§aPlayer entry reset for pocket dimension '" + dimSuffix + "'" "§aPlayer entry reset for pocket dimension '" + dimSuffix + "'"
@ -277,32 +222,6 @@ 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") .then(Commands.literal("listDimensions")
.requires(src -> src.hasPermission(2)) .requires(src -> src.hasPermission(2))
.executes(ctx -> { .executes(ctx -> {
@ -423,120 +342,10 @@ public class TravelersSuitcase {
return 1; 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); 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 @SubscribeEvent
public void onUseEntity(PlayerInteractEvent.EntityInteract event) { public void onUseEntity(PlayerInteractEvent.EntityInteract event) {
Level world = event.getLevel(); Level world = event.getLevel();
@ -593,6 +402,8 @@ public class TravelersSuitcase {
} }
MobEntryData data = MobEntryData.get(targetWorld); MobEntryData data = MobEntryData.get(targetWorld);
Vec3 dest = data.getEntryPos(); Vec3 dest = data.getEntryPos();
float yaw = data.getEntryYaw();
float pitch = data.getEntryPitch();
mob.changeDimension(targetWorld, new net.minecraftforge.common.util.ITeleporter() { mob.changeDimension(targetWorld, new net.minecraftforge.common.util.ITeleporter() {
@Override @Override
public Entity placeEntity(Entity entity, ServerLevel currentWorld, ServerLevel destWorld, float yaw, java.util.function.Function<Boolean, Entity> repositionEntity) { public Entity placeEntity(Entity entity, ServerLevel currentWorld, ServerLevel destWorld, float yaw, java.util.function.Function<Boolean, Entity> repositionEntity) {

View File

@ -4,7 +4,12 @@ import io.lampnet.travelerssuitcase.block.entity.SuitcaseBlockEntity;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.entity.BlockEntity; 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.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.ServerPlayer;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundEvents;
@ -12,20 +17,43 @@ import net.minecraft.sounds.SoundSource;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.nbt.Tag;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import net.minecraft.world.level.storage.loot.LootParams; import net.minecraft.world.level.storage.loot.LootParams;
public class PocketPortalBlock extends Block { 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) { public PocketPortalBlock(BlockBehaviour.Properties properties) {
super(properties); super(properties);
@ -37,9 +65,76 @@ 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 @Override
public void entityInside(@NotNull BlockState state, Level world, @NotNull BlockPos pos, @NotNull Entity entity) { public void entityInside(@NotNull BlockState state, Level world, @NotNull BlockPos pos, @NotNull Entity entity) {
@ -53,9 +148,28 @@ public class PocketPortalBlock extends Block {
ServerLevel overworld = Objects.requireNonNull(world.getServer()).getLevel(Level.OVERWORLD); ServerLevel overworld = Objects.requireNonNull(world.getServer()).getLevel(Level.OVERWORLD);
if (overworld == null) return; if (overworld == null) return;
// Primary method: Try to teleport to the suitcase block they entered from boolean teleported = false;
if (!attemptSuitcaseTeleport(world, overworld, player, keystoneName)) {
// Fallback: Take them to spawn if suitcase block cannot be found // 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) {
player.displayClientMessage(Component.literal("§c...").withStyle(ChatFormatting.RED), false); player.displayClientMessage(Component.literal("§c...").withStyle(ChatFormatting.RED), false);
teleportToPosition(world, player, overworld, teleportToPosition(world, player, overworld,
overworld.getSharedSpawnPos().getX() + 0.5, overworld.getSharedSpawnPos().getX() + 0.5,
@ -63,6 +177,7 @@ public class PocketPortalBlock extends Block {
overworld.getSharedSpawnPos().getZ() + 0.5, 0, 0); overworld.getSharedSpawnPos().getZ() + 0.5, 0, 0);
} }
SuitcaseBlockEntity.removeSuitcaseEntry(keystoneName, player.getUUID().toString(), world.getServer()); SuitcaseBlockEntity.removeSuitcaseEntry(keystoneName, player.getUUID().toString(), world.getServer());
LAST_KNOWN_POSITIONS.remove(player.getUUID().toString());
} }
} }
} }
@ -73,11 +188,10 @@ public class PocketPortalBlock extends Block {
player.fallDistance = 0f; player.fallDistance = 0f;
} }
// Teleport to the suitcase block coordinates they entered from // Method 1: Try to teleport to the original suitcase block entity
private boolean attemptSuitcaseTeleport(Level world, ServerLevel overworld, ServerPlayer player, String keystoneName) { private boolean attemptSuitcaseTeleport(Level world, ServerLevel overworld, ServerPlayer player, String keystoneName) {
BlockPos suitcasePos = SuitcaseBlockEntity.findSuitcasePosition(keystoneName, player.getUUID().toString()); BlockPos suitcasePos = SuitcaseBlockEntity.findSuitcasePosition(keystoneName, player.getUUID().toString());
if (suitcasePos == null) return false; if (suitcasePos == null) return false;
ChunkPos suitcaseChunkPos = new ChunkPos(suitcasePos); ChunkPos suitcaseChunkPos = new ChunkPos(suitcasePos);
overworld.setChunkForced(suitcaseChunkPos.x, suitcaseChunkPos.z, true); overworld.setChunkForced(suitcaseChunkPos.x, suitcaseChunkPos.z, true);
try { try {
@ -85,10 +199,7 @@ public class PocketPortalBlock extends Block {
if (targetEntity instanceof SuitcaseBlockEntity suitcase) { if (targetEntity instanceof SuitcaseBlockEntity suitcase) {
SuitcaseBlockEntity.EnteredPlayerData exitData = suitcase.getExitPosition(player.getUUID().toString()); SuitcaseBlockEntity.EnteredPlayerData exitData = suitcase.getExitPosition(player.getUUID().toString());
if (exitData != null) { if (exitData != null) {
// Teleport to the suitcase block position (not the original player position) teleportToPosition(world, player, overworld, exitData.x(), exitData.y(), exitData.z(), exitData.yaw(), player.getXRot());
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(() -> { Objects.requireNonNull(world.getServer()).execute(() -> {
overworld.setChunkForced(suitcaseChunkPos.x, suitcaseChunkPos.z, false); overworld.setChunkForced(suitcaseChunkPos.x, suitcaseChunkPos.z, false);
}); });
@ -101,24 +212,118 @@ public class PocketPortalBlock extends Block {
return false; 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, private void teleportToPosition(Level world, ServerPlayer player, ServerLevel targetWorld,
double x, double y, double z, float yaw, float pitch) { double x, double y, double z, float yaw, float pitch) {
if (!world.isClientSide) { if (!world.isClientSide) {
// Use changeDimension instead of teleportTo to avoid "Player moved wrongly" errors player.teleportTo(targetWorld, x, y, z, yaw, pitch);
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,11 +45,13 @@ import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.Containers;
import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.DyeColor;
import net.minecraft.network.protocol.game.ClientboundStopSoundPacket; 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.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -64,8 +66,6 @@ public class SuitcaseBlock extends BaseEntityBlock {
private final static VoxelShape SHAPE_S = Block.box(0, 0, 2, 16, 4, 14); private final static VoxelShape SHAPE_S = Block.box(0, 0, 2, 16, 4, 14);
private final static VoxelShape SHAPE_E = Block.box(2, 0, 0, 14, 4, 16); 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 final static VoxelShape SHAPE_W = Block.box(2, 0, 0, 14, 4, 16);
public SuitcaseBlock(BlockBehaviour.Properties properties) { public SuitcaseBlock(BlockBehaviour.Properties properties) {
super(properties); super(properties);
@ -119,6 +119,42 @@ public class SuitcaseBlock extends BaseEntityBlock {
@Override @Override
public void onRemove(BlockState state, @NotNull Level world, @NotNull BlockPos pos, BlockState newState, boolean isMoving) { public void onRemove(BlockState state, @NotNull Level world, @NotNull BlockPos pos, BlockState newState, boolean isMoving) {
if (!state.is(newState.getBlock())) { 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); super.onRemove(state, world, pos, newState, isMoving);
} }
} }
@ -227,41 +263,12 @@ public class SuitcaseBlock extends BaseEntityBlock {
player.fallDistance = 0f; player.fallDistance = 0f;
PlayerEntryData ped = PlayerEntryData.get(targetWorld); PlayerEntryData ped = PlayerEntryData.get(targetWorld);
// 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(); Vec3 dest = ped.getEntryPos();
float entryYaw = ped.getEntryYaw();
float yaw = ped.getEntryYaw();
float pitch = player.getXRot(); float pitch = player.getXRot();
// Ensure destination chunk is loaded before teleportation player.teleportTo(targetWorld, dest.x, dest.y, dest.z, yaw, pitch);
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)); player.connection.send(new ClientboundStopSoundPacket(null, null));
@ -276,8 +283,6 @@ public class SuitcaseBlock extends BaseEntityBlock {
} }
} }
@Override @Override
public @NotNull VoxelShape getShape(BlockState state, @NotNull BlockGetter world, @NotNull BlockPos pos, @NotNull CollisionContext context) { public @NotNull VoxelShape getShape(BlockState state, @NotNull BlockGetter world, @NotNull BlockPos pos, @NotNull CollisionContext context) {
return switch (state.getValue(FACING)) { return switch (state.getValue(FACING)) {

View File

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

View File

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

View File

@ -21,14 +21,9 @@ public class ServerChunkManagerMixin {
private void onRunDistanceManagerUpdates(CallbackInfoReturnable<Boolean> cir) { private void onRunDistanceManagerUpdates(CallbackInfoReturnable<Boolean> cir) {
// Only apply special chunk processing logic to RuntimeWorld instances (fantasy dimensions) // Only apply special chunk processing logic to RuntimeWorld instances (fantasy dimensions)
if (this.level instanceof RuntimeWorld) { if (this.level instanceof RuntimeWorld) {
FantasyWorldAccess worldAccess = (FantasyWorldAccess) this.level; if (!((FantasyWorldAccess) this.level).fantasy$shouldTick()) {
// Always allow distance manager updates if chunks are loaded (prevents breaking entity initialization) cir.setReturnValue(false);
// 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 // Regular worlds (overworld, nether, end) process chunks normally without interference
} }