From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: NanaChiyo0721 Date: Sat, 25 Jul 2026 21:16:19 +0800 Subject: [PATCH] Lithium optimized non poi block searching 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 } diff --git a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java index ff0f99d4b560a1c0721885b0eba5bd5ab2087bd7..90beeee7b74324f81690776546713b4f04887f75 100644 --- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java +++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java @@ -25,6 +25,19 @@ public class RemoveBlockGoal extends MoveToBlockGoal { private final Mob removerMob; private int ticksSinceReachedGoal; private static final int WAIT_AFTER_BLOCK_FOUND = 20; + // Shiroha start - Lithium Optimize remove block goal + private static final java.util.function.BiPredicate IS_VALID_TARGET_ABOVE_BIPREDICATE = + RemoveBlockGoal::lithium$isValidTargetAbove; + //Split check condition in order to use maybeHas + private boolean lithium$isValidTargetBlock(net.minecraft.world.level.block.state.BlockState blockState){ + return blockState.is(this.blockToRemove); + } + + private static boolean lithium$isValidTargetAbove(ChunkAccess chunkAccess, BlockPos.MutableBlockPos mutable) { + return chunkAccess.getBlockState(mutable.move(0, 1, 0)).isAir() + && chunkAccess.getBlockState(mutable.move(0, 1, 0)).isAir(); + } + // Shiroha end - Lithium Optimize remove block goal public RemoveBlockGoal(final Block blockToRemove, final PathfinderMob mob, final double speedModifier, final int verticalSearchRange) { super(mob, speedModifier, 24, verticalSearchRange); @@ -39,7 +52,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal { } else if (this.nextStartTick > 0) { this.nextStartTick--; return false; - } else if (this.findNearestBlock()) { + } else if (((net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.LithiumMoveToBlockGoal) this).lithium$findNearestBlock(this::lithium$isValidTargetBlock, IS_VALID_TARGET_ABOVE_BIPREDICATE, false)) { // Shiroha - Lithium Optimize remove block goal this.nextStartTick = reducedTickDelay(20); return true; } else { diff --git a/net/minecraft/world/entity/ai/sensing/HoglinSpecificSensor.java b/net/minecraft/world/entity/ai/sensing/HoglinSpecificSensor.java index 7022679c84e184999b416e06d1af2cc2500cb12b..ac8f4acc2aaee8ca9f7598d842ecf29f2ad73be3 100644 --- a/net/minecraft/world/entity/ai/sensing/HoglinSpecificSensor.java +++ b/net/minecraft/world/entity/ai/sensing/HoglinSpecificSensor.java @@ -16,6 +16,14 @@ import net.minecraft.world.entity.monster.hoglin.Hoglin; import net.minecraft.world.entity.monster.piglin.Piglin; public class HoglinSpecificSensor extends Sensor { + // Shiroha start - Lithium Optimizes Hoglin repellent search. + private static final java.util.function.Predicate IS_VALID_REPELLENT_PREDICATE = + HoglinSpecificSensor::lithium$isValidRepellent; + + private static boolean lithium$isValidRepellent(net.minecraft.world.level.block.state.BlockState blockState) { + return blockState.is(BlockTags.HOGLIN_REPELLENTS); + } + // Shiroha end - Lithium Optimizes Hoglin repellent search. @Override public Set> requires() { return ImmutableSet.of( @@ -30,8 +38,8 @@ public class HoglinSpecificSensor extends Sensor { @Override protected void doTick(final ServerLevel level, final Hoglin body) { - Brain brain = body.getBrain(); - brain.setMemory(MemoryModuleType.NEAREST_REPELLENT, this.findNearestRepellent(level, body)); + Brain brain = body.getBrain();; + brain.setMemory(MemoryModuleType.NEAREST_REPELLENT, net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.CommonBlockSearchesCheckAndCache.blockPosFindClosestMatch(level, body, 8, 4, IS_VALID_REPELLENT_PREDICATE, true)); // Shiroha - Lithium Optimizes Hoglin repellent search. Optional adultPiglin = Optional.empty(); int adultPiglinCount = 0; List adultHoglins = Lists.newArrayList(); diff --git a/net/minecraft/world/entity/ai/sensing/PiglinSpecificSensor.java b/net/minecraft/world/entity/ai/sensing/PiglinSpecificSensor.java index 060a53da799a3036a343cc6f1d73e3e5ca4b7af9..7e5accce5a9183165e7604ca5e144e600b6f503d 100644 --- a/net/minecraft/world/entity/ai/sensing/PiglinSpecificSensor.java +++ b/net/minecraft/world/entity/ai/sensing/PiglinSpecificSensor.java @@ -26,6 +26,15 @@ import net.minecraft.world.level.block.CampfireBlock; import net.minecraft.world.level.block.state.BlockState; public class PiglinSpecificSensor extends Sensor { + // Shiroha start - Lithium Optimizes Piglin repellent search. + private static final java.util.function.Predicate IS_VALID_REPELLENT_PREDICATE = + PiglinSpecificSensor::lithium$isValidRepellent; + private static boolean lithium$isValidRepellent(BlockState blockState){ + final boolean isPiglinRepellent = blockState.is(BlockTags.PIGLIN_REPELLENTS); + return isPiglinRepellent && blockState.is(Blocks.SOUL_CAMPFIRE) ? + CampfireBlock.isLitCampfire(blockState) : isPiglinRepellent; + } + // Shiroha end - Lithium Optimizes Piglin repellent search. @Override public Set> requires() { return ImmutableSet.of( @@ -47,7 +56,7 @@ public class PiglinSpecificSensor extends Sensor { @Override protected void doTick(final ServerLevel level, final LivingEntity body) { Brain brain = body.getBrain(); - brain.setMemory(MemoryModuleType.NEAREST_REPELLENT, findNearestRepellent(level, body)); + brain.setMemory(MemoryModuleType.NEAREST_REPELLENT, net.caffeinemc.mods.lithium.common.ai.non_poi_block_search.CommonBlockSearchesCheckAndCache.blockPosFindClosestMatch(level, body, 8, 4, IS_VALID_REPELLENT_PREDICATE, true)); // Shiroha - Lithium Optimizes Piglin repellent search. Optional nemesis = Optional.empty(); Optional huntableHoglin = Optional.empty(); Optional babyHoglin = Optional.empty();