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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user