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

feat(block): 实现栅栏门行为

This commit is contained in:
jhqwqmc
2025-06-20 11:11:11 +08:00
parent 5c954f9fc6
commit 90a643def2
4 changed files with 302 additions and 2 deletions

View File

@@ -13,7 +13,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
public static final Key SAPLING_BLOCK = Key.from("craftengine:sapling_block");
public static final Key ON_LIQUID_BLOCK = Key.from("craftengine:on_liquid_block");
public static final Key NEAR_LIQUID_BLOCK = Key.from("craftengine:near_liquid_block");
public static final Key WATERLOGGED_BLOCK = Key.from("craftengine:waterlogged_block");
public static final Key CONCRETE_POWDER_BLOCK = Key.from("craftengine:concrete_powder_block");
public static final Key VERTICAL_CROP_BLOCK = Key.from("craftengine:vertical_crop_block");
public static final Key CROP_BLOCK = Key.from("craftengine:crop_block");
@@ -23,6 +22,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
public static final Key DOOR_BLOCK = Key.from("craftengine:door_block");
public static final Key STACKABLE_BLOCK = Key.from("craftengine:stackable_block");
public static final Key STURDY_BASE_BLOCK = Key.from("craftengine:sturdy_base_block");
public static final Key FENCE_GATE_BLOCK = Key.from("craftengine:fence_gate_block");
public static void init() {
register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE);
@@ -43,5 +43,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
register(DOOR_BLOCK, DoorBlockBehavior.FACTORY);
register(STACKABLE_BLOCK, StackableBlockBehavior.FACTORY);
register(STURDY_BASE_BLOCK, SturdyBaseBlockBehavior.FACTORY);
register(FENCE_GATE_BLOCK, FenceGateBlockBehavior.FACTORY);
}
}

View File

