From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NanaChiyo0721 Date: Sat, 25 Jul 2026 21:30:17 +0800 Subject: [PATCH] Lithium Optimizes move to block goal diff --git a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java index c6e9b155c2341c13d569f28f54f0c1d2d7313676..7bd3c8a1c627cfd3e377a269c649c0c169634b49 100644 --- a/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/MoveToBlockGoal.java @@ -5,7 +5,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.entity.PathfinderMob; import net.minecraft.world.level.LevelReader; -public abstract class MoveToBlockGoal extends Goal { +public abstract class MoveToBlockGoal extends Goal implements net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.LithiumMoveToBlockGoal { // Shiroha - Lithium Optimizes move to block goal private static final int GIVE_UP_TICKS = 1200; private static final int STAY_TICKS = 1200; private static final int INTERVAL_TICKS = 200; @@ -133,4 +133,167 @@ public abstract class MoveToBlockGoal extends Goal { } protected abstract boolean isValidTarget(LevelReader level, BlockPos pos); + // Shiorha start - Lithium Optimizes move to block goal + /** + * Finds the nearest block matching the predicates. + *

+ * Side effect: The matching block position is stored in the blockPos field. + * + * @return Whether a matching block was found. + */ + @Override + public boolean lithium$findNearestBlock(java.util.function.Predicate requiredBlock, java.util.function.BiPredicate lithium$isValidTarget, final boolean shouldChunkLoad) { + //Center of the search starts 1 block below the mob's block position + BlockPos center = this.mob.blockPosition().offset(0,-1,0); + + //Range is +-(searchRange - 1), +-verticalSearchRange, +-(searchRange - 1) + //Cache ChunkAccesses - getting them is surprisingly expensive - and track whether subchunks have the block + final LevelReader levelReader = this.mob.level(); + net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.CheckAndCacheBlockChecker checker = new net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.CheckAndCacheBlockChecker(center, + this.searchRange-1, this.verticalSearchRange, + levelReader, requiredBlock, shouldChunkLoad); + it.unimi.dsi.fastutil.longs.LongArrayList sortedChunksMaybeWithBlock = new it.unimi.dsi.fastutil.longs.LongArrayList(checker.getChunkSize()); + checker.initializeChunks(sortedChunksMaybeWithBlock::addLast); + + if (checker.shouldStop()) { + return false; //No chunks with the target block - return early + } + + final int minY = net.caffeinemc.mods.lithium.common.util.Pos.BlockCoord.getMinY(levelReader); + final int maxY = net.caffeinemc.mods.lithium.common.util.Pos.BlockCoord.getMaxYInclusive(levelReader); + + // Prefer chunk aware search because it also cuts iterations inside "empty" chunk sections + if(!checker.hasUnloadedPossibleChunks()){ + return this.lithium$chunkAwareSearch(center, lithium$isValidTarget, checker, sortedChunksMaybeWithBlock, minY, maxY); + } + + // Use vanilla search because unordered search may observably alter chunk-loading behavior + return this.lithium$vanillaOrderSearch(center, lithium$isValidTarget, checker, minY, maxY); + } + + private boolean lithium$vanillaOrderSearch(BlockPos center, + java.util.function.BiPredicate lithium$isValidTarget, + net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.CheckAndCacheBlockChecker checker, final int minY, final int maxY) { + BlockPos.MutableBlockPos currentPos = new BlockPos.MutableBlockPos(); + final int centerY = center.getY(); + + for (int layer = this.verticalSearchStart; layer <= this.verticalSearchRange; layer = layer > 0 ? -layer : 1 - layer) { + final int y = centerY + layer; + + // Layer outside of build limit - skip + // Note: this is likely to be hit because farms where this lags tend to be built at world floor + if (y < minY || y > maxY) { + continue; + } + + for (int ring = 0; ring < this.searchRange; ring++) { + for (int dX = 0; dX <= ring; dX = dX > 0 ? -dX : 1 - dX) { + for (int dZ = dX < ring && dX > -ring ? ring : 0; dZ <= ring; dZ = dZ > 0 ? -dZ : 1 - dZ) { + currentPos.setWithOffset(center, dX, layer, dZ); + if (this.mob.isWithinHome(currentPos) && checker.checkPosition(currentPos)) { + // ChunkAccess is always loaded at this point + net.minecraft.world.level.chunk.ChunkAccess chunkAccess = checker.getCachedChunkAccess(currentPos); + if (lithium$isValidTarget.test(chunkAccess, currentPos)){ + this.blockPos = currentPos; + return true; + } + } + } + } + } + } + + return false; + } + + private boolean lithium$chunkAwareSearch(BlockPos center, + java.util.function.BiPredicate lithium$isValidTarget, + net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.CheckAndCacheBlockChecker checker, it.unimi.dsi.fastutil.longs.LongArrayList sortedChunksMaybeWithBlock, + final int minY, final int maxY) { + // Sort chunks by lowest sort order - has the earliest searched position + // Note: In this search order, the closest point normally is also the closest point in the search + sortedChunksMaybeWithBlock.sort((chunkLong0, chunkLong1) -> + net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.NonPOISearchDistances.MoveToBlockGoalDistances.getMinimumSortOrderOfChunk(center, chunkLong0) + - net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.NonPOISearchDistances.MoveToBlockGoalDistances.getMinimumSortOrderOfChunk(center, chunkLong1) + ); + + java.util.function.Predicate requiredBlock = checker.blockStatePredicate; + final int minSectionY = checker.minSectionY; + + BlockPos.MutableBlockPos foundPos = new BlockPos.MutableBlockPos(); + BlockPos.MutableBlockPos currentPos = new BlockPos.MutableBlockPos(); + + // Same layer order as vanilla - saves iterations if targets are found in the first layer + for (int layer = this.verticalSearchStart; layer <= this.verticalSearchRange; layer = layer > 0 ? -layer : 1 - layer) { + final int y = center.getY() + layer; + + // Layer outside of build limit - skip + // Note: this is likely to be hit because farms where this lags tend to be built at world floor + if (y < minY || y > maxY) { + continue; + } + + final int chunkY = net.minecraft.core.SectionPos.blockToSectionCoord(y); + final int ySectionIndex = chunkY - minSectionY; + + int closestFound = Integer.MAX_VALUE; + int ringMax = this.searchRange - 1; + + // Iterate through slices of chunks that may have the target blockState + for (long chunkPos: sortedChunksMaybeWithBlock) { + final int chunkX = net.minecraft.world.level.ChunkPos.getX(chunkPos); + final int chunkZ = net.minecraft.world.level.ChunkPos.getZ(chunkPos); + + // Break since no subsequent chunks can be closer + if (closestFound < net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.NonPOISearchDistances.MoveToBlockGoalDistances.getMinimumSortOrderOfChunk(center, chunkX, chunkZ)) { + break; + } + + // Skip if the current subchunk does not have the block + if (!checker.checkCachedSection(chunkX, chunkY, chunkZ)) { + continue; + } + + net.minecraft.world.level.chunk.ChunkAccess chunkAccess = checker.getCachedChunkAccess(chunkPos); + // If ChunkSection may have close enough targets, iterate layer in Paletted Container (x then z) order + final int chunkBlockX = net.minecraft.core.SectionPos.sectionToBlockCoord(chunkX); + int xMin = Math.max(center.getX() - ringMax, chunkBlockX); + int xMax = Math.min(center.getX() + ringMax, chunkBlockX + 15); + final int chunkBlockZ = net.minecraft.core.SectionPos.sectionToBlockCoord(chunkZ); + int zMin = Math.max(center.getZ() - ringMax, chunkBlockZ); + int zMax = Math.min(center.getZ() + ringMax, chunkBlockZ + 15); + net.minecraft.world.level.chunk.LevelChunkSection levelChunkSection = chunkAccess.getSections()[ySectionIndex]; + for (int z = zMin; z <= zMax; z++) { + for (int x = xMin; x <= xMax; x++) { + int dX = x - center.getX(); + int dZ = z - center.getZ(); + int ring = net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.NonPOISearchDistances.MoveToBlockGoalDistances.getRing(dX, dZ); + int currentDistance = net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.NonPOISearchDistances.MoveToBlockGoalDistances.getVanillaSortOrderInt(ring, dX, dZ); + if (currentDistance < closestFound + && this.mob.isWithinHome(currentPos.set(x, y, z)) + && requiredBlock.test(levelChunkSection.getBlockState(x & 15, y & 15, z & 15)) + && lithium$isValidTarget.test(chunkAccess, currentPos)) { + // Constrain search size when we find a valid target + ringMax = ring; + xMin = Math.max(center.getX() - ringMax, chunkBlockX); + xMax = Math.min(center.getX() + ringMax, chunkBlockX + 15); + zMax = Math.min(center.getZ() + ringMax, chunkBlockZ + 15); + foundPos.set(x, y, z); + closestFound = currentDistance; + } + } + } + } + + if (closestFound < Integer.MAX_VALUE) { + // Vanilla uses the mutable pos, no need to create immutable copy + this.blockPos = foundPos; + return true; + } + } + + return false; + } + // Shiorha end - Lithium Optimizes move to block goal }