9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-30 20:39:10 +00:00

混合方块行为

This commit is contained in:
XiaoMoMi
2025-05-25 21:15:51 +08:00
parent de47525c3b
commit 5c827ddf62
24 changed files with 361 additions and 223 deletions

View File

@@ -428,7 +428,7 @@ public class BukkitBlockManager extends AbstractBlockManager {
.properties(properties)
.settings(settings)
.lootTable(LootTable.fromMap(MiscUtils.castToMap(section.get("loot"), true)))
.behavior(MiscUtils.castToMap(section.get("behavior"), true))
.behavior(MiscUtils.getAsMapList(ResourceConfigUtils.get(section, "behavior", "behaviors")))
.events(EventFunctions.parseEvents(ResourceConfigUtils.get(section, "events", "event")))
.build();

View File

@@ -41,7 +41,7 @@ public class BukkitCustomBlock extends AbstractCustomBlock {
@NotNull Map<String, VariantState> variantMapper,
@NotNull BlockSettings settings,
@NotNull Map<EventTrigger, List<Function<PlayerOptionalContext>>> events,
@Nullable Map<String, Object> behavior,
@Nullable List<Map<String, Object>> behavior,
@Nullable LootTable<?> lootTable
) {
super(id, holder, properties, appearances, variantMapper, settings, events, behavior, lootTable);
@@ -159,7 +159,7 @@ public class BukkitCustomBlock extends AbstractCustomBlock {
protected Map<String, Integer> appearances;
protected Map<String, VariantState> variantMapper;
protected BlockSettings settings;
protected Map<String, Object> behavior;
protected List<Map<String, Object>> behavior;
protected LootTable<?> lootTable;
protected Map<EventTrigger, List<Function<PlayerOptionalContext>>> events;
@@ -180,7 +180,7 @@ public class BukkitCustomBlock extends AbstractCustomBlock {
}
@Override
public Builder behavior(Map<String, Object> behavior) {
public Builder behavior(List<Map<String, Object>> behavior) {
this.behavior = behavior;
return this;
}

View File

@@ -0,0 +1,77 @@
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.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.item.Item;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldEvents;
import net.momirealms.craftengine.core.world.WorldPosition;
import java.util.concurrent.Callable;
public abstract class AbstractCanSurviveBlockBehavior extends BukkitBlockBehavior {
protected AbstractCanSurviveBlockBehavior(CustomBlock customBlock) {
super(customBlock);
}
@Override
public boolean canSurvive(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object state = args[0];
Object world = args[1];
Object pos = args[2];
return canSurvive(thisBlock, state, world, pos);
}
@Override
public void onPlace(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object world = args[1];
Object blockPos = args[2];
Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 2);
}
@Override
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object level;
Object blockPos;
Object state = args[0];
if (VersionHelper.isOrAbove1_21_2()) {
level = args[1];
blockPos = args[3];
} else {
level = args[3];
blockPos = args[4];
}
int stateId = BlockStateUtils.blockStateToId(state);
ImmutableBlockState previousState = BukkitBlockManager.instance().getImmutableBlockState(stateId);
if (previousState == null || previousState.isEmpty()) {
return state;
}
if (!canSurvive(thisBlock, new Object[] {state, level, blockPos}, () -> true)) {
BlockPos pos = LocationUtils.fromBlockPos(blockPos);
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos));
ContextHolder.Builder builder = ContextHolder.builder()
.withParameter(DirectContextParameters.POSITION, position);
for (Item<Object> item : previousState.getDrops(builder, world, null)) {
world.dropItemNaturally(position, item);
}
world.playBlockSound(position, previousState.sounds().breakSound());
FastNMS.INSTANCE.method$Level$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, stateId);
return Reflections.method$Block$defaultBlockState.invoke(Reflections.instance$Blocks$AIR);
}
return state;
}
protected abstract boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws Exception;
}

View File

