getEffectiveRotation() {
+ if (Baritone.settings().freeLook.value) {
+ return Optional.ofNullable(this.serverRotation);
+ }
+ // If freeLook isn't on, just defer to the player's actual rotations
+ return Optional.empty();
+ }
+
@Override
public void onPlayerRotationMove(RotationMoveEvent event) {
if (this.target != null) {
+ final Rotation actual = this.processor.peekRotation(this.target.rotation);
+ event.setYaw(actual.getYaw());
+ event.setPitch(actual.getPitch());
+ }
+ }
- event.setYaw(this.target.getYaw());
+ private static final class AimProcessor extends AbstractAimProcessor {
- // If we have antiCheatCompatibility on, we're going to use the target value later in onPlayerUpdate()
- // Also the type has to be MOTION_UPDATE because that is called after JUMP
- if (!Baritone.settings().antiCheatCompatibility.value && event.getType() == RotationMoveEvent.Type.MOTION_UPDATE && !this.force) {
- this.target = null;
+ public AimProcessor(final IPlayerContext ctx) {
+ super(ctx);
+ }
+
+ @Override
+ protected Rotation getPrevRotation() {
+ // Implementation will use LookBehavior.serverRotation
+ return ctx.playerRotations();
+ }
+ }
+
+ private static abstract class AbstractAimProcessor implements ITickableAimProcessor {
+
+ protected final IPlayerContext ctx;
+ private final ForkableRandom rand;
+ private double randomYawOffset;
+ private double randomPitchOffset;
+
+ public AbstractAimProcessor(IPlayerContext ctx) {
+ this.ctx = ctx;
+ this.rand = new ForkableRandom();
+ }
+
+ private AbstractAimProcessor(final AbstractAimProcessor source) {
+ this.ctx = source.ctx;
+ this.rand = source.rand.fork();
+ this.randomYawOffset = source.randomYawOffset;
+ this.randomPitchOffset = source.randomPitchOffset;
+ }
+
+ @Override
+ public final Rotation peekRotation(final Rotation rotation) {
+ final Rotation prev = this.getPrevRotation();
+
+ float desiredYaw = rotation.getYaw();
+ float desiredPitch = rotation.getPitch();
+
+ // In other words, the target doesn't care about the pitch, so it used playerRotations().getPitch()
+ // and it's safe to adjust it to a normal level
+ if (desiredPitch == prev.getPitch()) {
+ desiredPitch = nudgeToLevel(desiredPitch);
+ }
+
+ desiredYaw += this.randomYawOffset;
+ desiredPitch += this.randomPitchOffset;
+
+ return new Rotation(
+ this.calculateMouseMove(prev.getYaw(), desiredYaw),
+ this.calculateMouseMove(prev.getPitch(), desiredPitch)
+ ).clamp();
+ }
+
+ @Override
+ public final void tick() {
+ // randomLooking
+ this.randomYawOffset = (this.rand.nextDouble() - 0.5) * Baritone.settings().randomLooking.value;
+ this.randomPitchOffset = (this.rand.nextDouble() - 0.5) * Baritone.settings().randomLooking.value;
+
+ // randomLooking113
+ double random = this.rand.nextDouble() - 0.5;
+ if (Math.abs(random) < 0.1) {
+ random *= 4;
+ }
+ this.randomYawOffset += random * Baritone.settings().randomLooking113.value;
+ }
+
+ @Override
+ public final void advance(int ticks) {
+ for (int i = 0; i < ticks; i++) {
+ this.tick();
+ }
+ }
+
+ @Override
+ public Rotation nextRotation(final Rotation rotation) {
+ final Rotation actual = this.peekRotation(rotation);
+ this.tick();
+ return actual;
+ }
+
+ @Override
+ public final ITickableAimProcessor fork() {
+ return new AbstractAimProcessor(this) {
+
+ private Rotation prev = AbstractAimProcessor.this.getPrevRotation();
+
+ @Override
+ public Rotation nextRotation(final Rotation rotation) {
+ return (this.prev = super.nextRotation(rotation));
+ }
+
+ @Override
+ protected Rotation getPrevRotation() {
+ return this.prev;
+ }
+ };
+ }
+
+ protected abstract Rotation getPrevRotation();
+
+ /**
+ * Nudges the player's pitch to a regular level. (Between {@code -20} and {@code 10}, increments are by {@code 1})
+ */
+ private float nudgeToLevel(float pitch) {
+ if (pitch < -20) {
+ return pitch + 1;
+ } else if (pitch > 10) {
+ return pitch - 1;
+ }
+ return pitch;
+ }
+
+ private float calculateMouseMove(float current, float target) {
+ final float delta = target - current;
+ final double deltaPx = angleToMouse(delta); // yes, even the mouse movements use double
+ return current + mouseToAngle(deltaPx);
+ }
+
+ private double angleToMouse(float angleDelta) {
+ final float minAngleChange = mouseToAngle(1);
+ return Math.round(angleDelta / minAngleChange);
+ }
+
+ private float mouseToAngle(double mouseDelta) {
+ // casting float literals to double gets us the precise values used by mc
+ final double f = ctx.minecraft().options.sensitivity().get() * (double) 0.6f + (double) 0.2f;
+ return (float) (mouseDelta * f * f * f * 8.0d) * 0.15f; // yes, one double and one float scaling factor
+ }
+ }
+
+ private static class Target {
+
+ public final Rotation rotation;
+ public final Mode mode;
+
+ public Target(Rotation rotation, Mode mode) {
+ this.rotation = rotation;
+ this.mode = mode;
+ }
+
+ enum Mode {
+ /**
+ * Rotation will be set client-side and is visual to the player
+ */
+ CLIENT,
+
+ /**
+ * Rotation will be set server-side and is silent to the player
+ */
+ SERVER,
+
+ /**
+ * Rotation will remain unaffected on both the client and server
+ */
+ NONE;
+
+ static Mode resolve(IPlayerContext ctx, boolean blockInteract) {
+ final Settings settings = Baritone.settings();
+ final boolean antiCheat = settings.antiCheatCompatibility.value;
+ final boolean blockFreeLook = settings.blockFreeLook.value;
+
+ if (ctx.player().isFallFlying()) {
+ // always need to set angles while flying
+ return settings.elytraFreeLook.value ? SERVER : CLIENT;
+ } else if (settings.freeLook.value) {
+ // Regardless of if antiCheatCompatibility is enabled, if a blockInteract is requested then the player
+ // rotation needs to be set somehow, otherwise Baritone will halt since objectMouseOver() will just be
+ // whatever the player is mousing over visually. Let's just settle for setting it silently.
+ if (blockInteract) {
+ return blockFreeLook ? SERVER : CLIENT;
+ }
+ return antiCheat ? SERVER : NONE;
+ }
+
+ // all freeLook settings are disabled so set the angles
+ return CLIENT;
}
}
}
-
- /**
- * Nudges the player's pitch to a regular level. (Between {@code -20} and {@code 10}, increments are by {@code 1})
- */
- private void nudgeToLevel() {
- if (ctx.player().getXRot() < -20) {
- ctx.player().setXRot(ctx.player().getXRot() + 1);
- } else if (ctx.player().getXRot() > 10) {
- ctx.player().setXRot(ctx.player().getXRot() - 1);
- }
- }
}
diff --git a/src/main/java/baritone/behavior/PathingBehavior.java b/src/main/java/baritone/behavior/PathingBehavior.java
index 9a8c4d1b5..b7c035598 100644
--- a/src/main/java/baritone/behavior/PathingBehavior.java
+++ b/src/main/java/baritone/behavior/PathingBehavior.java
@@ -33,6 +33,7 @@ import baritone.pathing.calc.AbstractNodeCostSearch;
import baritone.pathing.movement.CalculationContext;
import baritone.pathing.movement.MovementHelper;
import baritone.pathing.path.PathExecutor;
+import baritone.process.ElytraProcess;
import baritone.utils.PathRenderer;
import baritone.utils.PathingCommandContext;
import baritone.utils.pathing.Favoring;
@@ -238,11 +239,11 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior,
if (current != null) {
switch (event.getState()) {
case PRE:
- lastAutoJump = mc.options.autoJump().get();
- mc.options.autoJump().set(false);
+ lastAutoJump = ctx.minecraft().options.autoJump().get();
+ ctx.minecraft().options.autoJump().set(false);
break;
case POST:
- mc.options.autoJump().set(lastAutoJump);
+ ctx.minecraft().options.autoJump().set(lastAutoJump);
break;
default:
break;
@@ -308,7 +309,10 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior,
}
public boolean isSafeToCancel() {
- return current == null || safeToCancel;
+ if (current == null) {
+ return !baritone.getElytraProcess().isActive() || baritone.getElytraProcess().isSafeToCancel();
+ }
+ return safeToCancel;
}
public void requestPause() {
@@ -351,7 +355,7 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior,
}
// just cancel the current path
- private void secretInternalSegmentCancel() {
+ public void secretInternalSegmentCancel() {
queuePathEvent(PathEvent.CANCELED);
synchronized (pathPlanLock) {
getInProgress().ifPresent(AbstractNodeCostSearch::cancel);
diff --git a/src/main/java/baritone/behavior/look/ForkableRandom.java b/src/main/java/baritone/behavior/look/ForkableRandom.java
new file mode 100644
index 000000000..5f5f942d2
--- /dev/null
+++ b/src/main/java/baritone/behavior/look/ForkableRandom.java
@@ -0,0 +1,85 @@
+/*
+ * 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.behavior.look;
+
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.LongSupplier;
+
+/**
+ * Implementation of Xoroshiro256++
+ *
+ * Extended to produce random double-precision floating point numbers, and allow copies to be spawned via {@link #fork},
+ * which share the same internal state as the source object.
+ *
+ * @author Brady
+ */
+public final class ForkableRandom {
+
+ private static final double DOUBLE_UNIT = 0x1.0p-53;
+
+ private final long[] s;
+
+ public ForkableRandom() {
+ this(System.nanoTime() ^ System.currentTimeMillis());
+ }
+
+ public ForkableRandom(long seedIn) {
+ final AtomicLong seed = new AtomicLong(seedIn);
+ final LongSupplier splitmix64 = () -> {
+ long z = seed.addAndGet(0x9e3779b97f4a7c15L);
+ z = (z ^ (z >>> 30)) * 0xbf58476d1ce4e5b9L;
+ z = (z ^ (z >>> 27)) * 0x94d049bb133111ebL;
+ return z ^ (z >>> 31);
+ };
+ this.s = new long[] {
+ splitmix64.getAsLong(),
+ splitmix64.getAsLong(),
+ splitmix64.getAsLong(),
+ splitmix64.getAsLong()
+ };
+ }
+
+ private ForkableRandom(long[] s) {
+ this.s = s;
+ }
+
+ public double nextDouble() {
+ return (this.next() >>> 11) * DOUBLE_UNIT;
+ }
+
+ public long next() {
+ final long result = rotl(this.s[0] + this.s[3], 23) + this.s[0];
+ final long t = this.s[1] << 17;
+ this.s[2] ^= this.s[0];
+ this.s[3] ^= this.s[1];
+ this.s[1] ^= this.s[2];
+ this.s[0] ^= this.s[3];
+ this.s[2] ^= t;
+ this.s[3] = rotl(this.s[3], 45);
+ return result;
+ }
+
+ public ForkableRandom fork() {
+ return new ForkableRandom(Arrays.copyOf(this.s, 4));
+ }
+
+ private static long rotl(long x, int k) {
+ return (x << k) | (x >>> (64 - k));
+ }
+}
diff --git a/src/main/java/baritone/cache/CachedChunk.java b/src/main/java/baritone/cache/CachedChunk.java
index bbc713af7..2659c4feb 100644
--- a/src/main/java/baritone/cache/CachedChunk.java
+++ b/src/main/java/baritone/cache/CachedChunk.java
@@ -21,16 +21,17 @@ import baritone.api.utils.BlockUtils;
import baritone.utils.pathing.PathingBlockType;
import com.google.common.collect.ImmutableSet;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.List;
-import java.util.Map;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.dimension.DimensionType;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Map;
+
/**
* @author Brady
* @since 8/3/2018
@@ -217,7 +218,7 @@ public final class CachedChunk {
// nether roof is always unbreakable
return Blocks.BEDROCK.defaultBlockState();
}
- if (y < 5 && dimension.natural()) {
+ if (y < -59 && dimension.natural()) {
// solid blocks below 5 are commonly bedrock
// however, returning bedrock always would be a little yikes
// discourage paths that include breaking blocks below 5 a little more heavily just so that it takes paths breaking what's known to be stone (at 5 or above) instead of what could maybe be bedrock (below 5)
diff --git a/src/main/java/baritone/cache/CachedRegion.java b/src/main/java/baritone/cache/CachedRegion.java
index 9bcebcaad..8aa992d79 100644
--- a/src/main/java/baritone/cache/CachedRegion.java
+++ b/src/main/java/baritone/cache/CachedRegion.java
@@ -20,6 +20,10 @@ package baritone.cache;
import baritone.Baritone;
import baritone.api.cache.ICachedRegion;
import baritone.api.utils.BlockUtils;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.dimension.DimensionType;
+
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -27,9 +31,6 @@ import java.nio.file.Paths;
import java.util.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
-import net.minecraft.core.BlockPos;
-import net.minecraft.world.level.block.state.BlockState;
-import net.minecraft.world.level.dimension.DimensionType;
/**
* @author Brady
diff --git a/src/main/java/baritone/cache/CachedWorld.java b/src/main/java/baritone/cache/CachedWorld.java
index 4e323097c..e456382e6 100644
--- a/src/main/java/baritone/cache/CachedWorld.java
+++ b/src/main/java/baritone/cache/CachedWorld.java
@@ -23,22 +23,21 @@ import baritone.api.IBaritone;
import baritone.api.cache.ICachedWorld;
import baritone.api.cache.IWorldData;
import baritone.api.utils.Helper;
+import com.google.common.cache.CacheBuilder;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.dimension.DimensionType;
+
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
-import net.minecraft.core.BlockPos;
-import net.minecraft.resources.ResourceKey;
-import net.minecraft.world.level.ChunkPos;
-import net.minecraft.world.level.Level;
-import net.minecraft.world.level.chunk.LevelChunk;
-import net.minecraft.world.level.dimension.DimensionType;
/**
* @author Brady
@@ -71,7 +70,7 @@ public final class CachedWorld implements ICachedWorld, Helper {
* All chunk positions pending packing. This map will be updated in-place if a new update to the chunk occurs
* while waiting in the queue for the packer thread to get to it.
*/
- private final Map toPackMap = new ConcurrentHashMap<>();
+ private final Map toPackMap = CacheBuilder.newBuilder().softValues().build().asMap();
private final DimensionType dimension;
@@ -309,6 +308,9 @@ public final class CachedWorld implements ICachedWorld, Helper {
try {
ChunkPos pos = toPackQueue.take();
LevelChunk chunk = toPackMap.remove(pos);
+ if (toPackQueue.size() > Baritone.settings().chunkPackerQueueMaxSize.value) {
+ continue;
+ }
CachedChunk cached = ChunkPacker.pack(chunk);
CachedWorld.this.updateCachedChunk(cached);
//System.out.println("Processed chunk at " + chunk.x + "," + chunk.z);
diff --git a/src/main/java/baritone/cache/ChunkPacker.java b/src/main/java/baritone/cache/ChunkPacker.java
index d544ac74d..5d619543d 100644
--- a/src/main/java/baritone/cache/ChunkPacker.java
+++ b/src/main/java/baritone/cache/ChunkPacker.java
@@ -34,6 +34,7 @@ import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.dimension.BuiltinDimensionTypes;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.phys.Vec3;
+
import java.util.*;
import static baritone.utils.BlockStateInterface.getFromChunk;
diff --git a/src/main/java/baritone/cache/FasterWorldScanner.java b/src/main/java/baritone/cache/FasterWorldScanner.java
new file mode 100644
index 000000000..5ea6dcfbe
--- /dev/null
+++ b/src/main/java/baritone/cache/FasterWorldScanner.java
@@ -0,0 +1,257 @@
+/*
+ * 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.IPalettedContainer;
+import io.netty.buffer.Unpooled;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.IdMapper;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.util.BitStorage;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.*;
+
+import java.util.ArrayList;
+import java.util.List;
+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) {
+ assert ctx.world() != null;
+ if (maxSearchRadius < 0) {
+ throw new IllegalArgumentException("chunkRange must be >= 0");
+ }
+ return scanChunksInternal(ctx, filter, getChunkRange(ctx.playerFeet().x >> 4, ctx.playerFeet().z >> 4, maxSearchRadius), max);
+ }
+
+ @Override
+ public List scanChunk(IPlayerContext ctx, BlockOptionalMetaLookup filter, ChunkPos pos, int max, int yLevelThreshold) {
+ Stream stream = scanChunkInternal(ctx, filter, pos);
+ if (max >= 0) {
+ stream = stream.limit(max);
+ }
+ return stream.collect(Collectors.toList());
+ }
+
+ @Override
+ public int repack(IPlayerContext ctx) {
+ return this.repack(ctx, 40);
+ }
+
+ @Override
+ public int repack(IPlayerContext ctx, int range) {
+ ChunkSource chunkProvider = ctx.world().getChunkSource();
+ 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++) {
+ LevelChunk chunk = chunkProvider.getChunk(x, z, false);
+
+ if (chunk != null && !chunk.isEmpty()) {
+ queued++;
+ cachedWorld.queueForPacking(chunk);
+ }
+ }
+ }
+
+ return queued;
+ }
+
+ // ordered in a way that the closest blocks are generally first
+ public static List getChunkRange(int centerX, int centerZ, int chunkRadius) {
+ List chunks = new ArrayList<>();
+ // spiral out
+ chunks.add(new ChunkPos(centerX, centerZ));
+ for (int i = 1; i < chunkRadius; i++) {
+ for (int j = 0; j <= i; j++) {
+ chunks.add(new ChunkPos(centerX - j, centerZ - i));
+ if (j != 0) {
+ chunks.add(new ChunkPos(centerX + j, centerZ - i));
+ chunks.add(new ChunkPos(centerX - j, centerZ + i));
+ }
+ chunks.add(new ChunkPos(centerX + j, centerZ + i));
+ if (j != i) {
+ chunks.add(new ChunkPos(centerX - i, centerZ - j));
+ chunks.add(new ChunkPos(centerX + i, centerZ - j));
+ if (j != 0) {
+ chunks.add(new ChunkPos(centerX - i, centerZ + j));
+ chunks.add(new ChunkPos(centerX + i, centerZ + j));
+ }
+ }
+ }
+ }
+ return chunks;
+ }
+
+ private List scanChunksInternal(IPlayerContext ctx, BlockOptionalMetaLookup lookup, List chunkPositions, int maxBlocks) {
+ assert ctx.world() != null;
+ try {
+ // p -> scanChunkInternal(ctx, lookup, p)
+ Stream posStream = chunkPositions.parallelStream().flatMap(p -> scanChunkInternal(ctx, lookup, p));
+ if (maxBlocks >= 0) {
+ // WARNING: this can be expensive if maxBlocks is large...
+ // see limit's javadoc
+ posStream = posStream.limit(maxBlocks);
+ }
+ return posStream.collect(Collectors.toList());
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+
+ private Stream scanChunkInternal(IPlayerContext ctx, BlockOptionalMetaLookup lookup, ChunkPos pos) {
+ ChunkSource chunkProvider = ctx.world().getChunkSource();
+ // if chunk is not loaded, return empty stream
+ if (!chunkProvider.hasChunk(pos.x, pos.z)) {
+ return Stream.empty();
+ }
+
+ long chunkX = (long) pos.x << 4;
+ long chunkZ = (long) pos.z << 4;
+
+ int playerSectionY = ctx.playerFeet().y >> 4;
+
+ return collectChunkSections(lookup, chunkProvider.getChunk(pos.x, pos.z, false), chunkX, chunkZ, playerSectionY).stream();
+ }
+
+
+ private List collectChunkSections(BlockOptionalMetaLookup lookup, LevelChunk chunk, long chunkX, long chunkZ, int playerSection) {
+ // iterate over sections relative to player
+ List blocks = new ArrayList<>();
+ LevelChunkSection[] sections = chunk.getSections();
+ int l = sections.length;
+ int i = playerSection - 1;
+ int j = playerSection;
+ for (; i >= 0 || j < l; ++j, --i) {
+ if (j < l) {
+ visitSection(lookup, sections[j], blocks, chunkX, chunkZ, j);
+ }
+ if (i >= 0) {
+ visitSection(lookup, sections[i], blocks, chunkX, chunkZ, i);
+ }
+ }
+ return blocks;
+ }
+
+ private void visitSection(BlockOptionalMetaLookup lookup, LevelChunkSection section, List blocks, long chunkX, long chunkZ, int sectionIdx) {
+ if (section == null || section.hasOnlyAir()) {
+ return;
+ }
+
+ PalettedContainer sectionContainer = section.getStates();
+ //this won't work if the PaletteStorage is of the type EmptyPaletteStorage
+ if (((IPalettedContainer) sectionContainer).getStorage() == null) {
+ return;
+ }
+
+ boolean[] isInFilter = getIncludedFilterIndices(lookup, ((IPalettedContainer) sectionContainer).getPalette());
+ if (isInFilter.length == 0) {
+ return;
+ }
+
+ BitStorage array = ((IPalettedContainer) section.getStates()).getStorage();
+ long[] longArray = array.getRaw();
+ int arraySize = array.getSize();
+ int bitsPerEntry = array.getBits();
+ long maxEntryValue = (1L << bitsPerEntry) - 1L;
+
+
+ int yOffset = sectionIdx << 4;
+
+ for (int i = 0, idx = 0; i < longArray.length && idx < arraySize; ++i) {
+ long l = longArray[i];
+ for (int offset = 0; offset <= (64 - bitsPerEntry) && idx < arraySize; offset += bitsPerEntry, ++idx) {
+ int value = (int) ((l >> offset) & maxEntryValue);
+ if (isInFilter[value]) {
+ //noinspection DuplicateExpressions
+ blocks.add(new BlockPos(
+ (int) chunkX + ((idx & 255) & 15),
+ yOffset + (idx >> 8),
+ (int) chunkZ + ((idx & 255) >> 4)
+ ));
+ }
+ }
+ }
+ }
+
+ private boolean[] getIncludedFilterIndices(BlockOptionalMetaLookup lookup, Palette palette) {
+ boolean commonBlockFound = false;
+ IdMapper paletteMap = getPalette(palette);
+ int size = paletteMap.size();
+
+ boolean[] isInFilter = new boolean[size];
+
+ for (int i = 0; i < size; i++) {
+ BlockState state = paletteMap.byId(i);
+ if (lookup.has(state)) {
+ isInFilter[i] = true;
+ commonBlockFound = true;
+ } else {
+ isInFilter[i] = false;
+ }
+ }
+
+ if (!commonBlockFound) {
+ return new boolean[0];
+ }
+ return isInFilter;
+ }
+
+ /**
+ * cheats to get the actual map of id -> blockstate from the various palette implementations
+ */
+ private static IdMapper getPalette(Palette palette) {
+ if (palette instanceof GlobalPalette) {
+ return Block.BLOCK_STATE_REGISTRY;
+ } else {
+ FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
+ palette.write(buf);
+ int size = buf.readVarInt();
+ IdMapper states = new IdMapper<>();
+ for (int i = 0; i < size; i++) {
+ BlockState state = Block.BLOCK_STATE_REGISTRY.byId(buf.readVarInt());
+ assert state != null;
+ states.addMapping(state, i);
+ }
+ return states;
+ }
+ }
+}
diff --git a/src/main/java/baritone/cache/WorldProvider.java b/src/main/java/baritone/cache/WorldProvider.java
index f3d96a0db..035b50b98 100644
--- a/src/main/java/baritone/cache/WorldProvider.java
+++ b/src/main/java/baritone/cache/WorldProvider.java
@@ -19,104 +19,84 @@ package baritone.cache;
import baritone.Baritone;
import baritone.api.cache.IWorldProvider;
-import baritone.api.utils.Helper;
+import baritone.api.utils.IPlayerContext;
+import net.minecraft.client.multiplayer.ServerData;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.Tuple;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.dimension.DimensionType;
+import net.minecraft.world.level.storage.LevelResource;
import org.apache.commons.lang3.SystemUtils;
-import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
-import java.util.function.Consumer;
-import net.minecraft.client.server.IntegratedServer;
-import net.minecraft.resources.ResourceKey;
-import net.minecraft.world.level.Level;
-import net.minecraft.world.level.dimension.DimensionType;
-import net.minecraft.world.level.storage.LevelResource;
+import java.util.Optional;
/**
* @author Brady
* @since 8/4/2018
*/
-public class WorldProvider implements IWorldProvider, Helper {
+public class WorldProvider implements IWorldProvider {
- private static final Map worldCache = new HashMap<>(); // this is how the bots have the same cached world
+ private static final Map worldCache = new HashMap<>();
+ private final Baritone baritone;
+ private final IPlayerContext ctx;
private WorldData currentWorld;
- private Level mcWorld; // this let's us detect a broken load/unload hook
+
+ /**
+ * This lets us detect a broken load/unload hook.
+ * @see #detectAndHandleBrokenLoading()
+ */
+ private Level mcWorld;
+
+ public WorldProvider(Baritone baritone) {
+ this.baritone = baritone;
+ this.ctx = baritone.getPlayerContext();
+ }
@Override
public final WorldData getCurrentWorld() {
- detectAndHandleBrokenLoading();
+ this.detectAndHandleBrokenLoading();
return this.currentWorld;
}
/**
* Called when a new world is initialized to discover the
*
- * @param world The world's Registry Data
+ * @param world The new world
*/
- public final void initWorld(ResourceKey worldKey, DimensionType world) {
- Path directory;
- Path readme;
+ public final void initWorld(Level world) {
+ this.getSaveDirectories(world).ifPresent(dirs -> {
+ final Path worldDir = dirs.getA();
+ final Path readmeDir = dirs.getB();
- IntegratedServer integratedServer = mc.getSingleplayerServer();
-
- // If there is an integrated server running (Aka Singleplayer) then do magic to find the world save file
- if (mc.hasSingleplayerServer()) {
- directory = DimensionType.getStorageFolder(worldKey, integratedServer.getWorldPath(LevelResource.ROOT));
-
- // Gets the "depth" of this directory relative the the game's run directory, 2 is the location of the world
- if (directory.relativize(mc.gameDirectory.toPath()).getNameCount() != 2) {
- // subdirectory of the main save directory for this world
- directory = directory.getParent();
- }
-
- directory = directory.resolve("baritone");
- readme = directory;
- } else { // Otherwise, the server must be remote...
- String folderName;
- if (mc.getCurrentServer() != null) {
- folderName = mc.isConnectedToRealms() ? "realms" : mc.getCurrentServer().ip;
- } else {
- //replaymod causes null currentServer and false singleplayer.
- System.out.println("World seems to be a replay. Not loading Baritone cache.");
- currentWorld = null;
- mcWorld = mc.level;
- return;
- }
- if (SystemUtils.IS_OS_WINDOWS) {
- folderName = folderName.replace(":", "_");
- }
- directory = Baritone.getDir().toPath().resolve(folderName);
- readme = Baritone.getDir().toPath();
- }
-
- // lol wtf is this baritone folder in my minecraft save?
- try (FileOutputStream out = new FileOutputStream(readme.resolve("readme.txt").toFile())) {
- // good thing we have a readme
- out.write("https://github.com/cabaletta/baritone\n".getBytes());
- } catch (IOException ignored) {}
-
- // We will actually store the world data in a subfolder: "DIM"
- Path dir = getDimDir(worldKey, world.logicalHeight(), directory);
- if (!Files.exists(dir)) {
try {
- Files.createDirectories(dir);
+ // lol wtf is this baritone folder in my minecraft save?
+ // good thing we have a readme
+ Files.createDirectories(readmeDir);
+ Files.write(
+ readmeDir.resolve("readme.txt"),
+ "https://github.com/cabaletta/baritone\n".getBytes(StandardCharsets.US_ASCII)
+ );
} catch (IOException ignored) {}
- }
- System.out.println("Baritone world data dir: " + dir);
- synchronized (worldCache) {
- this.currentWorld = worldCache.computeIfAbsent(dir, d -> new WorldData(d, world));
- }
- this.mcWorld = mc.level;
- }
+ // We will actually store the world data in a subfolder: "DIM"
+ final Path worldDataDir = this.getWorldDataDirectory(worldDir, world);
+ try {
+ Files.createDirectories(worldDataDir);
+ } catch (IOException ignored) {}
- public final Path getDimDir(ResourceKey level, int height, Path directory) {
- return directory.resolve(level.location().getNamespace()).resolve(level.location().getPath() + "_" + height);
+ System.out.println("Baritone world data dir: " + worldDataDir);
+ synchronized (worldCache) {
+ this.currentWorld = worldCache.computeIfAbsent(worldDataDir, d -> new WorldData(d, world.dimensionType()));
+ }
+ this.mcWorld = ctx.world();
+ });
}
public final void closeWorld() {
@@ -129,26 +109,73 @@ public class WorldProvider implements IWorldProvider, Helper {
world.onClose();
}
- public final void ifWorldLoaded(Consumer currentWorldConsumer) {
- detectAndHandleBrokenLoading();
- if (this.currentWorld != null) {
- currentWorldConsumer.accept(this.currentWorld);
- }
+ private Path getWorldDataDirectory(Path parent, Level world) {
+ ResourceLocation dimId = world.dimension().location();
+ int height = world.dimensionType().logicalHeight();
+ return parent.resolve(dimId.getNamespace()).resolve(dimId.getPath() + "_" + height);
}
- private final void detectAndHandleBrokenLoading() {
- if (this.mcWorld != mc.level) {
+ /**
+ * @param world The world
+ * @return An {@link Optional} containing the world's baritone dir and readme dir, or {@link Optional#empty()} if
+ * the world isn't valid for caching.
+ */
+ private Optional> getSaveDirectories(Level world) {
+ Path worldDir;
+ Path readmeDir;
+
+ // If there is an integrated server running (Aka Singleplayer) then do magic to find the world save file
+ if (ctx.minecraft().hasSingleplayerServer()) {
+ worldDir = ctx.minecraft().getSingleplayerServer().getWorldPath(LevelResource.ROOT);
+
+ // Gets the "depth" of this directory relative to the game's run directory, 2 is the location of the world
+ if (worldDir.relativize(ctx.minecraft().gameDirectory.toPath()).getNameCount() != 2) {
+ // subdirectory of the main save directory for this world
+ worldDir = worldDir.getParent();
+ }
+
+ worldDir = worldDir.resolve("baritone");
+ readmeDir = worldDir;
+ } else { // Otherwise, the server must be remote...
+ String folderName;
+ final ServerData serverData = ctx.minecraft().getCurrentServer();
+ if (serverData != null) {
+ folderName = ctx.minecraft().isConnectedToRealms() ? "realms" : serverData.ip;
+ } else {
+ //replaymod causes null currentServer and false singleplayer.
+ System.out.println("World seems to be a replay. Not loading Baritone cache.");
+ currentWorld = null;
+ mcWorld = ctx.world();
+ return Optional.empty();
+ }
+ if (SystemUtils.IS_OS_WINDOWS) {
+ folderName = folderName.replace(":", "_");
+ }
+ // TODO: This should probably be in "baritone/servers"
+ worldDir = baritone.getDirectory().resolve(folderName);
+ // Just write the readme to the baritone directory instead of each server save in it
+ readmeDir = baritone.getDirectory();
+ }
+
+ return Optional.of(new Tuple<>(worldDir, readmeDir));
+ }
+
+ /**
+ * Why does this exist instead of fixing the event? Some mods break the event. Lol.
+ */
+ private void detectAndHandleBrokenLoading() {
+ if (this.mcWorld != ctx.world()) {
if (this.currentWorld != null) {
System.out.println("mc.world unloaded unnoticed! Unloading Baritone cache now.");
closeWorld();
}
- if (mc.level != null) {
+ if (ctx.world() != null) {
System.out.println("mc.world loaded unnoticed! Loading Baritone cache now.");
- initWorld(mc.level.dimension(), mc.level.dimensionType());
+ initWorld(ctx.world());
}
- } else if (currentWorld == null && mc.level != null && (mc.hasSingleplayerServer() || mc.getCurrentServer() != null)) {
+ } else if (this.currentWorld == null && ctx.world() != null && (ctx.minecraft().hasSingleplayerServer() || ctx.minecraft().getCurrentServer() != null)) {
System.out.println("Retrying to load Baritone cache");
- initWorld(mc.level.dimension(), mc.level.dimensionType());
+ initWorld(ctx.world());
}
}
}
diff --git a/src/main/java/baritone/cache/WorldScanner.java b/src/main/java/baritone/cache/WorldScanner.java
index 2ce865a8d..35e46ba23 100644
--- a/src/main/java/baritone/cache/WorldScanner.java
+++ b/src/main/java/baritone/cache/WorldScanner.java
@@ -22,8 +22,6 @@ import baritone.api.cache.IWorldScanner;
import baritone.api.utils.BetterBlockPos;
import baritone.api.utils.BlockOptionalMetaLookup;
import baritone.api.utils.IPlayerContext;
-import java.util.*;
-import java.util.stream.IntStream;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos;
@@ -33,6 +31,9 @@ import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
+import java.util.*;
+import java.util.stream.IntStream;
+
public enum WorldScanner implements IWorldScanner {
INSTANCE;
diff --git a/src/main/java/baritone/command/ExampleBaritoneControl.java b/src/main/java/baritone/command/ExampleBaritoneControl.java
index c3c6648fe..943b8afa6 100644
--- a/src/main/java/baritone/command/ExampleBaritoneControl.java
+++ b/src/main/java/baritone/command/ExampleBaritoneControl.java
@@ -17,8 +17,8 @@
package baritone.command;
+import baritone.Baritone;
import baritone.api.BaritoneAPI;
-import baritone.api.IBaritone;
import baritone.api.Settings;
import baritone.api.command.argument.ICommandArgument;
import baritone.api.command.exception.CommandNotEnoughArgumentsException;
@@ -27,9 +27,9 @@ import baritone.api.command.helpers.TabCompleteHelper;
import baritone.api.command.manager.ICommandManager;
import baritone.api.event.events.ChatEvent;
import baritone.api.event.events.TabCompleteEvent;
-import baritone.api.event.listener.AbstractGameEventListener;
import baritone.api.utils.Helper;
import baritone.api.utils.SettingsUtil;
+import baritone.behavior.Behavior;
import baritone.command.argument.ArgConsumer;
import baritone.command.argument.CommandArguments;
import baritone.command.manager.CommandManager;
@@ -46,14 +46,14 @@ import java.util.stream.Stream;
import static baritone.api.command.IBaritoneChatControl.FORCE_COMMAND_PREFIX;
-public class ExampleBaritoneControl implements Helper, AbstractGameEventListener {
+public class ExampleBaritoneControl extends Behavior implements Helper {
private static final Settings settings = BaritoneAPI.getSettings();
private final ICommandManager manager;
- public ExampleBaritoneControl(IBaritone baritone) {
+ public ExampleBaritoneControl(Baritone baritone) {
+ super(baritone);
this.manager = baritone.getCommandManager();
- baritone.getGameEventHandler().registerEventListener(this);
}
@Override
@@ -97,7 +97,7 @@ public class ExampleBaritoneControl implements Helper, AbstractGameEventListener
return false;
} else if (msg.trim().equalsIgnoreCase("orderpizza")) {
try {
- ((IGuiScreen) mc.screen).openLinkInvoker(new URI("https://www.dominos.com/en/pages/order/"));
+ ((IGuiScreen) ctx.minecraft().screen).openLinkInvoker(new URI("https://www.dominos.com/en/pages/order/"));
} catch (NullPointerException | URISyntaxException ignored) {}
return false;
}
@@ -121,7 +121,7 @@ public class ExampleBaritoneControl implements Helper, AbstractGameEventListener
}
} else if (argc.hasExactlyOne()) {
for (Settings.Setting setting : settings.allSettings) {
- if (SettingsUtil.javaOnlySetting(setting)) {
+ if (setting.isJavaOnly()) {
continue;
}
if (setting.getName().equalsIgnoreCase(pair.getA())) {
@@ -174,7 +174,7 @@ public class ExampleBaritoneControl implements Helper, AbstractGameEventListener
.stream();
}
Settings.Setting setting = settings.byLowerName.get(argc.getString().toLowerCase(Locale.US));
- if (setting != null && !SettingsUtil.javaOnlySetting(setting)) {
+ if (setting != null && !setting.isJavaOnly()) {
if (setting.getValueClass() == Boolean.class) {
TabCompleteHelper helper = new TabCompleteHelper();
if ((Boolean) setting.value) {
diff --git a/src/main/java/baritone/command/argument/ArgConsumer.java b/src/main/java/baritone/command/argument/ArgConsumer.java
index c4a6df002..99f30ce96 100644
--- a/src/main/java/baritone/command/argument/ArgConsumer.java
+++ b/src/main/java/baritone/command/argument/ArgConsumer.java
@@ -380,6 +380,8 @@ public class ArgConsumer implements IArgConsumer {
public Stream tabCompleteDatatype(T datatype) {
try {
return datatype.tabComplete(this.context);
+ } catch (CommandException ignored) {
+ // NOP
} catch (Exception e) {
e.printStackTrace();
}
diff --git a/src/main/java/baritone/command/defaults/BuildCommand.java b/src/main/java/baritone/command/defaults/BuildCommand.java
index 622aef4f8..bb34254ae 100644
--- a/src/main/java/baritone/command/defaults/BuildCommand.java
+++ b/src/main/java/baritone/command/defaults/BuildCommand.java
@@ -35,10 +35,11 @@ import java.util.stream.Stream;
public class BuildCommand extends Command {
- private static final File schematicsDir = new File(mc.gameDirectory, "schematics");
+ private final File schematicsDir;
public BuildCommand(IBaritone baritone) {
super(baritone, "build");
+ this.schematicsDir = new File(baritone.getPlayerContext().minecraft().gameDirectory, "schematics");
}
@Override
diff --git a/src/main/java/baritone/command/defaults/ComeCommand.java b/src/main/java/baritone/command/defaults/ComeCommand.java
index 5cbed55bc..b111ee1de 100644
--- a/src/main/java/baritone/command/defaults/ComeCommand.java
+++ b/src/main/java/baritone/command/defaults/ComeCommand.java
@@ -21,12 +21,11 @@ import baritone.api.IBaritone;
import baritone.api.command.Command;
import baritone.api.command.argument.IArgConsumer;
import baritone.api.command.exception.CommandException;
-import baritone.api.command.exception.CommandInvalidStateException;
import baritone.api.pathing.goals.GoalBlock;
+
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
-import net.minecraft.world.entity.Entity;
public class ComeCommand extends Command {
@@ -37,11 +36,7 @@ public class ComeCommand extends Command {
@Override
public void execute(String label, IArgConsumer args) throws CommandException {
args.requireMax(0);
- Entity entity = mc.getCameraEntity();
- if (entity == null) {
- throw new CommandInvalidStateException("render view entity is null");
- }
- baritone.getCustomGoalProcess().setGoalAndPath(new GoalBlock(entity.blockPosition()));
+ baritone.getCustomGoalProcess().setGoalAndPath(new GoalBlock(ctx.viewerPos()));
logDirect("Coming");
}
diff --git a/src/main/java/baritone/command/defaults/DefaultCommands.java b/src/main/java/baritone/command/defaults/DefaultCommands.java
index c2118ebe8..c810d07c1 100644
--- a/src/main/java/baritone/command/defaults/DefaultCommands.java
+++ b/src/main/java/baritone/command/defaults/DefaultCommands.java
@@ -66,7 +66,8 @@ public final class DefaultCommands {
new WaypointsCommand(baritone),
new CommandAlias(baritone, "sethome", "Sets your home waypoint", "waypoints save home"),
new CommandAlias(baritone, "home", "Path to your home waypoint", "waypoints goto home"),
- new SelCommand(baritone)
+ new SelCommand(baritone),
+ new ElytraCommand(baritone)
));
ExecutionControlCommands prc = new ExecutionControlCommands(baritone);
commands.add(prc.pauseCommand);
diff --git a/src/main/java/baritone/command/defaults/ElytraCommand.java b/src/main/java/baritone/command/defaults/ElytraCommand.java
new file mode 100644
index 000000000..3f7b6bfd6
--- /dev/null
+++ b/src/main/java/baritone/command/defaults/ElytraCommand.java
@@ -0,0 +1,225 @@
+/*
+ * 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.command.defaults;
+
+import baritone.Baritone;
+import baritone.api.IBaritone;
+import baritone.api.command.Command;
+import baritone.api.command.argument.IArgConsumer;
+import baritone.api.command.exception.CommandException;
+import baritone.api.command.exception.CommandInvalidStateException;
+import baritone.api.command.helpers.TabCompleteHelper;
+import baritone.api.pathing.goals.Goal;
+import baritone.api.process.ICustomGoalProcess;
+import baritone.api.process.IElytraProcess;
+import net.minecraft.ChatFormatting;
+import net.minecraft.client.multiplayer.ServerData;
+import net.minecraft.network.chat.ClickEvent;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.HoverEvent;
+import net.minecraft.network.chat.MutableComponent;
+import net.minecraft.world.level.Level;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static baritone.api.command.IBaritoneChatControl.FORCE_COMMAND_PREFIX;
+
+public class ElytraCommand extends Command {
+
+ public ElytraCommand(IBaritone baritone) {
+ super(baritone, "elytra");
+ }
+
+ @Override
+ public void execute(String label, IArgConsumer args) throws CommandException {
+ final ICustomGoalProcess customGoalProcess = baritone.getCustomGoalProcess();
+ final IElytraProcess elytra = baritone.getElytraProcess();
+ if (args.hasExactlyOne() && args.peekString().equals("supported")) {
+ logDirect(elytra.isLoaded() ? "yes" : unsupportedSystemMessage());
+ return;
+ }
+ if (!elytra.isLoaded()) {
+ throw new CommandInvalidStateException(unsupportedSystemMessage());
+ }
+
+ if (!args.hasAny()) {
+ if (Baritone.settings().elytraTermsAccepted.value) {
+ if (detectOn2b2t()) {
+ warn2b2t();
+ }
+ } else {
+ gatekeep();
+ }
+ Goal iGoal = customGoalProcess.mostRecentGoal();
+ if (iGoal == null) {
+ throw new CommandInvalidStateException("No goal has been set");
+ }
+ if (ctx.world().dimension() != Level.NETHER) {
+ throw new CommandInvalidStateException("Only works in the nether");
+ }
+ try {
+ elytra.pathTo(iGoal);
+ } catch (IllegalArgumentException ex) {
+ throw new CommandInvalidStateException(ex.getMessage());
+ }
+ return;
+ }
+
+ final String action = args.getString();
+ switch (action) {
+ case "reset": {
+ elytra.resetState();
+ logDirect("Reset state but still flying to same goal");
+ break;
+ }
+ case "repack": {
+ elytra.repackChunks();
+ logDirect("Queued all loaded chunks for repacking");
+ break;
+ }
+ default: {
+ throw new CommandInvalidStateException("Invalid action");
+ }
+ }
+ }
+
+ private void warn2b2t() {
+ if (Baritone.settings().elytraPredictTerrain.value) {
+ long seed = Baritone.settings().elytraNetherSeed.value;
+ if (seed != NEW_2B2T_SEED && seed != OLD_2B2T_SEED) {
+ logDirect(Component.literal("It looks like you're on 2b2t, but elytraNetherSeed is incorrect.")); // match color
+ logDirect(suggest2b2tSeeds());
+ }
+ }
+ }
+
+ private Component suggest2b2tSeeds() {
+ MutableComponent clippy = Component.literal("");
+ clippy.append("Within a few hundred blocks of spawn/axis/highways/etc, the terrain is too fragmented to be predictable. Baritone Elytra will still work, just with backtracking. ");
+ clippy.append("However, once you get more than a few thousand blocks out, you should try ");
+ MutableComponent olderSeed = Component.literal("the older seed (click here)");
+ olderSeed.setStyle(olderSeed.getStyle().withUnderlined(true).withBold(true).withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(Baritone.settings().prefix.value + "set elytraNetherSeed " + OLD_2B2T_SEED))).withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, FORCE_COMMAND_PREFIX + "set elytraNetherSeed " + OLD_2B2T_SEED)));
+ clippy.append(olderSeed);
+ clippy.append(". Once you're further out into newer terrain generation (this includes everything up through 1.12), you should try ");
+ MutableComponent newerSeed = Component.literal("the newer seed (click here)");
+ newerSeed.setStyle(newerSeed.getStyle().withUnderlined(true).withBold(true).withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(Baritone.settings().prefix.value + "set elytraNetherSeed " + NEW_2B2T_SEED))).withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, FORCE_COMMAND_PREFIX + "set elytraNetherSeed " + NEW_2B2T_SEED)));
+ clippy.append(newerSeed);
+ clippy.append(". Once you get into 1.19 terrain, the terrain becomes unpredictable again, due to custom non-vanilla generation, and you should set #elytraPredictTerrain to false. ");
+ return clippy;
+ }
+
+ private void gatekeep() {
+ MutableComponent gatekeep = Component.literal("");
+ gatekeep.append("To disable this message, enable the setting elytraTermsAccepted\n");
+ gatekeep.append("Baritone Elytra is an experimental feature. It is only intended for long distance travel in the Nether using fireworks for vanilla boost. It will not work with any other mods (\"hacks\") for non-vanilla boost. ");
+ MutableComponent gatekeep2 = Component.literal("If you want Baritone to attempt to take off from the ground for you, you can enable the elytraAutoJump setting (not advisable on laggy servers!). ");
+ gatekeep2.setStyle(gatekeep2.getStyle().withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(Baritone.settings().prefix.value + "set elytraAutoJump true"))));
+ gatekeep.append(gatekeep2);
+ MutableComponent gatekeep3 = Component.literal("If you want Baritone to go slower, enable the elytraConserveFireworks setting and/or decrease the elytraFireworkSpeed setting. ");
+ gatekeep3.setStyle(gatekeep3.getStyle().withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(Baritone.settings().prefix.value + "set elytraConserveFireworks true\n" + Baritone.settings().prefix.value + "set elytraFireworkSpeed 0.6\n(the 0.6 number is just an example, tweak to your liking)"))));
+ gatekeep.append(gatekeep3);
+ MutableComponent gatekeep4 = Component.literal("Baritone Elytra ");
+ MutableComponent red = Component.literal("wants to know the seed");
+ red.setStyle(red.getStyle().withColor(ChatFormatting.RED).withUnderlined(true).withBold(true));
+ gatekeep4.append(red);
+ gatekeep4.append(" of the world you are in. If it doesn't have the correct seed, it will frequently backtrack. It uses the seed to generate terrain far beyond what you can see, since terrain obstacles in the Nether can be much larger than your render distance. ");
+ gatekeep.append(gatekeep4);
+ gatekeep.append("\n");
+ if (detectOn2b2t()) {
+ MutableComponent gatekeep5 = Component.literal("It looks like you're on 2b2t. ");
+ gatekeep5.append(suggest2b2tSeeds());
+ if (!Baritone.settings().elytraPredictTerrain.value) {
+ gatekeep5.append(Baritone.settings().prefix.value + "elytraPredictTerrain is currently disabled. ");
+ } else {
+ if (Baritone.settings().elytraNetherSeed.value == NEW_2B2T_SEED) {
+ gatekeep5.append("You are using the newer seed. ");
+ } else if (Baritone.settings().elytraNetherSeed.value == OLD_2B2T_SEED) {
+ gatekeep5.append("You are using the older seed. ");
+ } else {
+ gatekeep5.append("Defaulting to the newer seed. ");
+ Baritone.settings().elytraNetherSeed.value = NEW_2B2T_SEED;
+ }
+ }
+ gatekeep.append(gatekeep5);
+ } else {
+ if (Baritone.settings().elytraNetherSeed.value == NEW_2B2T_SEED) {
+ MutableComponent gatekeep5 = Component.literal("Baritone doesn't know the seed of your world. Set it with: " + Baritone.settings().prefix.value + "set elytraNetherSeed seedgoeshere\n");
+ gatekeep5.append("For the time being, elytraPredictTerrain is defaulting to false since the seed is unknown.");
+ gatekeep.append(gatekeep5);
+ Baritone.settings().elytraPredictTerrain.value = false;
+ } else {
+ if (Baritone.settings().elytraPredictTerrain.value) {
+ MutableComponent gatekeep5 = Component.literal("Baritone Elytra is predicting terrain assuming that " + Baritone.settings().elytraNetherSeed.value + " is the correct seed. Change that with " + Baritone.settings().prefix.value + "set elytraNetherSeed seedgoeshere, or disable it with " + Baritone.settings().prefix.value + "set elytraPredictTerrain false");
+ gatekeep.append(gatekeep5);
+ } else {
+ MutableComponent gatekeep5 = Component.literal("Baritone Elytra is not predicting terrain. If you don't know the seed, this is the correct thing to do. If you do know the seed, input it with " + Baritone.settings().prefix.value + "set elytraNetherSeed seedgoeshere, and then enable it with " + Baritone.settings().prefix.value + "set elytraPredictTerrain true");
+ gatekeep.append(gatekeep5);
+ }
+ }
+ }
+ logDirect(gatekeep);
+ }
+
+ private boolean detectOn2b2t() {
+ ServerData data = ctx.minecraft().getCurrentServer();
+ return data != null && data.ip.toLowerCase().contains("2b2t.org");
+ }
+
+ private static final long OLD_2B2T_SEED = -4100785268875389365L;
+ private static final long NEW_2B2T_SEED = 146008555100680L;
+
+ @Override
+ public Stream tabComplete(String label, IArgConsumer args) throws CommandException {
+ TabCompleteHelper helper = new TabCompleteHelper();
+ if (args.hasExactlyOne()) {
+ helper.append("reset", "repack", "supported");
+ }
+ return helper.filterPrefix(args.getString()).stream();
+ }
+
+ @Override
+ public String getShortDesc() {
+ return "elytra time";
+ }
+
+ @Override
+ public List getLongDesc() {
+ return Arrays.asList(
+ "The elytra command tells baritone to, in the nether, automatically fly to the current goal.",
+ "",
+ "Usage:",
+ "> elytra - fly to the current goal",
+ "> elytra reset - Resets the state of the process, but will try to keep flying to the same goal.",
+ "> elytra repack - Queues all of the chunks in render distance to be given to the native library.",
+ "> elytra supported - Tells you if baritone ships a native library that is compatible with your PC."
+ );
+ }
+
+ private static String unsupportedSystemMessage() {
+ final String osArch = System.getProperty("os.arch");
+ final String osName = System.getProperty("os.name");
+ return String.format(
+ "Legacy architectures are not supported. Your CPU is %s and your operating system is %s. " +
+ "Supported architectures are 64 bit x86, and 64 bit ARM. Supported operating systems are Windows, " +
+ "Linux, and Mac",
+ osArch, osName
+ );
+ }
+}
diff --git a/src/main/java/baritone/command/defaults/ExploreFilterCommand.java b/src/main/java/baritone/command/defaults/ExploreFilterCommand.java
index 504f7c146..ecb5f8376 100644
--- a/src/main/java/baritone/command/defaults/ExploreFilterCommand.java
+++ b/src/main/java/baritone/command/defaults/ExploreFilterCommand.java
@@ -41,7 +41,7 @@ public class ExploreFilterCommand extends Command {
@Override
public void execute(String label, IArgConsumer args) throws CommandException {
args.requireMax(2);
- File file = args.getDatatypePost(RelativeFile.INSTANCE, mc.gameDirectory.getAbsoluteFile().getParentFile());
+ File file = args.getDatatypePost(RelativeFile.INSTANCE, ctx.minecraft().gameDirectory.getAbsoluteFile().getParentFile());
boolean invert = false;
if (args.hasAny()) {
if (args.getString().equalsIgnoreCase("invert")) {
@@ -65,7 +65,7 @@ public class ExploreFilterCommand extends Command {
@Override
public Stream tabComplete(String label, IArgConsumer args) throws CommandException {
if (args.hasExactlyOne()) {
- return RelativeFile.tabComplete(args, RelativeFile.gameDir());
+ return RelativeFile.tabComplete(args, RelativeFile.gameDir(ctx.minecraft()));
}
return Stream.empty();
}
diff --git a/src/main/java/baritone/command/defaults/GoalCommand.java b/src/main/java/baritone/command/defaults/GoalCommand.java
index 40822e057..a174ecad9 100644
--- a/src/main/java/baritone/command/defaults/GoalCommand.java
+++ b/src/main/java/baritone/command/defaults/GoalCommand.java
@@ -51,7 +51,7 @@ public class GoalCommand extends Command {
}
} else {
args.requireMax(3);
- BetterBlockPos origin = baritone.getPlayerContext().playerFeet();
+ BetterBlockPos origin = ctx.playerFeet();
Goal goal = args.getDatatypePost(RelativeGoal.INSTANCE, origin);
goalProcess.setGoal(goal);
logDirect(String.format("Goal: %s", goal.toString()));
diff --git a/src/main/java/baritone/command/defaults/GotoCommand.java b/src/main/java/baritone/command/defaults/GotoCommand.java
index 896e3f5f8..c64d7fa00 100644
--- a/src/main/java/baritone/command/defaults/GotoCommand.java
+++ b/src/main/java/baritone/command/defaults/GotoCommand.java
@@ -20,7 +20,6 @@ package baritone.command.defaults;
import baritone.api.IBaritone;
import baritone.api.command.Command;
import baritone.api.command.argument.IArgConsumer;
-import baritone.api.command.datatypes.BlockById;
import baritone.api.command.datatypes.ForBlockOptionalMeta;
import baritone.api.command.datatypes.RelativeCoordinate;
import baritone.api.command.datatypes.RelativeGoal;
@@ -46,7 +45,7 @@ public class GotoCommand extends Command {
// is no need to handle the case of empty arguments.
if (args.peekDatatypeOrNull(RelativeCoordinate.INSTANCE) != null) {
args.requireMax(3);
- BetterBlockPos origin = baritone.getPlayerContext().playerFeet();
+ BetterBlockPos origin = ctx.playerFeet();
Goal goal = args.getDatatypePost(RelativeGoal.INSTANCE, origin);
logDirect(String.format("Going to: %s", goal.toString()));
baritone.getCustomGoalProcess().setGoalAndPath(goal);
@@ -61,7 +60,8 @@ public class GotoCommand extends Command {
public Stream tabComplete(String label, IArgConsumer args) throws CommandException {
// since it's either a goal or a block, I don't think we can tab complete properly?
// so just tab complete for the block variant
- return args.tabCompleteDatatype(BlockById.INSTANCE);
+ args.requireMax(1);
+ return args.tabCompleteDatatype(ForBlockOptionalMeta.INSTANCE);
}
@Override
diff --git a/src/main/java/baritone/command/defaults/MineCommand.java b/src/main/java/baritone/command/defaults/MineCommand.java
index 98a2e264d..eb2c22c6d 100644
--- a/src/main/java/baritone/command/defaults/MineCommand.java
+++ b/src/main/java/baritone/command/defaults/MineCommand.java
@@ -17,14 +17,13 @@
package baritone.command.defaults;
+import baritone.api.BaritoneAPI;
import baritone.api.IBaritone;
import baritone.api.command.Command;
import baritone.api.command.argument.IArgConsumer;
-import baritone.api.command.datatypes.BlockById;
import baritone.api.command.datatypes.ForBlockOptionalMeta;
import baritone.api.command.exception.CommandException;
import baritone.api.utils.BlockOptionalMeta;
-import baritone.cache.WorldScanner;
import java.util.ArrayList;
import java.util.Arrays;
@@ -45,14 +44,18 @@ 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]));
}
@Override
- public Stream tabComplete(String label, IArgConsumer args) {
- return args.tabCompleteDatatype(BlockById.INSTANCE);
+ public Stream tabComplete(String label, IArgConsumer args) throws CommandException {
+ args.getAsOrDefault(Integer.class, 0);
+ while (args.has(2)) {
+ args.getDatatypeFor(ForBlockOptionalMeta.INSTANCE);
+ }
+ return args.tabCompleteDatatype(ForBlockOptionalMeta.INSTANCE);
}
@Override
diff --git a/src/main/java/baritone/command/defaults/PathCommand.java b/src/main/java/baritone/command/defaults/PathCommand.java
index 182a1e5bc..b2021adf6 100644
--- a/src/main/java/baritone/command/defaults/PathCommand.java
+++ b/src/main/java/baritone/command/defaults/PathCommand.java
@@ -17,12 +17,12 @@
package baritone.command.defaults;
+import baritone.api.BaritoneAPI;
import baritone.api.IBaritone;
import baritone.api.command.Command;
import baritone.api.command.argument.IArgConsumer;
import baritone.api.command.exception.CommandException;
import baritone.api.process.ICustomGoalProcess;
-import baritone.cache.WorldScanner;
import java.util.Arrays;
import java.util.List;
@@ -38,7 +38,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/RenderCommand.java b/src/main/java/baritone/command/defaults/RenderCommand.java
index 39dc6ea7c..543c3387c 100644
--- a/src/main/java/baritone/command/defaults/RenderCommand.java
+++ b/src/main/java/baritone/command/defaults/RenderCommand.java
@@ -37,8 +37,8 @@ public class RenderCommand extends Command {
public void execute(String label, IArgConsumer args) throws CommandException {
args.requireMax(0);
BetterBlockPos origin = ctx.playerFeet();
- int renderDistance = (mc.options.renderDistance().get() + 1) * 16;
- mc.levelRenderer.setBlocksDirty(
+ int renderDistance = (ctx.minecraft().options.renderDistance().get() + 1) * 16;
+ ctx.minecraft().levelRenderer.setBlocksDirty(
origin.x - renderDistance,
0,
origin.z - renderDistance,
diff --git a/src/main/java/baritone/command/defaults/RepackCommand.java b/src/main/java/baritone/command/defaults/RepackCommand.java
index cafbea524..9f972561d 100644
--- a/src/main/java/baritone/command/defaults/RepackCommand.java
+++ b/src/main/java/baritone/command/defaults/RepackCommand.java
@@ -17,11 +17,11 @@
package baritone.command.defaults;
+import baritone.api.BaritoneAPI;
import baritone.api.IBaritone;
import baritone.api.command.Command;
import baritone.api.command.argument.IArgConsumer;
import baritone.api.command.exception.CommandException;
-import baritone.cache.WorldScanner;
import java.util.Arrays;
import java.util.List;
@@ -36,7 +36,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/command/defaults/SelCommand.java b/src/main/java/baritone/command/defaults/SelCommand.java
index c5b82932d..14d22b0b4 100644
--- a/src/main/java/baritone/command/defaults/SelCommand.java
+++ b/src/main/java/baritone/command/defaults/SelCommand.java
@@ -21,6 +21,7 @@ import baritone.Baritone;
import baritone.api.IBaritone;
import baritone.api.command.Command;
import baritone.api.command.argument.IArgConsumer;
+import baritone.api.command.datatypes.ForAxis;
import baritone.api.command.datatypes.ForBlockOptionalMeta;
import baritone.api.command.datatypes.ForDirection;
import baritone.api.command.datatypes.RelativeBlockPos;
@@ -31,6 +32,8 @@ import baritone.api.command.helpers.TabCompleteHelper;
import baritone.api.event.events.RenderEvent;
import baritone.api.event.listener.AbstractGameEventListener;
import baritone.api.schematic.*;
+import baritone.api.schematic.mask.shape.CylinderMask;
+import baritone.api.schematic.mask.shape.SphereMask;
import baritone.api.selection.ISelection;
import baritone.api.selection.ISelectionManager;
import baritone.api.utils.BetterBlockPos;
@@ -50,6 +53,7 @@ import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.function.Function;
+import java.util.function.UnaryOperator;
import java.util.stream.Stream;
public class SelCommand extends Command {
@@ -72,7 +76,7 @@ public class SelCommand extends Command {
float lineWidth = Baritone.settings().selectionLineWidth.value;
boolean ignoreDepth = Baritone.settings().renderSelectionIgnoreDepth.value;
IRenderer.startLines(color, opacity, lineWidth, ignoreDepth);
- IRenderer.drawAABB(event.getModelViewStack(), new AABB(pos1, pos1.offset(1, 1, 1)));
+ IRenderer.emitAABB(event.getModelViewStack(), new AABB(pos1, pos1.offset(1, 1, 1)));
IRenderer.endLines(ignoreDepth);
}
});
@@ -88,7 +92,7 @@ public class SelCommand extends Command {
if (action == Action.POS2 && pos1 == null) {
throw new CommandInvalidStateException("Set pos1 first before using pos2");
}
- BetterBlockPos playerPos = mc.getCameraEntity() != null ? BetterBlockPos.from(mc.getCameraEntity().blockPosition()) : ctx.playerFeet();
+ BetterBlockPos playerPos = ctx.viewerPos();
BetterBlockPos pos = args.hasAny() ? args.getDatatypePost(RelativeBlockPos.INSTANCE, playerPos) : playerPos;
args.requireMax(0);
if (action == Action.POS1) {
@@ -117,11 +121,13 @@ public class SelCommand extends Command {
logDirect("Undid pos2");
}
}
- } else if (action == Action.SET || action == Action.WALLS || action == Action.SHELL || action == Action.CLEARAREA || action == Action.REPLACE) {
+ } else if (action.isFillAction()) {
BlockOptionalMeta type = action == Action.CLEARAREA
? new BlockOptionalMeta(Blocks.AIR)
: args.getDatatypeFor(ForBlockOptionalMeta.INSTANCE);
- BlockOptionalMetaLookup replaces = null;
+
+ final BlockOptionalMetaLookup replaces; // Action.REPLACE
+ final Direction.Axis alignment; // Action.(H)CYLINDER
if (action == Action.REPLACE) {
args.requireMin(1);
List replacesList = new ArrayList<>();
@@ -131,8 +137,15 @@ public class SelCommand extends Command {
}
type = args.getDatatypeFor(ForBlockOptionalMeta.INSTANCE);
replaces = new BlockOptionalMetaLookup(replacesList.toArray(new BlockOptionalMeta[0]));
+ alignment = null;
+ } else if (action == Action.CYLINDER || action == Action.HCYLINDER) {
+ args.requireMax(1);
+ alignment = args.hasAny() ? args.getDatatypeFor(ForAxis.INSTANCE) : Direction.Axis.Y;
+ replaces = null;
} else {
args.requireMax(0);
+ replaces = null;
+ alignment = null;
}
ISelection[] selections = manager.getSelections();
if (selections.length == 0) {
@@ -151,20 +164,41 @@ public class SelCommand extends Command {
for (ISelection selection : selections) {
Vec3i size = selection.size();
BetterBlockPos min = selection.min();
- ISchematic schematic = new FillSchematic(size.getX(), size.getY(), size.getZ(), type);
- if (action == Action.WALLS) {
- schematic = new WallsSchematic(schematic);
- } else if (action == Action.SHELL) {
- schematic = new ShellSchematic(schematic);
- } else if (action == Action.REPLACE) {
- schematic = new ReplaceSchematic(schematic, replaces);
- }
+
+ // Java 8 so no switch expressions 😿
+ UnaryOperator create = fill -> {
+ final int w = fill.widthX();
+ final int h = fill.heightY();
+ final int l = fill.lengthZ();
+
+ switch (action) {
+ case WALLS:
+ return new WallsSchematic(fill);
+ case SHELL:
+ return new ShellSchematic(fill);
+ case REPLACE:
+ return new ReplaceSchematic(fill, replaces);
+ case SPHERE:
+ return MaskSchematic.create(fill, new SphereMask(w, h, l, true).compute());
+ case HSPHERE:
+ return MaskSchematic.create(fill, new SphereMask(w, h, l, false).compute());
+ case CYLINDER:
+ return MaskSchematic.create(fill, new CylinderMask(w, h, l, true, alignment).compute());
+ case HCYLINDER:
+ return MaskSchematic.create(fill, new CylinderMask(w, h, l, false, alignment).compute());
+ default:
+ // Silent fail
+ return fill;
+ }
+ };
+
+ ISchematic schematic = create.apply(new FillSchematic(size.getX(), size.getY(), size.getZ(), type));
composite.put(schematic, min.x - origin.x, min.y - origin.y, min.z - origin.z);
}
baritone.getBuilderProcess().build("Fill", composite, origin);
logDirect("Filling now");
} else if (action == Action.COPY) {
- BetterBlockPos playerPos = mc.getCameraEntity() != null ? BetterBlockPos.from(mc.getCameraEntity().blockPosition()) : ctx.playerFeet();
+ BetterBlockPos playerPos = ctx.viewerPos();
BetterBlockPos pos = args.hasAny() ? args.getDatatypePost(RelativeBlockPos.INSTANCE, playerPos) : playerPos;
args.requireMax(0);
ISelection[] selections = manager.getSelections();
@@ -205,7 +239,7 @@ public class SelCommand extends Command {
clipboardOffset = origin.subtract(pos);
logDirect("Selection copied");
} else if (action == Action.PASTE) {
- BetterBlockPos playerPos = mc.getCameraEntity() != null ? BetterBlockPos.from(mc.getCameraEntity().blockPosition()) : ctx.playerFeet();
+ BetterBlockPos playerPos = ctx.viewerPos();
BetterBlockPos pos = args.hasAny() ? args.getDatatypePost(RelativeBlockPos.INSTANCE, playerPos) : playerPos;
args.requireMax(0);
if (clipboard == null) {
@@ -254,12 +288,15 @@ public class SelCommand extends Command {
if (args.hasAtMost(3)) {
return args.tabCompleteDatatype(RelativeBlockPos.INSTANCE);
}
- } else if (action == Action.SET || action == Action.WALLS || action == Action.CLEARAREA || action == Action.REPLACE) {
+ } else if (action.isFillAction()) {
if (args.hasExactlyOne() || action == Action.REPLACE) {
while (args.has(2)) {
args.get();
}
return args.tabCompleteDatatype(ForBlockOptionalMeta.INSTANCE);
+ } else if (args.hasExactly(2) && (action == Action.CYLINDER || action == Action.HCYLINDER)) {
+ args.get();
+ return args.tabCompleteDatatype(ForAxis.INSTANCE);
}
} else if (action == Action.EXPAND || action == Action.CONTRACT || action == Action.SHIFT) {
if (args.hasExactlyOne()) {
@@ -305,6 +342,10 @@ public class SelCommand extends Command {
"> sel set/fill/s/f [block] - Completely fill all selections with a block.",
"> sel walls/w [block] - Fill in the walls of the selection with a specified block.",
"> sel shell/shl [block] - The same as walls, but fills in a ceiling and floor too.",
+ "> sel sphere/sph [block] - Fills the selection with a sphere bounded by the sides.",
+ "> sel hsphere/hsph [block] - The same as sphere, but hollow.",
+ "> sel cylinder/cyl [block] - Fills the selection with a cylinder bounded by the sides, oriented about the given axis. (default=y)",
+ "> sel hcylinder/hcyl [block] - The same as cylinder, but hollow.",
"> sel cleararea/ca - Basically 'set air'.",
"> sel replace/r - Replaces blocks with another block.",
"> sel copy/cp - Copy the selected area relative to the specified or your position.",
@@ -324,6 +365,10 @@ public class SelCommand extends Command {
SET("set", "fill", "s", "f"),
WALLS("walls", "w"),
SHELL("shell", "shl"),
+ SPHERE("sphere", "sph"),
+ HSPHERE("hsphere", "hsph"),
+ CYLINDER("cylinder", "cyl"),
+ HCYLINDER("hcylinder", "hcyl"),
CLEARAREA("cleararea", "ca"),
REPLACE("replace", "r"),
EXPAND("expand", "ex"),
@@ -355,6 +400,18 @@ public class SelCommand extends Command {
}
return names.toArray(new String[0]);
}
+
+ public final boolean isFillAction() {
+ return this == SET
+ || this == WALLS
+ || this == SHELL
+ || this == SPHERE
+ || this == HSPHERE
+ || this == CYLINDER
+ || this == HCYLINDER
+ || this == CLEARAREA
+ || this == REPLACE;
+ }
}
enum TransformTarget {
diff --git a/src/main/java/baritone/command/defaults/SetCommand.java b/src/main/java/baritone/command/defaults/SetCommand.java
index 3d70c16b9..0f4439cef 100644
--- a/src/main/java/baritone/command/defaults/SetCommand.java
+++ b/src/main/java/baritone/command/defaults/SetCommand.java
@@ -22,23 +22,25 @@ import baritone.api.IBaritone;
import baritone.api.Settings;
import baritone.api.command.Command;
import baritone.api.command.argument.IArgConsumer;
+import baritone.api.command.datatypes.RelativeFile;
import baritone.api.command.exception.CommandException;
import baritone.api.command.exception.CommandInvalidStateException;
import baritone.api.command.exception.CommandInvalidTypeException;
import baritone.api.command.helpers.Paginator;
import baritone.api.command.helpers.TabCompleteHelper;
import baritone.api.utils.SettingsUtil;
+import net.minecraft.ChatFormatting;
+import net.minecraft.client.Minecraft;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.ClickEvent;
+import net.minecraft.network.chat.HoverEvent;
+import net.minecraft.network.chat.MutableComponent;
-import java.awt.*;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import net.minecraft.ChatFormatting;
-import net.minecraft.network.chat.*;
-import net.minecraft.network.chat.Component;
-import net.minecraft.network.chat.MutableComponent;
import static baritone.api.command.IBaritoneChatControl.FORCE_COMMAND_PREFIX;
import static baritone.api.utils.SettingsUtil.*;
@@ -57,6 +59,18 @@ public class SetCommand extends Command {
logDirect("Settings saved");
return;
}
+ if (Arrays.asList("load", "ld").contains(arg)) {
+ String file = SETTINGS_DEFAULT_NAME;
+ if (args.hasAny()) {
+ file = args.getString();
+ }
+ // reset to defaults
+ SettingsUtil.modifiedSettings(Baritone.settings()).forEach(Settings.Setting::reset);
+ // then load from disk
+ SettingsUtil.readAndApply(Baritone.settings(), file);
+ logDirect("Settings reloaded from " + file);
+ return;
+ }
boolean viewModified = Arrays.asList("m", "mod", "modified").contains(arg);
boolean viewAll = Arrays.asList("all", "l", "list").contains(arg);
boolean paginate = viewModified || viewAll;
@@ -65,7 +79,7 @@ public class SetCommand extends Command {
args.requireMax(1);
List extends Settings.Setting> toPaginate =
(viewModified ? SettingsUtil.modifiedSettings(Baritone.settings()) : Baritone.settings().allSettings).stream()
- .filter(s -> !javaOnlySetting(s))
+ .filter(s -> !s.isJavaOnly())
.filter(s -> s.getName().toLowerCase(Locale.US).contains(search.toLowerCase(Locale.US)))
.sorted((s1, s2) -> String.CASE_INSENSITIVE_ORDER.compare(s1.getName(), s2.getName()))
.collect(Collectors.toList());
@@ -129,7 +143,7 @@ public class SetCommand extends Command {
if (setting == null) {
throw new CommandInvalidTypeException(args.consumed(), "a valid setting");
}
- if (javaOnlySetting(setting)) {
+ if (setting.isJavaOnly()) {
// ideally it would act as if the setting didn't exist
// but users will see it in Settings.java or its javadoc
// so at some point we have to tell them or they will see it as a bug
@@ -209,6 +223,9 @@ public class SetCommand extends Command {
.addToggleableSettings()
.filterPrefix(args.getString())
.stream();
+ } else if (Arrays.asList("ld", "load").contains(arg.toLowerCase(Locale.US))) {
+ // settings always use the directory of the main Minecraft instance
+ return RelativeFile.tabComplete(args, Minecraft.getInstance().gameDirectory.toPath().resolve("baritone").toFile());
}
Settings.Setting setting = Baritone.settings().byLowerName.get(arg.toLowerCase(Locale.US));
if (setting != null) {
@@ -228,7 +245,7 @@ public class SetCommand extends Command {
return new TabCompleteHelper()
.addSettings()
.sortAlphabetically()
- .prepend("list", "modified", "reset", "toggle", "save")
+ .prepend("list", "modified", "reset", "toggle", "save", "load")
.filterPrefix(arg)
.stream();
}
@@ -255,7 +272,9 @@ public class SetCommand extends Command {
"> set reset all - Reset ALL SETTINGS to their defaults",
"> set reset - Reset a setting to its default",
"> set toggle - Toggle a boolean setting",
- "> set save - Save all settings (this is automatic tho)"
+ "> set save - Save all settings (this is automatic tho)",
+ "> set load - Load settings from settings.txt",
+ "> set load [filename] - Load settings from another file in your minecraft/baritone"
);
}
}
diff --git a/src/main/java/baritone/command/defaults/SurfaceCommand.java b/src/main/java/baritone/command/defaults/SurfaceCommand.java
index 3bbd4d738..a9c981cc3 100644
--- a/src/main/java/baritone/command/defaults/SurfaceCommand.java
+++ b/src/main/java/baritone/command/defaults/SurfaceCommand.java
@@ -37,13 +37,13 @@ public class SurfaceCommand extends Command {
@Override
public void execute(String label, IArgConsumer args) throws CommandException {
- final BetterBlockPos playerPos = baritone.getPlayerContext().playerFeet();
- final int surfaceLevel = baritone.getPlayerContext().world().getSeaLevel();
- final int worldHeight = baritone.getPlayerContext().world().getHeight();
+ final BetterBlockPos playerPos = ctx.playerFeet();
+ final int surfaceLevel = ctx.world().getSeaLevel();
+ final int worldHeight = ctx.world().getHeight();
// Ensure this command will not run if you are above the surface level and the block above you is air
// As this would imply that your are already on the open surface
- if (playerPos.getY() > surfaceLevel && mc.level.getBlockState(playerPos.above()).getBlock() instanceof AirBlock) {
+ if (playerPos.getY() > surfaceLevel && ctx.world().getBlockState(playerPos.above()).getBlock() instanceof AirBlock) {
logDirect("Already at surface");
return;
}
@@ -53,7 +53,7 @@ public class SurfaceCommand extends Command {
for (int currentIteratedY = startingYPos; currentIteratedY < worldHeight; currentIteratedY++) {
final BetterBlockPos newPos = new BetterBlockPos(playerPos.getX(), currentIteratedY, playerPos.getZ());
- if (!(mc.level.getBlockState(newPos).getBlock() instanceof AirBlock) && newPos.getY() > playerPos.getY()) {
+ if (!(ctx.world().getBlockState(newPos).getBlock() instanceof AirBlock) && newPos.getY() > playerPos.getY()) {
Goal goal = new GoalBlock(newPos.above());
logDirect(String.format("Going to: %s", goal.toString()));
baritone.getCustomGoalProcess().setGoalAndPath(goal);
diff --git a/src/main/java/baritone/command/defaults/WaypointsCommand.java b/src/main/java/baritone/command/defaults/WaypointsCommand.java
index 56a833517..6e7f0a1e5 100644
--- a/src/main/java/baritone/command/defaults/WaypointsCommand.java
+++ b/src/main/java/baritone/command/defaults/WaypointsCommand.java
@@ -149,7 +149,11 @@ public class WaypointsCommand extends Command {
logDirect(component);
} else if (action == Action.CLEAR) {
args.requireMax(1);
- IWaypoint.Tag tag = IWaypoint.Tag.getByName(args.getString());
+ String name = args.getString();
+ IWaypoint.Tag tag = IWaypoint.Tag.getByName(name);
+ if (tag == null) {
+ throw new CommandInvalidStateException("Invalid tag, \"" + name + "\"");
+ }
IWaypoint[] waypoints = ForWaypoints.getWaypointsByTag(this.baritone, tag);
for (IWaypoint waypoint : waypoints) {
ForWaypoints.waypoints(this.baritone).removeWaypoint(waypoint);
diff --git a/src/main/java/baritone/command/manager/CommandManager.java b/src/main/java/baritone/command/manager/CommandManager.java
index 4c33226d3..8712165f1 100644
--- a/src/main/java/baritone/command/manager/CommandManager.java
+++ b/src/main/java/baritone/command/manager/CommandManager.java
@@ -21,6 +21,7 @@ import baritone.Baritone;
import baritone.api.IBaritone;
import baritone.api.command.ICommand;
import baritone.api.command.argument.ICommandArgument;
+import baritone.api.command.exception.CommandException;
import baritone.api.command.exception.CommandUnhandledException;
import baritone.api.command.exception.ICommandException;
import baritone.api.command.helpers.TabCompleteHelper;
@@ -153,9 +154,12 @@ public class CommandManager implements ICommandManager {
private Stream tabComplete() {
try {
return this.command.tabComplete(this.label, this.args);
+ } catch (CommandException ignored) {
+ // NOP
} catch (Throwable t) {
- return Stream.empty();
+ t.printStackTrace();
}
+ return Stream.empty();
}
}
}
diff --git a/src/main/java/baritone/event/GameEventHandler.java b/src/main/java/baritone/event/GameEventHandler.java
index b7853e360..d716ff849 100644
--- a/src/main/java/baritone/event/GameEventHandler.java
+++ b/src/main/java/baritone/event/GameEventHandler.java
@@ -23,12 +23,17 @@ import baritone.api.event.events.type.EventState;
import baritone.api.event.listener.IEventBus;
import baritone.api.event.listener.IGameEventListener;
import baritone.api.utils.Helper;
+import baritone.api.utils.Pair;
+import baritone.cache.CachedChunk;
import baritone.cache.WorldProvider;
import baritone.utils.BlockStateInterface;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.LevelChunk;
+
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
-import net.minecraft.world.level.Level;
-import net.minecraft.world.level.chunk.LevelChunk;
/**
* @author Brady
@@ -59,6 +64,11 @@ public final class GameEventHandler implements IEventBus, Helper {
listeners.forEach(l -> l.onTick(event));
}
+ @Override
+ public void onPostTick(TickEvent event) {
+ listeners.forEach(l -> l.onPostTick(event));
+ }
+
@Override
public final void onPlayerUpdate(PlayerUpdateEvent event) {
listeners.forEach(l -> l.onPlayerUpdate(event));
@@ -75,13 +85,10 @@ public final class GameEventHandler implements IEventBus, Helper {
}
@Override
- public final void onChunkEvent(ChunkEvent event) {
+ public void onChunkEvent(ChunkEvent event) {
EventState state = event.getState();
ChunkEvent.Type type = event.getType();
- boolean isPostPopulate = state == EventState.POST
- && (type == ChunkEvent.Type.POPULATE_FULL || type == ChunkEvent.Type.POPULATE_PARTIAL);
-
Level world = baritone.getPlayerContext().world();
// Whenever the server sends us to another dimension, chunks are unloaded
@@ -91,7 +98,7 @@ public final class GameEventHandler implements IEventBus, Helper {
&& type == ChunkEvent.Type.UNLOAD
&& world.getChunkSource().getChunk(event.getX(), event.getZ(), null, false) != null;
- if (isPostPopulate || isPreUnload) {
+ if (event.isPostPopulate() || isPreUnload) {
baritone.getWorldProvider().ifWorldLoaded(worldData -> {
LevelChunk chunk = world.getChunk(event.getX(), event.getZ());
worldData.getCachedWorld().queueForPacking(chunk);
@@ -102,6 +109,25 @@ public final class GameEventHandler implements IEventBus, Helper {
listeners.forEach(l -> l.onChunkEvent(event));
}
+ @Override
+ public void onBlockChange(BlockChangeEvent event) {
+ if (Baritone.settings().repackOnAnyBlockChange.value) {
+ final boolean keepingTrackOf = event.getBlocks().stream()
+ .map(Pair::second).map(BlockState::getBlock)
+ .anyMatch(CachedChunk.BLOCKS_TO_KEEP_TRACK_OF::contains);
+
+ if (keepingTrackOf) {
+ baritone.getWorldProvider().ifWorldLoaded(worldData -> {
+ final Level world = baritone.getPlayerContext().world();
+ ChunkPos pos = event.getChunkPos();
+ worldData.getCachedWorld().queueForPacking(world.getChunk(pos.x, pos.z));
+ });
+ }
+ }
+
+ listeners.forEach(l -> l.onBlockChange(event));
+ }
+
@Override
public final void onRenderPass(RenderEvent event) {
listeners.forEach(l -> l.onRenderPass(event));
@@ -114,7 +140,7 @@ public final class GameEventHandler implements IEventBus, Helper {
if (event.getState() == EventState.POST) {
cache.closeWorld();
if (event.getWorld() != null) {
- cache.initWorld(event.getWorld().dimension(), event.getWorld().dimensionType());
+ cache.initWorld(event.getWorld());
}
}
diff --git a/src/main/java/baritone/pathing/movement/CalculationContext.java b/src/main/java/baritone/pathing/movement/CalculationContext.java
index c1cc0daf5..64aca2ceb 100644
--- a/src/main/java/baritone/pathing/movement/CalculationContext.java
+++ b/src/main/java/baritone/pathing/movement/CalculationContext.java
@@ -28,8 +28,8 @@ import baritone.utils.pathing.BetterWorldBorder;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Inventory;
-import net.minecraft.world.item.Items;
import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.Level;
@@ -66,11 +66,13 @@ public class CalculationContext {
public final boolean allowJumpAt256;
public final boolean allowParkourAscend;
public final boolean assumeWalkOnWater;
+ public boolean allowFallIntoLava;
public final int frostWalker;
public final boolean allowDiagonalDescend;
public final boolean allowDiagonalAscend;
public final boolean allowDownward;
- public final int maxFallHeightNoWater;
+ public int minFallHeight;
+ public int maxFallHeightNoWater;
public final int maxFallHeightBucket;
public final double waterWalkSpeed;
public final double breakBlockAdditionalCost;
@@ -91,8 +93,8 @@ public class CalculationContext {
this.baritone = baritone;
LocalPlayer player = baritone.getPlayerContext().player();
this.world = baritone.getPlayerContext().world();
- this.worldData = (WorldData) baritone.getWorldProvider().getCurrentWorld();
- this.bsi = new BlockStateInterface(world, worldData, forUseOnAnotherThread);
+ this.worldData = (WorldData) baritone.getPlayerContext().worldData();
+ this.bsi = new BlockStateInterface(baritone.getPlayerContext(), forUseOnAnotherThread);
this.toolSet = new ToolSet(player);
this.hasThrowaway = Baritone.settings().allowPlace.value && ((Baritone) baritone).getInventoryBehavior().hasGenericThrowaway();
this.hasWaterBucket = Baritone.settings().allowWaterBucketFall.value && Inventory.isHotbarSlot(player.getInventory().findSlotMatchingItem(STACK_BUCKET_WATER)) && world.dimension() != Level.NETHER;
@@ -105,10 +107,12 @@ public class CalculationContext {
this.allowJumpAt256 = Baritone.settings().allowJumpAt256.value;
this.allowParkourAscend = Baritone.settings().allowParkourAscend.value;
this.assumeWalkOnWater = Baritone.settings().assumeWalkOnWater.value;
+ this.allowFallIntoLava = false; // Super secret internal setting for ElytraBehavior
this.frostWalker = EnchantmentHelper.getEnchantmentLevel(Enchantments.FROST_WALKER, baritone.getPlayerContext().player());
this.allowDiagonalDescend = Baritone.settings().allowDiagonalDescend.value;
this.allowDiagonalAscend = Baritone.settings().allowDiagonalAscend.value;
this.allowDownward = Baritone.settings().allowDownward.value;
+ this.minFallHeight = 3; // Minimum fall height used by MovementFall
this.maxFallHeightNoWater = Baritone.settings().maxFallHeightNoWater.value;
this.maxFallHeightBucket = Baritone.settings().maxFallHeightBucket.value;
int depth = EnchantmentHelper.getDepthStrider(player);
diff --git a/src/main/java/baritone/pathing/movement/Movement.java b/src/main/java/baritone/pathing/movement/Movement.java
index d319cea62..739c8ee89 100644
--- a/src/main/java/baritone/pathing/movement/Movement.java
+++ b/src/main/java/baritone/pathing/movement/Movement.java
@@ -162,7 +162,7 @@ public abstract class Movement implements IMovement, MovementHelper {
if (!MovementHelper.canWalkThrough(ctx, blockPos)) { // can't break air, so don't try
somethingInTheWay = true;
MovementHelper.switchToBestToolFor(ctx, BlockStateInterface.get(ctx, blockPos));
- Optional reachable = RotationUtils.reachable(ctx.player(), blockPos, ctx.playerController().getBlockReachDistance());
+ Optional reachable = RotationUtils.reachable(ctx, blockPos, ctx.playerController().getBlockReachDistance());
if (reachable.isPresent()) {
Rotation rotTowardsBlock = reachable.get();
state.setTarget(new MovementState.MovementTarget(rotTowardsBlock, true));
diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java
index 2c642b0e3..f5c928cd4 100644
--- a/src/main/java/baritone/pathing/movement/MovementHelper.java
+++ b/src/main/java/baritone/pathing/movement/MovementHelper.java
@@ -650,9 +650,9 @@ public interface MovementHelper extends ActionCosts, Helper {
static void moveTowards(IPlayerContext ctx, MovementState state, BlockPos pos) {
state.setTarget(new MovementTarget(
- new Rotation(RotationUtils.calcRotationFromVec3d(ctx.playerHead(),
+ RotationUtils.calcRotationFromVec3d(ctx.playerHead(),
VecUtils.getBlockPosCenter(pos),
- ctx.playerRotations()).getYaw(), ctx.player().getXRot()),
+ ctx.playerRotations()).withPitch(ctx.playerRotations().getPitch()),
false
)).setInput(Input.MOVE_FORWARD, true);
}
@@ -759,7 +759,8 @@ public interface MovementHelper extends ActionCosts, Helper {
double faceY = (placeAt.getY() + against1.getY() + 0.5D) * 0.5D;
double faceZ = (placeAt.getZ() + against1.getZ() + 1.0D) * 0.5D;
Rotation place = RotationUtils.calcRotationFromVec3d(wouldSneak ? RayTraceUtils.inferSneakingEyePosition(ctx.player()) : ctx.playerHead(), new Vec3(faceX, faceY, faceZ), ctx.playerRotations());
- HitResult res = RayTraceUtils.rayTraceTowards(ctx.player(), place, ctx.playerController().getBlockReachDistance(), wouldSneak);
+ Rotation actual = baritone.getLookBehavior().getAimProcessor().peekRotation(place);
+ HitResult res = RayTraceUtils.rayTraceTowards(ctx.player(), actual, ctx.playerController().getBlockReachDistance(), wouldSneak);
if (res != null && res.getType() == HitResult.Type.BLOCK && ((BlockHitResult) res).getBlockPos().equals(against1) && ((BlockHitResult) res).getBlockPos().relative(((BlockHitResult) res).getDirection()).equals(placeAt)) {
state.setTarget(new MovementTarget(place, true));
found = true;
diff --git a/src/main/java/baritone/pathing/movement/movements/MovementDescend.java b/src/main/java/baritone/pathing/movement/movements/MovementDescend.java
index ec069ae4f..07d6d7d01 100644
--- a/src/main/java/baritone/pathing/movement/movements/MovementDescend.java
+++ b/src/main/java/baritone/pathing/movement/movements/MovementDescend.java
@@ -20,7 +20,6 @@ package baritone.pathing.movement.movements;
import baritone.api.IBaritone;
import baritone.api.pathing.movement.MovementStatus;
import baritone.api.utils.BetterBlockPos;
-import baritone.api.utils.Rotation;
import baritone.api.utils.RotationUtils;
import baritone.api.utils.input.Input;
import baritone.pathing.movement.CalculationContext;
@@ -30,8 +29,6 @@ import baritone.pathing.movement.MovementState;
import baritone.utils.BlockStateInterface;
import baritone.utils.pathing.MutableMoveResult;
import com.google.common.collect.ImmutableSet;
-import java.util.Set;
-import net.minecraft.client.player.LocalPlayer;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
@@ -39,6 +36,8 @@ import net.minecraft.world.level.block.FallingBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
+import java.util.Set;
+
public class MovementDescend extends Movement {
private int numTicks = 0;
@@ -153,10 +152,11 @@ public class MovementDescend extends Movement {
// this check prevents it from getting the block at y=-1 and crashing
return false;
}
+ boolean reachedMinimum = fallHeight >= context.minFallHeight;
BlockState ontoBlock = context.get(destX, newY, destZ);
int unprotectedFallHeight = fallHeight - (y - effectiveStartHeight); // equal to fallHeight - y + effectiveFallHeight, which is equal to -newY + effectiveFallHeight, which is equal to effectiveFallHeight - newY
double tentativeCost = WALK_OFF_BLOCK_COST + FALL_N_BLOCKS_COST[unprotectedFallHeight] + frontBreak + costSoFar;
- if (MovementHelper.isWater(ontoBlock)) {
+ if (reachedMinimum && MovementHelper.isWater(ontoBlock)) {
if (!MovementHelper.canWalkThrough(context, destX, newY, destZ, ontoBlock)) {
return false;
}
@@ -177,6 +177,14 @@ public class MovementDescend extends Movement {
res.cost = tentativeCost;// TODO incorporate water swim up cost?
return false;
}
+ if (reachedMinimum && context.allowFallIntoLava && MovementHelper.isLava(ontoBlock)) {
+ // found a fall into lava
+ res.x = destX;
+ res.y = newY;
+ res.z = destZ;
+ res.cost = tentativeCost;
+ return false;
+ }
if (unprotectedFallHeight <= 11 && (ontoBlock.getBlock() == Blocks.VINE || ontoBlock.getBlock() == Blocks.LADDER)) {
// if fall height is greater than or equal to 11, we don't actually grab on to vines or ladders. the more you know
// this effectively "resets" our falling speed
@@ -194,7 +202,7 @@ public class MovementDescend extends Movement {
if (MovementHelper.isBottomSlab(ontoBlock)) {
return false; // falling onto a half slab is really glitchy, and can cause more fall damage than we'd expect
}
- if (unprotectedFallHeight <= context.maxFallHeightNoWater + 1) {
+ if (reachedMinimum && unprotectedFallHeight <= context.maxFallHeightNoWater + 1) {
// fallHeight = 4 means onto.up() is 3 blocks down, which is the max
res.x = destX;
res.y = newY + 1;
@@ -202,7 +210,7 @@ public class MovementDescend extends Movement {
res.cost = tentativeCost;
return false;
}
- if (context.hasWaterBucket && unprotectedFallHeight <= context.maxFallHeightBucket + 1) {
+ if (reachedMinimum && context.hasWaterBucket && unprotectedFallHeight <= context.maxFallHeightBucket + 1) {
res.x = destX;
res.y = newY + 1;// this is the block we're falling onto, so dest is +1
res.z = destZ;
@@ -233,11 +241,10 @@ public class MovementDescend extends Movement {
if (safeMode()) {
double destX = (src.getX() + 0.5) * 0.17 + (dest.getX() + 0.5) * 0.83;
double destZ = (src.getZ() + 0.5) * 0.17 + (dest.getZ() + 0.5) * 0.83;
- LocalPlayer player = ctx.player();
state.setTarget(new MovementState.MovementTarget(
- new Rotation(RotationUtils.calcRotationFromVec3d(ctx.playerHead(),
+ RotationUtils.calcRotationFromVec3d(ctx.playerHead(),
new Vec3(destX, dest.getY(), destZ),
- new Rotation(player.getYRot(), player.getXRot())).getYaw(), player.getXRot()),
+ ctx.playerRotations()).withPitch(ctx.playerRotations().getPitch()),
false
)).setInput(Input.MOVE_FORWARD, true);
return state;
diff --git a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java
index 50ecf930c..18c105d2b 100644
--- a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java
+++ b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java
@@ -32,14 +32,7 @@ import baritone.pathing.movement.MovementState;
import baritone.utils.BlockStateInterface;
import com.google.common.collect.ImmutableSet;
import net.minecraft.core.BlockPos;
-import net.minecraft.world.level.block.AirBlock;
-import net.minecraft.world.level.block.Block;
-import net.minecraft.world.level.block.Blocks;
-import net.minecraft.world.level.block.CarpetBlock;
-import net.minecraft.world.level.block.FallingBlock;
-import net.minecraft.world.level.block.FenceGateBlock;
-import net.minecraft.world.level.block.LadderBlock;
-import net.minecraft.world.level.block.SlabBlock;
+import net.minecraft.world.level.block.*;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.SlabType;
import net.minecraft.world.phys.Vec3;
@@ -196,9 +189,9 @@ public class MovementPillar extends Movement {
boolean vine = fromDown.getBlock() == Blocks.VINE;
Rotation rotation = RotationUtils.calcRotationFromVec3d(ctx.playerHead(),
VecUtils.getBlockPosCenter(positionToPlace),
- new Rotation(ctx.player().getYRot(), ctx.player().getXRot()));
+ ctx.playerRotations());
if (!ladder) {
- state.setTarget(new MovementState.MovementTarget(new Rotation(ctx.player().getYRot(), rotation.getPitch()), true));
+ state.setTarget(new MovementState.MovementTarget(ctx.playerRotations().withPitch(rotation.getPitch()), true));
}
boolean blockIsThere = MovementHelper.canWalkOn(ctx, src) || ladder;
@@ -257,7 +250,7 @@ public class MovementPillar extends Movement {
Block fr = frState.getBlock();
// TODO: Evaluate usage of getMaterial().isReplaceable()
if (!(fr instanceof AirBlock || frState.canBeReplaced())) {
- RotationUtils.reachable(ctx.player(), src, ctx.playerController().getBlockReachDistance())
+ RotationUtils.reachable(ctx, src, ctx.playerController().getBlockReachDistance())
.map(rot -> new MovementState.MovementTarget(rot, true))
.ifPresent(state::setTarget);
state.setInput(Input.JUMP, false); // breaking is like 5x slower when you're jumping
diff --git a/src/main/java/baritone/process/BuilderProcess.java b/src/main/java/baritone/process/BuilderProcess.java
index 62c9a3da0..c484140fc 100644
--- a/src/main/java/baritone/process/BuilderProcess.java
+++ b/src/main/java/baritone/process/BuilderProcess.java
@@ -30,10 +30,7 @@ import baritone.api.schematic.ISchematic;
import baritone.api.schematic.IStaticSchematic;
import baritone.api.schematic.SubstituteSchematic;
import baritone.api.schematic.format.ISchematicFormat;
-import baritone.api.utils.BetterBlockPos;
-import baritone.api.utils.RayTraceUtils;
-import baritone.api.utils.Rotation;
-import baritone.api.utils.RotationUtils;
+import baritone.api.utils.*;
import baritone.api.utils.input.Input;
import baritone.pathing.movement.CalculationContext;
import baritone.pathing.movement.Movement;
@@ -60,7 +57,14 @@ import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.item.context.UseOnContext;
-import net.minecraft.world.level.block.*;
+import net.minecraft.world.level.block.AirBlock;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.HorizontalDirectionalBlock;
+import net.minecraft.world.level.block.LiquidBlock;
+import net.minecraft.world.level.block.PipeBlock;
+import net.minecraft.world.level.block.RotatedPillarBlock;
+import net.minecraft.world.level.block.StairBlock;
+import net.minecraft.world.level.block.TrapDoorBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.phys.AABB;
@@ -164,7 +168,6 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
if (!format.isPresent()) {
return false;
}
-
ISchematic parsed;
try {
parsed = format.get().parse(new FileInputStream(schematic));
@@ -172,20 +175,22 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
e.printStackTrace();
return false;
}
-
- if (Baritone.settings().mapArtMode.value) {
- parsed = new MapArtSchematic((IStaticSchematic) parsed);
- }
-
- if (Baritone.settings().buildOnlySelection.value) {
- parsed = new SelectionSchematic(parsed, origin, baritone.getSelectionManager().getSelections());
- }
-
-
+ parsed = applyMapArtAndSelection(origin, (IStaticSchematic) parsed);
build(name, parsed, origin);
return true;
}
+ private ISchematic applyMapArtAndSelection(Vec3i origin, IStaticSchematic parsed) {
+ ISchematic schematic = parsed;
+ if (Baritone.settings().mapArtMode.value) {
+ schematic = new MapArtSchematic(parsed);
+ }
+ if (Baritone.settings().buildOnlySelection.value) {
+ schematic = new SelectionSchematic(schematic, origin, baritone.getSelectionManager().getSelections());
+ }
+ return schematic;
+ }
+
@Override
public void buildOpenSchematic() {
if (SchematicaHelper.isSchematicaPresent()) {
@@ -219,7 +224,8 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
try {
LitematicaSchematic schematic1 = new LitematicaSchematic(NbtIo.readCompressed(Files.newInputStream(LitematicaHelper.getSchematicFile(i).toPath())), false);
Vec3i correctedOrigin = LitematicaHelper.getCorrectedOrigin(schematic1, i);
- LitematicaSchematic schematic2 = LitematicaHelper.blackMagicFuckery(schematic1, i);
+ ISchematic schematic2 = LitematicaHelper.blackMagicFuckery(schematic1, i);
+ schematic2 = applyMapArtAndSelection(origin, (IStaticSchematic) schematic2);
build(name, schematic2, correctedOrigin);
} catch (Exception e) {
logDirect("Schematic File could not be loaded.");
@@ -283,7 +289,7 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
BlockState curr = bcc.bsi.get0(x, y, z);
if (!(curr.getBlock() instanceof AirBlock) && !(curr.getBlock() == Blocks.WATER || curr.getBlock() == Blocks.LAVA) && !valid(curr, desired, false)) {
BetterBlockPos pos = new BetterBlockPos(x, y, z);
- Optional rot = RotationUtils.reachable(ctx.player(), pos, ctx.playerController().getBlockReachDistance());
+ Optional rot = RotationUtils.reachable(ctx, pos, ctx.playerController().getBlockReachDistance());
if (rot.isPresent()) {
return Optional.of(new Tuple<>(pos, rot.get()));
}
@@ -362,9 +368,10 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
double placeY = placeAgainstPos.y + aabb.minY * placementMultiplier.y + aabb.maxY * (1 - placementMultiplier.y);
double placeZ = placeAgainstPos.z + aabb.minZ * placementMultiplier.z + aabb.maxZ * (1 - placementMultiplier.z);
Rotation rot = RotationUtils.calcRotationFromVec3d(RayTraceUtils.inferSneakingEyePosition(ctx.player()), new Vec3(placeX, placeY, placeZ), ctx.playerRotations());
- HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), rot, ctx.playerController().getBlockReachDistance(), true);
+ Rotation actualRot = baritone.getLookBehavior().getAimProcessor().peekRotation(rot);
+ HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), actualRot, ctx.playerController().getBlockReachDistance(), true);
if (result != null && result.getType() == HitResult.Type.BLOCK && ((BlockHitResult) result).getBlockPos().equals(placeAgainstPos) && ((BlockHitResult) result).getDirection() == against.getOpposite()) {
- OptionalInt hotbar = hasAnyItemThatWouldPlace(toPlace, result, rot);
+ OptionalInt hotbar = hasAnyItemThatWouldPlace(toPlace, result, actualRot);
if (hotbar.isPresent()) {
return Optional.of(new Placement(hotbar.getAsInt(), placeAgainstPos, against.getOpposite(), rot));
}
@@ -574,7 +581,10 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
for (int i = 9; i < 36; i++) {
for (BlockState desired : noValidHotbarOption) {
if (valid(approxPlaceable.get(i), desired, true)) {
- baritone.getInventoryBehavior().attemptToPutOnHotbar(i, usefulSlots::contains);
+ if (!baritone.getInventoryBehavior().attemptToPutOnHotbar(i, usefulSlots::contains)) {
+ // awaiting inventory move, so pause
+ return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE);
+ }
break outer;
}
}
@@ -700,7 +710,7 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
incorrectPositions.forEach(pos -> {
BlockState state = bcc.bsi.get0(pos);
if (state.getBlock() instanceof AirBlock) {
- if (approxPlaceable.contains(bcc.getSchematic(pos.x, pos.y, pos.z, state))) {
+ if (containsBlockState(approxPlaceable, bcc.getSchematic(pos.x, pos.y, pos.z, state))) {
placeable.add(pos);
} else {
BlockState desired = bcc.getSchematic(pos.x, pos.y, pos.z, state);
@@ -773,6 +783,28 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
return primary.heuristic(x, y, z);
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ JankyGoalComposite goal = (JankyGoalComposite) o;
+ return Objects.equals(primary, goal.primary)
+ && Objects.equals(fallback, goal.fallback);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = -1701079641;
+ hash = hash * 1196141026 + primary.hashCode();
+ hash = hash * -80327868 + fallback.hashCode();
+ return hash;
+ }
+
@Override
public String toString() {
return "JankyComposite Primary: " + primary + " Fallback: " + fallback;
@@ -794,6 +826,21 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
// but any other adjacent works for breaking, including inside or below
return super.isInGoal(x, y, z);
}
+
+ @Override
+ public String toString() {
+ return String.format(
+ "GoalBreak{x=%s,y=%s,z=%s}",
+ SettingsUtil.maybeCensor(x),
+ SettingsUtil.maybeCensor(y),
+ SettingsUtil.maybeCensor(z)
+ );
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() * 1636324008;
+ }
}
private Goal placementGoal(BlockPos pos, BuilderCalculationContext bcc) {
@@ -837,6 +884,7 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
this.allowSameLevel = allowSameLevel;
}
+ @Override
public boolean isInGoal(int x, int y, int z) {
if (x == this.x && y == this.y && z == this.z) {
return false;
@@ -853,10 +901,41 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
return super.isInGoal(x, y, z);
}
+ @Override
public double heuristic(int x, int y, int z) {
// prioritize lower y coordinates
return this.y * 100 + super.heuristic(x, y, z);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (!super.equals(o)) {
+ return false;
+ }
+
+ GoalAdjacent goal = (GoalAdjacent) o;
+ return allowSameLevel == goal.allowSameLevel
+ && Objects.equals(no, goal.no);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 806368046;
+ hash = hash * 1412661222 + super.hashCode();
+ hash = hash * 1730799370 + (int) BetterBlockPos.longHash(no.getX(), no.getY(), no.getZ());
+ hash = hash * 260592149 + (allowSameLevel ? -1314802005 : 1565710265);
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "GoalAdjacent{x=%s,y=%s,z=%s}",
+ SettingsUtil.maybeCensor(x),
+ SettingsUtil.maybeCensor(y),
+ SettingsUtil.maybeCensor(z)
+ );
+ }
}
public static class GoalPlace extends GoalBlock {
@@ -865,10 +944,26 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
super(placeAt.above());
}
+ @Override
public double heuristic(int x, int y, int z) {
// prioritize lower y coordinates
return this.y * 100 + super.heuristic(x, y, z);
}
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() * 1910811835;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "GoalPlace{x=%s,y=%s,z=%s}",
+ SettingsUtil.maybeCensor(x),
+ SettingsUtil.maybeCensor(y),
+ SettingsUtil.maybeCensor(z)
+ );
+ }
}
@Override
@@ -943,6 +1038,15 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil
return true;
}
+ private boolean containsBlockState(Collection states, BlockState state) {
+ for (BlockState testee : states) {
+ if (sameBlockstate(testee, state)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean valid(BlockState current, BlockState desired, boolean itemVerify) {
if (desired == null) {
return true;
diff --git a/src/main/java/baritone/process/CustomGoalProcess.java b/src/main/java/baritone/process/CustomGoalProcess.java
index 3ee3e7b7c..d0dca9cbf 100644
--- a/src/main/java/baritone/process/CustomGoalProcess.java
+++ b/src/main/java/baritone/process/CustomGoalProcess.java
@@ -36,6 +36,11 @@ public final class CustomGoalProcess extends BaritoneProcessHelper implements IC
*/
private Goal goal;
+ /**
+ * The most recent goal. Not invalidated upon {@link #onLostControl()}
+ */
+ private Goal mostRecentGoal;
+
/**
* The current process state.
*
@@ -50,6 +55,10 @@ public final class CustomGoalProcess extends BaritoneProcessHelper implements IC
@Override
public void setGoal(Goal goal) {
this.goal = goal;
+ this.mostRecentGoal = goal;
+ if (baritone.getElytraProcess().isActive()) {
+ baritone.getElytraProcess().pathTo(goal);
+ }
if (this.state == State.NONE) {
this.state = State.GOAL_SET;
}
@@ -68,6 +77,11 @@ public final class CustomGoalProcess extends BaritoneProcessHelper implements IC
return this.goal;
}
+ @Override
+ public Goal mostRecentGoal() {
+ return this.mostRecentGoal;
+ }
+
@Override
public boolean isActive() {
return this.state != State.NONE;
diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java
new file mode 100644
index 000000000..da5be3398
--- /dev/null
+++ b/src/main/java/baritone/process/ElytraProcess.java
@@ -0,0 +1,571 @@
+/*
+ * 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.process;
+
+import baritone.Baritone;
+import baritone.api.IBaritone;
+import baritone.api.event.events.*;
+import baritone.api.event.events.type.EventState;
+import baritone.api.event.listener.AbstractGameEventListener;
+import baritone.api.pathing.goals.Goal;
+import baritone.api.pathing.goals.GoalBlock;
+import baritone.api.pathing.goals.GoalXZ;
+import baritone.api.pathing.goals.GoalYLevel;
+import baritone.api.pathing.movement.IMovement;
+import baritone.api.pathing.path.IPathExecutor;
+import baritone.api.process.IBaritoneProcess;
+import baritone.api.process.IElytraProcess;
+import baritone.api.process.PathingCommand;
+import baritone.api.process.PathingCommandType;
+import baritone.api.utils.BetterBlockPos;
+import baritone.api.utils.Rotation;
+import baritone.api.utils.RotationUtils;
+import baritone.api.utils.input.Input;
+import baritone.pathing.movement.CalculationContext;
+import baritone.pathing.movement.movements.MovementFall;
+import baritone.process.elytra.ElytraBehavior;
+import baritone.process.elytra.NetherPathfinderContext;
+import baritone.process.elytra.NullElytraProcess;
+import baritone.utils.BaritoneProcessHelper;
+import baritone.utils.PathingCommandContext;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.NonNullList;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.AirBlock;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.phys.Vec3;
+
+import java.util.*;
+
+import static baritone.api.pathing.movement.ActionCosts.COST_INF;
+
+public class ElytraProcess extends BaritoneProcessHelper implements IBaritoneProcess, IElytraProcess, AbstractGameEventListener {
+ public State state;
+ private boolean goingToLandingSpot;
+ private BetterBlockPos landingSpot;
+ private boolean reachedGoal; // this basically just prevents potential notification spam
+ private Goal goal;
+ private ElytraBehavior behavior;
+ private boolean predictingTerrain;
+
+ private ElytraProcess(Baritone baritone) {
+ super(baritone);
+ baritone.getGameEventHandler().registerEventListener(this);
+ }
+
+ public static T create(final Baritone baritone) {
+ return (T) (NetherPathfinderContext.isSupported()
+ ? new ElytraProcess(baritone)
+ : new NullElytraProcess(baritone));
+ }
+
+ @Override
+ public boolean isActive() {
+ return this.behavior != null;
+ }
+
+ @Override
+ public void resetState() {
+ BlockPos destination = this.currentDestination();
+ this.onLostControl();
+ if (destination != null) {
+ this.pathTo(destination);
+ this.repackChunks();
+ }
+ }
+
+ private static final String AUTO_JUMP_FAILURE_MSG = "Failed to compute a walking path to a spot to jump off from. Consider starting from a higher location, near an overhang. Or, you can disable elytraAutoJump and just manually begin gliding.";
+
+ @Override
+ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) {
+ final long seedSetting = Baritone.settings().elytraNetherSeed.value;
+ if (seedSetting != this.behavior.context.getSeed()) {
+ logDirect("Nether seed changed, recalculating path");
+ this.resetState();
+ }
+ if (predictingTerrain != Baritone.settings().elytraPredictTerrain.value) {
+ logDirect("elytraPredictTerrain setting changed, recalculating path");
+ predictingTerrain = Baritone.settings().elytraPredictTerrain.value;
+ this.resetState();
+ }
+
+ this.behavior.onTick();
+
+ if (calcFailed) {
+ onLostControl();
+ logDirect(AUTO_JUMP_FAILURE_MSG);
+ return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL);
+ }
+
+ boolean safetyLanding = false;
+ if (ctx.player().isFallFlying() && shouldLandForSafety()) {
+ if (Baritone.settings().elytraAllowEmergencyLand.value) {
+ logDirect("Emergency landing - almost out of elytra durability or fireworks");
+ safetyLanding = true;
+ } else {
+ logDirect("almost out of elytra durability or fireworks, but I'm going to continue since elytraAllowEmergencyLand is false");
+ }
+ }
+ if (ctx.player().isFallFlying() && this.state != State.LANDING && (this.behavior.pathManager.isComplete() || safetyLanding)) {
+ final BetterBlockPos last = this.behavior.pathManager.path.getLast();
+ if (last != null && (ctx.player().position().distanceToSqr(last.getCenter()) < (48 * 48) || safetyLanding) && (!goingToLandingSpot || (safetyLanding && this.landingSpot == null))) {
+ logDirect("Path complete, picking a nearby safe landing spot...");
+ BetterBlockPos landingSpot = findSafeLandingSpot(ctx.playerFeet());
+ // if this fails we will just keep orbiting the last node until we run out of rockets or the user intervenes
+ if (landingSpot != null) {
+ this.pathTo0(landingSpot, true);
+ this.landingSpot = landingSpot;
+ }
+ this.goingToLandingSpot = true;
+ }
+
+ if (last != null && ctx.player().position().distanceToSqr(last.getCenter()) < 1) {
+ if (Baritone.settings().notificationOnPathComplete.value && !reachedGoal) {
+ logNotification("Pathing complete", false);
+ }
+ if (Baritone.settings().disconnectOnArrival.value && !reachedGoal) {
+ // don't be active when the user logs back in
+ this.onLostControl();
+ ctx.world().disconnect();
+ return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL);
+ }
+ reachedGoal = true;
+
+ // we are goingToLandingSpot and we are in the last node of the path
+ if (this.goingToLandingSpot) {
+ this.state = State.LANDING;
+ logDirect("Above the landing spot, landing...");
+ }
+ }
+ }
+
+ if (this.state == State.LANDING) {
+ final BetterBlockPos endPos = this.landingSpot != null ? this.landingSpot : behavior.pathManager.path.getLast();
+ if (ctx.player().isFallFlying() && endPos != null) {
+ Vec3 from = ctx.player().position();
+ Vec3 to = new Vec3(((double) endPos.x) + 0.5, from.y, ((double) endPos.z) + 0.5);
+ Rotation rotation = RotationUtils.calcRotationFromVec3d(from, to, ctx.playerRotations());
+ baritone.getLookBehavior().updateTarget(new Rotation(rotation.getYaw(), 0), false); // this will be overwritten, probably, by behavior tick
+
+ if (ctx.player().position().y < endPos.y - LANDING_COLUMN_HEIGHT) {
+ logDirect("bad landing spot, trying again...");
+ landingSpotIsBad(endPos);
+ }
+ }
+ }
+
+ if (ctx.player().isFallFlying()) {
+ behavior.landingMode = this.state == State.LANDING;
+ this.goal = null;
+ baritone.getInputOverrideHandler().clearAllKeys();
+ behavior.tick();
+ return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL);
+ } else if (this.state == State.LANDING) {
+ if (ctx.playerMotion().multiply(1, 0, 1).length() > 0.001) {
+ logDirect("Landed, but still moving, waiting for velocity to die down... ");
+ baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true);
+ return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE);
+ }
+ logDirect("Done :)");
+ baritone.getInputOverrideHandler().clearAllKeys();
+ this.onLostControl();
+ return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE);
+ }
+
+ if (this.state == State.FLYING || this.state == State.START_FLYING) {
+ this.state = ctx.player().onGround() && Baritone.settings().elytraAutoJump.value
+ ? State.LOCATE_JUMP
+ : State.START_FLYING;
+ }
+
+ if (this.state == State.LOCATE_JUMP) {
+ if (shouldLandForSafety()) {
+ logDirect("Not taking off, because elytra durability or fireworks are so low that I would immediately emergency land anyway.");
+ onLostControl();
+ return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL);
+ }
+ if (this.goal == null) {
+ this.goal = new GoalYLevel(31);
+ }
+ final IPathExecutor executor = baritone.getPathingBehavior().getCurrent();
+ if (executor != null && executor.getPath().getGoal() == this.goal) {
+ final IMovement fall = executor.getPath().movements().stream()
+ .filter(movement -> movement instanceof MovementFall)
+ .findFirst().orElse(null);
+
+ if (fall != null) {
+ final BetterBlockPos from = new BetterBlockPos(
+ (fall.getSrc().x + fall.getDest().x) / 2,
+ (fall.getSrc().y + fall.getDest().y) / 2,
+ (fall.getSrc().z + fall.getDest().z) / 2
+ );
+ behavior.pathManager.pathToDestination(from).whenComplete((result, ex) -> {
+ if (ex == null) {
+ this.state = State.GET_TO_JUMP;
+ return;
+ }
+ onLostControl();
+ });
+ this.state = State.PAUSE;
+ } else {
+ onLostControl();
+ logDirect(AUTO_JUMP_FAILURE_MSG);
+ return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL);
+ }
+ }
+ return new PathingCommandContext(this.goal, PathingCommandType.SET_GOAL_AND_PAUSE, new WalkOffCalculationContext(baritone));
+ }
+
+ // yucky
+ if (this.state == State.PAUSE) {
+ return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE);
+ }
+
+ if (this.state == State.GET_TO_JUMP) {
+ final IPathExecutor executor = baritone.getPathingBehavior().getCurrent();
+ final boolean canStartFlying = ctx.player().fallDistance > 1.0f
+ && !isSafeToCancel
+ && executor != null
+ && executor.getPath().movements().get(executor.getPosition()) instanceof MovementFall;
+
+ if (canStartFlying) {
+ this.state = State.START_FLYING;
+ } else {
+ return new PathingCommand(null, PathingCommandType.SET_GOAL_AND_PATH);
+ }
+ }
+
+ if (this.state == State.START_FLYING) {
+ if (!isSafeToCancel) {
+ // owned
+ baritone.getPathingBehavior().secretInternalSegmentCancel();
+ }
+ baritone.getInputOverrideHandler().clearAllKeys();
+ if (ctx.player().fallDistance > 1.0f) {
+ baritone.getInputOverrideHandler().setInputForceState(Input.JUMP, true);
+ }
+ }
+ return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL);
+ }
+
+ public void landingSpotIsBad(BetterBlockPos endPos) {
+ badLandingSpots.add(endPos);
+ goingToLandingSpot = false;
+ this.landingSpot = null;
+ this.state = State.FLYING;
+ }
+
+ @Override
+ public void onLostControl() {
+ this.goal = null;
+ this.goingToLandingSpot = false;
+ this.landingSpot = null;
+ this.reachedGoal = false;
+ this.state = State.START_FLYING; // TODO: null state?
+ destroyBehaviorAsync();
+ }
+
+ private void destroyBehaviorAsync() {
+ ElytraBehavior behavior = this.behavior;
+ if (behavior != null) {
+ this.behavior = null;
+ Baritone.getExecutor().execute(behavior::destroy);
+ }
+ }
+
+ @Override
+ public double priority() {
+ return 0; // higher priority than CustomGoalProcess
+ }
+
+ @Override
+ public String displayName0() {
+ return "Elytra - " + this.state.description;
+ }
+
+ @Override
+ public void repackChunks() {
+ if (this.behavior != null) {
+ this.behavior.repackChunks();
+ }
+ }
+
+ @Override
+ public BlockPos currentDestination() {
+ return this.behavior != null ? this.behavior.destination : null;
+ }
+
+ @Override
+ public void pathTo(BlockPos destination) {
+ this.pathTo0(destination, false);
+ }
+
+ private void pathTo0(BlockPos destination, boolean appendDestination) {
+ if (ctx.player() == null || ctx.player().level().dimension() != Level.NETHER) {
+ return;
+ }
+ this.onLostControl();
+ this.predictingTerrain = Baritone.settings().elytraPredictTerrain.value;
+ this.behavior = new ElytraBehavior(this.baritone, this, destination, appendDestination);
+ if (ctx.world() != null) {
+ this.behavior.repackChunks();
+ }
+ this.behavior.pathTo();
+ }
+
+ @Override
+ public void pathTo(Goal iGoal) {
+ final int x;
+ final int y;
+ final int z;
+ if (iGoal instanceof GoalXZ) {
+ GoalXZ goal = (GoalXZ) iGoal;
+ x = goal.getX();
+ y = 64;
+ z = goal.getZ();
+ } else if (iGoal instanceof GoalBlock) {
+ GoalBlock goal = (GoalBlock) iGoal;
+ x = goal.x;
+ y = goal.y;
+ z = goal.z;
+ } else {
+ throw new IllegalArgumentException("The goal must be a GoalXZ or GoalBlock");
+ }
+ if (y <= 0 || y >= 128) {
+ throw new IllegalArgumentException("The y of the goal is not between 0 and 128");
+ }
+ this.pathTo(new BlockPos(x, y, z));
+ }
+
+ private boolean shouldLandForSafety() {
+ ItemStack chest = ctx.player().getItemBySlot(EquipmentSlot.CHEST);
+ if (chest.getItem() != Items.ELYTRA || chest.getItem().getMaxDamage() - chest.getDamageValue() < Baritone.settings().elytraMinimumDurability.value) {
+ // elytrabehavior replaces when durability <= minimumDurability, so if durability < minimumDurability then we can reasonably assume that the elytra will soon be broken without replacement
+ return true;
+ }
+
+ NonNullList inv = ctx.player().getInventory().items;
+ int qty = 0;
+ for (int i = 0; i < 36; i++) {
+ if (ElytraBehavior.isFireworks(inv.get(i))) {
+ qty += inv.get(i).getCount();
+ }
+ }
+ if (qty <= Baritone.settings().elytraMinFireworksBeforeLanding.value) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isLoaded() {
+ return true;
+ }
+
+ @Override
+ public boolean isSafeToCancel() {
+ return !this.isActive() || !(this.state == State.FLYING || this.state == State.START_FLYING);
+ }
+
+ public enum State {
+ LOCATE_JUMP("Finding spot to jump off"),
+ PAUSE("Waiting for elytra path"),
+ GET_TO_JUMP("Walking to takeoff"),
+ START_FLYING("Begin flying"),
+ FLYING("Flying"),
+ LANDING("Landing");
+
+ public final String description;
+
+ State(String desc) {
+ this.description = desc;
+ }
+ }
+
+ @Override
+ public void onRenderPass(RenderEvent event) {
+ System.out.println(event.getPartialTicks() + " " + ctx.player().getXRot() + " " + ctx.player().getYRot() + " " + ctx.player().xRotO + " " + ctx.player().yRotO);
+
+ if (this.behavior != null) this.behavior.onRenderPass(event);
+ }
+
+ @Override
+ public void onWorldEvent(WorldEvent event) {
+ if (event.getWorld() != null && event.getState() == EventState.POST) {
+ // Exiting the world, just destroy
+ destroyBehaviorAsync();
+ }
+ }
+
+ @Override
+ public void onChunkEvent(ChunkEvent event) {
+ if (this.behavior != null) this.behavior.onChunkEvent(event);
+ }
+
+ @Override
+ public void onBlockChange(BlockChangeEvent event) {
+ if (this.behavior != null) this.behavior.onBlockChange(event);
+ }
+
+ @Override
+ public void onReceivePacket(PacketEvent event) {
+ if (this.behavior != null) this.behavior.onReceivePacket(event);
+ }
+
+ @Override
+ public void onPostTick(TickEvent event) {
+ IBaritoneProcess procThisTick = baritone.getPathingControlManager().mostRecentInControl().orElse(null);
+ if (this.behavior != null && procThisTick == this) this.behavior.onPostTick(event);
+ }
+
+ /**
+ * Custom calculation context which makes the player fall into lava
+ */
+ public static final class WalkOffCalculationContext extends CalculationContext {
+
+ public WalkOffCalculationContext(IBaritone baritone) {
+ super(baritone, true);
+ this.allowFallIntoLava = true;
+ this.minFallHeight = 8;
+ this.maxFallHeightNoWater = 10000;
+ }
+
+ @Override
+ public double costOfPlacingAt(int x, int y, int z, BlockState current) {
+ return COST_INF;
+ }
+
+ @Override
+ public double breakCostMultiplierAt(int x, int y, int z, BlockState current) {
+ return COST_INF;
+ }
+
+ @Override
+ public double placeBucketCost() {
+ return COST_INF;
+ }
+ }
+
+ private static boolean isInBounds(BlockPos pos) {
+ return pos.getY() >= 0 && pos.getY() < 128;
+ }
+
+ private boolean isSafeBlock(Block block) {
+ return block == Blocks.NETHERRACK || block == Blocks.GRAVEL || (block == Blocks.NETHER_BRICKS && Baritone.settings().elytraAllowLandOnNetherFortress.value);
+ }
+
+ private boolean isSafeBlock(BlockPos pos) {
+ return isSafeBlock(ctx.world().getBlockState(pos).getBlock());
+ }
+
+ private boolean isAtEdge(BlockPos pos) {
+ return !isSafeBlock(pos.north())
+ || !isSafeBlock(pos.south())
+ || !isSafeBlock(pos.east())
+ || !isSafeBlock(pos.west())
+ // corners
+ || !isSafeBlock(pos.north().west())
+ || !isSafeBlock(pos.north().east())
+ || !isSafeBlock(pos.south().west())
+ || !isSafeBlock(pos.south().east());
+ }
+
+ private boolean isColumnAir(BlockPos landingSpot, int minHeight) {
+ BlockPos.MutableBlockPos mut = new BlockPos.MutableBlockPos(landingSpot.getX(), landingSpot.getY(), landingSpot.getZ());
+ final int maxY = mut.getY() + minHeight;
+ for (int y = mut.getY() + 1; y <= maxY; y++) {
+ mut.set(mut.getX(), y, mut.getZ());
+ if (!(ctx.world().getBlockState(mut).getBlock() instanceof AirBlock)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean hasAirBubble(BlockPos pos) {
+ final int radius = 4; // Half of the full width, rounded down, as we're counting blocks in each direction from the center
+ BlockPos.MutableBlockPos mut = new BlockPos.MutableBlockPos();
+ for (int x = -radius; x <= radius; x++) {
+ for (int y = -radius; y <= radius; y++) {
+ for (int z = -radius; z <= radius; z++) {
+ mut.set(pos.getX() + x, pos.getY() + y, pos.getZ() + z);
+ if (!(ctx.world().getBlockState(mut).getBlock() instanceof AirBlock)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private BetterBlockPos checkLandingSpot(BlockPos pos, LongOpenHashSet checkedSpots) {
+ BlockPos.MutableBlockPos mut = new BlockPos.MutableBlockPos(pos.getX(), pos.getY(), pos.getZ());
+ while (mut.getY() >= 0) {
+ if (checkedSpots.contains(mut.asLong())) {
+ return null;
+ }
+ checkedSpots.add(mut.asLong());
+ Block block = ctx.world().getBlockState(mut).getBlock();
+
+ if (isSafeBlock(block)) {
+ if (!isAtEdge(mut)) {
+ return new BetterBlockPos(mut);
+ }
+ return null;
+ } else if (block != Blocks.AIR) {
+ return null;
+ }
+ mut.set(mut.getX(), mut.getY() - 1, mut.getZ());
+ }
+ return null; // void
+ }
+
+ private static final int LANDING_COLUMN_HEIGHT = 15;
+ private Set badLandingSpots = new HashSet<>();
+
+ private BetterBlockPos findSafeLandingSpot(BetterBlockPos start) {
+ Queue queue = new PriorityQueue<>(Comparator.comparingInt(pos -> (pos.x - start.x) * (pos.x - start.x) + (pos.z - start.z) * (pos.z - start.z)).thenComparingInt(pos -> -pos.y));
+ Set visited = new HashSet<>();
+ LongOpenHashSet checkedPositions = new LongOpenHashSet();
+ queue.add(start);
+
+ while (!queue.isEmpty()) {
+ BetterBlockPos pos = queue.poll();
+ if (ctx.world().isLoaded(pos) && isInBounds(pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) {
+ BetterBlockPos actualLandingSpot = checkLandingSpot(pos, checkedPositions);
+ if (actualLandingSpot != null && isColumnAir(actualLandingSpot, LANDING_COLUMN_HEIGHT) && hasAirBubble(actualLandingSpot.above(LANDING_COLUMN_HEIGHT)) && !badLandingSpots.contains(actualLandingSpot.above(LANDING_COLUMN_HEIGHT))) {
+ return actualLandingSpot.above(LANDING_COLUMN_HEIGHT);
+ }
+ if (visited.add(pos.north())) queue.add(pos.north());
+ if (visited.add(pos.east())) queue.add(pos.east());
+ if (visited.add(pos.south())) queue.add(pos.south());
+ if (visited.add(pos.west())) queue.add(pos.west());
+ if (visited.add(pos.above())) queue.add(pos.above());
+ if (visited.add(pos.below())) queue.add(pos.below());
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/baritone/process/FarmProcess.java b/src/main/java/baritone/process/FarmProcess.java
index b9308591c..c453beff2 100644
--- a/src/main/java/baritone/process/FarmProcess.java
+++ b/src/main/java/baritone/process/FarmProcess.java
@@ -18,8 +18,10 @@
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.GoalGetToBlock;
import baritone.api.pathing.goals.GoalComposite;
import baritone.api.process.IFarmProcess;
import baritone.api.process.PathingCommand;
@@ -29,7 +31,6 @@ import baritone.api.utils.RayTraceUtils;
import baritone.api.utils.Rotation;
import baritone.api.utils.RotationUtils;
import baritone.api.utils.input.Input;
-import baritone.cache.WorldScanner;
import baritone.pathing.movement.MovementHelper;
import baritone.utils.BaritoneProcessHelper;
import net.minecraft.core.BlockPos;
@@ -45,6 +46,7 @@ import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.BonemealableBlock;
import net.minecraft.world.level.block.CactusBlock;
+import net.minecraft.world.level.block.CocoaBlock;
import net.minecraft.world.level.block.CropBlock;
import net.minecraft.world.level.block.NetherWartBlock;
import net.minecraft.world.level.block.SugarCaneBlock;
@@ -52,6 +54,7 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -90,6 +93,7 @@ public final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
Items.POTATO,
Items.CARROT,
Items.NETHER_WART,
+ Items.COCOA_BEANS,
Blocks.SUGAR_CANE.asItem(),
Blocks.CACTUS.asItem()
);
@@ -123,6 +127,7 @@ public final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
PUMPKIN(Blocks.PUMPKIN, state -> true),
MELON(Blocks.MELON, state -> true),
NETHERWART(Blocks.NETHER_WART, state -> state.getValue(NetherWartBlock.AGE) >= 3),
+ COCOA(Blocks.COCOA, state -> state.getValue(CocoaBlock.AGE) >= 2),
SUGARCANE(Blocks.SUGAR_CANE, null) {
@Override
public boolean readyToHarvest(Level world, BlockPos pos, BlockState state) {
@@ -180,6 +185,10 @@ public final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
return !stack.isEmpty() && stack.getItem().equals(Items.NETHER_WART);
}
+ private boolean isCocoa(ItemStack stack) {
+ return !stack.isEmpty() && stack.getItem().equals(Items.COCOA_BEANS);
+ }
+
@Override
public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) {
ArrayList scan = new ArrayList<>();
@@ -188,13 +197,14 @@ public final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
}
if (Baritone.settings().replantCrops.value) {
scan.add(Blocks.FARMLAND);
+ scan.add(Blocks.JUNGLE_LOG);
if (Baritone.settings().replantNetherWart.value) {
scan.add(Blocks.SOUL_SAND);
}
}
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);
@@ -203,6 +213,7 @@ public final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
List openFarmland = new ArrayList<>();
List bonemealable = new ArrayList<>();
List openSoulsand = new ArrayList<>();
+ List openLog = new ArrayList<>();
for (BlockPos pos : locations) {
//check if the target block is out of range.
if (range != 0 && pos.distSqr(center) > range * range) {
@@ -223,6 +234,15 @@ public final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
}
continue;
}
+ if (state.getBlock() == Blocks.JUNGLE_LOG) {
+ for (Direction direction : Direction.Plane.HORIZONTAL) {
+ if (ctx.world().getBlockState(pos.relative(direction)).getBlock() instanceof AirBlock) {
+ openLog.add(pos);
+ break;
+ }
+ }
+ continue;
+ }
if (readyForHarvest(ctx.world(), pos, state)) {
toBreak.add(pos);
continue;
@@ -251,7 +271,7 @@ public final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
both.addAll(openSoulsand);
for (BlockPos pos : both) {
boolean soulsand = openSoulsand.contains(pos);
- Optional rot = RotationUtils.reachableOffset(ctx.player(), pos, new Vec3(pos.getX() + 0.5, pos.getY() + 1, pos.getZ() + 0.5), ctx.playerController().getBlockReachDistance(), false);
+ Optional rot = RotationUtils.reachableOffset(ctx, pos, new Vec3(pos.getX() + 0.5, pos.getY() + 1, pos.getZ() + 0.5), ctx.playerController().getBlockReachDistance(), false);
if (rot.isPresent() && isSafeToCancel && baritone.getInventoryBehavior().throwaway(true, soulsand ? this::isNetherWart : this::isPlantable)) {
HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), rot.get(), ctx.playerController().getBlockReachDistance());
if (result instanceof BlockHitResult && ((BlockHitResult) result).getDirection() == Direction.UP) {
@@ -263,6 +283,25 @@ public final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
}
}
}
+ for (BlockPos pos : openLog) {
+ for (Direction dir : Direction.Plane.HORIZONTAL) {
+ if (!(ctx.world().getBlockState(pos.relative(dir)).getBlock() instanceof AirBlock)) {
+ continue;
+ }
+ Vec3 faceCenter = Vec3.atCenterOf(pos).add(Vec3.atLowerCornerOf(dir.getNormal()).scale(0.5));
+ Optional rot = RotationUtils.reachableOffset(ctx, pos, faceCenter, ctx.playerController().getBlockReachDistance(), false);
+ if (rot.isPresent() && isSafeToCancel && baritone.getInventoryBehavior().throwaway(true, this::isCocoa)) {
+ HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), rot.get(), ctx.playerController().getBlockReachDistance());
+ if (result instanceof BlockHitResult && ((BlockHitResult) result).getDirection() == dir) {
+ baritone.getLookBehavior().updateTarget(rot.get(), true);
+ if (ctx.isLookingAt(pos)) {
+ baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true);
+ }
+ return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE);
+ }
+ }
+ }
+ }
for (BlockPos pos : bonemealable) {
Optional rot = RotationUtils.reachable(ctx, pos);
if (rot.isPresent() && isSafeToCancel && baritone.getInventoryBehavior().throwaway(true, this::isBoneMeal)) {
@@ -297,6 +336,15 @@ public final class FarmProcess extends BaritoneProcessHelper implements IFarmPro
goalz.add(new GoalBlock(pos.above()));
}
}
+ if (baritone.getInventoryBehavior().throwaway(false, this::isCocoa)) {
+ for (BlockPos pos : openLog) {
+ for (Direction direction : Direction.Plane.HORIZONTAL) {
+ if (ctx.world().getBlockState(pos.relative(direction)).getBlock() instanceof AirBlock) {
+ goalz.add(new GoalGetToBlock(pos.relative(direction)));
+ }
+ }
+ }
+ }
if (baritone.getInventoryBehavior().throwaway(false, this::isBoneMeal)) {
for (BlockPos pos : bonemealable) {
goalz.add(new GoalBlock(pos));
diff --git a/src/main/java/baritone/process/GetToBlockProcess.java b/src/main/java/baritone/process/GetToBlockProcess.java
index 88cc5bfa2..1352232d4 100644
--- a/src/main/java/baritone/process/GetToBlockProcess.java
+++ b/src/main/java/baritone/process/GetToBlockProcess.java
@@ -211,7 +211,7 @@ public final class GetToBlockProcess extends BaritoneProcessHelper implements IG
private boolean rightClick() {
for (BlockPos pos : knownLocations) {
- Optional reachable = RotationUtils.reachable(ctx.player(), pos, ctx.playerController().getBlockReachDistance());
+ Optional reachable = RotationUtils.reachable(ctx, pos, ctx.playerController().getBlockReachDistance());
if (reachable.isPresent()) {
baritone.getLookBehavior().updateTarget(reachable.get(), true);
if (knownLocations.contains(ctx.getSelectedBlock().orElse(null))) {
diff --git a/src/main/java/baritone/process/InventoryPauserProcess.java b/src/main/java/baritone/process/InventoryPauserProcess.java
new file mode 100644
index 000000000..fc9f4735a
--- /dev/null
+++ b/src/main/java/baritone/process/InventoryPauserProcess.java
@@ -0,0 +1,90 @@
+/*
+ * 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.process;
+
+import baritone.Baritone;
+import baritone.api.process.PathingCommand;
+import baritone.api.process.PathingCommandType;
+import baritone.utils.BaritoneProcessHelper;
+
+public class InventoryPauserProcess extends BaritoneProcessHelper {
+
+ boolean pauseRequestedLastTick;
+ boolean safeToCancelLastTick;
+ int ticksOfStationary;
+
+ public InventoryPauserProcess(Baritone baritone) {
+ super(baritone);
+ }
+
+ @Override
+ public boolean isActive() {
+ if (ctx.player() == null || ctx.world() == null) {
+ return false;
+ }
+ return true;
+ }
+
+ private double motion() {
+ return ctx.player().getDeltaMovement().multiply(1, 0, 1).length();
+ }
+
+ private boolean stationaryNow() {
+ return motion() < 0.00001;
+ }
+
+ public boolean stationaryForInventoryMove() {
+ pauseRequestedLastTick = true;
+ return safeToCancelLastTick && ticksOfStationary > 1;
+ }
+
+ @Override
+ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) {
+ //logDebug(pauseRequestedLastTick + " " + safeToCancelLastTick + " " + ticksOfStationary);
+ safeToCancelLastTick = isSafeToCancel;
+ if (pauseRequestedLastTick) {
+ pauseRequestedLastTick = false;
+ if (stationaryNow()) {
+ ticksOfStationary++;
+ }
+ return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE);
+ }
+ ticksOfStationary = 0;
+ return new PathingCommand(null, PathingCommandType.DEFER);
+ }
+
+ @Override
+ public void onLostControl() {
+
+ }
+
+ @Override
+ public String displayName0() {
+ return "inventory pauser";
+ }
+
+ @Override
+ public double priority() {
+ return 5.1; // slightly higher than backfill
+ }
+
+ @Override
+ public boolean isTemporary() {
+ return true;
+ }
+}
diff --git a/src/main/java/baritone/process/MineProcess.java b/src/main/java/baritone/process/MineProcess.java
index 3e5423b70..852e6ba3d 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;
@@ -25,7 +26,6 @@ import baritone.api.process.PathingCommandType;
import baritone.api.utils.*;
import baritone.api.utils.input.Input;
import baritone.cache.CachedChunk;
-import baritone.cache.WorldScanner;
import baritone.pathing.movement.CalculationContext;
import baritone.pathing.movement.MovementHelper;
import baritone.utils.BaritoneProcessHelper;
@@ -320,6 +320,26 @@ public final class MineProcess extends BaritoneProcessHelper implements IMinePro
int zDiff = z - this.z;
return GoalBlock.calculate(xDiff, yDiff < -1 ? yDiff + 2 : yDiff == -1 ? 0 : yDiff, zDiff);
}
+
+ @Override
+ public boolean equals(Object o) {
+ return super.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() * 393857768;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "GoalThreeBlocks{x=%s,y=%s,z=%s}",
+ SettingsUtil.maybeCensor(x),
+ SettingsUtil.maybeCensor(y),
+ SettingsUtil.maybeCensor(z)
+ );
+ }
}
public List droppedItemsScan() {
@@ -363,7 +383,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,
@@ -398,7 +418,7 @@ public final class MineProcess extends BaritoneProcessHelper implements IMinePro
// is an x-ray and it'll get caught
if (filter.has(bsi.get0(x, y, z))) {
BlockPos pos = new BlockPos(x, y, z);
- if ((Baritone.settings().legitMineIncludeDiagonals.value && knownOreLocations.stream().anyMatch(ore -> ore.distSqr(pos) <= 2 /* sq means this is pytha dist <= sqrt(2) */)) || RotationUtils.reachable(ctx.player(), pos, fakedBlockReachDistance).isPresent()) {
+ if ((Baritone.settings().legitMineIncludeDiagonals.value && knownOreLocations.stream().anyMatch(ore -> ore.distSqr(pos) <= 2 /* sq means this is pytha dist <= sqrt(2) */)) || RotationUtils.reachable(ctx, pos, fakedBlockReachDistance).isPresent()) {
knownOreLocations.add(pos);
}
}
@@ -438,6 +458,8 @@ public final class MineProcess extends BaritoneProcessHelper implements IMinePro
.filter(pos -> pos.getY() >= Baritone.settings().minYLevelWhileMining.value + ctx.world.dimensionType().minY())
+ .filter(pos -> pos.getY() <= Baritone.settings().maxYLevelWhileMining.value)
+
.filter(pos -> !blacklist.contains(pos))
.sorted(Comparator.comparingDouble(ctx.getBaritone().getPlayerContext().player().blockPosition()::distSqr))
diff --git a/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java b/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java
new file mode 100644
index 000000000..7db0e2d64
--- /dev/null
+++ b/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java
@@ -0,0 +1,54 @@
+/*
+ * 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.process.elytra;
+
+import dev.babbaj.pathfinder.NetherPathfinder;
+import dev.babbaj.pathfinder.Octree;
+
+/**
+ * @author Brady
+ */
+public final class BlockStateOctreeInterface {
+
+ private final NetherPathfinderContext context;
+ private final long contextPtr;
+ transient long chunkPtr;
+
+ // Guarantee that the first lookup will fetch the context by setting MAX_VALUE
+ private int prevChunkX = Integer.MAX_VALUE;
+ private int prevChunkZ = Integer.MAX_VALUE;
+
+ public BlockStateOctreeInterface(final NetherPathfinderContext context) {
+ this.context = context;
+ this.contextPtr = context.context;
+ }
+
+ public boolean get0(final int x, final int y, final int z) {
+ if ((y | (127 - y)) < 0) {
+ return false;
+ }
+ final int chunkX = x >> 4;
+ final int chunkZ = z >> 4;
+ if (this.chunkPtr == 0 | ((chunkX ^ this.prevChunkX) | (chunkZ ^ this.prevChunkZ)) != 0) {
+ this.prevChunkX = chunkX;
+ this.prevChunkZ = chunkZ;
+ this.chunkPtr = NetherPathfinder.getOrCreateChunk(this.contextPtr, chunkX, chunkZ);
+ }
+ return Octree.getBlock(this.chunkPtr, x & 0xF, y & 0x7F, z & 0xF);
+ }
+}
diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java
new file mode 100644
index 000000000..2b105b75e
--- /dev/null
+++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java
@@ -0,0 +1,1308 @@
+/*
+ * 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.process.elytra;
+
+import baritone.Baritone;
+import baritone.api.Settings;
+import baritone.api.behavior.look.IAimProcessor;
+import baritone.api.behavior.look.ITickableAimProcessor;
+import baritone.api.event.events.*;
+import baritone.api.pathing.goals.GoalBlock;
+import baritone.api.utils.*;
+import baritone.pathing.movement.MovementHelper;
+import baritone.process.ElytraProcess;
+import baritone.utils.BlockStateInterface;
+import baritone.utils.IRenderer;
+import baritone.utils.PathRenderer;
+import baritone.utils.accessor.IFireworkRocketEntity;
+import it.unimi.dsi.fastutil.floats.FloatArrayList;
+import it.unimi.dsi.fastutil.floats.FloatIterator;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.NonNullList;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket;
+import net.minecraft.util.Mth;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.entity.projectile.FireworkRocketEntity;
+import net.minecraft.world.inventory.ClickType;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.ClipContext;
+import net.minecraft.world.level.block.AirBlock;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.ChunkSource;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.HitResult;
+import net.minecraft.world.phys.Vec3;
+
+import java.awt.*;
+import java.util.List;
+import java.util.Queue;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.function.UnaryOperator;
+
+import static baritone.utils.BaritoneMath.fastCeil;
+import static baritone.utils.BaritoneMath.fastFloor;
+
+public final class ElytraBehavior implements Helper {
+ private final Baritone baritone;
+ private final IPlayerContext ctx;
+
+ // Render stuff
+ private final List> clearLines;
+ private final List> blockedLines;
+ private List simulationLine;
+ private BlockPos aimPos;
+ private List visiblePath;
+
+ // :sunglasses:
+ public final NetherPathfinderContext context;
+ public final PathManager pathManager;
+ private final ElytraProcess process;
+
+ /**
+ * Remaining cool-down ticks between firework usage
+ */
+ private int remainingFireworkTicks;
+
+ /**
+ * Remaining cool-down ticks after the player's position and rotation are reset by the server
+ */
+ private int remainingSetBackTicks;
+
+ public boolean landingMode;
+
+ /**
+ * The most recent minimum number of firework boost ticks, equivalent to {@code 10 * (1 + Flight)}
+ *
+ * Updated every time a firework is automatically used
+ */
+ private int minimumBoostTicks;
+
+ private boolean deployedFireworkLastTick;
+ private final int[] nextTickBoostCounter;
+
+ private BlockStateInterface bsi;
+ private final BlockStateOctreeInterface boi;
+ public final BlockPos destination;
+ private final boolean appendDestination;
+
+ private final ExecutorService solverExecutor;
+ private Future solver;
+ private Solution pendingSolution;
+ private boolean solveNextTick;
+
+ private long timeLastCacheCull = 0L;
+
+ // auto swap
+ private int invTickCountdown = 0;
+ private final Queue invTransactionQueue = new LinkedList<>();
+
+ public ElytraBehavior(Baritone baritone, ElytraProcess process, BlockPos destination, boolean appendDestination) {
+ this.baritone = baritone;
+ this.ctx = baritone.getPlayerContext();
+ this.clearLines = new CopyOnWriteArrayList<>();
+ this.blockedLines = new CopyOnWriteArrayList<>();
+ this.pathManager = this.new PathManager();
+ this.process = process;
+ this.destination = destination;
+ this.appendDestination = appendDestination;
+ this.solverExecutor = Executors.newSingleThreadExecutor();
+ this.nextTickBoostCounter = new int[2];
+
+ this.context = new NetherPathfinderContext(Baritone.settings().elytraNetherSeed.value);
+ this.boi = new BlockStateOctreeInterface(context);
+ }
+
+ public final class PathManager {
+
+ public NetherPath path;
+ private boolean completePath;
+ private boolean recalculating;
+
+ private int maxPlayerNear;
+ private int ticksNearUnchanged;
+ private int playerNear;
+
+ public PathManager() {
+ // lol imagine initializing fields normally
+ this.clear();
+ }
+
+ public void tick() {
+ // Recalculate closest path node
+ this.updatePlayerNear();
+ final int prevMaxNear = this.maxPlayerNear;
+ this.maxPlayerNear = Math.max(this.maxPlayerNear, this.playerNear);
+
+ if (this.maxPlayerNear == prevMaxNear && ctx.player().isFallFlying()) {
+ this.ticksNearUnchanged++;
+ } else {
+ this.ticksNearUnchanged = 0;
+ }
+
+ // Obstacles are more important than an incomplete path, handle those first.
+ this.pathfindAroundObstacles();
+ this.attemptNextSegment();
+ }
+
+ public CompletableFuture pathToDestination() {
+ return this.pathToDestination(ctx.playerFeet());
+ }
+
+ public CompletableFuture pathToDestination(final BlockPos from) {
+ final long start = System.nanoTime();
+ return this.path0(from, ElytraBehavior.this.destination, UnaryOperator.identity())
+ .thenRun(() -> {
+ final double distance = this.path.get(0).distanceTo(this.path.get(this.path.size() - 1));
+ if (this.completePath) {
+ logDirect(String.format("Computed path (%.1f blocks in %.4f seconds)", distance, (System.nanoTime() - start) / 1e9d));
+ } else {
+ logDirect(String.format("Computed segment (Next %.1f blocks in %.4f seconds)", distance, (System.nanoTime() - start) / 1e9d));
+ }
+ })
+ .whenComplete((result, ex) -> {
+ this.recalculating = false;
+ if (ex != null) {
+ final Throwable cause = ex.getCause();
+ if (cause instanceof PathCalculationException) {
+ logDirect("Failed to compute path to destination");
+ } else {
+ logUnhandledException(cause);
+ }
+ }
+ });
+ }
+
+ public CompletableFuture pathRecalcSegment(final int upToIncl) {
+ if (this.recalculating) {
+ throw new IllegalStateException("already recalculating");
+ }
+
+ this.recalculating = true;
+ final List after = this.path.subList(upToIncl + 1, this.path.size());
+ final boolean complete = this.completePath;
+
+ return this.path0(ctx.playerFeet(), this.path.get(upToIncl), segment -> segment.append(after.stream(), complete))
+ .whenComplete((result, ex) -> {
+ this.recalculating = false;
+ if (ex != null) {
+ final Throwable cause = ex.getCause();
+ if (cause instanceof PathCalculationException) {
+ logDirect("Failed to recompute segment");
+ } else {
+ logUnhandledException(cause);
+ }
+ }
+ });
+ }
+
+ public void pathNextSegment(final int afterIncl) {
+ if (this.recalculating) {
+ return;
+ }
+
+ this.recalculating = true;
+ final List before = this.path.subList(0, afterIncl + 1);
+ final long start = System.nanoTime();
+ final BetterBlockPos pathStart = this.path.get(afterIncl);
+
+ this.path0(pathStart, ElytraBehavior.this.destination, segment -> segment.prepend(before.stream()))
+ .thenRun(() -> {
+ final int recompute = this.path.size() - before.size() - 1;
+ final double distance = this.path.get(0).distanceTo(this.path.get(recompute));
+
+ if (this.completePath) {
+ logDirect(String.format("Computed path (%.1f blocks in %.4f seconds)", distance, (System.nanoTime() - start) / 1e9d));
+ } else {
+ logDirect(String.format("Computed segment (Next %.1f blocks in %.4f seconds)", distance, (System.nanoTime() - start) / 1e9d));
+ }
+ })
+ .whenComplete((result, ex) -> {
+ this.recalculating = false;
+ if (ex != null) {
+ final Throwable cause = ex.getCause();
+ if (cause instanceof PathCalculationException) {
+ logDirect("Failed to compute next segment");
+ if (ctx.player().distanceToSqr(pathStart.getCenter()) < 16 * 16) {
+ logDirect("Player is near the segment start, therefore repeating this calculation is pointless. Marking as complete");
+ completePath = true;
+ }
+ } else {
+ logUnhandledException(cause);
+ }
+ }
+ });
+ }
+
+ public void clear() {
+ this.path = NetherPath.emptyPath();
+ this.completePath = true;
+ this.recalculating = false;
+ this.playerNear = 0;
+ this.ticksNearUnchanged = 0;
+ this.maxPlayerNear = 0;
+ }
+
+ private void setPath(final UnpackedSegment segment) {
+ List path = segment.collect();
+ if (ElytraBehavior.this.appendDestination) {
+ BlockPos dest = ElytraBehavior.this.destination;
+ BlockPos last = !path.isEmpty() ? path.get(path.size() - 1) : null;
+ if (last != null && ElytraBehavior.this.clearView(Vec3.atLowerCornerOf(dest), Vec3.atLowerCornerOf(last), false)) {
+ path.add(new BetterBlockPos(dest));
+ } else {
+ logDirect("unable to land at " + ElytraBehavior.this.destination);
+ process.landingSpotIsBad(new BetterBlockPos(ElytraBehavior.this.destination));
+ }
+ }
+ this.path = new NetherPath(path);
+ this.completePath = segment.isFinished();
+ this.playerNear = 0;
+ this.ticksNearUnchanged = 0;
+ this.maxPlayerNear = 0;
+ }
+
+ public NetherPath getPath() {
+ return this.path;
+ }
+
+ public int getNear() {
+ return this.playerNear;
+ }
+
+ // mickey resigned
+ private CompletableFuture path0(BlockPos src, BlockPos dst, UnaryOperator operator) {
+ return ElytraBehavior.this.context.pathFindAsync(src, dst)
+ .thenApply(UnpackedSegment::from)
+ .thenApply(operator)
+ .thenAcceptAsync(this::setPath, ctx.minecraft()::execute);
+ }
+
+ private void pathfindAroundObstacles() {
+ if (this.recalculating) {
+ return;
+ }
+
+ int rangeStartIncl = playerNear;
+ int rangeEndExcl = playerNear;
+ while (rangeEndExcl < path.size() && ctx.world().isLoaded(path.get(rangeEndExcl))) {
+ rangeEndExcl++;
+ }
+ // rangeEndExcl now represents an index either not in the path, or just outside render distance
+ if (rangeStartIncl >= rangeEndExcl) {
+ // not loaded yet?
+ return;
+ }
+ final BetterBlockPos rangeStart = path.get(rangeStartIncl);
+ if (!ElytraBehavior.this.passable(rangeStart.x, rangeStart.y, rangeStart.z, false)) {
+ // we're in a wall
+ return; // previous iterations of this function SHOULD have fixed this by now :rage_cat:
+ }
+
+ if (ElytraBehavior.this.process.state != ElytraProcess.State.LANDING && this.ticksNearUnchanged > 100) {
+ this.pathRecalcSegment(rangeEndExcl - 1)
+ .thenRun(() -> {
+ logDirect("Recalculating segment, no progress in last 100 ticks");
+ });
+ this.ticksNearUnchanged = 0;
+ return;
+ }
+
+ boolean canSeeAny = false;
+ for (int i = rangeStartIncl; i < rangeEndExcl - 1; i++) {
+ if (ElytraBehavior.this.clearView(ctx.playerFeetAsVec(), this.path.getVec(i), false) || ElytraBehavior.this.clearView(ctx.playerHead(), this.path.getVec(i), false)) {
+ canSeeAny = true;
+ }
+ if (!ElytraBehavior.this.clearView(this.path.getVec(i), this.path.getVec(i + 1), false)) {
+ // obstacle. where do we return to pathing?
+ // find the next valid segment
+ final BetterBlockPos blockage = this.path.get(i);
+ final double distance = ctx.playerFeet().distanceTo(this.path.get(rangeEndExcl - 1));
+
+ final long start = System.nanoTime();
+ this.pathRecalcSegment(rangeEndExcl - 1)
+ .thenRun(() -> {
+ logDirect(String.format("Recalculated segment around path blockage near %s %s %s (next %.1f blocks in %.4f seconds)",
+ SettingsUtil.maybeCensor(blockage.x),
+ SettingsUtil.maybeCensor(blockage.y),
+ SettingsUtil.maybeCensor(blockage.z),
+ distance,
+ (System.nanoTime() - start) / 1e9d
+ ));
+ });
+ return;
+ }
+ }
+ if (!canSeeAny && rangeStartIncl < rangeEndExcl - 2 && process.state != ElytraProcess.State.GET_TO_JUMP) {
+ this.pathRecalcSegment(rangeEndExcl - 1).thenRun(() -> logDirect("Recalculated segment since no path points were visible"));
+ }
+ }
+
+ private void attemptNextSegment() {
+ if (this.recalculating) {
+ return;
+ }
+
+ final int last = this.path.size() - 1;
+ if (!this.completePath && ctx.world().isLoaded(this.path.get(last))) {
+ this.pathNextSegment(last);
+ }
+ }
+
+ public void updatePlayerNear() {
+ if (this.path.isEmpty()) {
+ return;
+ }
+
+ int index = this.playerNear;
+ final BetterBlockPos pos = ctx.playerFeet();
+ for (int i = index; i >= Math.max(index - 1000, 0); i -= 10) {
+ if (path.get(i).distanceSq(pos) < path.get(index).distanceSq(pos)) {
+ index = i; // intentional: this changes the bound of the loop
+ }
+ }
+ for (int i = index; i < Math.min(index + 1000, path.size()); i += 10) {
+ if (path.get(i).distanceSq(pos) < path.get(index).distanceSq(pos)) {
+ index = i; // intentional: this changes the bound of the loop
+ }
+ }
+ for (int i = index; i >= Math.max(index - 50, 0); i--) {
+ if (path.get(i).distanceSq(pos) < path.get(index).distanceSq(pos)) {
+ index = i; // intentional: this changes the bound of the loop
+ }
+ }
+ for (int i = index; i < Math.min(index + 50, path.size()); i++) {
+ if (path.get(i).distanceSq(pos) < path.get(index).distanceSq(pos)) {
+ index = i; // intentional: this changes the bound of the loop
+ }
+ }
+ this.playerNear = index;
+ }
+
+ public boolean isComplete() {
+ return this.completePath;
+ }
+ }
+
+ public void onRenderPass(RenderEvent event) {
+
+ final Settings settings = Baritone.settings();
+ if (this.visiblePath != null) {
+ PathRenderer.drawPath(event.getModelViewStack(), this.visiblePath, 0, Color.RED, false, 0, 0, 0.0D);
+ }
+ if (this.aimPos != null) {
+ PathRenderer.drawGoal(event.getModelViewStack(), ctx, new GoalBlock(this.aimPos), event.getPartialTicks(), Color.GREEN);
+ }
+ if (!this.clearLines.isEmpty() && settings.elytraRenderRaytraces.value) {
+ IRenderer.startLines(Color.GREEN, settings.pathRenderLineWidthPixels.value, settings.renderPathIgnoreDepth.value);
+ for (Pair line : this.clearLines) {
+ IRenderer.emitLine(event.getModelViewStack(), line.first(), line.second());
+ }
+ IRenderer.endLines(settings.renderPathIgnoreDepth.value);
+ }
+ if (!this.blockedLines.isEmpty() && Baritone.settings().elytraRenderRaytraces.value) {
+ IRenderer.startLines(Color.BLUE, settings.pathRenderLineWidthPixels.value, settings.renderPathIgnoreDepth.value);
+ for (Pair line : this.blockedLines) {
+ IRenderer.emitLine(event.getModelViewStack(), line.first(), line.second());
+ }
+ IRenderer.endLines(settings.renderPathIgnoreDepth.value);
+ }
+ if (this.simulationLine != null && Baritone.settings().elytraRenderSimulation.value) {
+ IRenderer.startLines(new Color(0x36CCDC), settings.pathRenderLineWidthPixels.value, settings.renderPathIgnoreDepth.value);
+ final Vec3 offset = ctx.player().getPosition(event.getPartialTicks());
+ for (int i = 0; i < this.simulationLine.size() - 1; i++) {
+ final Vec3 src = this.simulationLine.get(i).add(offset);
+ final Vec3 dst = this.simulationLine.get(i + 1).add(offset);
+ IRenderer.emitLine(event.getModelViewStack(), src, dst);
+ }
+ IRenderer.endLines(settings.renderPathIgnoreDepth.value);
+ }
+ }
+
+ public void onChunkEvent(ChunkEvent event) {
+ if (event.isPostPopulate() && this.context != null) {
+ final LevelChunk chunk = ctx.world().getChunk(event.getX(), event.getZ());
+ this.context.queueForPacking(chunk);
+ }
+ }
+
+ public void onBlockChange(BlockChangeEvent event) {
+ this.context.queueBlockUpdate(event);
+ }
+
+ public void onReceivePacket(PacketEvent event) {
+ if (event.getPacket() instanceof ClientboundPlayerPositionPacket) {
+ ctx.minecraft().execute(() -> {
+ this.remainingSetBackTicks = Baritone.settings().elytraFireworkSetbackUseDelay.value;
+ });
+ }
+ }
+
+ public void pathTo() {
+ if (!Baritone.settings().elytraAutoJump.value || ctx.player().isFallFlying()) {
+ this.pathManager.pathToDestination();
+ }
+ }
+
+ public void destroy() {
+ if (this.solver != null) {
+ this.solver.cancel(true);
+ }
+ this.solverExecutor.shutdown();
+ try {
+ while (!this.solverExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)) {}
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ this.context.destroy();
+ }
+
+ public void repackChunks() {
+ ChunkSource chunkProvider = ctx.world().getChunkSource();
+
+ BetterBlockPos playerPos = ctx.playerFeet();
+
+ int playerChunkX = playerPos.getX() >> 4;
+ int playerChunkZ = playerPos.getZ() >> 4;
+
+ int minX = playerChunkX - 40;
+ int minZ = playerChunkZ - 40;
+ int maxX = playerChunkX + 40;
+ int maxZ = playerChunkZ + 40;
+
+ for (int x = minX; x <= maxX; x++) {
+ for (int z = minZ; z <= maxZ; z++) {
+ LevelChunk chunk = chunkProvider.getChunk(x, z, false);
+
+ if (chunk != null && !chunk.isEmpty()) {
+ this.context.queueForPacking(chunk);
+ }
+ }
+ }
+ }
+
+ public void onTick() {
+ synchronized (this.context.cullingLock) {
+ this.onTick0();
+ }
+ final long now = System.currentTimeMillis();
+ if ((now - this.timeLastCacheCull) / 1000 > Baritone.settings().elytraTimeBetweenCacheCullSecs.value) {
+ this.context.queueCacheCulling(ctx.player().chunkPosition().x, ctx.player().chunkPosition().z, Baritone.settings().elytraCacheCullDistance.value, this.boi);
+ this.timeLastCacheCull = now;
+ }
+ }
+
+ private void onTick0() {
+ // Fetch the previous solution, regardless of if it's going to be used
+ this.pendingSolution = null;
+ if (this.solver != null) {
+ try {
+ this.pendingSolution = this.solver.get();
+ } catch (Exception ignored) {
+ // it doesn't matter if get() fails since the solution can just be recalculated synchronously
+ } finally {
+ this.solver = null;
+ }
+ }
+
+ tickInventoryTransactions();
+
+ // Certified mojang employee incident
+ if (this.remainingFireworkTicks > 0) {
+ this.remainingFireworkTicks--;
+ }
+ if (this.remainingSetBackTicks > 0) {
+ this.remainingSetBackTicks--;
+ }
+ if (!this.getAttachedFirework().isPresent()) {
+ this.minimumBoostTicks = 0;
+ }
+
+ // Reset rendered elements
+ this.clearLines.clear();
+ this.blockedLines.clear();
+ this.visiblePath = null;
+ this.simulationLine = null;
+ this.aimPos = null;
+
+ final List path = this.pathManager.getPath();
+ if (path.isEmpty()) {
+ return;
+ } else if (this.destination == null) {
+ this.pathManager.clear();
+ return;
+ }
+
+ // ctx AND context???? :DDD
+ this.bsi = new BlockStateInterface(ctx);
+ this.pathManager.tick();
+
+ final int playerNear = this.pathManager.getNear();
+ this.visiblePath = path.subList(
+ Math.max(playerNear - 30, 0),
+ Math.min(playerNear + 100, path.size())
+ );
+ }
+
+ /**
+ * Called by {@link baritone.process.ElytraProcess#onTick(boolean, boolean)} when the process is in control and the player is flying
+ */
+ public void tick() {
+ if (this.pathManager.getPath().isEmpty()) {
+ return;
+ }
+
+ trySwapElytra();
+
+ if (ctx.player().horizontalCollision) {
+ logDirect("hbonk");
+ }
+ if (ctx.player().verticalCollision) {
+ logDirect("vbonk");
+ }
+
+ final SolverContext solverContext = this.new SolverContext(false);
+ this.solveNextTick = true;
+
+ // If there's no previously calculated solution to use, or the context used at the end of last tick doesn't match this tick
+ final Solution solution;
+ if (this.pendingSolution == null || !this.pendingSolution.context.equals(solverContext)) {
+ solution = this.solveAngles(solverContext);
+ } else {
+ solution = this.pendingSolution;
+ }
+
+ if (this.deployedFireworkLastTick) {
+ this.nextTickBoostCounter[solverContext.boost.isBoosted() ? 1 : 0]++;
+ this.deployedFireworkLastTick = false;
+ }
+
+ if (solution == null) {
+ logDirect("no solution");
+ return;
+ }
+
+ baritone.getLookBehavior().updateTarget(solution.rotation, false);
+
+ if (!solution.solvedPitch) {
+ logDirect("no pitch solution, probably gonna crash in a few ticks LOL!!!");
+ return;
+ } else {
+ this.aimPos = new BetterBlockPos(solution.goingTo.x, solution.goingTo.y, solution.goingTo.z);
+ }
+
+ this.tickUseFireworks(
+ solution.context.start,
+ solution.goingTo,
+ solution.context.boost.isBoosted(),
+ solution.forceUseFirework
+ );
+ }
+
+ public void onPostTick(TickEvent event) {
+ if (event.getType() == TickEvent.Type.IN && this.solveNextTick) {
+ // We're at the end of the tick, the player's position likely updated and the closest path node could've
+ // changed. Updating it now will avoid unnecessary recalculation on the main thread.
+ this.pathManager.updatePlayerNear();
+
+ final SolverContext context = this.new SolverContext(true);
+ this.solver = this.solverExecutor.submit(() -> this.solveAngles(context));
+ this.solveNextTick = false;
+ }
+ }
+
+ private Solution solveAngles(final SolverContext context) {
+ final NetherPath path = context.path;
+ final int playerNear = landingMode ? path.size() - 1 : context.playerNear;
+ final Vec3 start = context.start;
+ Solution solution = null;
+
+ for (int relaxation = 0; relaxation < 3; relaxation++) { // try for a strict solution first, then relax more and more (if we're in a corner or near some blocks, it will have to relax its constraints a bit)
+ int[] heights = context.boost.isBoosted() ? new int[]{20, 10, 5, 0} : new int[]{0}; // attempt to gain height, if we can, so as not to waste the boost
+ int lookahead = relaxation == 0 ? 2 : 3; // ideally this would be expressed as a distance in blocks, rather than a number of voxel steps
+ //int minStep = Math.max(0, playerNear - relaxation);
+ int minStep = playerNear;
+
+ for (int i = Math.min(playerNear + 20, path.size() - 1); i >= minStep; i--) {
+ final List> candidates = new ArrayList<>();
+ for (int dy : heights) {
+ if (relaxation == 0 || i == minStep) {
+ // no interp
+ candidates.add(new Pair<>(path.getVec(i), dy));
+ } else if (relaxation == 1) {
+ final double[] interps = new double[]{1.0, 0.75, 0.5, 0.25};
+ for (double interp : interps) {
+ final Vec3 dest = interp == 1.0
+ ? path.getVec(i)
+ : path.getVec(i).scale(interp).add(path.getVec(i - 1).scale(1.0 - interp));
+ candidates.add(new Pair<>(dest, dy));
+ }
+ } else {
+ // Create a point along the segment every block
+ final Vec3 delta = path.getVec(i).subtract(path.getVec(i - 1));
+ final int steps = fastFloor(delta.length());
+ final Vec3 step = delta.normalize();
+ Vec3 stepped = path.getVec(i);
+ for (int interp = 0; interp < steps; interp++) {
+ candidates.add(new Pair<>(stepped, dy));
+ stepped = stepped.subtract(step);
+ }
+ }
+ }
+
+ for (final Pair candidate : candidates) {
+ final Integer augment = candidate.second();
+ Vec3 dest = candidate.first().add(0, augment, 0);
+ if (landingMode) {
+ dest = dest.add(0.5, 0.5, 0.5);
+ }
+
+ if (augment != 0) {
+ if (i + lookahead >= path.size()) {
+ continue;
+ }
+ if (start.distanceTo(dest) < 40) {
+ if (!this.clearView(dest, path.getVec(i + lookahead).add(0, augment, 0), false)
+ || !this.clearView(dest, path.getVec(i + lookahead), false)) {
+ // aka: don't go upwards if doing so would prevent us from being able to see the next position **OR** the modified next position
+ continue;
+ }
+ } else {
+ // but if it's far away, allow gaining altitude if we could lose it again by the time we get there
+ if (!this.clearView(dest, path.getVec(i), false)) {
+ continue;
+ }
+ }
+ }
+
+ final double minAvoidance = Baritone.settings().elytraMinimumAvoidance.value;
+ final Double growth = relaxation == 2 ? null
+ : relaxation == 0 ? 2 * minAvoidance : minAvoidance;
+
+ if (this.isHitboxClear(context, dest, growth)) {
+ // Yaw is trivial, just calculate the rotation required to face the destination
+ final float yaw = RotationUtils.calcRotationFromVec3d(start, dest, ctx.playerRotations()).getYaw();
+
+ final Pair pitch = this.solvePitch(context, dest, relaxation);
+ if (pitch == null) {
+ solution = new Solution(context, new Rotation(yaw, ctx.playerRotations().getPitch()), null, false, false);
+ continue;
+ }
+
+ // A solution was found with yaw AND pitch, so just immediately return it.
+ return new Solution(context, new Rotation(yaw, pitch.first()), dest, true, pitch.second());
+ }
+ }
+ }
+ }
+ return solution;
+ }
+
+ private void tickUseFireworks(final Vec3 start, final Vec3 goingTo, final boolean isBoosted, final boolean forceUseFirework) {
+ if (this.remainingSetBackTicks > 0) {
+ logDebug("waiting for elytraFireworkSetbackUseDelay: " + this.remainingSetBackTicks);
+ return;
+ }
+ if (this.landingMode) {
+ return;
+ }
+ final boolean useOnDescend = !Baritone.settings().elytraConserveFireworks.value || ctx.player().position().y < goingTo.y + 5;
+ final double currentSpeed = new Vec3(
+ ctx.player().getDeltaMovement().x,
+ // ignore y component if we are BOTH below where we want to be AND descending
+ ctx.player().position().y < goingTo.y ? Math.max(0, ctx.player().getDeltaMovement().y) : ctx.player().getDeltaMovement().y,
+ ctx.player().getDeltaMovement().z
+ ).lengthSqr();
+
+ final double elytraFireworkSpeed = Baritone.settings().elytraFireworkSpeed.value;
+ if (this.remainingFireworkTicks <= 0 && (forceUseFirework || (!isBoosted
+ && useOnDescend
+ && (ctx.player().position().y < goingTo.y - 5 || start.distanceTo(new Vec3(goingTo.x + 0.5, ctx.player().position().y, goingTo.z + 0.5)) > 5) // UGH!!!!!!!
+ && currentSpeed < elytraFireworkSpeed * elytraFireworkSpeed))
+ ) {
+ // Prioritize boosting fireworks over regular ones
+ // TODO: Take the minimum boost time into account?
+ if (!baritone.getInventoryBehavior().throwaway(true, ElytraBehavior::isBoostingFireworks) &&
+ !baritone.getInventoryBehavior().throwaway(true, ElytraBehavior::isFireworks)) {
+ logDirect("no fireworks");
+ return;
+ }
+ logDirect("attempting to use firework" + (forceUseFirework ? " (forced)" : ""));
+ ctx.playerController().processRightClick(ctx.player(), ctx.world(), InteractionHand.MAIN_HAND);
+ this.minimumBoostTicks = 10 * (1 + getFireworkBoost(ctx.player().getItemInHand(InteractionHand.MAIN_HAND)).orElse(0));
+ this.remainingFireworkTicks = 10;
+ this.deployedFireworkLastTick = true;
+ }
+ }
+
+ private final class SolverContext {
+
+ public final NetherPath path;
+ public final int playerNear;
+ public final Vec3 start;
+ public final Vec3 motion;
+ public final AABB boundingBox;
+ public final boolean ignoreLava;
+ public final FireworkBoost boost;
+ public final IAimProcessor aimProcessor;
+
+ /**
+ * Creates a new SolverContext using the current state of the path, player, and firework boost at the time of
+ * construction.
+ *
+ * @param async Whether the computation is being done asynchronously at the end of a game tick.
+ */
+ public SolverContext(boolean async) {
+ this.path = ElytraBehavior.this.pathManager.getPath();
+ this.playerNear = ElytraBehavior.this.pathManager.getNear();
+
+ this.start = ctx.playerFeetAsVec();
+ this.motion = ctx.playerMotion();
+ this.boundingBox = ctx.player().getBoundingBox();
+ this.ignoreLava = ctx.player().isInLava();
+
+ final Integer fireworkTicksExisted;
+ if (async && ElytraBehavior.this.deployedFireworkLastTick) {
+ final int[] counter = ElytraBehavior.this.nextTickBoostCounter;
+ fireworkTicksExisted = counter[1] > counter[0] ? 0 : null;
+ } else {
+ fireworkTicksExisted = ElytraBehavior.this.getAttachedFirework().map(e -> e.tickCount).orElse(null);
+ }
+ this.boost = new FireworkBoost(fireworkTicksExisted, ElytraBehavior.this.minimumBoostTicks);
+
+ ITickableAimProcessor aim = ElytraBehavior.this.baritone.getLookBehavior().getAimProcessor().fork();
+ if (async) {
+ // async computation is done at the end of a tick, advance by 1 to prepare for the next tick
+ aim.advance(1);
+ }
+ this.aimProcessor = aim;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || o.getClass() != SolverContext.class) {
+ return false;
+ }
+
+ SolverContext other = (SolverContext) o;
+ return this.path == other.path // Contents aren't modified, just compare by reference
+ && this.playerNear == other.playerNear
+ && Objects.equals(this.start, other.start)
+ && Objects.equals(this.motion, other.motion)
+ && Objects.equals(this.boundingBox, other.boundingBox)
+ && this.ignoreLava == other.ignoreLava
+ && Objects.equals(this.boost, other.boost);
+ }
+ }
+
+ private static final class FireworkBoost {
+
+ private final Integer fireworkTicksExisted;
+ private final int minimumBoostTicks;
+ private final int maximumBoostTicks;
+
+ /**
+ * @param fireworkTicksExisted The ticksExisted of the attached firework entity, or {@code null} if no entity.
+ * @param minimumBoostTicks The minimum number of boost ticks that the attached firework entity, if any, will
+ * provide.
+ */
+ public FireworkBoost(final Integer fireworkTicksExisted, final int minimumBoostTicks) {
+ this.fireworkTicksExisted = fireworkTicksExisted;
+
+ // this.lifetime = 10 * i + this.rand.nextInt(6) + this.rand.nextInt(7);
+ this.minimumBoostTicks = minimumBoostTicks;
+ this.maximumBoostTicks = minimumBoostTicks + 11;
+ }
+
+ public boolean isBoosted() {
+ return this.fireworkTicksExisted != null;
+ }
+
+ /**
+ * @return The guaranteed number of remaining ticks with boost
+ */
+ public int getGuaranteedBoostTicks() {
+ return this.isBoosted() ? Math.max(0, this.minimumBoostTicks - this.fireworkTicksExisted) : 0;
+ }
+
+ /**
+ * @return The maximum number of remaining ticks with boost
+ */
+ public int getMaximumBoostTicks() {
+ return this.isBoosted() ? Math.max(0, this.maximumBoostTicks - this.fireworkTicksExisted) : 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || o.getClass() != FireworkBoost.class) {
+ return false;
+ }
+
+ FireworkBoost other = (FireworkBoost) o;
+ if (!this.isBoosted() && !other.isBoosted()) {
+ return true;
+ }
+
+ return Objects.equals(this.fireworkTicksExisted, other.fireworkTicksExisted)
+ && this.minimumBoostTicks == other.minimumBoostTicks
+ && this.maximumBoostTicks == other.maximumBoostTicks;
+ }
+ }
+
+ private static final class PitchResult {
+
+ public final float pitch;
+ public final double dot;
+ public final List steps;
+
+ public PitchResult(float pitch, double dot, List steps) {
+ this.pitch = pitch;
+ this.dot = dot;
+ this.steps = steps;
+ }
+ }
+
+ private static final class Solution {
+
+ public final SolverContext context;
+ public final Rotation rotation;
+ public final Vec3 goingTo;
+ public final boolean solvedPitch;
+ public final boolean forceUseFirework;
+
+ public Solution(SolverContext context, Rotation rotation, Vec3 goingTo, boolean solvedPitch, boolean forceUseFirework) {
+ this.context = context;
+ this.rotation = rotation;
+ this.goingTo = goingTo;
+ this.solvedPitch = solvedPitch;
+ this.forceUseFirework = forceUseFirework;
+ }
+ }
+
+ public static boolean isFireworks(final ItemStack itemStack) {
+ if (itemStack.getItem() != Items.FIREWORK_ROCKET) {
+ return false;
+ }
+ // If it has NBT data, make sure it won't cause us to explode.
+ final CompoundTag compound = itemStack.getTagElement("Fireworks");
+ return compound == null || !compound.getAllKeys().contains("Explosions");
+ }
+
+ private static boolean isBoostingFireworks(final ItemStack itemStack) {
+ return getFireworkBoost(itemStack).isPresent();
+ }
+
+ private static OptionalInt getFireworkBoost(final ItemStack itemStack) {
+ if (isFireworks(itemStack)) {
+ final CompoundTag compound = itemStack.getTagElement("Fireworks");
+ if (compound != null && compound.getAllKeys().contains("Flight")) {
+ return OptionalInt.of(compound.getByte("Flight"));
+ }
+ }
+ return OptionalInt.empty();
+ }
+
+ private Optional getAttachedFirework() {
+ return ctx.entitiesStream()
+ .filter(x -> x instanceof FireworkRocketEntity)
+ .filter(x -> Objects.equals(((IFireworkRocketEntity) x).getBoostedEntity(), ctx.player()))
+ .map(x -> (FireworkRocketEntity) x)
+ .findFirst();
+ }
+
+ private boolean isHitboxClear(final SolverContext context, final Vec3 dest, final Double growAmount) {
+ final Vec3 start = context.start;
+ final boolean ignoreLava = context.ignoreLava;
+
+ if (!this.clearView(start, dest, ignoreLava)) {
+ return false;
+ }
+ if (growAmount == null) {
+ return true;
+ }
+
+ final AABB bb = context.boundingBox.inflate(growAmount);
+
+ final double ox = dest.x - start.x;
+ final double oy = dest.y - start.y;
+ final double oz = dest.z - start.z;
+
+ final double[] src = new double[]{
+ bb.minX, bb.minY, bb.minZ,
+ bb.minX, bb.minY, bb.maxZ,
+ bb.minX, bb.maxY, bb.minZ,
+ bb.minX, bb.maxY, bb.maxZ,
+ bb.maxX, bb.minY, bb.minZ,
+ bb.maxX, bb.minY, bb.maxZ,
+ bb.maxX, bb.maxY, bb.minZ,
+ bb.maxX, bb.maxY, bb.maxZ,
+ };
+ final double[] dst = new double[]{
+ bb.minX + ox, bb.minY + oy, bb.minZ + oz,
+ bb.minX + ox, bb.minY + oy, bb.maxZ + oz,
+ bb.minX + ox, bb.maxY + oy, bb.minZ + oz,
+ bb.minX + ox, bb.maxY + oy, bb.maxZ + oz,
+ bb.maxX + ox, bb.minY + oy, bb.minZ + oz,
+ bb.maxX + ox, bb.minY + oy, bb.maxZ + oz,
+ bb.maxX + ox, bb.maxY + oy, bb.minZ + oz,
+ bb.maxX + ox, bb.maxY + oy, bb.maxZ + oz,
+ };
+
+ // Use non-batching method without early failure
+ if (Baritone.settings().elytraRenderHitboxRaytraces.value) {
+ boolean clear = true;
+ for (int i = 0; i < 8; i++) {
+ final Vec3 s = new Vec3(src[i * 3], src[i * 3 + 1], src[i * 3 + 2]);
+ final Vec3 d = new Vec3(dst[i * 3], dst[i * 3 + 1], dst[i * 3 + 2]);
+ // Don't forward ignoreLava since the batch call doesn't care about it
+ if (!this.clearView(s, d, false)) {
+ clear = false;
+ }
+ }
+ return clear;
+ }
+
+ return this.context.raytrace(8, src, dst, NetherPathfinderContext.Visibility.ALL);
+ }
+
+ public boolean clearView(Vec3 start, Vec3 dest, boolean ignoreLava) {
+ final boolean clear;
+ if (!ignoreLava) {
+ // if start == dest then the cpp raytracer dies
+ clear = start.equals(dest) || this.context.raytrace(start, dest);
+ } else {
+ clear = ctx.world().clip(new ClipContext(start, dest, ClipContext.Block.COLLIDER, ClipContext.Fluid.ANY, ctx.player())).getType() == HitResult.Type.MISS;
+ }
+
+ if (Baritone.settings().elytraRenderRaytraces.value) {
+ (clear ? this.clearLines : this.blockedLines).add(new Pair<>(start, dest));
+ }
+ return clear;
+ }
+
+ private static FloatArrayList pitchesToSolveFor(final float goodPitch, final boolean desperate) {
+ final float minPitch = desperate ? -90 : Math.max(goodPitch - Baritone.settings().elytraPitchRange.value, -89);
+ final float maxPitch = desperate ? 90 : Math.min(goodPitch + Baritone.settings().elytraPitchRange.value, 89);
+
+ final FloatArrayList pitchValues = new FloatArrayList(fastCeil(maxPitch - minPitch) + 1);
+ for (float pitch = goodPitch; pitch <= maxPitch; pitch++) {
+ pitchValues.add(pitch);
+ }
+ for (float pitch = goodPitch - 1; pitch >= minPitch; pitch--) {
+ pitchValues.add(pitch);
+ }
+
+ return pitchValues;
+ }
+
+ @FunctionalInterface
+ private interface IntTriFunction {
+ T apply(int first, int second, int third);
+ }
+
+ private static final class IntTriple {
+ public final int first;
+ public final int second;
+ public final int third;
+
+ public IntTriple(int first, int second, int third) {
+ this.first = first;
+ this.second = second;
+ this.third = third;
+ }
+ }
+
+ private Pair solvePitch(final SolverContext context, final Vec3 goal, final int relaxation) {
+ final boolean desperate = relaxation == 2;
+ final float goodPitch = RotationUtils.calcRotationFromVec3d(context.start, goal, ctx.playerRotations()).getPitch();
+ final FloatArrayList pitches = pitchesToSolveFor(goodPitch, desperate);
+
+ final IntTriFunction solve = (ticks, ticksBoosted, ticksBoostDelay) ->
+ this.solvePitch(context, goal, relaxation, pitches.iterator(), ticks, ticksBoosted, ticksBoostDelay);
+
+ final List