Skip to content

Elytra in overworld #4698

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 30 commits into
base: 1.19.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
75a3acd
Add initial support for overworld/end flight
underscore-zi Mar 15, 2025
dd2240a
update to nether-pathfinder api/concurrency changes
babbaj Mar 16, 2025
9d0a4cb
Make usage of the Baritone cache optional for elytra flight
underscore-zi Mar 16, 2025
984c71e
Set a better default Y for elytra GoalXZ
underscore-zi Mar 16, 2025
319aa58
Make sure player is in bounds before attempting elytra pathfinding
underscore-zi Mar 16, 2025
18a6899
Allow landing on bedrock (necessary for nether roof)
underscore-zi Mar 16, 2025
662ba65
Revert "Set a better default Y for elytra GoalXZ"
underscore-zi Mar 16, 2025
c9aa9f7
GoalXZ y-level will consider if player is above nether roof
underscore-zi Mar 24, 2025
b0ba8d3
Rework landing to be safed when descending from high y-levels, and ut…
underscore-zi Mar 24, 2025
1a6c43e
Check negative y landing spots too
underscore-zi Mar 24, 2025
2004a1d
Merge branch 'cabaletta:1.19.4' into 1.19.4
underscore-zi Mar 26, 2025
cf3260c
dynamically change destination y if the roof gets in the way
babbaj Apr 2, 2025
2290801
1.6
babbaj Apr 4, 2025
3fb41ea
memset if no air also cave air is real
babbaj Apr 4, 2025
c7b76bc
custom allocator by default :^)
babbaj Apr 5, 2025
094b3ef
Respect allowAboveRoof roof setting when looking for safe landing spots
underscore-zi Apr 5, 2025
1ea67c2
Allow landing on cobblestone and obsidian
underscore-zi Apr 5, 2025
7feaa51
elytra fixes
babbaj Apr 10, 2025
f3e6116
hold write lock in queueBlockUpdate
babbaj Apr 11, 2025
d0c3aea
Keep zero-based coordinates to within pathfinder code
underscore-zi Apr 12, 2025
75096e1
fix crashes
babbaj Apr 13, 2025
42e1a7a
Merge remote-tracking branch 'origin/1.19.4' into nether-elytra-update
babbaj Apr 13, 2025
470ef10
final
babbaj Apr 13, 2025
0912069
rename elytraUseBaritoneCache
babbaj Apr 13, 2025
20c1631
oops
babbaj Apr 13, 2025
68dc110
fix #elytra reset bypassing y checks
babbaj Apr 15, 2025
04813a8
i forgor to commit this
babbaj Apr 15, 2025
3d5b744
fixes
babbaj Apr 15, 2025
e545294
fix exception when setting out of bounds goal while elytra flying
babbaj Apr 22, 2025
5624d4a
fix deadlock
babbaj Apr 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ forge_version=45.0.43

fabric_version=0.14.11

nether_pathfinder_version=1.4.1
nether_pathfinder_version=1.6