@@ -0,0 +1,281 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.item.BukkitItemManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MBlocks;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MTagKey;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
import net.momirealms.craftengine.bukkit.util.InteractUtils;
import net.momirealms.craftengine.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
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.UpdateOption;
import net.momirealms.craftengine.core.block.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.entity.player.InteractionResult;
import net.momirealms.craftengine.core.entity.player.Player;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.item.ItemKeys;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.item.context.UseOnContext;
import net.momirealms.craftengine.core.sound.SoundData;
import net.momirealms.craftengine.core.util.Direction;
import net.momirealms.craftengine.core.util.HorizontalDirection;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.World;
import org.bukkit.Bukkit;
import org.bukkit.GameEvent;
import org.bukkit.block.Block;
import org.bukkit.event.block.BlockRedstoneEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
public class FenceGateBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Property<HorizontalDirection> facingProperty;
private final Property<Boolean> inWallProperty;
private final Property<Boolean> openProperty;
private final Property<Boolean> poweredProperty;
private final boolean canOpenWithHand;
private final boolean canOpenByWindCharge;
private final SoundData openSound;
private final SoundData closeSound;
public FenceGateBlockBehavior(
CustomBlock customBlock,
Property<HorizontalDirection> facing,
Property<Boolean> inWall,
Property<Boolean> open,
Property<Boolean> powered,
boolean canOpenWithHand,
boolean canOpenByWindCharge,
SoundData openSound,
SoundData closeSound
) {
super(customBlock);
this.facingProperty = facing;
this.inWallProperty = inWall;
this.openProperty = open;
this.poweredProperty = powered;
this.canOpenWithHand = canOpenWithHand;
this.canOpenByWindCharge = canOpenByWindCharge;
this.openSound = openSound;
this.closeSound = closeSound;
}
public boolean isOpen(ImmutableBlockState state) {
if (state == null || state.isEmpty() || !state.contains(this.openProperty)) return false;
return state.get(this.openProperty);
}
public boolean isWall(Object state) {
if (state == null) return false;
return FastNMS.INSTANCE.method$BlockStateBase$isTagKeyBlock(state, MTagKey.Block$WALLS);
}
private Object getBlockState(Object level, BlockPos blockPos) {
return FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, LocationUtils.toBlockPos(blockPos));
}
@Override
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object blockState = args[0];
Direction direction = DirectionUtils.fromNMSDirection(VersionHelper.isOrAbove1_21_2() ? args[4] : args[0]);
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
if (state == null || state.isEmpty()) return blockState;
if (state.get(this.facingProperty).toDirection().clockWise().axis() != direction.axis()) {
return superMethod.call();
}
Object neighborState = VersionHelper.isOrAbove1_21_2() ? args[6] : args[2];
Object level = VersionHelper.isOrAbove1_21_2() ? args[1] : args[3];
BlockPos blockPos = LocationUtils.fromBlockPos(VersionHelper.isOrAbove1_21_2() ? args[3] : args[4]);
Object relativeState = getBlockState(level, blockPos.relative(direction.opposite()));
boolean flag = this.isWall(neighborState) || this.isWall(relativeState);
return state.with(this.inWallProperty, flag).customBlockState().handle();
}
@Override
public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) {
Object level = context.getLevel().serverWorld();
BlockPos clickedPos = context.getClickedPos();
boolean hasNeighborSignal = FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, LocationUtils.toBlockPos(clickedPos));
Direction horizontalDirection = context.getHorizontalDirection();
Direction.Axis axis = horizontalDirection.axis();
boolean flag = axis == Direction.Axis.Z && (this.isWall(getBlockState(level, clickedPos.relative(Direction.WEST))))
|| this.isWall(getBlockState(level, clickedPos.relative(Direction.EAST)))
|| axis == Direction.Axis.X && (this.isWall(getBlockState(level, clickedPos.relative(Direction.NORTH)))
|| this.isWall(getBlockState(level, clickedPos.relative(Direction.SOUTH))));
return state.owner().value().defaultState()
.with(this.facingProperty, horizontalDirection.toHorizontalDirection())
.with(this.openProperty, hasNeighborSignal)
.with(this.poweredProperty, hasNeighborSignal)
.with(this.inWallProperty, flag);
}
@Override
public InteractionResult useOnBlock(UseOnContext context, ImmutableBlockState state) {
if (!this.canOpenWithHand) {
return InteractionResult.PASS;
}
if (context.getItem() == null) {
playerToggle(context, state);
return InteractionResult.SUCCESS;
} else if (!context.getPlayer().isSecondaryUseActive()) {
playerToggle(context, state);
return InteractionResult.SUCCESS_AND_CANCEL;
} else {
return InteractionResult.PASS;
}
}
@SuppressWarnings("unchecked")
private void playerToggle(UseOnContext context, ImmutableBlockState state) {
Player player = context.getPlayer();
this.toggle(state, context.getLevel(), context.getClickedPos(), player);
if (!InteractUtils.isInteractable((org.bukkit.entity.Player) player.platformPlayer(), BlockStateUtils.fromBlockData(state.vanillaBlockState().handle()), context.getHitResult(), (Item<ItemStack>) context.getItem())) {
player.swingHand(context.getHand());
}
}
@Override
public boolean isPathFindable(Object thisBlock, Object[] args, Callable<Object> superMethod) {
Object type = VersionHelper.isOrAbove1_20_5() ? args[1] : args[3];
Object blockState = args[0];
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
if (state == null || state.isEmpty()) return false;
if (type == CoreReflections.instance$PathComputationType$LAND || type == CoreReflections.instance$PathComputationType$AIR) {
return isOpen(state);
}
return false;
}
@Override
public void onExplosionHit(Object thisBlock, Object[] args, Callable<Object> superMethod) {
if (this.canOpenByWindCharge && FastNMS.INSTANCE.method$Explosion$canTriggerBlocks(args[3])) {
ImmutableBlockState state = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(args[0]));
if (state == null || state.isEmpty()) return;
this.toggle(state, new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(args[1])), LocationUtils.fromBlockPos(args[2]), null);
}
}
@SuppressWarnings("UnstableApiUsage")
@Override
public void neighborChanged(Object thisBlock, Object[] args, Callable<Object> superMethod) {
Object blockState = args[0];
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
if (immutableBlockState == null || immutableBlockState.isEmpty()) return;
Object level = args[1];
Object blockPos = args[2];
boolean hasSignal = FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, blockPos);
if (hasSignal == immutableBlockState.get(this.poweredProperty)) return;
Block bblock = FastNMS.INSTANCE.method$CraftBlock$at(level, blockPos);
int power = bblock.getBlockPower();
int oldPower = isOpen(immutableBlockState) ? 15 : 0;
Object neighborBlock = args[3];
if (oldPower == 0 ^ power == 0 || FastNMS.INSTANCE.method$BlockStateBase$isSignalSource(FastNMS.INSTANCE.method$Block$defaultState(neighborBlock))) {
BlockRedstoneEvent event = new BlockRedstoneEvent(bblock, oldPower, power);
Bukkit.getPluginManager().callEvent(event);
hasSignal = event.getNewCurrent() > 0;
}
World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
boolean changed = isOpen(immutableBlockState) != hasSignal;
if (hasSignal && changed) {
Object abovePos = LocationUtils.above(blockPos);
Object aboveBlockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, abovePos);
if (CoreReflections.clazz$RedStoneWireBlock.isInstance(FastNMS.INSTANCE.method$BlockState$getBlock(aboveBlockState))) {
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, abovePos, MBlocks.AIR$defaultState, UpdateOption.UPDATE_ALL.flags());
world.dropItemNaturally(
new Vec3d(FastNMS.INSTANCE.field$Vec3i$x(abovePos) + 0.5, FastNMS.INSTANCE.field$Vec3i$y(abovePos) + 0.5, FastNMS.INSTANCE.field$Vec3i$z(abovePos) + 0.5),
BukkitItemManager.instance().createWrappedItem(ItemKeys.REDSTONE, null)
);
if (FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, blockPos) != blockPos) {
return;
}
}
}
if (changed) {
immutableBlockState = immutableBlockState.with(this.openProperty, hasSignal);
FastNMS.INSTANCE.method$Level$getCraftWorld(level).sendGameEvent(null,
hasSignal ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE,
new Vector(FastNMS.INSTANCE.field$Vec3i$x(blockPos), FastNMS.INSTANCE.field$Vec3i$y(blockPos), FastNMS.INSTANCE.field$Vec3i$z(blockPos))
);
this.playSound(LocationUtils.fromBlockPos(blockPos), world, hasSignal);
}
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, immutableBlockState.with(this.poweredProperty, hasSignal).customBlockState().handle(), UpdateOption.Flags.UPDATE_CLIENTS);
}
private void toggle(ImmutableBlockState state, World world, BlockPos pos, @Nullable Player player) {
ImmutableBlockState newState;
if (state.get(this.openProperty)) {
newState = state.with(this.openProperty, false);
} else {
ImmutableBlockState blockState = state;
if (player != null) {
Direction direction = player.getDirection();
if (state.get(this.facingProperty).toDirection() == direction.opposite()) {
blockState = blockState.with(this.facingProperty, direction.toHorizontalDirection());
}
}
newState = blockState.with(this.openProperty, true);
}
FastNMS.INSTANCE.method$LevelWriter$setBlock(world.serverWorld(), LocationUtils.toBlockPos(pos), newState.customBlockState().handle(), UpdateOption.UPDATE_ALL.flags());
boolean open = isOpen(newState);
((org.bukkit.World) world.platformWorld()).sendGameEvent(
player != null ? (org.bukkit.entity.Player) player.platformPlayer() : null,
open ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE,
new Vector(pos.x(), pos.y(), pos.z())
);
this.playSound(pos, world, open);
}
private void playSound(BlockPos pos, World world, boolean open) {
if (open) {
if (this.openSound != null) {
world.playBlockSound(new Vec3d(pos.x() + 0.5, pos.y() + 0.5, pos.z() + 0.5), this.openSound);
}
} else {
if (this.closeSound != null) {
world.playBlockSound(new Vec3d(pos.x() + 0.5, pos.y() + 0.5, pos.z() + 0.5), this.closeSound);
}
}
}
public static class Factory implements BlockBehaviorFactory {
@Override
@SuppressWarnings("unchecked")
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Property<HorizontalDirection> facing = (Property<HorizontalDirection>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.fence_gate.missing_facing");
Property<Boolean> inWall = (Property<Boolean>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("in_wall"), "warning.config.block.behavior.fence_gate.missing_in_wall");
Property<Boolean> open = (Property<Boolean>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("open"), "warning.config.block.behavior.fence_gate.missing_open");
Property<Boolean> powered = (Property<Boolean>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.fence_gate.missing_powered");
boolean canOpenWithHand = (boolean) arguments.getOrDefault("can-open-with-hand", true);
boolean canOpenByWindCharge = (boolean) arguments.getOrDefault("can-open-by-wind-charge", true);
Map<String, Object> sounds = (Map<String, Object>) arguments.get("sounds");
SoundData openSound = null;
SoundData closeSound = null;
if (sounds != null) {
openSound = Optional.ofNullable(sounds.get("open")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null);
closeSound = Optional.ofNullable(sounds.get("close")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null);
}
return new FenceGateBlockBehavior(block, facing, inWall, open, powered, canOpenWithHand, canOpenByWindCharge, openSound, closeSound);
}
}
}

View File

@@ -0,0 +1,18 @@
package net.momirealms.craftengine.bukkit.plugin.reflection.minecraft;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import java.util.Objects;
public class MTagKey {
private MTagKey() {}
public static final Object Item$WOOL = create(MRegistries.ITEM, "wool");
public static final Object Block$WALLS = create(MRegistries.BLOCK, "walls");
private static Object create(Object registry, String location) {
Object resourceLocation = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", location);
Object tagKey = FastNMS.INSTANCE.method$TagKey$create(registry, resourceLocation);
return Objects.requireNonNull(tagKey);
}
}