diff --git a/.gitignore b/.gitignore index 50a166bce..191a4edc5 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,8 @@ baritone_Client.launch .vscode/launch.json .architectury-transformer -mapping \ No newline at end of file + +mapping + +libs/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar +libs/java-objc-bridge-1.1.jar diff --git a/README.md b/README.md index c6a90ff93..bc3552c5a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,8 @@ GitHub commits Code size GitHub repo size - Lines of Code + Lines of Code + yes

@@ -45,8 +46,8 @@

- forthebadge - forthebadge + forthebadge + forthebadge

A Minecraft pathfinder bot. @@ -59,16 +60,18 @@ Baritone is the pathfinding system used in [Impact](https://impactclient.net/) s | Forge | Fabric | |---------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------| -| [1.12.2 Forge](https://github.com/cabaletta/baritone/releases/download/v1.2.17/baritone-api-forge-1.2.17.jar) | | -| [1.16.5 Forge](https://github.com/cabaletta/baritone/releases/download/v1.6.4/baritone-api-forge-1.6.4.jar) | [1.16.5 Fabric](https://github.com/cabaletta/baritone/releases/download/v1.6.4/baritone-api-fabric-1.6.4.jar) | +| [1.12.2 Forge](https://github.com/cabaletta/baritone/releases/download/v1.2.18/baritone-api-forge-1.2.18.jar) | | +| [1.16.5 Forge](https://github.com/cabaletta/baritone/releases/download/v1.6.5/baritone-api-forge-1.6.5.jar) | [1.16.5 Fabric](https://github.com/cabaletta/baritone/releases/download/v1.6.5/baritone-api-fabric-1.6.5.jar) | | [1.17.1 Forge](https://github.com/cabaletta/baritone/releases/download/v1.7.3/baritone-api-forge-1.7.3.jar) | [1.17.1 Fabric](https://github.com/cabaletta/baritone/releases/download/v1.7.3/baritone-api-fabric-1.7.3.jar) | -| [1.18.2 Forge](https://github.com/cabaletta/baritone/releases/download/v1.8.4/baritone-api-forge-1.8.4.jar) | [1.18.2 Fabric](https://github.com/cabaletta/baritone/releases/download/v1.8.4/baritone-api-fabric-1.8.4.jar) | +| [1.18.2 Forge](https://github.com/cabaletta/baritone/releases/download/v1.8.5/baritone-api-forge-1.8.5.jar) | [1.18.2 Fabric](https://github.com/cabaletta/baritone/releases/download/v1.8.5/baritone-api-fabric-1.8.5.jar) | | [1.19.2 Forge](https://github.com/cabaletta/baritone/releases/download/v1.9.4/baritone-api-forge-1.9.4.jar) | [1.19.2 Fabric](https://github.com/cabaletta/baritone/releases/download/v1.9.4/baritone-api-fabric-1.9.4.jar) | | [1.19.3 Forge](https://github.com/cabaletta/baritone/releases/download/v1.9.1/baritone-api-forge-1.9.1.jar) | [1.19.3 Fabric](https://github.com/cabaletta/baritone/releases/download/v1.9.1/baritone-api-fabric-1.9.1.jar) | | [1.19.4 Forge](https://github.com/cabaletta/baritone/releases/download/v1.9.3/baritone-api-forge-1.9.3.jar) | [1.19.4 Fabric](https://github.com/cabaletta/baritone/releases/download/v1.9.3/baritone-api-fabric-1.9.3.jar) | | [1.20.1 Forge](https://github.com/cabaletta/baritone/releases/download/v1.10.1/baritone-api-forge-1.10.1.jar) | [1.20.1 Fabric](https://github.com/cabaletta/baritone/releases/download/v1.10.1/baritone-api-fabric-1.10.1.jar) | -**How to immediately get started:** Type `#goto 1000 500` in chat to go to x=1000 z=500. Type `#mine diamond_ore` to mine diamond ore. Type `#stop` to stop. For more, read [the usage page](USAGE.md) and/or watch this [tutorial playlist](https://www.youtube.com/playlist?list=PLnwnJ1qsS7CoQl9Si-RTluuzCo_4Oulpa) +**Message for 2b2t players looking for 1.19/1.20 Baritone** Download it from right above ^. But also please check back in a few days for Baritone Elytra ([vid 1](https://youtu.be/4bGGPo8yiHo) [vid 2](https://www.youtube.com/watch?v=pUN9nmINe3I)), which will be ported to 1.19/1.20 soon! It will work on 2b2t with its anticheat, that was the whole point of Baritone Elytra (it's fully vanilla compatible). Also join [**the discord**](http://discord.gg/s6fRBAUpmr). Thanks! + +**How to immediately get started:** Type `#goto 1000 500` in chat to go to x=1000 z=500. Type `#mine diamond_ore` to mine diamond ore. Type `#stop` to stop. For more, read [the usage page](USAGE.md) and/or watch this [tutorial playlist](https://www.youtube.com/playlist?list=PLnwnJ1qsS7CoQl9Si-RTluuzCo_4Oulpa). Also try `#elytra` for Elytra flying in the Nether using fireworks. For other versions of Minecraft or more complicated situations or for development, see [Installation & setup](SETUP.md). Also consider just installing [Impact](https://impactclient.net/), which comes with Baritone and is easier to install than wrangling with version JSONs and zips. For 1.16.5, [click here](https://www.youtube.com/watch?v=_4eVJ9Qz2J8) and see description. Once Baritone is installed, look [here](USAGE.md) for instructions on how to use it. There's a [showcase video](https://youtu.be/CZkLXWo4Fg4) made by @Adovin#6313 on Baritone which I recommend. diff --git a/build.gradle b/build.gradle index 471bb608f..b981972d3 100755 --- a/build.gradle +++ b/build.gradle @@ -59,6 +59,10 @@ allprojects { } } mavenCentral() + maven { + name = 'babbaj-repo' + url = 'https://babbaj.github.io/maven/' + } } dependencies { @@ -72,6 +76,8 @@ allprojects { implementation "org.ow2.asm:asm:9.3" // The following line declares the yarn mappings you may select this one as well. // mappings "net.fabricmc:yarn:1.17.1+build.32:v2" + //launchImplementation('dev.babbaj:nether-pathfinder:1.3.0') + implementation 'dev.babbaj:nether-pathfinder:1.3.0' } tasks.withType(JavaCompile).configureEach { diff --git a/buildSrc/src/main/java/baritone/gradle/task/ProguardTask.java b/buildSrc/src/main/java/baritone/gradle/task/ProguardTask.java index 5d80a65ea..331f22ebe 100644 --- a/buildSrc/src/main/java/baritone/gradle/task/ProguardTask.java +++ b/buildSrc/src/main/java/baritone/gradle/task/ProguardTask.java @@ -18,7 +18,6 @@ package baritone.gradle.task; import baritone.gradle.util.Determinizer; -import org.apache.commons.io.IOUtils; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.TaskAction; @@ -31,18 +30,17 @@ import xyz.wagyourtail.unimined.api.minecraft.EnvType; import xyz.wagyourtail.unimined.api.minecraft.MinecraftProvider; import java.io.*; -import java.net.MalformedURLException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; - /** * @author Brady * @since 10/11/2018 @@ -63,16 +61,22 @@ public class ProguardTask extends BaritoneGradleTask { return extract; } + private List requiredLibraries; + + private File mixin; + private File pathfinder; + @TaskAction protected void exec() throws Exception { super.doFirst(); super.verifyArtifacts(); // "Haha brady why don't you make separate tasks" - processArtifact(); downloadProguard(); extractProguard(); generateConfigs(); + acquireDependencies(); + processArtifact(); proguardApi(); proguardStandalone(); cleanup(); @@ -93,7 +97,7 @@ public class ProguardTask extends BaritoneGradleTask { Files.delete(this.artifactUnoptimizedPath); } - Determinizer.determinize(this.artifactPath.toString(), this.artifactUnoptimizedPath.toString()); + Determinizer.determinize(this.artifactPath.toString(), this.artifactUnoptimizedPath.toString(), Arrays.asList(pathfinder), false); } private void downloadProguard() throws Exception { @@ -118,8 +122,7 @@ public class ProguardTask extends BaritoneGradleTask { try { path = findJavaPathByGradleConfig(); if (path != null) return path; - } - catch (Exception ex) { + } catch (Exception ex) { System.err.println("Unable to find java by javaCompile options"); ex.printStackTrace(); } @@ -127,8 +130,7 @@ public class ProguardTask extends BaritoneGradleTask { try { path = findJavaByJavaHome(); if (path != null) return path; - } - catch(Exception ex) { + } catch (Exception ex) { System.err.println("Unable to find java by JAVA_HOME"); ex.printStackTrace(); } @@ -136,30 +138,23 @@ public class ProguardTask extends BaritoneGradleTask { path = findJavaByGradleCurrentRuntime(); if (path != null) return path; - + throw new Exception("Unable to find java to determine ProGuard libraryjars. Please specify forkOptions.executable in javaCompile," + " JAVA_HOME environment variable, or make sure to run Gradle with the correct JDK (a v1.8 only)"); } private String findJavaByGradleCurrentRuntime() { String path = Jvm.current().getJavaExecutable().getAbsolutePath(); - - if (this.validateJavaVersion(path)) { - System.out.println("Using Gradle's runtime Java for ProGuard"); - return path; - } - return null; + System.out.println("Using Gradle's runtime Java for ProGuard"); + return path; } private String findJavaByJavaHome() { final String javaHomeEnv = System.getenv("JAVA_HOME"); if (javaHomeEnv != null) { - String path = Jvm.forHome(new File(javaHomeEnv)).getJavaExecutable().getAbsolutePath(); - if (this.validateJavaVersion(path)) { - System.out.println("Detected Java path by JAVA_HOME"); - return path; - } + System.out.println("Detected Java path by JAVA_HOME"); + return path; } return null; } @@ -175,19 +170,11 @@ public class ProguardTask extends BaritoneGradleTask { if (javacPath != null) { File javacFile = new File(javacPath); if (javacFile.exists()) { - File[] maybeJava = javacFile.getParentFile().listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.equals("java"); - } - }); - + File[] maybeJava = javacFile.getParentFile().listFiles((dir, name) -> name.equals("java")); if (maybeJava != null && maybeJava.length > 0) { String path = maybeJava[0].getAbsolutePath(); - if (this.validateJavaVersion(path)) { - System.out.println("Detected Java path by forkOptions"); - return path; - } + System.out.println("Detected Java path by forkOptions"); + return path; } } } @@ -210,7 +197,7 @@ public class ProguardTask extends BaritoneGradleTask { } private void generateConfigs() throws Exception { - Files.copy(getRootRelativeFile(PROGUARD_CONFIG_TEMPLATE), getTemporaryFile(PROGUARD_CONFIG_DEST), REPLACE_EXISTING); + Files.copy(getRootRelativeFile(PROGUARD_CONFIG_TEMPLATE), getTemporaryFile(PROGUARD_CONFIG_DEST), StandardCopyOption.REPLACE_EXISTING); // Setup the template that will be used to derive the API and Standalone configs List template = Files.readAllLines(getTemporaryFile(PROGUARD_CONFIG_DEST)); @@ -233,11 +220,11 @@ public class ProguardTask extends BaritoneGradleTask { { // Discover all of the libraries that we will need to acquire from gradle final Stream dependencies = acquireDependencies() - // remove MCP mapped jar, and nashorn - .filter(f -> !f.toString().endsWith("-recomp.jar") && !f.getName().startsWith("nashorn") && !f.getName().startsWith("coremods")); + // remove MCP mapped jar, and nashorn + .filter(f -> !f.toString().endsWith("-recomp.jar") && !f.getName().startsWith("nashorn") && !f.getName().startsWith("coremods")); libraries = dependencies - .map(f -> isMcJar(f) ? mcJar : f); + .map(f -> isMcJar(f) ? mcJar : f); } libraries.forEach(f -> { template.add(2, "-libraryjars '" + f + "'"); @@ -250,29 +237,48 @@ public class ProguardTask extends BaritoneGradleTask { api.add(2, "-printmapping " + new File(this.getRootRelativeFile(PROGUARD_MAPPING_DIR).toFile(), "mappings-" + addCompTypeFirst("api.txt"))); // API config doesn't require any changes from the changes that we made to the template - Files.write(getTemporaryFile(compType+PROGUARD_API_CONFIG), api); + Files.write(getTemporaryFile(compType + PROGUARD_API_CONFIG), api); // For the Standalone config, don't keep the API package List standalone = new ArrayList<>(template); standalone.removeIf(s -> s.contains("# this is the keep api")); standalone.add(2, "-printmapping " + new File(this.getRootRelativeFile(PROGUARD_MAPPING_DIR).toFile(), "mappings-" + addCompTypeFirst("standalone.txt"))); - Files.write(getTemporaryFile(compType+PROGUARD_STANDALONE_CONFIG), standalone); + Files.write(getTemporaryFile(compType + PROGUARD_STANDALONE_CONFIG), standalone); } private Stream acquireDependencies() { return getProject().getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().findByName("main").getCompileClasspath().getFiles() - .stream() - .filter(File::isFile); + .stream() + .filter(File::isFile); } private void proguardApi() throws Exception { - runProguard(getTemporaryFile(compType+PROGUARD_API_CONFIG)); - Determinizer.determinize(this.proguardOut.toString(), this.artifactApiPath.toString()); + runProguard(getTemporaryFile(compType + PROGUARD_API_CONFIG)); + Determinizer.determinize(this.proguardOut.toString(), this.artifactApiPath.toString(), Arrays.asList(pathfinder), false); } private void proguardStandalone() throws Exception { - runProguard(getTemporaryFile(compType+PROGUARD_STANDALONE_CONFIG)); - Determinizer.determinize(this.proguardOut.toString(), this.artifactStandalonePath.toString()); + runProguard(getTemporaryFile(compType + PROGUARD_STANDALONE_CONFIG)); + Determinizer.determinize(this.proguardOut.toString(), this.artifactStandalonePath.toString(), Arrays.asList(pathfinder), false); + } + + private static final class Pair { + public final A a; + public final B b; + + private Pair(final A a, final B b) { + this.a = a; + this.b = b; + } + + @Override + public String toString() { + return "Pair{" + + "a=" + this.a + + ", " + + "b=" + this.b + + '}'; + } } private void cleanup() { @@ -285,9 +291,11 @@ public class ProguardTask extends BaritoneGradleTask { this.url = url; } + public void setExtract(String extract) { this.extract = extract; } + private void runProguard(Path config) throws Exception { // Delete the existing proguard output file. Proguard probably handles this already, but why not do it ourselves if (Files.exists(this.proguardOut)) { @@ -298,7 +306,7 @@ public class ProguardTask extends BaritoneGradleTask { Path workingDirectory = getTemporaryFile(""); Path proguardJar = workingDirectory.relativize(getTemporaryFile(PROGUARD_JAR)); config = workingDirectory.relativize(config); - + // Honestly, if you still have spaces in your path at this point, you're SOL. Process p = new ProcessBuilder("java", "-jar", proguardJar.toString(), "@" + config.toString()) @@ -312,6 +320,7 @@ public class ProguardTask extends BaritoneGradleTask { // Halt the current thread until the process is complete, if the exit code isn't 0, throw an exception int exitCode = p.waitFor(); if (exitCode != 0) { + Thread.sleep(1000); throw new IllegalStateException("Proguard exited with code " + exitCode); } } diff --git a/buildSrc/src/main/java/baritone/gradle/util/Determinizer.java b/buildSrc/src/main/java/baritone/gradle/util/Determinizer.java index fc268cd35..781aaec24 100644 --- a/buildSrc/src/main/java/baritone/gradle/util/Determinizer.java +++ b/buildSrc/src/main/java/baritone/gradle/util/Determinizer.java @@ -39,10 +39,11 @@ import java.util.stream.Collectors; */ public class Determinizer { - public static void determinize(String inputPath, String outputPath) throws IOException { + public static void determinize(String inputPath, String outputPath, List toInclude, boolean doForgeReplacementOfMetaInf) throws IOException { System.out.println("Running Determinizer"); System.out.println(" Input path: " + inputPath); System.out.println(" Output path: " + outputPath); + System.out.println(" Shade: " + toInclude); try ( JarFile jarFile = new JarFile(new File(inputPath)); @@ -64,14 +65,35 @@ public class Determinizer { clone.setTime(42069); jos.putNextEntry(clone); if (entry.getName().endsWith(".refmap.json")) { - JsonObject object = new JsonParser().parse(new InputStreamReader(jarFile.getInputStream(entry))).getAsJsonObject(); - jos.write(writeSorted(object).getBytes()); + JsonElement json = new JsonParser().parse(new InputStreamReader(jarFile.getInputStream(entry))); + jos.write(writeSorted(json).getBytes()); + } else if (entry.getName().equals("META-INF/MANIFEST.MF") && doForgeReplacementOfMetaInf) { // only replace for forge jar + ByteArrayOutputStream cancer = new ByteArrayOutputStream(); + copy(jarFile.getInputStream(entry), cancer); + String manifest = new String(cancer.toByteArray()); + if (!manifest.contains("baritone.launch.BaritoneTweaker")) { + throw new IllegalStateException("unable to replace"); + } + manifest = manifest.replace("baritone.launch.BaritoneTweaker", "org.spongepowered.asm.launch.MixinTweaker"); + jos.write(manifest.getBytes()); } else { copy(jarFile.getInputStream(entry), jos); } } + for (File file : toInclude) { + try (JarFile mixin = new JarFile(file)) { + for (JarEntry entry : mixin.stream().sorted(Comparator.comparing(JarEntry::getName)).collect(Collectors.toList())) { + if (entry.getName().startsWith("META-INF") && !entry.getName().startsWith("META-INF/services")) { + continue; + } + jos.putNextEntry(entry); + copy(mixin.getInputStream(entry), jos); + } + } + } jos.finish(); } + System.out.println("Done with determinizer"); } private static void copy(InputStream is, OutputStream os) throws IOException { @@ -82,7 +104,7 @@ public class Determinizer { } } - private static String writeSorted(JsonObject in) throws IOException { + private static String writeSorted(JsonElement in) throws IOException { StringWriter writer = new StringWriter(); JsonWriter jw = new JsonWriter(writer); ORDERED_JSON_WRITER.write(jw, in); diff --git a/gradlew b/gradlew index 4f906e0c8..744e882ed 100755 --- a/gradlew +++ b/gradlew @@ -72,7 +72,7 @@ case "`uname`" in Darwin* ) darwin=true ;; - MINGW* ) + MSYS* | MINGW* ) msys=true ;; NONSTOP* ) diff --git a/src/api/java/baritone/api/IBaritone.java b/src/api/java/baritone/api/IBaritone.java index 0cc73b0d1..3c9681532 100644 --- a/src/api/java/baritone/api/IBaritone.java +++ b/src/api/java/baritone/api/IBaritone.java @@ -88,6 +88,12 @@ public interface IBaritone { */ IGetToBlockProcess getGetToBlockProcess(); + /** + * @return The {@link IElytraProcess} instance + * @see IElytraProcess + */ + IElytraProcess getElytraProcess(); + /** * @return The {@link IWorldProvider} instance * @see IWorldProvider diff --git a/src/api/java/baritone/api/IBaritoneProvider.java b/src/api/java/baritone/api/IBaritoneProvider.java index 5eb0ba86a..1daec2ef5 100644 --- a/src/api/java/baritone/api/IBaritoneProvider.java +++ b/src/api/java/baritone/api/IBaritoneProvider.java @@ -22,6 +22,7 @@ import baritone.api.command.ICommand; import baritone.api.command.ICommandSystem; import baritone.api.schematic.ISchematicSystem; import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.player.LocalPlayer; import java.util.List; @@ -82,6 +83,22 @@ public interface IBaritoneProvider { return null; } + /** + * Provides the {@link IBaritone} instance for the player with the specified connection. + * + * @param connection The connection + * @return The {@link IBaritone} instance. + */ + default IBaritone getBaritoneForConnection(ClientPacketListener connection) { + for (IBaritone baritone : this.getAllBaritones()) { + final LocalPlayer player = baritone.getPlayerContext().player(); + if (player != null && player.connection == connection) { + return baritone; + } + } + return null; + } + /** * Creates and registers a new {@link IBaritone} instance using the specified {@link Minecraft}. The existing * instance is returned if already registered. diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index 26600a51a..177c8988d 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -29,6 +29,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.world.item.Item; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; + import java.awt.*; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -37,8 +38,9 @@ import java.lang.annotation.Target; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.*; import java.util.List; +import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -738,6 +740,26 @@ public final class Settings { */ public final Setting blockFreeLook = new Setting<>(false); + /** + * Automatically elytra fly without having to force the client-sided rotations. + */ + public final Setting elytraFreeLook = new Setting<>(false); + + /** + * Forces the client-sided yaw rotation to an average of the last {@link #smoothLookTicks} of server-sided rotations. + */ + public final Setting smoothLook = new Setting<>(false); + + /** + * Same as {@link #smoothLook} but for elytra flying. + */ + public final Setting elytraSmoothLook = new Setting<>(true); + + /** + * The number of ticks to average across for {@link #smoothLook}; + */ + public final Setting smoothLookTicks = new Setting<>(5); + /** * When true, the player will remain with its existing look direction as often as possible. * Although, in some cases this can get it stuck, hence this setting to disable that behavior. @@ -1187,8 +1209,8 @@ public final class Settings { */ @JavaOnly public final Setting> logger = new Setting<>((msg) -> { - final GuiMessageTag tag = useMessageTag.value ? Helper.MESSAGE_TAG : null; - Minecraft.getInstance().gui.getChat().addMessage(msg, null, tag); + final GuiMessageTag tag = useMessageTag.value ? Helper.MESSAGE_TAG : null; + Minecraft.getInstance().gui.getChat().addMessage(msg, null, tag); }); /** @@ -1337,6 +1359,115 @@ public final class Settings { */ public final Setting notificationOnMineFail = new Setting<>(true); + /** + * The number of ticks of elytra movement to simulate while firework boost is not active. Higher values are + * computationally more expensive. + */ + public final Setting elytraSimulationTicks = new Setting<>(20); + + /** + * The maximum allowed deviation in pitch from a direct line-of-sight to the flight target. Higher values are + * computationally more expensive. + */ + public final Setting elytraPitchRange = new Setting<>(25); + + /** + * The minimum speed that the player can drop to (in blocks/tick) before a firework is automatically deployed. + */ + public final Setting elytraFireworkSpeed = new Setting<>(1.2); + + /** + * The delay after the player's position is set-back by the server that a firework may be automatically deployed. + * Value is in ticks. + */ + public final Setting elytraFireworkSetbackUseDelay = new Setting<>(15); + + /** + * The minimum padding value that is added to the player's hitbox when considering which point to fly to on the + * path. High values can result in points not being considered which are otherwise safe to fly to. Low values can + * result in flight paths which are extremely tight, and there's the possibility of crashing due to getting too low + * to the ground. + */ + public final Setting elytraMinimumAvoidance = new Setting<>(0.2); + + /** + * If enabled, avoids using fireworks when descending along the flight path. + */ + public final Setting elytraConserveFireworks = new Setting<>(false); + + /** + * Renders the raytraces that are performed by the elytra fly calculation. + */ + public final Setting elytraRenderRaytraces = new Setting<>(false); + + /** + * Renders the raytraces that are used in the hitbox part of the elytra fly calculation. + * Requires {@link #elytraRenderRaytraces}. + */ + public final Setting elytraRenderHitboxRaytraces = new Setting<>(false); + + /** + * Renders the best elytra flight path that was simulated each tick. + */ + public final Setting elytraRenderSimulation = new Setting<>(true); + + /** + * Automatically path to and jump off of ledges to initiate elytra flight when grounded. + */ + public final Setting elytraAutoJump = new Setting<>(false); + + /** + * The seed used to generate chunks for long distance elytra path-finding in the nether. + * Defaults to 2b2t's nether seed. + */ + public final Setting elytraNetherSeed = new Setting<>(146008555100680L); + + /** + * Whether nether-pathfinder should generate terrain based on {@link #elytraNetherSeed}. + * If false all chunks that haven't been loaded are assumed to be air. + */ + public final Setting elytraPredictTerrain = new Setting<>(true); + + /** + * Automatically swap the current elytra with a new one when the durability gets too low + */ + public final Setting elytraAutoSwap = new Setting<>(true); + + /** + * The minimum durability an elytra can have before being swapped + */ + public final Setting elytraMinimumDurability = new Setting<>(5); + + /** + * The minimum fireworks before landing early for safety + */ + public final Setting elytraMinFireworksBeforeLanding = new Setting<>(5); + + /** + * Automatically land when elytra is almost out of durability, or almost out of fireworks + */ + public final Setting elytraAllowEmergencyLand = new Setting<>(true); + + /** + * Time between culling far away chunks from the nether pathfinder chunk cache + */ + public final Setting elytraTimeBetweenCacheCullSecs = new Setting<>(TimeUnit.MINUTES.toSeconds(3)); + + /** + * Maximum distance chunks can be before being culled from the nether pathfinder chunk cache + */ + public final Setting elytraCacheCullDistance = new Setting<>(5000); + + /** + * Should elytra consider nether brick a valid landing block + */ + public final Setting elytraAllowLandOnNetherFortress = new Setting<>(false); + + /** + * Has the user read and understood the elytra terms and conditions + */ + public final Setting elytraTermsAccepted = new Setting<>(false); + /** * A map of lowercase setting field names to their respective setting */ diff --git a/src/api/java/baritone/api/command/exception/CommandUnhandledException.java b/src/api/java/baritone/api/command/exception/CommandUnhandledException.java index d615ff2cd..a1f826262 100644 --- a/src/api/java/baritone/api/command/exception/CommandUnhandledException.java +++ b/src/api/java/baritone/api/command/exception/CommandUnhandledException.java @@ -19,8 +19,8 @@ package baritone.api.command.exception; import baritone.api.command.ICommand; import baritone.api.command.argument.ICommandArgument; + import java.util.List; -import net.minecraft.ChatFormatting; import static baritone.api.utils.Helper.HELPER; @@ -36,10 +36,6 @@ public class CommandUnhandledException extends RuntimeException implements IComm @Override public void handle(ICommand command, List args) { - HELPER.logDirect("An unhandled exception occurred. " + - "The error is in your game's log, please report this at https://github.com/cabaletta/baritone/issues", - ChatFormatting.RED); - - this.printStackTrace(); + HELPER.logUnhandledException(this); } } diff --git a/src/api/java/baritone/api/event/events/BlockChangeEvent.java b/src/api/java/baritone/api/event/events/BlockChangeEvent.java new file mode 100644 index 000000000..4fc496eb5 --- /dev/null +++ b/src/api/java/baritone/api/event/events/BlockChangeEvent.java @@ -0,0 +1,47 @@ +/* + * 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.api.event.events; + +import baritone.api.utils.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.List; + +/** + * @author Brady + */ +public final class BlockChangeEvent { + + private final ChunkPos chunk; + private final List> blocks; + + public BlockChangeEvent(ChunkPos pos, List> blocks) { + this.chunk = pos; + this.blocks = blocks; + } + + public ChunkPos getChunkPos() { + return this.chunk; + } + + public List> getBlocks() { + return this.blocks; + } +} diff --git a/src/api/java/baritone/api/event/events/ChunkEvent.java b/src/api/java/baritone/api/event/events/ChunkEvent.java index f27475bce..bb22a47b1 100644 --- a/src/api/java/baritone/api/event/events/ChunkEvent.java +++ b/src/api/java/baritone/api/event/events/ChunkEvent.java @@ -57,31 +57,38 @@ public final class ChunkEvent { /** * @return The state of the event */ - public final EventState getState() { + public EventState getState() { return this.state; } /** * @return The type of chunk event that occurred; */ - public final Type getType() { + public Type getType() { return this.type; } /** * @return The Chunk X position. */ - public final int getX() { + public int getX() { return this.x; } /** * @return The Chunk Z position. */ - public final int getZ() { + public int getZ() { return this.z; } + /** + * @return {@code true} if the event was fired after a chunk population + */ + public boolean isPostPopulate() { + return this.state == EventState.POST && this.type.isPopulate(); + } + public enum Type { /** @@ -106,6 +113,10 @@ public final class ChunkEvent { *

* And it's a partial chunk */ - POPULATE_PARTIAL + POPULATE_PARTIAL; + + public final boolean isPopulate() { + return this == POPULATE_FULL || this == POPULATE_PARTIAL; + } } } diff --git a/src/api/java/baritone/api/event/events/TickEvent.java b/src/api/java/baritone/api/event/events/TickEvent.java index 5c484ae49..405ad3c49 100644 --- a/src/api/java/baritone/api/event/events/TickEvent.java +++ b/src/api/java/baritone/api/event/events/TickEvent.java @@ -18,9 +18,18 @@ package baritone.api.event.events; import baritone.api.event.events.type.EventState; +import net.minecraft.client.Minecraft; import java.util.function.BiFunction; +/** + * Called on and after each game tick of the primary {@link Minecraft} instance and dispatched to all Baritone + * instances. + *

+ * When {@link #state} is {@link EventState#PRE}, the event is being called just prior to when the current in-game + * screen is ticked. When {@link #state} is {@link EventState#POST}, the event is being called at the very end + * of the {@link Minecraft#runTick()} method. + */ public final class TickEvent { private static int overallTickCount; diff --git a/src/api/java/baritone/api/event/listener/AbstractGameEventListener.java b/src/api/java/baritone/api/event/listener/AbstractGameEventListener.java index 9eac8de46..54aabe387 100644 --- a/src/api/java/baritone/api/event/listener/AbstractGameEventListener.java +++ b/src/api/java/baritone/api/event/listener/AbstractGameEventListener.java @@ -33,6 +33,9 @@ public interface AbstractGameEventListener extends IGameEventListener { @Override default void onTick(TickEvent event) {} + @Override + default void onPostTick(TickEvent event) {} + @Override default void onPlayerUpdate(PlayerUpdateEvent event) {} @@ -45,6 +48,9 @@ public interface AbstractGameEventListener extends IGameEventListener { @Override default void onChunkEvent(ChunkEvent event) {} + @Override + default void onBlockChange(BlockChangeEvent event) {} + @Override default void onRenderPass(RenderEvent event) {} diff --git a/src/api/java/baritone/api/event/listener/IGameEventListener.java b/src/api/java/baritone/api/event/listener/IGameEventListener.java index a34ea4ef3..6939f8d66 100644 --- a/src/api/java/baritone/api/event/listener/IGameEventListener.java +++ b/src/api/java/baritone/api/event/listener/IGameEventListener.java @@ -40,6 +40,14 @@ public interface IGameEventListener { */ void onTick(TickEvent event); + /** + * Run once per game tick after the tick is completed + * + * @param event The event + * @see Minecraft#runTick() + */ + void onPostTick(TickEvent event); + /** * Run once per game tick from before and after the player rotation is sent to the server. * @@ -70,6 +78,13 @@ public interface IGameEventListener { */ void onChunkEvent(ChunkEvent event); + /** + * Runs after a single or multi block change packet is received and processed. + * + * @param event The event + */ + void onBlockChange(BlockChangeEvent event); + /** * Runs once per world render pass. * diff --git a/src/api/java/baritone/api/process/ICustomGoalProcess.java b/src/api/java/baritone/api/process/ICustomGoalProcess.java index 5084aff2f..2bae55ce3 100644 --- a/src/api/java/baritone/api/process/ICustomGoalProcess.java +++ b/src/api/java/baritone/api/process/ICustomGoalProcess.java @@ -38,6 +38,11 @@ public interface ICustomGoalProcess extends IBaritoneProcess { */ Goal getGoal(); + /** + * @return The most recent set goal, which doesn't invalidate upon {@link #onLostControl()} + */ + Goal mostRecentGoal(); + /** * Sets the goal and begins the path execution. * diff --git a/src/api/java/baritone/api/process/IElytraProcess.java b/src/api/java/baritone/api/process/IElytraProcess.java new file mode 100644 index 000000000..28328f901 --- /dev/null +++ b/src/api/java/baritone/api/process/IElytraProcess.java @@ -0,0 +1,50 @@ +/* + * 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.api.process; + +import baritone.api.pathing.goals.Goal; +import net.minecraft.core.BlockPos; + +public interface IElytraProcess extends IBaritoneProcess { + + void repackChunks(); + + /** + * @return Where it is currently flying to, null if not active + */ + BlockPos currentDestination(); + + void pathTo(BlockPos destination); + + void pathTo(Goal destination); + + /** + * Resets the state of the process but will maintain the same destination and will try to keep flying + */ + void resetState(); + + /** + * @return {@code true} if the native library loaded and elytra is actually usable + */ + boolean isLoaded(); + + /* + * FOR INTERNAL USE ONLY. MAY BE REMOVED AT ANY TIME. + */ + boolean isSafeToCancel(); +} diff --git a/src/api/java/baritone/api/process/PathingCommandType.java b/src/api/java/baritone/api/process/PathingCommandType.java index af25591af..cde38eaf2 100644 --- a/src/api/java/baritone/api/process/PathingCommandType.java +++ b/src/api/java/baritone/api/process/PathingCommandType.java @@ -56,5 +56,10 @@ public enum PathingCommandType { /** * Go and ask the next process what to do */ - DEFER + DEFER, + + /** + * Sets the goal and calculates a path, but pauses instead of immediately starting the path. + */ + SET_GOAL_AND_PAUSE } diff --git a/src/api/java/baritone/api/utils/BetterBlockPos.java b/src/api/java/baritone/api/utils/BetterBlockPos.java index cee7649b0..5add76555 100644 --- a/src/api/java/baritone/api/utils/BetterBlockPos.java +++ b/src/api/java/baritone/api/utils/BetterBlockPos.java @@ -34,6 +34,15 @@ import net.minecraft.util.Mth; */ public final class BetterBlockPos extends BlockPos { + private static final int NUM_X_BITS = 26; + private static final int NUM_Z_BITS = NUM_X_BITS; + private static final int NUM_Y_BITS = 64 - NUM_X_BITS - NUM_Z_BITS; + private static final int Y_SHIFT = NUM_Z_BITS; + private static final int X_SHIFT = Y_SHIFT + NUM_Y_BITS; + private static final long X_MASK = (1L << NUM_X_BITS) - 1L; + private static final long Y_MASK = (1L << NUM_Y_BITS) - 1L; + private static final long Z_MASK = (1L << NUM_Z_BITS) - 1L; + public static final BetterBlockPos ORIGIN = new BetterBlockPos(0, 0, 0); public final int x; @@ -201,6 +210,20 @@ public final class BetterBlockPos extends BlockPos { return amt == 0 ? this : new BetterBlockPos(x - amt, y, z); } + public double distanceSq(final BetterBlockPos to) { + double dx = (double) this.x - to.x; + double dy = (double) this.y - to.y; + double dz = (double) this.z - to.z; + return dx * dx + dy * dy + dz * dz; + } + + public double distanceTo(final BetterBlockPos to) { + double dx = (double) this.x - to.x; + double dy = (double) this.y - to.y; + double dz = (double) this.z - to.z; + return Math.sqrt(dx * dx + dy * dy + dz * dz); + } + @Override @Nonnull public String toString() { @@ -211,4 +234,15 @@ public final class BetterBlockPos extends BlockPos { SettingsUtil.maybeCensor(z) ); } + + public static long serializeToLong(final int x, final int y, final int z) { + return ((long) x & X_MASK) << X_SHIFT | ((long) y & Y_MASK) << Y_SHIFT | ((long) z & Z_MASK); + } + + public static BetterBlockPos deserializeFromLong(final long serialized) { + final int x = (int) (serialized << 64 - X_SHIFT - NUM_X_BITS >> 64 - NUM_X_BITS); + final int y = (int) (serialized << 64 - Y_SHIFT - NUM_Y_BITS >> 64 - NUM_Y_BITS); + final int z = (int) (serialized << 64 - NUM_Z_BITS >> 64 - NUM_Z_BITS); + return new BetterBlockPos(x, y, z); + } } diff --git a/src/api/java/baritone/api/utils/Helper.java b/src/api/java/baritone/api/utils/Helper.java index d66d035f2..507cd85a7 100755 --- a/src/api/java/baritone/api/utils/Helper.java +++ b/src/api/java/baritone/api/utils/Helper.java @@ -19,14 +19,12 @@ package baritone.api.utils; import baritone.api.BaritoneAPI; import baritone.api.Settings; -import baritone.api.utils.gui.BaritoneToast; import net.minecraft.ChatFormatting; import net.minecraft.client.GuiMessageTag; import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; -import java.awt.*; import java.util.Arrays; import java.util.Calendar; import java.util.stream.Stream; @@ -237,4 +235,11 @@ public interface Helper { default void logDirect(String message) { logDirect(message, BaritoneAPI.getSettings().logAsToast.value); } + + default void logUnhandledException(final Throwable exception) { + HELPER.logDirect("An unhandled exception occurred. " + + "The error is in your game's log, please report this at https://github.com/cabaletta/baritone/issues", + ChatFormatting.RED); + exception.printStackTrace(); + } } diff --git a/src/api/java/baritone/api/utils/IPlayerContext.java b/src/api/java/baritone/api/utils/IPlayerContext.java index 1829bb757..a97475a5b 100644 --- a/src/api/java/baritone/api/utils/IPlayerContext.java +++ b/src/api/java/baritone/api/utils/IPlayerContext.java @@ -89,6 +89,10 @@ public interface IPlayerContext { return new Vec3(player().position().x, player().position().y + player().getEyeHeight(), player().position().z); } + default Vec3 playerMotion() { + return player().getDeltaMovement(); + } + BetterBlockPos viewerPos(); default Rotation playerRotations() { diff --git a/src/api/java/baritone/api/utils/Pair.java b/src/api/java/baritone/api/utils/Pair.java new file mode 100644 index 000000000..ca7259520 --- /dev/null +++ b/src/api/java/baritone/api/utils/Pair.java @@ -0,0 +1,59 @@ +/* + * 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.api.utils; + +import java.util.Objects; + +/** + * @author Brady + */ +public final class Pair { + + private final A a; + private final B b; + + public Pair(A a, B b) { + this.a = a; + this.b = b; + } + + public A first() { + return this.a; + } + + public B second() { + return this.b; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || o.getClass() != Pair.class) { + return false; + } + Pair pair = (Pair) o; + return Objects.equals(this.a, pair.a) && Objects.equals(this.b, pair.b); + } + + @Override + public int hashCode() { + return 31 * Objects.hashCode(this.a) + Objects.hashCode(this.b); + } +} diff --git a/src/api/java/baritone/api/utils/RayTraceUtils.java b/src/api/java/baritone/api/utils/RayTraceUtils.java index 70c79be0b..e99566863 100644 --- a/src/api/java/baritone/api/utils/RayTraceUtils.java +++ b/src/api/java/baritone/api/utils/RayTraceUtils.java @@ -51,7 +51,8 @@ public final class RayTraceUtils { } else { start = entity.getEyePosition(1.0F); // do whatever is correct } - Vec3 direction = RotationUtils.calcVector3dFromRotation(rotation); + + Vec3 direction = RotationUtils.calcLookDirectionFromRotation(rotation); Vec3 end = start.add( direction.x * blockReachDistance, direction.y * blockReachDistance, diff --git a/src/api/java/baritone/api/utils/RotationUtils.java b/src/api/java/baritone/api/utils/RotationUtils.java index 5db577839..bfab3fa87 100644 --- a/src/api/java/baritone/api/utils/RotationUtils.java +++ b/src/api/java/baritone/api/utils/RotationUtils.java @@ -19,7 +19,6 @@ package baritone.api.utils; import baritone.api.BaritoneAPI; import baritone.api.IBaritone; -import java.util.Optional; import net.minecraft.client.player.LocalPlayer; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -33,6 +32,8 @@ import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; +import java.util.Optional; + /** * @author Brady * @since 9/25/2018 @@ -43,11 +44,13 @@ public final class RotationUtils { * Constant that a degree value is multiplied by to get the equivalent radian value */ public static final double DEG_TO_RAD = Math.PI / 180.0; + public static final float DEG_TO_RAD_F = (float) DEG_TO_RAD; /** * Constant that a radian value is multiplied by to get the equivalent degree value */ public static final double RAD_TO_DEG = 180.0 / Math.PI; + public static final float RAD_TO_DEG_F = (float) RAD_TO_DEG; /** * Offsets from the root block position to the center of each side. @@ -128,12 +131,17 @@ public final class RotationUtils { * @param rotation The input rotation * @return Look vector for the rotation */ - public static Vec3 calcVector3dFromRotation(Rotation rotation) { - float f = Mth.cos(-rotation.getYaw() * (float) DEG_TO_RAD - (float) Math.PI); - float f1 = Mth.sin(-rotation.getYaw() * (float) DEG_TO_RAD - (float) Math.PI); - float f2 = -Mth.cos(-rotation.getPitch() * (float) DEG_TO_RAD); - float f3 = Mth.sin(-rotation.getPitch() * (float) DEG_TO_RAD); - return new Vec3((double) (f1 * f2), (double) f3, (double) (f * f2)); + public static Vec3 calcLookDirectionFromRotation(Rotation rotation) { + float flatZ = Mth.cos((-rotation.getYaw() * DEG_TO_RAD_F) - (float) Math.PI); + float flatX = Mth.sin((-rotation.getYaw() * DEG_TO_RAD_F) - (float) Math.PI); + float pitchBase = -Mth.cos(-rotation.getPitch() * DEG_TO_RAD_F); + float pitchHeight = Mth.sin(-rotation.getPitch() * DEG_TO_RAD_F); + return new Vec3(flatX * pitchBase, pitchHeight, flatZ * pitchBase); + } + + @Deprecated + public static Vec3 calcVec3dFromRotation(Rotation rotation) { + return calcLookDirectionFromRotation(rotation); } /** diff --git a/src/launch/java/baritone/launch/mixins/MixinClientPlayNetHandler.java b/src/launch/java/baritone/launch/mixins/MixinClientPlayNetHandler.java index e718685c9..460250617 100644 --- a/src/launch/java/baritone/launch/mixins/MixinClientPlayNetHandler.java +++ b/src/launch/java/baritone/launch/mixins/MixinClientPlayNetHandler.java @@ -20,16 +20,19 @@ package baritone.launch.mixins; import baritone.Baritone; import baritone.api.BaritoneAPI; import baritone.api.IBaritone; +import baritone.api.event.events.BlockChangeEvent; import baritone.api.event.events.ChatEvent; import baritone.api.event.events.ChunkEvent; import baritone.api.event.events.type.EventState; +import baritone.api.utils.Pair; import baritone.cache.CachedChunk; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.player.LocalPlayer; -import net.minecraft.network.chat.Component; +import net.minecraft.core.BlockPos; import net.minecraft.network.protocol.game.*; import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.block.state.BlockState; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -37,6 +40,9 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.util.ArrayList; +import java.util.List; + /** * @author Brady * @since 8/3/2018 @@ -68,12 +74,14 @@ public class MixinClientPlayNetHandler { } }*/ - @Shadow @Final private Minecraft minecraft; + @Shadow + @Final + private Minecraft minecraft; @Inject( - method = "sendChat(Ljava/lang/String;)V", - at = @At("HEAD"), - cancellable = true + method = "sendChat(Ljava/lang/String;)V", + at = @At("HEAD"), + cancellable = true ) private void sendChatMessage(String string, CallbackInfo ci) { ChatEvent event = new ChatEvent(string); @@ -168,9 +176,22 @@ public class MixinClientPlayNetHandler { at = @At("RETURN") ) private void postHandleMultiBlockChange(ClientboundSectionBlocksUpdatePacket packetIn, CallbackInfo ci) { - if (!Baritone.settings().repackOnAnyBlockChange.value) { + IBaritone baritone = BaritoneAPI.getProvider().getBaritoneForConnection((ClientPacketListener) (Object) this); + if (baritone == null) { return; } + + List> changes = new ArrayList<>(); + packetIn.runUpdates((mutPos, state) -> { + changes.add(new Pair<>(mutPos.immutable(), state)); // bradyfix is this right + }); + if (changes.isEmpty()) { + return; + } + baritone.getGameEventHandler().onBlockChange(new BlockChangeEvent( + new ChunkPos(changes.get(0).first()), + changes + )); ChunkPos[] chunkPos = new ChunkPos[1]; packetIn.runUpdates((pos, state) -> { if (CachedChunk.BLOCKS_TO_KEEP_TRACK_OF.contains(state.getBlock())) { @@ -210,4 +231,71 @@ public class MixinClientPlayNetHandler { } } } + + /* + @Inject( + method = "handleChunkData", + at = @At( + value = "INVOKE", + target = "net/minecraft/world/chunk/Chunk.read(Lnet/minecraft/network/PacketBuffer;IZ)V" + ) + ) + private void preRead(SPacketChunkData packetIn, CallbackInfo ci) { + IBaritone baritone = BaritoneAPI.getProvider().getBaritoneForConnection((NetHandlerPlayClient) (Object) this); + if (baritone == null) { + return; + } + baritone.getGameEventHandler().onChunkEvent( + new ChunkEvent( + EventState.PRE, + packetIn.isFullChunk() ? ChunkEvent.Type.POPULATE_FULL : ChunkEvent.Type.POPULATE_PARTIAL, + packetIn.getChunkX(), + packetIn.getChunkZ() + ) + ); + } + + @Inject( + method = "handleChunkData", + at = @At("RETURN") + ) + private void postHandleChunkData(SPacketChunkData packetIn, CallbackInfo ci) { + IBaritone baritone = BaritoneAPI.getProvider().getBaritoneForConnection((NetHandlerPlayClient) (Object) this); + if (baritone == null) { + return; + } + baritone.getGameEventHandler().onChunkEvent( + new ChunkEvent( + EventState.POST, + packetIn.isFullChunk() ? ChunkEvent.Type.POPULATE_FULL : ChunkEvent.Type.POPULATE_PARTIAL, + packetIn.getChunkX(), + packetIn.getChunkZ() + ) + ); + } + + @Inject( + method = "handleBlockChange", + at = @At("RETURN") + ) + private void postHandleBlockChange(SPacketBlockChange packetIn, CallbackInfo ci) { + IBaritone baritone = BaritoneAPI.getProvider().getBaritoneForConnection((NetHandlerPlayClient) (Object) this); + if (baritone == null) { + return; + } + + final ChunkPos pos = new ChunkPos(packetIn.getBlockPosition().getX() >> 4, packetIn.getBlockPosition().getZ() >> 4); + final Pair changed = new Pair<>(packetIn.getBlockPosition(), packetIn.getBlockState()); + baritone.getGameEventHandler().onBlockChange(new BlockChangeEvent(pos, Collections.singletonList(changed))); + } + + @Inject( + method = "handleMultiBlockChange", + at = @At("RETURN") + ) + private void postHandleMultiBlockChange(SPacketMultiBlockChange packetIn, CallbackInfo ci) { + + } + + */ } diff --git a/src/launch/java/baritone/launch/mixins/MixinClientPlayerEntity.java b/src/launch/java/baritone/launch/mixins/MixinClientPlayerEntity.java index b95c579aa..e835d5e46 100644 --- a/src/launch/java/baritone/launch/mixins/MixinClientPlayerEntity.java +++ b/src/launch/java/baritone/launch/mixins/MixinClientPlayerEntity.java @@ -19,15 +19,15 @@ package baritone.launch.mixins; import baritone.api.BaritoneAPI; import baritone.api.IBaritone; -import baritone.api.event.events.ChatEvent; import baritone.api.event.events.PlayerUpdateEvent; import baritone.api.event.events.SprintStateEvent; import baritone.api.event.events.type.EventState; import baritone.behavior.LookBehavior; import net.minecraft.client.KeyMapping; import net.minecraft.client.player.LocalPlayer; -import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Abilities; +import net.minecraft.world.item.ElytraItem; +import net.minecraft.world.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -107,4 +107,19 @@ public class MixinClientPlayerEntity { ((LookBehavior) baritone.getLookBehavior()).pig(); } } + + @Redirect( + method = "aiStep", + at = @At( + value = "INVOKE", + target = "net/minecraft/world/item/ElytraItem.isFlyEnabled(Lnet/minecraft/world/item/ItemStack;)Z" + ) + ) + private boolean isFlyEnabled(ItemStack stack) { + IBaritone baritone = BaritoneAPI.getProvider().getBaritoneForPlayer((LocalPlayer) (Object) this); + if (baritone != null && baritone.getPathingBehavior().isPathing()) { + return false; + } + return ElytraItem.isFlyEnabled(stack); + } // bradyfix is this right? } diff --git a/src/launch/java/baritone/launch/mixins/MixinFireworkRocketEntity.java b/src/launch/java/baritone/launch/mixins/MixinFireworkRocketEntity.java new file mode 100644 index 000000000..d982d134a --- /dev/null +++ b/src/launch/java/baritone/launch/mixins/MixinFireworkRocketEntity.java @@ -0,0 +1,60 @@ +/* + * 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.launch.mixins; + +import baritone.utils.accessor.IFireworkRocketEntity; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.projectile.FireworkRocketEntity; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.OptionalInt; + +@Mixin(FireworkRocketEntity.class) +public abstract class MixinFireworkRocketEntity extends Entity implements IFireworkRocketEntity { + + @Shadow + @Final + private static EntityDataAccessor DATA_ATTACHED_TO_TARGET; + + @Shadow + private LivingEntity attachedToEntity; + + @Shadow + public abstract boolean isAttachedToEntity(); + + private MixinFireworkRocketEntity(Level level) { + super(EntityType.FIREWORK_ROCKET, level); + } + + @Override + public LivingEntity getBoostedEntity() { + if (this.isAttachedToEntity() && this.attachedToEntity == null) { // isAttachedToEntity checks if the optional is present + final Entity entity = this.level.getEntity(this.entityData.get(DATA_ATTACHED_TO_TARGET).getAsInt()); + if (entity instanceof LivingEntity) { + this.attachedToEntity = (LivingEntity) entity; + } + } + return this.attachedToEntity; + } +} diff --git a/src/launch/java/baritone/launch/mixins/MixinLivingEntity.java b/src/launch/java/baritone/launch/mixins/MixinLivingEntity.java index 6858a61aa..ecc7829d9 100644 --- a/src/launch/java/baritone/launch/mixins/MixinLivingEntity.java +++ b/src/launch/java/baritone/launch/mixins/MixinLivingEntity.java @@ -35,8 +35,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.Optional; -import static org.objectweb.asm.Opcodes.GETFIELD; - /** * @author Brady * @since 9/10/2018 @@ -113,6 +111,33 @@ public abstract class MixinLivingEntity extends Entity { this.elytraRotationEvent = null; } } + // bradyfix in 1.12.2 this is a redirect of moveRelative as called from travel, but it looks like in 1.19.4 we're doing it in MixinEntity as injecting into moveRelative itself, is that right? + /*@Redirect( + method = "travel", + at = @At( + value = "INVOKE", + target = "net/minecraft/entity/EntityLivingBase.moveRelative(FFFF)V" + ) + ) + private void onMoveRelative(EntityLivingBase self, float strafe, float up, float forward, float friction) { + Optional baritone = this.getBaritone(); + if (!baritone.isPresent()) { + // If a shadow is used here it breaks on Forge + this.moveRelative(strafe, up, forward, friction); + return; + } + + RotationMoveEvent event = new RotationMoveEvent(RotationMoveEvent.Type.MOTION_UPDATE, this.rotationYaw, this.rotationPitch); + baritone.get().getGameEventHandler().onPlayerRotationMove(event); + + this.rotationYaw = event.getYaw(); + this.rotationPitch = event.getPitch(); + + this.moveRelative(strafe, up, forward, friction); + + this.rotationYaw = event.getOriginal().getYaw(); + this.rotationPitch = event.getOriginal().getPitch(); + }*/ @Unique private Optional getBaritone() { diff --git a/src/launch/java/baritone/launch/mixins/MixinMinecraft.java b/src/launch/java/baritone/launch/mixins/MixinMinecraft.java index 686e22d25..d50534b7a 100644 --- a/src/launch/java/baritone/launch/mixins/MixinMinecraft.java +++ b/src/launch/java/baritone/launch/mixins/MixinMinecraft.java @@ -30,6 +30,7 @@ import net.minecraft.client.player.LocalPlayer; import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; @@ -49,6 +50,9 @@ public class MixinMinecraft { @Shadow public ClientLevel level; + @Unique + private BiFunction tickProvider; + @Inject( method = "", at = @At("RETURN") @@ -61,27 +65,60 @@ public class MixinMinecraft { @Inject( method = "tick", at = @At( - value = "FIELD", - opcode = Opcodes.GETFIELD, - target = "Lnet/minecraft/client/Minecraft;screen:Lnet/minecraft/client/gui/screens/Screen;", - ordinal = 4, - shift = At.Shift.BY, - by = -3 +//<<<<<<< HEAD + value = "FIELD", + opcode = Opcodes.GETFIELD, + target = "Lnet/minecraft/client/Minecraft;screen:Lnet/minecraft/client/gui/screens/Screen;", + ordinal = 4, + shift = At.Shift.BY, + by = -3 +/*======= + value = "FIELD", + opcode = Opcodes.GETFIELD, + target = "net/minecraft/client/Minecraft.currentScreen:Lnet/minecraft/client/gui/GuiScreen;", + ordinal = 0, + shift = At.Shift.BEFORE + ), + slice = @Slice( + from = @At( + value = "FIELD", + opcode = Opcodes.PUTFIELD, + target = "net/minecraft/client/Minecraft.leftClickCounter:I" + ) +>>>>>>> master*/ + // bradyfix ^ ) ) private void runTick(CallbackInfo ci) { - final BiFunction tickProvider = TickEvent.createNextProvider(); + this.tickProvider = TickEvent.createNextProvider(); for (IBaritone baritone : BaritoneAPI.getProvider().getAllBaritones()) { - TickEvent.Type type = baritone.getPlayerContext().player() != null && baritone.getPlayerContext().world() != null ? TickEvent.Type.IN : TickEvent.Type.OUT; - - baritone.getGameEventHandler().onTick(tickProvider.apply(EventState.PRE, type)); + baritone.getGameEventHandler().onTick(this.tickProvider.apply(EventState.PRE, type)); } } + @Inject( + method = "tick", + at = @At("RETURN") + ) + private void postRunTick(CallbackInfo ci) { + if (this.tickProvider == null) { + return; + } + + for (IBaritone baritone : BaritoneAPI.getProvider().getAllBaritones()) { + TickEvent.Type type = baritone.getPlayerContext().player() != null && baritone.getPlayerContext().world() != null + ? TickEvent.Type.IN + : TickEvent.Type.OUT; + baritone.getGameEventHandler().onPostTick(this.tickProvider.apply(EventState.POST, type)); + } + + this.tickProvider = null; + } + @Inject( method = "tick", at = @At( @@ -93,6 +130,8 @@ public class MixinMinecraft { private void postUpdateEntities(CallbackInfo ci) { IBaritone baritone = BaritoneAPI.getProvider().getBaritoneForPlayer(this.player); if (baritone != null) { + // Intentionally call this after all entities have been updated. That way, any modification to rotations + // can be recognized by other entity code. (Fireworks and Pigs, for example) baritone.getGameEventHandler().onPlayerUpdate(new PlayerUpdateEvent(EventState.POST)); } } diff --git a/src/launch/resources/mixins.baritone.json b/src/launch/resources/mixins.baritone.json index b00c7a2a6..35d5ae0d6 100644 --- a/src/launch/resources/mixins.baritone.json +++ b/src/launch/resources/mixins.baritone.json @@ -15,6 +15,7 @@ "MixinCommandSuggestionHelper", "MixinEntity", "MixinEntityRenderManager", + "MixinFireworkRocketEntity", "MixinItemStack", "MixinLivingEntity", "MixinLootContext", @@ -26,4 +27,4 @@ "MixinScreen", "MixinWorldRenderer" ] -} \ No newline at end of file +} diff --git a/src/main/java/baritone/Baritone.java b/src/main/java/baritone/Baritone.java index 667e5c901..c1176a3d4 100755 --- a/src/main/java/baritone/Baritone.java +++ b/src/main/java/baritone/Baritone.java @@ -23,6 +23,7 @@ import baritone.api.Settings; import baritone.api.behavior.IBehavior; import baritone.api.event.listener.IEventBus; import baritone.api.process.IBaritoneProcess; +import baritone.api.process.IElytraProcess; import baritone.api.utils.IPlayerContext; import baritone.behavior.*; import baritone.cache.WorldProvider; @@ -40,6 +41,8 @@ import net.minecraft.client.Minecraft; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -76,6 +79,7 @@ public class Baritone implements IBaritone { private final ExploreProcess exploreProcess; private final FarmProcess farmProcess; private final InventoryPauserProcess inventoryPauserProcess; + private final ElytraProcess elytraProcess; private final PathingControlManager pathingControlManager; private final SelectionManager selectionManager; @@ -118,6 +122,7 @@ public class Baritone implements IBaritone { this.exploreProcess = this.registerProcess(ExploreProcess::new); this.farmProcess = this.registerProcess(FarmProcess::new); this.inventoryPauserProcess = this.registerProcess(InventoryPauserProcess::new); + this.elytraProcess = this.registerProcess(ElytraProcess::create); this.registerProcess(BackfillProcess::new); } @@ -230,6 +235,11 @@ public class Baritone implements IBaritone { return this.commandManager; } + @Override + public IElytraProcess getElytraProcess() { + return this.elytraProcess; + } + @Override public void openClick() { new Thread(() -> { diff --git a/src/main/java/baritone/behavior/LookBehavior.java b/src/main/java/baritone/behavior/LookBehavior.java index 722258148..2ff732675 100644 --- a/src/main/java/baritone/behavior/LookBehavior.java +++ b/src/main/java/baritone/behavior/LookBehavior.java @@ -28,6 +28,8 @@ import baritone.api.utils.Rotation; import baritone.behavior.look.ForkableRandom; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.Optional; public final class LookBehavior extends Behavior implements ILookBehavior { @@ -51,14 +53,19 @@ public final class LookBehavior extends Behavior implements ILookBehavior { private final AimProcessor processor; + private final Deque smoothYawBuffer; + private final Deque smoothPitchBuffer; + public LookBehavior(Baritone baritone) { super(baritone); this.processor = new AimProcessor(baritone.getPlayerContext()); + this.smoothYawBuffer = new ArrayDeque<>(); + this.smoothPitchBuffer = new ArrayDeque<>(); } @Override public void updateTarget(Rotation rotation, boolean blockInteract) { - this.target = new Target(rotation, blockInteract); + this.target = new Target(rotation, Target.Mode.resolve(ctx, blockInteract)); } @Override @@ -84,10 +91,8 @@ public final class LookBehavior extends Behavior implements ILookBehavior { // Just return for PRE, we still want to set target to null on POST return; } - if (this.target.mode == Target.Mode.SERVER) { - this.prevRotation = new Rotation(ctx.player().getYRot(), ctx.player().getXRot()); - } + this.prevRotation = new Rotation(ctx.player().getXRot(), ctx.player().getYRot()); final Rotation actual = this.processor.peekRotation(this.target.rotation); ctx.player().setYRot(actual.getYaw()); ctx.player().setXRot(actual.getPitch()); @@ -96,8 +101,23 @@ public final class LookBehavior extends Behavior implements ILookBehavior { case POST: { // Reset the player's rotations back to their original values if (this.prevRotation != null) { - ctx.player().setYRot(this.prevRotation.getYaw()); - ctx.player().setXRot(this.prevRotation.getPitch()); + this.smoothYawBuffer.addLast(this.target.rotation.getYaw()); + while (this.smoothYawBuffer.size() > Baritone.settings().smoothLookTicks.value) { + this.smoothYawBuffer.removeFirst(); + } + this.smoothPitchBuffer.addLast(this.target.rotation.getPitch()); + while (this.smoothPitchBuffer.size() > Baritone.settings().smoothLookTicks.value) { + this.smoothPitchBuffer.removeFirst(); + } + if (this.target.mode == Target.Mode.SERVER) { + ctx.player().setXRot(this.prevRotation.getYaw()); + ctx.player().setYRot(this.prevRotation.getPitch()); + } else if (ctx.player().isFallFlying() ? Baritone.settings().elytraSmoothLook.value : Baritone.settings().smoothLook.value) { + ctx.player().setXRot((float) this.smoothYawBuffer.stream().mapToDouble(d -> d).average().orElse(this.prevRotation.getYaw())); + if (ctx.player().isFallFlying()) { + ctx.player().setYRot((float) this.smoothPitchBuffer.stream().mapToDouble(d -> d).average().orElse(this.prevRotation.getPitch())); + } + } this.prevRotation = null; } // The target is done being used for this game tick, so it can be invalidated @@ -288,9 +308,9 @@ public final class LookBehavior extends Behavior implements ILookBehavior { public final Rotation rotation; public final Mode mode; - public Target(Rotation rotation, boolean blockInteract) { + public Target(Rotation rotation, Mode mode) { this.rotation = rotation; - this.mode = Mode.resolve(blockInteract); + this.mode = mode; } enum Mode { @@ -309,22 +329,26 @@ public final class LookBehavior extends Behavior implements ILookBehavior { */ NONE; - static Mode resolve(boolean blockInteract) { + static Mode resolve(IPlayerContext ctx, boolean blockInteract) { final Settings settings = Baritone.settings(); final boolean antiCheat = settings.antiCheatCompatibility.value; final boolean blockFreeLook = settings.blockFreeLook.value; - final boolean freeLook = settings.freeLook.value; - if (!freeLook) return CLIENT; - if (!blockFreeLook && blockInteract) return CLIENT; + if (ctx.player().isFallFlying()) { + // always need to set angles while flying + return settings.elytraFreeLook.value ? SERVER : CLIENT; + } else if (settings.freeLook.value) { + // Regardless of if antiCheatCompatibility is enabled, if a blockInteract is requested then the player + // rotation needs to be set somehow, otherwise Baritone will halt since objectMouseOver() will just be + // whatever the player is mousing over visually. Let's just settle for setting it silently. + if (blockInteract) { + return blockFreeLook ? SERVER : CLIENT; + } + return antiCheat ? SERVER : NONE; + } - // Regardless of if antiCheatCompatibility is enabled, if a blockInteract is requested then the player - // rotation needs to be set somehow, otherwise Baritone will halt since objectMouseOver() will just be - // whatever the player is mousing over visually. Let's just settle for setting it silently. - if (antiCheat || blockInteract) return SERVER; - - // Pathing regularly without antiCheatCompatibility, don't set the player rotation - return NONE; + // all freeLook settings are disabled so set the angles + return CLIENT; } } } diff --git a/src/main/java/baritone/behavior/PathingBehavior.java b/src/main/java/baritone/behavior/PathingBehavior.java index b9d2f9cd9..d3bd62d6b 100644 --- a/src/main/java/baritone/behavior/PathingBehavior.java +++ b/src/main/java/baritone/behavior/PathingBehavior.java @@ -33,6 +33,7 @@ import baritone.pathing.calc.AbstractNodeCostSearch; import baritone.pathing.movement.CalculationContext; import baritone.pathing.movement.MovementHelper; import baritone.pathing.path.PathExecutor; +import baritone.process.ElytraProcess; import baritone.utils.PathRenderer; import baritone.utils.PathingCommandContext; import baritone.utils.pathing.Favoring; @@ -308,7 +309,10 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior, } public boolean isSafeToCancel() { - return current == null || safeToCancel; + if (current == null) { + return !baritone.getElytraProcess().isActive() || baritone.getElytraProcess().isSafeToCancel(); + } + return safeToCancel; } public void requestPause() { @@ -351,7 +355,7 @@ public final class PathingBehavior extends Behavior implements IPathingBehavior, } // just cancel the current path - private void secretInternalSegmentCancel() { + public void secretInternalSegmentCancel() { queuePathEvent(PathEvent.CANCELED); synchronized (pathPlanLock) { getInProgress().ifPresent(AbstractNodeCostSearch::cancel); diff --git a/src/main/java/baritone/cache/CachedWorld.java b/src/main/java/baritone/cache/CachedWorld.java index fe8119993..e456382e6 100644 --- a/src/main/java/baritone/cache/CachedWorld.java +++ b/src/main/java/baritone/cache/CachedWorld.java @@ -23,12 +23,11 @@ import baritone.api.IBaritone; import baritone.api.cache.ICachedWorld; import baritone.api.cache.IWorldData; import baritone.api.utils.Helper; +import com.google.common.cache.CacheBuilder; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minecraft.core.BlockPos; -import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.dimension.DimensionType; @@ -38,7 +37,6 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; /** @@ -72,7 +70,7 @@ public final class CachedWorld implements ICachedWorld, Helper { * All chunk positions pending packing. This map will be updated in-place if a new update to the chunk occurs * while waiting in the queue for the packer thread to get to it. */ - private final Map toPackMap = new ConcurrentHashMap<>(); + private final Map toPackMap = CacheBuilder.newBuilder().softValues().build().asMap(); private final DimensionType dimension; diff --git a/src/main/java/baritone/command/defaults/DefaultCommands.java b/src/main/java/baritone/command/defaults/DefaultCommands.java index c2118ebe8..c810d07c1 100644 --- a/src/main/java/baritone/command/defaults/DefaultCommands.java +++ b/src/main/java/baritone/command/defaults/DefaultCommands.java @@ -66,7 +66,8 @@ public final class DefaultCommands { new WaypointsCommand(baritone), new CommandAlias(baritone, "sethome", "Sets your home waypoint", "waypoints save home"), new CommandAlias(baritone, "home", "Path to your home waypoint", "waypoints goto home"), - new SelCommand(baritone) + new SelCommand(baritone), + new ElytraCommand(baritone) )); ExecutionControlCommands prc = new ExecutionControlCommands(baritone); commands.add(prc.pauseCommand); diff --git a/src/main/java/baritone/command/defaults/ElytraCommand.java b/src/main/java/baritone/command/defaults/ElytraCommand.java new file mode 100644 index 000000000..3f7b6bfd6 --- /dev/null +++ b/src/main/java/baritone/command/defaults/ElytraCommand.java @@ -0,0 +1,225 @@ +/* + * 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.command.defaults; + +import baritone.Baritone; +import baritone.api.IBaritone; +import baritone.api.command.Command; +import baritone.api.command.argument.IArgConsumer; +import baritone.api.command.exception.CommandException; +import baritone.api.command.exception.CommandInvalidStateException; +import baritone.api.command.helpers.TabCompleteHelper; +import baritone.api.pathing.goals.Goal; +import baritone.api.process.ICustomGoalProcess; +import baritone.api.process.IElytraProcess; +import net.minecraft.ChatFormatting; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.level.Level; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static baritone.api.command.IBaritoneChatControl.FORCE_COMMAND_PREFIX; + +public class ElytraCommand extends Command { + + public ElytraCommand(IBaritone baritone) { + super(baritone, "elytra"); + } + + @Override + public void execute(String label, IArgConsumer args) throws CommandException { + final ICustomGoalProcess customGoalProcess = baritone.getCustomGoalProcess(); + final IElytraProcess elytra = baritone.getElytraProcess(); + if (args.hasExactlyOne() && args.peekString().equals("supported")) { + logDirect(elytra.isLoaded() ? "yes" : unsupportedSystemMessage()); + return; + } + if (!elytra.isLoaded()) { + throw new CommandInvalidStateException(unsupportedSystemMessage()); + } + + if (!args.hasAny()) { + if (Baritone.settings().elytraTermsAccepted.value) { + if (detectOn2b2t()) { + warn2b2t(); + } + } else { + gatekeep(); + } + Goal iGoal = customGoalProcess.mostRecentGoal(); + 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) { + throw new CommandInvalidStateException(ex.getMessage()); + } + return; + } + + final String action = args.getString(); + switch (action) { + case "reset": { + elytra.resetState(); + logDirect("Reset state but still flying to same goal"); + break; + } + case "repack": { + elytra.repackChunks(); + logDirect("Queued all loaded chunks for repacking"); + break; + } + default: { + throw new CommandInvalidStateException("Invalid action"); + } + } + } + + private void warn2b2t() { + if (Baritone.settings().elytraPredictTerrain.value) { + long seed = Baritone.settings().elytraNetherSeed.value; + if (seed != NEW_2B2T_SEED && seed != OLD_2B2T_SEED) { + logDirect(Component.literal("It looks like you're on 2b2t, but elytraNetherSeed is incorrect.")); // match color + logDirect(suggest2b2tSeeds()); + } + } + } + + private Component suggest2b2tSeeds() { + MutableComponent clippy = Component.literal(""); + clippy.append("Within a few hundred blocks of spawn/axis/highways/etc, the terrain is too fragmented to be predictable. Baritone Elytra will still work, just with backtracking. "); + clippy.append("However, once you get more than a few thousand blocks out, you should try "); + MutableComponent olderSeed = Component.literal("the older seed (click here)"); + olderSeed.setStyle(olderSeed.getStyle().withUnderlined(true).withBold(true).withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(Baritone.settings().prefix.value + "set elytraNetherSeed " + OLD_2B2T_SEED))).withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, FORCE_COMMAND_PREFIX + "set elytraNetherSeed " + OLD_2B2T_SEED))); + clippy.append(olderSeed); + clippy.append(". Once you're further out into newer terrain generation (this includes everything up through 1.12), you should try "); + MutableComponent newerSeed = Component.literal("the newer seed (click here)"); + newerSeed.setStyle(newerSeed.getStyle().withUnderlined(true).withBold(true).withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal(Baritone.settings().prefix.value + "set elytraNetherSeed " + NEW_2B2T_SEED))).withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, FORCE_COMMAND_PREFIX + "set elytraNetherSeed " + NEW_2B2T_SEED))); + clippy.append(newerSeed); + clippy.append(". Once you get into 1.19 terrain, the terrain becomes unpredictable again, due to custom non-vanilla generation, and you should set #elytraPredictTerrain to false. "); + return clippy; + } + + 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. "); + 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); + 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. "); + gatekeep5.append(suggest2b2tSeeds()); + if (!Baritone.settings().elytraPredictTerrain.value) { + gatekeep5.append(Baritone.settings().prefix.value + "elytraPredictTerrain is currently disabled. "); + } else { + if (Baritone.settings().elytraNetherSeed.value == NEW_2B2T_SEED) { + gatekeep5.append("You are using the newer seed. "); + } else if (Baritone.settings().elytraNetherSeed.value == OLD_2B2T_SEED) { + gatekeep5.append("You are using the older seed. "); + } else { + gatekeep5.append("Defaulting to the newer seed. "); + Baritone.settings().elytraNetherSeed.value = NEW_2B2T_SEED; + } + } + gatekeep.append(gatekeep5); + } else { + if (Baritone.settings().elytraNetherSeed.value == NEW_2B2T_SEED) { + MutableComponent gatekeep5 = Component.literal("Baritone doesn't know the seed of your world. Set it with: " + Baritone.settings().prefix.value + "set elytraNetherSeed seedgoeshere\n"); + gatekeep5.append("For the time being, elytraPredictTerrain is defaulting to false since the seed is unknown."); + gatekeep.append(gatekeep5); + Baritone.settings().elytraPredictTerrain.value = false; + } else { + if (Baritone.settings().elytraPredictTerrain.value) { + MutableComponent gatekeep5 = Component.literal("Baritone Elytra is predicting terrain assuming that " + Baritone.settings().elytraNetherSeed.value + " is the correct seed. Change that with " + Baritone.settings().prefix.value + "set elytraNetherSeed seedgoeshere, or disable it with " + Baritone.settings().prefix.value + "set elytraPredictTerrain false"); + gatekeep.append(gatekeep5); + } else { + MutableComponent gatekeep5 = Component.literal("Baritone Elytra is not predicting terrain. If you don't know the seed, this is the correct thing to do. If you do know the seed, input it with " + Baritone.settings().prefix.value + "set elytraNetherSeed seedgoeshere, and then enable it with " + Baritone.settings().prefix.value + "set elytraPredictTerrain true"); + gatekeep.append(gatekeep5); + } + } + } + logDirect(gatekeep); + } + + private boolean detectOn2b2t() { + ServerData data = ctx.minecraft().getCurrentServer(); + return data != null && data.ip.toLowerCase().contains("2b2t.org"); + } + + private static final long OLD_2B2T_SEED = -4100785268875389365L; + private static final long NEW_2B2T_SEED = 146008555100680L; + + @Override + public Stream tabComplete(String label, IArgConsumer args) throws CommandException { + TabCompleteHelper helper = new TabCompleteHelper(); + if (args.hasExactlyOne()) { + helper.append("reset", "repack", "supported"); + } + return helper.filterPrefix(args.getString()).stream(); + } + + @Override + public String getShortDesc() { + return "elytra time"; + } + + @Override + public List getLongDesc() { + return Arrays.asList( + "The elytra command tells baritone to, in the nether, automatically fly to the current goal.", + "", + "Usage:", + "> elytra - fly to the current goal", + "> elytra reset - Resets the state of the process, but will try to keep flying to the same goal.", + "> elytra repack - Queues all of the chunks in render distance to be given to the native library.", + "> elytra supported - Tells you if baritone ships a native library that is compatible with your PC." + ); + } + + private static String unsupportedSystemMessage() { + final String osArch = System.getProperty("os.arch"); + final String osName = System.getProperty("os.name"); + return String.format( + "Legacy architectures are not supported. Your CPU is %s and your operating system is %s. " + + "Supported architectures are 64 bit x86, and 64 bit ARM. Supported operating systems are Windows, " + + "Linux, and Mac", + osArch, osName + ); + } +} diff --git a/src/main/java/baritone/event/GameEventHandler.java b/src/main/java/baritone/event/GameEventHandler.java index 16331ea89..d716ff849 100644 --- a/src/main/java/baritone/event/GameEventHandler.java +++ b/src/main/java/baritone/event/GameEventHandler.java @@ -23,9 +23,13 @@ import baritone.api.event.events.type.EventState; import baritone.api.event.listener.IEventBus; import baritone.api.event.listener.IGameEventListener; import baritone.api.utils.Helper; +import baritone.api.utils.Pair; +import baritone.cache.CachedChunk; import baritone.cache.WorldProvider; import baritone.utils.BlockStateInterface; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.LevelChunk; import java.util.List; @@ -60,6 +64,11 @@ public final class GameEventHandler implements IEventBus, Helper { listeners.forEach(l -> l.onTick(event)); } + @Override + public void onPostTick(TickEvent event) { + listeners.forEach(l -> l.onPostTick(event)); + } + @Override public final void onPlayerUpdate(PlayerUpdateEvent event) { listeners.forEach(l -> l.onPlayerUpdate(event)); @@ -76,13 +85,10 @@ public final class GameEventHandler implements IEventBus, Helper { } @Override - public final void onChunkEvent(ChunkEvent event) { + public void onChunkEvent(ChunkEvent event) { EventState state = event.getState(); ChunkEvent.Type type = event.getType(); - boolean isPostPopulate = state == EventState.POST - && (type == ChunkEvent.Type.POPULATE_FULL || type == ChunkEvent.Type.POPULATE_PARTIAL); - Level world = baritone.getPlayerContext().world(); // Whenever the server sends us to another dimension, chunks are unloaded @@ -92,7 +98,7 @@ public final class GameEventHandler implements IEventBus, Helper { && type == ChunkEvent.Type.UNLOAD && world.getChunkSource().getChunk(event.getX(), event.getZ(), null, false) != null; - if (isPostPopulate || isPreUnload) { + if (event.isPostPopulate() || isPreUnload) { baritone.getWorldProvider().ifWorldLoaded(worldData -> { LevelChunk chunk = world.getChunk(event.getX(), event.getZ()); worldData.getCachedWorld().queueForPacking(chunk); @@ -103,6 +109,25 @@ public final class GameEventHandler implements IEventBus, Helper { listeners.forEach(l -> l.onChunkEvent(event)); } + @Override + public void onBlockChange(BlockChangeEvent event) { + if (Baritone.settings().repackOnAnyBlockChange.value) { + final boolean keepingTrackOf = event.getBlocks().stream() + .map(Pair::second).map(BlockState::getBlock) + .anyMatch(CachedChunk.BLOCKS_TO_KEEP_TRACK_OF::contains); + + if (keepingTrackOf) { + baritone.getWorldProvider().ifWorldLoaded(worldData -> { + final Level world = baritone.getPlayerContext().world(); + ChunkPos pos = event.getChunkPos(); + worldData.getCachedWorld().queueForPacking(world.getChunk(pos.x, pos.z)); + }); + } + } + + listeners.forEach(l -> l.onBlockChange(event)); + } + @Override public final void onRenderPass(RenderEvent event) { listeners.forEach(l -> l.onRenderPass(event)); diff --git a/src/main/java/baritone/pathing/movement/CalculationContext.java b/src/main/java/baritone/pathing/movement/CalculationContext.java index bd70d263e..64aca2ceb 100644 --- a/src/main/java/baritone/pathing/movement/CalculationContext.java +++ b/src/main/java/baritone/pathing/movement/CalculationContext.java @@ -28,8 +28,8 @@ import baritone.utils.pathing.BetterWorldBorder; import net.minecraft.client.player.LocalPlayer; import net.minecraft.core.BlockPos; import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.item.Items; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.Level; @@ -66,11 +66,13 @@ public class CalculationContext { public final boolean allowJumpAt256; public final boolean allowParkourAscend; public final boolean assumeWalkOnWater; + public boolean allowFallIntoLava; public final int frostWalker; public final boolean allowDiagonalDescend; public final boolean allowDiagonalAscend; public final boolean allowDownward; - public final int maxFallHeightNoWater; + public int minFallHeight; + public int maxFallHeightNoWater; public final int maxFallHeightBucket; public final double waterWalkSpeed; public final double breakBlockAdditionalCost; @@ -105,10 +107,12 @@ public class CalculationContext { this.allowJumpAt256 = Baritone.settings().allowJumpAt256.value; this.allowParkourAscend = Baritone.settings().allowParkourAscend.value; this.assumeWalkOnWater = Baritone.settings().assumeWalkOnWater.value; + this.allowFallIntoLava = false; // Super secret internal setting for ElytraBehavior this.frostWalker = EnchantmentHelper.getEnchantmentLevel(Enchantments.FROST_WALKER, baritone.getPlayerContext().player()); this.allowDiagonalDescend = Baritone.settings().allowDiagonalDescend.value; this.allowDiagonalAscend = Baritone.settings().allowDiagonalAscend.value; this.allowDownward = Baritone.settings().allowDownward.value; + this.minFallHeight = 3; // Minimum fall height used by MovementFall this.maxFallHeightNoWater = Baritone.settings().maxFallHeightNoWater.value; this.maxFallHeightBucket = Baritone.settings().maxFallHeightBucket.value; int depth = EnchantmentHelper.getDepthStrider(player); diff --git a/src/main/java/baritone/pathing/movement/movements/MovementDescend.java b/src/main/java/baritone/pathing/movement/movements/MovementDescend.java index a982b7415..07d6d7d01 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementDescend.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementDescend.java @@ -152,10 +152,11 @@ public class MovementDescend extends Movement { // this check prevents it from getting the block at y=-1 and crashing return false; } + boolean reachedMinimum = fallHeight >= context.minFallHeight; BlockState ontoBlock = context.get(destX, newY, destZ); int unprotectedFallHeight = fallHeight - (y - effectiveStartHeight); // equal to fallHeight - y + effectiveFallHeight, which is equal to -newY + effectiveFallHeight, which is equal to effectiveFallHeight - newY double tentativeCost = WALK_OFF_BLOCK_COST + FALL_N_BLOCKS_COST[unprotectedFallHeight] + frontBreak + costSoFar; - if (MovementHelper.isWater(ontoBlock)) { + if (reachedMinimum && MovementHelper.isWater(ontoBlock)) { if (!MovementHelper.canWalkThrough(context, destX, newY, destZ, ontoBlock)) { return false; } @@ -176,6 +177,14 @@ public class MovementDescend extends Movement { res.cost = tentativeCost;// TODO incorporate water swim up cost? return false; } + if (reachedMinimum && context.allowFallIntoLava && MovementHelper.isLava(ontoBlock)) { + // found a fall into lava + res.x = destX; + res.y = newY; + res.z = destZ; + res.cost = tentativeCost; + return false; + } if (unprotectedFallHeight <= 11 && (ontoBlock.getBlock() == Blocks.VINE || ontoBlock.getBlock() == Blocks.LADDER)) { // if fall height is greater than or equal to 11, we don't actually grab on to vines or ladders. the more you know // this effectively "resets" our falling speed @@ -193,7 +202,7 @@ public class MovementDescend extends Movement { if (MovementHelper.isBottomSlab(ontoBlock)) { return false; // falling onto a half slab is really glitchy, and can cause more fall damage than we'd expect } - if (unprotectedFallHeight <= context.maxFallHeightNoWater + 1) { + if (reachedMinimum && unprotectedFallHeight <= context.maxFallHeightNoWater + 1) { // fallHeight = 4 means onto.up() is 3 blocks down, which is the max res.x = destX; res.y = newY + 1; @@ -201,7 +210,7 @@ public class MovementDescend extends Movement { res.cost = tentativeCost; return false; } - if (context.hasWaterBucket && unprotectedFallHeight <= context.maxFallHeightBucket + 1) { + if (reachedMinimum && context.hasWaterBucket && unprotectedFallHeight <= context.maxFallHeightBucket + 1) { res.x = destX; res.y = newY + 1;// this is the block we're falling onto, so dest is +1 res.z = destZ; diff --git a/src/main/java/baritone/process/CustomGoalProcess.java b/src/main/java/baritone/process/CustomGoalProcess.java index 3ee3e7b7c..d0dca9cbf 100644 --- a/src/main/java/baritone/process/CustomGoalProcess.java +++ b/src/main/java/baritone/process/CustomGoalProcess.java @@ -36,6 +36,11 @@ public final class CustomGoalProcess extends BaritoneProcessHelper implements IC */ private Goal goal; + /** + * The most recent goal. Not invalidated upon {@link #onLostControl()} + */ + private Goal mostRecentGoal; + /** * The current process state. * @@ -50,6 +55,10 @@ public final class CustomGoalProcess extends BaritoneProcessHelper implements IC @Override public void setGoal(Goal goal) { this.goal = goal; + this.mostRecentGoal = goal; + if (baritone.getElytraProcess().isActive()) { + baritone.getElytraProcess().pathTo(goal); + } if (this.state == State.NONE) { this.state = State.GOAL_SET; } @@ -68,6 +77,11 @@ public final class CustomGoalProcess extends BaritoneProcessHelper implements IC return this.goal; } + @Override + public Goal mostRecentGoal() { + return this.mostRecentGoal; + } + @Override public boolean isActive() { return this.state != State.NONE; diff --git a/src/main/java/baritone/process/ElytraProcess.java b/src/main/java/baritone/process/ElytraProcess.java new file mode 100644 index 000000000..7ede8543f --- /dev/null +++ b/src/main/java/baritone/process/ElytraProcess.java @@ -0,0 +1,568 @@ +/* + * 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; + +import baritone.Baritone; +import baritone.api.IBaritone; +import baritone.api.event.events.*; +import baritone.api.event.events.type.EventState; +import baritone.api.event.listener.AbstractGameEventListener; +import baritone.api.pathing.goals.Goal; +import baritone.api.pathing.goals.GoalBlock; +import baritone.api.pathing.goals.GoalXZ; +import baritone.api.pathing.goals.GoalYLevel; +import baritone.api.pathing.movement.IMovement; +import baritone.api.pathing.path.IPathExecutor; +import baritone.api.process.IBaritoneProcess; +import baritone.api.process.IElytraProcess; +import baritone.api.process.PathingCommand; +import baritone.api.process.PathingCommandType; +import baritone.api.utils.BetterBlockPos; +import baritone.api.utils.Rotation; +import baritone.api.utils.RotationUtils; +import baritone.api.utils.input.Input; +import baritone.pathing.movement.CalculationContext; +import baritone.pathing.movement.movements.MovementFall; +import baritone.process.elytra.ElytraBehavior; +import baritone.process.elytra.NetherPathfinderContext; +import baritone.process.elytra.NullElytraProcess; +import baritone.utils.BaritoneProcessHelper; +import baritone.utils.PathingCommandContext; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import net.minecraft.core.BlockPos; +import net.minecraft.core.NonNullList; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.AirBlock; +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.phys.Vec3; + +import java.util.*; + +import static baritone.api.pathing.movement.ActionCosts.COST_INF; + +public class ElytraProcess extends BaritoneProcessHelper implements IBaritoneProcess, IElytraProcess, AbstractGameEventListener { + public State state; + private boolean goingToLandingSpot; + private BetterBlockPos landingSpot; + private boolean reachedGoal; // this basically just prevents potential notification spam + private Goal goal; + private ElytraBehavior behavior; + private boolean predictingTerrain; + + private ElytraProcess(Baritone baritone) { + super(baritone); + baritone.getGameEventHandler().registerEventListener(this); + } + + public static T create(final Baritone baritone) { + return (T) (NetherPathfinderContext.isSupported() + ? new ElytraProcess(baritone) + : new NullElytraProcess(baritone)); + } + + @Override + public boolean isActive() { + return this.behavior != null; + } + + @Override + public void resetState() { + BlockPos destination = this.currentDestination(); + this.onLostControl(); + if (destination != null) { + this.pathTo(destination); + this.repackChunks(); + } + } + + private static final String AUTO_JUMP_FAILURE_MSG = "Failed to compute a walking path to a spot to jump off from. Consider starting from a higher location, near an overhang. Or, you can disable elytraAutoJump and just manually begin gliding."; + + @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(); + } + + this.behavior.onTick(); + + if (calcFailed) { + onLostControl(); + logDirect(AUTO_JUMP_FAILURE_MSG); + return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); + } + + boolean safetyLanding = false; + if (ctx.player().isFallFlying() && shouldLandForSafety()) { + if (Baritone.settings().elytraAllowEmergencyLand.value) { + logDirect("Emergency landing - almost out of elytra durability or fireworks"); + safetyLanding = true; + } else { + logDirect("almost out of elytra durability or fireworks, but I'm going to continue since elytraAllowEmergencyLand is false"); + } + } + if (ctx.player().isFallFlying() && this.state != State.LANDING && (this.behavior.pathManager.isComplete() || safetyLanding)) { + final BetterBlockPos last = this.behavior.pathManager.path.getLast(); + if (last != null && (ctx.player().position().distanceToSqr(last.getCenter()) < (48 * 48) || safetyLanding) && (!goingToLandingSpot || (safetyLanding && this.landingSpot == null))) { + logDirect("Path complete, picking a nearby safe landing spot..."); + BetterBlockPos landingSpot = findSafeLandingSpot(ctx.playerFeet()); + // if this fails we will just keep orbiting the last node until we run out of rockets or the user intervenes + if (landingSpot != null) { + this.pathTo0(landingSpot, true); + this.landingSpot = landingSpot; + } + this.goingToLandingSpot = true; + } + + if (last != null && ctx.player().position().distanceToSqr(last.getCenter()) < 1) { + if (Baritone.settings().notificationOnPathComplete.value && !reachedGoal) { + logNotification("Pathing complete", false); + } + if (Baritone.settings().disconnectOnArrival.value && !reachedGoal) { + // don't be active when the user logs back in + this.onLostControl(); + ctx.world().disconnect(); + return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); + } + reachedGoal = true; + + // we are goingToLandingSpot and we are in the last node of the path + if (this.goingToLandingSpot) { + this.state = State.LANDING; + logDirect("Above the landing spot, landing..."); + } + } + } + + if (this.state == State.LANDING) { + final BetterBlockPos endPos = this.landingSpot != null ? this.landingSpot : behavior.pathManager.path.getLast(); + if (ctx.player().isFallFlying() && endPos != null) { + Vec3 from = ctx.player().position(); + Vec3 to = new Vec3(((double) endPos.x) + 0.5, from.y, ((double) endPos.z) + 0.5); + 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) { + logDirect("bad landing spot, trying again..."); + landingSpotIsBad(endPos); + } + } + } + + if (ctx.player().isFallFlying()) { + behavior.landingMode = this.state == State.LANDING; + this.goal = null; + baritone.getInputOverrideHandler().clearAllKeys(); + behavior.tick(); + 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) { + logDirect("Landed, but still moving, waiting for velocity to die down... "); + baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true); + return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); + } + logDirect("Done :)"); + baritone.getInputOverrideHandler().clearAllKeys(); + this.onLostControl(); + return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); + } + + if (this.state == State.FLYING || this.state == State.START_FLYING) { + this.state = ctx.player().isOnGround() && Baritone.settings().elytraAutoJump.value + ? State.LOCATE_JUMP + : State.START_FLYING; + } + + if (this.state == State.LOCATE_JUMP) { + if (shouldLandForSafety()) { + logDirect("Not taking off, because elytra durability or fireworks are so low that I would immediately emergency land anyway."); + onLostControl(); + return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); + } + if (this.goal == null) { + this.goal = new GoalYLevel(31); + } + final IPathExecutor executor = baritone.getPathingBehavior().getCurrent(); + if (executor != null && executor.getPath().getGoal() == this.goal) { + final IMovement fall = executor.getPath().movements().stream() + .filter(movement -> movement instanceof MovementFall) + .findFirst().orElse(null); + + if (fall != null) { + final BetterBlockPos from = new BetterBlockPos( + (fall.getSrc().x + fall.getDest().x) / 2, + (fall.getSrc().y + fall.getDest().y) / 2, + (fall.getSrc().z + fall.getDest().z) / 2 + ); + behavior.pathManager.pathToDestination(from).whenComplete((result, ex) -> { + if (ex == null) { + this.state = State.GET_TO_JUMP; + return; + } + onLostControl(); + }); + this.state = State.PAUSE; + } else { + onLostControl(); + logDirect(AUTO_JUMP_FAILURE_MSG); + return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); + } + } + return new PathingCommandContext(this.goal, PathingCommandType.SET_GOAL_AND_PAUSE, new WalkOffCalculationContext(baritone)); + } + + // yucky + if (this.state == State.PAUSE) { + return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); + } + + if (this.state == State.GET_TO_JUMP) { + final IPathExecutor executor = baritone.getPathingBehavior().getCurrent(); + final boolean canStartFlying = ctx.player().fallDistance > 1.0f + && !isSafeToCancel + && executor != null + && executor.getPath().movements().get(executor.getPosition()) instanceof MovementFall; + + if (canStartFlying) { + this.state = State.START_FLYING; + } else { + return new PathingCommand(null, PathingCommandType.SET_GOAL_AND_PATH); + } + } + + if (this.state == State.START_FLYING) { + if (!isSafeToCancel) { + // owned + baritone.getPathingBehavior().secretInternalSegmentCancel(); + } + baritone.getInputOverrideHandler().clearAllKeys(); + if (ctx.player().fallDistance > 1.0f) { + baritone.getInputOverrideHandler().setInputForceState(Input.JUMP, true); + } + } + return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); + } + + public void landingSpotIsBad(BetterBlockPos endPos) { + badLandingSpots.add(endPos); + goingToLandingSpot = false; + this.landingSpot = null; + this.state = State.FLYING; + } + + @Override + public void onLostControl() { + this.goal = null; + this.goingToLandingSpot = false; + this.landingSpot = null; + this.reachedGoal = false; + this.state = State.START_FLYING; // TODO: null state? + destroyBehaviorAsync(); + } + + private void destroyBehaviorAsync() { + ElytraBehavior behavior = this.behavior; + if (behavior != null) { + this.behavior = null; + Baritone.getExecutor().execute(behavior::destroy); + } + } + + @Override + public double priority() { + return 0; // higher priority than CustomGoalProcess + } + + @Override + public String displayName0() { + return "Elytra - " + this.state.description; + } + + @Override + public void repackChunks() { + if (this.behavior != null) { + this.behavior.repackChunks(); + } + } + + @Override + public BlockPos currentDestination() { + return this.behavior != null ? this.behavior.destination : null; + } + + @Override + public void pathTo(BlockPos destination) { + this.pathTo0(destination, false); + } + + private void pathTo0(BlockPos destination, boolean appendDestination) { + if (ctx.player() == null || ctx.player().level.dimension() != Level.NETHER) { + return; + } + this.onLostControl(); + this.predictingTerrain = Baritone.settings().elytraPredictTerrain.value; + this.behavior = new ElytraBehavior(this.baritone, this, destination, appendDestination); + if (ctx.world() != null) { + this.behavior.repackChunks(); + } + this.behavior.pathTo(); + } + + @Override + public void pathTo(Goal iGoal) { + final int x; + final int y; + final int z; + if (iGoal instanceof GoalXZ) { + GoalXZ goal = (GoalXZ) iGoal; + x = goal.getX(); + y = 64; + z = goal.getZ(); + } else if (iGoal instanceof GoalBlock) { + GoalBlock goal = (GoalBlock) iGoal; + x = goal.x; + y = goal.y; + z = goal.z; + } 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)); + } + + private boolean shouldLandForSafety() { + ItemStack chest = ctx.player().getItemBySlot(EquipmentSlot.CHEST); + if (chest.getItem() != Items.ELYTRA || chest.getItem().getMaxDamage() - chest.getDamageValue() < Baritone.settings().elytraMinimumDurability.value) { + // elytrabehavior replaces when durability <= minimumDurability, so if durability < minimumDurability then we can reasonably assume that the elytra will soon be broken without replacement + return true; + } + NonNullList inv = ctx.player().inventoryMenu.getItems(); + int qty = 0; + for (int i = 0; i < 36; i++) { + if (ElytraBehavior.isFireworks(inv.get(i))) { + qty += inv.get(i).getCount(); + } + } + if (qty <= Baritone.settings().elytraMinFireworksBeforeLanding.value) { + return true; + } + return false; + } + + @Override + public boolean isLoaded() { + return true; + } + + @Override + public boolean isSafeToCancel() { + return !this.isActive() || !(this.state == State.FLYING || this.state == State.START_FLYING); + } + + public enum State { + LOCATE_JUMP("Finding spot to jump off"), + PAUSE("Waiting for elytra path"), + GET_TO_JUMP("Walking to takeoff"), + START_FLYING("Begin flying"), + FLYING("Flying"), + LANDING("Landing"); + + public final String description; + + State(String desc) { + this.description = desc; + } + } + + @Override + public void onRenderPass(RenderEvent event) { + if (this.behavior != null) this.behavior.onRenderPass(event); + } + + @Override + public void onWorldEvent(WorldEvent event) { + if (event.getWorld() != null && event.getState() == EventState.POST) { + // Exiting the world, just destroy + destroyBehaviorAsync(); + } + } + + @Override + public void onChunkEvent(ChunkEvent event) { + if (this.behavior != null) this.behavior.onChunkEvent(event); + } + + @Override + public void onBlockChange(BlockChangeEvent event) { + if (this.behavior != null) this.behavior.onBlockChange(event); + } + + @Override + public void onReceivePacket(PacketEvent event) { + if (this.behavior != null) this.behavior.onReceivePacket(event); + } + + @Override + public void onPostTick(TickEvent event) { + IBaritoneProcess procThisTick = baritone.getPathingControlManager().mostRecentInControl().orElse(null); + if (this.behavior != null && procThisTick == this) this.behavior.onPostTick(event); + } + + /** + * Custom calculation context which makes the player fall into lava + */ + public static final class WalkOffCalculationContext extends CalculationContext { + + public WalkOffCalculationContext(IBaritone baritone) { + super(baritone, true); + this.allowFallIntoLava = true; + this.minFallHeight = 8; + this.maxFallHeightNoWater = 10000; + } + + @Override + public double costOfPlacingAt(int x, int y, int z, BlockState current) { + return COST_INF; + } + + @Override + public double breakCostMultiplierAt(int x, int y, int z, BlockState current) { + return COST_INF; + } + + @Override + public double placeBucketCost() { + return COST_INF; + } + } + + private static boolean isInBounds(BlockPos pos) { + return pos.getY() >= 0 && pos.getY() < 128; + } + + private boolean isSafeBlock(Block block) { + return block == Blocks.NETHERRACK || block == Blocks.GRAVEL || (block == Blocks.NETHER_BRICKS && Baritone.settings().elytraAllowLandOnNetherFortress.value); + } + + private boolean isSafeBlock(BlockPos pos) { + return isSafeBlock(ctx.world().getBlockState(pos).getBlock()); + } + + private boolean isAtEdge(BlockPos pos) { + return !isSafeBlock(pos.north()) + || !isSafeBlock(pos.south()) + || !isSafeBlock(pos.east()) + || !isSafeBlock(pos.west()) + // corners + || !isSafeBlock(pos.north().west()) + || !isSafeBlock(pos.north().east()) + || !isSafeBlock(pos.south().west()) + || !isSafeBlock(pos.south().east()); + } + + private boolean isColumnAir(BlockPos landingSpot, int minHeight) { + BlockPos.MutableBlockPos mut = new BlockPos.MutableBlockPos(landingSpot.getX(), landingSpot.getY(), landingSpot.getZ()); + final int maxY = mut.getY() + minHeight; + for (int y = mut.getY() + 1; y <= maxY; y++) { + mut.set(mut.getX(), y, mut.getZ()); + if (!(ctx.world().getBlockState(mut).getBlock() instanceof AirBlock)) { + return false; + } + } + return true; + } + + private boolean hasAirBubble(BlockPos pos) { + final int radius = 4; // Half of the full width, rounded down, as we're counting blocks in each direction from the center + BlockPos.MutableBlockPos mut = new BlockPos.MutableBlockPos(); + for (int x = -radius; x <= radius; x++) { + for (int y = -radius; y <= radius; y++) { + for (int z = -radius; z <= radius; z++) { + mut.set(pos.getX() + x, pos.getY() + y, pos.getZ() + z); + if (!(ctx.world().getBlockState(mut).getBlock() instanceof AirBlock)) { + return false; + } + } + } + } + + return true; + } + + private BetterBlockPos checkLandingSpot(BlockPos pos, LongOpenHashSet checkedSpots) { + BlockPos.MutableBlockPos mut = new BlockPos.MutableBlockPos(pos.getX(), pos.getY(), pos.getZ()); + while (mut.getY() >= 0) { + if (checkedSpots.contains(mut.asLong())) { + return null; + } + checkedSpots.add(mut.asLong()); + Block block = ctx.world().getBlockState(mut).getBlock(); + + if (isSafeBlock(block)) { + if (!isAtEdge(mut)) { + return new BetterBlockPos(mut); + } + return null; + } else if (block != Blocks.AIR) { + return null; + } + mut.set(mut.getX(), mut.getY() - 1, mut.getZ()); + } + return null; // void + } + + private static final int LANDING_COLUMN_HEIGHT = 15; + private Set badLandingSpots = new HashSet<>(); + + private BetterBlockPos findSafeLandingSpot(BetterBlockPos start) { + Queue queue = new PriorityQueue<>(Comparator.comparingInt(pos -> (pos.x - start.x) * (pos.x - start.x) + (pos.z - start.z) * (pos.z - start.z)).thenComparingInt(pos -> -pos.y)); + Set 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) { + 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 (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()); + if (visited.add(pos.above())) queue.add(pos.above()); + if (visited.add(pos.below())) queue.add(pos.below()); + } + } + return null; + } +} diff --git a/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java b/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java new file mode 100644 index 000000000..7db0e2d64 --- /dev/null +++ b/src/main/java/baritone/process/elytra/BlockStateOctreeInterface.java @@ -0,0 +1,54 @@ +/* + * 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 dev.babbaj.pathfinder.NetherPathfinder; +import dev.babbaj.pathfinder.Octree; + +/** + * @author Brady + */ +public final class BlockStateOctreeInterface { + + private final NetherPathfinderContext context; + private final long contextPtr; + transient long chunkPtr; + + // Guarantee that the first lookup will fetch the context by setting MAX_VALUE + private int prevChunkX = Integer.MAX_VALUE; + private int prevChunkZ = Integer.MAX_VALUE; + + public BlockStateOctreeInterface(final NetherPathfinderContext context) { + this.context = context; + this.contextPtr = context.context; + } + + public boolean get0(final int x, final int y, final int z) { + if ((y | (127 - y)) < 0) { + 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); + } + return Octree.getBlock(this.chunkPtr, x & 0xF, y & 0x7F, z & 0xF); + } +} diff --git a/src/main/java/baritone/process/elytra/ElytraBehavior.java b/src/main/java/baritone/process/elytra/ElytraBehavior.java new file mode 100644 index 000000000..82225456e --- /dev/null +++ b/src/main/java/baritone/process/elytra/ElytraBehavior.java @@ -0,0 +1,1305 @@ +/* + * 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.Settings; +import baritone.api.behavior.look.IAimProcessor; +import baritone.api.behavior.look.ITickableAimProcessor; +import baritone.api.event.events.*; +import baritone.api.pathing.goals.GoalBlock; +import baritone.api.utils.*; +import baritone.process.ElytraProcess; +import baritone.utils.BlockStateInterface; +import baritone.utils.IRenderer; +import baritone.utils.PathRenderer; +import baritone.utils.accessor.IFireworkRocketEntity; +import it.unimi.dsi.fastutil.floats.FloatArrayList; +import it.unimi.dsi.fastutil.floats.FloatIterator; +import net.minecraft.core.BlockPos; +import net.minecraft.core.NonNullList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; +import net.minecraft.util.Mth; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.projectile.FireworkRocketEntity; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.chunk.ChunkSource; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.material.Material; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; + +import java.awt.*; +import java.util.List; +import java.util.Queue; +import java.util.*; +import java.util.concurrent.*; +import java.util.function.UnaryOperator; + +import static baritone.utils.BaritoneMath.fastCeil; +import static baritone.utils.BaritoneMath.fastFloor; + +public final class ElytraBehavior implements Helper { + private final Baritone baritone; + private final IPlayerContext ctx; + + // Render stuff + private final List> clearLines; + private final List> blockedLines; + private List simulationLine; + private BlockPos aimPos; + private List 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(); +}