From 668ddae98bf5f2f542d81ad758aedfaf454383ec Mon Sep 17 00:00:00 2001 From: AlexProgrammerDE <40795980+AlexProgrammerDE@users.noreply.github.com> Date: Mon, 6 May 2024 08:36:42 +0200 Subject: [PATCH] Implement build height limits --- .../pathfinding/graph/MinecraftGraph.java | 15 +++++++-- .../pathfinding/graph/ProjectedLevel.java | 10 ++++++ .../graph/actions/DownMovement.java | 4 +-- .../graph/actions/ParkourMovement.java | 2 +- .../graph/actions/SimpleMovement.java | 8 ++--- .../pathfinding/graph/actions/UpMovement.java | 31 ++++++++++++++----- .../protocol/bot/block/BlockAccessor.java | 5 +++ .../protocol/bot/state/ChunkHolder.java | 11 +++++++ .../test/utils/TestBlockAccessor.java | 13 ++++++++ 9 files changed, 83 insertions(+), 16 deletions(-) diff --git a/server/src/main/java/com/soulfiremc/server/pathfinding/graph/MinecraftGraph.java b/server/src/main/java/com/soulfiremc/server/pathfinding/graph/MinecraftGraph.java index fc307e873..32a8fc30c 100644 --- a/server/src/main/java/com/soulfiremc/server/pathfinding/graph/MinecraftGraph.java +++ b/server/src/main/java/com/soulfiremc/server/pathfinding/graph/MinecraftGraph.java @@ -35,6 +35,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.IntConsumer; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -85,6 +86,14 @@ public static boolean isBlockFree(BlockState blockState) { return blockState.blockShapeGroup().hasNoCollisions() && blockState.blockType().fluidType() == FluidType.EMPTY; } + public boolean disallowedToPlaceBlock(SFVec3i position) { + if (!canPlaceBlocks) { + return true; + } + + return !level.isPlaceable(position); + } + public void insertActions( SFVec3i node, Consumer callback) { log.debug("Inserting actions for node: {}", node); @@ -117,6 +126,8 @@ private void processSubscription( BlockState blockState = null; SFVec3i absolutePositionBlock = null; + IntConsumer impossibleActionRemover = actionIndex -> actions[actionIndex] = null; + // We cache only this, but not solid because solid will only occur a single time LazyBoolean isFree = null; for (var subscriber : value) { @@ -150,7 +161,7 @@ private void processSubscription( callback.accept(instruction); } } - case IMPOSSIBLE -> actions[subscriber.actionIndex] = null; + case IMPOSSIBLE -> impossibleActionRemover.accept(subscriber.actionIndex); } } } @@ -167,7 +178,7 @@ SubscriptionSingleResult processBlock( M action, LazyBoolean isFree, BlockState blockState, - SFVec3i absolutePositionBlock); + SFVec3i absoluteKey); default SubscriptionSingleResult processBlockUnsafe( MinecraftGraph graph, diff --git a/server/src/main/java/com/soulfiremc/server/pathfinding/graph/ProjectedLevel.java b/server/src/main/java/com/soulfiremc/server/pathfinding/graph/ProjectedLevel.java index 8fe4c2833..f40a7cef2 100644 --- a/server/src/main/java/com/soulfiremc/server/pathfinding/graph/ProjectedLevel.java +++ b/server/src/main/java/com/soulfiremc/server/pathfinding/graph/ProjectedLevel.java @@ -62,4 +62,14 @@ public BlockState getBlockState(SFVec3i position) { return accessor.getBlockState(position.x, position.y, position.z); } + + public boolean isPlaceable(SFVec3i position) { + var minBuildHeight = accessor.minBuildHeight(); + if (minBuildHeight.isPresent() && position.y < minBuildHeight.getAsInt()) { + return false; + } + + var maxBuildHeight = accessor.maxBuildHeight(); + return maxBuildHeight.isEmpty() || position.y < maxBuildHeight.getAsInt(); + } } diff --git a/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/DownMovement.java b/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/DownMovement.java index e948a924a..96e48832b 100644 --- a/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/DownMovement.java +++ b/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/DownMovement.java @@ -223,7 +223,7 @@ record DownMovementBlockSubscription( @Override public MinecraftGraph.SubscriptionSingleResult processBlock(MinecraftGraph graph, SFVec3i key, DownMovement downMovement, LazyBoolean isFree, - BlockState blockState, SFVec3i absolutePositionBlock) { + BlockState blockState, SFVec3i absoluteKey) { return switch (type) { case MOVEMENT_FREE -> { if (!graph.canBreakBlocks() @@ -238,7 +238,7 @@ public MinecraftGraph.SubscriptionSingleResult processBlock(MinecraftGraph graph // We can mine this block, lets add costs and continue downMovement.breakCost( new MovementMiningCost( - absolutePositionBlock, + absoluteKey, cacheableMiningCost.miningCost(), cacheableMiningCost.willDrop(), blockBreakSideHint)); diff --git a/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/ParkourMovement.java b/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/ParkourMovement.java index a9bd1aa5d..ebedee046 100644 --- a/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/ParkourMovement.java +++ b/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/ParkourMovement.java @@ -154,7 +154,7 @@ record ParkourMovementBlockSubscription( @Override public MinecraftGraph.SubscriptionSingleResult processBlock(MinecraftGraph graph, SFVec3i key, ParkourMovement parkourMovement, LazyBoolean isFree, - BlockState blockState, SFVec3i absolutePositionBlock) { + BlockState blockState, SFVec3i absoluteKey) { return switch (type) { case MOVEMENT_FREE -> { diff --git a/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/SimpleMovement.java b/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/SimpleMovement.java index 3e6919e11..aafdde5f8 100644 --- a/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/SimpleMovement.java +++ b/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/SimpleMovement.java @@ -506,7 +506,7 @@ record SimpleMovementBlockSubscription( @Override public MinecraftGraph.SubscriptionSingleResult processBlock(MinecraftGraph graph, SFVec3i key, SimpleMovement simpleMovement, LazyBoolean isFree, - BlockState blockState, SFVec3i absolutePositionBlock) { + BlockState blockState, SFVec3i absoluteKey) { return switch (type) { case MOVEMENT_FREE -> { if (isFree.get()) { @@ -534,7 +534,7 @@ public MinecraftGraph.SubscriptionSingleResult processBlock(MinecraftGraph graph // We can mine this block, lets add costs and continue simpleMovement.blockBreakCosts()[blockArrayIndex] = new MovementMiningCost( - absolutePositionBlock, + absoluteKey, cacheableMiningCost.miningCost(), cacheableMiningCost.willDrop(), blockBreakSideHint); @@ -578,7 +578,7 @@ public MinecraftGraph.SubscriptionSingleResult processBlock(MinecraftGraph graph yield MinecraftGraph.SubscriptionSingleResult.CONTINUE; } - if (!graph.canPlaceBlocks() + if (graph.disallowedToPlaceBlock(absoluteKey) || !simpleMovement.allowBlockActions() || !blockState.blockType().replaceable()) { yield MinecraftGraph.SubscriptionSingleResult.IMPOSSIBLE; @@ -602,7 +602,7 @@ public MinecraftGraph.SubscriptionSingleResult processBlock(MinecraftGraph graph // Fixup the position to be the block we are placing against instead of relative simpleMovement.blockPlaceAgainstData( new BotActionManager.BlockPlaceAgainstData( - absolutePositionBlock, blockToPlaceAgainst.blockFace())); + absoluteKey, blockToPlaceAgainst.blockFace())); yield MinecraftGraph.SubscriptionSingleResult.CONTINUE; } case MOVEMENT_ADD_CORNER_COST_IF_SOLID -> { diff --git a/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/UpMovement.java b/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/UpMovement.java index c66945a47..89fcf9852 100644 --- a/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/UpMovement.java +++ b/server/src/main/java/com/soulfiremc/server/pathfinding/graph/actions/UpMovement.java @@ -81,6 +81,11 @@ private static UpMovement registerUpMovement( } } + { + blockSubscribers + .accept(movement.blockPlacePosition(), new UpMovementBlockSubscription(UpMovementBlockSubscription.SubscriptionType.MOVEMENT_SOLID)); + } + { var safeBlocks = movement.listCheckSafeMineBlocks(); for (var i = 0; i < safeBlocks.length; i++) { @@ -145,6 +150,10 @@ public BlockSafetyData[][] listCheckSafeMineBlocks() { return results; } + public SFVec3i blockPlacePosition() { + return FEET_POSITION_RELATIVE_BLOCK; + } + @Override public List getInstructions(SFVec3i node) { var actions = new ObjectArrayList(); @@ -198,6 +207,10 @@ record UpMovementBlockSubscription( int blockArrayIndex, BlockFace blockBreakSideHint, BlockSafetyData.BlockSafetyType safetyType) implements MinecraftGraph.MovementSubscription { + UpMovementBlockSubscription(SubscriptionType type) { + this(type, -1, null, null); + } + UpMovementBlockSubscription(SubscriptionType type, int blockArrayIndex, BlockFace blockBreakSideHint) { this(type, blockArrayIndex, blockBreakSideHint, null); } @@ -211,12 +224,7 @@ record UpMovementBlockSubscription( @Override public MinecraftGraph.SubscriptionSingleResult processBlock(MinecraftGraph graph, SFVec3i key, UpMovement upMovement, LazyBoolean isFree, - BlockState blockState, SFVec3i absolutePositionBlock) { - // Towering requires placing a block below - if (!graph.canPlaceBlocks()) { - return MinecraftGraph.SubscriptionSingleResult.IMPOSSIBLE; - } - + BlockState blockState, SFVec3i absoluteKey) { return switch (type) { case MOVEMENT_FREE -> { if (isFree.get()) { @@ -237,12 +245,20 @@ public MinecraftGraph.SubscriptionSingleResult processBlock(MinecraftGraph graph // We can mine this block, lets add costs and continue upMovement.blockBreakCosts()[blockArrayIndex] = new MovementMiningCost( - absolutePositionBlock, + absoluteKey, cacheableMiningCost.miningCost(), cacheableMiningCost.willDrop(), blockBreakSideHint); yield MinecraftGraph.SubscriptionSingleResult.CONTINUE; } + case MOVEMENT_SOLID -> { + // Towering requires placing a block at old feet position + if (graph.disallowedToPlaceBlock(absoluteKey)) { + yield MinecraftGraph.SubscriptionSingleResult.IMPOSSIBLE; + } + + yield MinecraftGraph.SubscriptionSingleResult.CONTINUE; + } case MOVEMENT_BREAK_SAFETY_CHECK -> { // There is no need to break this block, so there is no need for safety checks if (upMovement.noNeedToBreak()[blockArrayIndex]) { @@ -285,6 +301,7 @@ public UpMovement castAction(GraphAction action) { enum SubscriptionType { MOVEMENT_FREE, + MOVEMENT_SOLID, MOVEMENT_BREAK_SAFETY_CHECK } } diff --git a/server/src/main/java/com/soulfiremc/server/protocol/bot/block/BlockAccessor.java b/server/src/main/java/com/soulfiremc/server/protocol/bot/block/BlockAccessor.java index 840173d23..e70aae6e1 100644 --- a/server/src/main/java/com/soulfiremc/server/protocol/bot/block/BlockAccessor.java +++ b/server/src/main/java/com/soulfiremc/server/protocol/bot/block/BlockAccessor.java @@ -18,6 +18,7 @@ package com.soulfiremc.server.protocol.bot.block; import com.soulfiremc.server.data.BlockState; +import java.util.OptionalInt; import org.cloudburstmc.math.vector.Vector3i; public interface BlockAccessor { @@ -26,4 +27,8 @@ public interface BlockAccessor { default BlockState getBlockState(Vector3i pos) { return getBlockState(pos.getX(), pos.getY(), pos.getZ()); } + + OptionalInt minBuildHeight(); + + OptionalInt maxBuildHeight(); } diff --git a/server/src/main/java/com/soulfiremc/server/protocol/bot/state/ChunkHolder.java b/server/src/main/java/com/soulfiremc/server/protocol/bot/state/ChunkHolder.java index b95109a36..29870cf15 100644 --- a/server/src/main/java/com/soulfiremc/server/protocol/bot/state/ChunkHolder.java +++ b/server/src/main/java/com/soulfiremc/server/protocol/bot/state/ChunkHolder.java @@ -26,6 +26,7 @@ import com.soulfiremc.server.util.NoopLock; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import java.util.OptionalInt; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -134,6 +135,16 @@ public BlockState getBlockState(int x, int y, int z) { return GlobalBlockPalette.INSTANCE.getBlockStateForStateId(chunkData.getBlock(x, y, z)); } + @Override + public OptionalInt minBuildHeight() { + return OptionalInt.of(minBuildHeight); + } + + @Override + public OptionalInt maxBuildHeight() { + return OptionalInt.of(maxBuildHeight); + } + public Long2ObjectMap getChunks() { return chunks.clone(); } diff --git a/server/src/test/java/com/soulfiremc/test/utils/TestBlockAccessor.java b/server/src/test/java/com/soulfiremc/test/utils/TestBlockAccessor.java index ddf315af1..fcda75669 100644 --- a/server/src/test/java/com/soulfiremc/test/utils/TestBlockAccessor.java +++ b/server/src/test/java/com/soulfiremc/test/utils/TestBlockAccessor.java @@ -22,6 +22,7 @@ import com.soulfiremc.server.pathfinding.SFVec3i; import com.soulfiremc.server.protocol.bot.block.BlockAccessor; import com.soulfiremc.server.util.Vec2ObjectOpenHashMap; +import java.util.OptionalInt; public class TestBlockAccessor implements BlockAccessor { private final Vec2ObjectOpenHashMap blocks = new Vec2ObjectOpenHashMap<>(); @@ -43,4 +44,16 @@ public void setBlockAt(int x, int y, int z, BlockType block) { public BlockState getBlockState(int x, int y, int z) { return blocks.getOrDefault(new SFVec3i(x, y, z), defaultBlock); } + + @Override + public OptionalInt minBuildHeight() { + // For tests, it can be any height + return OptionalInt.empty(); + } + + @Override + public OptionalInt maxBuildHeight() { + // For tests, it can be any height + return OptionalInt.empty(); + } }