From 6ed54af7ab634675ef451bfee01a86e18fc6bcfe Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 19 Sep 2025 09:16:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(block):=20=E6=B7=BB=E5=8A=A0=E6=8C=89?= =?UTF-8?q?=E9=92=AE=E6=96=B9=E5=9D=97=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 | 4 + .../block/behavior/ButtonBlockBehavior.java | 217 ++++++++++++++++++ .../DirectionalAttachedBlockBehavior.java | 20 +- ...hedHorizontalDirectionalBlockBehavior.java | 150 ++++++++++++ .../behavior/PressurePlateBlockBehavior.java | 7 +- .../item/recipe/BukkitRecipeManager.java | 2 +- .../plugin/network/BukkitNetworkManager.java | 9 +- .../reflection/minecraft/CoreReflections.java | 6 + .../minecraft/MBuiltInRegistries.java | 5 + .../reflection/minecraft/MEntitySelector.java | 12 + .../reflection/minecraft/MGameEvent.java | 15 ++ .../craftengine/core/block/BlockBehavior.java | 10 +- .../core/block/properties/Properties.java | 2 + .../block/state/properties/AttachFace.java | 7 + gradle.properties | 2 +- 15 files changed, 444 insertions(+), 24 deletions(-) create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelector.java create mode 100644 bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvent.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AttachFace.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 4070b0f48..fc8ba1cc6 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 @@ -37,6 +37,8 @@ public class BukkitBlockBehaviors extends BlockBehaviors { public static final Key SIMPLE_PARTICLE_BLOCK = Key.from("craftengine:simple_particle_block"); public static final Key WALL_TORCH_PARTICLE_BLOCK = Key.from("craftengine:wall_torch_particle_block"); public static final Key FENCE_BLOCK = Key.from("craftengine:fence_block"); + public static final Key BUTTON_BLOCK = Key.from("craftengine:button_block"); + public static final Key FACE_ATTACHED_HORIZONTAL_DIRECTIONAL_BLOCK = Key.from("craftengine:face_attached_horizontal_directional_block"); public static void init() { register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE); @@ -72,5 +74,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors { register(SIMPLE_PARTICLE_BLOCK, SimpleParticleBlockBehavior.FACTORY); register(WALL_TORCH_PARTICLE_BLOCK, WallTorchParticleBlockBehavior.FACTORY); register(FENCE_BLOCK, FenceBlockBehavior.FACTORY); + register(BUTTON_BLOCK, ButtonBlockBehavior.FACTORY); + register(FACE_ATTACHED_HORIZONTAL_DIRECTIONAL_BLOCK, FaceAttachedHorizontalDirectionalBlockBehavior.FACTORY); } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java new file mode 100644 index 000000000..3be8fa890 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/ButtonBlockBehavior.java @@ -0,0 +1,217 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntitySelector; +import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MGameEvent; +import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; +import net.momirealms.craftengine.bukkit.util.KeyUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +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.BooleanProperty; +import net.momirealms.craftengine.core.block.properties.Property; +import net.momirealms.craftengine.core.entity.player.InteractionResult; +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 javax.annotation.Nullable; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.Callable; + +public class ButtonBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final BooleanProperty poweredProperty; + private final int ticksToStayPressed; + private final boolean canButtonBeActivatedByArrows; + private final SoundData buttonClickOnSound; + private final SoundData buttonClickOffSound; + + public ButtonBlockBehavior(CustomBlock customBlock, + BooleanProperty powered, + int ticksToStayPressed, + boolean canButtonBeActivatedByArrows, + SoundData buttonClickOnSound, + SoundData buttonClickOffSound) { + super(customBlock); + this.poweredProperty = powered; + this.ticksToStayPressed = ticksToStayPressed; + this.canButtonBeActivatedByArrows = canButtonBeActivatedByArrows; + this.buttonClickOnSound = buttonClickOnSound; + this.buttonClickOffSound = buttonClickOffSound; + } + + @Override + public InteractionResult useWithoutItem(UseOnContext context, ImmutableBlockState state) { + if (!state.get(this.poweredProperty)) { + press(BlockStateUtils.getBlockOwner(state.customBlockState().literalObject()), + state, context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()), + context.getPlayer() != null ? context.getPlayer().serverPlayer() : null); + return InteractionResult.SUCCESS_AND_CANCEL; + } + return InteractionResult.PASS; + } + + @Override + public void onExplosionHit(Object thisBlock, Object[] args, Callable superMethod) { + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (blockState == null) return; + if (FastNMS.INSTANCE.method$Explosion$canTriggerBlocks(args[3]) && !blockState.get(this.poweredProperty)) { + press(thisBlock, blockState, args[1], args[2], null); + } + } + + @Override + public void affectNeighborsAfterRemoval(Object thisBlock, Object[] args, Callable superMethod) { + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (blockState == null) return; + if (!(boolean) args[3] && blockState.get(this.poweredProperty)) { + updateNeighbours(thisBlock, blockState, args[1], args[2]); + } + } + + @Override + public void onRemove(Object thisBlock, Object[] args, Callable superMethod) { + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (blockState == null) return; + if (!(boolean) args[4] && blockState.get(this.poweredProperty)) { + updateNeighbours(thisBlock, blockState, args[1], args[2]); + } + } + + @Override + public int getSignal(Object thisBlock, Object[] args, Callable superMethod) { + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (blockState == null) return 0; + return blockState.get(this.poweredProperty) ? 15 : 0; + } + + @Override + public int getDirectSignal(Object thisBlock, Object[] args, Callable superMethod) { + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); + if (blockState == null) return 0; + return blockState.get(this.poweredProperty) + && FaceAttachedHorizontalDirectionalBlockBehavior.getConnectedDirection(blockState) + == DirectionUtils.fromNMSDirection(args[3]) ? 15 : 0; + } + + @Override + public boolean isSignalSource(Object thisBlock, Object[] args, Callable superMethod) { + return true; + } + + @Override + public void tick(Object thisBlock, Object[] args, Callable superMethod) { + Object state = args[0]; + Object level = args[1]; + Object pos = args[2]; + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); + if (blockState == null) return; + if (blockState.get(this.poweredProperty)) { + checkPressed(thisBlock, state, level, pos); + } + } + + @Override + public void entityInside(Object thisBlock, Object[] args, Callable superMethod) { + Object state = args[0]; + Object level = args[1]; + Object pos = args[2]; + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); + if (blockState == null) return; + if (this.canButtonBeActivatedByArrows && !blockState.get(this.poweredProperty)) { + checkPressed(thisBlock, state, level, pos); + } + } + + private void checkPressed(Object thisBlock, Object state, Object level, Object pos) { + Object abstractArrow = this.canButtonBeActivatedByArrows ? FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass( + level, CoreReflections.clazz$AbstractArrow, FastNMS.INSTANCE.method$AABB$move( + FastNMS.INSTANCE.method$VoxelShape$bounds(FastNMS.INSTANCE.method$BlockState$getShape( + state, level, pos, CoreReflections.instance$CollisionContext$empty + )), pos), MEntitySelector.NO_SPECTATORS).stream().findFirst().orElse(null) : null; + boolean flag = abstractArrow != null; + ImmutableBlockState blockState = BlockStateUtils.getOptionalCustomBlockState(state).orElse(null); + if (blockState == null) return; + boolean poweredValue = blockState.get(this.poweredProperty); + if (flag != poweredValue) { + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.poweredProperty, flag).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); + updateNeighbours(thisBlock, blockState, level, pos); + playSound(null, level, pos, flag); + Object gameEvent = VersionHelper.isOrAbove1_20_5() + ? FastNMS.INSTANCE.method$Holder$direct(flag ? MGameEvent.BLOCK_ACTIVATE : MGameEvent.BLOCK_DEACTIVATE) + : flag ? MGameEvent.BLOCK_ACTIVATE : MGameEvent.BLOCK_DEACTIVATE; + FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, abstractArrow, gameEvent, pos); + } + + if (flag) { + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed); + } + } + + private void updateNeighbours(Object thisBlock, ImmutableBlockState state, Object level, Object pos) { + Direction direction = FaceAttachedHorizontalDirectionalBlockBehavior.getConnectedDirection(state); + if (direction == null) return; + Direction opposite = direction.opposite(); + Object nmsDirection = DirectionUtils.toNMSDirection(opposite); + Object orientation = null; + if (VersionHelper.isOrAbove1_21_2()) { + @SuppressWarnings("unchecked") + Property facing = (Property) state.owner().value().getProperty("facing"); + if (facing != null) { + orientation = FastNMS.INSTANCE.method$ExperimentalRedstoneUtils$initialOrientation( + level, nmsDirection, opposite.axis().isHorizontal() ? CoreReflections.instance$Direction$UP : DirectionUtils.toNMSDirection(state.get(facing).toDirection()) + ); + } + } + FastNMS.INSTANCE.method$Level$updateNeighborsAt(level, pos, thisBlock, orientation); + FastNMS.INSTANCE.method$Level$updateNeighborsAt(level, FastNMS.INSTANCE.method$BlockPos$relative(pos, nmsDirection), thisBlock, orientation); + } + + private void playSound(@Nullable Object player, Object level, Object pos, boolean hitByArrow) { + SoundData soundData = getSound(hitByArrow); + if (soundData == null) return; + Object sound = FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(soundData.id()), Optional.empty()); + FastNMS.INSTANCE.method$LevelAccessor$playSound(level, player, pos, sound, CoreReflections.instance$SoundSource$BLOCKS, soundData.volume().get(), soundData.pitch().get()); + } + + private SoundData getSound(boolean isOn) { + return isOn ? this.buttonClickOnSound : this.buttonClickOffSound; + } + + private void press(Object thisBlock, ImmutableBlockState state, Object level, Object pos, @Nullable Object player) { + FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, state.with(this.poweredProperty, true).customBlockState().literalObject(), UpdateOption.UPDATE_ALL.flags()); + FastNMS.INSTANCE.method$ScheduledTickAccess$scheduleBlockTick(level, pos, thisBlock, this.ticksToStayPressed); + playSound(player, level, pos, true); + Object gameEvent = VersionHelper.isOrAbove1_20_5() ? FastNMS.INSTANCE.method$Holder$direct(MGameEvent.BLOCK_ACTIVATE) : MGameEvent.BLOCK_ACTIVATE; + FastNMS.INSTANCE.method$LevelAccessor$gameEvent(level, player, gameEvent, pos); + } + + public static class Factory implements BlockBehaviorFactory { + + @SuppressWarnings({"unchecked", "DuplicatedCode"}) + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + BooleanProperty powered = (BooleanProperty) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("powered"), "warning.config.block.behavior.button.missing_powered"); + int ticksToStayPressed = ResourceConfigUtils.getAsInt(arguments.getOrDefault("ticks-to-stay-pressed", 30), "ticks-to-stay-pressed"); + boolean canButtonBeActivatedByArrows = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("can-button-be-activated-by-arrows", true), "can-button-be-activated-by-arrows"); + Map sounds = (Map) arguments.get("sounds"); + SoundData buttonClickOnSound = null; + SoundData buttonClickOffSound = null; + if (sounds != null) { + buttonClickOnSound = Optional.ofNullable(sounds.get("on")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null); + buttonClickOffSound = Optional.ofNullable(sounds.get("off")).map(obj -> SoundData.create(obj, SoundData.SoundValue.FIXED_1, SoundData.SoundValue.ranged(0.9f, 1f))).orElse(null); + } + return new ButtonBlockBehavior(block, powered, ticksToStayPressed, canButtonBeActivatedByArrows, buttonClickOnSound, buttonClickOffSound); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java index a155235db..f2e4d9987 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/DirectionalAttachedBlockBehavior.java @@ -25,7 +25,7 @@ import org.bukkit.Registry; import java.util.*; import java.util.concurrent.Callable; -public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBehavior { +public class DirectionalAttachedBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); private final Property facingProperty; private final boolean isSixDirection; @@ -37,12 +37,11 @@ public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBeh public DirectionalAttachedBlockBehavior(CustomBlock customBlock, Property facingProperty, boolean isSixDirection, - int delay, boolean blacklist, List tagsCanSurviveOn, Set blockStatesCanSurviveOn, Set customBlocksCansSurviveOn) { - super(customBlock, delay); + super(customBlock); this.facingProperty = facingProperty; this.isSixDirection = isSixDirection; this.tagsCanSurviveOn = tagsCanSurviveOn; @@ -69,8 +68,8 @@ public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBeh } @Override - protected boolean canSurvive(Object thisBlock, Object blockState, Object world, Object pos) throws Exception { - ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(blockState).orElse(null); + public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + ImmutableBlockState state = BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null); if (state == null) return false; DirectionalAttachedBlockBehavior behavior = state.behavior().getAs(DirectionalAttachedBlockBehavior.class).orElse(null); if (behavior == null) return false; @@ -80,10 +79,10 @@ public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBeh } else { direction = ((HorizontalDirection) state.get(behavior.facingProperty)).opposite().toDirection(); } - BlockPos blockPos = LocationUtils.fromBlockPos(pos).relative(direction); + BlockPos blockPos = LocationUtils.fromBlockPos(args[2]).relative(direction); Object nmsPos = LocationUtils.toBlockPos(blockPos); - Object nmsState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, nmsPos); - return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(nmsState, world, nmsPos, DirectionUtils.toNMSDirection(direction), CoreReflections.instance$SupportType$FULL) + Object nmsState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(args[1], nmsPos); + return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy(nmsState, args[1], nmsPos, DirectionUtils.toNMSDirection(direction), CoreReflections.instance$SupportType$FULL) && mayPlaceOn(nmsState); } @@ -144,14 +143,13 @@ public class DirectionalAttachedBlockBehavior extends AbstractCanSurviveBlockBeh throw new LocalizedResourceConfigException("warning.config.block.behavior.directional_attached.missing_facing"); } Tuple, Set, Set> tuple = readTagsAndState(arguments); - int delay = ResourceConfigUtils.getAsInt(arguments.getOrDefault("delay", 0), "delay"); boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", true), "blacklist"); - return new DirectionalAttachedBlockBehavior(block, facing, isDirection, delay, blacklistMode, tuple.left(), tuple.mid(), tuple.right()); + return new DirectionalAttachedBlockBehavior(block, facing, isDirection, blacklistMode, tuple.left(), tuple.mid(), tuple.right()); } } @SuppressWarnings("DuplicatedCode") - private static Tuple, Set, Set> readTagsAndState(Map arguments) { + public static Tuple, Set, Set> readTagsAndState(Map arguments) { List mcTags = new ArrayList<>(); for (String tag : MiscUtils.getAsStringList(arguments.getOrDefault("attached-block-tags", List.of()))) { mcTags.add(BlockTags.getOrCreate(Key.of(tag))); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java new file mode 100644 index 000000000..cc877ef06 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/FaceAttachedHorizontalDirectionalBlockBehavior.java @@ -0,0 +1,150 @@ +package net.momirealms.craftengine.bukkit.block.behavior; + +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.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.DirectionUtils; +import net.momirealms.craftengine.bukkit.util.LocationUtils; +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.properties.Property; +import net.momirealms.craftengine.core.block.state.properties.AttachFace; +import net.momirealms.craftengine.core.item.context.BlockPlaceContext; +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.Tuple; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Callable; + +public class FaceAttachedHorizontalDirectionalBlockBehavior extends BukkitBlockBehavior { + public static final Factory FACTORY = new Factory(); + private final Property attachFaceProperty; + private final Property facingProperty; + private final List tagsCanSurviveOn; + private final Set blockStatesCanSurviveOn; + private final Set customBlocksCansSurviveOn; + private final boolean blacklistMode; + + public FaceAttachedHorizontalDirectionalBlockBehavior(CustomBlock customBlock, + boolean blacklist, + List tagsCanSurviveOn, + Set blockStatesCanSurviveOn, + Set customBlocksCansSurviveOn, + Property attachFace, + Property facing) { + super(customBlock); + this.tagsCanSurviveOn = tagsCanSurviveOn; + this.blockStatesCanSurviveOn = blockStatesCanSurviveOn; + this.customBlocksCansSurviveOn = customBlocksCansSurviveOn; + this.blacklistMode = blacklist; + this.attachFaceProperty = attachFace; + this.facingProperty = facing; + } + + @Override + public boolean canSurvive(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Direction direction = getConnectedDirection(BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null)); + if (direction == null) return false; + direction = direction.opposite(); + Object nmsDirection = DirectionUtils.toNMSDirection(direction); + Object targetPos = FastNMS.INSTANCE.method$BlockPos$relative(args[2], nmsDirection); + Object targetState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(args[1], targetPos); + return canAttach(args[1], targetPos, nmsDirection, targetState) && mayPlaceOn(targetState); + } + + @SuppressWarnings("unchecked") + @Override + public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) { + Property face = (Property) state.owner().value().getProperty("face"); + Property facing = (Property) state.owner().value().getProperty("facing"); + if (face == null || facing == null) return null; + for (Direction direction : context.getNearestLookingDirections()) { + if (direction.axis() == Direction.Axis.Y) { + state = state + .with(face, direction == Direction.UP ? AttachFace.CEILING : AttachFace.FLOOR) + .with(facing, context.getHorizontalDirection().toHorizontalDirection()); + } else { + state = state.with(face, AttachFace.WALL).with(facing, direction.opposite().toHorizontalDirection()); + } + if (FastNMS.INSTANCE.method$BlockStateBase$canSurvive(state.customBlockState().literalObject(), context.getLevel().serverWorld(), LocationUtils.toBlockPos(context.getClickedPos()))) { + return state; + } + } + return null; + } + + @Override + public Object updateShape(Object thisBlock, Object[] args, Callable superMethod) throws Exception { + Direction direction = getConnectedDirection(BlockStateUtils.getOptionalCustomBlockState(args[0]).orElse(null)); + if (direction == null) return MBlocks.AIR$defaultState; + if (DirectionUtils.toNMSDirection(direction.opposite()) == args[updateShape$direction] && !FastNMS.INSTANCE.method$BlockStateBase$canSurvive(args[0], args[updateShape$level], args[updateShape$blockPos])) { + return MBlocks.AIR$defaultState; + } + return superMethod.call(); + } + + private boolean mayPlaceOn(Object state) { + for (Object tag : this.tagsCanSurviveOn) { + if (FastNMS.INSTANCE.method$BlockStateBase$is(state, tag)) { + return !this.blacklistMode; + } + } + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(state); + if (optionalCustomState.isEmpty()) { + if (!this.blockStatesCanSurviveOn.isEmpty() && this.blockStatesCanSurviveOn.contains(state)) { + return !this.blacklistMode; + } + } else { + ImmutableBlockState belowCustomState = optionalCustomState.get(); + if (this.customBlocksCansSurviveOn.contains(belowCustomState.owner().value().id().toString())) { + return !this.blacklistMode; + } + if (this.customBlocksCansSurviveOn.contains(belowCustomState.toString())) { + return !this.blacklistMode; + } + } + return this.blacklistMode; + } + + public static boolean canAttach(Object level, Object targetPos, Object direction, Object targetState) { + return FastNMS.INSTANCE.method$BlockStateBase$isFaceSturdy( + targetState, level, targetPos, + FastNMS.INSTANCE.method$Direction$getOpposite(direction), + CoreReflections.instance$SupportType$FULL + ); + } + + @Nullable + public static Direction getConnectedDirection(ImmutableBlockState state) { + if (state == null) return null; + FaceAttachedHorizontalDirectionalBlockBehavior behavior = state.behavior().getAs(FaceAttachedHorizontalDirectionalBlockBehavior.class).orElse(null); + if (behavior == null) return null; + return switch (state.get(behavior.attachFaceProperty)) { + case CEILING -> Direction.DOWN; + case FLOOR -> Direction.UP; + default -> state.get(behavior.facingProperty).toDirection(); + }; + } + + public static class Factory implements BlockBehaviorFactory { + + @SuppressWarnings("unchecked") + @Override + public BlockBehavior create(CustomBlock block, Map arguments) { + Property attachFace = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("face"), "warning.config.block.behavior.face_attached_horizontal_directional.missing_face"); + Property facing = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.face_attached_horizontal_directional.missing_facing"); + Tuple, Set, Set> tuple = DirectionalAttachedBlockBehavior.readTagsAndState(arguments); + boolean blacklistMode = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("blacklist", true), "blacklist"); + return new FaceAttachedHorizontalDirectionalBlockBehavior(block, blacklistMode, tuple.left(), tuple.mid(), tuple.right(), attachFace, facing); + } + } +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java index 09c8c920d..5ec5cc268 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/PressurePlateBlockBehavior.java @@ -4,6 +4,7 @@ import io.papermc.paper.event.entity.EntityInsideBlockEvent; 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.MEntitySelector; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; import net.momirealms.craftengine.bukkit.util.DirectionUtils; import net.momirealms.craftengine.bukkit.util.EventUtils; @@ -28,6 +29,7 @@ import javax.annotation.Nullable; import java.util.Map; import java.util.Optional; import java.util.concurrent.Callable; +import java.util.function.Predicate; public class PressurePlateBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); @@ -111,7 +113,10 @@ public class PressurePlateBlockBehavior extends BukkitBlockBehavior { case MOBS -> CoreReflections.clazz$LivingEntity; }; Object box = FastNMS.INSTANCE.method$AABB$move(CoreReflections.instance$BasePressurePlateBlock$TOUCH_AABB, pos); - return FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass(level, box, clazz) > 0 ? 15 : 0; + return !FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass( + level, clazz, box, + MEntitySelector.NO_SPECTATORS.and(entity -> !FastNMS.INSTANCE.method$Entity$isIgnoringBlockTriggers(entity)) + ).isEmpty() ? 15 : 0; } private Object setSignalForState(Object state, int strength) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java index 06d11bf38..e385ffca4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/item/recipe/BukkitRecipeManager.java @@ -395,7 +395,7 @@ public class BukkitRecipeManager extends AbstractRecipeManager { JsonObject jsonObject = entry.getValue(); Key serializerType = Key.of(jsonObject.get("type").getAsString()); - // noinspection unchecked + @SuppressWarnings("unchecked") RecipeSerializer> serializer = (RecipeSerializer>) BuiltInRegistries.RECIPE_SERIALIZER.getValue(serializerType); if (serializer == null) { continue; diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 2952fe1bc..4671b7ed5 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -961,6 +961,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes */ public static class HelloListener implements NMSPacketListener { + @SuppressWarnings("unchecked") @Override public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { BukkitServerPlayer player = (BukkitServerPlayer) user; @@ -984,7 +985,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } else { Optional uuid; try { - // noinspection unchecked uuid = (Optional) NetworkReflections.methodHandle$ServerboundHelloPacket$uuidGetter.invokeExact(packet); } catch (Throwable t) { CraftEngine.instance().logger().severe("Failed to get uuid from ServerboundHelloPacket", t); @@ -1478,6 +1478,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes public static class EditBookListener implements NMSPacketListener { + @SuppressWarnings("unchecked") @Override public void onPacketReceive(NetWorkUser user, NMSPacketEvent event, Object packet) { if (!Config.filterBook()) return; @@ -1492,7 +1493,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes List pages; try { - // noinspection unchecked pages = (List) NetworkReflections.methodHandle$ServerboundEditBookPacket$pagesGetter.invokeExact(packet); } catch (Throwable t) { CraftEngine.instance().logger().warn("Failed to get pages from ServerboundEditBookPacket", t); @@ -1501,7 +1501,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes List newPages = new ArrayList<>(pages.size()); Optional title; try { - // noinspection unchecked title = (Optional) NetworkReflections.methodHandle$ServerboundEditBookPacket$titleGetter.invokeExact(packet); } catch (Throwable t) { CraftEngine.instance().logger().warn("Failed to get title from ServerboundEditBookPacket", t); @@ -1733,6 +1732,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes public static class FinishConfigurationListener implements NMSPacketListener { + @SuppressWarnings("unchecked") @Override public void onPacketSend(NetWorkUser user, NMSPacketEvent event, Object packet) { if (!VersionHelper.isOrAbove1_20_2() || !Config.sendPackOnJoin()) { @@ -1793,7 +1793,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } Queue configurationTasks; try { - // noinspection unchecked configurationTasks = (Queue) CoreReflections.methodHandle$ServerConfigurationPacketListenerImpl$configurationTasksGetter.invokeExact(packetListener); } catch (Throwable e) { CraftEngine.instance().logger().warn("Failed to get configuration tasks for player " + user.name(), e); @@ -3665,7 +3664,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes Object packedItem = packedItems.get(i); int entityDataId = FastNMS.INSTANCE.field$SynchedEntityData$DataValue$id(packedItem); if (entityDataId != BaseEntityData.CustomName.id()) continue; - // noinspection unchecked + @SuppressWarnings("unchecked") Optional optionalTextComponent = (Optional) FastNMS.INSTANCE.field$SynchedEntityData$DataValue$value(packedItem); if (optionalTextComponent.isEmpty()) continue; Object textComponent = optionalTextComponent.get(); diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java index 859f826ed..4fb178606 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/CoreReflections.java @@ -4400,4 +4400,10 @@ public final class CoreReflections { "world.level.block.FenceGateBlock" ) ); + + public static final Class clazz$GameEvent = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("world.level.gameevent.GameEvent") + ) + ); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBuiltInRegistries.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBuiltInRegistries.java index ef6d7a8b1..565ae69c3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBuiltInRegistries.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MBuiltInRegistries.java @@ -22,6 +22,7 @@ public final class MBuiltInRegistries { public static final Object PARTICLE_TYPE; public static final Object DATA_COMPONENT_TYPE; public static final Object LOOT_POOL_ENTRY_TYPE; + public static final Object GAME_EVENT; static { Field[] fields = CoreReflections.clazz$BuiltInRegistries.getDeclaredFields(); @@ -37,6 +38,7 @@ public final class MBuiltInRegistries { Object registries$RecipeType = null; Object registries$DataComponentType = null; Object registries$LootPoolEntryType = null; + Object registries$GameEvent = null; for (Field field : fields) { Type fieldType = field.getGenericType(); if (fieldType instanceof ParameterizedType paramType) { @@ -67,6 +69,8 @@ public final class MBuiltInRegistries { registries$Fluid = field.get(null); } else if (type == CoreReflections.clazz$LootPoolEntryType) { registries$LootPoolEntryType = field.get(null); + } else if (type == CoreReflections.clazz$GameEvent) { + registries$GameEvent = field.get(null); } } } @@ -82,6 +86,7 @@ public final class MBuiltInRegistries { RECIPE_TYPE = requireNonNull(registries$RecipeType); LOOT_POOL_ENTRY_TYPE = requireNonNull(registries$LootPoolEntryType); DATA_COMPONENT_TYPE = registries$DataComponentType; + GAME_EVENT = requireNonNull(registries$GameEvent); } catch (ReflectiveOperationException e) { throw new ReflectionInitException("Failed to init BuiltInRegistries", e); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelector.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelector.java new file mode 100644 index 000000000..7a2b43ab7 --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MEntitySelector.java @@ -0,0 +1,12 @@ +package net.momirealms.craftengine.bukkit.plugin.reflection.minecraft; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; + +import java.util.function.Predicate; + +public final class MEntitySelector { + private MEntitySelector() {} + + public static final Predicate NO_SPECTATORS = entity -> !FastNMS.INSTANCE.method$Entity$isSpectator(entity); + +} diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvent.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvent.java new file mode 100644 index 000000000..500f39d8c --- /dev/null +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/reflection/minecraft/MGameEvent.java @@ -0,0 +1,15 @@ +package net.momirealms.craftengine.bukkit.plugin.reflection.minecraft; + +import net.momirealms.craftengine.bukkit.nms.FastNMS; + +public final class MGameEvent { + private MGameEvent() {} + + public static final Object BLOCK_ACTIVATE = getById("block_activate"); + public static final Object BLOCK_DEACTIVATE = getById("block_deactivate"); + + private static Object getById(String id) { + Object rl = FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", id); + return FastNMS.INSTANCE.method$Registry$getValue(MBuiltInRegistries.GAME_EVENT, rl); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java index f1aba39ce..e969cab8c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockBehavior.java @@ -55,18 +55,18 @@ public abstract class BlockBehavior { superMethod.call(); } - // ServerLevel level, BlockPos pos, RandomSource random + // BlockState state, ServerLevel level, BlockPos pos, RandomSource random public void tick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { superMethod.call(); } - // ServerLevel level, BlockPos pos, RandomSource random + // BlockState state, ServerLevel level, BlockPos pos, RandomSource random public void randomTick(Object thisBlock, Object[] args, Callable superMethod) throws Exception { superMethod.call(); } // 1.20-1.20.4 BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify, UseOnContext context - // 1.20.5+ Level level, BlockPos pos, BlockState oldState, boolean movedByPiston + // 1.20.5+ BlockState state, Level level, BlockPos pos, BlockState oldState, boolean movedByPiston public void onPlace(Object thisBlock, Object[] args, Callable superMethod) throws Exception { superMethod.call(); } @@ -95,12 +95,12 @@ public abstract class BlockBehavior { return false; } - //BlockState state + // BlockState state public boolean hasAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception { return false; } - //BlockState state, Level level, BlockPos pos + // BlockState state, Level level, BlockPos pos public int getAnalogOutputSignal(Object thisBlock, Object[] args) throws Exception { return 0; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java index e5a143264..e559a080a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/properties/Properties.java @@ -22,6 +22,7 @@ public final class Properties { public static final Key STAIRS_SHAPE = Key.of("craftengine:stairs_shape"); public static final Key SLAB_TYPE = Key.of("craftengine:slab_type"); public static final Key SOFA_SHAPE = Key.of("craftengine:sofa_shape"); + public static final Key ATTACH_FACE = Key.of("craftengine:attach_face"); static { register(BOOLEAN, BooleanProperty.FACTORY); @@ -38,6 +39,7 @@ public final class Properties { register(STAIRS_SHAPE, new EnumProperty.Factory<>(StairsShape.class)); register(SLAB_TYPE, new EnumProperty.Factory<>(SlabType.class)); register(SOFA_SHAPE, new EnumProperty.Factory<>(SofaShape.class)); + register(ATTACH_FACE, new EnumProperty.Factory<>(AttachFace.class)); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AttachFace.java b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AttachFace.java new file mode 100644 index 000000000..de9d6267b --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/block/state/properties/AttachFace.java @@ -0,0 +1,7 @@ +package net.momirealms.craftengine.core.block.state.properties; + +public enum AttachFace { + FLOOR, + WALL, + CEILING +} diff --git a/gradle.properties b/gradle.properties index e6e8440a9..e0c51c8a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ byte_buddy_version=1.17.5 ahocorasick_version=0.6.3 snake_yaml_version=2.5 anti_grief_version=0.20 -nms_helper_version=1.0.91 +nms_helper_version=1.0.92 evalex_version=3.5.0 reactive_streams_version=1.0.4 amazon_awssdk_version=2.33.1