@@ -12,9 +12,10 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
public static final Key STRIPPABLE_BLOCK = Key.from("craftengine:strippable_block");
public static final Key SAPLING_BLOCK = Key.from("craftengine:sapling_block");
public static final Key ON_LIQUID_BLOCK = Key.from("craftengine:on_liquid_block");
public static final Key NEAR_LIQUID_BLOCK = Key.from("craftengine:near_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 final Key VERTICAL_CROP_BLOCK = Key.from("craftengine:vertical_crop_block");
public static final Key CROP_BLOCK = Key.from("craftengine:crop_block");
public static final Key GRASS_BLOCK = Key.from("craftengine:grass_block");
public static final Key LAMP_BLOCK = Key.from("craftengine:lamp_block");
@@ -28,9 +29,10 @@ public class BukkitBlockBehaviors extends BlockBehaviors {
register(STRIPPABLE_BLOCK, StrippableBlockBehavior.FACTORY);
register(SAPLING_BLOCK, SaplingBlockBehavior.FACTORY);
register(ON_LIQUID_BLOCK, OnLiquidBlockBehavior.FACTORY);
register(NEAR_LIQUID_BLOCK, NearLiquidBlockBehavior.FACTORY);
register(WATERLOGGED_BLOCK, WaterLoggedBlockBehavior.FACTORY);
register(CONCRETE_POWDER_BLOCK, ConcretePowderBlockBehavior.FACTORY);
register(SUGARCANE_BLOCK, SugarCaneBlockBehavior.FACTORY);
register(VERTICAL_CROP_BLOCK, VerticalCropBlockBehavior.FACTORY);
register(CROP_BLOCK, CropBlockBehavior.FACTORY);
register(GRASS_BLOCK, GrassBlockBehavior.FACTORY);
register(LAMP_BLOCK, LampBlockBehavior.FACTORY);

View File

@@ -4,23 +4,13 @@ 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.BlockTags;
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.behavior.BlockBehaviorFactory;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.MiscUtils;
import net.momirealms.craftengine.core.util.Tuple;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldEvents;
import net.momirealms.craftengine.core.world.WorldPosition;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import org.bukkit.Bukkit;
import org.bukkit.Material;
@@ -28,76 +18,31 @@ import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import java.util.*;
import java.util.concurrent.Callable;
public class BushBlockBehavior extends BukkitBlockBehavior {
public class BushBlockBehavior extends AbstractCanSurviveBlockBehavior {
public static final Factory FACTORY = new Factory();
protected final List<Object> tagsCanSurviveOn;
protected final Set<Object> blocksCansSurviveOn;
protected final Set<String> customBlocksCansSurviveOn;
protected final boolean any;
protected final boolean stackable;
public BushBlockBehavior(CustomBlock block, List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn) {
public BushBlockBehavior(CustomBlock block, boolean stackable, List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn) {
super(block);
this.stackable = stackable;
this.tagsCanSurviveOn = tagsCanSurviveOn;
this.blocksCansSurviveOn = blocksCansSurviveOn;
this.customBlocksCansSurviveOn = customBlocksCansSurviveOn;
this.any = this.tagsCanSurviveOn.isEmpty() && this.blocksCansSurviveOn.isEmpty() && this.customBlocksCansSurviveOn.isEmpty();
}
@Override
public void onPlace(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object world = args[1];
Object blockPos = args[2];
Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 2);
}
@Override
public Object updateShape(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object level;
Object blockPos;
Object state = args[0];
if (VersionHelper.isOrAbove1_21_2()) {
level = args[1];
blockPos = args[3];
} else {
level = args[3];
blockPos = args[4];
}
if (!canSurvive(thisBlock, state, level, blockPos)) {
int stateId = BlockStateUtils.blockStateToId(state);
ImmutableBlockState previousState = BukkitBlockManager.instance().getImmutableBlockState(stateId);
if (previousState != null && !previousState.isEmpty()) {
BlockPos pos = LocationUtils.fromBlockPos(blockPos);
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(pos));
ContextHolder.Builder builder = ContextHolder.builder()
.withParameter(DirectContextParameters.POSITION, position);
for (Item<Object> item : previousState.getDrops(builder, world, null)) {
world.dropItemNaturally(position, item);
}
world.playBlockSound(position, previousState.sounds().breakSound());
FastNMS.INSTANCE.method$Level$levelEvent(level, WorldEvents.BLOCK_BREAK_EFFECT, blockPos, stateId);
}
return Reflections.method$Block$defaultBlockState.invoke(Reflections.instance$Blocks$AIR);
}
return super.updateShape(thisBlock, args, superMethod);
}
@Override
public boolean canSurvive(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object state = args[0];
Object world = args[1];
Object pos = args[2];
return canSurvive(thisBlock, state, world, pos);
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments, false);
return new BushBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right());
boolean stackable = (boolean) arguments.getOrDefault("stackable", false);
return new BushBlockBehavior(block, stackable, tuple.left(), tuple.mid(), tuple.right());
}
}
@@ -127,7 +72,8 @@ public class BushBlockBehavior extends BukkitBlockBehavior {
return new Tuple<>(mcTags, mcBlocks, customBlocks);
}
protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException {
@Override
protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws Exception {
int y = FastNMS.INSTANCE.field$Vec3i$y(blockPos);
int x = FastNMS.INSTANCE.field$Vec3i$x(blockPos);
int z = FastNMS.INSTANCE.field$Vec3i$z(blockPos);
@@ -151,12 +97,17 @@ public class BushBlockBehavior extends BukkitBlockBehavior {
return true;
}
} else {
ImmutableBlockState previousState = BukkitBlockManager.instance().getImmutableBlockState(id);
if (previousState != null && !previousState.isEmpty()) {
if (this.customBlocksCansSurviveOn.contains(previousState.owner().value().id().toString())) {
ImmutableBlockState belowCustomState = BukkitBlockManager.instance().getImmutableBlockState(id);
if (belowCustomState != null && !belowCustomState.isEmpty()) {
if (stackable) {
if (belowCustomState.owner().value() == super.customBlock) {
return true;
}
}
if (this.customBlocksCansSurviveOn.contains(belowCustomState.owner().value().id().toString())) {
return true;
}
if (this.customBlocksCansSurviveOn.contains(previousState.toString())) {
if (this.customBlocksCansSurviveOn.contains(belowCustomState.toString())) {
return true;
}
}

View File

@@ -25,15 +25,14 @@ import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
// TODO Inject FallingBlockEntity?
public class ConcretePowderBlockBehavior extends FallingBlockBehavior {
public class ConcretePowderBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Key targetBlock;
private final Key targetBlock; // TODO 更宽泛的使用state似乎也不是很好的方案
private Object defaultBlockState;
private ImmutableBlockState defaultImmutableBlockState;
public ConcretePowderBlockBehavior(CustomBlock block, float hurtAmount, int maxHurt, Key targetBlock) {
super(block, hurtAmount, maxHurt);
public ConcretePowderBlockBehavior(CustomBlock block, Key targetBlock) {
super(block);
this.targetBlock = targetBlock;
}
@@ -79,7 +78,7 @@ public class ConcretePowderBlockBehavior extends FallingBlockBehavior {
return super.updateStateForPlacement(context, state);
}
}
} catch (ReflectiveOperationException e) {
} catch (Exception e) {
CraftEngine.instance().logger().warn("Failed to update state for placement " + context.getClickedPos(), e);
}
return super.updateStateForPlacement(context, state);
@@ -118,7 +117,7 @@ public class ConcretePowderBlockBehavior extends FallingBlockBehavior {
}
}
}
return super.updateShape(thisBlock, args, superMethod);
return args[0];
}
private static boolean shouldSolidify(Object level, Object blockPos, Object blockState) throws ReflectiveOperationException {
@@ -155,10 +154,8 @@ public class ConcretePowderBlockBehavior extends FallingBlockBehavior {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
float hurtAmount = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("hurt-amount", -1f), "hurt-amount");
int hurtMax = ResourceConfigUtils.getAsInt(arguments.getOrDefault("max-hurt", -1), "max-hurt");
String solidBlock = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("solid-block"), "warning.config.block.behavior.concrete.missing_solid");
return new ConcretePowderBlockBehavior(block, hurtAmount, hurtMax, Key.of(solidBlock));
return new ConcretePowderBlockBehavior(block, Key.of(solidBlock));
}
}
}