// These dependencies are used for common and tweaker
// while mod loaders usually ship their own version
Expand Down
20 changes: 20 additions & 0 deletions src/api/java/baritone/api/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -1543,6 +1543,26 @@ public final class Settings {
*/
public final Setting<Boolean> elytraChatSpam = new Setting<>(false);

/**
* May reduce memory usage by using a custom allocator for pathfinding
*/
public final Setting<Boolean> elytraCustomAllocator = new Setting<>(false);

/**
* Allow the pathfinder to attempt flight in tighter spaces, useful in caves but can be dangerous.
*/
public final Setting<Boolean> elytraAllowTightSpaces = new Setting<>(false);

/**
* Allow the pathfinder to fly above y 128 in the nether.
*/
public final Setting<Boolean> elytraAllowAboveRoof = new Setting<>(false);

/**
* Allow the pathfinder to access the baritone cache to improve pathing
*/
public final Setting<Boolean> elytraUseBaritoneCache = new Setting<>(true);

/**
* A map of lowercase setting field names to their respective setting
*/
Expand Down
3 changes: 0 additions & 3 deletions src/main/java/baritone/command/defaults/ElytraCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,6 @@ public void execute(String label, IArgConsumer args) throws CommandException {
if (iGoal == null) {
throw new CommandInvalidStateException("No goal has been set");
}
if (ctx.world().dimension() != Level.NETHER) {
throw new CommandInvalidStateException("Only works in the nether");
}
try {
elytra.pathTo(iGoal);
} catch (IllegalArgumentException ex) {
Expand Down
96 changes: 82 additions & 14 deletions src/main/java/baritone/process/ElytraProcess.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.phys.Vec3;

import java.util.*;
Expand Down Expand Up @@ -178,7 +180,7 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) {
Rotation rotation = RotationUtils.calcRotationFromVec3d(from, to, ctx.playerRotations());
baritone.getLookBehavior().updateTarget(new Rotation(rotation.getYaw(), 0), false); // this will be overwritten, probably, by behavior tick

if (ctx.player().position().y < endPos.y - LANDING_COLUMN_HEIGHT) {
if (ctx.player().position().y < endPos.y - this.landingColumnHeight) {
logDirect("bad landing spot, trying again...");
landingSpotIsBad(endPos);
}
Expand All @@ -189,7 +191,12 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) {
behavior.landingMode = this.state == State.LANDING;
this.goal = null;
baritone.getInputOverrideHandler().clearAllKeys();
behavior.tick();
behavior.context.readLock.lock();
try {
behavior.tick();
} finally {
behavior.context.readLock.unlock();
}
return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL);
} else if (this.state == State.LANDING) {
if (ctx.playerMotion().multiply(1, 0, 1).length() > 0.001) {
Expand Down Expand Up @@ -322,11 +329,11 @@ public void pathTo(BlockPos destination) {
}

private void pathTo0(BlockPos destination, boolean appendDestination) {
if (ctx.player() == null || ctx.player().level.dimension() != Level.NETHER) {
if (ctx.player() == null) {
return;
}
this.onLostControl();
this.predictingTerrain = Baritone.settings().elytraPredictTerrain.value;
this.predictingTerrain = ctx.player().level.dimension() == Level.NETHER && Baritone.settings().elytraPredictTerrain.value;
this.behavior = new ElytraBehavior(this.baritone, this, destination, appendDestination);
if (ctx.world() != null) {
this.behavior.repackChunks();
Expand All @@ -342,6 +349,7 @@ public void pathTo(Goal iGoal) {
if (iGoal instanceof GoalXZ) {
GoalXZ goal = (GoalXZ) iGoal;
x = goal.getX();
// ElytraBehavior will automatically change the destination height depending on if we're above or below the roof
y = 64;
z = goal.getZ();
} else if (iGoal instanceof GoalBlock) {
Expand All @@ -352,9 +360,18 @@ public void pathTo(Goal iGoal) {
} else {
throw new IllegalArgumentException("The goal must be a GoalXZ or GoalBlock");
}
if (y <= 0 || y >= 128) {
throw new IllegalArgumentException("The y of the goal is not between 0 and 128");

int minY = ctx.world().dimensionType().minY();
int maxY = (ctx.world().dimension() == Level.NETHER && !Baritone.settings().elytraAllowAboveRoof.value) ? 127 : Math.min(minY + 384, ctx.world().dimensionType().height() + minY);
if (y < minY || y >= maxY) {
throw new IllegalArgumentException("The goal must have a y value between " + minY + " and " + maxY);
}

int playerY = (int)ctx.player().getY();
if (playerY < minY || playerY >= maxY) {
throw new IllegalArgumentException("The player must have a y value between " + minY + " and " + maxY);
}

this.pathTo(new BlockPos(x, y, z));
}

Expand Down Expand Up @@ -465,12 +482,20 @@ public double placeBucketCost() {
}
}

private static boolean isInBounds(BlockPos pos) {
return pos.getY() >= 0 && pos.getY() < 128;
private static boolean isInBounds(Level dim, BlockPos pos) {
// TODO: Optionally limit nether to just y=127
DimensionType dimType = dim.dimensionType();
int minY = dimType.minY();
int maxY = Math.min(minY + 384, dimType.height() + minY);
return pos.getY() >= minY && pos.getY() < maxY;
}

private boolean isSafeBlock(Block block) {
return block == Blocks.NETHERRACK || block == Blocks.GRAVEL || (block == Blocks.NETHER_BRICKS && Baritone.settings().elytraAllowLandOnNetherFortress.value);
return block == Blocks.NETHERRACK || block == Blocks.GRAVEL || block == Blocks.SOUL_SAND || block == Blocks.SOUL_SOIL || (block == Blocks.NETHER_BRICKS && Baritone.settings().elytraAllowLandOnNetherFortress.value)
|| block == Blocks.STONE || block == Blocks.DEEPSLATE || block == Blocks.GRASS_BLOCK || block == Blocks.SAND || block == Blocks.RED_SAND || block == Blocks.TERRACOTTA
|| block == Blocks.SNOW || block == Blocks.ICE || block == Blocks.MYCELIUM || block == Blocks.PODZOL
|| block == Blocks.DARK_OAK_LEAVES || block == Blocks.JUNGLE_LEAVES
|| block == Blocks.END_STONE || block == Blocks.BEDROCK;
}

private boolean isSafeBlock(BlockPos pos) {
Expand Down Expand Up @@ -520,7 +545,7 @@ private boolean hasAirBubble(BlockPos pos) {

private BetterBlockPos checkLandingSpot(BlockPos pos, LongOpenHashSet checkedSpots) {
BlockPos.MutableBlockPos mut = new BlockPos.MutableBlockPos(pos.getX(), pos.getY(), pos.getZ());
while (mut.getY() >= 0) {
while (mut.getY() >= ctx.world().dimensionType().minY()) {
if (checkedSpots.contains(mut.asLong())) {
return null;
}
Expand All @@ -540,21 +565,34 @@ private BetterBlockPos checkLandingSpot(BlockPos pos, LongOpenHashSet checkedSpo
return null; // void
}

private static final int LANDING_COLUMN_HEIGHT = 15;
private static final int SHORT_LANDING_COLUMN_HEIGHT = 15;
private static final int LONG_LANDING_COLUMN_HEIGHT = 39;
private int landingColumnHeight = SHORT_LANDING_COLUMN_HEIGHT;
private Set<BetterBlockPos> badLandingSpots = new HashSet<>();

private BetterBlockPos findSafeLandingSpot(BetterBlockPos start) {
if(ctx.player().getY() > ctx.world().getHeight(Heightmap.Types.MOTION_BLOCKING, start.getX(), start.getZ())) {
return findSafeLandingSpot_heightmap(start);
} else {
return findSafeLandingSpot_underground(start);
}
}

private BetterBlockPos findSafeLandingSpot_underground(BetterBlockPos start) {
Queue<BetterBlockPos> queue = new PriorityQueue<>(Comparator.<BetterBlockPos>comparingInt(pos -> (pos.x - start.x) * (pos.x - start.x) + (pos.z - start.z) * (pos.z - start.z)).thenComparingInt(pos -> -pos.y));
Set<BetterBlockPos> visited = new HashSet<>();
LongOpenHashSet checkedPositions = new LongOpenHashSet();
queue.add(start);

while (!queue.isEmpty()) {
BetterBlockPos pos = queue.poll();
if (ctx.world().isLoaded(pos) && isInBounds(pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) {
if (ctx.world().isLoaded(pos) && isInBounds(ctx.world(), pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) {
BetterBlockPos actualLandingSpot = checkLandingSpot(pos, checkedPositions);
if (actualLandingSpot != null && isColumnAir(actualLandingSpot, LANDING_COLUMN_HEIGHT) && hasAirBubble(actualLandingSpot.above(LANDING_COLUMN_HEIGHT)) && !badLandingSpots.contains(actualLandingSpot.above(LANDING_COLUMN_HEIGHT))) {
return actualLandingSpot.above(LANDING_COLUMN_HEIGHT);
if(actualLandingSpot != null) {
landingColumnHeight = SHORT_LANDING_COLUMN_HEIGHT;
if (isColumnAir(actualLandingSpot, this.landingColumnHeight) && hasAirBubble(actualLandingSpot.above(this.landingColumnHeight)) && !badLandingSpots.contains(actualLandingSpot.above(this.landingColumnHeight))) {
return actualLandingSpot.above(this.landingColumnHeight);
}
}
if (visited.add(pos.north())) queue.add(pos.north());
if (visited.add(pos.east())) queue.add(pos.east());
Expand All @@ -566,4 +604,34 @@ private BetterBlockPos findSafeLandingSpot(BetterBlockPos start) {
}
return null;
}

private BetterBlockPos findSafeLandingSpot_heightmap(BetterBlockPos start) {
Queue<BetterBlockPos> queue = new PriorityQueue<>(Comparator.<BetterBlockPos>comparingInt(pos -> (pos.x - start.x) * (pos.x - start.x) + (pos.z - start.z) * (pos.z - start.z)).thenComparingInt(pos -> -pos.y));
Set<BetterBlockPos> visited = new HashSet<>();
LongOpenHashSet checkedPositions = new LongOpenHashSet();
queue.add(start);

while (!queue.isEmpty()) {
BetterBlockPos qPos = queue.poll();
if (!ctx.world().isLoaded(qPos)) continue;

var height = ctx.world().getHeight(Heightmap.Types.MOTION_BLOCKING, qPos.getX(), qPos.getZ());
var pos = new BetterBlockPos(qPos.getX(), height+1, qPos.getZ());
if (ctx.world().isLoaded(pos) && isInBounds(ctx.world(), pos) && ctx.world().getBlockState(pos).getBlock() == Blocks.AIR) {
BetterBlockPos actualLandingSpot = checkLandingSpot(pos, checkedPositions);
if(actualLandingSpot != null) {
landingColumnHeight = ctx.playerFeet().y - actualLandingSpot.y < LONG_LANDING_COLUMN_HEIGHT ? SHORT_LANDING_COLUMN_HEIGHT : LONG_LANDING_COLUMN_HEIGHT;
if (hasAirBubble(actualLandingSpot.above(landingColumnHeight)) && !badLandingSpots.contains(actualLandingSpot.above(landingColumnHeight))) {
return actualLandingSpot.above(landingColumnHeight);
}
}

if (visited.add(pos.north())) queue.add(pos.north());
if (visited.add(pos.east())) queue.add(pos.east());
if (visited.add(pos.south())) queue.add(pos.south());
if (visited.add(pos.west())) queue.add(pos.west());
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ public BlockStateOctreeInterface(final NetherPathfinderContext context) {
}

public boolean get0(final int x, final int y, final int z) {
if ((y | (127 - y)) < 0) {
if (y < 0 || y > 383) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This always checks for the maximum chunk height, is that correct? i.e. does nether-pathfinder always allocate full-size chunks?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

return false;
}
final int chunkX = x >> 4;
final int chunkZ = z >> 4;
if (this.chunkPtr == 0 | ((chunkX ^ this.prevChunkX) | (chunkZ ^ this.prevChunkZ)) != 0) {
this.prevChunkX = chunkX;
this.prevChunkZ = chunkZ;
this.chunkPtr = NetherPathfinder.getOrCreateChunk(this.contextPtr, chunkX, chunkZ);
this.chunkPtr = NetherPathfinder.getChunkOrDefault(this.contextPtr, chunkX, chunkZ, true);
}
return Octree.getBlock(this.chunkPtr, x & 0xF, y & 0x7F, z & 0xF);
return Octree.getBlock(this.chunkPtr, x & 0xF, y, z & 0xF);
}
}
Loading