Lithium Optimize non poi block searching
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: NanaChiyo0721 <nanachiyo0721@163.com>
|
||||
Date: Sat, 25 Jul 2026 21:07:48 +0800
|
||||
Subject: [PATCH] Lithium optimized collections for WeightedList
|
||||
|
||||
|
||||
diff --git a/net/minecraft/util/random/WeightedList.java b/net/minecraft/util/random/WeightedList.java
|
||||
index a8481259e42aa116e29bccd1e30fdfd91f51a8cc..e6dd554742ab60c23afed69953e7eb6b762754d6 100644
|
||||
--- a/net/minecraft/util/random/WeightedList.java
|
||||
+++ b/net/minecraft/util/random/WeightedList.java
|
||||
@@ -23,7 +23,7 @@ public class WeightedList<E> { // Paper - non-final
|
||||
private final WeightedList.@Nullable Selector<E> selector;
|
||||
|
||||
protected WeightedList(final List<? extends Weighted<E>> items) { // Paper - protected
|
||||
- this.items = List.copyOf(items);
|
||||
+ this.items = items.size() > 4 ? new net.caffeinemc.mods.lithium.common.util.collections.HashedReferenceList(items) : new it.unimi.dsi.fastutil.objects.ReferenceArrayList<>(items); // Shiroha - Lithium optimized collections for WeightedList
|
||||
this.totalWeight = WeightedRandom.getTotalWeight(items, Weighted::weight);
|
||||
if (this.totalWeight == 0) {
|
||||
this.selector = null;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: NanaChiyo0721 <nanachiyo0721@163.com>
|
||||
Date: Sat, 25 Jul 2026 21:16:19 +0800
|
||||
Subject: [PATCH] Lithium Optimizes Hoglin repellent search.
|
||||
|
||||
|
||||
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<Hoglin> {
|
||||
+ // Shiroha start - Lithium Optimizes Hoglin repellent search.
|
||||
+ private static final java.util.function.Predicate<net.minecraft.world.level.block.state.BlockState> 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<MemoryModuleType<?>> requires() {
|
||||
return ImmutableSet.of(
|
||||
@@ -30,8 +38,8 @@ public class HoglinSpecificSensor extends Sensor<Hoglin> {
|
||||
|
||||
@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<Piglin> adultPiglin = Optional.empty();
|
||||
int adultPiglinCount = 0;
|
||||
List<Hoglin> adultHoglins = Lists.newArrayList();
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: NanaChiyo0721 <nanachiyo0721@163.com>
|
||||
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.
|
||||
+ * <p>
|
||||
+ * 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<net.minecraft.world.level.block.state.BlockState> requiredBlock, java.util.function.BiPredicate<net.minecraft.world.level.chunk.ChunkAccess,
|
||||
+ BlockPos.MutableBlockPos> 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<net.minecraft.world.level.chunk.ChunkAccess, BlockPos.MutableBlockPos> 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<net.minecraft.world.level.chunk.ChunkAccess, BlockPos.MutableBlockPos> 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<net.minecraft.world.level.block.state.BlockState> 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
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: NanaChiyo0721 <nanachiyo0721@163.com>
|
||||
Date: Sat, 25 Jul 2026 21:30:26 +0800
|
||||
Subject: [PATCH] Lithium Optimizes Piglin repellent search.
|
||||
|
||||
|
||||
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<LivingEntity> {
|
||||
+ // Shiroha start - Lithium Optimizes Piglin repellent search.
|
||||
+ private static final java.util.function.Predicate<BlockState> 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<MemoryModuleType<?>> requires() {
|
||||
return ImmutableSet.of(
|
||||
@@ -47,7 +56,7 @@ public class PiglinSpecificSensor extends Sensor<LivingEntity> {
|
||||
@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<Mob> nemesis = Optional.empty();
|
||||
Optional<Hoglin> huntableHoglin = Optional.empty();
|
||||
Optional<Hoglin> babyHoglin = Optional.empty();
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: NanaChiyo0721 <nanachiyo0721@163.com>
|
||||
Date: Sat, 25 Jul 2026 21:33:58 +0800
|
||||
Subject: [PATCH] Lithium Optimize remove 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<ChunkAccess, BlockPos.MutableBlockPos> 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 {
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* This file is part of Lithium
|
||||
*
|
||||
* Lithium is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Lithium is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.caffeinemc.mods.lithium.common.ai.non_poi_block_search;
|
||||
|
||||
import net.caffeinemc.mods.lithium.common.util.collections.FixedChunkAccessSectionBitBuffer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* This is intended to be used to optimize Non-POI block searches by pre-checking the ChunkSections, getting the
|
||||
* ChunkAccesses which then allows for returning early or only calling getBlockState in possible ChunkSections.
|
||||
* <p>
|
||||
* Note: Please correctly specify shouldChunkLoad based on whether the getBlockState in vanilla can chunk load or not.
|
||||
* The default in game is that it can. Setting it to true will mean that the search will assume that unloaded chunks
|
||||
* may have the target and will chunk load then search them if and when it reaches it.
|
||||
*
|
||||
* @author jcw780
|
||||
*/
|
||||
public class CheckAndCacheBlockChecker {
|
||||
private final FixedChunkAccessSectionBitBuffer chunkSections2MaybeContainsMatchingBlock;
|
||||
private final LevelReader levelReader;
|
||||
public final boolean shouldChunkLoad;
|
||||
public final Predicate<BlockState> blockStatePredicate;
|
||||
private int unloadedPossibleChunkSections = 0;
|
||||
public final int minSectionY;
|
||||
|
||||
public CheckAndCacheBlockChecker(BlockPos origin, int horizontalRangeInclusive, int verticalRangeInclusive, LevelReader levelReader,
|
||||
Predicate<BlockState> blockStatePredicate, boolean shouldChunkLoad) {
|
||||
this.chunkSections2MaybeContainsMatchingBlock = new FixedChunkAccessSectionBitBuffer(origin, horizontalRangeInclusive, verticalRangeInclusive);
|
||||
this.levelReader = levelReader;
|
||||
this.shouldChunkLoad = shouldChunkLoad;
|
||||
this.blockStatePredicate = blockStatePredicate;
|
||||
this.minSectionY = levelReader.getMinSectionY();
|
||||
}
|
||||
|
||||
public void initializeChunks() {
|
||||
this.initializeChunks(null);
|
||||
}
|
||||
|
||||
public void initializeChunks(Consumer<Long> chunkCollector) {
|
||||
final boolean nullChunkCollector = chunkCollector == null;
|
||||
for (long chunkPos : this.chunkSections2MaybeContainsMatchingBlock.getChunkPosInRange()) {
|
||||
int x = ChunkPos.getX(chunkPos);
|
||||
int z = ChunkPos.getZ(chunkPos);
|
||||
boolean chunkMaybeHas = false;
|
||||
|
||||
//Never load chunks in the first pass to avoid observably altering chunk loading behavior
|
||||
//Otherwise full region will be loaded vs partial region if the search finds the block early.
|
||||
ChunkAccess chunkAccess = levelReader.getChunk(x, z, ChunkStatus.FULL, false);
|
||||
if (chunkAccess != null) {
|
||||
this.chunkSections2MaybeContainsMatchingBlock.setChunkAccess(chunkPos, chunkAccess);
|
||||
for (int y : this.chunkSections2MaybeContainsMatchingBlock.getSectionYInRange()) {
|
||||
chunkMaybeHas = this.checkChunkSection(chunkAccess, x, y, z) || chunkMaybeHas;
|
||||
}
|
||||
} else if (this.shouldChunkLoad) {
|
||||
/* If the search may chunk load then it is possible that target blocks may be revealed when the search
|
||||
* reaches it. Since we cannot load the chunks and check now, we cannot definitively exclude subchunks
|
||||
* inside the chunk. This means that we must flag subchunks that are within build limit - otherwise air
|
||||
* anyway - for the search.
|
||||
*/
|
||||
for (int y : this.chunkSections2MaybeContainsMatchingBlock.getSectionYInRange()) {
|
||||
this.chunkSections2MaybeContainsMatchingBlock.setChunkSectionStatus(SectionPos.asLong(x, y, z),
|
||||
!levelReader.isOutsideBuildHeight(SectionPos.sectionToBlockCoord(y)));
|
||||
++this.unloadedPossibleChunkSections;
|
||||
}
|
||||
chunkMaybeHas = true;
|
||||
|
||||
}
|
||||
|
||||
if (!nullChunkCollector && chunkMaybeHas) {
|
||||
chunkCollector.accept(chunkPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getChunkSize(){
|
||||
return this.chunkSections2MaybeContainsMatchingBlock.numChunks;
|
||||
}
|
||||
|
||||
public boolean hasUnloadedPossibleChunks(){
|
||||
return this.unloadedPossibleChunkSections > 0;
|
||||
}
|
||||
|
||||
private boolean checkChunkSection(ChunkAccess chunkAccess, int chunkX, int chunkY, int chunkZ) {
|
||||
final int chunkSectionYIndex = chunkY - this.minSectionY;
|
||||
LevelChunkSection[] chunkSections = chunkAccess.getSections();
|
||||
if (chunkSectionYIndex >= 0
|
||||
&& chunkSectionYIndex < chunkSections.length
|
||||
&& chunkSections[chunkSectionYIndex].maybeHas(blockStatePredicate)) {
|
||||
this.chunkSections2MaybeContainsMatchingBlock.setChunkSectionStatus(
|
||||
SectionPos.asLong(chunkX, chunkY, chunkZ), true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean checkCachedSection(int chunkX, int chunkY, int chunkZ) {
|
||||
return this.chunkSections2MaybeContainsMatchingBlock.getChunkSectionBit(chunkX, chunkY, chunkZ);
|
||||
}
|
||||
|
||||
public ChunkAccess getCachedChunkAccess(long chunkPos) {
|
||||
return this.chunkSections2MaybeContainsMatchingBlock.getChunkAccess(chunkPos);
|
||||
}
|
||||
|
||||
public ChunkAccess getCachedChunkAccess(BlockPos blockPos) {
|
||||
return this.chunkSections2MaybeContainsMatchingBlock.getChunkAccess(blockPos);
|
||||
}
|
||||
|
||||
public boolean shouldStop(){
|
||||
return this.chunkSections2MaybeContainsMatchingBlock.hasNoTrueChunkSections();
|
||||
}
|
||||
|
||||
public boolean checkPosition(BlockPos blockPos) {
|
||||
if(!this.chunkSections2MaybeContainsMatchingBlock.getChunkSectionBit(blockPos)) return false;
|
||||
ChunkAccess chunkAccess = this.chunkSections2MaybeContainsMatchingBlock.getChunkAccess(blockPos);
|
||||
if(chunkAccess == null) {
|
||||
if (!this.shouldChunkLoad) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int chunkX = SectionPos.blockToSectionCoord(blockPos.getX());
|
||||
final int chunkY = SectionPos.blockToSectionCoord(blockPos.getY());
|
||||
final int chunkZ = SectionPos.blockToSectionCoord(blockPos.getZ());
|
||||
chunkAccess = levelReader.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true);
|
||||
//this chunkAccess cannot be null and reach here because it should throw earlier
|
||||
assert chunkAccess != null;
|
||||
this.chunkSections2MaybeContainsMatchingBlock.setChunkAccess(blockPos, chunkAccess);
|
||||
if (!checkChunkSection(chunkAccess, chunkX, chunkY, chunkZ)) {
|
||||
--this.unloadedPossibleChunkSections;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return blockStatePredicate.test(chunkAccess.getBlockState(blockPos));
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of Lithium
|
||||
*
|
||||
* Lithium is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Lithium is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.caffeinemc.mods.lithium.common.ai.non_poi_block_search;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Uses CheckAndCacheBlockChecker to improve common block searches
|
||||
*/
|
||||
public class CommonBlockSearchesCheckAndCache {
|
||||
/**
|
||||
* Optimizes BlockPos::findClosestMatch
|
||||
* [Vanilla Copy] search order and chunk-loading - even though the latter is unlikely to be observable in vanilla.
|
||||
*/
|
||||
public static Optional<BlockPos> blockPosFindClosestMatch(LevelReader levelReader, LivingEntity livingEntity,
|
||||
int horizontalRange, int verticalRange,
|
||||
Predicate<BlockState> blockStatePredicate,
|
||||
boolean shouldChunkLoad){
|
||||
BlockPos mobPos = livingEntity.blockPosition();
|
||||
CheckAndCacheBlockChecker checker = new CheckAndCacheBlockChecker(
|
||||
mobPos, horizontalRange, verticalRange, levelReader, blockStatePredicate, shouldChunkLoad);
|
||||
checker.initializeChunks();
|
||||
if(checker.shouldStop()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return BlockPos.findClosestMatch(mobPos, horizontalRange, verticalRange, checker::checkPosition);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* This file is part of Lithium
|
||||
*
|
||||
* Lithium is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Lithium is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.caffeinemc.mods.lithium.common.ai.non_poi_block_search;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public interface LithiumMoveToBlockGoal {
|
||||
boolean lithium$findNearestBlock(Predicate<BlockState> requiredBlock,
|
||||
BiPredicate<ChunkAccess, BlockPos.MutableBlockPos> lithium$isValidTarget,
|
||||
final boolean shouldChunkLoad);
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* This file is part of Lithium
|
||||
*
|
||||
* Lithium is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Lithium is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.caffeinemc.mods.lithium.common.ai.non_poi_block_search;
|
||||
|
||||
import net.caffeinemc.mods.lithium.common.util.Distances;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
|
||||
public class NonPOISearchDistances {
|
||||
public static class MoveToBlockGoalDistances {
|
||||
public static int getMinimumSortOrderOfChunk(BlockPos center, final long chunkPos) {
|
||||
return getMinimumSortOrderOfChunk(center, ChunkPos.getX(chunkPos), ChunkPos.getZ(chunkPos));
|
||||
}
|
||||
|
||||
public static int getMinimumSortOrderOfChunk(BlockPos center, final int chunkX, final int chunkZ) {
|
||||
final int dX = Distances.getClosestBlockCoordInSection(center.getX(), chunkX) - center.getX();
|
||||
final int dZ = Distances.getClosestBlockCoordInSection(center.getZ(), chunkZ) - center.getZ();
|
||||
|
||||
//This will always get the closest one due to the nature of the search
|
||||
return getVanillaSortOrderInt(getRing(dX, dZ), dX, dZ);
|
||||
}
|
||||
|
||||
public static int getRing(final int dX, final int dZ){
|
||||
return Math.max(Math.abs(dX), Math.abs(dZ));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort order function for 1 layer of MoveToBlockGoal findNearestBlock
|
||||
* This is equivalent to:
|
||||
* int withinRingX = Math.abs(dX) * 2 - (dX > 0 ? 1 : 0);
|
||||
* int withinRingZ = Math.abs(dZ) * 2 - (dZ > 0 ? 1 : 0);
|
||||
* return ring << 16 | withinRingX << 8 | withinRingZ;
|
||||
* <p>
|
||||
* This works because the search prioritizes in order of:
|
||||
* 1. The distance of y from the center - Not used
|
||||
* 2. Whether y is - or + (+ is closer) - Not used
|
||||
* 3. The square ring that the block is in (outer is further)
|
||||
* 4. The distance of x from the center
|
||||
* 5. Whether x is - or + (+ is closer)
|
||||
* 6. The distance of z from the center
|
||||
* 7. Whether z is - or + (+ is closer)
|
||||
* <p>
|
||||
* Note: The bit-packing only works for horizontal search ranges of <=128.
|
||||
* You can convert to longs if you somehow exceed that, but also seriously consider POIs instead.
|
||||
*
|
||||
* @param ring Which square ring the block is at relative to the center
|
||||
* @param dX Relative x position of the block to the center
|
||||
* @param dZ Relative z position of the block to the center
|
||||
*/
|
||||
public static int getVanillaSortOrderInt(final int ring, final int dX, final int dZ) {
|
||||
return (ring << 16 | Math.abs(dX) << 9 | Math.abs(dZ) << 1) - ((dX > 0 ? 1 : 0) << 8 | (dZ > 0 ? 1 : 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* This file is part of Lithium
|
||||
*
|
||||
* Lithium is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Lithium is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.caffeinemc.mods.lithium.common.util;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
|
||||
public class Distances {
|
||||
|
||||
public static long getMinChunkToBlockDistanceL2Sq(BlockPos origin, int chunkX, int chunkZ) {
|
||||
int chunkMinX = SectionPos.sectionToBlockCoord(chunkX);
|
||||
int chunkMinZ = SectionPos.sectionToBlockCoord(chunkZ);
|
||||
|
||||
int xDistance = origin.getX() - chunkMinX;
|
||||
if (xDistance > 0) {
|
||||
xDistance = Math.max(0, xDistance - 15);
|
||||
}
|
||||
int zDistance = origin.getZ() - chunkMinZ;
|
||||
if (zDistance > 0) {
|
||||
zDistance = Math.max(0, zDistance - 15);
|
||||
}
|
||||
|
||||
return (long) xDistance * (long) xDistance + (long) zDistance * (long) zDistance;
|
||||
}
|
||||
|
||||
public static BlockPos getClosestPosInChunk(BlockPos origin, int chunkX, int chunkZ) {
|
||||
int closestX = getClosestBlockCoordInSection(origin.getX(), chunkX);
|
||||
int closestZ = getClosestBlockCoordInSection(origin.getZ(), chunkZ);
|
||||
return new BlockPos(closestX, origin.getY(), closestZ);
|
||||
}
|
||||
|
||||
public static boolean isWithinCubeRadius(BlockPos origin, int radius, BlockPos pos) {
|
||||
return Math.abs(pos.getX() - origin.getX()) <= radius &&
|
||||
Math.abs(pos.getZ() - origin.getZ()) <= radius;
|
||||
}
|
||||
|
||||
public static boolean isWithinSphereRadius(BlockPos origin, long radiusSq, BlockPos pos) {
|
||||
return distanceSq(origin, pos) <= radiusSq;
|
||||
}
|
||||
|
||||
public static int getClosestBlockCoordInSection(int blockCoord, int sectionCoord) {
|
||||
final int minBlockInSection = SectionPos.sectionToBlockCoord(sectionCoord);
|
||||
return Math.min(Math.max(blockCoord, minBlockInSection), minBlockInSection + 15);
|
||||
}
|
||||
|
||||
public static long getMinSectionDistanceSq(BlockPos origin, int chunkX, int chunkY, int chunkZ) {
|
||||
int originX = origin.getX(), originY = origin.getY(), originZ = origin.getZ();
|
||||
long distX = getClosestBlockCoordInSection(originX, chunkX) - originX;
|
||||
long distY = getClosestBlockCoordInSection(originY, chunkY) - originY;
|
||||
long distZ = getClosestBlockCoordInSection(originZ, chunkZ) - originZ;
|
||||
|
||||
return distX * distX + distY * distY + distZ * distZ;
|
||||
}
|
||||
|
||||
public static long distanceSq(BlockPos a, BlockPos b) {
|
||||
long dx = a.getX() - b.getX();
|
||||
long dy = a.getY() - b.getY();
|
||||
long dz = a.getZ() - b.getZ();
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
public static int distanceSqInt(BlockPos a, BlockPos b) {
|
||||
int dx = a.getX() - b.getX();
|
||||
int dy = a.getY() - b.getY();
|
||||
int dz = a.getZ() - b.getZ();
|
||||
// Check overflows to avoid silent incorrect results
|
||||
return Math.addExact(Math.addExact(Math.multiplyExact(dx, dx), Math.multiplyExact(dy, dy)), Math.multiplyExact(dz, dz));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* This file is part of Lithium
|
||||
*
|
||||
* Lithium is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Lithium is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.caffeinemc.mods.lithium.common.util;
|
||||
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
|
||||
public class Pos {
|
||||
|
||||
public static class BlockCoord {
|
||||
public static int getYSize(LevelHeightAccessor view) {
|
||||
return view.getHeight();
|
||||
}
|
||||
public static int getMinY(LevelHeightAccessor view) {
|
||||
return view.getMinY();
|
||||
}
|
||||
public static int getMaxYInclusive(LevelHeightAccessor view) {
|
||||
return view.getMaxY();
|
||||
}
|
||||
public static int getMaxYExclusive(LevelHeightAccessor view) {
|
||||
return view.getMaxY() + 1;
|
||||
}
|
||||
|
||||
public static int getMaxInSectionCoord(int sectionCoord) {
|
||||
return 15 + getMinInSectionCoord(sectionCoord);
|
||||
}
|
||||
|
||||
public static int getMaxYInSectionIndex(LevelHeightAccessor view, int sectionIndex){
|
||||
return getMaxInSectionCoord(SectionYCoord.fromSectionIndex(view, sectionIndex));
|
||||
}
|
||||
|
||||
public static int getMinInSectionCoord(int sectionCoord) {
|
||||
return SectionPos.sectionToBlockCoord(sectionCoord);
|
||||
}
|
||||
|
||||
public static int getMinYInSectionIndex(LevelHeightAccessor view, int sectionIndex) {
|
||||
return getMinInSectionCoord(SectionYCoord.fromSectionIndex(view, sectionIndex));
|
||||
}
|
||||
}
|
||||
|
||||
public static class ChunkCoord {
|
||||
public static int fromBlockCoord(int blockCoord) {
|
||||
return SectionPos.blockToSectionCoord(blockCoord);
|
||||
}
|
||||
|
||||
public static int fromBlockSize(int i) {
|
||||
return i >> 4; //same method as fromBlockCoord, just be clear about coord/size semantic difference
|
||||
}
|
||||
}
|
||||
|
||||
public static class SectionYCoord {
|
||||
public static int getNumYSections(LevelHeightAccessor view) {
|
||||
return view.getSectionsCount();
|
||||
}
|
||||
public static int getMinYSection(LevelHeightAccessor view) {
|
||||
return view.getMinSectionY();
|
||||
}
|
||||
public static int getMaxYSectionInclusive(LevelHeightAccessor view) {
|
||||
return view.getMaxSectionY();
|
||||
}
|
||||
public static int getMaxYSectionExclusive(LevelHeightAccessor view) {
|
||||
return view.getMaxSectionY() + 1;
|
||||
}
|
||||
|
||||
public static int fromSectionIndex(LevelHeightAccessor view, int sectionCoord) {
|
||||
return sectionCoord + SectionYCoord.getMinYSection(view);
|
||||
}
|
||||
public static int fromBlockCoord(int blockCoord) {
|
||||
return SectionPos.blockToSectionCoord(blockCoord);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SectionYIndex {
|
||||
public static int getNumYSections(LevelHeightAccessor view) {
|
||||
return view.getSectionsCount();
|
||||
}
|
||||
public static int getMinYSectionIndex(LevelHeightAccessor view) {
|
||||
return 0;
|
||||
}
|
||||
public static int getMaxYSectionIndexInclusive(LevelHeightAccessor view) {
|
||||
return view.getSectionsCount() - 1;
|
||||
}
|
||||
public static int getMaxYSectionIndexExclusive(LevelHeightAccessor view) {
|
||||
return view.getSectionsCount();
|
||||
}
|
||||
|
||||
|
||||
public static int fromSectionCoord(LevelHeightAccessor view, int sectionCoord) {
|
||||
return sectionCoord - SectionYCoord.getMinYSection(view);
|
||||
}
|
||||
public static int fromBlockCoord(LevelHeightAccessor view, int blockCoord) {
|
||||
return fromSectionCoord(view, SectionPos.blockToSectionCoord(blockCoord));
|
||||
}
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* This file is part of Lithium
|
||||
*
|
||||
* Lithium is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Lithium is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.caffeinemc.mods.lithium.common.util.collections;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntIterable;
|
||||
import it.unimi.dsi.fastutil.ints.IntIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterable;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
|
||||
public class FixedChunkAccessSectionBitBuffer {
|
||||
public final int xMin, yMin, zMin;
|
||||
public final int xLength, yLength, zLength, numChunks, numSections;
|
||||
|
||||
public final BitSet chunkSectionBits;
|
||||
public final ArrayList<ChunkAccess> chunkAccesses;
|
||||
|
||||
public FixedChunkAccessSectionBitBuffer(int x0, int x1, int y0, int y1, int z0, int z1) {
|
||||
this.xMin = Math.min(x0, x1);
|
||||
this.yMin = Math.min(y0, y1);
|
||||
this.zMin = Math.min(z0, z1);
|
||||
|
||||
this.xLength = Math.max(x0, x1) - this.xMin + 1;
|
||||
this.yLength = Math.max(y0, y1) - this.yMin + 1;
|
||||
this.zLength = Math.max(z0, z1) - this.zMin + 1;
|
||||
|
||||
this.numChunks = xLength * zLength;
|
||||
this.numSections = yLength * xLength * zLength;
|
||||
|
||||
this.chunkSectionBits = new BitSet(numSections);
|
||||
this.chunkAccesses = new ArrayList<>(Collections.nCopies(xLength * zLength,null));
|
||||
}
|
||||
|
||||
public FixedChunkAccessSectionBitBuffer(BlockPos center, int horizontalRangeInclusive, int verticalRangeInclusive) {
|
||||
this(SectionPos.blockToSectionCoord(center.getX() - horizontalRangeInclusive),
|
||||
SectionPos.blockToSectionCoord(center.getX() + horizontalRangeInclusive),
|
||||
SectionPos.blockToSectionCoord(center.getY() - verticalRangeInclusive),
|
||||
SectionPos.blockToSectionCoord(center.getY() + verticalRangeInclusive),
|
||||
SectionPos.blockToSectionCoord(center.getZ() - horizontalRangeInclusive),
|
||||
SectionPos.blockToSectionCoord(center.getZ() + horizontalRangeInclusive)
|
||||
);
|
||||
}
|
||||
|
||||
public int getSectionIndex(int x, int y, int z) {
|
||||
int dx = x - this.xMin;
|
||||
int dy = y - this.yMin;
|
||||
int dz = z - this.zMin;
|
||||
|
||||
return (dx * this.zLength + dz) * this.yLength + dy;
|
||||
}
|
||||
|
||||
public int getSectionIndex(long sectionPos) {
|
||||
return this.getSectionIndex(
|
||||
SectionPos.x(sectionPos),
|
||||
SectionPos.y(sectionPos),
|
||||
SectionPos.z(sectionPos)
|
||||
);
|
||||
}
|
||||
|
||||
public boolean getChunkSectionBit(BlockPos blockPos) {
|
||||
return this.getChunkSectionBit(SectionPos.blockToSectionCoord(blockPos.getX()), SectionPos.blockToSectionCoord(blockPos.getY()), SectionPos.blockToSectionCoord(blockPos.getZ()));
|
||||
}
|
||||
|
||||
public boolean getChunkSectionBit(int chunkX, int chunkY, int chunkZ) {
|
||||
return this.chunkSectionBits.get(this.getSectionIndex(chunkX, chunkY, chunkZ));
|
||||
}
|
||||
|
||||
public void setChunkSectionStatus(long sectionPos, boolean value) {
|
||||
this.chunkSectionBits.set(this.getSectionIndex(sectionPos), value);
|
||||
}
|
||||
|
||||
public int getChunkIndex(int x, int z) {
|
||||
int dx = x - this.xMin;
|
||||
int dz = z - this.zMin;
|
||||
|
||||
return dx * this.zLength + dz;
|
||||
}
|
||||
|
||||
public int getChunkIndex(long chunkPos) {
|
||||
return this.getChunkIndex(ChunkPos.getX(chunkPos), ChunkPos.getZ(chunkPos));
|
||||
}
|
||||
|
||||
public ChunkAccess getChunkAccess(long chunkPos){
|
||||
return this.chunkAccesses.get(this.getChunkIndex(chunkPos));
|
||||
}
|
||||
|
||||
public ChunkAccess getChunkAccess(BlockPos blockPos){
|
||||
return this.getChunkAccess(ChunkPos.pack(blockPos));
|
||||
}
|
||||
|
||||
public void setChunkAccess(long chunkPos, ChunkAccess chunkAccess) {
|
||||
this.chunkAccesses.set(this.getChunkIndex(chunkPos), chunkAccess);
|
||||
}
|
||||
|
||||
public void setChunkAccess(BlockPos blockPos, ChunkAccess chunkAccess) {
|
||||
this.setChunkAccess(ChunkPos.pack(blockPos), chunkAccess);
|
||||
}
|
||||
|
||||
public boolean hasNoTrueChunkSections(){
|
||||
return this.chunkSectionBits.nextSetBit(0) == -1;
|
||||
}
|
||||
|
||||
public LongIterable getChunkPosInRange() {
|
||||
return new LongIterable() {
|
||||
@Override
|
||||
public @NotNull LongIterator iterator(){
|
||||
return getChunkPosInRangeIterator();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public LongIterator getChunkPosInRangeIterator() {
|
||||
final int xMin = this.xMin;
|
||||
final int xMax = this.xMin + this.xLength - 1;
|
||||
final int zMin = this.zMin;
|
||||
final int zMax = this.zMin + this.zLength - 1;
|
||||
return new LongIterator() {
|
||||
int x = xMin;
|
||||
int z = zMin;
|
||||
|
||||
@Override
|
||||
public long nextLong () {
|
||||
long result = ChunkPos.pack(x, z);
|
||||
if (z < zMax) {
|
||||
z++;
|
||||
} else {
|
||||
z = zMin;
|
||||
x++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext(){
|
||||
return x <= xMax;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public IntIterable getSectionYInRange() {
|
||||
return new IntIterable() {
|
||||
@Override
|
||||
public @NotNull IntIterator iterator(){
|
||||
return getSectionYInRangeIterator();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public IntIterator getSectionYInRangeIterator() {
|
||||
final int yMin = this.yMin;
|
||||
final int yLimit = yMin + this.yLength;
|
||||
return new IntIterator() {
|
||||
int y = yMin;
|
||||
|
||||
@Override
|
||||
public int nextInt(){
|
||||
return y++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext(){
|
||||
return y < yLimit;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* This file is part of Lithium
|
||||
*
|
||||
* Lithium is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Lithium is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Lithium. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.caffeinemc.mods.lithium.common.util.collections;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Wraps a {@link List} with a hash table which provides O(1) lookups for {@link Collection#contains(Object)}. The type
|
||||
* contained by this list must use reference-equality semantics.
|
||||
*/
|
||||
@SuppressWarnings("SuspiciousMethodCalls")
|
||||
public class HashedReferenceList<T> implements List<T> {
|
||||
private final ReferenceArrayList<T> list;
|
||||
private final Reference2IntOpenHashMap<T> counter;
|
||||
|
||||
public HashedReferenceList(Collection<T> list) {
|
||||
this.list = new ReferenceArrayList<>();
|
||||
this.list.addAll(list);
|
||||
|
||||
this.counter = new Reference2IntOpenHashMap<>();
|
||||
this.counter.defaultReturnValue(0);
|
||||
|
||||
for (T obj : this.list) {
|
||||
this.counter.addTo(obj, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return this.counter.containsKey(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return this.listIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return this.list.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T1> T1[] toArray(T1 @NotNull [] a) {
|
||||
return this.list.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T t) {
|
||||
this.trackReferenceAdded(t);
|
||||
|
||||
return this.list.add(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
this.trackReferenceRemoved(o);
|
||||
|
||||
return this.list.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
for (Object obj : c) {
|
||||
if (!this.counter.containsKey(obj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends T> c) {
|
||||
for (T obj : c) {
|
||||
this.trackReferenceAdded(obj);
|
||||
}
|
||||
|
||||
return this.list.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends T> c) {
|
||||
for (T obj : c) {
|
||||
this.trackReferenceAdded(obj);
|
||||
}
|
||||
|
||||
return this.list.addAll(index, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(@NotNull Collection<?> c) {
|
||||
if (this.size() >= 2 && c.size() > 4 && c instanceof List) {
|
||||
//HashReferenceList uses reference equality, so using ReferenceOpenHashSet is fine
|
||||
c = new ReferenceOpenHashSet<>(c);
|
||||
}
|
||||
this.counter.keySet().removeAll(c);
|
||||
return this.list.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(@NotNull Collection<?> c) {
|
||||
this.counter.keySet().retainAll(c);
|
||||
return this.list.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
this.counter.clear();
|
||||
this.list.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int index) {
|
||||
return this.list.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T set(int index, T element) {
|
||||
T prev = this.list.set(index, element);
|
||||
|
||||
if (prev != element) {
|
||||
if (prev != null) {
|
||||
this.trackReferenceRemoved(prev);
|
||||
}
|
||||
|
||||
this.trackReferenceAdded(element);
|
||||
}
|
||||
|
||||
return prev;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, T element) {
|
||||
this.trackReferenceAdded(element);
|
||||
|
||||
this.list.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(int index) {
|
||||
T prev = this.list.remove(index);
|
||||
|
||||
if (prev != null) {
|
||||
this.trackReferenceRemoved(prev);
|
||||
}
|
||||
|
||||
return prev;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return this.list.indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
return this.list.lastIndexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<T> listIterator() {
|
||||
return this.listIterator(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<T> listIterator(int index) {
|
||||
return new ListIterator<>() {
|
||||
private final ListIterator<T> inner = HashedReferenceList.this.list.listIterator(index);
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.inner.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return this.inner.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrevious() {
|
||||
return this.inner.hasPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T previous() {
|
||||
return this.inner.previous();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextIndex() {
|
||||
return this.inner.nextIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int previousIndex() {
|
||||
return this.inner.previousIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
int last = this.previousIndex();
|
||||
|
||||
if (last == -1) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
T prev = HashedReferenceList.this.get(last);
|
||||
|
||||
if (prev != null) {
|
||||
HashedReferenceList.this.trackReferenceRemoved(prev);
|
||||
}
|
||||
|
||||
this.inner.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(T t) {
|
||||
int last = this.previousIndex();
|
||||
|
||||
if (last == -1) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
T prev = HashedReferenceList.this.get(last);
|
||||
|
||||
if (prev != t) {
|
||||
if (prev != null) {
|
||||
HashedReferenceList.this.trackReferenceRemoved(prev);
|
||||
}
|
||||
|
||||
HashedReferenceList.this.trackReferenceAdded(t);
|
||||
}
|
||||
|
||||
this.inner.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(T t) {
|
||||
HashedReferenceList.this.trackReferenceAdded(t);
|
||||
|
||||
this.inner.add(t);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> subList(int fromIndex, int toIndex) {
|
||||
return this.list.subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
private void trackReferenceAdded(T t) {
|
||||
this.counter.addTo(t, 1);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void trackReferenceRemoved(Object o) {
|
||||
if (this.counter.addTo((T) o, -1) <= 1) {
|
||||
this.counter.removeInt(o);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user