Skip to content

Commit b22aa5d

Browse files
authored
1.21.1 Non POI Block Searches (#695)
* backport non_poi_block_searches to 1.21.1 * update gitignore * build artefacts
1 parent eaff7c7 commit b22aa5d

15 files changed

+814
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ bin/
2525
/run/
2626
/common/run/
2727
/fabric/run/
28+
/fabric/config/
29+
/fabric/logs/
2830
/neoforge/run/
2931
/neoforge/runs/
3032

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package net.caffeinemc.mods.lithium.common.ai.non_poi_block_search;
2+
3+
import net.caffeinemc.mods.lithium.common.util.collections.FixedChunkAccessSectionBitBuffer;
4+
import net.minecraft.core.BlockPos;
5+
import net.minecraft.core.SectionPos;
6+
import net.minecraft.world.level.ChunkPos;
7+
import net.minecraft.world.level.LevelReader;
8+
import net.minecraft.world.level.block.state.BlockState;
9+
import net.minecraft.world.level.chunk.ChunkAccess;
10+
import net.minecraft.world.level.chunk.LevelChunkSection;
11+
import net.minecraft.world.level.chunk.status.ChunkStatus;
12+
13+
import java.util.function.Consumer;
14+
import java.util.function.Predicate;
15+
16+
/**
17+
* This is intended to be used to optimize Non-POI block searches by pre-checking the ChunkSections, getting the
18+
* ChunkAccesses which then allows for returning early or only calling getBlockState in possible ChunkSections.
19+
* <p>
20+
* Note: Please correctly specify shouldChunkLoad based on whether the getBlockState in vanilla can chunk load or not.
21+
* The default in game is that it can. Setting it to true will mean that the search will assume that unloaded chunks
22+
* may have the target and will chunk load then search them if and when it reaches it.
23+
*
24+
* @author jcw780
25+
*/
26+
public class CheckAndCacheBlockChecker {
27+
private final FixedChunkAccessSectionBitBuffer chunkSections2MaybeContainsMatchingBlock;
28+
private final LevelReader levelReader;
29+
public final boolean shouldChunkLoad;
30+
public final Predicate<BlockState> blockStatePredicate;
31+
private int unloadedPossibleChunkSections = 0;
32+
public final int minSectionY;
33+
34+
public CheckAndCacheBlockChecker(BlockPos origin, int horizontalRangeInclusive, int verticalRangeInclusive, LevelReader levelReader,
35+
Predicate<BlockState> blockStatePredicate, boolean shouldChunkLoad) {
36+
this.chunkSections2MaybeContainsMatchingBlock = new FixedChunkAccessSectionBitBuffer(origin, horizontalRangeInclusive, verticalRangeInclusive);
37+
this.levelReader = levelReader;
38+
this.shouldChunkLoad = shouldChunkLoad;
39+
this.blockStatePredicate = blockStatePredicate;
40+
this.minSectionY = levelReader.getMinSection();
41+
}
42+
43+
public void initializeChunks() {
44+
this.initializeChunks(null);
45+
}
46+
47+
public void initializeChunks(Consumer<Long> chunkCollector) {
48+
final boolean nullChunkCollector = chunkCollector == null;
49+
for (long chunkPos : this.chunkSections2MaybeContainsMatchingBlock.getChunkPosInRange()) {
50+
int x = ChunkPos.getX(chunkPos);
51+
int z = ChunkPos.getZ(chunkPos);
52+
boolean chunkMaybeHas = false;
53+
54+
//Never load chunks in the first pass to avoid observably altering chunk loading behavior
55+
//Otherwise full region will be loaded vs partial region if the search finds the block early.
56+
ChunkAccess chunkAccess = levelReader.getChunk(x, z, ChunkStatus.FULL, false);
57+
if (chunkAccess != null) {
58+
this.chunkSections2MaybeContainsMatchingBlock.setChunkAccess(chunkPos, chunkAccess);
59+
for (int y : this.chunkSections2MaybeContainsMatchingBlock.getSectionYInRange()) {
60+
chunkMaybeHas = this.checkChunkSection(chunkAccess, x, y, z) || chunkMaybeHas;
61+
}
62+
} else if (this.shouldChunkLoad) {
63+
/* If the search may chunk load then it is possible that target blocks may be revealed when the search
64+
* reaches it. Since we cannot load the chunks and check now, we cannot definitively exclude subchunks
65+
* inside the chunk. This means that we must flag subchunks that are within build limit - otherwise air
66+
* anyway - for the search.
67+
*/
68+
for (int y : this.chunkSections2MaybeContainsMatchingBlock.getSectionYInRange()) {
69+
this.chunkSections2MaybeContainsMatchingBlock.setChunkSectionStatus(SectionPos.asLong(x, y, z),
70+
!levelReader.isOutsideBuildHeight(SectionPos.sectionToBlockCoord(y)));
71+
++this.unloadedPossibleChunkSections;
72+
}
73+
chunkMaybeHas = true;
74+
75+
}
76+
77+
if (!nullChunkCollector && chunkMaybeHas) {
78+
chunkCollector.accept(chunkPos);
79+
}
80+
}
81+
}
82+
83+
public int getChunkSize(){
84+
return this.chunkSections2MaybeContainsMatchingBlock.numChunks;
85+
}
86+
87+
public boolean hasUnloadedPossibleChunks(){
88+
return this.unloadedPossibleChunkSections > 0;
89+
}
90+
91+
private boolean checkChunkSection(ChunkAccess chunkAccess, int chunkX, int chunkY, int chunkZ) {
92+
final int chunkSectionYIndex = chunkY - this.minSectionY;
93+
LevelChunkSection[] chunkSections = chunkAccess.getSections();
94+
if (chunkSectionYIndex >= 0
95+
&& chunkSectionYIndex < chunkSections.length
96+
&& chunkSections[chunkSectionYIndex].maybeHas(blockStatePredicate)) {
97+
this.chunkSections2MaybeContainsMatchingBlock.setChunkSectionStatus(
98+
SectionPos.asLong(chunkX, chunkY, chunkZ), true);
99+
return true;
100+
}
101+
return false;
102+
}
103+
104+
public boolean checkCachedSection(int chunkX, int chunkY, int chunkZ) {
105+
return this.chunkSections2MaybeContainsMatchingBlock.getChunkSectionBit(chunkX, chunkY, chunkZ);
106+
}
107+
108+
public ChunkAccess getCachedChunkAccess(long chunkPos) {
109+
return this.chunkSections2MaybeContainsMatchingBlock.getChunkAccess(chunkPos);
110+
}
111+
112+
public ChunkAccess getCachedChunkAccess(BlockPos blockPos) {
113+
return this.chunkSections2MaybeContainsMatchingBlock.getChunkAccess(blockPos);
114+
}
115+
116+
public boolean shouldStop(){
117+
return this.chunkSections2MaybeContainsMatchingBlock.hasNoTrueChunkSections();
118+
}
119+
120+
public boolean checkPosition(BlockPos blockPos) {
121+
if(!this.chunkSections2MaybeContainsMatchingBlock.getChunkSectionBit(blockPos)) return false;
122+
ChunkAccess chunkAccess = this.chunkSections2MaybeContainsMatchingBlock.getChunkAccess(blockPos);
123+
if(chunkAccess == null) {
124+
if (!this.shouldChunkLoad) {
125+
return false;
126+
}
127+
128+
final int chunkX = SectionPos.blockToSectionCoord(blockPos.getX());
129+
final int chunkY = SectionPos.blockToSectionCoord(blockPos.getY());
130+
final int chunkZ = SectionPos.blockToSectionCoord(blockPos.getZ());
131+
chunkAccess = levelReader.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true);
132+
//this chunkAccess cannot be null and reach here because it should throw earlier
133+
assert chunkAccess != null;
134+
this.chunkSections2MaybeContainsMatchingBlock.setChunkAccess(blockPos, chunkAccess);
135+
if (!checkChunkSection(chunkAccess, chunkX, chunkY, chunkZ)) {
136+
--this.unloadedPossibleChunkSections;
137+
return false;
138+
}
139+
}
140+
141+
return blockStatePredicate.test(chunkAccess.getBlockState(blockPos));
142+
}
143+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package net.caffeinemc.mods.lithium.common.ai.non_poi_block_search;
2+
3+
import net.minecraft.core.BlockPos;
4+
import net.minecraft.world.entity.LivingEntity;
5+
import net.minecraft.world.level.LevelReader;
6+
import net.minecraft.world.level.block.state.BlockState;
7+
8+
import java.util.Optional;
9+
import java.util.function.Predicate;
10+
11+
/**
12+
* Uses CheckAndCacheBlockChecker to improve common block searches
13+
*/
14+
public class CommonBlockSearchesCheckAndCache {
15+
/**
16+
* Optimizes BlockPos::findClosestMatch
17+
* [Vanilla Copy] search order and chunk-loading - even though the latter is unlikely to be observable in vanilla.
18+
*/
19+
public static Optional<BlockPos> blockPosFindClosestMatch(LevelReader levelReader, LivingEntity livingEntity,
20+
int horizontalRange, int verticalRange,
21+
Predicate<BlockState> blockStatePredicate,
22+
boolean shouldChunkLoad){
23+
BlockPos mobPos = livingEntity.blockPosition();
24+
CheckAndCacheBlockChecker checker = new CheckAndCacheBlockChecker(
25+
mobPos, horizontalRange, verticalRange, levelReader, blockStatePredicate, shouldChunkLoad);
26+
checker.initializeChunks();
27+
if(checker.shouldStop()) {
28+
return Optional.empty();
29+
}
30+
return BlockPos.findClosestMatch(mobPos, horizontalRange, verticalRange, checker::checkPosition);
31+
}
32+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package net.caffeinemc.mods.lithium.common.ai.non_poi_block_search;
2+
3+
import net.minecraft.core.BlockPos;
4+
import net.minecraft.world.level.block.state.BlockState;
5+
import net.minecraft.world.level.chunk.ChunkAccess;
6+
7+
import java.util.function.BiPredicate;
8+
import java.util.function.Predicate;
9+
10+
public interface LithiumMoveToBlockGoal {
11+
boolean lithium$findNearestBlock(Predicate<BlockState> requiredBlock,
12+
BiPredicate<ChunkAccess, BlockPos.MutableBlockPos> lithium$isValidTarget,
13+
final boolean shouldChunkLoad);
14+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package net.caffeinemc.mods.lithium.common.ai.non_poi_block_search;
2+
3+
import net.caffeinemc.mods.lithium.common.util.Distances;
4+
import net.minecraft.core.BlockPos;
5+
import net.minecraft.world.level.ChunkPos;
6+
7+
public class NonPOISearchDistances {
8+
public static class MoveToBlockGoalDistances {
9+
public static int getMinimumSortOrderOfChunk(BlockPos center, final long chunkPos) {
10+
return getMinimumSortOrderOfChunk(center, ChunkPos.getX(chunkPos), ChunkPos.getZ(chunkPos));
11+
}
12+
13+
public static int getMinimumSortOrderOfChunk(BlockPos center, final int chunkX, final int chunkZ) {
14+
final long closest = Distances.getClosestPositionWithinChunk(center, chunkX, chunkZ);
15+
16+
final int dX = BlockPos.getX(closest) - center.getX();
17+
final int dZ = BlockPos.getZ(closest) - center.getZ();
18+
19+
//This will always get the closest one due to the nature of the search
20+
return getVanillaSortOrderInt(getRing(dX, dZ), dX, dZ);
21+
}
22+
23+
public static int getRing(final int dX, final int dZ){
24+
return Math.max(Math.abs(dX), Math.abs(dZ));
25+
}
26+
27+
/**
28+
* Sort order function for 1 layer of MoveToBlockGoal findNearestBlock
29+
* This is equivalent to:
30+
* int withinRingX = Math.abs(dX) * 2 - (dX > 0 ? 1 : 0);
31+
* int withinRingZ = Math.abs(dZ) * 2 - (dZ > 0 ? 1 : 0);
32+
* return ring << 16 | withinRingX << 8 | withinRingZ;
33+
* <p>
34+
* This works because the search prioritizes in order of:
35+
* 1. The distance of y from the center - Not used
36+
* 2. Whether y is - or + (+ is closer) - Not used
37+
* 3. The square ring that the block is in (outer is further)
38+
* 4. The distance of x from the center
39+
* 5. Whether x is - or + (+ is closer)
40+
* 6. The distance of z from the center
41+
* 7. Whether z is - or + (+ is closer)
42+
* <p>
43+
* Note: The bit-packing only works for horizontal search ranges of <=128.
44+
* You can convert to longs if you somehow exceed that, but also seriously consider POIs instead.
45+
*
46+
* @param ring Which square ring the block is at relative to the center
47+
* @param dX Relative x position of the block to the center
48+
* @param dZ Relative z position of the block to the center
49+
*/
50+
public static int getVanillaSortOrderInt(final int ring, final int dX, final int dZ) {
51+
return (ring << 16 | Math.abs(dX) << 9 | Math.abs(dZ) << 1) - ((dX > 0 ? 1 : 0) << 8 | (dZ > 0 ? 1 : 0));
52+
}
53+
}
54+
}

common/src/main/java/net/caffeinemc/mods/lithium/common/util/Distances.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,15 @@ public static boolean isWithinSquareRadius(BlockPos origin, int radius, BlockPos
2929
public static boolean isWithinCircleRadius(BlockPos origin, double radiusSq, BlockPos pos) {
3030
return origin.distSqr(pos) <= radiusSq;
3131
}
32+
33+
public static int getClosestAlongSectionAxis(int originAxis, int chunkAxis){
34+
final int chunkMinAxis = SectionPos.sectionToBlockCoord(chunkAxis);
35+
return Math.min(Math.max(originAxis, chunkMinAxis), chunkMinAxis+15);
36+
}
37+
38+
public static long getClosestPositionWithinChunk(BlockPos origin, int chunkX, int chunkZ){
39+
return BlockPos.asLong(getClosestAlongSectionAxis(origin.getX(), chunkX),
40+
origin.getY(), getClosestAlongSectionAxis(origin.getZ(), chunkZ));
41+
42+
}
3243
}

0 commit comments

Comments
 (0)