9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-28 19:39:11 +00:00

add sugarcane block

This commit is contained in:
XiaoMoMi
2025-03-28 03:25:30 +08:00
parent 2c648e46f8
commit 1f7fb926d2
13 changed files with 300 additions and 23 deletions

View File

@@ -14,6 +14,7 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
public static final Key ON_LIQUID_BLOCK = Key.from("craftengine:on_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 SUGARCANE_BLOCK = Key.from("craftengine:sugar_cane_block");
public static void init() {
register(EMPTY, (block, args) -> EmptyBlockBehavior.INSTANCE);
@@ -26,5 +27,6 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
register(ON_LIQUID_BLOCK, OnLiquidBlockBehavior.FACTORY);
register(WATERLOGGED_BLOCK, WaterLoggedBlockBehavior.FACTORY);
register(CONCRETE_POWDER_BLOCK, ConcretePowderBlockBehavior.FACTORY);
register(SUGARCANE_BLOCK, SugarCaneBlockBehavior.FACTORY);
}
}

View File

@@ -32,12 +32,14 @@ public class SaplingBlockBehavior extends BushBlockBehavior {
private final Key feature;
private final Property<Integer> stageProperty;
private final double boneMealSuccessChance;
private final float growSpeed;
public SaplingBlockBehavior(Key feature, Property<Integer> stageProperty, List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn, double boneMealSuccessChance) {
public SaplingBlockBehavior(Key feature, Property<Integer> stageProperty, List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn, double boneMealSuccessChance, float growSpeed) {
super(tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
this.feature = feature;
this.stageProperty = stageProperty;
this.boneMealSuccessChance = boneMealSuccessChance;
this.growSpeed = growSpeed;
}
public Key treeFeature() {
@@ -50,7 +52,7 @@ public class SaplingBlockBehavior extends BushBlockBehavior {
Object blockPos = args[2];
Object blockState = args[0];
Object aboveBlockPos = LocationUtils.above(blockPos);
if ((int) Reflections.method$LevelReader$getMaxLocalRawBrightness.invoke(world, aboveBlockPos) >= 9 && (float) Reflections.method$RandomSource$nextFloat.invoke(args[3]) < (1.0f / 7.0f)) {
if ((int) Reflections.method$LevelReader$getMaxLocalRawBrightness.invoke(world, aboveBlockPos) >= 9 && (float) RandomUtils.generateRandomFloat(0, 1) < growSpeed) {
increaseStage(world, blockPos, blockState, args[3]);
}
}
@@ -125,7 +127,8 @@ public class SaplingBlockBehavior extends BushBlockBehavior {
}
double boneMealSuccessChance = MiscUtils.getAsDouble(arguments.getOrDefault("bone-meal-success-chance", 0.45));
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
return new SaplingBlockBehavior(Key.of(feature), stageProperty, tuple.left(), tuple.mid(), tuple.right(), boneMealSuccessChance);
return new SaplingBlockBehavior(Key.of(feature), stageProperty, tuple.left(), tuple.mid(), tuple.right(), boneMealSuccessChance,
MiscUtils.getAsFloat(arguments.getOrDefault("grow-speed", 1.0 / 7.0)));
}
}
}

View File

@@ -1,36 +1,55 @@
package net.momirealms.craftengine.bukkit.block.behavior;
import com.sk89q.worldedit.blocks.Blocks;
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.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
import net.momirealms.craftengine.bukkit.world.BukkitWorld;
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.IntegerProperty;
import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.loot.parameter.LootParameters;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.RandomUtils;
import net.momirealms.craftengine.core.util.Tuple;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.util.context.ContextHolder;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import org.bukkit.Material;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
public class SugarCaneBlockBehavior extends BushBlockBehavior {
public static final Factory FACTORY = new Factory();
private static final List<Object> WATER = List.of(Reflections.instance$Fluids$WATER, Reflections.instance$Fluids$FLOWING_WATER);
private static final List<Object> LAVA = List.of(Reflections.instance$Fluids$LAVA, Reflections.instance$Fluids$FLOWING_LAVA);
private static final List<Object> HORIZON_DIRECTIONS = List.of(Reflections.instance$Direction$NORTH, Reflections.instance$Direction$EAST, Reflections.instance$Direction$SOUTH, Reflections.instance$Direction$WEST);
private final int maxHeight;
private final boolean nearWater;
private final boolean nearLava;
private final IntegerProperty age;
private final IntegerProperty ageProperty;
private final float growSpeed;
private final CustomBlock customBlock;
public SugarCaneBlockBehavior(List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn, IntegerProperty age, int maxHeight, boolean nearWater, boolean nearLava) {
public SugarCaneBlockBehavior(CustomBlock customBlock, List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn, Property<Integer> ageProperty,
int maxHeight, boolean nearWater, boolean nearLava, float growSpeed) {
super(tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
this.nearWater = nearWater;
this.nearLava = nearLava;
this.maxHeight = maxHeight;
this.age = age;
this.ageProperty = (IntegerProperty) ageProperty;
this.growSpeed = growSpeed;
this.customBlock = customBlock;
}
@Override
@@ -38,17 +57,156 @@ public class SugarCaneBlockBehavior extends BushBlockBehavior {
Object blockState = args[0];
Object level = args[1];
Object blockPos = args[2];
ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
if (currentState != null && !currentState.isEmpty()) {
Reflections.method$Level$removeBlock.invoke(level, blockPos, false);
Vec3d vec3d = Vec3d.atCenterOf(LocationUtils.fromBlockPos(blockPos));
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
ContextHolder.Builder builder = ContextHolder.builder()
.withParameter(LootParameters.LOCATION, vec3d)
.withParameter(LootParameters.WORLD, world);
for (Item<Object> item : currentState.getDrops(builder, world)) {
world.dropItemNaturally(vec3d, item);
if (!canSurvive(thisBlock, blockState, level, blockPos)) {
ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
if (currentState != null && !currentState.isEmpty()) {
// break the sugar cane
Reflections.method$Level$removeBlock.invoke(level, blockPos, false);
Vec3d vec3d = Vec3d.atCenterOf(LocationUtils.fromBlockPos(blockPos));
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
// TODO client side particles?
ContextHolder.Builder builder = ContextHolder.builder()
.withParameter(LootParameters.LOCATION, vec3d)
.withParameter(LootParameters.WORLD, world);
for (Item<Object> item : currentState.getDrops(builder, world)) {
world.dropItemNaturally(vec3d, item);
}
}
}
}
@Override
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object world;
Object blockPos;
if (VersionHelper.isVersionNewerThan1_21_2()) {
world = args[1];
blockPos = args[3];
} else {
world = args[3];
blockPos = args[4];
}
Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 1);
// return state, do not call super.
return superMethod.call();
}
@Override
public void randomTick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object blockState = args[0];
Object level = args[1];
Object blockPos = args[2];
// above block is empty
if (FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, LocationUtils.above(blockPos)) == Reflections.instance$Blocks$AIR$defaultState) {
int currentHeight = 1;
BlockPos currentPos = LocationUtils.fromBlockPos(blockPos);
ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(blockState));
if (currentState != null && !currentState.isEmpty()) {
while (true) {
Object belowPos = LocationUtils.toBlockPos(currentPos.x(), currentPos.y() - currentHeight, currentPos.z());
Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, belowPos);
ImmutableBlockState belowImmutableState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(belowState));
if (belowImmutableState != null && !belowImmutableState.isEmpty() && belowImmutableState.owner() == currentState.owner()) {
currentHeight++;
} else {
break;
}
}
} else {
return;
}
if (currentHeight < this.maxHeight) {
int age = currentState.get(ageProperty);
if (age >= this.ageProperty.max || RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) {
Object abovePos = LocationUtils.above(blockPos);
Reflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, abovePos, customBlock.defaultState().customBlockState().handle());
Reflections.method$Level$setBlock.invoke(level, blockPos, currentState.with(this.ageProperty, this.ageProperty.min).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags());
} else if (RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) {
Reflections.method$Level$setBlock.invoke(level, blockPos, currentState.with(this.ageProperty, age + 1).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags());
}
}
}
}
@Override
protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException {
int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos);
int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos);
int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos);
Object belowPos = FastNMS.INSTANCE.constructor$BlockPos(x, y - 1, z);
Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, belowPos);
int id = BlockStateUtils.blockStateToId(belowState);
// 如果下方是同种方块
if (!BlockStateUtils.isVanillaBlock(id)) {
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(id);
if (immutableBlockState.owner().value() == this.customBlock) {
return true;
}
}
if (!super.mayPlaceOn(belowState, world, belowPos)) {
return false;
}
// 如果不需要依靠流体
if (!this.nearWater && !this.nearLava) {
return true;
}
// 需要流体
if (this.nearWater) {
if (hasNearbyLiquid(world, belowPos, true)) {
return true;
}
}
if (this.nearLava) {
if (hasNearbyLiquid(world, belowPos, false)) {
return true;
}
}
return false;
}
private boolean hasNearbyLiquid(Object world, Object blockPos, boolean waterOrLava) throws ReflectiveOperationException {
for (Object direction : HORIZON_DIRECTIONS) {
Object relativePos = Reflections.method$BlockPos$relative.invoke(blockPos, direction);
if (waterOrLava) {
// water
Object blockState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, relativePos);
if (Reflections.method$BlockStateBase$getBlock.invoke(blockState) == Reflections.instance$Blocks$ICE) {
return true;
}
Object fluidState = Reflections.method$Level$getFluidState.invoke(world, relativePos);
Object fluidType = Reflections.method$FluidState$getType.invoke(fluidState);
if (WATER.contains(fluidType)) {
return true;
}
} else {
// lava
Object fluidState = Reflections.method$Level$getFluidState.invoke(world, relativePos);
Object fluidType = Reflections.method$FluidState$getType.invoke(fluidState);
if (LAVA.contains(fluidType)) {
return true;
}
}
}
return false;
}
public static class Factory implements BlockBehaviorFactory {
@SuppressWarnings("unchecked")
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments);
Property<Integer> ageProperty = (Property<Integer>) block.getProperty("age");
if (ageProperty == null) {
throw new IllegalArgumentException("age property not set for sugar cane");
}
int maxHeight = MiscUtils.getAsInt(arguments.getOrDefault("max-height", 3));
List<String> nearbyLiquids = MiscUtils.getAsStringList(arguments.getOrDefault("required-adjacent-liquids", List.of()));
boolean nearWater = nearbyLiquids.contains("water");
boolean nearLava = nearbyLiquids.contains("lava");
return new SugarCaneBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right(), ageProperty, maxHeight, nearWater, nearLava,
MiscUtils.getAsFloat(arguments.getOrDefault("grow-speed", 1)));
}
}
}

View File

@@ -5685,4 +5685,10 @@ public class Reflections {
clazz$ClientboundDisconnectPacket, clazz$Component
)
);
public static final Method method$CraftEventFactory$handleBlockGrowEvent = requireNonNull(
ReflectionUtils.getStaticMethod(
clazz$CraftEventFactory, boolean.class, clazz$Level, clazz$BlockPos, clazz$BlockState
)
);
}