Compare commits
2 Commits
54c9024615
...
57f0f340a9
| Author | SHA1 | Date | |
|---|---|---|---|
| 57f0f340a9 | |||
| 881ba1a9a2 |
@ -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<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) {
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
@ -66,6 +64,8 @@ public class SuitcaseBlock extends BaseEntityBlock {
|
||||
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_W = Block.box(2, 0, 0, 14, 4, 16);
|
||||
|
||||
|
||||
|
||||
public SuitcaseBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
@ -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);
|
||||
|
||||
// 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<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)) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user