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(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(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(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 tests = new ArrayList<>();
+
+ if (context.boost.isBoosted()) {
+ final int guaranteed = context.boost.getGuaranteedBoostTicks();
+ if (guaranteed == 0) {
+ // uncertain when boost will run out
+ final int lookahead = Math.max(4, 10 - context.boost.getMaximumBoostTicks());
+ tests.add(new IntTriple(lookahead, 1, 0));
+ } else if (guaranteed <= 5) {
+ // boost will run out within 5 ticks
+ tests.add(new IntTriple(guaranteed + 5, guaranteed, 0));
+ } else {
+ // there's plenty of guaranteed boost
+ tests.add(new IntTriple(guaranteed + 1, guaranteed, 0));
+ }
+ }
+
+ // Standard test, assume (not) boosted for entire duration
+ final int ticks = desperate ? 3 : context.boost.isBoosted() ? Math.max(5, context.boost.getGuaranteedBoostTicks()) : Baritone.settings().elytraSimulationTicks.value;
+ tests.add(new IntTriple(ticks, context.boost.isBoosted() ? ticks : 0, 0));
+
+ final Optional result = tests.stream()
+ .map(i -> solve.apply(i.first, i.second, i.third))
+ .filter(Objects::nonNull)
+ .findFirst();
+ if (result.isPresent()) {
+ return new Pair<>(result.get().pitch, false);
+ }
+
+ // If we used a firework would we be able to get out of the current situation??? perhaps
+ if (desperate) {
+ final List testsBoost = new ArrayList<>();
+ testsBoost.add(new IntTriple(ticks, 10, 3));
+ testsBoost.add(new IntTriple(ticks, 10, 2));
+ testsBoost.add(new IntTriple(ticks, 10, 1));
+
+ final Optional resultBoost = testsBoost.stream()
+ .map(i -> solve.apply(i.first, i.second, i.third))
+ .filter(Objects::nonNull)
+ .findFirst();
+ if (resultBoost.isPresent()) {
+ return new Pair<>(resultBoost.get().pitch, true);
+ }
+ }
+
+ return null;
+ }
+
+ private PitchResult solvePitch(final SolverContext context, final Vec3 goal, final int relaxation,
+ final FloatIterator pitches, final int ticks, final int ticksBoosted,
+ final int ticksBoostDelay) {
+ // we are at a certain velocity, but we have a target velocity
+ // what pitch would get us closest to our target velocity?
+ // yaw is easy so we only care about pitch
+
+ final Vec3 goalDelta = goal.subtract(context.start);
+ final Vec3 goalDirection = goalDelta.normalize();
+
+ final Deque bestResults = new ArrayDeque<>();
+
+ while (pitches.hasNext()) {
+ final float pitch = pitches.nextFloat();
+ final List displacement = this.simulate(
+ context,
+ goalDelta,
+ pitch,
+ ticks,
+ ticksBoosted,
+ ticksBoostDelay
+ );
+ if (displacement == null) {
+ continue;
+ }
+ final Vec3 last = displacement.get(displacement.size() - 1);
+ double goodness = goalDirection.dot(last.normalize());
+ if (landingMode) {
+ goodness = -goalDelta.subtract(last).length();
+ }
+ final PitchResult bestSoFar = bestResults.peek();
+ if (bestSoFar == null || goodness > bestSoFar.dot) {
+ bestResults.push(new PitchResult(pitch, goodness, displacement));
+ }
+ }
+
+ outer:
+ for (final PitchResult result : bestResults) {
+ if (relaxation < 2) {
+ // Ensure that the goal is visible along the entire simulated path
+ // Reverse order iteration since the last position is most likely to fail
+ for (int i = result.steps.size() - 1; i >= 1; i--) {
+ if (!clearView(context.start.add(result.steps.get(i)), goal, context.ignoreLava)) {
+ continue outer;
+ }
+ }
+ } else {
+ // Ensure that the goal is visible from the final position
+ if (!clearView(context.start.add(result.steps.get(result.steps.size() - 1)), goal, context.ignoreLava)) {
+ continue;
+ }
+ }
+
+ this.simulationLine = result.steps;
+ return result;
+ }
+ return null;
+ }
+
+ private List simulate(final SolverContext context, final Vec3 goalDelta, final float pitch, final int ticks,
+ final int ticksBoosted, final int ticksBoostDelay) {
+ final ITickableAimProcessor aimProcessor = context.aimProcessor.fork();
+ Vec3 delta = goalDelta;
+ Vec3 motion = context.motion;
+ AABB hitbox = context.boundingBox;
+ List displacement = new ArrayList<>(ticks + 1);
+ displacement.add(Vec3.ZERO);
+ int remainingTicksBoosted = ticksBoosted;
+
+ for (int i = 0; i < ticks; i++) {
+ final double cx = hitbox.minX + (hitbox.maxX - hitbox.minX) * 0.5D;
+ final double cz = hitbox.minZ + (hitbox.maxZ - hitbox.minZ) * 0.5D;
+ if (delta.lengthSqr() < 1) {
+ break;
+ }
+ final Rotation rotation = aimProcessor.nextRotation(
+ RotationUtils.calcRotationFromVec3d(Vec3.ZERO, delta, ctx.playerRotations()).withPitch(pitch)
+ );
+ final Vec3 lookDirection = RotationUtils.calcLookDirectionFromRotation(rotation);
+
+ motion = step(motion, lookDirection, rotation.getPitch());
+ delta = delta.subtract(motion);
+
+ // Collision box while the player is in motion, with additional padding for safety
+ final AABB inMotion = hitbox.inflate(motion.x, motion.y, motion.z).inflate(0.01);
+
+ int xmin = fastFloor(inMotion.minX);
+ int xmax = fastCeil(inMotion.maxX);
+ int ymin = fastFloor(inMotion.minY);
+ int ymax = fastCeil(inMotion.maxY);
+ int zmin = fastFloor(inMotion.minZ);
+ int zmax = fastCeil(inMotion.maxZ);
+ for (int x = xmin; x < xmax; x++) {
+ for (int y = ymin; y < ymax; y++) {
+ for (int z = zmin; z < zmax; z++) {
+ if (!this.passable(x, y, z, context.ignoreLava)) {
+ return null;
+ }
+ }
+ }
+ }
+
+ hitbox = hitbox.move(motion);
+ displacement.add(displacement.get(displacement.size() - 1).add(motion));
+
+ if (i >= ticksBoostDelay && remainingTicksBoosted-- > 0) {
+ // See EntityFireworkRocket
+ motion = motion.add(
+ lookDirection.x * 0.1 + (lookDirection.x * 1.5 - motion.x) * 0.5,
+ lookDirection.y * 0.1 + (lookDirection.y * 1.5 - motion.y) * 0.5,
+ lookDirection.z * 0.1 + (lookDirection.z * 1.5 - motion.z) * 0.5
+ );
+ }
+ }
+
+ return displacement;
+ }
+
+ private static Vec3 step(final Vec3 motion, final Vec3 lookDirection, final float pitch) {
+ double motionX = motion.x;
+ double motionY = motion.y;
+ double motionZ = motion.z;
+
+ float pitchRadians = pitch * RotationUtils.DEG_TO_RAD_F;
+ double pitchBase2 = Math.sqrt(lookDirection.x * lookDirection.x + lookDirection.z * lookDirection.z);
+ double flatMotion = Math.sqrt(motionX * motionX + motionZ * motionZ);
+ double thisIsAlwaysOne = lookDirection.length();
+ float pitchBase3 = Mth.cos(pitchRadians);
+ //System.out.println("always the same lol " + -pitchBase + " " + pitchBase3);
+ //System.out.println("always the same lol " + Math.abs(pitchBase3) + " " + pitchBase2);
+ //System.out.println("always 1 lol " + thisIsAlwaysOne);
+ pitchBase3 = (float) ((double) pitchBase3 * (double) pitchBase3 * Math.min(1, thisIsAlwaysOne / 0.4));
+ motionY += -0.08 + (double) pitchBase3 * 0.06;
+ if (motionY < 0 && pitchBase2 > 0) {
+ double speedModifier = motionY * -0.1 * (double) pitchBase3;
+ motionY += speedModifier;
+ motionX += lookDirection.x * speedModifier / pitchBase2;
+ motionZ += lookDirection.z * speedModifier / pitchBase2;
+ }
+ if (pitchRadians < 0) { // if you are looking down (below level)
+ double anotherSpeedModifier = flatMotion * (double) (-Mth.sin(pitchRadians)) * 0.04;
+ motionY += anotherSpeedModifier * 3.2;
+ motionX -= lookDirection.x * anotherSpeedModifier / pitchBase2;
+ motionZ -= lookDirection.z * anotherSpeedModifier / pitchBase2;
+ }
+ if (pitchBase2 > 0) { // this is always true unless you are looking literally straight up (let's just say the bot will never do that)
+ motionX += (lookDirection.x / pitchBase2 * flatMotion - motionX) * 0.1;
+ motionZ += (lookDirection.z / pitchBase2 * flatMotion - motionZ) * 0.1;
+ }
+ motionX *= 0.99f;
+ motionY *= 0.98f;
+ motionZ *= 0.99f;
+ //System.out.println(motionX + " " + motionY + " " + motionZ);
+
+ return new Vec3(motionX, motionY, motionZ);
+ }
+
+ private boolean passable(int x, int y, int z, boolean ignoreLava) {
+ if (ignoreLava) {
+ final Material mat = this.bsi.get0(x, y, z).getMaterial();
+ return mat == Material.AIR || mat == Material.LAVA;
+ } else {
+ return !this.boi.get0(x, y, z);
+ }
+ }
+
+ private void tickInventoryTransactions() {
+ if (invTickCountdown <= 0) {
+ Runnable r = invTransactionQueue.poll();
+ if (r != null) {
+ r.run();
+ invTickCountdown = Baritone.settings().ticksBetweenInventoryMoves.value;
+ }
+ }
+ if (invTickCountdown > 0) invTickCountdown--;
+ }
+
+ private void queueWindowClick(int windowId, int slotId, int button, ClickType type) {
+ invTransactionQueue.add(() -> ctx.playerController().windowClick(windowId, slotId, button, type, ctx.player()));
+ }
+
+ private int findGoodElytra() {
+ NonNullList invy = ctx.player().inventoryMenu.getItems();
+ for (int i = 0; i < invy.size(); i++) {
+ ItemStack slot = invy.get(i);
+ if (slot.getItem() == Items.ELYTRA && (slot.getItem().getMaxDamage() - slot.getDamageValue()) > Baritone.settings().elytraMinimumDurability.value) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private void trySwapElytra() {
+ if (!Baritone.settings().elytraAutoSwap.value || !invTransactionQueue.isEmpty()) {
+ return;
+ }
+
+ ItemStack chest = ctx.player().getItemBySlot(EquipmentSlot.CHEST);
+ if (chest.getItem() != Items.ELYTRA
+ || chest.getItem().getMaxDamage() - chest.getDamageValue() > Baritone.settings().elytraMinimumDurability.value) {
+ return;
+ }
+
+ int goodElytraSlot = findGoodElytra();
+ if (goodElytraSlot != -1) {
+ final int CHEST_SLOT = 6;
+ final int slotId = goodElytraSlot < 9 ? goodElytraSlot + 36 : goodElytraSlot;
+ queueWindowClick(ctx.player().inventoryMenu.containerId, slotId, 0, ClickType.PICKUP);
+ queueWindowClick(ctx.player().inventoryMenu.containerId, CHEST_SLOT, 0, ClickType.PICKUP);
+ queueWindowClick(ctx.player().inventoryMenu.containerId, slotId, 0, ClickType.PICKUP);
+ }
+ }
+}
diff --git a/src/main/java/baritone/process/elytra/NetherPath.java b/src/main/java/baritone/process/elytra/NetherPath.java
new file mode 100644
index 000000000..73531de4d
--- /dev/null
+++ b/src/main/java/baritone/process/elytra/NetherPath.java
@@ -0,0 +1,65 @@
+/*
+ * 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.api.utils.BetterBlockPos;
+import net.minecraft.world.phys.Vec3;
+
+import java.util.AbstractList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Brady
+ */
+public final class NetherPath extends AbstractList {
+
+ private static final NetherPath EMPTY_PATH = new NetherPath(Collections.emptyList());
+
+ private final List backing;
+
+ NetherPath(List backing) {
+ this.backing = backing;
+ }
+
+ @Override
+ public BetterBlockPos get(int index) {
+ return this.backing.get(index);
+ }
+
+ @Override
+ public int size() {
+ return this.backing.size();
+ }
+
+ /**
+ * @return The last position in the path, or {@code null} if empty
+ */
+ public BetterBlockPos getLast() {
+ return this.isEmpty() ? null : this.backing.get(this.backing.size() - 1);
+ }
+
+ public Vec3 getVec(int index) {
+ final BetterBlockPos pos = this.get(index);
+ return new Vec3(pos.x, pos.y, pos.z);
+ }
+
+ public static NetherPath emptyPath() {
+ return EMPTY_PATH;
+ }
+}
diff --git a/src/main/java/baritone/process/elytra/NetherPathfinderContext.java b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java
new file mode 100644
index 000000000..47ecdca96
--- /dev/null
+++ b/src/main/java/baritone/process/elytra/NetherPathfinderContext.java
@@ -0,0 +1,242 @@
+/*
+ * 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.event.events.BlockChangeEvent;
+import baritone.utils.accessor.IPalettedContainer;
+import dev.babbaj.pathfinder.NetherPathfinder;
+import dev.babbaj.pathfinder.Octree;
+import dev.babbaj.pathfinder.PathSegment;
+import net.minecraft.core.BlockPos;
+import net.minecraft.util.BitStorage;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.chunk.LevelChunk;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.chunk.PalettedContainer;
+import net.minecraft.world.phys.Vec3;
+
+import java.lang.ref.SoftReference;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Brady
+ */
+public final class NetherPathfinderContext {
+
+ private static final BlockState AIR_BLOCK_STATE = Blocks.AIR.defaultBlockState();
+ // This lock must be held while there are active pointers to chunks in java,
+ // but we just hold it for the entire tick so we don't have to think much about it.
+ public final Object cullingLock = new Object();
+
+ // Visible for access in BlockStateOctreeInterface
+ final long context;
+ private final long seed;
+ private final ExecutorService executor;
+
+ public NetherPathfinderContext(long seed) {
+ this.context = NetherPathfinder.newContext(seed);
+ this.seed = seed;
+ this.executor = Executors.newSingleThreadExecutor();
+ }
+
+ public void queueCacheCulling(int chunkX, int chunkZ, int maxDistanceBlocks, BlockStateOctreeInterface boi) {
+ this.executor.execute(() -> {
+ synchronized (this.cullingLock) {
+ boi.chunkPtr = 0L;
+ NetherPathfinder.cullFarChunks(this.context, chunkX, chunkZ, maxDistanceBlocks);
+ }
+ });
+ }
+
+ public void queueForPacking(final LevelChunk chunkIn) {
+ final SoftReference ref = new SoftReference<>(chunkIn);
+ this.executor.execute(() -> {
+ // TODO: Prioritize packing recent chunks and/or ones that the path goes through,
+ // and prune the oldest chunks per chunkPackerQueueMaxSize
+ final LevelChunk chunk = ref.get();
+ if (chunk != null) {
+ long ptr = NetherPathfinder.getOrCreateChunk(this.context, chunk.getPos().x, chunk.getPos().z);
+ writeChunkData(chunk, ptr);
+ }
+ });
+ }
+
+ public void queueBlockUpdate(BlockChangeEvent event) {
+ this.executor.execute(() -> {
+ ChunkPos chunkPos = event.getChunkPos();
+ long ptr = NetherPathfinder.getChunkPointer(this.context, chunkPos.x, chunkPos.z);
+ if (ptr == 0) return; // this shouldn't ever happen
+ event.getBlocks().forEach(pair -> {
+ BlockPos pos = pair.first();
+ if (pos.getY() >= 128) return;
+ boolean isSolid = pair.second() != AIR_BLOCK_STATE;
+ Octree.setBlock(ptr, pos.getX() & 15, pos.getY(), pos.getZ() & 15, isSolid);
+ });
+ });
+ }
+
+ public CompletableFuture pathFindAsync(final BlockPos src, final BlockPos dst) {
+ return CompletableFuture.supplyAsync(() -> {
+ final PathSegment segment = NetherPathfinder.pathFind(
+ this.context,
+ src.getX(), src.getY(), src.getZ(),
+ dst.getX(), dst.getY(), dst.getZ(),
+ true,
+ false,
+ 10000,
+ !Baritone.settings().elytraPredictTerrain.value
+ );
+ if (segment == null) {
+ throw new PathCalculationException("Path calculation failed");
+ }
+ return segment;
+ }, this.executor);
+ }
+
+ /**
+ * Performs a raytrace from the given start position to the given end position, returning {@code true} if there is
+ * visibility between the two points.
+ *
+ * @param startX The start X coordinate
+ * @param startY The start Y coordinate
+ * @param startZ The start Z coordinate
+ * @param endX The end X coordinate
+ * @param endY The end Y coordinate
+ * @param endZ The end Z coordinate
+ * @return {@code true} if there is visibility between the points
+ */
+ public boolean raytrace(final double startX, final double startY, final double startZ,
+ final double endX, final double endY, final double endZ) {
+ return NetherPathfinder.isVisible(this.context, NetherPathfinder.CACHE_MISS_SOLID, startX, startY, startZ, endX, endY, endZ);
+ }
+
+ /**
+ * Performs a raytrace from the given start position to the given end position, returning {@code true} if there is
+ * visibility between the two points.
+ *
+ * @param start The starting point
+ * @param end The ending point
+ * @return {@code true} if there is visibility between the points
+ */
+ public boolean raytrace(final Vec3 start, final Vec3 end) {
+ return NetherPathfinder.isVisible(this.context, NetherPathfinder.CACHE_MISS_SOLID, start.x, start.y, start.z, end.x, end.y, end.z);
+ }
+
+ public boolean raytrace(final int count, final double[] src, final double[] dst, final int visibility) {
+ switch (visibility) {
+ case Visibility.ALL:
+ return NetherPathfinder.isVisibleMulti(this.context, NetherPathfinder.CACHE_MISS_SOLID, count, src, dst, false) == -1;
+ case Visibility.NONE:
+ return NetherPathfinder.isVisibleMulti(this.context, NetherPathfinder.CACHE_MISS_SOLID, count, src, dst, true) == -1;
+ case Visibility.ANY:
+ return NetherPathfinder.isVisibleMulti(this.context, NetherPathfinder.CACHE_MISS_SOLID, count, src, dst, true) != -1;
+ default:
+ throw new IllegalArgumentException("lol");
+ }
+ }
+
+ public void raytrace(final int count, final double[] src, final double[] dst, final boolean[] hitsOut, final double[] hitPosOut) {
+ NetherPathfinder.raytrace(this.context, NetherPathfinder.CACHE_MISS_SOLID, count, src, dst, hitsOut, hitPosOut);
+ }
+
+ public void cancel() {
+ NetherPathfinder.cancel(this.context);
+ }
+
+ public void destroy() {
+ this.cancel();
+ // Ignore anything that was queued up, just shutdown the executor
+ this.executor.shutdownNow();
+
+ try {
+ while (!this.executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)) {}
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ NetherPathfinder.freeContext(this.context);
+ }
+
+ public long getSeed() {
+ return this.seed;
+ }
+
+ private static void writeChunkData(LevelChunk chunk, long ptr) {
+ try {
+ LevelChunkSection[] chunkInternalStorageArray = chunk.getSections();
+ for (int y0 = 0; y0 < 8; y0++) {
+ final LevelChunkSection extendedblockstorage = chunkInternalStorageArray[y0];
+ if (extendedblockstorage == null) {
+ continue;
+ }
+ final PalettedContainer bsc = extendedblockstorage.getStates();
+ final int airId = ((IPalettedContainer) bsc).getPalette().idFor(AIR_BLOCK_STATE);
+ // pasted from FasterWorldScanner
+ final BitStorage array = ((IPalettedContainer) bsc).getStorage();
+ if (array == null) continue;
+ final long[] longArray = array.getRaw();
+ final int arraySize = array.getSize();
+ int bitsPerEntry = array.getBits();
+ long maxEntryValue = (1L << bitsPerEntry) - 1L;
+
+ final int yReal = y0 << 4;
+ for (int idx = 0, kl = bitsPerEntry - 1; idx < arraySize; idx++, kl += bitsPerEntry) {
+ final int i = idx * bitsPerEntry;
+ final int j = i >> 6;
+ final int l = i & 63;
+ final int k = kl >> 6;
+ final long jl = longArray[j] >>> l;
+
+ final int id;
+ if (j == k) {
+ id = (int) (jl & maxEntryValue);
+ } else {
+ id = (int) ((jl | longArray[k] << (64 - l)) & maxEntryValue);
+ }
+ int x = (idx & 15);
+ int y = yReal + (idx >> 8);
+ int z = ((idx >> 4) & 15);
+ Octree.setBlock(ptr, x, y, z, id != airId);
+ }
+ }
+ Octree.setIsFromJava(ptr);
+ } catch (Exception e) {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static final class Visibility {
+
+ public static final int ALL = 0;
+ public static final int NONE = 1;
+ public static final int ANY = 2;
+
+ private Visibility() {}
+ }
+
+ public static boolean isSupported() {
+ return NetherPathfinder.isThisSystemSupported();
+ }
+}
diff --git a/src/main/java/baritone/process/elytra/NullElytraProcess.java b/src/main/java/baritone/process/elytra/NullElytraProcess.java
new file mode 100644
index 000000000..07d5fde0e
--- /dev/null
+++ b/src/main/java/baritone/process/elytra/NullElytraProcess.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.elytra;
+
+import baritone.Baritone;
+import baritone.api.pathing.goals.Goal;
+import baritone.api.process.IElytraProcess;
+import baritone.api.process.PathingCommand;
+import baritone.utils.BaritoneProcessHelper;
+import net.minecraft.core.BlockPos;
+
+/**
+ * @author Brady
+ */
+public final class NullElytraProcess extends BaritoneProcessHelper implements IElytraProcess {
+
+ public NullElytraProcess(Baritone baritone) {
+ super(baritone);
+ }
+
+ @Override
+ public void repackChunks() {
+ throw new UnsupportedOperationException("Called repackChunks() on NullElytraBehavior");
+ }
+
+ @Override
+ public BlockPos currentDestination() {
+ return null;
+ }
+
+ @Override
+ public void pathTo(BlockPos destination) {
+ throw new UnsupportedOperationException("Called pathTo() on NullElytraBehavior");
+ }
+
+ @Override
+ public void pathTo(Goal destination) {
+ throw new UnsupportedOperationException("Called pathTo() on NullElytraBehavior");
+ }
+
+ @Override
+ public void resetState() {
+
+ }
+
+ @Override
+ public boolean isActive() {
+ return false;
+ }
+
+ @Override
+ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) {
+ throw new UnsupportedOperationException("Called onTick on NullElytraProcess");
+ }
+
+ @Override
+ public void onLostControl() {
+
+ }
+
+ @Override
+ public String displayName0() {
+ return "NullElytraProcess";
+ }
+
+ @Override
+ public boolean isLoaded() {
+ return false;
+ }
+
+ @Override
+ public boolean isSafeToCancel() {
+ return true;
+ }
+}
diff --git a/src/main/java/baritone/process/elytra/PathCalculationException.java b/src/main/java/baritone/process/elytra/PathCalculationException.java
new file mode 100644
index 000000000..682ddd296
--- /dev/null
+++ b/src/main/java/baritone/process/elytra/PathCalculationException.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * @author Brady
+ */
+public final class PathCalculationException extends RuntimeException {
+
+ public PathCalculationException(final String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/baritone/process/elytra/UnpackedSegment.java b/src/main/java/baritone/process/elytra/UnpackedSegment.java
new file mode 100644
index 000000000..e50ab3235
--- /dev/null
+++ b/src/main/java/baritone/process/elytra/UnpackedSegment.java
@@ -0,0 +1,83 @@
+/*
+ * 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.api.utils.BetterBlockPos;
+import dev.babbaj.pathfinder.PathSegment;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author Brady
+ */
+public final class UnpackedSegment {
+
+ private final Stream path;
+ private final boolean finished;
+
+ public UnpackedSegment(Stream path, boolean finished) {
+ this.path = path;
+ this.finished = finished;
+ }
+
+ public UnpackedSegment append(Stream other, boolean otherFinished) {
+ // The new segment is only finished if the one getting added on is
+ return new UnpackedSegment(Stream.concat(this.path, other), otherFinished);
+ }
+
+ public UnpackedSegment prepend(Stream other) {
+ return new UnpackedSegment(Stream.concat(other, this.path), this.finished);
+ }
+
+ public List collect() {
+ final List path = this.path.collect(Collectors.toList());
+
+ // Remove backtracks
+ final Map positionFirstSeen = new HashMap<>();
+ for (int i = 0; i < path.size(); i++) {
+ BetterBlockPos pos = path.get(i);
+ if (positionFirstSeen.containsKey(pos)) {
+ int j = positionFirstSeen.get(pos);
+ while (i > j) {
+ path.remove(i);
+ i--;
+ }
+ } else {
+ positionFirstSeen.put(pos, i);
+ }
+ }
+
+ return path;
+ }
+
+ public boolean isFinished() {
+ return this.finished;
+ }
+
+ public static UnpackedSegment from(final PathSegment segment) {
+ return new UnpackedSegment(
+ Arrays.stream(segment.packed).mapToObj(BetterBlockPos::deserializeFromLong),
+ segment.finished
+ );
+ }
+}
diff --git a/src/main/java/baritone/utils/BaritoneMath.java b/src/main/java/baritone/utils/BaritoneMath.java
new file mode 100644
index 000000000..be546f248
--- /dev/null
+++ b/src/main/java/baritone/utils/BaritoneMath.java
@@ -0,0 +1,37 @@
+/*
+ * 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.utils;
+
+/**
+ * @author Brady
+ */
+public final class BaritoneMath {
+
+ private static final double FLOOR_DOUBLE_D = 1_073_741_824.0;
+ private static final int FLOOR_DOUBLE_I = 1_073_741_824;
+
+ private BaritoneMath() {}
+
+ public static int fastFloor(final double v) {
+ return (int) (v + FLOOR_DOUBLE_D) - FLOOR_DOUBLE_I;
+ }
+
+ public static int fastCeil(final double v) {
+ return FLOOR_DOUBLE_I - (int) (FLOOR_DOUBLE_D - v);
+ }
+}
diff --git a/src/main/java/baritone/utils/IRenderer.java b/src/main/java/baritone/utils/IRenderer.java
index ded456335..0721019c6 100644
--- a/src/main/java/baritone/utils/IRenderer.java
+++ b/src/main/java/baritone/utils/IRenderer.java
@@ -27,6 +27,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
@@ -40,7 +41,7 @@ public interface IRenderer {
TextureManager textureManager = Minecraft.getInstance().getTextureManager();
Settings settings = BaritoneAPI.getSettings();
- float[] color = new float[] {1.0F, 1.0F, 1.0F, 255.0F};
+ float[] color = new float[]{1.0F, 1.0F, 1.0F, 255.0F};
static void glColor(Color color, float alpha) {
float[] colorComponents = color.getColorComponents(null);
@@ -144,4 +145,16 @@ public interface IRenderer {
static void emitAABB(PoseStack stack, AABB aabb, double expand) {
emitAABB(stack, aabb.inflate(expand, expand, expand));
}
+
+ static void emitLine(Vec3 start, Vec3 end) {
+ emitLine(start.x, start.y, start.z, end.x, end.y, end.z);
+ }
+
+ static void emitLine(double x1, double y1, double z1, double x2, double y2, double z2) {
+ double vpX = renderManager.renderPosX();
+ double vpY = renderManager.renderPosY();
+ double vpZ = renderManager.renderPosZ();
+ buffer.vertex(x1 - vpX, y1 - vpY, z1 - vpZ).color(color[0], color[1], color[2], color[3]).endVertex();
+ buffer.vertex(x2 - vpX, y2 - vpY, z2 - vpZ).color(color[0], color[1], color[2], color[3]).endVertex();
+ }
}
diff --git a/src/main/java/baritone/utils/InputOverrideHandler.java b/src/main/java/baritone/utils/InputOverrideHandler.java
index 6e3d60dc9..38a32f515 100755
--- a/src/main/java/baritone/utils/InputOverrideHandler.java
+++ b/src/main/java/baritone/utils/InputOverrideHandler.java
@@ -107,7 +107,7 @@ public final class InputOverrideHandler extends Behavior implements IInputOverri
}
private boolean inControl() {
- for (Input input : new Input[]{Input.MOVE_FORWARD, Input.MOVE_BACK, Input.MOVE_LEFT, Input.MOVE_RIGHT, Input.SNEAK}) {
+ for (Input input : new Input[]{Input.MOVE_FORWARD, Input.MOVE_BACK, Input.MOVE_LEFT, Input.MOVE_RIGHT, Input.SNEAK, Input.JUMP}) {
if (isInputForcedDown(input)) {
return true;
}
diff --git a/src/main/java/baritone/utils/PathRenderer.java b/src/main/java/baritone/utils/PathRenderer.java
index e83d9c894..44c7716f6 100644
--- a/src/main/java/baritone/utils/PathRenderer.java
+++ b/src/main/java/baritone/utils/PathRenderer.java
@@ -19,7 +19,6 @@ package baritone.utils;
import baritone.api.BaritoneAPI;
import baritone.api.event.events.RenderEvent;
-import baritone.api.pathing.calc.IPath;
import baritone.api.pathing.goals.*;
import baritone.api.utils.BetterBlockPos;
import baritone.api.utils.IPlayerContext;
@@ -38,7 +37,6 @@ import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
-import org.joml.Matrix4f;
import java.awt.*;
import java.util.Arrays;
@@ -110,33 +108,36 @@ public final class PathRenderer implements IRenderer {
// Render the current path, if there is one
if (current != null && current.getPath() != null) {
int renderBegin = Math.max(current.getPosition() - 3, 0);
- drawPath(event.getModelViewStack(), current.getPath(), renderBegin, settings.colorCurrentPath.value, settings.fadePath.value, 10, 20);
+ drawPath(event.getModelViewStack(), current.getPath().positions(), renderBegin, settings.colorCurrentPath.value, settings.fadePath.value, 10, 20);
}
if (next != null && next.getPath() != null) {
- drawPath(event.getModelViewStack(), next.getPath(), 0, settings.colorNextPath.value, settings.fadePath.value, 10, 20);
+ drawPath(event.getModelViewStack(), next.getPath().positions(), 0, settings.colorNextPath.value, settings.fadePath.value, 10, 20);
}
// If there is a path calculation currently running, render the path calculation process
behavior.getInProgress().ifPresent(currentlyRunning -> {
currentlyRunning.bestPathSoFar().ifPresent(p -> {
- drawPath(event.getModelViewStack(), p, 0, settings.colorBestPathSoFar.value, settings.fadePath.value, 10, 20);
+ drawPath(event.getModelViewStack(), p.positions(), 0, settings.colorBestPathSoFar.value, settings.fadePath.value, 10, 20);
});
currentlyRunning.pathToMostRecentNodeConsidered().ifPresent(mr -> {
- drawPath(event.getModelViewStack(), mr, 0, settings.colorMostRecentConsidered.value, settings.fadePath.value, 10, 20);
+ drawPath(event.getModelViewStack(), mr.positions(), 0, settings.colorMostRecentConsidered.value, settings.fadePath.value, 10, 20);
drawManySelectionBoxes(event.getModelViewStack(), ctx.player(), Collections.singletonList(mr.getDest()), settings.colorMostRecentConsidered.value);
});
});
}
- private static void drawPath(PoseStack stack, IPath path, int startIndex, Color color, boolean fadeOut, int fadeStart0, int fadeEnd0) {
+ public static void drawPath(PoseStack stack, List positions, int startIndex, Color color, boolean fadeOut, int fadeStart0, int fadeEnd0) {
+ drawPath(stack, positions, startIndex, color, fadeOut, fadeStart0, fadeEnd0, 0.5D);
+ }
+
+ public static void drawPath(PoseStack stack, List positions, int startIndex, Color color, boolean fadeOut, int fadeStart0, int fadeEnd0, double offset) {
IRenderer.startLines(color, settings.pathRenderLineWidthPixels.value, settings.renderPathIgnoreDepth.value);
int fadeStart = fadeStart0 + startIndex;
int fadeEnd = fadeEnd0 + startIndex;
- List positions = path.positions();
for (int i = startIndex, next; i < positions.size() - 1; i = next) {
BetterBlockPos start = positions.get(i);
BetterBlockPos end = positions.get(next = i + 1);
@@ -166,34 +167,36 @@ public final class PathRenderer implements IRenderer {
IRenderer.glColor(color, alpha);
}
- emitPathLine(stack, start.x, start.y, start.z, end.x, end.y, end.z);
+ emitPathLine(stack, start.x, start.y, start.z, end.x, end.y, end.z, offset);
}
IRenderer.endLines(settings.renderPathIgnoreDepth.value);
}
- private static void emitPathLine(PoseStack stack, double x1, double y1, double z1, double x2, double y2, double z2) {
+ private static void emitPathLine(PoseStack stack, double x1, double y1, double z1, double x2, double y2, double z2, double offset) {
+ final double extraOffset = offset + 0.03D;
+
double vpX = posX();
double vpY = posY();
double vpZ = posZ();
boolean renderPathAsFrickinThingy = !settings.renderPathAsLine.value;
IRenderer.emitLine(stack,
- x1 + 0.5D - vpX, y1 + 0.5D - vpY, z1 + 0.5D - vpZ,
- x2 + 0.5D - vpX, y2 + 0.5D - vpY, z2 + 0.5D - vpZ
+ x1 + offset - vpX, y1 + offset - vpY, z1 + offset - vpZ,
+ x2 + offset - vpX, y2 + offset - vpY, z2 + offset - vpZ
);
if (renderPathAsFrickinThingy) {
IRenderer.emitLine(stack,
- x2 + 0.5D - vpX, y2 + 0.5D - vpY, z2 + 0.5D - vpZ,
- x2 + 0.5D - vpX, y2 + 0.53D - vpY, z2 + 0.5D - vpZ
+ x2 + offset - vpX, y2 + offset - vpY, z2 + offset - vpZ,
+ x2 + offset - vpX, y2 + extraOffset - vpY, z2 + offset - vpZ
);
IRenderer.emitLine(stack,
- x2 + 0.5D - vpX, y2 + 0.53D - vpY, z2 + 0.5D - vpZ,
- x1 + 0.5D - vpX, y1 + 0.53D - vpY, z1 + 0.5D - vpZ
+ x2 + offset - vpX, y2 + extraOffset - vpY, z2 + offset - vpZ,
+ x1 + offset - vpX, y1 + extraOffset - vpY, z1 + offset - vpZ
);
IRenderer.emitLine(stack,
- x1 + 0.5D - vpX, y1 + 0.53D - vpY, z1 + 0.5D - vpZ,
- x1 + 0.5D - vpX, y1 + 0.5D - vpY, z1 + 0.5D - vpZ
+ x1 + offset - vpX, y1 + extraOffset - vpY, z1 + offset - vpZ,
+ x1 + offset - vpX, y1 + offset - vpY, z1 + offset - vpZ
);
}
}
@@ -215,7 +218,7 @@ public final class PathRenderer implements IRenderer {
IRenderer.endLines(settings.renderSelectionBoxesIgnoreDepth.value);
}
- private static void drawGoal(PoseStack stack, IPlayerContext ctx, Goal goal, float partialTicks, Color color) {
+ public static void drawGoal(PoseStack stack, IPlayerContext ctx, Goal goal, float partialTicks, Color color) {
drawGoal(stack, ctx, goal, partialTicks, color, true);
}
diff --git a/src/main/java/baritone/utils/PathingControlManager.java b/src/main/java/baritone/utils/PathingControlManager.java
index 86350ff0b..3566cd23a 100644
--- a/src/main/java/baritone/utils/PathingControlManager.java
+++ b/src/main/java/baritone/utils/PathingControlManager.java
@@ -27,9 +27,10 @@ import baritone.api.process.PathingCommand;
import baritone.api.process.PathingCommandType;
import baritone.behavior.PathingBehavior;
import baritone.pathing.path.PathExecutor;
-import java.util.*;
import net.minecraft.core.BlockPos;
+import java.util.*;
+
public class PathingControlManager implements IPathingControlManager {
private final Baritone baritone;
@@ -98,6 +99,8 @@ public class PathingControlManager implements IPathingControlManager {
// get rid of the in progress stuff from the last process
}
switch (command.commandType) {
+ case SET_GOAL_AND_PAUSE:
+ p.secretInternalSetGoalAndPath(command);
case REQUEST_PAUSE:
p.requestPause();
break;
@@ -106,10 +109,6 @@ public class PathingControlManager implements IPathingControlManager {
p.cancelSegmentIfSafe();
break;
case FORCE_REVALIDATE_GOAL_AND_PATH:
- if (!p.isPathing() && !p.getInProgress().isPresent()) {
- p.secretInternalSetGoalAndPath(command);
- }
- break;
case REVALIDATE_GOAL_AND_PATH:
if (!p.isPathing() && !p.getInProgress().isPresent()) {
p.secretInternalSetGoalAndPath(command);
@@ -118,7 +117,7 @@ public class PathingControlManager implements IPathingControlManager {
case SET_GOAL_AND_PATH:
// now this i can do
if (command.goal != null) {
- baritone.getPathingBehavior().secretInternalSetGoalAndPath(command);
+ p.secretInternalSetGoalAndPath(command);
}
break;
default:
diff --git a/src/main/java/baritone/utils/accessor/IFireworkRocketEntity.java b/src/main/java/baritone/utils/accessor/IFireworkRocketEntity.java
new file mode 100644
index 000000000..36690dbc1
--- /dev/null
+++ b/src/main/java/baritone/utils/accessor/IFireworkRocketEntity.java
@@ -0,0 +1,25 @@
+/*
+ * 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.utils.accessor;
+
+import net.minecraft.world.entity.LivingEntity;
+
+public interface IFireworkRocketEntity {
+
+ LivingEntity getBoostedEntity();
+}