From f724faaf86be4fea2340e680299f14769eeb95a0 Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 12 Sep 2025 13:21:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(block):=20=E6=B7=BB=E5=8A=A0=E5=BA=A7?= =?UTF-8?q?=E6=A4=85=E6=96=B9=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 2 + .../block/behavior/SeatBlockBehavior.java | 77 ++++++++ .../block/entity/BukkitBlockEntityTypes.java | 1 + .../bukkit/block/entity/SeatBlockEntity.java | 179 ++++++++++++++++++ .../furniture/BukkitFurnitureManager.java | 3 + .../default/configuration/blocks.yml | 13 +- .../block/behavior/EntityBlockBehavior.java | 5 + .../block/entity/BlockEntityTypeKeys.java | 1 + 8 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java index 34435ad14..f13476d45 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/BukkitBlockBehaviors.java @@ -32,6 +32,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key TOGGLEABLE_LAMP_BLOCK = Key.from("craftengine:toggleable_lamp_block"); public static final Key SOFA_BLOCK = Key.from("craftengine:sofa_block"); public static final Key BOUNCING_BLOCK = Key.from("craftengine:bouncing_block"); + public static final Key SEAT_BLOCK = Key.from("craftengine:seat_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -62,5 +63,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(TOGGLEABLE_LAMP_BLOCK, ToggleableLampBlockBehavior.FACTORY); register(SOFA_BLOCK, SofaBlockBehavior.FACTORY); register(BOUNCING_BLOCK, BouncingBlockBehavior.FACTORY); + register(SEAT_BLOCK, SeatBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java new file mode 100644 index 000000000..252c8b11d --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SeatBlockBehavior.java @@ -0,0 +1,77 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.block.entity.BukkitBlockEntityTypes; +import net.momirealms.craftengine.bukkit.block.entity.SeatBlockEntity; +import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; +import net.momirealms.craftengine.core.block.BlockBehavior; +import net.momirealms.craftengine.core.block.CustomBlock; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory; +import net.momirealms.craftengine.core.block.behavior.EntityBlockBehavior; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.BlockEntityType; +import net.momirealms.craftengine.core.block.entity.tick.BlockEntityTicker; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +import net.momirealms.craftengine.core.item.context.UseOnContext; +import net.momirealms.craftengine.core.util.ResourceConfigUtils; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; +import org.joml.Vector3f; + +import java.util.Map; + +public class SeatBlockBehavior extends BukkitBlockBehavior implements EntityBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final Vector3f offset; + private final float yaw; + private final boolean limitPlayerRotation; + + public SeatBlockBehavior(CustomBlock customBlock, Vector3f offset, float yaw, boolean limitPlayerRotation) { + super(customBlock); + this.offset = offset; + this.yaw = yaw; + this.limitPlayerRotation = limitPlayerRotation; + } + + @Override + public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { + BukkitServerPlayer player = (BukkitServerPlayer) context.getPlayer(); + if (player == null || player.isSecondaryUseActive() || player.platformPlayer() == null) { + return InteractionResult.PASS; + } + CEWorld world = context.getLevel().storageWorld(); + BlockEntity blockEntity = world.getBlockEntityAtIfLoaded(context.getClickedPos()); + if (!(blockEntity instanceof SeatBlockEntity seatBlockEntity) || !seatBlockEntity.seatEntities().isEmpty()) { + return InteractionResult.PASS; + } + seatBlockEntity.spawnSeatEntityForPlayer(player.platformPlayer(), this.offset, this.yaw, this.limitPlayerRotation); + return InteractionResult.SUCCESS_AND_CANCEL; + } + + @SuppressWarnings("unchecked") + @Override + public BlockEntityType blockEntityType() { + return (BlockEntityType) BukkitBlockEntityTypes.SEAT; + } + + @Override + public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) { + return new SeatBlockEntity(pos, state); + } + + @Override + public BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { + return EntityBlockBehavior.createTickerHelper(SeatBlockEntity::tick); + } + + public static class Factory implements BlockBehaviorFactory { + + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + Vector3f offset = ResourceConfigUtils.getAsVector3f(arguments.getOrDefault("offset", "0,0,0"), "offset"); + float yaw = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("yaw", 0f), "yaw"); + boolean limitPlayerRotation = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("limit-player-rotation", true), "limit-player-rotation"); + return new SeatBlockBehavior(block, offset, yaw, limitPlayerRotation); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java index 3e778bd32..c739d0bc4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/BukkitBlockEntityTypes.java @@ -6,4 +6,5 @@ import net.momirealms.craftengine.core.block.entity.BlockEntityTypes; public class BukkitBlockEntityTypes extends BlockEntityTypes { public static final BlockEntityType SIMPLE_STORAGE = register(BlockEntityTypeKeys.SIMPLE_STORAGE, SimpleStorageBlockEntity::new); + public static final BlockEntityType SEAT = register(BlockEntityTypeKeys.SEAT, SeatBlockEntity::new); } \ No newline at end of file diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java new file mode 100644 index 000000000..40f6473cf --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/entity/SeatBlockEntity.java @@ -0,0 +1,179 @@ +package net.momirealms.craftengine.bukkit.block.entity; + +import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import net.momirealms.craftengine.bukkit.util.EntityUtils; +import net.momirealms.craftengine.bukkit.util.LegacyAttributeUtils; +import net.momirealms.craftengine.core.block.ImmutableBlockState; +import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.util.HorizontalDirection; +import net.momirealms.craftengine.core.util.QuaternionUtils; +import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.world.BlockPos; +import net.momirealms.craftengine.core.world.CEWorld; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.attribute.Attribute; +import org.bukkit.entity.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.joml.Vector3f; + +import java.util.Map; +import java.util.Objects; + +@SuppressWarnings("DuplicatedCode") +public class SeatBlockEntity extends BlockEntity { + private final Map seatEntities = new Reference2ObjectArrayMap<>(1); + + public SeatBlockEntity(BlockPos pos, ImmutableBlockState blockState) { + super(BukkitBlockEntityTypes.SEAT, pos, blockState); + } + + public Map seatEntities() { + return this.seatEntities; + } + + public static void tick(CEWorld world, BlockPos pos, ImmutableBlockState state, SeatBlockEntity seat) { + if (seat.seatEntities.isEmpty()) return; + for (Map.Entry entry : seat.seatEntities.entrySet()) { + Entity entity = entry.getKey(); + if (!entity.getPassengers().isEmpty()) continue; + Player player = entry.getValue(); + seat.tryLeavingSeat(player, entity); + seat.seatEntities.remove(entity); + } + } + + @Override + public void preRemove() { + if (this.seatEntities.isEmpty()) return; + for (Map.Entry entry : this.seatEntities.entrySet()) { + Entity entity = entry.getKey(); + entity.remove(); + this.seatEntities.remove(entity); + } + this.seatEntities.clear(); + } + + public void spawnSeatEntityForPlayer(@NotNull Player player, @NotNull Vector3f offset, float yaw, boolean limitPlayerRotation) { + if (!this.seatEntities.isEmpty() || !this.isValid()) return; + Location location = calculateSeatLocation(player, this.pos, this.blockState, offset, yaw); + Entity seatEntity = limitPlayerRotation ? + EntityUtils.spawnEntity(player.getWorld(), + VersionHelper.isOrAbove1_20_2() ? location.subtract(0, 0.9875, 0) : location.subtract(0, 0.990625, 0), + EntityType.ARMOR_STAND, + entity -> { + ArmorStand armorStand = (ArmorStand) entity; + if (VersionHelper.isOrAbove1_21_3()) { + Objects.requireNonNull(armorStand.getAttribute(Attribute.MAX_HEALTH)).setBaseValue(0.01); + } else { + LegacyAttributeUtils.setMaxHealth(armorStand); + } + armorStand.setSmall(true); + armorStand.setInvisible(true); + armorStand.setSilent(true); + armorStand.setInvulnerable(true); + armorStand.setArms(false); + armorStand.setCanTick(false); + armorStand.setAI(false); + armorStand.setGravity(false); + armorStand.setPersistent(false); + }) : + EntityUtils.spawnEntity(player.getWorld(), + VersionHelper.isOrAbove1_20_2() ? location : location.subtract(0, 0.25, 0), + EntityType.ITEM_DISPLAY, + entity -> { + ItemDisplay itemDisplay = (ItemDisplay) entity; + itemDisplay.setPersistent(false); + }); + if (!seatEntity.addPassenger(player)) { + seatEntity.remove(); + return; + } + this.seatEntities.put(seatEntity, player); + } + + private Location calculateSeatLocation(Player player, BlockPos pos, ImmutableBlockState state, Vector3f offset, float yaw) { + Location location = new Location(player.getWorld(), pos.x() + 0.5, pos.y() + 0.5, pos.z() + 0.5); + for (Property property : state.getProperties()) { + if (property.name().equals("facing") && property.valueClass() == HorizontalDirection.class) { + switch ((HorizontalDirection) state.get(property)) { + case NORTH -> location.setYaw(0); + case SOUTH -> location.setYaw(180); + case WEST -> location.setYaw(270); + case EAST -> location.setYaw(90); + } + break; + } + if (property.name().equals("facing_clockwise") && property.valueClass() == HorizontalDirection.class) { + switch ((HorizontalDirection) state.get(property)) { + case NORTH -> location.setYaw(90); + case SOUTH -> location.setYaw(270); + case WEST -> location.setYaw(0); + case EAST -> location.setYaw(180); + } + break; + } + } + Vector3f newOffset = QuaternionUtils.toQuaternionf(0, Math.toRadians(180 - location.getYaw()), 0) + .conjugate() + .transform(new Vector3f(offset)); + double newYaw = yaw + location.getYaw(); + if (newYaw < -180) newYaw += 360; + Location newLocation = location.clone(); + newLocation.setYaw((float) newYaw); + newLocation.add(newOffset.x, newOffset.y + 0.6, -newOffset.z); + return newLocation; + } + + private void tryLeavingSeat(@NotNull Player player, @NotNull Entity vehicle) { + vehicle.remove(); + if (player.getVehicle() != null) return; + Location vehicleLocation = vehicle.getLocation(); + Location originalLocation = vehicleLocation.clone(); + originalLocation.setY(this.pos.y()); + Location targetLocation = originalLocation.clone().add(vehicleLocation.getDirection().multiply(1.1)); + if (!isSafeLocation(targetLocation)) { + targetLocation = findSafeLocationNearby(originalLocation); + if (targetLocation == null) return; + } + targetLocation.setYaw(player.getLocation().getYaw()); + targetLocation.setPitch(player.getLocation().getPitch()); + if (VersionHelper.isFolia()) { + player.teleportAsync(targetLocation); + } else { + player.teleport(targetLocation); + } + } + + private boolean isSafeLocation(Location location) { + World world = location.getWorld(); + if (world == null) return false; + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + if (!world.getBlockAt(x, y - 1, z).getType().isSolid()) return false; + if (!world.getBlockAt(x, y, z).isPassable()) return false; + return world.getBlockAt(x, y + 1, z).isPassable(); + } + + @Nullable + private Location findSafeLocationNearby(Location center) { + World world = center.getWorld(); + if (world == null) return null; + int centerX = center.getBlockX(); + int centerY = center.getBlockY(); + int centerZ = center.getBlockZ(); + for (int dx = -1; dx <= 1; dx++) { + for (int dz = -1; dz <= 1; dz++) { + if (dx == 0 && dz == 0) continue; + int x = centerX + dx; + int z = centerZ + dz; + Location nearbyLocation = new Location(world, x + 0.5, centerY, z + 0.5); + if (isSafeLocation(nearbyLocation)) return nearbyLocation; + } + } + return null; + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java index 7cc579dce..b7e58f8be 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/entity/furniture/BukkitFurnitureManager.java @@ -337,6 +337,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { plugin.scheduler().sync().runDelayed(() -> tryLeavingSeat(player, entity), player.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4); } + @SuppressWarnings("DuplicatedCode") protected void tryLeavingSeat(@NotNull Player player, @NotNull Entity vehicle) { Integer baseFurniture = vehicle.getPersistentDataContainer().get(FURNITURE_SEAT_BASE_ENTITY_KEY, PersistentDataType.INTEGER); if (baseFurniture == null) return; @@ -375,6 +376,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { return (entity instanceof ArmorStand || entity instanceof ItemDisplay); } + @SuppressWarnings("DuplicatedCode") private boolean isSafeLocation(Location location) { World world = location.getWorld(); if (world == null) return false; @@ -386,6 +388,7 @@ public class BukkitFurnitureManager extends AbstractFurnitureManager { return world.getBlockAt(x, y + 1, z).isPassable(); } + @SuppressWarnings("DuplicatedCode") @Nullable private Location findSafeLocationNearby(Location center) { World world = center.getWorld(); diff --git a/common-files/src/main/resources/resources/default/configuration/blocks.yml b/common-files/src/main/resources/resources/default/configuration/blocks.yml index 2384cba31..f3b182d5b 100644 --- a/common-files/src/main/resources/resources/default/configuration/blocks.yml +++ b/common-files/src/main/resources/resources/default/configuration/blocks.yml @@ -587,10 +587,13 @@ items#misc: step: minecraft:block.wood.step tags: - minecraft:mineable/axe - behavior: - type: bouncing_block - bounce-height: 0.66 - sync-player-position: false + behaviors: + - type: bouncing_block + bounce-height: 0.66 + sync-player-position: false + - type: seat_block + offset: 0,-0.5,0 + limit-player-rotation: false state: id: 0 state: white_bed[facing=west,occupied=false,part=foot] @@ -638,6 +641,8 @@ items#misc: - type: sofa_block - type: bouncing_block bounce-height: 0.66 + - type: seat_block + offset: 0,-0.45,0 states: properties: facing: diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java index e994ca03e..5df15876e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/behavior/EntityBlockBehavior.java @@ -18,4 +18,9 @@ public interface EntityBlockBehavior { default BlockEntityTicker createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType blockEntityType) { return null; } + + @SuppressWarnings("unchecked") + static BlockEntityTicker createTickerHelper(BlockEntityTicker ticker) { + return (BlockEntityTicker) ticker; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java index cb452fd4d..b801d9156 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/BlockEntityTypeKeys.java @@ -7,4 +7,5 @@ public final class BlockEntityTypeKeys { public static final Key UNSAFE_COMPOSITE = Key.of("craftengine:unsafe_composite"); public static final Key SIMPLE_STORAGE = Key.of("craftengine:simple_storage"); + public static final Key SEAT = Key.of("craftengine:seat"); }