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 all 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<>(true);

/**
* 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> elytraUseCache = new Setting<>(true);

/**
* A map of lowercase setting field names to their respective setting
*/
Expand Down
25 changes: 15 additions & 10 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 All @@ -85,7 +82,11 @@ public void execute(String label, IArgConsumer args) throws CommandException {
final String action = args.getString();
switch (action) {
case "reset": {
elytra.resetState();
try {
elytra.resetState();
} catch (IllegalArgumentException ex) {
throw new CommandInvalidStateException(ex.getMessage());
}
logDirect("Reset state but still flying to same goal");
break;
}
Expand Down Expand Up @@ -128,22 +129,26 @@ private Component suggest2b2tSeeds() {
private void gatekeep() {
MutableComponent gatekeep = Component.literal("");
gatekeep.append("To disable this message, enable the setting elytraTermsAccepted\n");
gatekeep.append("Baritone Elytra is an experimental feature. It is only intended for long distance travel in the Nether using fireworks for vanilla boost. It will not work with any other mods (\"hacks\") for non-vanilla boost. ");
gatekeep.append("Baritone Elytra is an experimental feature. It is intended for long distance travel in the Nether but will also work in the Overworld, using fireworks for vanilla boost. It will not work with any other mods (\"hacks\") for non-vanilla boost. ");
MutableComponent gatekeep2 = Component.literal("If you want Baritone to attempt to take off from the ground for you, you can enable the elytraAutoJump setting (not advisable on laggy servers!). ");
gatekeep2.setStyle(gatekeep2.getStyle().withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(Baritone.settings().prefix.value + "set elytraAutoJump true"))));
gatekeep.append(gatekeep2);
MutableComponent gatekeep3 = Component.literal("If you want Baritone to go slower, enable the elytraConserveFireworks setting and/or decrease the elytraFireworkSpeed setting. ");
gatekeep3.setStyle(gatekeep3.getStyle().withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(Baritone.settings().prefix.value + "set elytraConserveFireworks true\n" + Baritone.settings().prefix.value + "set elytraFireworkSpeed 0.6\n(the 0.6 number is just an example, tweak to your liking)"))));
gatekeep.append(gatekeep3);
MutableComponent gatekeep4 = Component.literal("Baritone Elytra ");
MutableComponent red = Component.literal("wants to know the seed");
red.setStyle(red.getStyle().withColor(ChatFormatting.RED).withUnderlined(true).withBold(true));
gatekeep4.append(red);
MutableComponent gatekeep4 = Component.literal("Baritone Elytra for use in the ");
MutableComponent red1 = Component.literal("Nether");
red1.setStyle(red1.getStyle().withColor(ChatFormatting.RED).withUnderlined(true).withBold(true));
gatekeep4.append(red1);
gatekeep4.append(", ");
MutableComponent red2 = Component.literal("wants to know the seed");
red2.setStyle(red2.getStyle().withColor(ChatFormatting.RED).withUnderlined(true).withBold(true));
gatekeep4.append(red2);
gatekeep4.append(" of the world you are in. If it doesn't have the correct seed, it will frequently backtrack. It uses the seed to generate terrain far beyond what you can see, since terrain obstacles in the Nether can be much larger than your render distance. ");
gatekeep.append(gatekeep4);
gatekeep.append("\n");
if (detectOn2b2t()) {
MutableComponent gatekeep5 = Component.literal("It looks like you're on 2b2t. ");
MutableComponent gatekeep5 = Component.literal("It looks like you're on 2b2t. Terrain prediction can be used but new nether terrain can not be predicted on 2b2t. ");
gatekeep5.append(suggest2b2tSeeds());
if (!Baritone.settings().elytraPredictTerrain.value) {
gatekeep5.append(Baritone.settings().prefix.value + "elytraPredictTerrain is currently disabled. ");
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/baritone/process/CustomGoalProcess.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import baritone.api.process.PathingCommand;
import baritone.api.process.PathingCommandType;
import baritone.utils.BaritoneProcessHelper;
import net.minecraft.ChatFormatting;

/**
* As set by ExampleBaritoneControl or something idk
Expand Down Expand Up @@ -57,7 +58,11 @@ public void setGoal(Goal goal) {
this.goal = goal;
this.mostRecentGoal = goal;
if (baritone.getElytraProcess().isActive()) {
baritone.getElytraProcess().pathTo(goal);
try {
baritone.getElytraProcess().pathTo(goal);
} catch (IllegalArgumentException e) {
logDirect("Failed to update elytra goal because: " + e.getMessage(), ChatFormatting.RED);
}
}
if (this.state == State.NONE) {
this.state = State.GOAL_SET;
Expand Down
129 changes: 105 additions & 24 deletions src/main/java/baritone/process/ElytraProcess.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import baritone.utils.BaritoneProcessHelper;
import baritone.utils.PathingCommandContext;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.NonNullList;
import net.minecraft.world.entity.EquipmentSlot;
Expand All @@ -54,6 +55,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 All @@ -68,6 +71,7 @@ public class ElytraProcess extends BaritoneProcessHelper implements IBaritonePro
private Goal goal;
private ElytraBehavior behavior;
private boolean predictingTerrain;
private boolean allowTight;

@Override
public void onLostControl() {
Expand Down Expand Up @@ -109,15 +113,25 @@ public void resetState() {

@Override
public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) {
final long seedSetting = Baritone.settings().elytraNetherSeed.value;
if (seedSetting != this.behavior.context.getSeed()) {
logDirect("Nether seed changed, recalculating path");
this.resetState();
}
if (predictingTerrain != Baritone.settings().elytraPredictTerrain.value) {
logDirect("elytraPredictTerrain setting changed, recalculating path");
predictingTerrain = Baritone.settings().elytraPredictTerrain.value;
this.resetState();
try {
final long seedSetting = Baritone.settings().elytraNetherSeed.value;
if (seedSetting != this.behavior.context.getSeed()) {
logDirect("Nether seed changed, recalculating path");
this.resetState();
}
if (predictingTerrain != Baritone.settings().elytraPredictTerrain.value && ctx.player().level.dimension() == Level.NETHER) {
logDirect("elytraPredictTerrain setting changed, recalculating path from scratch");
predictingTerrain = Baritone.settings().elytraPredictTerrain.value;
this.resetState();
}
if (allowTight != Baritone.settings().elytraAllowTightSpaces.value) {
logDirect("elytraAllowTightSpaces setting changed, recalculating path from scratch");
allowTight = Baritone.settings().elytraAllowTightSpaces.value;
this.resetState();
}
} catch (IllegalArgumentException e) {
logDirect(e.getMessage(), ChatFormatting.RED);
return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL);
}

this.behavior.onTick();
Expand Down Expand Up @@ -178,7 +192,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 +203,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 @@ -318,15 +337,27 @@ public BlockPos currentDestination() {

@Override
public void pathTo(BlockPos destination) {
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 (destination.getY() < minY || destination.getY() >= 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.pathTo0(destination, false);
}

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.allowTight = Baritone.settings().elytraAllowTightSpaces.value;
this.behavior = new ElytraBehavior(this.baritone, this, destination, appendDestination);
if (ctx.world() != null) {
this.behavior.repackChunks();
Expand All @@ -342,6 +373,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 +384,7 @@ 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");
}

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

Expand Down Expand Up @@ -465,12 +495,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) {
DimensionType dimType = dim.dimensionType();
int minY = dimType.minY();
int maxY = (dim.dimension() == Level.NETHER && !Baritone.settings().elytraAllowAboveRoof.value) ? 127 : 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
|| block == Blocks.OBSIDIAN || block == Blocks.COBBLESTONE;
}

private boolean isSafeBlock(BlockPos pos) {
Expand Down Expand Up @@ -520,7 +558,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 +578,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 +617,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;
}
}
Loading