9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-26 02:19:23 +00:00

feat(block): 添加镐方块和放置方块行为

This commit is contained in:
jhqwqmc
2025-06-22 19:58:03 +08:00
parent f3a8f245f9
commit 4073f40c08
8 changed files with 362 additions and 2 deletions

View File

@@ -26,6 +26,8 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
public static final Key SLAB_BLOCK = Key.from("craftengine:slab_block");
public static final Key STAIRS_BLOCK = Key.from("craftengine:stairs_block");
public static final Key PRESSURE_PLATE_BLOCK = Key.from("craftengine:pressure_plate_block");
public static final Key PICKAXE_BLOCK = Key.from("craftengine:pickaxe_block");
public static final Key PLACE_BLOCK = Key.from("craftengine:place_block");
public static void init() {
register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE);
@@ -50,5 +52,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
register(SLAB_BLOCK, SlabBlockBehavior.FACTORY);
register(STAIRS_BLOCK, StairsBlockBehavior.FACTORY);
register(PRESSURE_PLATE_BLOCK, PressurePlateBlockBehavior.FACTORY);
register(PICKAXE_BLOCK, PickaxeBlockBehavior.FACTORY);
register(PLACE_BLOCK, PlaceBlockBehavior.FACTORY);
}
}

View File

@@ -0,0 +1,49 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.core.block.CustomBlock;
import net.momirealms.craftengine.core.block.ImmutableBlockState;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Direction;
import java.util.concurrent.Callable;
public abstract class FacingTriggerableBlockBehavior extends BukkitBlockBehavior {
protected final Property<Direction> facingProperty;
protected final Property<Boolean> triggeredProperty;
public FacingTriggerableBlockBehavior(CustomBlock customBlock, Property<Direction> facing, Property<Boolean> triggered) {
super(customBlock);
this.facingProperty = facing;
this.triggeredProperty = triggered;
}
@Override
public void neighborChanged(Object thisBlock, Object[] args, Callable<Object> superMethod) {
CraftEngine.instance().logger().warn("FacingTriggerableBlockBehavior.neighborChanged");
Object state = args[0];
Object level = args[1];
Object pos = args[2];
boolean hasNeighborSignal = FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, pos);
ImmutableBlockState blockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(state));
if (blockState == null || blockState.isEmpty()) return;
boolean triggeredValue = blockState.get(this.triggeredProperty);
if (hasNeighborSignal && !triggeredValue) {
FastNMS.INSTANCE.method$LevelAccessor$scheduleBlockTick(level, pos, thisBlock, 1, this.getTickPriority());
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.triggeredProperty, true).customBlockState().handle(), 2);
} else if (!hasNeighborSignal && triggeredValue) {
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, pos, blockState.with(this.triggeredProperty, false).customBlockState().handle(), 2);
}
}
@Override
public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) {
return state.owner().value().defaultState().with(this.facingProperty, context.getNearestLookingDirection().opposite());
}
protected abstract Object getTickPriority();
}

View File

@@ -0,0 +1,54 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.util.BlockStateUtils;
import net.momirealms.craftengine.bukkit.util.DirectionUtils;
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.util.Direction;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import java.util.Map;
import java.util.concurrent.Callable;
public class PickaxeBlockBehavior extends FacingTriggerableBlockBehavior {
public static final Factory FACTORY = new Factory();
public PickaxeBlockBehavior(CustomBlock customBlock, Property<Direction> facing, Property<Boolean> triggered) {
super(customBlock, facing, triggered);
}
@Override
protected Object getTickPriority() {
return CoreReflections.instance$TickPriority$EXTREMELY_HIGH;
}
@Override
public void tick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object state = args[0];
Object level = args[1];
Object pos = args[2];
ImmutableBlockState blockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(state));
if (blockState == null || blockState.isEmpty()) return;
Object blockPos = FastNMS.INSTANCE.method$BlockPos$relative(pos, DirectionUtils.toNMSDirection(blockState.get(this.facingProperty)));
if (!FastNMS.INSTANCE.method$BlockStateBase$isAir(FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, blockPos))) {
FastNMS.INSTANCE.method$LevelWriter$destroyBlock(level, blockPos, true, null, 512);
}
}
public static class Factory implements BlockBehaviorFactory {
@Override
@SuppressWarnings("unchecked")
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Property<Direction> facing = (Property<Direction>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.pickaxe.missing_facing");
Property<Boolean> triggered = (Property<Boolean>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("triggered"), "warning.config.block.behavior.pickaxe.missing_triggered");
return new PickaxeBlockBehavior(block, facing, triggered);
}
}
}

