diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java index dfc001ca8..e608fc7b4 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/GrassBlockBehavior.java @@ -2,7 +2,10 @@ 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.plugin.reflection.minecraft.MRegistries; import net.momirealms.craftengine.bukkit.util.BlockStateUtils; +import net.momirealms.craftengine.bukkit.util.FeatureUtils; import net.momirealms.craftengine.bukkit.util.LocationUtils; import net.momirealms.craftengine.bukkit.util.ParticleUtils; import net.momirealms.craftengine.bukkit.world.BukkitBlockInWorld; @@ -14,7 +17,8 @@ import net.momirealms.craftengine.core.entity.player.InteractionResult; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.item.ItemKeys; import net.momirealms.craftengine.core.item.context.UseOnContext; -import net.momirealms.craftengine.core.util.VersionHelper; +import net.momirealms.craftengine.core.plugin.CraftEngine; +import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.BlockPos; import org.bukkit.World; import org.bukkit.block.Block; @@ -25,9 +29,15 @@ import java.util.Optional; @SuppressWarnings("DuplicatedCode") public class GrassBlockBehavior extends BukkitBlockBehavior { public static final Factory FACTORY = new Factory(); + private final Key feature; - public GrassBlockBehavior(CustomBlock block) { + public GrassBlockBehavior(CustomBlock block, Key feature) { super(block); + this.feature = feature; + } + + public Key boneMealFeature() { + return feature; } @Override @@ -97,15 +107,58 @@ public class GrassBlockBehavior extends BukkitBlockBehavior { return InteractionResult.SUCCESS; } + @SuppressWarnings("unchecked") @Override - public void performBoneMeal(Object thisBlock, Object[] args) { - // TODO 使用骨粉 + public void performBoneMeal(Object thisBlock, Object[] args) throws Exception { + Object registry = CoreReflections.method$RegistryAccess$registryOrThrow.invoke(FastNMS.INSTANCE.registryAccess(), MRegistries.PLACED_FEATURE); + if (registry == null) return; + Optional holder = (Optional) CoreReflections.method$Registry$getHolder1.invoke(registry, FeatureUtils.createPlacedFeatureKey(boneMealFeature())); + if (holder.isEmpty()) { + CraftEngine.instance().logger().warn("Placed feature not found: " + boneMealFeature()); + return; + } + BlockPos grassPos = LocationUtils.fromBlockPos(args[2]); + Object world = args[0]; + Object random = args[1]; + BlockPos topPos = grassPos.above(); + out: + for (int i = 0; i < 128; i++) { + BlockPos currentPos = topPos; + for (int j = 0; j < i / 16; ++j) { + currentPos = currentPos.offset( + RandomUtils.generateRandomInt(-1, 2), RandomUtils.generateRandomInt(-1, 2) * RandomUtils.generateRandomInt(0, 3) / 2, RandomUtils.generateRandomInt(-1, 2) + ); + BlockPos belowPos = currentPos.relative(Direction.DOWN); + Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, LocationUtils.toBlockPos(belowPos)); + Optional optionalCustomState = BlockStateUtils.getOptionalCustomBlockState(belowState); + if (optionalCustomState.isEmpty()) { + continue out; + } + if (optionalCustomState.get().owner().value() != super.customBlock) { + continue out; + } + Object nmsCurrentPos = LocationUtils.toBlockPos(currentPos); + Object currentState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, nmsCurrentPos); + if (FastNMS.INSTANCE.method$BlockStateBase$isCollisionShapeFullBlock(currentState, world, nmsCurrentPos)) { + continue out; + } + if (BlockStateUtils.getBlockOwner(currentState) == MBlocks.SHORT_GRASS && RandomUtils.generateRandomInt(0, 10) == 0) { + CoreReflections.method$BonemealableBlock$performBonemeal.invoke(MBlocks.SHORT_GRASS, world, random, nmsCurrentPos, currentState); + } + if (FastNMS.INSTANCE.method$BlockStateBase$isAir(currentState)) { + Object chunkGenerator = CoreReflections.method$ServerChunkCache$getGenerator.invoke(FastNMS.INSTANCE.method$ServerLevel$getChunkSource(world)); + Object placedFeature = CoreReflections.method$Holder$value.invoke(holder.get()); + CoreReflections.method$PlacedFeature$place.invoke(placedFeature, world, chunkGenerator, random, nmsCurrentPos); + } + } + } } public static class Factory implements BlockBehaviorFactory { @Override public BlockBehavior create(CustomBlock block, Map arguments) { - return new GrassBlockBehavior(block); + String feature = ResourceConfigUtils.requireNonEmptyStringOrThrow(ResourceConfigUtils.get(arguments, "feature", "placed-feature"), "warning.config.block.behavior.grass.missing_feature"); + return new GrassBlockBehavior(block, Key.of(feature)); } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java index 90bffc5cc..c9a069d2a 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/behavior/SaplingBlockBehavior.java @@ -82,7 +82,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior { Object registry = CoreReflections.method$RegistryAccess$registryOrThrow.invoke(FastNMS.INSTANCE.registryAccess(), MRegistries.CONFIGURED_FEATURE); if (registry == null) return; @SuppressWarnings("unchecked") - Optional holder = (Optional) CoreReflections.method$Registry$getHolder1.invoke(registry, FeatureUtils.createFeatureKey(treeFeature())); + Optional holder = (Optional) CoreReflections.method$Registry$getHolder1.invoke(registry, FeatureUtils.createConfiguredFeatureKey(treeFeature())); if (holder.isEmpty()) { CraftEngine.instance().logger().warn("Configured feature not found: " + treeFeature()); return; @@ -172,7 +172,7 @@ public class SaplingBlockBehavior extends BukkitBlockBehavior { @SuppressWarnings("unchecked") @Override public BlockBehavior create(CustomBlock block, Map arguments) { - String feature = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("feature"), "warning.config.block.behavior.sapling.missing_feature"); + String feature = ResourceConfigUtils.requireNonEmptyStringOrThrow(ResourceConfigUtils.get(arguments, "feature", "configured-feature"), "warning.config.block.behavior.sapling.missing_feature"); Property stageProperty = (Property) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("stage"), "warning.config.block.behavior.sapling.missing_stage"); double boneMealSuccessChance = ResourceConfigUtils.getAsDouble(arguments.getOrDefault("bone-meal-success-chance", 0.45), "bone-meal-success-chance"); return new SaplingBlockBehavior(block, Key.of(feature), (IntegerProperty) stageProperty, boneMealSuccessChance, 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 388597721..319446db0 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 @@ -2149,6 +2149,10 @@ public final class CoreReflections { ReflectionUtils.getMethod(clazz$ConfiguredFeature, boolean.class, clazz$WorldGenLevel, clazz$ChunkGenerator, clazz$RandomSource, clazz$BlockPos) ); + public static final Method method$PlacedFeature$place = requireNonNull( + ReflectionUtils.getMethod(clazz$PlacedFeature, boolean.class, clazz$WorldGenLevel, clazz$ChunkGenerator, clazz$RandomSource, clazz$BlockPos) + ); + public static final Class clazz$BonemealableBlock = requireNonNull( BukkitReflectionUtils.findReobfOrMojmapClass( "world.level.block.IBlockFragilePlantElement", diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/FeatureUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/FeatureUtils.java index e985bf7d6..e40450582 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/FeatureUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/FeatureUtils.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.bukkit.util; +import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.MRegistries; import net.momirealms.craftengine.core.util.Key; @@ -8,11 +9,11 @@ public class FeatureUtils { private FeatureUtils() {} - public static Object createFeatureKey(Key id) { - try { - return CoreReflections.method$ResourceKey$create.invoke(null, MRegistries.CONFIGURED_FEATURE, KeyUtils.toResourceLocation(id)); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } + public static Object createConfiguredFeatureKey(Key id) { + return FastNMS.INSTANCE.method$ResourceKey$create(MRegistries.CONFIGURED_FEATURE, KeyUtils.toResourceLocation(id)); + } + + public static Object createPlacedFeatureKey(Key id) { + return FastNMS.INSTANCE.method$ResourceKey$create(MRegistries.PLACED_FEATURE, KeyUtils.toResourceLocation(id)); } } diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 8e9d2319f..6ffd99fae 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -272,6 +272,7 @@ warning.config.block.behavior.stairs.missing_facing: "Issue found in fil warning.config.block.behavior.stairs.missing_half: "Issue found in file - The block '' is missing the required 'half' property for 'stairs_block' behavior." warning.config.block.behavior.stairs.missing_shape: "Issue found in file - The block '' is missing the required 'shape' property for 'stairs_block' behavior." warning.config.block.behavior.pressure_plate.missing_powered: "Issue found in file - The block '' is missing the required 'powered' property for 'pressure_plate_block' behavior." +warning.config.block.behavior.grass.missing_feature: "Issue found in file - The block '' is missing the required 'feature' argument for 'grass_block' behavior." warning.config.model.generation.missing_parent: "Issue found in file - The config '' is missing the required 'parent' argument in 'generation' section." warning.config.model.generation.invalid_display_position: "Issue found in file - The config '' is using an invalid display position '' in 'generation.display' section. Allowed display positions: []" warning.config.model.generation.invalid_gui_light: "Issue found in file - The config '' is using an invalid gui-light option '' in 'generation' section. Allowed gui light options: []" diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index fcfa0a54f..2cb79684d 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -273,6 +273,7 @@ warning.config.block.behavior.stairs.missing_facing: "在文件 warning.config.block.behavior.stairs.missing_half: "在文件 发现问题 - 方块 '' 的 'stairs_block' 行为缺少必需的 'half' 属性" warning.config.block.behavior.stairs.missing_shape: "在文件 发现问题 - 方块 '' 的 'stairs_block' 行为缺少必需的 'shape' 属性" warning.config.block.behavior.pressure_plate.missing_powered: "在文件 发现问题 - 方块 '' 的 'pressure_plate_block' 行为缺少必需的 'powered' 属性" +warning.config.block.behavior.grass.missing_feature: "在文件 发现问题 - 方块 '' 的 'grass_block' 行为缺少必需的 'feature' 参数" warning.config.model.generation.missing_parent: "在文件 发现问题 - 配置项 '' 的 'generation' 段落缺少必需的 'parent' 参数" warning.config.model.generation.conflict: "在文件 发现问题 - 无法为 '' 生成模型 存在多个配置尝试使用相同路径 '' 生成不同的 JSON 模型" warning.config.model.generation.invalid_display_position: "在文件 发现问题 - 配置项 '' 在 'generation.display' 区域使用了无效的 display 位置类型 ''. 可用展示类型: []" diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java index 4c0d6b1a2..686b5e95d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java @@ -50,4 +50,8 @@ public class BlockPos extends Vec3i { public static long asLong(int x, int y, int z) { return (((long) x & (long) 67108863) << 38) | (((long) y & (long) 4095)) | (((long) z & (long) 67108863) << 12); } + + public BlockPos offset(int x, int y, int z) { + return x == 0 && y == 0 && z == 0 ? this : new BlockPos(this.x() + x, this.y() + y, this.z() + z); + } }