diff --git a/src/launch/java/baritone/launch/mixins/MixinBitArray.java b/src/launch/java/baritone/launch/mixins/MixinBitArray.java
index bece3e3bf..bf7d9e535 100644
--- a/src/launch/java/baritone/launch/mixins/MixinBitArray.java
+++ b/src/launch/java/baritone/launch/mixins/MixinBitArray.java
@@ -64,4 +64,14 @@ public abstract class MixinBitArray implements IBitArray {
return out;
}
+
+ @Override
+ public long getMaxEntryValue() {
+ return maxEntryValue;
+ }
+
+ @Override
+ public int getBitsPerEntry() {
+ return bitsPerEntry;
+ }
}
diff --git a/src/launch/java/baritone/launch/mixins/MixinBlockStateContainer.java b/src/launch/java/baritone/launch/mixins/MixinBlockStateContainer.java
index f1cc28b9f..566c3cf8b 100644
--- a/src/launch/java/baritone/launch/mixins/MixinBlockStateContainer.java
+++ b/src/launch/java/baritone/launch/mixins/MixinBlockStateContainer.java
@@ -35,6 +35,16 @@ public abstract class MixinBlockStateContainer implements IBlockStateContainer {
@Shadow
protected IBlockStatePalette palette;
+ @Override
+ public IBlockStatePalette getPalette() {
+ return palette;
+ }
+
+ @Override
+ public BitArray getStorage() {
+ return storage;
+ }
+
@Override
public IBlockState getAtPalette(int index) {
return palette.getBlockState(index);
diff --git a/src/main/java/baritone/BaritoneProvider.java b/src/main/java/baritone/BaritoneProvider.java
index d5457cf85..c49c02e10 100644
--- a/src/main/java/baritone/BaritoneProvider.java
+++ b/src/main/java/baritone/BaritoneProvider.java
@@ -22,6 +22,7 @@ import baritone.api.IBaritoneProvider;
import baritone.api.cache.IWorldScanner;
import baritone.api.command.ICommandSystem;
import baritone.api.schematic.ISchematicSystem;
+import baritone.cache.FasterWorldScanner;
import baritone.cache.WorldScanner;
import baritone.command.CommandSystem;
import baritone.command.ExampleBaritoneControl;
@@ -59,7 +60,7 @@ public final class BaritoneProvider implements IBaritoneProvider {
@Override
public IWorldScanner getWorldScanner() {
- return WorldScanner.INSTANCE;
+ return FasterWorldScanner.INSTANCE;
}
@Override
diff --git a/src/main/java/baritone/cache/FasterWorldScanner.java b/src/main/java/baritone/cache/FasterWorldScanner.java
new file mode 100644
index 000000000..f1829f9f0
--- /dev/null
+++ b/src/main/java/baritone/cache/FasterWorldScanner.java
@@ -0,0 +1,270 @@
+/*
+ * This file is part of Baritone.
+ *
+ * Baritone 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.
+ *
+ * Baritone 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 Baritone. If not, see .
+ */
+
+package baritone.cache;
+
+import baritone.api.cache.ICachedWorld;
+import baritone.api.cache.IWorldScanner;
+import baritone.api.utils.BetterBlockPos;
+import baritone.api.utils.BlockOptionalMetaLookup;
+import baritone.api.utils.IPlayerContext;
+import baritone.utils.accessor.IBitArray;
+import baritone.utils.accessor.IBlockStateContainer;
+import io.netty.buffer.Unpooled;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import net.minecraft.block.Block;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.network.PacketBuffer;
+import net.minecraft.util.BitArray;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.ChunkPos;
+import net.minecraft.world.chunk.*;
+import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import java.util.function.IntConsumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public enum FasterWorldScanner implements IWorldScanner {
+ INSTANCE;
+ @Override
+ public List scanChunkRadius(IPlayerContext ctx, BlockOptionalMetaLookup filter, int max, int yLevelThreshold, int maxSearchRadius) {
+ return new WorldScannerContext(filter, ctx).scanAroundPlayerRange(maxSearchRadius);
+ }
+
+ @Override
+ public List scanChunk(IPlayerContext ctx, BlockOptionalMetaLookup filter, ChunkPos pos, int max, int yLevelThreshold) {
+ return new WorldScannerContext(filter, ctx).scanAroundPlayerUntilCount(max);
+ }
+
+ @Override
+ public int repack(IPlayerContext ctx) {
+ return this.repack(ctx, 40);
+ }
+
+ @Override
+ public int repack(IPlayerContext ctx, int range) {
+ IChunkProvider chunkProvider = ctx.world().getChunkProvider();
+ ICachedWorld cachedWorld = ctx.worldData().getCachedWorld();
+
+ BetterBlockPos playerPos = ctx.playerFeet();
+
+ int playerChunkX = playerPos.getX() >> 4;
+ int playerChunkZ = playerPos.getZ() >> 4;
+
+ int minX = playerChunkX - range;
+ int minZ = playerChunkZ - range;
+ int maxX = playerChunkX + range;
+ int maxZ = playerChunkZ + range;
+
+ int queued = 0;
+ for (int x = minX; x <= maxX; x++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ Chunk chunk = chunkProvider.getLoadedChunk(x, z);
+
+ if (chunk != null && !chunk.isEmpty()) {
+ queued++;
+ cachedWorld.queueForPacking(chunk);
+ }
+ }
+ }
+
+ return queued;
+ }
+
+ // for porting, see {@link https://github.com/JsMacros/JsMacros/blob/backport-1.12.2/common/src/main/java/xyz/wagyourtail/jsmacros/client/api/classes/worldscanner/WorldScanner.java}
+ public static class WorldScannerContext {
+ private final BlockOptionalMetaLookup filter;
+ private final IPlayerContext ctx;
+ private final Map cachedFilter = new ConcurrentHashMap<>();
+
+ public WorldScannerContext(BlockOptionalMetaLookup filter, IPlayerContext ctx) {
+ this.filter = filter;
+ this.ctx = ctx;
+ }
+
+ public List getChunkRange(int centerX, int centerZ, int chunkrange) {
+ List chunks = new ArrayList<>();
+ for (int x = centerX - chunkrange; x <= centerX + chunkrange; x++) {
+ for (int z = centerZ - chunkrange; z <= centerZ + chunkrange; z++) {
+ chunks.add(new ChunkPos(x, z));
+ }
+ }
+ return chunks;
+ }
+
+ public List scanAroundPlayerRange(int range) {
+ return scanAroundPlayer(range, -1);
+ }
+
+ public List scanAroundPlayerUntilCount(int count) {
+ return scanAroundPlayer(32, count);
+ }
+
+ public List scanAroundPlayer(int range, int maxCount) {
+ assert ctx.player() != null;
+ return scanChunkRange(ctx.playerFeet().x >> 4, ctx.playerFeet().z >> 4, range, maxCount);
+ }
+
+ public List scanChunkRange(int centerX, int centerZ, int chunkRange, int maxBlocks) {
+ assert ctx.world() != null;
+ if (chunkRange < 0) {
+ throw new IllegalArgumentException("chunkRange must be >= 0");
+ }
+ return scanChunksInternal(getChunkRange(centerX, centerZ, chunkRange), maxBlocks);
+ }
+
+ private List scanChunksInternal(List chunkPositions, int maxBlocks) {
+ assert ctx.world() != null;
+ return chunkPositions.parallelStream().flatMap(this::scanChunkInternal).limit(maxBlocks <= 0 ? Long.MAX_VALUE : maxBlocks).collect(Collectors.toList());
+ }
+ private Stream scanChunkInternal(ChunkPos pos) {
+ IChunkProvider chunkProvider = ctx.world().getChunkProvider();
+ // if chunk is not loaded, return empty stream
+ if (!chunkProvider.isChunkGeneratedAt(pos.x, pos.z)) {
+ return Stream.empty();
+ }
+
+ long chunkX = (long) pos.x << 4;
+ long chunkZ = (long) pos.z << 4;
+
+ List blocks = new ArrayList<>();
+
+ streamChunkSections(chunkProvider.getLoadedChunk(pos.x, pos.z), (section, isInFilter) -> {
+ int yOffset = section.getYLocation();
+ BitArray array = (BitArray) ((IBlockStateContainer) section.getData()).getStorage();
+ forEach(array, isInFilter, place -> blocks.add(new BlockPos(
+ chunkX + ((place & 255) & 15),
+ yOffset + (place >> 8),
+ chunkZ + ((place & 255) >> 4)
+ )));
+ });
+ return blocks.stream();
+ }
+
+ private void streamChunkSections(Chunk chunk, BiConsumer consumer) {
+ for (ExtendedBlockStorage section : chunk.getBlockStorageArray()) {
+ if (section == null || section.isEmpty()) {
+ continue;
+ }
+
+ BlockStateContainer sectionContainer = section.getData();
+ //this won't work if the PaletteStorage is of the type EmptyPaletteStorage
+ if (((IBlockStateContainer) sectionContainer).getStorage() == null) {
+ continue;
+ }
+
+ boolean[] isInFilter = getIncludedFilterIndices(((IBlockStateContainer) sectionContainer).getPalette());
+ if (isInFilter.length == 0) {
+ continue;
+ }
+ consumer.accept(section, isInFilter);
+ }
+ }
+
+ private boolean getFilterResult(IBlockState state) {
+ Boolean v;
+ return (v = cachedFilter.get(state)) == null ? addCachedState(state) : v;
+ }
+
+ private boolean addCachedState(IBlockState state) {
+ boolean isInFilter = false;
+
+ if (filter != null) {
+ isInFilter = filter.has(state);
+ }
+
+ cachedFilter.put(state, isInFilter);
+ return isInFilter;
+ }
+
+ private boolean[] getIncludedFilterIndices(IBlockStatePalette palette) {
+ boolean commonBlockFound = false;
+ Int2ObjectOpenHashMap paletteMap = getPalette(palette);
+ int size = paletteMap.size();
+
+ boolean[] isInFilter = new boolean[size];
+
+ for (int i = 0; i < size; i++) {
+ IBlockState state = paletteMap.get(i);
+ if (getFilterResult(state)) {
+ isInFilter[i] = true;
+ commonBlockFound = true;
+ } else {
+ isInFilter[i] = false;
+ }
+ }
+
+ if (!commonBlockFound) {
+ return new boolean[0];
+ }
+ return isInFilter;
+ }
+
+ private static void forEach(BitArray array, boolean[] isInFilter, IntConsumer action) {
+ int counter = 0;
+ long[] storage = array.getBackingLongArray();
+ int arraySize = array.size();
+ int elementBits = ((IBitArray) array).getBitsPerEntry();
+ long maxValue = ((IBitArray) array).getMaxEntryValue();
+ int storageLength = storage.length;
+
+ if (storageLength != 0) {
+ int lastStorageIdx = 0;
+ long row = storage[0];
+ long nextRow = storageLength > 1 ? storage[1] : 0L;
+
+ for (int idx = 0; idx < arraySize; idx++) {
+ int n = idx * elementBits;
+ int storageIdx = n >> 6;
+ int p = (idx + 1) * elementBits - 1 >> 6;
+ int q = n ^ storageIdx << 6;
+ if (storageIdx != lastStorageIdx) {
+ row = nextRow;
+ nextRow = storageIdx + 1 < storageLength ? storage[storageIdx + 1] : 0L;
+ lastStorageIdx = storageIdx;
+ }
+ if (storageIdx == p) {
+ if (isInFilter[(int) (row >>> q & maxValue)]) {
+ action.accept(counter);
+ } else {
+ if (isInFilter[(int) ((row >>> q | nextRow << (64 - q)) & maxValue)]) {
+ action.accept(counter);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static Int2ObjectOpenHashMap getPalette(IBlockStatePalette palette) {
+ PacketBuffer buf = new PacketBuffer(Unpooled.buffer());
+ palette.write(buf);
+ int size = buf.readVarInt();
+ Int2ObjectOpenHashMap paletteMap = new Int2ObjectOpenHashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ paletteMap.put(i, Block.BLOCK_STATE_IDS.getByValue(buf.readVarInt()));
+ }
+ return paletteMap;
+ }
+ }
+}
diff --git a/src/main/java/baritone/command/defaults/MineCommand.java b/src/main/java/baritone/command/defaults/MineCommand.java
index 63712fe3e..1ab5c3321 100644
--- a/src/main/java/baritone/command/defaults/MineCommand.java
+++ b/src/main/java/baritone/command/defaults/MineCommand.java
@@ -17,6 +17,7 @@
package baritone.command.defaults;
+import baritone.api.BaritoneAPI;
import baritone.api.IBaritone;
import baritone.api.command.Command;
import baritone.api.command.argument.IArgConsumer;
@@ -45,7 +46,7 @@ public class MineCommand extends Command {
while (args.hasAny()) {
boms.add(args.getDatatypeFor(ForBlockOptionalMeta.INSTANCE));
}
- WorldScanner.INSTANCE.repack(ctx);
+ BaritoneAPI.getProvider().getWorldScanner().repack(ctx);
logDirect(String.format("Mining %s", boms.toString()));
baritone.getMineProcess().mine(quantity, boms.toArray(new BlockOptionalMeta[0]));
}
diff --git a/src/main/java/baritone/command/defaults/PathCommand.java b/src/main/java/baritone/command/defaults/PathCommand.java
index 182a1e5bc..24f47d625 100644
--- a/src/main/java/baritone/command/defaults/PathCommand.java
+++ b/src/main/java/baritone/command/defaults/PathCommand.java
@@ -17,6 +17,7 @@
package baritone.command.defaults;
+import baritone.api.BaritoneAPI;
import baritone.api.IBaritone;
import baritone.api.command.Command;
import baritone.api.command.argument.IArgConsumer;
@@ -38,7 +39,7 @@ public class PathCommand extends Command {
public void execute(String label, IArgConsumer args) throws CommandException {
ICustomGoalProcess customGoalProcess = baritone.getCustomGoalProcess();
args.requireMax(0);
- WorldScanner.INSTANCE.repack(ctx);
+ BaritoneAPI.getProvider().getWorldScanner().repack(ctx);
customGoalProcess.path();
logDirect("Now pathing");
}
diff --git a/src/main/java/baritone/command/defaults/RepackCommand.java b/src/main/java/baritone/command/defaults/RepackCommand.java
index cafbea524..cd054b10b 100644
--- a/src/main/java/baritone/command/defaults/RepackCommand.java
+++ b/src/main/java/baritone/command/defaults/RepackCommand.java
@@ -17,6 +17,7 @@
package baritone.command.defaults;
+import baritone.api.BaritoneAPI;
import baritone.api.IBaritone;
import baritone.api.command.Command;
import baritone.api.command.argument.IArgConsumer;
@@ -36,7 +37,7 @@ public class RepackCommand extends Command {
@Override
public void execute(String label, IArgConsumer args) throws CommandException {
args.requireMax(0);
- logDirect(String.format("Queued %d chunks for repacking", WorldScanner.INSTANCE.repack(ctx)));
+ logDirect(String.format("Queued %d chunks for repacking", BaritoneAPI.getProvider().getWorldScanner().repack(ctx)));
}
@Override
diff --git a/src/main/java/baritone/process/FarmProcess.java b/src/main/java/baritone/process/FarmProcess.java
index 0ef85f07d..73c575d22 100644
--- a/src/main/java/baritone/process/FarmProcess.java
+++ b/src/main/java/baritone/process/FarmProcess.java
@@ -18,6 +18,7 @@
package baritone.process;
import baritone.Baritone;
+import baritone.api.BaritoneAPI;
import baritone.api.pathing.goals.Goal;
import baritone.api.pathing.goals.GoalBlock;
import baritone.api.pathing.goals.GoalComposite;
@@ -189,7 +190,7 @@ public final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
}
if (Baritone.settings().mineGoalUpdateInterval.value != 0 && tickCount++ % Baritone.settings().mineGoalUpdateInterval.value == 0) {
- Baritone.getExecutor().execute(() -> locations = WorldScanner.INSTANCE.scanChunkRadius(ctx, scan, 256, 10, 10));
+ Baritone.getExecutor().execute(() -> locations = BaritoneAPI.getProvider().getWorldScanner().scanChunkRadius(ctx, scan, 256, 10, 10));
}
if (locations == null) {
return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE);
diff --git a/src/main/java/baritone/process/MineProcess.java b/src/main/java/baritone/process/MineProcess.java
index 1ec47cd92..a10a569aa 100644
--- a/src/main/java/baritone/process/MineProcess.java
+++ b/src/main/java/baritone/process/MineProcess.java
@@ -18,6 +18,7 @@
package baritone.process;
import baritone.Baritone;
+import baritone.api.BaritoneAPI;
import baritone.api.pathing.goals.*;
import baritone.api.process.IMineProcess;
import baritone.api.process.PathingCommand;
@@ -355,7 +356,7 @@ public final class MineProcess extends BaritoneProcessHelper implements IMinePro
locs = prune(ctx, locs, filter, max, blacklist, dropped);
if (!untracked.isEmpty() || (Baritone.settings().extendCacheOnThreshold.value && locs.size() < max)) {
- locs.addAll(WorldScanner.INSTANCE.scanChunkRadius(
+ locs.addAll(BaritoneAPI.getProvider().getWorldScanner().scanChunkRadius(
ctx.getBaritone().getPlayerContext(),
filter,
max,
diff --git a/src/main/java/baritone/utils/accessor/IBitArray.java b/src/main/java/baritone/utils/accessor/IBitArray.java
index baea5c1da..08f54584c 100644
--- a/src/main/java/baritone/utils/accessor/IBitArray.java
+++ b/src/main/java/baritone/utils/accessor/IBitArray.java
@@ -3,4 +3,8 @@ package baritone.utils.accessor;
public interface IBitArray {
int[] toArray();
+
+ long getMaxEntryValue();
+
+ int getBitsPerEntry();
}
diff --git a/src/main/java/baritone/utils/accessor/IBlockStateContainer.java b/src/main/java/baritone/utils/accessor/IBlockStateContainer.java
index 39572fc5d..bd280aeb1 100644
--- a/src/main/java/baritone/utils/accessor/IBlockStateContainer.java
+++ b/src/main/java/baritone/utils/accessor/IBlockStateContainer.java
@@ -1,9 +1,15 @@
package baritone.utils.accessor;
import net.minecraft.block.state.IBlockState;
+import net.minecraft.util.BitArray;
+import net.minecraft.world.chunk.IBlockStatePalette;
public interface IBlockStateContainer {
+ IBlockStatePalette getPalette();
+
+ BitArray getStorage();
+
IBlockState getAtPalette(int index);
int[] storageArray();