View File

@@ -0,0 +1,176 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import net.momirealms.craftengine.bukkit.block.BukkitBlockManager;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections;
import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MEntityTypes;
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.util.Direction;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Function;
public class PlaceBlockBehavior extends FacingTriggerableBlockBehavior {
public static final Factory FACTORY = new Factory();
public PlaceBlockBehavior(CustomBlock customBlock, Property<Direction> facing, Property<Boolean> triggered) {
super(customBlock, facing, triggered);
}
@Override
protected Object getTickPriority() {
return CoreReflections.instance$TickPriority$EXTREMELY_LOW;
}
@Override
public void tick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object state = args[0];
Object level = args[1];
BlockPos pos = LocationUtils.fromBlockPos(args[2]);
ImmutableBlockState blockState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(state));
if (blockState == null || blockState.isEmpty()) return;
Direction direction = blockState.get(this.facingProperty);
Direction opposite = direction.opposite();
BlockPos blockPos = pos.relative(opposite);
BlockPos blockPos1 = pos.relative(direction);
getItemAndDoThings(level, blockPos, opposite, itemStack -> {
if (FastNMS.INSTANCE.method$ItemStack$isEmpty(itemStack)) {
return false;
} else {
Object itemStack1 = FastNMS.INSTANCE.method$ItemEntity$getItem(itemStack);
boolean flag = CoreReflections.clazz$BlockItem.isInstance(itemStack1)
&& FastNMS.INSTANCE.method$InteractionResult$consumesAction(FastNMS.INSTANCE.method$BlockItem$place(
itemStack1, FastNMS.INSTANCE.constructor$PlaceBlockBlockPlaceContext(
level, CoreReflections.instance$InteractionHand$MAIN_HAND, itemStack,
FastNMS.INSTANCE.constructor$BlockHitResult(
FastNMS.INSTANCE.method$BlockPos$getCenter(LocationUtils.toBlockPos(blockPos1)),
DirectionUtils.toNMSDirection(opposite),
LocationUtils.toBlockPos(blockPos1),
false
)
)));
if (!flag) {
double d = FastNMS.INSTANCE.method$EntityType$getHeight(MEntityTypes.ITEM) / 2.0;
double d1 = blockPos1.x() + 0.5;
double d2 = blockPos1.y() + 0.5 - d;
double d3 = blockPos1.z() + 0.5;
Object itemEntity = FastNMS.INSTANCE.constructor$ItemEntity(level, d1, d2, d3, itemStack);
FastNMS.INSTANCE.method$ItemEntity$setDefaultPickUpDelay(itemEntity);
FastNMS.INSTANCE.method$LevelWriter$addFreshEntity(level, itemEntity);
}
return true;
}
});
}
private static boolean getItemAndDoThings(Object level, BlockPos blockPos, Direction direction, Function<Object, Boolean> function) {
for (Object container : getContainersAt(level, blockPos)) {
boolean flag = FastNMS.INSTANCE.method$HopperBlockEntity$getSlots(container, direction).anyMatch(i -> {
Object itemStack = FastNMS.INSTANCE.method$Container$removeItem(container, i, 1);
if (!FastNMS.INSTANCE.method$ItemStack$isEmpty(itemStack)) {
boolean flag1 = function.apply(FastNMS.INSTANCE.method$ItemStack$copy(itemStack));
if (flag1) {
FastNMS.INSTANCE.method$Container$setChanged(container);
} else {
FastNMS.INSTANCE.method$Container$setItem(container, i, itemStack);
}
return true;
} else {
return false;
}
});
if (flag) {
return true;
}
}
Object itemAt = getItemAt(level, blockPos);
if (itemAt != null) {
Object item = FastNMS.INSTANCE.method$ItemEntity$getItem(itemAt);
if (!FastNMS.INSTANCE.method$ItemStack$isEmpty(item)) {
boolean flag = function.apply(FastNMS.INSTANCE.method$ItemStack$copyWithCount(item, 1));
if (flag) {
FastNMS.INSTANCE.method$ItemStack$shrink(item, 1);
if (FastNMS.INSTANCE.method$ItemStack$getCount(item) <= 0) {
FastNMS.INSTANCE.method$Entity$discard(itemAt);
}
}
return true;
}
}
return false;
}
@SuppressWarnings("unchecked")
public static List<Object> getContainersAt(Object level, BlockPos blockPos) {
Object nmsBlockPos = LocationUtils.toBlockPos(blockPos);
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, nmsBlockPos);
Object block = FastNMS.INSTANCE.method$BlockState$getBlock(blockState);
if (CoreReflections.clazz$WorldlyContainerHolder.isInstance(block)) {
Object container = FastNMS.INSTANCE.method$WorldlyContainerHolder$getContainer(block, blockState, level, nmsBlockPos);
if (container != null) {
return List.of(container);
}
} else if (FastNMS.INSTANCE.method$BlockStateBase$hasBlockEntity(blockState)) {
Object blockEntity = FastNMS.INSTANCE.method$BlockGetter$getBlockEntity(level, nmsBlockPos);
if (CoreReflections.clazz$Container.isInstance(blockEntity)) {
if (!(CoreReflections.clazz$ChestBlockEntity.isInstance(blockEntity)) || !(CoreReflections.clazz$ChestBlock.isInstance(block))) {
return List.of(blockEntity);
}
Object container = FastNMS.INSTANCE.method$ChestBlock$getContainer(block, blockState, level, nmsBlockPos, true);
if (container != null) {
return List.of(container);
}
}
}
List<Object> list = new ArrayList<>();
for (Object entity : (List<Object>) FastNMS.INSTANCE.method$EntityGetter$getEntities(
level, null, blockAABB(nmsBlockPos),
entity -> CoreReflections.clazz$Container.isInstance(entity) && FastNMS.INSTANCE.method$Entity$isAlive(entity))
) {
if (CoreReflections.clazz$Container.isInstance(entity)) {
list.add(entity);
}
}
return list;
}
@Nullable
@SuppressWarnings("unchecked")
public static Object getItemAt(Object level, Object blockPos) {
List<Object> entitiesOfClass = (List<Object>) FastNMS.INSTANCE.method$EntityGetter$getEntitiesOfClass(
level, CoreReflections.clazz$ItemEntity, blockAABB(blockPos), FastNMS.INSTANCE::method$Entity$isAlive
);
return entitiesOfClass.isEmpty() ? null : entitiesOfClass.getFirst();
}
private static Object blockAABB(Object blockPos) {
return FastNMS.INSTANCE.method$AABB$ofSize(FastNMS.INSTANCE.method$BlockPos$getCenter(blockPos), 0.9999999, 0.9999999, 0.9999999);
}
public static class Factory implements BlockBehaviorFactory {
@Override
@SuppressWarnings("unchecked")
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Property<Direction> facing = (Property<Direction>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("facing"), "warning.config.block.behavior.place_block.missing_facing");
Property<Boolean> triggered = (Property<Boolean>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("triggered"), "warning.config.block.behavior.place_block.missing_triggered");
return new PlaceBlockBehavior(block, facing, triggered);
}
}
}