View File

@@ -25,7 +25,6 @@ import net.momirealms.craftengine.core.plugin.context.number.NumberProviders;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.util.RandomUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.Tuple;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.Vec3i;
import net.momirealms.craftengine.core.world.WorldPosition;
@@ -33,12 +32,10 @@ import net.momirealms.craftengine.shared.block.BlockBehavior;
import org.bukkit.World;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
public class CropBlockBehavior extends BushBlockBehavior {
public class CropBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final IntegerProperty ageProperty;
private final float growSpeed;
@@ -46,9 +43,8 @@ public class CropBlockBehavior extends BushBlockBehavior {
private final boolean isBoneMealTarget;
private final NumberProvider boneMealBonus;
public CropBlockBehavior(CustomBlock block, List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn,
Property<Integer> ageProperty, float growSpeed, int minGrowLight, boolean isBoneMealTarget, NumberProvider boneMealBonus) {
super(block, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
public CropBlockBehavior(CustomBlock block, Property<Integer> ageProperty, float growSpeed, int minGrowLight, boolean isBoneMealTarget, NumberProvider boneMealBonus) {
super(block);
this.ageProperty = (IntegerProperty) ageProperty;
this.growSpeed = growSpeed;
this.minGrowLight = minGrowLight;
@@ -105,8 +101,10 @@ public class CropBlockBehavior extends BushBlockBehavior {
}
@Override
protected boolean canSurvive(Object thisBlock, Object state, Object world, Object blockPos) throws ReflectiveOperationException {
return hasSufficientLight(world, blockPos) && super.canSurvive(thisBlock, state, world, blockPos);
public boolean canSurvive(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object world = args[1];
Object pos = args[2];
return hasSufficientLight(world, pos);
}
@Override
@@ -204,13 +202,12 @@ public class CropBlockBehavior extends BushBlockBehavior {
@SuppressWarnings("unchecked")
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments, false);
Property<Integer> ageProperty = (Property<Integer>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("age"), "warning.config.block.behavior.crop.missing_age");
int minGrowLight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("light-requirement", 9), "light-requirement");
float growSpeed = ResourceConfigUtils.getAsFloat(arguments.getOrDefault("grow-speed", 0.125f), "grow-speed");
boolean isBoneMealTarget = (boolean) arguments.getOrDefault("is-bone-meal-target", true);
NumberProvider boneMealAgeBonus = NumberProviders.fromObject(arguments.getOrDefault("bone-meal-age-bonus", 1));
return new CropBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right(), ageProperty, growSpeed, minGrowLight, isBoneMealTarget, boneMealAgeBonus);
return new CropBlockBehavior(block, ageProperty, growSpeed, minGrowLight, isBoneMealTarget, boneMealAgeBonus);
}
}
}

View File

@@ -51,7 +51,7 @@ public class FallingBlockBehavior extends BukkitBlockBehavior {
blockPos = args[4];
}
Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 2);
return super.updateShape(thisBlock, args, superMethod);
return args[0];
}
@Override
@@ -109,6 +109,8 @@ public class FallingBlockBehavior extends BukkitBlockBehavior {
@Override
public void onLand(Object thisBlock, Object[] args) throws Exception {
Object fallingBlock = args[4];
Object level = args[0];
Object pos = args[1];
Object entityData = Reflections.field$Entity$entityData.get(fallingBlock);
boolean isSilent = (boolean) Reflections.method$SynchedEntityData$get.invoke(entityData, Reflections.instance$Entity$DATA_SILENT);
if (!isSilent) {
@@ -116,8 +118,6 @@ public class FallingBlockBehavior extends BukkitBlockBehavior {
int stateId = BlockStateUtils.blockStateToId(blockState);
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockState(stateId);
if (immutableBlockState == null || immutableBlockState.isEmpty()) return;
Object level = args[0];
Object pos = args[1];
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
world.playBlockSound(Vec3d.atCenterOf(LocationUtils.fromBlockPos(pos)), immutableBlockState.sounds().landSound());
}

View File

@@ -13,8 +13,8 @@ import java.util.Set;
public class HangingBlockBehavior extends BushBlockBehavior {
public static final Factory FACTORY = new Factory();
public HangingBlockBehavior(CustomBlock block, List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn) {
super(block, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
public HangingBlockBehavior(CustomBlock block, boolean stackable, List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn) {
super(block, stackable, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
}
@Override
@@ -32,7 +32,8 @@ public class HangingBlockBehavior extends BushBlockBehavior {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments, true);
return new HangingBlockBehavior(block, tuple.left(), tuple.mid(), tuple.right());
boolean stackable = (boolean) arguments.getOrDefault("stackable", false);
return new HangingBlockBehavior(block, stackable, tuple.left(), tuple.mid(), tuple.right());
}
}
}

View File

@@ -3,11 +3,13 @@ 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.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
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.item.context.BlockPlaceContext;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.shared.block.BlockBehavior;
@@ -23,6 +25,13 @@ public class LampBlockBehavior extends BukkitBlockBehavior {
this.litProperty = litProperty;
}
@Override
public ImmutableBlockState updateStateForPlacement(BlockPlaceContext context, ImmutableBlockState state) {
Object level = context.getLevel().serverWorld();
state = state.with(this.litProperty, FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(level, LocationUtils.toBlockPos(context.getClickedPos())));
return state;
}
@Override
public void tick(Object thisBlock, Object[] args, Callable<Object> superMethod) throws Exception {
Object blockState = args[0];
@@ -30,9 +39,16 @@ public class LampBlockBehavior extends BukkitBlockBehavior {
if (state == null || state.isEmpty()) return;
Object world = args[1];
Object blockPos = args[2];
if (state.get(this.litProperty) && !FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(world, blockPos)) {
// TODO Call Event
FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, state.cycle(this.litProperty).customBlockState().handle(), 2);
if (state.get(this.litProperty)) {
if (!FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(world, blockPos)) {
// TODO Call Event
FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, state.cycle(this.litProperty).customBlockState().handle(), 2);
}
} else {
if (FastNMS.INSTANCE.method$SignalGetter$hasNeighborSignal(world, blockPos)) {
// TODO Call Event
FastNMS.INSTANCE.method$LevelWriter$setBlock(world, blockPos, state.cycle(this.litProperty).customBlockState().handle(), 2);
}
}
}

View File

@@ -0,0 +1,102 @@
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.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
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.util.MiscUtils;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import java.util.List;
import java.util.Map;
public class NearLiquidBlockBehavior extends AbstractCanSurviveBlockBehavior {
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);
public static final Factory FACTORY = new Factory();
private final boolean onWater;
private final boolean onLava;
private final boolean stackable;
private final BlockPos[] positions;
public NearLiquidBlockBehavior(CustomBlock block, BlockPos[] positions, boolean stackable, boolean onWater, boolean onLava) {
super(block);
this.onWater = onWater;
this.onLava = onLava;
this.stackable = stackable;
this.positions = positions;
}
public boolean onWater() {
return this.onWater;
}
public boolean onLava() {
return this.onLava;
}
public static class Factory implements BlockBehaviorFactory {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
List<String> liquidTypes = MiscUtils.getAsStringList(arguments.getOrDefault("liquid-type", List.of("water")));
boolean stackable = (boolean) arguments.getOrDefault("stackable", false);
List<String> positionsToCheck = MiscUtils.getAsStringList(arguments.getOrDefault("positions", List.of()));
if (positionsToCheck.isEmpty()) {
return new NearLiquidBlockBehavior(block, new BlockPos[]{new BlockPos(0,-1,0)}, stackable, liquidTypes.contains("water"), liquidTypes.contains("lava"));
} else {
BlockPos[] pos = new BlockPos[positionsToCheck.size()];
for (int i = 0; i < pos.length; i++) {
String[] split = positionsToCheck.get(i).split(",");
pos[i] = new BlockPos(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2]));
}
return new NearLiquidBlockBehavior(block, pos, stackable, liquidTypes.contains("water"), liquidTypes.contains("lava"));
}
}
}
@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);
if (this.stackable) {
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() == super.customBlock) {
return true;
}
}
}
for (BlockPos pos : positions) {
Object belowPos = FastNMS.INSTANCE.constructor$BlockPos(x + pos.x(), y + pos.y(), z + pos.z());
Object belowState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(world, belowPos);
if (mayPlaceOn(belowState, world, belowPos)) {
return true;
}
}
return false;
}
protected boolean mayPlaceOn(Object belowState, Object world, Object belowPos) throws ReflectiveOperationException {
Object fluidState = Reflections.method$Level$getFluidState.invoke(world, belowPos);
Object fluidStateAbove = Reflections.method$Level$getFluidState.invoke(world, LocationUtils.above(belowPos));
if (Reflections.method$FluidState$getType.invoke(fluidStateAbove) != Reflections.instance$Fluids$EMPTY) {
return false;
}
if (this.onWater && (WATER.contains(Reflections.method$FluidState$getType.invoke(fluidState)) || Reflections.field$StateHolder$owner.get(belowState) == Reflections.instance$Blocks$ICE)) {
return true;
}
if (this.onLava && LAVA.contains(Reflections.method$FluidState$getType.invoke(fluidState))) {
return true;
}
return false;
}
}

