|
| 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 | +} |
0 commit comments