View File

@@ -3509,4 +3509,73 @@ public final class CoreReflections {
clazz$Level, boolean.class, clazz$BlockPos, boolean.class, clazz$Entity, int.class
)
);
public static final Class<?> clazz$TickPriority = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.ticks.TickListPriority",
"world.ticks.TickPriority"
)
);
public static final Method method$TickPriority$values = requireNonNull(
ReflectionUtils.getStaticMethod(clazz$TickPriority, clazz$TickPriority.arrayType())
);
public static final Object instance$TickPriority$EXTREMELY_HIGH;
public static final Object instance$TickPriority$VERY_HIGH;
public static final Object instance$TickPriority$HIGH;
public static final Object instance$TickPriority$NORMAL;
public static final Object instance$TickPriority$LOW;
public static final Object instance$TickPriority$VERY_LOW;
public static final Object instance$TickPriority$EXTREMELY_LOW;
static {
try {
Object[] values = (Object[]) method$TickPriority$values.invoke(null);
instance$TickPriority$EXTREMELY_HIGH = values[0];
instance$TickPriority$VERY_HIGH = values[1];
instance$TickPriority$HIGH = values[2];
instance$TickPriority$NORMAL = values[3];
instance$TickPriority$LOW = values[4];
instance$TickPriority$VERY_LOW = values[5];
instance$TickPriority$EXTREMELY_LOW = values[6];
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static final Class<?> clazz$WorldlyContainerHolder = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.IInventoryHolder",
"world.WorldlyContainerHolder"
)
);
public static final Class<?> clazz$ChestBlockEntity = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.level.block.entity.TileEntityChest",
"world.level.block.entity.ChestBlockEntity"
)
);
public static final Class<?> clazz$ChestBlock = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.level.block.BlockChest",
"world.level.block.ChestBlock"
)
);
public static final Class<?> clazz$ItemEntity = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.entity.item.EntityItem",
"world.entity.item.ItemEntity"
)
);
public static final Class<?> clazz$BlockItem = requireNonNull(
BukkitReflectionUtils.findReobfOrMojmapClass(
"world.item.ItemBlock",
"world.item.BlockItem"
)
);
}

