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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user