381 lines
18 KiB
Java
Raw Normal View History

package io.lampnet.travelerssuitcase.block;
import io.lampnet.travelerssuitcase.TravelersSuitcase;
import io.lampnet.travelerssuitcase.block.entity.ModBlockEntities;
import io.lampnet.travelerssuitcase.block.entity.SuitcaseBlockEntity;
import io.lampnet.travelerssuitcase.data.PlayerEntryData;
import io.lampnet.travelerssuitcase.item.KeystoneItem;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.level.storage.loot.LootParams;
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundSource;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.network.chat.Component;
import net.minecraft.ChatFormatting;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
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;
2025-08-03 01:10:46 -04:00
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.item.DyeColor;
import net.minecraft.network.protocol.game.ClientboundStopSoundPacket;
2025-05-10 16:52:22 -04:00
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
2025-05-10 16:52:22 -04:00
import java.util.*;
public class SuitcaseBlock extends BaseEntityBlock {
public static final BooleanProperty OPEN = BooleanProperty.create("open");
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public static final EnumProperty<DyeColor> COLOR = EnumProperty.create("color", DyeColor.class);
private final static VoxelShape SHAPE_N = 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_W = Block.box(2, 0, 0, 14, 4, 16);
2025-08-03 01:10:46 -04:00
public SuitcaseBlock(BlockBehaviour.Properties properties) {
super(properties);
this.registerDefaultState(this.stateDefinition.any()
.setValue(OPEN, false)
.setValue(FACING, Direction.NORTH)
.setValue(COLOR, DyeColor.BROWN));
}
@Override
2025-05-10 16:52:22 -04:00
public @NotNull RenderShape getRenderShape(@NotNull BlockState pState) {
return RenderShape.MODEL;
}
@Nullable
@Override
2025-05-10 16:52:22 -04:00
public BlockEntity newBlockEntity(@NotNull BlockPos pPos, @NotNull BlockState pState) {
return new SuitcaseBlockEntity(pPos, pState);
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> pBuilder) {
pBuilder.add(OPEN, FACING, COLOR);
}
@Override
2025-05-10 16:52:22 -04:00
public void setPlacedBy(@NotNull Level world, @NotNull BlockPos pos, @NotNull BlockState state, @Nullable LivingEntity placer, @NotNull ItemStack itemStack) {
super.setPlacedBy(world, pos, state, placer, itemStack);
if (!world.isClientSide()) {
BlockEntity blockEntity = world.getBlockEntity(pos);
if (blockEntity instanceof SuitcaseBlockEntity suitcase) {
CompoundTag blockEntityTag = BlockItem.getBlockEntityData(itemStack);
if (blockEntityTag != null) {
suitcase.load(blockEntityTag);
String keystoneName = suitcase.getBoundKeystoneName();
if (keystoneName != null) {
List<SuitcaseBlockEntity.EnteredPlayerData> players = suitcase.getEnteredPlayers();
for (SuitcaseBlockEntity.EnteredPlayerData player : players) {
2025-05-10 16:52:22 -04:00
suitcase.updatePlayerSuitcasePosition(player.uuid(), pos);
Map<String, BlockPos> suitcases = SuitcaseBlockEntity.SUITCASE_REGISTRY.computeIfAbsent(
keystoneName, k -> new HashMap<>()
);
2025-05-10 16:52:22 -04:00
suitcases.put(player.uuid(), pos);
}
}
}
}
}
}
@Override
2025-05-10 16:52:22 -04:00
public void onRemove(BlockState state, @NotNull Level world, @NotNull BlockPos pos, BlockState newState, boolean isMoving) {
if (!state.is(newState.getBlock())) {
super.onRemove(state, world, pos, newState, isMoving);
}
}
@Override
2025-05-10 16:52:22 -04:00
public @NotNull InteractionResult use(@NotNull BlockState state, Level world, @NotNull BlockPos pos, @NotNull Player player, @NotNull InteractionHand hand, @NotNull BlockHitResult hit) {
if (world.isClientSide()) {
return InteractionResult.SUCCESS;
}
ItemStack heldItem = player.getItemInHand(hand);
BlockEntity blockEntity = world.getBlockEntity(pos);
if (!(blockEntity instanceof SuitcaseBlockEntity suitcase)) {
return InteractionResult.PASS;
}
if (!suitcase.canOpenInDimension(world)) {
world.playSound(null, pos, SoundEvents.IRON_DOOR_CLOSE,
SoundSource.BLOCKS, 0.3F, 2.0F);
player.displayClientMessage(Component.literal("§c☒"), true);
return InteractionResult.SUCCESS;
}
String boundKeystone = suitcase.getBoundKeystoneName();
if (heldItem.getItem() instanceof KeystoneItem) {
String keystoneName = heldItem.getHoverName().getString().toLowerCase();
if (boundKeystone != null && boundKeystone.equals(keystoneName)) {
boolean newLockState = !suitcase.isLocked();
suitcase.setLocked(newLockState);
world.playSound(null, pos,
newLockState ? SoundEvents.IRON_DOOR_CLOSE : SoundEvents.IRON_DOOR_OPEN,
SoundSource.BLOCKS, 0.3F, 2.0F);
player.displayClientMessage(Component.literal(newLockState ? "§7☒" : "§7☐"), true);
return InteractionResult.SUCCESS;
}
if (suitcase.isLocked()) {
world.playSound(null, pos, SoundEvents.IRON_DOOR_CLOSE,
SoundSource.BLOCKS, 0.3F, 2.0F);
player.displayClientMessage(Component.literal("§c☒"), true);
return InteractionResult.FAIL;
}
if (keystoneName.equals(Component.translatable("item.travelerssuitcase.keystone").getString().toLowerCase()) || !KeystoneItem.isValidKeystone(heldItem)) {
player.displayClientMessage(Component.literal("§cName the key to bind or ensure it is valid."), false);
return InteractionResult.FAIL;
}
suitcase.bindKeystone(keystoneName);
world.playSound(null, pos, SoundEvents.LODESTONE_COMPASS_LOCK,
SoundSource.BLOCKS, 2.0F, 0.0F);
return InteractionResult.SUCCESS;
}
if (!player.isShiftKeyDown() || heldItem.isEmpty()) {
if (boundKeystone == null) {
world.playSound(null, pos, SoundEvents.CHAIN_PLACE,
SoundSource.BLOCKS, 0.5F, 2.0F);
return InteractionResult.FAIL;
}
if (suitcase.isLocked()) {
world.playSound(null, pos, SoundEvents.IRON_DOOR_CLOSE,
SoundSource.BLOCKS, 0.3F, 2.0F);
return InteractionResult.FAIL;
}
boolean isOpen = state.getValue(OPEN);
world.setBlockAndUpdate(pos, state.setValue(OPEN, !isOpen));
if (!isOpen) {
world.playSound(null, pos, SoundEvents.LADDER_STEP,
SoundSource.BLOCKS, 0.3F, 0.0F);
world.playSound(null, pos, SoundEvents.CHEST_LOCKED,
SoundSource.BLOCKS, 0.3F, 2.0F);
} else {
world.playSound(null, pos, SoundEvents.LADDER_BREAK,
SoundSource.BLOCKS, 0.3F, 0.0F);
world.playSound(null, pos, SoundEvents.BAMBOO_WOOD_TRAPDOOR_CLOSE,
SoundSource.BLOCKS, 0.3F, 0.0F);
}
return InteractionResult.SUCCESS;
}
return InteractionResult.PASS;
}
@Override
2025-05-10 16:52:22 -04:00
public void entityInside(@NotNull BlockState state, Level world, @NotNull BlockPos pos, @NotNull Entity entity) {
if (!world.isClientSide() && entity instanceof ServerPlayer player) {
if (!state.getValue(OPEN) || !player.isShiftKeyDown()) {
return;
}
BlockEntity blockEntity = world.getBlockEntity(pos);
if (!(blockEntity instanceof SuitcaseBlockEntity suitcase)) {
return;
}
String keystoneName = suitcase.getBoundKeystoneName();
if (keystoneName == null) {
return;
}
String dimensionName = "pocket_dimension_" + keystoneName;
ResourceLocation dimensionRL = ResourceLocation.fromNamespaceAndPath(TravelersSuitcase.MODID, dimensionName);
ResourceKey<Level> dimensionKey = ResourceKey.create(Registries.DIMENSION, dimensionRL);
2025-05-10 16:52:22 -04:00
ServerLevel targetWorld = Objects.requireNonNull(world.getServer()).getLevel(dimensionKey);
if (targetWorld != null) {
boolean wasFirstTime = suitcase.isFirstTimeEntering(player);
suitcase.playerEntered(player);
if (wasFirstTime) {
TravelersSuitcase.ENTER_POCKET_DIMENSION.trigger(player);
}
player.stopRiding();
player.setDeltaMovement(Vec3.ZERO);
player.fallDistance = 0f;
PlayerEntryData ped = PlayerEntryData.get(targetWorld);
2025-08-03 01:10:46 -04:00
// 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();
2025-08-03 01:10:46 -04:00
float entryYaw = ped.getEntryYaw();
float pitch = player.getXRot();
2025-08-03 01:10:46 -04:00
// 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));
world.playSound(
null,
pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
SoundEvents.BUNDLE_DROP_CONTENTS,
SoundSource.PLAYERS,
2.0f, 1.0f
);
}
}
}
2025-08-03 01:10:46 -04:00
@Override
2025-05-10 16:52:22 -04:00
public @NotNull VoxelShape getShape(BlockState state, @NotNull BlockGetter world, @NotNull BlockPos pos, @NotNull CollisionContext context) {
return switch (state.getValue(FACING)) {
case SOUTH -> SHAPE_S;
case EAST -> SHAPE_E;
case WEST -> SHAPE_W;
default -> SHAPE_N;
};
}
@Nullable
@Override
public BlockState getStateForPlacement(BlockPlaceContext ctx) {
return this.defaultBlockState()
.setValue(FACING, ctx.getHorizontalDirection().getOpposite())
.setValue(OPEN, false);
}
@Override
2025-05-10 16:52:22 -04:00
public @NotNull ItemStack getCloneItemStack(@NotNull BlockGetter world, @NotNull BlockPos pos, @NotNull BlockState state) {
ItemStack stack = super.getCloneItemStack(world, pos, state);
BlockEntity blockEntity = world.getBlockEntity(pos);
if (blockEntity instanceof SuitcaseBlockEntity suitcase) {
CompoundTag beNbt = new CompoundTag();
suitcase.saveAdditional(beNbt);
2025-05-10 16:52:22 -04:00
if (!beNbt.isEmpty()) {
BlockItem.setBlockEntityData(stack, ModBlockEntities.SUITCASE_BLOCK_ENTITY.get(), beNbt);
}
String boundKeystone = suitcase.getBoundKeystoneName();
if (boundKeystone != null) {
CompoundTag display = stack.getOrCreateTagElement("display");
ListTag lore = new ListTag();
if (!suitcase.getEnteredPlayers().isEmpty()) {
2025-08-03 20:51:43 -04:00
Component warningText = Component.literal("§c⚠ Contains " + suitcase.getEnteredPlayers().size() + " Travelers!")
.withStyle(ChatFormatting.RED);
lore.add(StringTag.valueOf(Component.Serializer.toJson(warningText)));
}
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);
}
lore.add(StringTag.valueOf(Component.Serializer.toJson(boundText)));
Component lockText = Component.literal(suitcase.isLocked() ? "§cLocked" : "§aUnlocked")
.withStyle(ChatFormatting.GRAY);
lore.add(StringTag.valueOf(Component.Serializer.toJson(lockText)));
display.put("Lore", lore);
}
}
return stack;
}
@Override
2025-05-10 16:52:22 -04:00
public @NotNull List<ItemStack> getDrops(@NotNull BlockState state, LootParams.Builder builder) {
ItemStack stack = new ItemStack(this);
BlockEntity blockEntity = builder.getOptionalParameter(LootContextParams.BLOCK_ENTITY);
if (blockEntity instanceof SuitcaseBlockEntity suitcase) {
CompoundTag beNbt = new CompoundTag();
suitcase.saveAdditional(beNbt);
2025-05-10 16:52:22 -04:00
if (!beNbt.isEmpty()) {
BlockItem.setBlockEntityData(stack, ModBlockEntities.SUITCASE_BLOCK_ENTITY.get(), beNbt);
}
String boundKeystone = suitcase.getBoundKeystoneName();
if (boundKeystone != null) {
CompoundTag display = stack.getOrCreateTagElement("display");
ListTag lore = new ListTag();
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)));
}
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);
}
lore.add(StringTag.valueOf(Component.Serializer.toJson(boundText)));
Component lockText = Component.literal(suitcase.isLocked() ? "§cLocked" : "§aUnlocked")
.withStyle(ChatFormatting.GRAY);
lore.add(StringTag.valueOf(Component.Serializer.toJson(lockText)));
display.put("Lore", lore);
}
}
return Collections.singletonList(stack);
}
@Override
2025-05-10 16:52:22 -04:00
public boolean triggerEvent(@NotNull BlockState state, @NotNull Level world, @NotNull BlockPos pos, int type, int data) {
super.triggerEvent(state, world, pos, type, data);
BlockEntity blockentity = world.getBlockEntity(pos);
2025-05-10 16:52:22 -04:00
return blockentity != null && blockentity.triggerEvent(type, data);
}
}