View File

@@ -1,25 +1,30 @@
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.bukkit.util.LocationUtils;
import net.momirealms.craftengine.bukkit.util.Reflections;
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.util.MiscUtils;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class OnLiquidBlockBehavior extends BushBlockBehavior {
public class OnLiquidBlockBehavior extends AbstractCanSurviveBlockBehavior {
public static final Factory FACTORY = new Factory();
private final boolean onWater;
private final boolean onLava;
private final boolean stackable;
public OnLiquidBlockBehavior(CustomBlock block, boolean onWater, boolean onLava) {
super(block, List.of(), Set.of(), Set.of());
public OnLiquidBlockBehavior(CustomBlock block, boolean stackable, boolean onWater, boolean onLava) {
super(block);
this.onWater = onWater;
this.onLava = onLava;
this.stackable = stackable;
}
public boolean onWater() {
@@ -34,12 +39,31 @@ public class OnLiquidBlockBehavior extends BushBlockBehavior {
@Override
public BlockBehavior create(CustomBlock block, Map<String, Object> arguments) {
List<String> liquidTypes = MiscUtils.getAsStringList(arguments.getOrDefault("liquid-type", List.of("water")));
return new OnLiquidBlockBehavior(block, liquidTypes.contains("water"), liquidTypes.contains("lava"));
boolean stackable = (boolean) arguments.getOrDefault("stackable", false);
return new OnLiquidBlockBehavior(block, stackable, liquidTypes.contains("water"), liquidTypes.contains("lava"));
}
}
@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);
return mayPlaceOn(belowState, world, belowPos);
}
protected boolean mayPlaceOn(Object belowState, Object world, Object belowPos) throws ReflectiveOperationException {
if (this.stackable) {
int id = BlockStateUtils.blockStateToId(belowState);
if (!BlockStateUtils.isVanillaBlock(id)) {
ImmutableBlockState immutableBlockState = BukkitBlockManager.instance().getImmutableBlockStateUnsafe(id);
if (immutableBlockState.owner().value() == super.customBlock) {
return true;
}
}
}
Object fluidState = Reflections.method$Level$getFluidState.invoke(world, belowPos);
Object fluidStateAbove = Reflections.method$Level$getFluidState.invoke(world, LocationUtils.above(belowPos));
if (Reflections.method$FluidState$getType.invoke(fluidStateAbove) != Reflections.instance$Fluids$EMPTY) {

View File

@@ -17,26 +17,23 @@ import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.util.Key;
import net.momirealms.craftengine.core.util.RandomUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.Tuple;
import net.momirealms.craftengine.shared.block.BlockBehavior;
import org.bukkit.Location;
import org.bukkit.World;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
public class SaplingBlockBehavior extends BushBlockBehavior {
public class SaplingBlockBehavior extends BukkitBlockBehavior {
public static final Factory FACTORY = new Factory();
private final Key feature;
private final Property<Integer> stageProperty;
private final double boneMealSuccessChance;
private final float growSpeed;
public SaplingBlockBehavior(CustomBlock block, Key feature, Property<Integer> stageProperty, List<Object> tagsCanSurviveOn, Set<Object> blocksCansSurviveOn, Set<String> customBlocksCansSurviveOn, double boneMealSuccessChance, float growSpeed) {
super(block, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
public SaplingBlockBehavior(CustomBlock block, Key feature, Property<Integer> stageProperty, double boneMealSuccessChance, float growSpeed) {
super(block);
this.feature = feature;
this.stageProperty = stageProperty;
this.boneMealSuccessChance = boneMealSuccessChance;
@@ -53,7 +50,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) RandomUtils.generateRandomFloat(0, 1) < growSpeed) {
if ((int) Reflections.method$LevelReader$getMaxLocalRawBrightness.invoke(world, aboveBlockPos) >= 9 && RandomUtils.generateRandomFloat(0, 1) < growSpeed) {
increaseStage(world, blockPos, blockState, args[3]);
}
}
@@ -175,8 +172,7 @@ public class SaplingBlockBehavior extends BushBlockBehavior {
String feature = ResourceConfigUtils.requireNonEmptyStringOrThrow(arguments.get("feature"), "warning.config.block.behavior.sapling.missing_feature");
Property<Integer> stageProperty = (Property<Integer>) 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");
Tuple<List<Object>, Set<Object>, Set<String>> tuple = readTagsAndState(arguments, false);
return new SaplingBlockBehavior(block, Key.of(feature), stageProperty, tuple.left(), tuple.mid(), tuple.right(), boneMealSuccessChance,
return new SaplingBlockBehavior(block, Key.of(feature), stageProperty, boneMealSuccessChance,
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("grow-speed", 1.0 / 7.0), "grow-speed"));
}
}

View File

@@ -15,37 +15,32 @@ import net.momirealms.craftengine.core.block.properties.Property;
import net.momirealms.craftengine.core.item.Item;
import net.momirealms.craftengine.core.plugin.context.ContextHolder;
import net.momirealms.craftengine.core.plugin.context.parameter.DirectContextParameters;
import net.momirealms.craftengine.core.util.*;
import net.momirealms.craftengine.core.util.RandomUtils;
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
import net.momirealms.craftengine.core.util.VersionHelper;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.Vec3d;
import net.momirealms.craftengine.core.world.WorldEvents;
import net.momirealms.craftengine.core.world.WorldPosition;
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 class VerticalCropBlockBehavior extends BukkitBlockBehavior {
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 ageProperty;
private final float growSpeed;
private final boolean direction;
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(customBlock, tagsCanSurviveOn, blocksCansSurviveOn, customBlocksCansSurviveOn);
this.nearWater = nearWater;
this.nearLava = nearLava;
public VerticalCropBlockBehavior(CustomBlock customBlock, Property<Integer> ageProperty,
int maxHeight, float growSpeed, boolean direction) {
super(customBlock);
this.maxHeight = maxHeight;
this.ageProperty = (IntegerProperty) ageProperty;
this.growSpeed = growSpeed;
this.direction = direction;
}
@Override
@@ -53,11 +48,11 @@ public class SugarCaneBlockBehavior extends BushBlockBehavior {
Object blockState = args[0];
Object level = args[1];
Object blockPos = args[2];
if (!canSurvive(thisBlock, blockState, level, blockPos)) {
if (!canSurvive(thisBlock, args, () -> true)) {
int stateId = BlockStateUtils.blockStateToId(blockState);
ImmutableBlockState currentState = BukkitBlockManager.instance().getImmutableBlockState(stateId);
if (currentState != null && !currentState.isEmpty()) {
// break the sugar cane
// break the crop
FastNMS.INSTANCE.method$Level$removeBlock(level, blockPos, false);
net.momirealms.craftengine.core.world.World world = new BukkitWorld(FastNMS.INSTANCE.method$Level$getCraftWorld(level));
WorldPosition position = new WorldPosition(world, Vec3d.atCenterOf(LocationUtils.fromBlockPos(blockPos)));
@@ -85,7 +80,7 @@ public class SugarCaneBlockBehavior extends BushBlockBehavior {
}
Reflections.method$LevelAccessor$scheduleTick.invoke(world, blockPos, thisBlock, 1);
// return state, do not call super.
return superMethod.call();
return args[0];
}
@Override
@@ -94,15 +89,15 @@ public class SugarCaneBlockBehavior extends BushBlockBehavior {
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) {
if (FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, (this.direction ? LocationUtils.above(blockPos) : LocationUtils.below(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));
Object nextPos = LocationUtils.toBlockPos(currentPos.x(), this.direction ? currentPos.y() - currentHeight : currentPos.y() + currentHeight, currentPos.z());
Object nextState = FastNMS.INSTANCE.method$BlockGetter$getBlockState(level, nextPos);
ImmutableBlockState belowImmutableState = BukkitBlockManager.instance().getImmutableBlockState(BlockStateUtils.blockStateToId(nextState));
if (belowImmutableState != null && !belowImmutableState.isEmpty() && belowImmutableState.owner() == currentState.owner()) {
currentHeight++;
} else {
@@ -116,11 +111,11 @@ public class SugarCaneBlockBehavior extends BushBlockBehavior {
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);
Object nextPos = this.direction ? LocationUtils.above(blockPos) : LocationUtils.below(blockPos);
if (VersionHelper.isOrAbove1_21_5()) {
Reflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, abovePos, super.customBlock.defaultState().customBlockState().handle(), UpdateOption.UPDATE_ALL.flags());
Reflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, nextPos, super.customBlock.defaultState().customBlockState().handle(), UpdateOption.UPDATE_ALL.flags());
} else {
Reflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, abovePos, super.customBlock.defaultState().customBlockState().handle());
Reflections.method$CraftEventFactory$handleBlockGrowEvent.invoke(null, level, nextPos, super.customBlock.defaultState().customBlockState().handle());
}
FastNMS.INSTANCE.method$LevelWriter$setBlock(level, blockPos, currentState.with(this.ageProperty, this.ageProperty.min).customBlockState().handle(), UpdateOption.UPDATE_NONE.flags());
} else if (RandomUtils.generateRandomFloat(0, 1) < this.growSpeed) {
@@ -130,81 +125,16 @@ public class SugarCaneBlockBehavior extends BushBlockBehavior {
}
}
@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() == super.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, false);
Property<Integer> ageProperty = (Property<Integer>) ResourceConfigUtils.requireNonNullOrThrow(block.getProperty("age"), "warning.config.block.behavior.sugar_cane.missing_age");
int maxHeight = ResourceConfigUtils.getAsInt(arguments.getOrDefault("max-height", 3), "max-height");
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,
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("grow-speed", 1), "grow-speed"));
boolean direction = arguments.getOrDefault("direction", "up").toString().equalsIgnoreCase("up");
return new VerticalCropBlockBehavior(block, ageProperty, maxHeight,
ResourceConfigUtils.getAsFloat(arguments.getOrDefault("grow-speed", 1), "grow-speed"), direction);
}
}
}

View File

@@ -37,10 +37,14 @@ public class LocationUtils {
return toBlockPos(pos.x(), pos.y(), pos.z());
}
public static Object above(Object blockPos) throws ReflectiveOperationException {
public static Object above(Object blockPos) {
return toBlockPos(FastNMS.INSTANCE.field$Vec3i$x(blockPos), FastNMS.INSTANCE.field$Vec3i$y(blockPos) + 1, FastNMS.INSTANCE.field$Vec3i$z(blockPos));
}
public static Object below(Object blockPos) {
return toBlockPos(FastNMS.INSTANCE.field$Vec3i$x(blockPos), FastNMS.INSTANCE.field$Vec3i$y(blockPos) - 1, FastNMS.INSTANCE.field$Vec3i$z(blockPos));
}
public static Object toBlockPos(int x, int y, int z) {
return FastNMS.INSTANCE.constructor$BlockPos(x, y, z);
}
@@ -49,7 +53,7 @@ public class LocationUtils {
return new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ());
}
public static BlockPos fromBlockPos(Object pos) throws ReflectiveOperationException {
public static BlockPos fromBlockPos(Object pos) {
return new BlockPos(
FastNMS.INSTANCE.field$Vec3i$x(pos),
FastNMS.INSTANCE.field$Vec3i$y(pos),