baritone/src/main/java/baritone/behavior/ElytraBehavior.java

691 lines
30 KiB
Java

/*
* 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 <https://www.gnu.org/licenses/>.
*/
package baritone.behavior;
import baritone.Baritone;
import baritone.api.event.events.ChunkEvent;
import baritone.api.event.events.TickEvent;
import baritone.api.event.events.type.EventState;
import baritone.api.utils.*;
import baritone.behavior.elytra.NetherPathfinderContext;
import baritone.behavior.elytra.UnpackedSegment;
import baritone.utils.BlockStateInterface;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.item.EntityFireworkRocket;
import net.minecraft.util.EnumHand;
import net.minecraft.util.math.*;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.UnaryOperator;
public final class ElytraBehavior extends Behavior implements Helper {
/**
* 2b2t seed
*/
private static final long NETHER_SEED = 146008555100680L;
// Used exclusively for PathRenderer
public List<Pair<Vec3d, Vec3d>> lines;
public BlockPos aimPos;
public List<BetterBlockPos> visiblePath;
// :sunglasses:
private final NetherPathfinderContext context;
private final PathManager pathManager;
private int sinceFirework;
public ElytraBehavior(Baritone baritone) {
super(baritone);
this.context = new NetherPathfinderContext(NETHER_SEED);
this.lines = new ArrayList<>();
this.visiblePath = Collections.emptyList();
this.pathManager = new PathManager();
}
private final class PathManager {
private BlockPos destination;
private List<BetterBlockPos> path;
private boolean completePath;
private int playerNear;
private int goingTo;
private boolean recalculating;
public PathManager() {
// lol imagine initializing fields normally
this.clear();
}
public void tick() {
// Recalculate closest path node
this.playerNear = this.calculateNear(this.playerNear);
// Obstacles are more important than an incomplete path, handle those first.
this.pathfindAroundObstacles();
this.attemptNextSegment();
}
public void pathToDestination(BlockPos destination) {
this.destination = destination;
this.path0(ctx.playerFeet(), destination, UnaryOperator.identity())
.thenRun(() -> {
final int distance = (int) this.pathAt(0).distanceTo(this.pathAt(this.path.size() - 1));
if (this.completePath) {
logDirect(String.format("Computed path (%d blocks)", distance));
} else {
logDirect(String.format("Computed segment (Next %d blocks)", distance));
}
})
.whenComplete((result, ex) -> {
this.recalculating = false;
if (ex != null) {
logDirect("Failed to compute path to destination");
}
});
}
public void pathRecalcSegment(final int upToIncl) {
if (this.recalculating) {
return;
}
this.recalculating = true;
final List<BetterBlockPos> after = this.path.subList(upToIncl, this.path.size());
final boolean complete = this.completePath;
this.path0(ctx.playerFeet(), this.path.get(upToIncl), segment -> segment.append(after.stream(), complete))
.thenRun(() -> {
final int recompute = this.path.size() - after.size() - 1;
final int distance = (int) this.pathAt(0).distanceTo(this.pathAt(recompute));
logDirect(String.format("Recomputed segment (Next %d blocks)", distance));
})
.whenComplete((result, ex) -> {
this.recalculating = false;
if (ex != null) {
logDirect("Failed to recompute segment");
}
});
}
public void pathNextSegment(final int afterIncl) {
if (this.recalculating) {
return;
}
this.recalculating = true;
final List<BetterBlockPos> before = this.path.subList(0, afterIncl + 1);
this.path0(this.path.get(afterIncl), this.destination, segment -> segment.prepend(before.stream()))
.thenRun(() -> {
final int recompute = this.path.size() - before.size() - 1;
final int distance = (int) this.pathAt(0).distanceTo(this.pathAt(recompute));
if (this.completePath) {
logDirect(String.format("Computed path (%d blocks)", distance));
} else {
logDirect(String.format("Computed next segment (Next %d blocks)", distance));
}
})
.whenComplete((result, ex) -> {
this.recalculating = false;
if (ex != null) {
logDirect("Failed to compute next segment");
}
});
}
private Vec3d pathAt(int i) {
return new Vec3d(
this.path.get(i).x,
this.path.get(i).y,
this.path.get(i).z
);
}
public void clear() {
this.path = Collections.emptyList();
this.playerNear = 0;
this.goingTo = 0;
this.completePath = true;
}
private void setPath(final UnpackedSegment segment) {
this.path = segment.collect();
this.removeBacktracks();
this.playerNear = 0;
this.goingTo = 0;
this.completePath = segment.isFinished();
}
public List<BetterBlockPos> getPath() {
return this.path;
}
public int getNear() {
return this.playerNear;
}
public void setGoingTo(int index) {
this.goingTo = index;
}
public BetterBlockPos goingTo() {
return this.path.get(this.goingTo);
}
// mickey resigned
private CompletableFuture<Void> path0(BlockPos src, BlockPos dst, UnaryOperator<UnpackedSegment> operator) {
return ElytraBehavior.this.context.pathFindAsync(src, dst)
.thenApply(UnpackedSegment::from)
.thenApply(operator)
.thenAcceptAsync(this::setPath, ctx.minecraft()::addScheduledTask);
}
private void pathfindAroundObstacles() {
if (this.recalculating) {
return;
}
outer:
while (true) {
int rangeStartIncl = playerNear;
int rangeEndExcl = playerNear;
while (rangeEndExcl < path.size() && ctx.world().isBlockLoaded(path.get(rangeEndExcl), false)) {
rangeEndExcl++;
}
if (rangeStartIncl >= rangeEndExcl) {
// not loaded yet?
return;
}
if (!passable(ctx.world().getBlockState(path.get(rangeStartIncl)))) {
// we're in a wall
return; // previous iterations of this function SHOULD have fixed this by now :rage_cat:
}
for (int i = rangeStartIncl; i < rangeEndExcl - 1; i++) {
if (!clearView(pathAt(i), pathAt(i + 1))) {
// obstacle. where do we return to pathing?
// find the next valid segment
this.pathRecalcSegment(rangeEndExcl - 1);
break outer;
}
}
break;
}
}
private void attemptNextSegment() {
if (this.recalculating) {
return;
}
final int last = this.path.size() - 1;
if (!this.completePath && ctx.world().isBlockLoaded(this.path.get(last), false)) {
this.pathNextSegment(last);
}
}
private int calculateNear(int index) {
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
}
}
return index;
}
private void removeBacktracks() {
Map<BetterBlockPos, Integer> positionFirstSeen = new HashMap<>();
for (int i = 0; i < this.path.size(); i++) {
BetterBlockPos pos = this.path.get(i);
if (positionFirstSeen.containsKey(pos)) {
int j = positionFirstSeen.get(pos);
while (i > j) {
this.path.remove(i);
i--;
}
} else {
positionFirstSeen.put(pos, i);
}
}
}
}
@Override
public void onChunkEvent(ChunkEvent event) {
if (event.getState() == EventState.POST && event.getType().isPopulate()) {
final Chunk chunk = ctx.world().getChunk(event.getX(), event.getZ());
this.context.queueForPacking(chunk);
}
}
public void path(BlockPos destination) {
this.pathManager.pathToDestination(destination);
}
public void cancel() {
this.visiblePath = Collections.emptyList();
this.pathManager.clear();
this.aimPos = null;
this.sinceFirework = 0;
}
@Override
public void onTick(TickEvent event) {
if (event.getType() == TickEvent.Type.OUT) {
return;
}
this.lines.clear();
final List<BetterBlockPos> path = this.pathManager.getPath();
if (path.isEmpty()) {
return;
}
this.pathManager.tick();
final int playerNear = this.pathManager.getNear();
this.visiblePath = path.subList(
Math.max(playerNear - 30, 0),
Math.min(playerNear + 100, path.size())
);
if (!ctx.player().isElytraFlying()) {
return;
}
baritone.getInputOverrideHandler().clearAllKeys();
if (ctx.player().collidedHorizontally) {
logDirect("hbonk");
}
if (ctx.player().collidedVertically) {
logDirect("vbonk");
}
final Vec3d start = ctx.playerFeetAsVec();
final boolean firework = isFireworkActive();
boolean forceUseFirework = false;
this.sinceFirework++;
outermost:
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 = firework ? new int[]{20, 10, 5, 0} : new int[]{0}; // attempt to gain height, if we can, so as not to waste the boost
int steps = relaxation < 2 ? firework ? 5 : Baritone.settings().elytraSimulationTicks.value : 3;
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--) {
for (int dy : heights) {
Vec3d dest = this.pathManager.pathAt(i).add(0, dy, 0);
if (dy != 0) {
if (i + lookahead >= path.size()) {
continue;
}
if (start.distanceTo(dest) < 40) {
if (!clearView(dest, this.pathManager.pathAt(i + lookahead).add(0, dy, 0)) || !clearView(dest, this.pathManager.pathAt(i + lookahead))) {
// 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 (!clearView(dest, this.pathManager.pathAt(i))) {
continue;
}
}
}
// 1.0 -> 0.25 -> none
final Double grow = relaxation == 2 ? null
: relaxation == 0 ? 1.0d : 0.25d;
if (isClear(start, dest, grow)) {
final float yaw = RotationUtils.calcRotationFromVec3d(start, dest, ctx.playerRotations()).getYaw();
final Pair<Float, Boolean> pitch = this.solvePitch(dest.subtract(start), steps, relaxation, firework);
if (pitch.first() == null) {
baritone.getLookBehavior().updateTarget(new Rotation(yaw, ctx.playerRotations().getPitch()), false);
continue;
}
forceUseFirework = pitch.second();
this.pathManager.setGoingTo(i);
this.aimPos = path.get(i).add(0, dy, 0);
baritone.getLookBehavior().updateTarget(new Rotation(yaw, pitch.first()), false);
break outermost;
}
}
}
if (relaxation == 2) {
logDirect("no pitch solution, probably gonna crash in a few ticks LOL!!!");
return;
}
}
final BetterBlockPos goingTo = this.pathManager.goingTo();
final boolean useOnDescend = !Baritone.settings().conserveFireworks.value || ctx.player().posY < goingTo.y + 5;
final double currentSpeed = new Vec3d(
ctx.player().motionX,
// ignore y component if we are BOTH below where we want to be AND descending
ctx.player().posY < goingTo.y ? Math.max(0, ctx.player().motionY) : ctx.player().motionY,
ctx.player().motionZ
).length();
if (forceUseFirework || (!firework
&& sinceFirework > 10
&& useOnDescend
&& (ctx.player().posY < goingTo.y - 5 || start.distanceTo(new Vec3d(goingTo.x + 0.5, ctx.player().posY, goingTo.z + 0.5)) > 5) // UGH!!!!!!!
&& currentSpeed < Baritone.settings().elytraFireworkSpeed.value)
) {
logDirect("firework" + (forceUseFirework ? " takeoff" : ""));
ctx.playerController().processRightClick(ctx.player(), ctx.world(), EnumHand.MAIN_HAND);
sinceFirework = 0;
}
}
private boolean isFireworkActive() {
// TODO: Validate that the EntityFireworkRocket is attached to ctx.player()
return ctx.world().loadedEntityList.stream()
.anyMatch(x -> (x instanceof EntityFireworkRocket) && ((EntityFireworkRocket) x).isAttachedToEntity());
}
private boolean isClear(final Vec3d start, final Vec3d dest, final Double growAmount) {
if (!clearView(start, dest)) {
return false;
}
if (growAmount == null) {
return true;
}
final AxisAlignedBB bb = ctx.player().getEntityBoundingBox().grow(growAmount);
final Vec3d[] corners = new Vec3d[]{
new Vec3d(bb.minX, bb.minY, bb.minZ),
new Vec3d(bb.minX, bb.minY, bb.maxZ),
new Vec3d(bb.minX, bb.maxY, bb.minZ),
new Vec3d(bb.minX, bb.maxY, bb.maxZ),
new Vec3d(bb.maxX, bb.minY, bb.minZ),
new Vec3d(bb.maxX, bb.minY, bb.maxZ),
new Vec3d(bb.maxX, bb.maxY, bb.minZ),
new Vec3d(bb.maxX, bb.maxY, bb.maxZ),
};
for (final Vec3d corner : corners) {
if (!clearView(corner, dest.add(corner.subtract(start)))) {
return false;
}
}
return true;
}
private boolean clearView(Vec3d start, Vec3d dest) {
lines.add(new Pair<>(start, dest));
return !rayTraceBlocks(ctx.world(), start.x, start.y, start.z, dest.x, dest.y, dest.z);
}
private Pair<Float, Boolean> solvePitch(Vec3d goalDirection, int steps, int relaxation, boolean currentlyBoosted) {
final Float pitch = this.solvePitch(goalDirection, steps, relaxation == 2, currentlyBoosted);
if (pitch != null) {
return new Pair<>(pitch, false);
}
if (Baritone.settings().experimentalTakeoff.value && relaxation > 0) {
final Float usingFirework = this.solvePitch(goalDirection, steps, relaxation == 2, true);
if (usingFirework != null) {
return new Pair<>(usingFirework, true);
}
}
return new Pair<>(null, false);
}
private Float solvePitch(Vec3d goalDirection, int steps, boolean desperate, boolean firework) {
// 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
goalDirection = goalDirection.normalize();
Rotation good = RotationUtils.calcRotationFromVec3d(new Vec3d(0, 0, 0), goalDirection, ctx.playerRotations()); // lazy lol
Float bestPitch = null;
double bestDot = Double.NEGATIVE_INFINITY;
Vec3d motion = new Vec3d(ctx.player().motionX, ctx.player().motionY, ctx.player().motionZ);
BlockStateInterface bsi = new BlockStateInterface(ctx);
float minPitch = desperate ? -90 : Math.max(good.getPitch() - Baritone.settings().elytraPitchRange.value, -89);
float maxPitch = desperate ? 90 : Math.min(good.getPitch() + Baritone.settings().elytraPitchRange.value, 89);
outer:
for (float pitch = minPitch; pitch <= maxPitch; pitch++) {
Vec3d stepped = motion;
Vec3d totalMotion = new Vec3d(0, 0, 0);
for (int i = 0; i < steps; i++) {
stepped = step(stepped, pitch, good.getYaw(), firework);
Vec3d actualPositionPrevTick = ctx.playerFeetAsVec().add(totalMotion);
totalMotion = totalMotion.add(stepped);
Vec3d actualPosition = ctx.playerFeetAsVec().add(totalMotion);
for (int x = MathHelper.floor(Math.min(actualPosition.x, actualPositionPrevTick.x) - 0.31); x <= Math.max(actualPosition.x, actualPositionPrevTick.x) + 0.31; x++) {
for (int y = MathHelper.floor(Math.min(actualPosition.y, actualPositionPrevTick.y) - 0.2); y <= Math.max(actualPosition.y, actualPositionPrevTick.y) + 1; y++) {
for (int z = MathHelper.floor(Math.min(actualPosition.z, actualPositionPrevTick.z) - 0.31); z <= Math.max(actualPosition.z, actualPositionPrevTick.z) + 0.31; z++) {
if (!passable(bsi.get0(x, y, z))) {
continue outer;
}
}
}
}
}
double directionalGoodness = goalDirection.dotProduct(totalMotion.normalize());
// tried to incorporate a "speedGoodness" but it kept making it do stupid stuff (aka always losing altitude)
double goodness = directionalGoodness;
if (goodness > bestDot) {
bestDot = goodness;
bestPitch = pitch;
}
}
return bestPitch;
}
public static boolean passable(IBlockState state) {
return state.getMaterial() == Material.AIR;
}
private static Vec3d step(Vec3d motion, float rotationPitch, float rotationYaw, boolean firework) {
double motionX = motion.x;
double motionY = motion.y;
double motionZ = motion.z;
float flatZ = MathHelper.cos(-rotationYaw * 0.017453292F - (float) Math.PI); // 0.174... is Math.PI / 180
float flatX = MathHelper.sin(-rotationYaw * 0.017453292F - (float) Math.PI);
float pitchBase = -MathHelper.cos(-rotationPitch * 0.017453292F);
float pitchHeight = MathHelper.sin(-rotationPitch * 0.017453292F);
Vec3d lookDirection = new Vec3d(flatX * pitchBase, pitchHeight, flatZ * pitchBase);
if (firework) {
// See EntityFireworkRocket
motionX += lookDirection.x * 0.1 + (lookDirection.x * 1.5 - motionX) * 0.5;
motionY += lookDirection.y * 0.1 + (lookDirection.y * 1.5 - motionY) * 0.5;
motionZ += lookDirection.z * 0.1 + (lookDirection.z * 1.5 - motionZ) * 0.5;
}
float pitchRadians = rotationPitch * 0.017453292F;
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 = MathHelper.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) (-MathHelper.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.99;
motionY *= 0.98;
motionZ *= 0.99;
//System.out.println(motionX + " " + motionY + " " + motionZ);
return new Vec3d(motionX, motionY, motionZ);
}
private final BlockPos.MutableBlockPos mutableRaytraceBlockPos = new BlockPos.MutableBlockPos();
private boolean rayTraceBlocks(final World world,
final double startX, final double startY, final double startZ,
final double endX, final double endY, final double endZ) {
int voxelCurrX = fastFloor(startX);
int voxelCurrY = fastFloor(startY);
int voxelCurrZ = fastFloor(startZ);
Chunk prevChunk;
IBlockState currentState = (prevChunk = cachedChunk(world, this.mutableRaytraceBlockPos.setPos(voxelCurrX, voxelCurrY, voxelCurrZ), null)).getBlockState(this.mutableRaytraceBlockPos);
// This is true if player is standing inside of block.
if (!passable(currentState)) {
return true;
}
final int voxelEndX = fastFloor(endX);
final int voxelEndY = fastFloor(endY);
final int voxelEndZ = fastFloor(endZ);
double currPosX = startX;
double currPosY = startY;
double currPosZ = startZ;
int steps = 200; // TODO: should we lower the max steps?
while (steps-- >= 0) {
if (voxelCurrX == voxelEndX && voxelCurrY == voxelEndY && voxelCurrZ == voxelEndZ) {
return false;
}
final double distanceFromStartToEndX = endX - currPosX;
final double distanceFromStartToEndY = endY - currPosY;
final double distanceFromStartToEndZ = endZ - currPosZ;
double nextIntegerX;
double nextIntegerY;
double nextIntegerZ;
// potentially more based branchless impl?
nextIntegerX = voxelCurrX + ((voxelCurrX - voxelEndX) >>> 31); // if voxelEnd > voxelIn, then voxelIn-voxelEnd will be negative, meaning the sign bit is 1
nextIntegerY = voxelCurrY + ((voxelCurrY - voxelEndY) >>> 31); // if we do an unsigned right shift by 31, that sign bit becomes the LSB
nextIntegerZ = voxelCurrZ + ((voxelCurrZ - voxelEndZ) >>> 31); // therefore, this increments nextInteger iff EndX>inX, otherwise it leaves it alone
// remember: don't have to worry about the case when voxelEnd == voxelIn, because nextInteger value wont be used
// these just have to be strictly greater than 1, might as well just go up to the next int
double fracIfSkipX = 2.0D;
double fracIfSkipY = 2.0D;
double fracIfSkipZ = 2.0D;
// reminder to future self: don't "branchlessify" this, it's MUCH slower (pretty obviously, floating point div is much worse than a branch mispredict, but integer increment (like the other two removed branches) are cheap enough to be worth doing either way)
if (voxelEndX != voxelCurrX) {
fracIfSkipX = (nextIntegerX - currPosX) / distanceFromStartToEndX;
}
if (voxelEndY != voxelCurrY) {
fracIfSkipY = (nextIntegerY - currPosY) / distanceFromStartToEndY;
}
if (voxelEndZ != voxelCurrZ) {
fracIfSkipZ = (nextIntegerZ - currPosZ) / distanceFromStartToEndZ;
}
if (fracIfSkipX < fracIfSkipY && fracIfSkipX < fracIfSkipZ) {
// note: voxelEndX == voxelInX is impossible because allowSkip would be set to false in that case, meaning that the elapsed distance would stay at default
currPosX = nextIntegerX;
currPosY += distanceFromStartToEndY * fracIfSkipX;
currPosZ += distanceFromStartToEndZ * fracIfSkipX;
// tested: faster to paste this 3 times with only one of the subtractions in each
final int xFloorOffset = (voxelEndX - voxelCurrX) >>> 31;
voxelCurrX = (fastFloor(currPosX) - xFloorOffset);
voxelCurrY = (fastFloor(currPosY));
voxelCurrZ = (fastFloor(currPosZ));
} else if (fracIfSkipY < fracIfSkipZ) {
currPosX += distanceFromStartToEndX * fracIfSkipY;
currPosY = nextIntegerY;
currPosZ += distanceFromStartToEndZ * fracIfSkipY;
// tested: faster to paste this 3 times with only one of the subtractions in each
final int yFloorOffset = (voxelEndY - voxelCurrY) >>> 31;
voxelCurrX = (fastFloor(currPosX));
voxelCurrY = (fastFloor(currPosY) - yFloorOffset);
voxelCurrZ = (fastFloor(currPosZ));
} else {
currPosX += distanceFromStartToEndX * fracIfSkipZ;
currPosY += distanceFromStartToEndY * fracIfSkipZ;
currPosZ = nextIntegerZ;
// tested: faster to paste this 3 times with only one of the subtractions in each
final int zFloorOffset = (voxelEndZ - voxelCurrZ) >>> 31;
voxelCurrX = (fastFloor(currPosX));
voxelCurrY = (fastFloor(currPosY));
voxelCurrZ = (fastFloor(currPosZ) - zFloorOffset);
}
currentState = (prevChunk = cachedChunk(world, this.mutableRaytraceBlockPos.setPos(voxelCurrX, voxelCurrY, voxelCurrZ), prevChunk)).getBlockState(this.mutableRaytraceBlockPos);
if (!passable(currentState)) {
return true;
}
}
return false;
}
private static final double FLOOR_DOUBLE_D = 1_073_741_824.0;
private static final int FLOOR_DOUBLE_I = 1_073_741_824;
private static int fastFloor(final double v) {
return ((int) (v + FLOOR_DOUBLE_D)) - FLOOR_DOUBLE_I;
}
private static Chunk cachedChunk(final World world,
final BlockPos pos,
final Chunk prevLookup) {
final int chunkX = pos.getX() >> 4;
final int chunkZ = pos.getZ() >> 4;
if (prevLookup != null && prevLookup.x == chunkX && prevLookup.z == chunkZ) {
return prevLookup;
}
return world.getChunk(chunkX, chunkZ);
}
}