From 90a643def21cb5ff02a4f064e0a86a384543c00b Mon Sep 17 00:00:00 2001 From: jhqwqmc <2110242767@qq.com> Date: Fri, 20 Jun 2025 11:11:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(block):=20=E5=AE=9E=E7=8E=B0=E6=A0=85?= =?UTF-8?q?=E6=A0=8F=E9=97=A8=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/behavior/BukkitBlockBehaviors.java | 3 +- .../behavior/FenceGateBlockBehavior.java | 281 ++++++++++++++++++ .../plugin/reflection/minecraft/MTagKey.java | 18 ++ gradle.properties | 2 +- 4 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MTagKey.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 07082539c..70cc2a541 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 @@ -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); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java new file mode 100644 index 000000000..49f14f4d1 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FenceGateBlockBehavior.java @@ -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 facingProperty; + private final Property inWallProperty; + private final Property openProperty; + private final Property poweredProperty; + private final boolean canOpenWithHand; + private final boolean canOpenByWindCharge; + private final SoundData openSound; + private final SoundData closeSound; + + public FenceGateBlockBehavior( + CustomBlock customBlock, + Property facing, + Property inWall, + Property open, + Property 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 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) context.getItem())) { + player.swingHand(context.getHand()); + } + } + + @Override + public boolean isPathFindable(Object thisBlock, Object[] args, Callable 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 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 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 arguments) { + Property facing = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.fence_gate.missing_facing"); + Property inWall = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("in_wall"), "warning.config.block.behavior.fence_gate.missing_in_wall"); + Property open = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("open"), "warning.config.block.behavior.fence_gate.missing_open"); + Property powered = (Property) 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 sounds = (Map) 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); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MTagKey.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MTagKey.java new file mode 100644 index 000000000..07d6dc00c --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MTagKey.java @@ -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); + } +} diff --git a/gradle.properties b/gradle.properties index a065f1c6c..cd8da7527 100644 --- a/gradle.properties +++ b/gradle.properties @@ -51,7 +51,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.4 anti_grief_version=0.17 -nms_helper_version=0.67.26 +nms_helper_version=0.67.27 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.31.23