View File

@@ -267,6 +267,10 @@ warning.config.block.behavior.stairs.missing_facing: "<yellow>Issue found in fil
warning.config.block.behavior.stairs.missing_half: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'half' property for 'stairs_block' behavior.</yellow>"
warning.config.block.behavior.stairs.missing_shape: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'shape' property for 'stairs_block' behavior.</yellow>"
warning.config.block.behavior.pressure_plate.missing_powered: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'powered' property for 'pressure_plate_block' behavior.</yellow>"
warning.config.block.behavior.pickaxe.missing_facing: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'facing' property for 'pickaxe_block' behavior.</yellow>"
warning.config.block.behavior.pickaxe.missing_triggered: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'triggered' property for 'pickaxe_block' behavior.</yellow>"
warning.config.block.behavior.place_block.missing_facing: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'facing' property for 'place_block' behavior.</yellow>"
warning.config.block.behavior.place_block.missing_triggered: "<yellow>Issue found in file <arg:0> - The block '<arg:1>' is missing the required 'triggered' property for 'place_block' behavior.</yellow>"
warning.config.model.generation.missing_parent: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is missing the required 'parent' argument in 'generation' section.</yellow>"
warning.config.model.generation.invalid_display_position: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid display position '<arg:2>' in 'generation.display' section. Allowed display positions: [<arg:3>]</yellow>"
warning.config.model.generation.invalid_gui_light: "<yellow>Issue found in file <arg:0> - The config '<arg:1>' is using an invalid gui-light option '<arg:2>' in 'generation' section. Allowed gui light options: [<arg:3>]</yellow>"

View File

@@ -267,6 +267,10 @@ warning.config.block.behavior.stairs.missing_facing: "<yellow>在文件 <arg:0>
warning.config.block.behavior.stairs.missing_half: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'stairs_block' 行为缺少必需的 'half' 属性</yellow>"
warning.config.block.behavior.stairs.missing_shape: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'stairs_block' 行为缺少必需的 'shape' 属性</yellow>"
warning.config.block.behavior.pressure_plate.missing_powered: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'pressure_plate_block' 行为缺少必需的 'powered' 属性</yellow>"
warning.config.block.behavior.pickaxe.missing_facing: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'pickaxe_block' 行为缺少必需的 'facing' 属性</yellow>"
warning.config.block.behavior.pickaxe.missing_triggered: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'pickaxe_block' 行为缺少必需的 'triggered' 属性</yellow>"
warning.config.block.behavior.place_block.missing_facing: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'place_block' 行为缺少必需的 'facing' 属性</yellow>"
warning.config.block.behavior.place_block.missing_triggered: "<yellow>在文件 <arg:0> 发现问题 - 方块 '<arg:1>' 的 'place_block' 行为缺少必需的 'triggered' 属性</yellow>"
warning.config.model.generation.missing_parent: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 的 'generation' 段落缺少必需的 'parent' 参数</yellow>"
warning.config.model.generation.conflict: "<yellow>在文件 <arg:0> 发现问题 - 无法为 '<arg:1>' 生成模型 存在多个配置尝试使用相同路径 '<arg:2>' 生成不同的 JSON 模型</yellow>"
warning.config.model.generation.invalid_display_position: "<yellow>在文件 <arg:0> 发现问题 - 配置项 '<arg:1>' 在 'generation.display' 区域使用了无效的 display 位置类型 '<arg:2>'. 可用展示类型: [<arg:3>]</yellow>"

View File

@@ -2,7 +2,7 @@ org.gradle.jvmargs=-Xmx1G
# Project settings
# Rule: [major update].[feature update].[bug fix]
project_version=0.0.58
project_version=0.0.58.1
config_version=38
lang_version=19
project_group=net.momirealms
@@ -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.31
nms_helper_version=0.67.35
evalex_version=3.5.0
reactive_streams_version=1.0.4
amazon_awssdk_version=2.31.23