9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-25 01:49:30 +00:00

feat(block): 添加座椅方块

This commit is contained in:
jhqwqmc
2025-09-12 13:21:41 +08:00
parent c285e6be78
commit f724faaf86
8 changed files with 277 additions and 4 deletions

View File

@@ -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);
}
}

View File

@@ -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 <T extends BlockEntity> BlockEntityType<T> blockEntityType() {
return (BlockEntityType<T>) BukkitBlockEntityTypes.SEAT;
}
@Override
public BlockEntity createBlockEntity(BlockPos pos, ImmutableBlockState state) {
return new SeatBlockEntity(pos, state);
}
@Override
public <T extends BlockEntity> BlockEntityTicker<T> createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType<T> blockEntityType) {
return EntityBlockBehavior.createTickerHelper(SeatBlockEntity::tick);
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> 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);
}
}
}

View File

@@ -6,4 +6,5 @@ import net.momirealms.craftengine.core.block.entity.BlockEntityTypes;
public class BukkitBlockEntityTypes extends BlockEntityTypes {
public static final BlockEntityType<SimpleStorageBlockEntity> SIMPLE_STORAGE = register(BlockEntityTypeKeys.SIMPLE_STORAGE, SimpleStorageBlockEntity::new);
public static final BlockEntityType<SeatBlockEntity> SEAT = register(BlockEntityTypeKeys.SEAT, SeatBlockEntity::new);
}

View File

@@ -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<Entity, Player> seatEntities = new Reference2ObjectArrayMap<>(1);
public SeatBlockEntity(BlockPos pos, ImmutableBlockState blockState) {
super(BukkitBlockEntityTypes.SEAT, pos, blockState);
}
public Map<Entity, Player> seatEntities() {
return this.seatEntities;
}
public static void tick(CEWorld world, BlockPos pos, ImmutableBlockState state, SeatBlockEntity seat) {
if (seat.seatEntities.isEmpty()) return;
for (Map.Entry<Entity, Player> 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<Entity, Player> 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;
}
}

View File

@@ -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();

View File

@@ -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:

View File

@@ -18,4 +18,9 @@ public interface EntityBlockBehavior {
default <T extends BlockEntity> BlockEntityTicker<T> createBlockEntityTicker(CEWorld level, ImmutableBlockState state, BlockEntityType<T> blockEntityType) {
return null;
}
@SuppressWarnings("unchecked")
static <E extends BlockEntity, A extends BlockEntity> BlockEntityTicker<A> createTickerHelper(BlockEntityTicker<? super E> ticker) {
return (BlockEntityTicker<A>) ticker;
}
}

View File

@@ -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");
}