diff --git a/src/main/java/baritone/behavior/ElytraBehavior.java b/src/main/java/baritone/behavior/ElytraBehavior.java index 7e328bfd4..b4b119921 100644 --- a/src/main/java/baritone/behavior/ElytraBehavior.java +++ b/src/main/java/baritone/behavior/ElytraBehavior.java @@ -37,6 +37,8 @@ import baritone.behavior.elytra.UnpackedSegment; import baritone.cache.FasterWorldScanner; import baritone.utils.BlockStateInterface; import baritone.utils.accessor.IEntityFireworkRocket; +import it.unimi.dsi.fastutil.floats.FloatArrayList; +import it.unimi.dsi.fastutil.floats.FloatIterator; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.entity.item.EntityFireworkRocket; @@ -52,6 +54,7 @@ import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; +import java.util.function.Supplier; import java.util.function.UnaryOperator; public final class ElytraBehavior extends Behavior implements IElytraBehavior, Helper { @@ -530,57 +533,84 @@ public final class ElytraBehavior extends Behavior implements IElytraBehavior, H 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 - float[] interps = new float[] {1.0f, 0.75f, 0.5f, 0.25f}; 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) { - for (float interp : interps) { - Vec3d dest; - if (interp == 1 || i == minStep) { - dest = path.getVec(i); + if (i == minStep) { + // no interp + candidates.add(new Pair<>(path.getVec(i).add(0, dy, 0), dy)); + } else { + if (relaxation == 2) { + final Vec3d delta = path.getVec(i).subtract(path.getVec(i - 1)); + final double stepLength = 0.5; + final int steps = MathHelper.floor(delta.length() / stepLength); + final Vec3d step = delta.normalize().scale(stepLength); + + Vec3d stepped = path.getVec(i); + for (int interp = 0; interp < steps; interp++) { + candidates.add(new Pair<>(stepped.add(0, dy, 0), dy)); + stepped = stepped.subtract(step); + } } else { - dest = path.getVec(i).scale(interp).add(path.getVec(i - 1).scale(1.0d - interp)); + final float[] interps = relaxation == 0 + ? new float[]{1.0f, 0.75f, 0.5f, 0.25f} + : new float[]{1.0f, 0.875f, 0.75f, 0.625f, 0.5f, 0.375f, 0.25f, 0.125f}; + for (float interp : interps) { + Vec3d dest; + if (interp == 1) { + dest = path.getVec(i); + } else { + dest = path.getVec(i).scale(interp).add(path.getVec(i - 1).scale(1.0d - interp)); + } + candidates.add(new Pair<>(dest.add(0, dy, 0), dy)); + } } + } + } - dest = dest.add(0, dy, 0); - if (dy != 0) { - if (i + lookahead >= path.size()) { + for (final Pair candidate : candidates) { + final Vec3d dest = candidate.first(); + final Integer augment = candidate.second(); + + 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; } - if (start.distanceTo(dest) < 40) { - if (!this.clearView(dest, path.getVec(i + lookahead).add(0, dy, 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(start, dest, growth, isInLava)) { - // 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.subtract(start), relaxation, isInLava); - if (pitch.first() == null) { - solution = new Solution(context, new Rotation(yaw, ctx.playerRotations().getPitch()), null, false, false); + } 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; } - - // 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()); } } + + final double minAvoidance = Baritone.settings().elytraMinimumAvoidance.value; + final Double growth = relaxation == 2 ? null + : relaxation == 0 ? 2 * minAvoidance : minAvoidance; + + if (this.isHitboxClear(start, dest, growth, isInLava)) { + // 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, isInLava); + 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()); + } } } } @@ -680,10 +710,16 @@ public final class ElytraBehavior extends Behavior implements IElytraBehavior, H return this.firework != null; } + /** + * @return The guaranteed number of remaining ticks with boost + */ public int getGuaranteedBoostTicks() { return this.isBoosted() ? Math.max(0, this.minimumBoostTicks - this.firework.ticksExisted) : 0; } + /** + * @return The maximum number of remaining ticks with boost + */ public int getMaximumBoostTicks() { return this.isBoosted() ? Math.max(0, this.maximumBoostTicks - this.firework.ticksExisted) : 0; } @@ -835,43 +871,92 @@ public final class ElytraBehavior extends Behavior implements IElytraBehavior, H } } - private Pair solvePitch(final SolverContext context, final Vec3d goalDelta, - final int relaxation, final boolean ignoreLava) { - final boolean desperate = relaxation == 2; - final int ticks = desperate ? 3 : context.boost.isBoosted() ? 5 : Baritone.settings().elytraSimulationTicks.value; - final int ticksBoosted = context.boost.isBoosted() ? ticks : 0; + @FunctionalInterface + private interface IntBiFunction { + T apply(int left, int right); + } - final PitchResult pitch = this.solvePitch(context, goalDelta, ticks, ticksBoosted, desperate, ignoreLava); - if (pitch != null) { - return new Pair<>(pitch.pitch, false); + 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(MathHelper.ceil(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; + } + + private Pair solvePitch(final SolverContext context, final Vec3d goal, + final int relaxation, final boolean ignoreLava) { + + final boolean desperate = relaxation == 2; + final float goodPitch = RotationUtils.calcRotationFromVec3d(context.start, goal, ctx.playerRotations()).getPitch(); + final FloatArrayList pitches = pitchesToSolveFor(goodPitch, desperate); + + final IntBiFunction solve = (ticks, ticksBoosted) -> + this.solvePitch(context, goal, relaxation, pitches.iterator(), ticks, ticksBoosted, ignoreLava); + + 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(() -> solve.apply(lookahead, 1)); + tests.add(() -> solve.apply(5, 5)); + } else if (guaranteed <= 5) { + // boost will run out within 5 ticks + tests.add(() -> solve.apply(guaranteed + 5, guaranteed)); + } else { + // there's plenty of guaranteed boost + tests.add(() -> solve.apply(guaranteed + 1, guaranteed)); + } + } + + // 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(() -> solve.apply(ticks, context.boost.isBoosted() ? ticks : 0)); + + final Optional result = tests.stream() + .map(Supplier::get) + .filter(Objects::nonNull) + .findFirst(); + if (result.isPresent()) { + return new Pair<>(result.get().pitch, false); } if (Baritone.settings().experimentalTakeoff.value && relaxation > 0) { // Simulate as if we were boosted for the entire duration - final PitchResult usingFirework = this.solvePitch(context, goalDelta, ticks, ticks, desperate, ignoreLava); + final PitchResult usingFirework = solve.apply(ticks, ticks); if (usingFirework != null) { return new Pair<>(usingFirework.pitch, true); } } - return new Pair<>(null, false); + return null; } - private PitchResult solvePitch(final SolverContext context, final Vec3d goalDelta, final int ticks, - final int ticksBoosted, final boolean desperate, final boolean ignoreLava) { + private PitchResult solvePitch(final SolverContext context, final Vec3d goal, final int relaxation, + final FloatIterator pitches, final int ticks, final int ticksBoosted, + final boolean ignoreLava) { // 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 Vec3d goalDelta = goal.subtract(context.start); final Vec3d goalDirection = goalDelta.normalize(); - final float goodPitch = RotationUtils.calcRotationFromVec3d(Vec3d.ZERO, goalDirection, ctx.playerRotations()).getPitch(); PitchResult result = null; - 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); - - for (float pitch = minPitch; pitch <= maxPitch; pitch++) { + outer: + while (pitches.hasNext()) { + final float pitch = pitches.nextFloat(); final List displacement = this.simulate( context.aimProcessor.fork(), goalDelta, @@ -883,9 +968,24 @@ public final class ElytraBehavior extends Behavior implements IElytraBehavior, H if (displacement == null) { continue; } - final Vec3d last = displacement.get(displacement.size() - 1); - double goodness = goalDirection.dotProduct(last.normalize()); + final int lastIndex = displacement.size() - 1; + final Vec3d last = displacement.get(lastIndex); + final double goodness = goalDirection.dotProduct(last.normalize()); if (result == null || goodness > result.dot) { + if (relaxation == 0) { + // 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 = lastIndex; i >= 1; i--) { + if (!clearView(context.start.add(displacement.get(i)), goal, false)) { + continue outer; + } + } + } else if (relaxation == 1) { + // Ensure that the goal is visible from the final position + if (!clearView(context.start.add(last), goal, false)) { + continue; + } + } result = new PitchResult(pitch, goodness, displacement); } }