diff --git a/.github/workflows/nightly_build.yml b/.github/workflows/nightly_build.yml new file mode 100644 index 0000000..9bd0075 --- /dev/null +++ b/.github/workflows/nightly_build.yml @@ -0,0 +1,53 @@ +name: Nightly Build + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + build: + runs-on : ubuntu-latest + env : + BUILD : ${{ github.run_number }} + WEBHOOK : ${{ secrets.HIGHWAYTOOLS_NIGHTLY }} + + steps: + - name: Check out repository + uses: actions/checkout@v2 + + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Change wrapper permissions + run: chmod +x ./gradlew + + - name: Gradle cache + uses: actions/cache@v2 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Gradle build + run: ./gradlew --build-cache build + + - name: Archive artifact + uses: actions/upload-artifact@v2 + with: + name: HighwayTools-${{ github.sha }} + path: build/libs/ + + - name: Get branch name + uses: nelonoel/branch-name@v1.0.1 + + - name: Send Discord build message + if: github.ref == 'refs/heads/master' + run: | + COMMITMESSAGE=`git log --pretty=format:'- \`%h\` %s' -5 --reverse` && + (curl "$WEBHOOK" -sS -H "Content-Type:application/json" -X POST -d "{\"content\":null,\"embeds\":[{\"title\":\"Build $BUILD\",\"description\":\"**Branch:** $BRANCH_NAME\\n**Changes:**\\n$COMMITMESSAGE\",\"url\":\"https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID\",\"color\":1487872,\"fields\":[{\"name\":\"Artifacts:\",\"value\":\"- [HighwayTools-${{ github.sha }}.zip](https://nightly.link/$GITHUB_REPOSITORY/workflows/nightly_build/$BRANCH_NAME/HighwayTools-${{ github.sha }}.zip)\"}],\"footer\":{\"text\":\"$GITHUB_REPOSITORY\"},\"thumbnail\":{\"url\":\"https://raw.githubusercontent.com/lambda-client/lambda/master/src/main/resources/assets/minecraft/lambda/lambda_map.png\"}}],\"username\":\"Github Actions\",\"avatar_url\":\"https://www.2b2t.com.au/assets/github.jpeg\"}") \ No newline at end of file diff --git a/README.md b/README.md index a663114..8113893 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,80 @@ -# Lambda Plugin SDK + -This project in an example to show how a proper plugin is set up. +### Description -## Setup +Adding fully automated highway building as plugin to Lambda. +The tool places building material and destroys wrong blocks in high frequency using well-timed packets. +The module can build autonomously in all 8 directions while the pathfinder keeps the desired position and takes care of any possible dangerous situations like liquids or gaps. +Advanced inventory management allows the bot to fully utilize materials stocked in shulker boxes or in the ender chest. +It is highly customizable and allows deep changes in system using configurations. +It is fully compatible with most modules of Lambda like `LagNotifier`, `AutoEat` etc and also is confirmed to work with `Rusherhack`, `Future`, `Impact` etc. -Follow these steps to build your own plugin for lambda. +### Features +- [x] Digs tunnel and paves Obsidian floor at the same time +- [x] Faster block breaking and placing then any other solution + - Reaches `20+ blocks per second` mining interactions on 2b2t + - Reaches `7+ blocks per second` placing interactions on 2b2t + - Confirmed over `300%` faster than previous cutting edge digging solutions (source: MEG) +- [x] Intelligent liquid handling + - Reacts on liquid pockets using cutting edge placing exploits to patch lava pockets before even opening them + - Reacts on complex flow structures and cleans up +- [x] Long term inventory management + - Dynamic material restock from shulker boxes and ender chests + - Built in native AutoObsidian. Enable option Storage Management > Grind Obsidian + - Saves minimum requirements of materials + - No mouse grab on container open +- [x] Diagonal highway mode +- [x] Intelligent repair mode +- [x] The built-in Anti-Anti-Cheat works with 2b2t's anti-cheat and `NoCheatPlus` +- [x] Pauses on lag to avoid kick (enable `LagNotifier` for this feature) +- [x] Ignore Blocks: `Signs, Portals, Banners, Bedrock` and more +- [x] Choose custom-building materials +- [x] Auto clipping to starting coordinates +- [x] Commands: + - `;highwaytools` - alias: `;ht` + - `;ht ignore add ` Adds block to ignore list + - `;ht ignore del ` Removes block from ignore list + - `;ht material ` Choose an alternative building block (default: Obsidian) + - `;ht filler ` Choose an alternative filler block to fill liquids (default: Netherrack) + - `;ht settings` or `;ht` Shows detailed settings of the module + - `;ht distance 500` for running the bot for a limited distance. (e.g. 500 blocks) +- [x] Compatible with: + - `LagNotifier (Baritone mode)` To stop while lagging to not get kicked + - `AutoObsidian` to automatically get new Obsidian from Ender Chests even from shulker boxes + - `AutoEat` set `PauseBaritone` on false and below health 19.0, and you're safe from lava and other threads having gapples in inventory + - `AutoLog` to logout on any given danger + - `AutoReconnect (Support pending)` to get back on server after waiting (for example a player comes in range) + - `AntiHunger` slows food level decrease but makes block breaking slower +- [x] Highly dynamical generated blueprints + - Three Modes: `Highway` (for full highways), `Tunnel` (optimized for digging), `Flat` (for repair obsidian sky) + - `ClearSpace` Choose to break wrong blocks and tunneling + - `ClearHeight` Choose the height of tunnel + - `BuildWidth` Choose the width of the highway + - `Railing` Choose if the highway has rims/guardrails + - `RailingHeight` Choose height of the rims/guardrails + - `CornerBlock` Choose if u want to have a corner block or not -### Clone Repository + + -### Setup IDE +### Installation +1. Get the latest Lambda release here https://github.com/lambda-client/lambda/releases +2. Open Lambda menu in main menu to open plugin settings +3. Press `Open Plugin Folder` +4. Move plugin `HighwayTools-*.jar` into the folder `.minecraft/lambda/plugins` -### Configure Gradle +### Known issues +- `AutoLog` is not compatible with `AutoReconnect` -### Plugin +### Troubleshooting +- Deactivate `AntiHunger` for faster block breaking because it makes you basically float. +- If stuck check, if AutoCenter is on `MOTION` to get moved to middle of the block (Baritone can't move exactly to block center) +- If placed block disappear increase the `TickDelayPlace` until it works +- Deactivate `IllegalPlacements` if the server requires (like 2b2t) -### PluginModule +Any suggestions and questions: Constructor#9948 on Discord Made by @Avanatiker +Report bugs on [Issues](https://github.com/lambda-plugins/HighwayTools/issues) and if not possible message EnigmA_008#1505 on Discord. -### ClientCommand +`Copyright ©2019-2022` Constructor#9948 alias Avanatiker. All Rights Reserved. Permission to use, copy, modify, and distribute this software and its documentation for educational, research, and not-for-profit purposes, without fee and without a signed licensing agreement, is hereby granted, provided that the above copyright notice, this paragraph appears in all copies, modifications, and distributions. -### PluginLabelHud - -### Background Jobs - -### Config - -### Build \ No newline at end of file +By downloading this software you agree to be bound by the terms of service. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 5360c50..e8729f6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,47 +1,50 @@ -version project.modVersion -group project.modGroup - -wrapper { gradleVersion = '6.8.3' } - buildscript { repositories { - maven { url = 'https://files.minecraftforge.net/maven' } - maven { url = 'https://repo.spongepowered.org/repository/maven-public/' } + mavenCentral() + maven { url = 'https://maven.minecraftforge.net/' } + maven { url = 'https://repo.spongepowered.org/maven/' } } - dependencies { - classpath 'net.minecraftforge.gradle:ForgeGradle:4.+' + classpath 'net.minecraftforge.gradle:ForgeGradle:5.+' classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.github.jengelman.gradle.plugins:shadow:6.1.0' } } -apply plugin: 'idea' -apply plugin: 'kotlin' +plugins { + id 'org.jetbrains.kotlin.jvm' version "$kotlinVersion" +} + apply plugin: 'net.minecraftforge.gradle' -apply plugin: 'org.spongepowered.mixin' +apply plugin: 'com.github.johnrengelman.shadow' -compileKotlin { - kotlinOptions { - jvmTarget = "1.8" - useIR = true - } -} +version project.modVersion +group project.modGroup compileJava { sourceCompatibility = targetCompatibility = '1.8' options.encoding = 'UTF-8' + // Disables Gradle build caching for this task + // If build caching is enabled this can cause the refmap to not be built and included + outputs.upToDateWhen { false } +} + +compileKotlin.kotlinOptions { + freeCompilerArgs += '-Xlambdas=indy' + freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn' + freeCompilerArgs += '-Xopt-in=kotlin.contracts.ExperimentalContracts' } repositories { maven { url = 'https://repo.spongepowered.org/repository/maven-public/' } maven { url = 'https://impactdevelopment.github.io/maven/' } maven { url = "https://jitpack.io" } + mavenCentral() } minecraft { - mappings channel: 'stable', version: '39-1.12' + mappings channel: "$mappingsChannel", version: "$mappingsVersion" runs { client { @@ -52,12 +55,6 @@ minecraft { property 'forge.logging.markers', 'SCAN,REGISTRIES,REGISTRYDUMP' property 'forge.logging.console.level', 'debug' - - mods { - highwaytools { - source sourceSets.main - } - } } } } @@ -73,10 +70,12 @@ configurations { } dependencies { - minecraft 'net.minecraftforge:forge:1.12.2-14.23.5.2855' - implementation(files("lib/lambda-2.04.xx-dev-api.jar")) + minecraft "net.minecraftforge:forge:$minecraftVersion-$forgeVersion" - implementation('org.spongepowered:mixin:0.7.11-SNAPSHOT') { + // Online maven dependency coming soon + implementation files("lib/lambda-3.3.0-api.jar") + + implementation('org.spongepowered:mixin:0.8.5') { exclude module: 'commons-io' exclude module: 'gson' exclude module: 'guava' @@ -85,51 +84,83 @@ dependencies { } // Hacky way to get mixin work - annotationProcessor('org.spongepowered:mixin:0.8.2:processor') { + annotationProcessor('org.spongepowered:mixin:0.8.5:processor') { exclude module: 'gson' } // Kotlin libs // kotlin-stdlib-common and annotations aren't required at runtime - implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version") { + implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") { exclude module: 'kotlin-stdlib-common' exclude module: 'annotations' } - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version") { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion") { exclude module: 'kotlin-stdlib-common' exclude module: 'annotations' } - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version") { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") { exclude module: 'kotlin-stdlib-common' exclude module: 'annotations' } - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version") { + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") { exclude module: 'kotlin-stdlib-common' exclude module: 'annotations' } - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines_version") { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion") { exclude module: 'kotlin-stdlib-common' exclude module: 'annotations' } // Add them back to compileOnly (provided) - compileOnly "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version" - compileOnly 'org.jetbrains:annotations:20.1.0' + compileOnly "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion" + compileOnly 'org.jetbrains:annotations:23.0.0' // This Baritone will NOT be included in the jar implementation 'com.github.cabaletta:baritone:1.2.14' - + + // Unit Testing frameworks + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' + // Add your dependencies below // jarLibs 'com.lambda:example:1.0.0' } -mixin { - defaultObfuscationEnv 'searge' - add sourceSets.main, 'mixins.lambda.refmap.json' +processResources { + exclude '**/rawimagefiles' + + from(sourceSets.main.resources.srcDirs) { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + include 'plugin_info.json' + expand 'version': project.version + } +} + +test { + useJUnitPlatform() +} + +jar.finalizedBy('reobfJar') + +shadowJar { + archiveClassifier.set('') + configurations = [] + relocate 'kotlin', 'com.lambda.shadow.kotlin' + relocate 'kotlinx', 'com.lambda.shadow.kotlinx' + finalizedBy 'reobfShadowJar' +} + +reobf { + shadowJar {} + jar { + enabled = false + } } -jar.finalizedBy('reobfJar') \ No newline at end of file +artifacts { + shadowJar +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index bfea7f4..bb17730 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,13 @@ -kotlin.code.style=official org.gradle.jvmargs=-Xmx3G +org.gradle.parallel=true + modGroup=com.lambda -modVersion=9.9 -kotlin_version=1.4.32 -kotlinx_coroutines_version=1.4.3 \ No newline at end of file +modVersion=10.3.1 + +minecraftVersion=1.12.2 +forgeVersion=14.23.5.2860 +mappingsChannel=stable +mappingsVersion=39-1.12 + +kotlinVersion=1.8.10 +kotlinxCoroutinesVersion=1.6.4 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 0d4a951..e708b1c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d913..ae04661 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/gradlew.bat b/gradlew.bat index 107acd3..ac1b06f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,89 +1,89 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lib/lambda-2.04.xx-dev-api.jar b/lib/lambda-2.04.xx-dev-api.jar deleted file mode 100644 index caede2f..0000000 Binary files a/lib/lambda-2.04.xx-dev-api.jar and /dev/null differ diff --git a/lib/lambda-3.3.0-api-source.jar b/lib/lambda-3.3.0-api-source.jar new file mode 100644 index 0000000..d293d8b Binary files /dev/null and b/lib/lambda-3.3.0-api-source.jar differ diff --git a/lib/lambda-3.3.0-api.jar b/lib/lambda-3.3.0-api.jar new file mode 100644 index 0000000..d2ef77c Binary files /dev/null and b/lib/lambda-3.3.0-api.jar differ diff --git a/setupWorkspace.sh b/setupWorkspace.sh new file mode 100755 index 0000000..74e4296 --- /dev/null +++ b/setupWorkspace.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +# Used to setup workspace and fix building on unix / Git BASH +# +# Usage: "./setupWorkspace.sh" + +# + +# To allow use from outside the lambda directory +cd "$(dirname "$0")" || exit + +echo "[$(date +"%H:%M:%S")] Running gradlew classes without daemon..." +./gradlew --no-daemon classes || { + echo "[$(date +"%H:%M:%S")] ERROR: Running gradlew build failed! Run './gradlew --no-daemon classes' manually" + exit 1 +} + +cat logo_ascii.txt 2>/dev/null +echo "==========================================================================" +echo "" +echo "[$(date +"%H:%M:%S")] Build succeeded! All checks passed, you can build normally now! Welcome to Lambda." +echo "" +echo "==========================================================================" diff --git a/src/main/kotlin/HighwayTools.kt b/src/main/kotlin/HighwayTools.kt index 9511bfb..00c83e8 100644 --- a/src/main/kotlin/HighwayTools.kt +++ b/src/main/kotlin/HighwayTools.kt @@ -1,85 +1,36 @@ -import baritone.api.pathing.goals.GoalNear -import com.lambda.client.event.SafeClientEvent import com.lambda.client.event.events.PacketEvent +import com.lambda.client.event.events.PlayerTravelEvent +import com.lambda.client.event.events.RenderOverlayEvent import com.lambda.client.event.events.RenderWorldEvent -import com.lambda.client.manager.managers.PlayerPacketManager.sendPlayerPacket +import com.lambda.client.event.listener.listener import com.lambda.client.module.Category -import com.lambda.client.module.modules.client.Hud.primaryColor -import com.lambda.client.module.modules.client.Hud.secondaryColor -import com.lambda.client.module.modules.combat.AutoLog -import com.lambda.client.module.modules.misc.AntiAFK -import com.lambda.client.module.modules.misc.AutoObsidian -import com.lambda.client.module.modules.movement.AntiHunger -import com.lambda.client.module.modules.movement.Velocity -import com.lambda.client.module.modules.player.AutoEat -import com.lambda.client.module.modules.player.InventoryManager -import com.lambda.client.module.modules.player.LagNotifier import com.lambda.client.plugin.api.PluginModule -import com.lambda.client.process.PauseProcess import com.lambda.client.setting.settings.impl.collection.CollectionSetting -import com.lambda.client.util.* -import com.lambda.client.util.EntityUtils.flooredPosition -import com.lambda.client.util.EntityUtils.getDroppedItems -import com.lambda.client.util.color.ColorHolder -import com.lambda.client.util.graphics.ESPRenderer -import com.lambda.client.util.graphics.font.TextComponent -import com.lambda.client.util.items.* -import com.lambda.client.util.math.CoordinateConverter.asString -import com.lambda.client.util.math.Direction -import com.lambda.client.util.math.RotationUtils.getRotationTo -import com.lambda.client.util.math.VectorUtils -import com.lambda.client.util.math.VectorUtils.distanceTo -import com.lambda.client.util.math.VectorUtils.multiply -import com.lambda.client.util.math.VectorUtils.toVec3dCenter -import com.lambda.client.util.math.isInSight -import com.lambda.client.util.text.MessageSendHelper.sendChatMessage -import com.lambda.client.util.text.MessageSendHelper.sendRawChatMessage +import com.lambda.client.util.items.shulkerList import com.lambda.client.util.threads.* -import com.lambda.client.util.world.* -import com.lambda.commons.extension.ceilToInt -import com.lambda.commons.extension.floorToInt -import com.lambda.event.listener.listener -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import net.minecraft.block.Block -import net.minecraft.block.BlockLiquid -import net.minecraft.client.audio.PositionedSoundRecord -import net.minecraft.client.gui.inventory.GuiContainer -import net.minecraft.enchantment.EnchantmentHelper import net.minecraft.init.Blocks -import net.minecraft.init.Enchantments import net.minecraft.init.Items -import net.minecraft.init.SoundEvents -import net.minecraft.inventory.ClickType -import net.minecraft.inventory.ItemStackHelper -import net.minecraft.inventory.Slot -import net.minecraft.item.* -import net.minecraft.network.play.client.* -import net.minecraft.network.play.server.SPacketBlockChange -import net.minecraft.network.play.server.SPacketOpenWindow -import net.minecraft.network.play.server.SPacketPlayerPosLook -import net.minecraft.stats.StatList -import net.minecraft.util.EnumFacing -import net.minecraft.util.EnumHand -import net.minecraft.util.NonNullList -import net.minecraft.util.SoundCategory -import net.minecraft.util.math.AxisAlignedBB -import net.minecraft.util.math.BlockPos -import net.minecraft.util.math.RayTraceResult -import net.minecraft.util.math.Vec3d -import net.minecraft.world.EnumDifficulty +import net.minecraft.item.Item import net.minecraftforge.fml.common.gameevent.TickEvent -import kotlin.math.abs -import kotlin.random.Random.Default.nextInt +import trombone.IO.DebugLevel +import trombone.IO.DisableMode +import trombone.IO.pauseCheck +import trombone.Pathfinder.updatePathing +import trombone.Renderer.renderOverlay +import trombone.Renderer.renderWorld +import trombone.Trombone.Structure +import trombone.Trombone.active +import trombone.Trombone.tick +import trombone.Trombone.onDisable +import trombone.Trombone.onEnable +import trombone.handler.Packet.handlePacket /** * @author Avanatiker * @since 20/08/2020 */ -internal object HighwayTools : PluginModule( +object HighwayTools : PluginModule( name = "HighwayTools", description = "Be the grief a step a head.", category = Category.MISC, @@ -87,7 +38,7 @@ internal object HighwayTools : PluginModule( modulePriority = 10, pluginMain = HighwayToolsPlugin ) { - private val page by setting("Page", Page.BUILD, description = "Switch between the setting pages") + private val page by setting("Page", Page.BLUEPRINT, description = "Switch between setting pages") private val defaultIgnoreBlocks = linkedSetOf( "minecraft:standing_sign", @@ -97,98 +48,90 @@ internal object HighwayTools : PluginModule( "minecraft:bedrock", "minecraft:end_portal", "minecraft:end_portal_frame", - "minecraft:portal" + "minecraft:portal", + "minecraft:piston_extension", + "minecraft:barrier" ) - // build settings - val mode by setting("Mode", Mode.HIGHWAY, { page == Page.BUILD }, description = "Choose the structure") - private val width by setting("Width", 6, 1..11, 1, { page == Page.BUILD }, description = "Sets the width of blueprint") - private val height by setting("Height", 4, 1..6, 1, { page == Page.BUILD && clearSpace }, description = "Sets height of blueprint") - private val backfill by setting("Backfill", false, { page == Page.BUILD && mode == Mode.TUNNEL }, description = "Fills the tunnel behind you") - private val clearSpace by setting("Clear Space", true, { page == Page.BUILD && mode == Mode.HIGHWAY }, description = "Clears out the tunnel if necessary") - private val cleanFloor by setting("Clean Floor", false, { page == Page.BUILD && mode == Mode.TUNNEL && !backfill }, description = "Cleans up the tunnels floor") - private val cleanWalls by setting("Clean Walls", false, { page == Page.BUILD && mode == Mode.TUNNEL && !backfill }, description = "Cleans up the tunnels walls") - private val cleanRoof by setting("Clean Roof", false, { page == Page.BUILD && mode == Mode.TUNNEL && !backfill }, description = "Cleans up the tunnels roof") - private val cleanCorner by setting("Clean Corner", false, { page == Page.BUILD && mode == Mode.TUNNEL && !cornerBlock && !backfill && width > 2 }, description = "Cleans up the tunnels corner") - private val cornerBlock by setting("Corner Block", false, { page == Page.BUILD && (mode == Mode.HIGHWAY || (mode == Mode.TUNNEL && !backfill && width > 2)) }, description = "If activated will break the corner in tunnel or place a corner while paving") - private val railing by setting("Railing", true, { page == Page.BUILD && mode == Mode.HIGHWAY }, description = "Adds a railing / rim / border to the highway") - private val railingHeight by setting("Railing Height", 1, 1..4, 1, { railing && page == Page.BUILD && mode == Mode.HIGHWAY }, description = "Sets height of railing") + // blueprint + val mode by setting("Mode", Structure.HIGHWAY, { page == Page.BLUEPRINT }, description = "Choose the structure") + val width by setting("Width", 6, 1..11, 1, { page == Page.BLUEPRINT }, description = "Sets the width of blueprint", unit = " blocks") + val height by setting("Height", 4, 2..6, 1, { page == Page.BLUEPRINT && clearSpace }, description = "Sets height of blueprint", unit = " blocks") + val backfill by setting("Backfill", false, { page == Page.BLUEPRINT && mode == Structure.TUNNEL }, description = "Fills the tunnel behind you") + val clearSpace by setting("Clear Space", true, { page == Page.BLUEPRINT && mode == Structure.HIGHWAY }, description = "Clears out the tunnel if necessary") + val cleanFloor by setting("Clean Floor", false, { page == Page.BLUEPRINT && mode == Structure.TUNNEL && !backfill }, description = "Cleans up the tunnels floor") + val cleanRightWall by setting("Clean Right Wall", false, { page == Page.BLUEPRINT && mode == Structure.TUNNEL && !backfill }, description = "Cleans up the right wall") + val cleanLeftWall by setting("Clean Left Wall", false, { page == Page.BLUEPRINT && mode == Structure.TUNNEL && !backfill }, description = "Cleans up the left wall") + val cleanRoof by setting("Clean Roof", false, { page == Page.BLUEPRINT && mode == Structure.TUNNEL && !backfill }, description = "Cleans up the tunnels roof") + val cleanCorner by setting("Clean Corner", false, { page == Page.BLUEPRINT && mode == Structure.TUNNEL && !cornerBlock && !backfill && width > 2 }, description = "Cleans up the tunnels corner") + val cornerBlock by setting("Corner Block", false, { page == Page.BLUEPRINT && (mode == Structure.HIGHWAY || (mode == Structure.TUNNEL && !backfill && width > 2)) }, description = "If activated will break the corner in tunnel or place a corner while paving") + val railing by setting("Railing", true, { page == Page.BLUEPRINT && mode == Structure.HIGHWAY }, description = "Adds a railing/rim/border to the highway") + val railingHeight by setting("Railing Height", 1, 1..4, 1, { railing && page == Page.BLUEPRINT && mode == Structure.HIGHWAY }, description = "Sets height of railing", unit = " blocks") private val materialSaved = setting("Material", "minecraft:obsidian", { false }) private val fillerMatSaved = setting("FillerMat", "minecraft:netherrack", { false }) + private val foodItem = setting("FoodItem", "minecraft:golden_apple", { false }) val ignoreBlocks = setting(CollectionSetting("IgnoreList", defaultIgnoreBlocks, { false })) - // behavior settings - private val interacting by setting("Rotation Mode", RotationMode.SPOOF, { page == Page.BEHAVIOR }, description = "Force view client side, only server side or no interaction at all") - private val dynamicDelay by setting("Dynamic Place Delay", true, { page == Page.BEHAVIOR }, description = "Slows down on failed placement attempts") - private val placeDelay by setting("Place Delay", 3, 1..20, 1, { page == Page.BEHAVIOR }, description = "Sets the delay ticks between placement tasks") - private val breakDelay by setting("Break Delay", 1, 1..20, 1, { page == Page.BEHAVIOR }, description = "Sets the delay ticks between break tasks") - private val illegalPlacements by setting("Illegal Placements", false, { page == Page.BEHAVIOR }, description = "Do not use on 2b2t. Tries to interact with invisible surfaces") - private val bridging by setting("Bridging", true, { page == Page.BEHAVIOR }, description = "Tries to bridge / scaffold when stuck placing") - private val instantMine by setting("Instant Mine", false, { page == Page.BEHAVIOR }, description = "Instant mine NCP exploit.") - private val multiBuilding by setting("Shuffle Tasks", false, { page == Page.BEHAVIOR }, description = "Only activate when working with several players") - private val taskTimeout by setting("Task Timeout", 8, 0..20, 1, { page == Page.BEHAVIOR }, description = "Timeout for waiting for the server to try again") - private val rubberbandTimeout by setting("Rubberband Timeout", 50, 5..100, 5, { page == Page.BEHAVIOR }, description = "Timeout for pausing after a lag") - private val maxReach by setting("Max Reach", 4.9f, 1.0f..6.0f, 0.1f, { page == Page.BEHAVIOR }, description = "Sets the range of the blueprint. Decrease when tasks fail!") - private val maxBreaks by setting("Multi Break", 1, 1..5, 1, { page == Page.BEHAVIOR }, description = "EXPERIMENTAL: Breaks multiple instant breaking blocks per tick in view") - private val limitOrigin by setting("Limited by", LimitMode.FIXED, { page == Page.BEHAVIOR }, description = "Changes the origin of limit: Client / Server TPS") - private val limitFactor by setting("Limit Factor", 1.0f, 0.5f..2.0f, 0.01f, { page == Page.BEHAVIOR }, description = "EXPERIMENTAL: Factor for TPS which acts as limit for maximum breaks per second.") - private val placementSearch by setting("Place Deep Search", 2, 1..4, 1, { page == Page.BEHAVIOR }, description = "EXPERIMENTAL: Attempts to find a support block for placing against") + // behavior + val maxReach by setting("Max Reach", 4.9f, 1.0f..7.0f, 0.1f, { page == Page.BEHAVIOR }, description = "Sets the range of the blueprint. Decrease when tasks fail!", unit = " blocks") + val multiBuilding by setting("Shuffle Tasks", false, { page == Page.PLACING }, description = "Only activate when working with several players") + val rubberbandTimeout by setting("Rubberband Timeout", 50, 5..100, 5, { page == Page.BEHAVIOR }, description = "Timeout for pausing after a lag") + val taskTimeout by setting("Task Timeout", 8, 0..20, 1, { page == Page.BEHAVIOR }, description = "Timeout for waiting for the server to try again", unit = " ticks") + val moveSpeed by setting("Packet Move Speed", 0.2f, 0.0f..1.0f, 0.01f, { page == Page.BEHAVIOR }, description = "Maximum player velocity per tick", unit = "m/t") + + // mining + val breakDelay by setting("Break Delay", 1, 1..20, 1, { page == Page.MINING }, description = "Sets the delay ticks between break tasks", unit = " ticks") + val miningSpeedFactor by setting("Mining Speed Factor", 1.0f, 0.0f..2.0f, 0.01f, { page == Page.MINING }, description = "Factor to manipulate calculated mining speed") + val interactionLimit by setting("Interaction Limit", 20, 1..100, 1, { page == Page.MINING }, description = "Set the interaction limit per second", unit = " interactions/s") + val multiBreak by setting("Multi Break", true, { page == Page.MINING }, description = "Breaks multiple instant breaking blocks intersecting with view vector") + val packetFlood by setting("Packet Flood", false, { page == Page.MINING }, description = "Exploit for faster packet breaks. Sends START and STOP packet on same tick.") + val instantMine by setting("Ender Chest Instant Mine", false, { page == Page.MINING && packetFlood }, description = "Instant mine NCP exploit") + + // placing + val placeDelay by setting("Place Delay", 3, 1..20, 1, { page == Page.PLACING }, description = "Sets the delay ticks between placement tasks") + val dynamicDelay by setting("Dynamic Place Delay", true, { page == Page.PLACING }, description = "Slows down on failed placement attempts") + val illegalPlacements by setting("Illegal Placements", false, { page == Page.PLACING }, description = "Do not use on 2b2t. Tries to interact with invisible surfaces") + val scaffold by setting("Scaffold", true, { page == Page.PLACING }, description = "Tries to bridge / scaffold when stuck placing") + val placementSearch by setting("Place Deep Search", 2, 1..4, 1, { page == Page.PLACING }, description = "EXPERIMENTAL: Attempts to find a support block for placing against", unit = " blocks") // storage management - private val storageManagement by setting("Manage Storage", false, { page == Page.STORAGE_MANAGEMENT }, description = "Choose to interact with container using only packets.") - private val leaveEmptyShulkers by setting("Leave Empty Shulkers", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Does not break empty shulkers.") - private val grindObsidian by setting("Grind Obsidian", true, { page == Page.STORAGE_MANAGEMENT }, description = "Destroy Ender Chests to obtain Obsidian.") - private val saveMaterial by setting("Save Material", 12, 0..64, 1, { page == Page.STORAGE_MANAGEMENT }, description = "How many material blocks are saved") - private val saveTools by setting("Save Tools", 1, 0..36, 1, { page == Page.STORAGE_MANAGEMENT }, description = "How many tools are saved") - private val saveEnder by setting("Save Ender Chests", 1, 0..64, 1, { page == Page.STORAGE_MANAGEMENT }, description = "How many ender chests are saved") - private val disableMode by setting("Disable Mode", DisableMode.NONE, { page == Page.STORAGE_MANAGEMENT }, description = "Choose action when bot is out of materials or tools") - private val tryRefreshSlots by setting("Try refresh slot", false, { page == Page.STORAGE_MANAGEMENT }, description = "Clicks a slot on desync") - - // stat settings - val anonymizeStats by setting("Anonymize", false, { page == Page.STATS }, description = "Censors all coordinates in HUD and Chat") - private val simpleMovingAverageRange by setting("Moving Average", 60, 5..600, 5, { page == Page.STATS }, description = "Sets the timeframe of the average in seconds") - private val showSession by setting("Show Session", true, { page == Page.STATS }, description = "Toggles the Session section in HUD") - private val showLifeTime by setting("Show Lifetime", true, { page == Page.STATS }, description = "Toggles the Lifetime section in HUD") - private val showPerformance by setting("Show Performance", true, { page == Page.STATS }, description = "Toggles the Performance section in HUD") - private val showEnvironment by setting("Show Environment", true, { page == Page.STATS }, description = "Toggles the Environment section in HUD") - private val showTask by setting("Show Task", true, { page == Page.STATS }, description = "Toggles the Task section in HUD") - private val showEstimations by setting("Show Estimations", true, { page == Page.STATS }, description = "Toggles the Estimations section in HUD") - private val resetStats = setting("Reset Stats", false, { page == Page.STATS }, description = "Resets the stats") - - // config - private val fakeSounds by setting("Fake Sounds", true, { page == Page.CONFIG }, description = "Adds artificial sounds to the actions") - private val info by setting("Show Info", true, { page == Page.CONFIG }, description = "Prints session stats in chat") - private val printDebug by setting("Show Queue", false, { page == Page.CONFIG }, description = "Shows task queue in HUD") - private val debugMessages by setting("Debug Messages", DebugMessages.IMPORTANT, { page == Page.CONFIG }, description = "Sets the debug log depth level") - private val goalRender by setting("Goal Render", false, { page == Page.CONFIG }, description = "Renders the baritone goal") - private val filled by setting("Filled", true, { page == Page.CONFIG }, description = "Renders colored task surfaces") - private val outline by setting("Outline", true, { page == Page.CONFIG }, description = "Renders colored task outlines") - private val aFilled by setting("Filled Alpha", 26, 0..255, 1, { filled && page == Page.CONFIG }, description = "Sets the opacity") - private val aOutline by setting("Outline Alpha", 91, 0..255, 1, { outline && page == Page.CONFIG }, description = "Sets the opacity") - - enum class Mode { - HIGHWAY, FLAT, TUNNEL - } + val storageManagement by setting("Manage Storage", true, { page == Page.STORAGE_MANAGEMENT }, description = "Choose to interact with container using only packets") + val searchEChest by setting("Search Ender Chest", false, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Allow access to your ender chest") + val leaveEmptyShulkers by setting("Leave Empty Shulkers", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Does not break empty shulkers") + val grindObsidian by setting("Grind Obsidian", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Destroy Ender Chests to obtain Obsidian") + val fastFill by setting("Fast Fill", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Moves as many item stacks to inventory as possible") + val keepFreeSlots by setting("Free Slots", 1, 0..30, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many inventory slots are untouched on refill", unit = " slots") + val preferEnderChests by setting("Prefer Ender Chests", false, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Prevent using raw material shulkers") + val manageFood by setting("Manage Food", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Choose to manage food") + val saveMaterial by setting("Save Material", 12, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many material blocks are saved") + val saveTools by setting("Save Tools", 1, 0..36, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many tools are saved") + val saveEnder by setting("Save Ender Chests", 1, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many ender chests are saved") + val saveFood by setting("Save Food", 1, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && manageFood && storageManagement}, description = "How many food items are saved") + val minDistance by setting("Min Container Distance", 1.5, 0.0..3.0, 0.1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Avoid player movement collision with placement.", unit = " blocks") + val disableMode by setting("Disable Mode", DisableMode.NONE, { page == Page.STORAGE_MANAGEMENT }, description = "Choose action when bot is out of materials or tools") + val usingProxy by setting("Proxy", false, { disableMode == DisableMode.LOGOUT && page == Page.STORAGE_MANAGEMENT }, description = "Enable this if you are using a proxy to call the given command") + val proxyCommand by setting("Proxy Command", "/dc", { usingProxy && disableMode == DisableMode.LOGOUT && page == Page.STORAGE_MANAGEMENT }, description = "Command to be sent to log out") + + // render + val anonymizeStats by setting("Anonymize", false, { page == Page.RENDER }, description = "Censors all coordinates in HUD and Chat") + val fakeSounds by setting("Fake Sounds", true, { page == Page.RENDER }, description = "Adds artificial sounds to the actions") + val info by setting("Show Info", true, { page == Page.RENDER }, description = "Prints session stats in chat") + val debugLevel by setting("Debug Level", DebugLevel.IMPORTANT, { page == Page.RENDER }, description = "Sets the debug log depth level") + val goalRender by setting("Baritone Goal", false, { page == Page.RENDER }, description = "Renders the baritone goal") + val showCurrentPos by setting("Current Pos", false, { page == Page.RENDER }, description = "Renders the current position") + val filled by setting("Filled", true, { page == Page.RENDER }, description = "Renders colored task surfaces") + val outline by setting("Outline", true, { page == Page.RENDER }, description = "Renders colored task outlines") + val popUp by setting("Pop up", true, { page == Page.RENDER }, description = "Funny render effect") + val popUpSpeed by setting("Pop up speed", 150, 0..500, 1, { popUp && page == Page.RENDER }, description = "Sets speed of the pop up effect", unit = "ms") + val showDebugRender by setting("Debug Render", false, { page == Page.RENDER }, description = "Render debug info on tasks") + val textScale by setting("Text Scale", 1.0f, 0.0f..4.0f, 0.25f, { showDebugRender && page == Page.RENDER }, description = "Scale of debug text") + val disableWarnings by setting("Disable Warnings", false, { page == Page.RENDER }, description = "DANGEROUS: Disable warnings on enable") + val aFilled by setting("Filled Alpha", 26, 0..255, 1, { filled && page == Page.RENDER }, description = "Sets the opacity") + val aOutline by setting("Outline Alpha", 91, 0..255, 1, { outline && page == Page.RENDER }, description = "Sets the opacity") + val thickness by setting("Thickness", 2.0f, 0.25f..4.0f, 0.25f, { outline && page == Page.RENDER }, description = "Sets thickness of outline") private enum class Page { - BUILD, BEHAVIOR, STORAGE_MANAGEMENT, STATS, CONFIG - } - - @Suppress("UNUSED") - private enum class RotationMode { - OFF, SPOOF, VIEW_LOCK - } - - private enum class LimitMode { - FIXED, SERVER - } - - private enum class DisableMode { - NONE, ANTI_AFK, LOGOUT - } - - private enum class DebugMessages { - OFF, IMPORTANT, ALL + BLUEPRINT, BEHAVIOR, MINING, PLACING, STORAGE_MANAGEMENT, RENDER } // internal settings @@ -202,65 +145,12 @@ internal object HighwayTools : PluginModule( set(value) { fillerMatSaved.value = value.registryName.toString() } - private var baritoneSettingAllowPlace = false - private var baritoneSettingAllowBreak = false - private var baritoneSettingRenderGoal = false - - // Blue print - private var startingDirection = Direction.NORTH - private var currentBlockPos = BlockPos(0, -1, 0) - private var startingBlockPos = BlockPos(0, -1, 0) - var targetBlockPos = BlockPos(0, -1, 0) - var distancePending = 0 - private val blueprint = LinkedHashMap() - - // State - private val rubberbandTimer = TickTimer(TimeUnit.TICKS) - private var active = false - private var waitTicks = 0 - private var extraPlaceDelay = 0 - - // Rotation - private var lastHitVec = Vec3d.ZERO - private val rotateTimer = TickTimer(TimeUnit.TICKS) - - // Pathing - var goal: GoalNear? = null; private set - private var moveState = MovementState.RUNNING - - // Tasks - private val pendingTasks = LinkedHashMap() - private val doneTasks = LinkedHashMap() - private var sortedTasks: List = emptyList() - var lastTask: BlockTask? = null; private set - - private var containerTask = BlockTask(BlockPos.ORIGIN, TaskState.DONE, Blocks.AIR, Items.AIR) - private val shulkerOpenTimer = TickTimer(TimeUnit.TICKS) - - private val packetLimiterMutex = Mutex() - private val packetLimiter = ArrayDeque() - - // Stats - private val simpleMovingAveragePlaces = ArrayDeque() - private val simpleMovingAverageBreaks = ArrayDeque() - private val simpleMovingAverageDistance = ArrayDeque() - private var totalBlocksPlaced = 0 - private var totalBlocksBroken = 0 - private var totalDistance = 0.0 - private var runtimeMilliSeconds = 0 - private var prevFood = 0 - private var foodLoss = 1 - private var materialLeft = 0 - private var fillerMatLeft = 0 - private var lastToolDamage = 0 - private var durabilityUsages = 0 - var matPlaced = 0 - private var enderMined = 0 - var netherrackMined = 0 - private var pickaxeBroken = 0 + var food: Item + get() = Item.getByNameOrId(foodItem.value) ?: Items.GOLDEN_APPLE + set(value) { + foodItem.value = value.registryName.toString() + } - private val stateUpdateMutex = Mutex() - private val renderer = ESPRenderer() override fun isActive(): Boolean { return isEnabled && active @@ -273,2013 +163,36 @@ internal object HighwayTools : PluginModule( onEnable { runSafeR { - startingBlockPos = player.flooredPosition - currentBlockPos = startingBlockPos - startingDirection = Direction.fromEntity(player) - - baritoneSettingAllowPlace = BaritoneUtils.settings?.allowPlace?.value ?: true - baritoneSettingAllowBreak = BaritoneUtils.settings?.allowBreak?.value ?: true - BaritoneUtils.settings?.allowPlace?.value = false - BaritoneUtils.settings?.allowBreak?.value = false - - if (!goalRender) { - baritoneSettingRenderGoal = BaritoneUtils.settings?.renderGoal?.value ?: true - BaritoneUtils.settings?.renderGoal?.value = false - } - - pendingTasks.clear() - containerTask.updateState(TaskState.DONE) - refreshData() - printEnable() + onEnable() } ?: disable() } onDisable { runSafe { - BaritoneUtils.settings?.allowPlace?.value = baritoneSettingAllowPlace - BaritoneUtils.settings?.allowBreak?.value = baritoneSettingAllowBreak - BaritoneUtils.settings?.renderGoal?.value = baritoneSettingRenderGoal - - active = false - goal = null - lastTask = null - totalDistance += startingBlockPos.distanceTo(currentBlockPos) - - printDisable() - } - } - - resetStats.consumers.add { _, it -> - if (it) resetStats() - false - } - } - - private fun printEnable() { - if (info) { - sendRawChatMessage(" §9> §7Direction: §a${startingDirection.displayName} / ${startingDirection.displayNameXY}§r") - - if (!anonymizeStats) { - if (startingDirection.isDiagonal) { - sendRawChatMessage(" §9> §7Axis offset: §a%,d %,d§r".format(startingBlockPos.x, startingBlockPos.z)) - - if (abs(startingBlockPos.x) != abs(startingBlockPos.z)) { - sendRawChatMessage(" §9> §cYou may have an offset to diagonal highway position!") - } - } else { - if (startingDirection == Direction.NORTH || startingDirection == Direction.SOUTH) { - sendRawChatMessage(" §9> §7Axis offset: §a%,d§r".format(startingBlockPos.x)) - } else { - sendRawChatMessage(" §9> §7Axis offset: §a%,d§r".format(startingBlockPos.z)) - } - - } - } - - if (startingBlockPos.y != 120 && mode != Mode.TUNNEL) { - sendRawChatMessage(" §9> §cCheck altitude and make sure to build at Y: 120 for the correct height") - } - - if (AntiHunger.isEnabled) { - sendRawChatMessage(" §9> §cAntiHunger does slow down block interactions.") - } - - if (LagNotifier.isDisabled) { - sendRawChatMessage(" §9> §cYou should activate LagNotifier to make the bot stop on server lag.") - } - - if (AutoEat.isDisabled) { - sendRawChatMessage(" §9> §cYou should activate AutoEat to not die on starvation.") - } - - if (AutoLog.isDisabled) { - sendRawChatMessage(" §9> §cYou should activate AutoLog to prevent most deaths when afk.") - } - - if (multiBuilding && Velocity.isDisabled) { - sendRawChatMessage(" §9> §cMake sure to enable Velocity to not get pushed from your mates.") + onDisable() } - - if (material == fillerMat) { - sendRawChatMessage(" §9> §cMake sure to use §aTunnel Mode§c instead of having same material for both main and filler!") - } - - if (mode == Mode.HIGHWAY && height < 3) { - sendRawChatMessage(" §9> §cYou may increase the height to at least 3") - } - - } - } - - private fun printDisable() { - if (info) { - sendRawChatMessage(" §9> §7Placed blocks: §a%,d§r".format(totalBlocksPlaced)) - sendRawChatMessage(" §9> §7Destroyed blocks: §a%,d§r".format(totalBlocksBroken)) - sendRawChatMessage(" §9> §7Distance: §a%,d§r".format(startingBlockPos.distanceTo(currentBlockPos).toInt())) } } init { - safeListener { event -> - when (event.packet) { - is SPacketBlockChange -> { - val packet = event.packet as SPacketBlockChange - val pos = packet.blockPosition - if (!isInsideBlueprint(pos)) return@safeListener - - val prev = world.getBlockState(pos).block - val new = packet.getBlockState().block - - if (prev != new) { - val task = if (pos == containerTask.blockPos) { - containerTask - } else { - pendingTasks[pos] ?: return@safeListener - } - - when (task.taskState) { - TaskState.PENDING_BREAK, TaskState.BREAKING -> { - if (new == Blocks.AIR) { - runBlocking { - stateUpdateMutex.withLock { - task.updateState(TaskState.BROKEN) - } - } - } - } - TaskState.PENDING_PLACE -> { - if (task.block != Blocks.AIR && task.block == new) { - runBlocking { - stateUpdateMutex.withLock { - task.updateState(TaskState.PLACED) - } - } - } - } - else -> { - // Ignored - } - } - } - } - is SPacketPlayerPosLook -> { - rubberbandTimer.reset() - } - is SPacketOpenWindow -> { - val packet = event.packet as SPacketOpenWindow - if (containerTask.taskState != TaskState.DONE && - packet.guiId == "minecraft:shulker_box" && containerTask.isShulker || - packet.guiId == "minecraft:container" && !containerTask.isShulker) { - containerTask.isOpen = true - } - } - else -> { - // Nothing - } - } + safeListener { + handlePacket(it.packet) } listener { - renderer.render(false) - } - - safeListener { event -> - if (event.phase != TickEvent.Phase.START) return@safeListener - - updateRenderer() - updateFood() - - if (!rubberbandTimer.tick(rubberbandTimeout.toLong(), false) || - PauseProcess.isActive || - AutoObsidian.isActive() || - (world.difficulty == EnumDifficulty.PEACEFUL && - player.dimension == 1 && - @Suppress("UNNECESSARY_SAFE_CALL") - player.serverBrand?.contains("2b2t") == true - )) { - refreshData() - return@safeListener - } - - if (!active) { - active = true - BaritoneUtils.primary?.pathingControlManager?.registerProcess(HighwayToolsProcess) - } else { - // Cant update at higher frequency - if (runtimeMilliSeconds % 15000 == 0) { - connection.sendPacket(CPacketClientStatus(CPacketClientStatus.State.REQUEST_STATS)) - } - runtimeMilliSeconds += 50 - updateDequeues() - } - - doPathing() - runTasks() - - doRotation() - } - } - - private fun SafeClientEvent.updateRenderer() { - renderer.clear() - renderer.aFilled = if (filled) aFilled else 0 - renderer.aOutline = if (outline) aOutline else 0 - -// renderer.add(world.getBlockState(currentBlockPos).getSelectedBoundingBox(world, currentBlockPos), ColorHolder(255, 255, 255)) - - if (containerTask.taskState != TaskState.DONE) renderer.add(world.getBlockState(containerTask.blockPos).getSelectedBoundingBox(world, containerTask.blockPos), containerTask.taskState.color) - - pendingTasks.values.forEach { - if (it.taskState == TaskState.DONE) return@forEach - renderer.add(world.getBlockState(it.blockPos).getSelectedBoundingBox(world, it.blockPos), it.taskState.color) - } - - doneTasks.values.forEach { - if (it.block == Blocks.AIR || it.isShulker) return@forEach - renderer.add(world.getBlockState(it.blockPos).getSelectedBoundingBox(world, it.blockPos), it.taskState.color) - } - } - - private fun SafeClientEvent.updateFood() { - val currentFood = player.foodStats.foodLevel - if (currentFood < 7.0) { - sendChatMessage("$chatName Out of food, disabling") - mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)) - disable() - } - if (currentFood != prevFood) { - if (currentFood < prevFood) foodLoss++ - prevFood = currentFood - } - } - - private fun updateDequeues() { - val removeTime = System.currentTimeMillis() - simpleMovingAverageRange * 1000L - - updateDeque(simpleMovingAveragePlaces, removeTime) - updateDeque(simpleMovingAverageBreaks, removeTime) - updateDeque(simpleMovingAverageDistance, removeTime) - - runBlocking { - packetLimiterMutex.withLock { - updateDeque(packetLimiter, System.currentTimeMillis() - 1000L) - } - } - } - - private fun updateDeque(deque: ArrayDeque, removeTime: Long) { - while (deque.isNotEmpty() && deque.first() < removeTime) { - deque.removeFirst() - } - } - - private fun SafeClientEvent.doRotation() { - if (rotateTimer.tick(20L, false)) return - val rotation = lastHitVec?.let { getRotationTo(it) } ?: return - - when (interacting) { - RotationMode.SPOOF -> { - sendPlayerPacket { - rotate(rotation) - } - } - RotationMode.VIEW_LOCK -> { - player.rotationYaw = rotation.x - player.rotationPitch = rotation.y - } - else -> { - // RotationMode.OFF - } - } - } - - private fun SafeClientEvent.refreshData(originPos: BlockPos = currentBlockPos) { - moveState = MovementState.RUNNING - pendingTasks.clear() - doneTasks.clear() - lastTask = null - - blueprint.clear() - generateBluePrint(originPos) - - blueprint.forEach { (pos, block) -> - if (block == Blocks.AIR) { - addTaskClear(pos) - } else { - addTaskBuild(pos, block) - } - } - } - - private fun SafeClientEvent.addTaskBuild(pos: BlockPos, block: Block) { - val blockState = world.getBlockState(pos) - - when { - blockState.block == block -> { - addTaskToDone(pos, block) - } - world.isPlaceable(pos, true) -> { - if (checkSupport(pos, block)) { - addTaskToDone(pos, block) - } else { - addTaskToPending(pos, TaskState.PLACE, block) - } - } - else -> { - if (checkSupport(pos, block)) { - addTaskToDone(pos, block) - } else { - addTaskToPending(pos, TaskState.BREAK, block) - } - } - } - } - - private fun SafeClientEvent.checkSupport(pos: BlockPos, block: Block): Boolean { - return mode == Mode.HIGHWAY && - startingDirection.isDiagonal && - world.getBlockState(pos.up()).block == material && - block == fillerMat - } - - private fun SafeClientEvent.addTaskClear(pos: BlockPos) { - when { - world.isAirBlock(pos) -> { - addTaskToDone(pos, Blocks.AIR) - } - ignoreBlocks.contains(world.getBlockState(pos).block.registryName.toString()) -> { - addTaskToDone(pos, world.getBlockState(pos).block) - } - else -> { - addTaskToPending(pos, TaskState.BREAK, Blocks.AIR) - } - } - } - - private fun SafeClientEvent.generateBluePrint(feetPos: BlockPos) { - val basePos = feetPos.down() - - if (mode != Mode.FLAT) { - val zDirection = startingDirection - val xDirection = zDirection.clockwise(if (zDirection.isDiagonal) 1 else 2) - - for (x in -maxReach.floorToInt() * 2..maxReach.ceilToInt() * 2) { - val thisPos = basePos.add(zDirection.directionVec.multiply(x)) - if (clearSpace) generateClear(thisPos, xDirection) - if (mode == Mode.TUNNEL) { - if (backfill) { - generateBackfill(thisPos, xDirection) - } else { - if (cleanFloor) generateFloor(thisPos, xDirection) - if (cleanWalls) generateWalls(thisPos, xDirection) - if (cleanRoof) generateRoof(thisPos, xDirection) - if (cleanCorner && !cornerBlock && width > 2) generateCorner(thisPos, xDirection) - } - } else { - generateBase(thisPos, xDirection) - } - } - if (mode == Mode.TUNNEL && (!cleanFloor || backfill)) { - if (startingDirection.isDiagonal) { - for (x in 0..maxReach.floorToInt()) { - val pos = basePos.add(zDirection.directionVec.multiply(x)) - blueprint[pos] = fillerMat - blueprint[pos.add(startingDirection.clockwise(7).directionVec)] = fillerMat - } - } else { - for (x in 0..maxReach.floorToInt()) { - blueprint[basePos.add(zDirection.directionVec.multiply(x))] = fillerMat - } - } - } - - pickTasksInRange() - } else { - generateFlat(basePos) - } - } - - private fun SafeClientEvent.pickTasksInRange() { - val eyePos = player.getPositionEyes(1f) - - blueprint.keys.removeIf { - eyePos.distanceTo(it) > maxReach - 0.7 || - startingBlockPos.add(startingDirection.clockwise(4).directionVec.multiply(maxReach.toInt())).distanceTo(it) < maxReach - 1 - } - } - - private fun generateClear(basePos: BlockPos, xDirection: Direction) { - for (w in 0 until width) { - for (h in 0 until height) { - val x = w - width / 2 - val pos = basePos.add(xDirection.directionVec.multiply(x)).up(h) - - if (mode == Mode.HIGHWAY && h == 0 && isRail(w)) { - continue - } - - if (mode == Mode.HIGHWAY) { - blueprint[pos] = Blocks.AIR - } else { - if (!(isRail(w) && h == 0 && !cornerBlock && width > 2)) blueprint[pos.up()] = Blocks.AIR - } - } - } - } - - private fun generateBase(basePos: BlockPos, xDirection: Direction) { - for (w in 0 until width) { - val x = w - width / 2 - val pos = basePos.add(xDirection.directionVec.multiply(x)) - - if (mode == Mode.HIGHWAY && isRail(w)) { - if (!cornerBlock && width > 2 && startingDirection.isDiagonal) blueprint[pos] = fillerMat - val startHeight = if (cornerBlock && width > 2) 0 else 1 - for (y in startHeight..railingHeight) { - blueprint[pos.up(y)] = material - } - } else { - blueprint[pos] = material - } - } - } - - private fun generateFloor(basePos: BlockPos, xDirection: Direction) { - val wid = if (cornerBlock && width > 2) { - width - } else { - width - 2 - } - for (w in 0 until wid) { - val x = w - wid / 2 - val pos = basePos.add(xDirection.directionVec.multiply(x)) - blueprint[pos] = fillerMat - } - } - - private fun generateWalls(basePos: BlockPos, xDirection: Direction) { - val cb = if (!cornerBlock && width > 2) { - 1 - } else { - 0 - } - for (h in cb until height) { - blueprint[basePos.add(xDirection.directionVec.multiply(-1 - width / 2)).up(h + 1)] = fillerMat - blueprint[basePos.add(xDirection.directionVec.multiply(width - width / 2)).up(h + 1)] = fillerMat - } - } - - private fun generateRoof(basePos: BlockPos, xDirection: Direction) { - for (w in 0 until width) { - val x = w - width / 2 - val pos = basePos.add(xDirection.directionVec.multiply(x)) - blueprint[pos.up(height + 1)] = fillerMat - } - } - - private fun generateCorner(basePos: BlockPos, xDirection: Direction) { - blueprint[basePos.add(xDirection.directionVec.multiply(-1 - width / 2 + 1)).up()] = fillerMat - blueprint[basePos.add(xDirection.directionVec.multiply(width - width / 2 - 1)).up()] = fillerMat - } - - private fun generateBackfill(basePos: BlockPos, xDirection: Direction) { - for (w in 0 until width) { - for (h in 0 until height) { - val x = w - width / 2 - val pos = basePos.add(xDirection.directionVec.multiply(x)).up(h + 1) - - if (startingBlockPos.distanceTo(pos) < startingBlockPos.distanceTo(currentBlockPos)) { - blueprint[pos] = fillerMat - } - } - } - } - - private fun isRail(w: Int) = railing && w !in 1 until width - 1 - - private fun generateFlat(basePos: BlockPos) { - // Base - for (w1 in 0 until width) { - for (w2 in 0 until width) { - val x = w1 - width / 2 - val z = w2 - width / 2 - val pos = basePos.add(x, 0, z) - - blueprint[pos] = material - } - } - - // Clear - if (!clearSpace) return - for (w1 in -width..width) { - for (w2 in -width..width) { - for (y in 1 until height) { - val x = w1 - width / 2 - val z = w2 - width / 2 - val pos = basePos.add(x, y, z) - - blueprint[pos] = Blocks.AIR - } - } - } - } - - private fun addTaskToPending(blockPos: BlockPos, taskState: TaskState, material: Block) { - pendingTasks[blockPos] = (BlockTask(blockPos, taskState, material)) - } - - private fun addTaskToDone(blockPos: BlockPos, material: Block) { - doneTasks[blockPos] = (BlockTask(blockPos, TaskState.DONE, material)) - } - - private fun SafeClientEvent.doPathing() { - when (moveState) { - MovementState.RUNNING -> { - val nextPos = getNextPos() - - if (currentBlockPos.distanceTo(targetBlockPos) < 2 || - (distancePending > 0 && currentBlockPos.distanceTo(startingDirection.directionVec.multiply(distancePending)) < 2)) { - sendChatMessage("$chatName Reached target destination") - mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)) - disable() - return - } - - if (player.flooredPosition.distanceTo(nextPos) < 2) { - currentBlockPos = nextPos - } - - goal = GoalNear(nextPos, 0) - } - MovementState.PICKUP -> { - val droppedItemPos = getCollectingPosition() - goal = if (droppedItemPos != null) { - GoalNear(droppedItemPos, 0) - } else { - null - } - } - MovementState.BRIDGE -> { - // Bridge update - } - } - } - - private fun SafeClientEvent.getNextPos(): BlockPos { - var nextPos = currentBlockPos - - val possiblePos = currentBlockPos.add(startingDirection.directionVec) - - if (!isTaskDone(possiblePos) || - !isTaskDone(possiblePos.up()) || - !isTaskDone(possiblePos.down())) return nextPos - - if (checkTasks(possiblePos.up())) nextPos = possiblePos - - if (currentBlockPos != nextPos) { - for (x in 0..currentBlockPos.distanceTo(nextPos).toInt()) { - simpleMovingAverageDistance.add(System.currentTimeMillis()) - } - refreshData() - } - - return nextPos - } - - private fun SafeClientEvent.isTaskDone(pos: BlockPos) = - (pendingTasks[pos] ?: doneTasks[pos])?.let { - it.taskState == TaskState.DONE && world.getBlockState(pos).block != Blocks.PORTAL - } ?: false - - private fun checkTasks(pos: BlockPos): Boolean { - return pendingTasks.values.all { - it.taskState == TaskState.DONE || pos.distanceTo(it.blockPos) < maxReach - 0.7 - } - } - - private fun SafeClientEvent.runTasks() { - when { - pendingTasks.isEmpty() -> { - if (checkDoneTasks()) doneTasks.clear() - refreshData() - } - containerTask.taskState != TaskState.DONE -> { - if (containerTask.stuckTicks > containerTask.taskState.stuckTimeout) { - when (containerTask.taskState) { - TaskState.PICKUP -> { - player.inventorySlots.firstEmpty()?.let { - if (tryRefreshSlots) updateSlot(it.slotNumber) - } - containerTask.updateState(TaskState.DONE) - } - else -> { - // Nothing - } - } - } - pendingTasks.values.toList().forEach { - doTask(it, true) - } - doTask(containerTask, false) - } - else -> { - waitTicks-- - - pendingTasks.values.toList().forEach { - doTask(it, true) - } - - sortTasks() - - for (task in sortedTasks) { - if (!checkStuckTimeout(task)) return - if (task.taskState != TaskState.DONE && waitTicks > 0) return - - doTask(task, false) - - when (task.taskState) { - TaskState.DONE, TaskState.BROKEN, TaskState.PLACED -> { - continue - } - else -> { - break - } - } - } - } - } - } - - private fun SafeClientEvent.checkDoneTasks(): Boolean { - for (blockTask in doneTasks.values) { - val block = world.getBlockState(blockTask.blockPos).block - if (ignoreBlocks.contains(block.registryName.toString())) continue - - when { - blockTask.block == material && block != material -> return false - mode == Mode.TUNNEL && blockTask.block == fillerMat && block != fillerMat -> return false - blockTask.block == Blocks.AIR && block != Blocks.AIR -> return false - } - - } - return true - } - - private fun SafeClientEvent.sortTasks() { - - if (multiBuilding) { - pendingTasks.values.forEach { - it.shuffle() - } - - runBlocking { - stateUpdateMutex.withLock { - sortedTasks = pendingTasks.values.sortedWith( - compareBy { - it.taskState.ordinal - }.thenBy { - it.stuckTicks - }.thenBy { - it.shuffle - } - ) - } - } - } else { - val eyePos = player.getPositionEyes(1.0f) - - pendingTasks.values.forEach { - it.prepareSortInfo(this, eyePos) - } - - runBlocking { - stateUpdateMutex.withLock { - sortedTasks = pendingTasks.values.sortedWith( - compareBy { - it.taskState.ordinal - }.thenBy { - it.stuckTicks - }.thenByDescending { - it.sides - }.thenBy { - it.startDistance - }.thenBy { - it.eyeDistance - }.thenBy { - it.hitVecDistance - } - ) - } - } - } - } - - private fun SafeClientEvent.checkStuckTimeout(blockTask: BlockTask): Boolean { - val timeout = blockTask.taskState.stuckTimeout - - if (blockTask.stuckTicks > timeout) { - when (blockTask.taskState) { - TaskState.PENDING_BREAK -> { - blockTask.updateState(TaskState.BREAK) - } - TaskState.PENDING_PLACE -> { - blockTask.updateState(TaskState.PLACE) - } - else -> { - if (debugMessages != DebugMessages.OFF) { - if (!anonymizeStats) { - sendChatMessage("$chatName Stuck while ${blockTask.taskState}@(${blockTask.blockPos.asString()}) for more than $timeout ticks (${blockTask.stuckTicks}), refreshing data.") - } else { - sendChatMessage("$chatName Stuck while ${blockTask.taskState} for more than $timeout ticks (${blockTask.stuckTicks}), refreshing data.") - } - } - - when (blockTask.taskState) { - TaskState.PLACE -> { - if (dynamicDelay && extraPlaceDelay < 10) extraPlaceDelay += 1 - - if (tryRefreshSlots) updateSlot() - } - TaskState.BREAK -> { - if (tryRefreshSlots) updateSlot() - } - else -> { - // Nothing - } - } - - refreshData() - return false - } - } + renderWorld() } - return true - } - - private fun SafeClientEvent.doTask(blockTask: BlockTask, updateOnly: Boolean) { - if (!updateOnly) blockTask.onTick() - - when (blockTask.taskState) { - TaskState.DONE -> { - doDone(blockTask) - } - TaskState.RESTOCK -> { - doRestock() - } - TaskState.PICKUP -> { - doPickup() - } - TaskState.OPEN_CONTAINER -> { - doOpenContainer() - } - TaskState.BREAKING -> { - doBreaking(blockTask, updateOnly) - } - TaskState.BROKEN -> { - doBroken(blockTask) - } - TaskState.PLACED -> { - doPlaced(blockTask) - } - TaskState.BREAK -> { - doBreak(blockTask, updateOnly) - } - TaskState.PLACE, TaskState.LIQUID_SOURCE, TaskState.LIQUID_FLOW -> { - doPlace(blockTask, updateOnly) - } - TaskState.PENDING_BREAK, TaskState.PENDING_PLACE -> { -// if (!updateOnly && debugMessages == DebugMessages.ALL) { -// sendChatMessage("$chatName Currently waiting for blockState updates...") -// } - blockTask.onStuck() - } + listener { + renderOverlay() } - } - - private fun doDone(blockTask: BlockTask) { - pendingTasks.remove(blockTask.blockPos) - doneTasks[blockTask.blockPos] = blockTask - } - - private fun SafeClientEvent.doRestock() { - if (mc.currentScreen is GuiContainer) { - val container = player.openContainer - container.getSlots(0..26).firstItem(containerTask.item)?.let { - moveToInventory(it) - } ?: run { - getShulkerWith(container.getSlots(0..26), containerTask.item)?.let { - moveToInventory(it) - } ?: run { - sendChatMessage("$chatName No material left in any container.") - mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f)) - disable() - when (disableMode) { - DisableMode.ANTI_AFK -> { - sendChatMessage("$chatName Going into AFK mode.") - AntiAFK.enable() - } - DisableMode.LOGOUT -> { - sendChatMessage("$chatName CAUTION: Logging of in X Minutes.") - } - DisableMode.NONE -> { - // Nothing - } - } - } - } - } else { - containerTask.updateState(TaskState.OPEN_CONTAINER) + safeListener { + if (it.phase == TickEvent.Phase.START) tick() } - } - private fun SafeClientEvent.doPickup() { - if (getCollectingPosition() == null) { - moveState = MovementState.RUNNING - containerTask.updateState(TaskState.DONE) - } else { - if (player.inventorySlots.firstEmpty() == null) { - getEjectSlot()?.let { - throwAllInSlot(it) - } - } - containerTask.onStuck() + safeListener { + if (!pauseCheck()) updatePathing() } } - - private fun SafeClientEvent.doOpenContainer() { - if (containerTask.isOpen) { - containerTask.updateState(TaskState.RESTOCK) - } else { - val center = containerTask.blockPos.toVec3dCenter() - val diff = player.getPositionEyes(1f).subtract(center) - val normalizedVec = diff.normalize() - - val side = EnumFacing.getFacingFromVector(normalizedVec.x.toFloat(), normalizedVec.y.toFloat(), normalizedVec.z.toFloat()) - val hitVecOffset = getHitVecOffset(side) - - lastHitVec = getHitVec(containerTask.blockPos, side) - rotateTimer.reset() - - if (shulkerOpenTimer.tick(50)) { - defaultScope.launch { - delay(20L) - onMainThreadSafe { - connection.sendPacket(CPacketPlayerTryUseItemOnBlock(containerTask.blockPos, side, EnumHand.MAIN_HAND, hitVecOffset.x.toFloat(), hitVecOffset.y.toFloat(), hitVecOffset.z.toFloat())) - player.swingArm(EnumHand.MAIN_HAND) - } - } - } - } - } - - private fun SafeClientEvent.doBreaking(blockTask: BlockTask, updateOnly: Boolean) { - when (world.getBlockState(blockTask.blockPos).block) { - Blocks.AIR -> { - waitTicks = breakDelay - blockTask.updateState(TaskState.BROKEN) - return - } - is BlockLiquid -> { - updateLiquidTask(blockTask) - return - } - } - - if (!updateOnly && swapOrMoveBestTool(blockTask)) { - mineBlock(blockTask) - } - } - - private fun SafeClientEvent.doBroken(blockTask: BlockTask) { - when (world.getBlockState(blockTask.blockPos).block) { - Blocks.AIR -> { - totalBlocksBroken++ - simpleMovingAverageBreaks.add(System.currentTimeMillis()) - - when { - blockTask.block == Blocks.AIR -> { - if (fakeSounds) { - val soundType = blockTask.block.getSoundType(world.getBlockState(blockTask.blockPos), world, blockTask.blockPos, player) - world.playSound(player, blockTask.blockPos, soundType.breakSound, SoundCategory.BLOCKS, (soundType.getVolume() + 1.0f) / 2.0f, soundType.getPitch() * 0.8f) - } - blockTask.updateState(TaskState.DONE) - } - blockTask == containerTask -> { - moveState = MovementState.PICKUP - blockTask.updateState(TaskState.PICKUP) - } - else -> { - blockTask.updateState(TaskState.PLACE) - } - } - } - else -> { - blockTask.updateState(TaskState.BREAK) - } - } - } - - private fun SafeClientEvent.doPlaced(blockTask: BlockTask) { - val currentBlock = world.getBlockState(blockTask.blockPos).block - - when { - blockTask.block == currentBlock && currentBlock != Blocks.AIR -> { - totalBlocksPlaced++ - simpleMovingAveragePlaces.add(System.currentTimeMillis()) - - if (dynamicDelay && extraPlaceDelay > 0) extraPlaceDelay -= 1 - - if (blockTask == containerTask) { - if (blockTask.destroy) { - blockTask.updateState(TaskState.BREAK) - } else { - blockTask.updateState(TaskState.RESTOCK) - } - } else { - blockTask.updateState(TaskState.DONE) - } - if (fakeSounds) { - val soundType = currentBlock.getSoundType(world.getBlockState(blockTask.blockPos), world, blockTask.blockPos, player) - world.playSound(player, blockTask.blockPos, soundType.placeSound, SoundCategory.BLOCKS, (soundType.getVolume() + 1.0f) / 2.0f, soundType.getPitch() * 0.8f) - } - } - blockTask.block == currentBlock && currentBlock == Blocks.AIR -> { - blockTask.updateState(TaskState.BREAK) - } - blockTask.block == Blocks.AIR && currentBlock != Blocks.AIR -> { - blockTask.updateState(TaskState.BREAK) - } - else -> { - blockTask.updateState(TaskState.PLACE) - } - } - } - - private fun SafeClientEvent.doBreak(blockTask: BlockTask, updateOnly: Boolean) { - val currentBlock = world.getBlockState(blockTask.blockPos).block - - if (ignoreBlocks.contains(currentBlock.registryName.toString()) && - !blockTask.isShulker && - !isInsideBlueprintBuild(blockTask.blockPos) || - currentBlock == Blocks.PORTAL || - currentBlock == Blocks.END_PORTAL || - currentBlock == Blocks.END_PORTAL_FRAME || - currentBlock == Blocks.BEDROCK) { - blockTask.updateState(TaskState.DONE) - } - - when (blockTask.block) { - fillerMat -> { - if (world.getBlockState(blockTask.blockPos.up()).block == material || - (!world.isPlaceable(blockTask.blockPos) && - world.getCollisionBox(blockTask.blockPos) != null)) { - blockTask.updateState(TaskState.DONE) - return - } - } - material -> { - if (currentBlock == material) { - blockTask.updateState(TaskState.DONE) - return - } - } - } - - when (currentBlock) { - Blocks.AIR -> { - if (blockTask.block == Blocks.AIR) { - blockTask.updateState(TaskState.BROKEN) - return - } else { - blockTask.updateState(TaskState.PLACE) - return - } - } - is BlockLiquid -> { - updateLiquidTask(blockTask) - return - } - } - - if (!updateOnly && player.onGround && swapOrMoveBestTool(blockTask)) { - if (handleLiquid(blockTask)) return - mineBlock(blockTask) - } - } - - private fun SafeClientEvent.doPlace(blockTask: BlockTask, updateOnly: Boolean) { - val currentBlock = world.getBlockState(blockTask.blockPos).block - - if (bridging && player.positionVector.distanceTo(currentBlockPos) < 1 && shouldBridge()) { - val factor = if (startingDirection.isDiagonal) { - 0.555 - } else { - 0.505 - } - val target = currentBlockPos.toVec3dCenter().add(Vec3d(startingDirection.directionVec).scale(factor)) - player.motionX = (target.x - player.posX).coerceIn(-0.2, 0.2) - player.motionZ = (target.z - player.posZ).coerceIn(-0.2, 0.2) - } - - if ((blockTask.taskState == TaskState.LIQUID_FLOW || - blockTask.taskState == TaskState.LIQUID_SOURCE) && - !world.isLiquid(blockTask.blockPos)) { - blockTask.updateState(TaskState.DONE) - return - } - - when (blockTask.block) { - material -> { - if (currentBlock == material) { - blockTask.updateState(TaskState.PLACED) - return - } else if (currentBlock != Blocks.AIR && !world.isLiquid(blockTask.blockPos)) { - blockTask.updateState(TaskState.BREAK) - return - } - } - fillerMat -> { - if (currentBlock == fillerMat) { - blockTask.updateState(TaskState.PLACED) - return - } else if (currentBlock != fillerMat && - mode == Mode.HIGHWAY && - world.getBlockState(blockTask.blockPos.up()).block == material) { - blockTask.updateState(TaskState.DONE) - return - } - } - Blocks.AIR -> { - if (!world.isLiquid(blockTask.blockPos)) { - if (currentBlock != Blocks.AIR) { - blockTask.updateState(TaskState.BREAK) - } else { - blockTask.updateState(TaskState.BROKEN) - } - return - } - } - } - - if (!updateOnly) { - if (!world.isPlaceable(blockTask.blockPos)) { - if (debugMessages == DebugMessages.ALL) { - if (!anonymizeStats) { - sendChatMessage("$chatName Invalid place position @(${blockTask.blockPos.asString()}) Removing task") - } else { - sendChatMessage("$chatName Invalid place position. Removing task") - } - } - - if (blockTask == containerTask) { - if (containerTask.block == currentBlock) { - containerTask.updateState(TaskState.BREAK) - } else { - containerTask.updateState(TaskState.DONE) - } - } else { - pendingTasks.remove(blockTask.blockPos) - } - return - } - - if (!swapOrMoveBlock(blockTask)) { - blockTask.onStuck() - return - } - - placeBlock(blockTask) - } - } - - private fun SafeClientEvent.placeBlock(blockTask: BlockTask) { - val neighbours = if (illegalPlacements) { - getNeighbourSequence(blockTask.blockPos, placementSearch, maxReach) - } else { - getNeighbourSequence(blockTask.blockPos, placementSearch, maxReach, true) - } - - when (neighbours.size) { - 0 -> { - if (debugMessages == DebugMessages.ALL) { - if (!anonymizeStats) { - sendChatMessage("$chatName No neighbours found for ${blockTask.blockPos}") - } else { - sendChatMessage("$chatName No neighbours found") - } - } - if (blockTask == containerTask) blockTask.updateState(TaskState.DONE) - blockTask.onStuck(21) - return - } - 1 -> { - val last = neighbours.last() - lastHitVec = getHitVec(last.pos, last.side) - rotateTimer.reset() - - placeBlockNormal(blockTask, last.pos, last.side) - } - else -> { - neighbours.forEach { - addTaskToPending(it.pos, TaskState.PLACE, fillerMat) - } - } - } - } - - private fun SafeClientEvent.placeBlockNormal(blockTask: BlockTask, placePos: BlockPos, side: EnumFacing) { - val hitVecOffset = getHitVecOffset(side) - val currentBlock = world.getBlockState(placePos).block - - waitTicks = if (dynamicDelay) { - placeDelay + extraPlaceDelay - } else { - placeDelay - } - blockTask.updateState(TaskState.PENDING_PLACE) - - if (currentBlock in blockBlacklist) { - connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.START_SNEAKING)) - } - - defaultScope.launch { - delay(20L) - onMainThreadSafe { - val placePacket = CPacketPlayerTryUseItemOnBlock(placePos, side, EnumHand.MAIN_HAND, hitVecOffset.x.toFloat(), hitVecOffset.y.toFloat(), hitVecOffset.z.toFloat()) - connection.sendPacket(placePacket) - player.swingArm(EnumHand.MAIN_HAND) - } - - if (currentBlock in blockBlacklist) { - delay(20L) - onMainThreadSafe { - connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.STOP_SNEAKING)) - } - } - - delay(50L * taskTimeout) - if (blockTask.taskState == TaskState.PENDING_PLACE) { - stateUpdateMutex.withLock { - blockTask.updateState(TaskState.PLACE) - } - if (dynamicDelay && extraPlaceDelay < 10) extraPlaceDelay += 1 - } - } - } - - private fun SafeClientEvent.mineBlock(blockTask: BlockTask) { - val blockState = world.getBlockState(blockTask.blockPos) - - if (blockState.block == Blocks.FIRE) { - val sides = getNeighbourSequence(blockTask.blockPos, 1, maxReach, true) - if (sides.isEmpty()) { - blockTask.updateState(TaskState.PLACE) - return - } - - lastHitVec = getHitVec(sides.last().pos, sides.last().side) - rotateTimer.reset() - - mineBlockNormal(blockTask, sides.last().side) - } else { - var side = getMiningSide(blockTask.blockPos) ?: run { - blockTask.onStuck() - return - } - - if (containerTask.primed && containerTask.destroy && instantMine) { - side = side.opposite - } else { - containerTask.primed - } - lastHitVec = getHitVec(blockTask.blockPos, side) - rotateTimer.reset() - - if (blockState.getPlayerRelativeBlockHardness(player, world, blockTask.blockPos) > 2.8) { - mineBlockInstant(blockTask, side) - } else { - mineBlockNormal(blockTask, side) - } - } - } - - private fun mineBlockInstant(blockTask: BlockTask, side: EnumFacing) { - waitTicks = breakDelay - blockTask.updateState(TaskState.PENDING_BREAK) - - defaultScope.launch { - packetLimiterMutex.withLock { - packetLimiter.add(System.currentTimeMillis()) - } - - delay(20L) - sendMiningPackets(blockTask.blockPos, side) - - if (maxBreaks > 1) { - tryMultiBreak(blockTask) - } - - delay(50L * taskTimeout) - if (blockTask.taskState == TaskState.PENDING_BREAK) { - stateUpdateMutex.withLock { - blockTask.updateState(TaskState.BREAK) - } - } - } - } - - private suspend fun tryMultiBreak(blockTask: BlockTask) { - runSafeSuspend { - val eyePos = player.getPositionEyes(1.0f) - val viewVec = lastHitVec.subtract(eyePos).normalize() - var breakCount = 1 - - for (task in sortedTasks) { - if (breakCount >= maxBreaks) break - - val size = packetLimiterMutex.withLock { - packetLimiter.size - } - - val limit = when (limitOrigin) { - LimitMode.FIXED -> 20.0f - LimitMode.SERVER -> TpsCalculator.tickRate - } - - if (size > limit * limitFactor) { - if (debugMessages == DebugMessages.ALL) { - sendChatMessage("$chatName Dropped possible instant mine action @ TPS($limit) Actions(${size})") - } - break - } - - if (task == blockTask) continue - if (task.taskState != TaskState.BREAK) continue - if (world.getBlockState(task.blockPos).block != Blocks.NETHERRACK) continue - - val box = AxisAlignedBB(task.blockPos) - val rayTraceResult = box.isInSight(eyePos, viewVec) ?: continue - - if (handleLiquid(task)) break - - breakCount++ - packetLimiterMutex.withLock { - packetLimiter.add(System.currentTimeMillis()) - } - - defaultScope.launch { - sendMiningPackets(task.blockPos, rayTraceResult.sideHit) - - delay(50L * taskTimeout) - if (blockTask.taskState == TaskState.PENDING_BREAK) { - stateUpdateMutex.withLock { - blockTask.updateState(TaskState.BREAK) - } - } - } - } - } - } - - private fun mineBlockNormal(blockTask: BlockTask, side: EnumFacing) { - if (blockTask.taskState == TaskState.BREAK) { - blockTask.updateState(TaskState.BREAKING) - } - - defaultScope.launch { - delay(20L) - sendMiningPackets(blockTask.blockPos, side) - } - } - - private suspend fun sendMiningPackets(pos: BlockPos, side: EnumFacing) { - onMainThreadSafe { - connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.START_DESTROY_BLOCK, pos, side)) - connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.STOP_DESTROY_BLOCK, pos, side)) - player.swingArm(EnumHand.MAIN_HAND) - } - } - - private fun SafeClientEvent.shouldBridge(): Boolean { - return world.isAirBlock(currentBlockPos.add(startingDirection.directionVec).down()) && - !sortedTasks.any { - it.taskState == TaskState.PLACE && - getNeighbourSequence(it.blockPos, placementSearch, maxReach, true).isNotEmpty() - } - } - - private fun SafeClientEvent.getBestTool(blockTask: BlockTask): Slot? { - return player.inventorySlots.asReversed().maxByOrNull { - val stack = it.stack - if (stack.isEmpty) { - 0.0f - } else { - var speed = stack.getDestroySpeed(world.getBlockState(blockTask.blockPos)) - - if (speed > 1.0f) { - val efficiency = EnchantmentHelper.getEnchantmentLevel(Enchantments.EFFICIENCY, stack) - if (efficiency > 0) { - speed += efficiency * efficiency + 1.0f - } - } - - speed - } - } - } - - private fun SafeClientEvent.swapOrMoveBlock(blockTask: BlockTask): Boolean { - if (blockTask.isShulker) { - getShulkerWith(player.inventorySlots, blockTask.item)?.let { slot -> - blockTask.itemID = slot.stack.item.id - slot.toHotbarSlotOrNull()?.let { - swapToSlot(it) - } ?: run { - val slotTo = player.hotbarSlots.firstEmpty()?.hotbarSlot ?: 0 - moveToHotbar(slot.slotNumber, slotTo) - } - } - return true - } else { - if (storageManagement && grindObsidian && - containerTask.taskState == TaskState.DONE && -// player.inventorySlots.countBlock(material) < saveMaterial || - player.inventorySlots.any { it.stack.isEmpty || InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) }) { - if (player.inventorySlots.countItem(Items.DIAMOND_PICKAXE) > saveTools) { - handleRestock(material.item) - } else { - handleRestock(Items.DIAMOND_PICKAXE) - } - return false - } - - val useBlock = when { - player.inventorySlots.countBlock(blockTask.block) > 0 -> blockTask.block - player.inventorySlots.countBlock(material) > 0 -> material - player.inventorySlots.countBlock(fillerMat) > 0 && mode == Mode.TUNNEL -> fillerMat - else -> blockTask.block - } - - val success = swapToBlockOrMove(useBlock, predicateSlot = { - it.item is ItemBlock - }) - - return if (!success) { - sendChatMessage("$chatName No ${blockTask.block.localizedName} was found in inventory") - mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)) - disable() - false - } else { - true - } - } - } - - private fun SafeClientEvent.swapOrMoveBestTool(blockTask: BlockTask): Boolean { - if (player.inventorySlots.countItem(Items.DIAMOND_PICKAXE) <= saveTools) { - return if (containerTask.taskState == TaskState.DONE && storageManagement) { - handleRestock(Items.DIAMOND_PICKAXE) - false - } else { - swapOrMoveTool(blockTask) - } - } - - return swapOrMoveTool(blockTask) - } - - private fun SafeClientEvent.swapOrMoveTool(blockTask: BlockTask) = - getBestTool(blockTask)?.let { slotFrom -> - slotFrom.toHotbarSlotOrNull()?.let { - swapToSlot(it) - } ?: run { - val slotTo = player.hotbarSlots.firstEmpty()?.hotbarSlot ?: 0 - moveToHotbar(slotFrom.slotNumber, slotTo) - } - true - } ?: run { - false - } - - private fun SafeClientEvent.handleRestock(item: Item) { - getShulkerWith(player.inventorySlots, item)?.let { slot -> - getRemotePos()?.let { pos -> - containerTask = BlockTask(pos, TaskState.PLACE, slot.stack.item.block, item) - containerTask.isShulker = true - } ?: run { - disableNoPosition() - } - } ?: run { - if (item.block == Blocks.OBSIDIAN) { - if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) <= saveEnder) { - getShulkerWith(player.inventorySlots, Blocks.ENDER_CHEST.item)?.let { slot -> - getRemotePos()?.let { pos -> - containerTask = BlockTask(pos, TaskState.PLACE, slot.stack.item.block, Blocks.ENDER_CHEST.item) - containerTask.isShulker = true - } ?: run { - disableNoPosition() - } - } ?: run { - dispatchEnderChest(Blocks.ENDER_CHEST.item) - } - } else { - getRemotePos()?.let { pos -> - containerTask = BlockTask(pos, TaskState.PLACE, Blocks.ENDER_CHEST) - containerTask.destroy = true - containerTask.itemID = Blocks.OBSIDIAN.id - } ?: run { - disableNoPosition() - } - } - } else { - dispatchEnderChest(item) - } - } - } - - private fun SafeClientEvent.dispatchEnderChest(item: Item) { - if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) > 0) { - getRemotePos()?.let { pos -> - containerTask = BlockTask(pos, TaskState.PLACE, Blocks.ENDER_CHEST, item) - containerTask.itemID = Blocks.OBSIDIAN.id - } ?: run { - disableNoPosition() - } - } else { - getShulkerWith(player.inventorySlots, Blocks.ENDER_CHEST.item)?.let { slot -> - getRemotePos()?.let { pos -> - containerTask = BlockTask(pos, TaskState.PLACE, slot.stack.item.block, Blocks.ENDER_CHEST.item) - containerTask.isShulker = true - } ?: run { - disableNoPosition() - } - } ?: run { - sendChatMessage("$chatName No Ender Chest was found in inventory.") - mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f)) - disable() - } - } - } - - private fun disableNoPosition() { - sendChatMessage("$chatName Cant find possible container position.") - mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1f, 1f)) - disable() - } - - private fun SafeClientEvent.getRemotePos(): BlockPos? { - val origin = currentBlockPos.up().toVec3dCenter() - - return VectorUtils.getBlockPosInSphere(origin, maxReach).asSequence() - .filter { pos -> - !isInsideBlueprintBuild(pos) && - pos != currentBlockPos && - world.isPlaceable(pos) && - !world.getBlockState(pos.down()).isReplaceable && - world.isAirBlock(pos.up()) && - world.rayTraceBlocks(origin, pos.toVec3dCenter())?.let { it.typeOfHit == RayTraceResult.Type.MISS } ?: true - } - .sortedWith( - compareBy { - it.distanceSqToCenter(origin.x, origin.y, origin.z).ceilToInt() - }.thenBy { - it.y - } - ).firstOrNull() - } - - private fun getShulkerWith(slots: List, item: Item): Slot? { - return slots.filter { - it.stack.item is ItemShulkerBox && getShulkerData(it.stack, item) > 0 - }.minByOrNull { - getShulkerData(it.stack, item) - } - } - - private fun SafeClientEvent.moveToInventory(slot: Slot) { - player.hotbarSlots.firstOrNull { - InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) - }?.let { - clickSlot(player.openContainer.windowId, slot, it.hotbarSlot, ClickType.SWAP) - } ?: run { - clickSlot(player.openContainer.windowId, slot, 0, ClickType.QUICK_MOVE) - } - - if (leaveEmptyShulkers && - player.openContainer.getSlots(0..26).all { it.stack.isEmpty || InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) }) { - if (debugMessages != DebugMessages.OFF) { - if (!anonymizeStats) { - sendChatMessage("$chatName Left empty ${containerTask.block.localizedName}@(${containerTask.blockPos.asString()})") - } else { - sendChatMessage("$chatName Left empty ${containerTask.block.localizedName}") - } - } - containerTask.updateState(TaskState.DONE) - } else { - containerTask.updateState(TaskState.BREAK) - } - - containerTask.isOpen = false - player.closeScreen() - } - - @JvmStatic - fun getShulkerData(stack: ItemStack, item: Item): Int { - val tagCompound = if (stack.item is ItemShulkerBox) stack.tagCompound else return 0 - - if (tagCompound != null && tagCompound.hasKey("BlockEntityTag", 10)) { - val blockEntityTag = tagCompound.getCompoundTag("BlockEntityTag") - if (blockEntityTag.hasKey("Items", 9)) { - val shulkerInventory = NonNullList.withSize(27, ItemStack.EMPTY) - ItemStackHelper.loadAllItems(blockEntityTag, shulkerInventory) - return shulkerInventory.count { it.item == item } - } - } - - return 0 - } - - private fun SafeClientEvent.getCollectingPosition(): BlockPos? { - getDroppedItems(containerTask.itemID, range = 8f) - .minByOrNull { player.getDistance(it) } - ?.positionVector - ?.let { itemVec -> - return VectorUtils.getBlockPosInSphere(itemVec, 5f).asSequence() - .filter { pos -> - world.isAirBlock(pos.up()) && - world.isAirBlock(pos) && - !world.isPlaceable(pos.down()) - } - .sortedWith( - compareBy { - it.distanceSqToCenter(itemVec.x, itemVec.y, itemVec.z) - }.thenBy { - it.y - } - ).firstOrNull() - } - return null - } - - private fun SafeClientEvent.eject(): Boolean { - return if (player.inventorySlots.firstEmpty() == null) { - getEjectSlot()?.let { - throwAllInSlot(it) - } - false - } else { - true - } - } - - private fun SafeClientEvent.getEjectSlot(): Slot? { - return player.inventorySlots.firstByStack { - !it.isEmpty && - InventoryManager.ejectList.contains(it.item.registryName.toString()) - } - } - - private fun SafeClientEvent.updateSlot(slot: Int = player.inventory.currentItem + 36) { - clickSlot(0, slot, 0, ClickType.PICKUP) - connection.sendPacket(CPacketCloseWindow(0)) - runBlocking { - onMainThreadSafe { playerController.updateController() } - } - } - - private fun SafeClientEvent.handleLiquid(blockTask: BlockTask): Boolean { - var foundLiquid = false - - for (side in EnumFacing.values()) { - val neighbourPos = blockTask.blockPos.offset(side) - - if (world.getBlockState(neighbourPos).block !is BlockLiquid) continue - - if (player.distanceTo(neighbourPos) > maxReach) { - blockTask.updateState(TaskState.DONE) - return true - } - - foundLiquid = true - - val isFlowing = world.getBlockState(blockTask.blockPos).let { - it.block is BlockLiquid && it.getValue(BlockLiquid.LEVEL) != 0 - } - - val filler = if (isInsideBlueprintBuild(neighbourPos)) material else fillerMat - - pendingTasks[neighbourPos]?.let { - if (isFlowing) { - it.updateState(TaskState.LIQUID_FLOW) - } else { - it.updateState(TaskState.LIQUID_FLOW) - } - - it.updateMaterial(filler) - } ?: run { - if (isFlowing) { - addTaskToPending(neighbourPos, TaskState.LIQUID_FLOW, filler) - } else { - addTaskToPending(neighbourPos, TaskState.LIQUID_SOURCE, filler) - } - } - } - - return foundLiquid - } - - private fun SafeClientEvent.updateLiquidTask(blockTask: BlockTask) { - val filler = if (player.inventorySlots.countBlock(fillerMat) == 0 || isInsideBlueprintBuild(blockTask.blockPos)) material - else fillerMat - - if (world.getBlockState(blockTask.blockPos).getValue(BlockLiquid.LEVEL) != 0) { - blockTask.updateState(TaskState.LIQUID_FLOW) - blockTask.updateMaterial(filler) - } else { - blockTask.updateState(TaskState.LIQUID_SOURCE) - blockTask.updateMaterial(filler) - } - } - - private fun isInsideBlueprint(pos: BlockPos): Boolean { - return blueprint.containsKey(pos) - } - - private fun isInsideBlueprintBuild(pos: BlockPos): Boolean { - val mat = when (mode) { - Mode.HIGHWAY, Mode.FLAT -> material - Mode.TUNNEL -> fillerMat - } - return blueprint[pos]?.let { it == mat } ?: false - } - - fun printSettings() { - StringBuilder(ignoreBlocks.size + 1).run { - append("$chatName Settings" + - "\n §9> §rMain material: §7${material.localizedName}" + - "\n §9> §rFiller material: §7${fillerMat.localizedName}" + - "\n §9> §rIgnored Blocks:") - - ignoreBlocks.forEach { - append("\n §9> §7$it") - } - - sendChatMessage(toString()) - } - } - - fun SafeClientEvent.gatherStatistics(displayText: TextComponent) { - val runtimeSec = (runtimeMilliSeconds / 1000) + 0.0001 - val distanceDone = startingBlockPos.distanceTo(currentBlockPos).toInt() + totalDistance - - if (showSession) gatherSession(displayText, runtimeSec) - - if (showLifeTime) gatherLifeTime(displayText) - - if (showPerformance) gatherPerformance(displayText, runtimeSec, distanceDone) - - if (showEnvironment) gatherEnvironment(displayText) - - if (showTask) gatherTask(displayText) - - if (showEstimations) gatherEstimations(displayText, runtimeSec, distanceDone) - - if (printDebug) { - if (containerTask.taskState != TaskState.DONE) { - displayText.addLine("Container", primaryColor, scale = 0.6f) - displayText.addLine(containerTask.prettyPrint(), primaryColor, scale = 0.6f) - } - - if (sortedTasks.isNotEmpty()) { - displayText.addLine("Pending", primaryColor, scale = 0.6f) - addTaskComponentList(displayText, sortedTasks) - } - - if (sortedTasks.isNotEmpty()) { - displayText.addLine("Done", primaryColor, scale = 0.6f) - addTaskComponentList(displayText, doneTasks.values) - } - } - - displayText.addLine("by Constructor#9948/Avanatiker", primaryColor, scale = 0.6f) - } - - private fun gatherSession(displayText: TextComponent, runtimeSec: Double) { - val seconds = (runtimeSec % 60.0).toInt().toString().padStart(2, '0') - val minutes = ((runtimeSec % 3600.0) / 60.0).toInt().toString().padStart(2, '0') - val hours = (runtimeSec / 3600.0).toInt().toString().padStart(2, '0') - - displayText.addLine("Session", primaryColor) - - displayText.add(" Runtime:", primaryColor) - displayText.addLine("$hours:$minutes:$seconds", secondaryColor) - - displayText.add(" Direction:", primaryColor) - displayText.addLine("${startingDirection.displayName} / ${startingDirection.displayNameXY}", secondaryColor) - - if (!anonymizeStats) displayText.add(" Start:", primaryColor) - if (!anonymizeStats) displayText.addLine("(${startingBlockPos.asString()})", secondaryColor) - - displayText.add(" Session placed / destroyed:", primaryColor) - displayText.addLine("%,d".format(totalBlocksPlaced) + " / " + "%,d".format(totalBlocksBroken), secondaryColor) - - - } - - private fun SafeClientEvent.gatherLifeTime(displayText: TextComponent) { - matPlaced = StatList.getObjectUseStats(material.item)?.let { - player.statFileWriter.readStat(it) - } ?: 0 - enderMined = StatList.getBlockStats(Blocks.ENDER_CHEST)?.let { - player.statFileWriter.readStat(it) - } ?: 0 - netherrackMined = StatList.getBlockStats(Blocks.NETHERRACK)?.let { - player.statFileWriter.readStat(it) - } ?: 0 - pickaxeBroken = StatList.getObjectBreakStats(Items.DIAMOND_PICKAXE)?.let { - player.statFileWriter.readStat(it) - } ?: 0 - - if (matPlaced + enderMined + netherrackMined + pickaxeBroken > 0) { - displayText.addLine("Lifetime", primaryColor) - } - - if (mode == Mode.HIGHWAY || mode == Mode.FLAT) { - if (matPlaced > 0) { - displayText.add(" ${material.localizedName} placed:", primaryColor) - displayText.addLine("%,d".format(matPlaced), secondaryColor) - } - - if (enderMined > 0) { - displayText.add(" ${Blocks.ENDER_CHEST.localizedName} mined:", primaryColor) - displayText.addLine("%,d".format(enderMined), secondaryColor) - } - } - - if (netherrackMined > 0) { - displayText.add(" ${Blocks.NETHERRACK.localizedName} mined:", primaryColor) - displayText.addLine("%,d".format(netherrackMined), secondaryColor) - } - - if (pickaxeBroken > 0) { - displayText.add(" Diamond Pickaxe broken:", primaryColor) - displayText.addLine("%,d".format(pickaxeBroken), secondaryColor) - } - } - - private fun gatherPerformance(displayText: TextComponent, runtimeSec: Double, distanceDone: Double) { - displayText.addLine("Performance", primaryColor) - - displayText.add(" Placements / s: ", primaryColor) - displayText.addLine("%.2f SMA(%.2f)".format(totalBlocksPlaced / runtimeSec, simpleMovingAveragePlaces.size / simpleMovingAverageRange.toDouble()), secondaryColor) - - displayText.add(" Breaks / s:", primaryColor) - displayText.addLine("%.2f SMA(%.2f)".format(totalBlocksBroken / runtimeSec, simpleMovingAverageBreaks.size / simpleMovingAverageRange.toDouble()), secondaryColor) - - displayText.add(" Distance km / h:", primaryColor) - displayText.addLine("%.3f SMA(%.3f)".format((distanceDone / runtimeSec * 60.0 * 60.0) / 1000.0, (simpleMovingAverageDistance.size / simpleMovingAverageRange * 60.0 * 60.0) / 1000.0), secondaryColor) - - displayText.add(" Food level loss / h:", primaryColor) - displayText.addLine("%.2f".format(totalBlocksBroken / foodLoss.toDouble()), secondaryColor) - - displayText.add(" Pickaxes / h:", primaryColor) - displayText.addLine("%.2f".format((durabilityUsages / runtimeSec) * 60.0 * 60.0 / 1561.0), secondaryColor) - } - - private fun gatherEnvironment(displayText: TextComponent) { - displayText.addLine("Environment", primaryColor) - - displayText.add(" Materials:", primaryColor) - displayText.addLine("Main(${material.localizedName}) Filler(${fillerMat.localizedName})", secondaryColor) - - displayText.add(" Dimensions:", primaryColor) - displayText.addLine("Width($width) Height($height)", secondaryColor) - - displayText.add(" Delays:", primaryColor) - if (dynamicDelay) { - displayText.addLine("Place(${placeDelay + extraPlaceDelay}) Break($breakDelay)", secondaryColor) - } else { - displayText.addLine("Place($placeDelay) Break($breakDelay)", secondaryColor) - } - - displayText.add(" Movement:", primaryColor) - displayText.addLine("$moveState", secondaryColor) - } - - private fun gatherTask(displayText: TextComponent) { - sortedTasks.firstOrNull()?.let { - displayText.addLine("Task", primaryColor) - - displayText.add(" Status:", primaryColor) - displayText.addLine("${it.taskState}", secondaryColor) - - displayText.add(" Target block:", primaryColor) - displayText.addLine(it.block.localizedName, secondaryColor) - - if (!anonymizeStats) displayText.add(" Position:", primaryColor) - if (!anonymizeStats) displayText.addLine("(${it.blockPos.asString()})", secondaryColor) - - displayText.add(" Ticks stuck:", primaryColor) - displayText.addLine("${it.stuckTicks}", secondaryColor) - } - } - - private fun SafeClientEvent.gatherEstimations(displayText: TextComponent, runtimeSec: Double, distanceDone: Double) { - when (mode) { - Mode.HIGHWAY, Mode.FLAT -> { - materialLeft = player.inventorySlots.countBlock(material) - fillerMatLeft = player.inventorySlots.countBlock(fillerMat) - val indirectMaterialLeft = 8 * player.inventorySlots.countBlock(Blocks.ENDER_CHEST) - - val pavingLeft = materialLeft / (totalBlocksPlaced.coerceAtLeast(1) / distanceDone.coerceAtLeast(1.0)) - - // ToDo: Cache shulker count - -// val pavingLeftAll = (materialLeft + indirectMaterialLeft) / ((totalBlocksPlaced + 0.001) / (distanceDone + 0.001)) - - val secLeft = (pavingLeft).coerceAtLeast(0.0) / (startingBlockPos.distanceTo(currentBlockPos).toInt() / runtimeSec) - val secondsLeft = (secLeft % 60).toInt().toString().padStart(2, '0') - val minutesLeft = ((secLeft % 3600) / 60).toInt().toString().padStart(2, '0') - val hoursLeft = (secLeft / 3600).toInt().toString().padStart(2, '0') - - displayText.addLine("Next refill", primaryColor) - displayText.add(" ${material.localizedName}:", primaryColor) - - if (material == Blocks.OBSIDIAN) { - displayText.addLine("Direct($materialLeft) Indirect($indirectMaterialLeft)", secondaryColor) - } else { - displayText.addLine("$materialLeft", secondaryColor) - } - - displayText.add(" ${fillerMat.localizedName}:", primaryColor) - displayText.addLine("$fillerMatLeft", secondaryColor) - - displayText.add(" Distance left:", primaryColor) - displayText.addLine("${pavingLeft.toInt()}", secondaryColor) - - if (!anonymizeStats) displayText.add(" Destination:", primaryColor) - if (!anonymizeStats) displayText.addLine("(${currentBlockPos.add(startingDirection.directionVec.multiply(pavingLeft.toInt())).asString()})", secondaryColor) - - displayText.add(" ETA:", primaryColor) - displayText.addLine("$hoursLeft:$minutesLeft:$secondsLeft", secondaryColor) - } - Mode.TUNNEL -> { - val pickaxesLeft = player.inventorySlots.countItem() - - val tunnelingLeft = (pickaxesLeft * 1561) / (durabilityUsages.coerceAtLeast(1) / distanceDone.coerceAtLeast(1.0)) - - val secLeft = tunnelingLeft.coerceAtLeast(0.0) / (startingBlockPos.distanceTo(currentBlockPos).toInt() / runtimeSec) - val secondsLeft = (secLeft % 60).toInt().toString().padStart(2, '0') - val minutesLeft = ((secLeft % 3600) / 60).toInt().toString().padStart(2, '0') - val hoursLeft = (secLeft / 3600).toInt().toString().padStart(2, '0') - - displayText.addLine("Destination:", primaryColor) - - displayText.add(" Pickaxes:", primaryColor) - displayText.addLine("$pickaxesLeft", secondaryColor) - - displayText.add(" Distance left:", primaryColor) - displayText.addLine("${tunnelingLeft.toInt()}", secondaryColor) - - if (!anonymizeStats) displayText.add(" Destination:", primaryColor) - if (!anonymizeStats) displayText.addLine("(${currentBlockPos.add(startingDirection.directionVec.multiply(tunnelingLeft.toInt())).asString()})", secondaryColor) - - displayText.add(" ETA:", primaryColor) - displayText.addLine("$hoursLeft:$minutesLeft:$secondsLeft", secondaryColor) - } - } - } - - private fun resetStats() { - simpleMovingAveragePlaces.clear() - simpleMovingAverageBreaks.clear() - simpleMovingAverageDistance.clear() - totalBlocksPlaced = 0 - totalBlocksBroken = 0 - totalDistance = 0.0 - runtimeMilliSeconds = 0 - prevFood = 0 - foodLoss = 1 - materialLeft = 0 - fillerMatLeft = 0 - lastToolDamage = 0 - durabilityUsages = 0 - } - - private fun addTaskComponentList(displayText: TextComponent, tasks: Collection) { - tasks.forEach { - displayText.addLine(it.prettyPrint(), primaryColor, scale = 0.6f) - } - } - - class BlockTask( - val blockPos: BlockPos, - var taskState: TaskState, - var block: Block, - var item: Item = Items.AIR - ) { - private var ranTicks = 0 - var stuckTicks = 0; private set - var shuffle = 0; private set - var sides = 0; private set - var startDistance = 0.0; private set - var eyeDistance = 0.0; private set - var hitVecDistance = 0.0; private set - - var isShulker = false - var isOpen = false - var itemID = 0 - var destroy = false - var primed = false - -// var isBridge = false ToDo: Implement - - fun updateState(state: TaskState) { - if (state == taskState) return - - taskState = state - if (state == TaskState.DONE || state == TaskState.PLACED || state == TaskState.BROKEN) { - onUpdate() - } - } - - fun updateMaterial(material: Block) { - if (material == block) return - - block = material - onUpdate() - } - - fun onTick() { - ranTicks++ - if (ranTicks > taskState.stuckThreshold) { - stuckTicks++ - } - } - - fun onStuck(weight: Int = 1) { - stuckTicks += weight - } - - fun prepareSortInfo(event: SafeClientEvent, eyePos: Vec3d) { - sides = when (taskState) { - TaskState.PLACE -> { - event.getNeighbourSequence(blockPos, placementSearch, maxReach, true).size - } - //TaskState.BREAK -> - else -> 0 - } - - // ToDo: We need a function that makes a score out of those 3 parameters - startDistance = startingBlockPos.distanceTo(blockPos) - eyeDistance = eyePos.distanceTo(blockPos) - hitVecDistance = (lastHitVec?.distanceTo(blockPos) ?: 0.0) - } - - fun shuffle() { - shuffle = nextInt(0, 1000) - } - - fun prettyPrint(): String { - return " ${block.localizedName}@(${blockPos.asString()}) State: $taskState Timings: (Threshold: ${taskState.stuckThreshold} Timeout: ${taskState.stuckTimeout}) Priority: ${taskState.ordinal} Stuck: $stuckTicks" - } - - private fun onUpdate() { - stuckTicks = 0 - ranTicks = 0 - } - - override fun toString(): String { - return "Block: ${block.localizedName} @ Position: (${blockPos.asString()}) State: ${taskState.name}" - } - - override fun equals(other: Any?) = this === other - || (other is BlockTask - && blockPos == other.blockPos) - - override fun hashCode() = blockPos.hashCode() - } - - enum class MovementState { - RUNNING, PICKUP, BRIDGE - } - - enum class TaskState(val stuckThreshold: Int, val stuckTimeout: Int, val color: ColorHolder) { - DONE(69420, 0x22, ColorHolder(50, 50, 50)), - BROKEN(1000, 1000, ColorHolder(111, 0, 0)), - PLACED(1000, 1000, ColorHolder(53, 222, 66)), - LIQUID_SOURCE(100, 100, ColorHolder(114, 27, 255)), - LIQUID_FLOW(100, 100, ColorHolder(68, 27, 255)), - PICKUP(500, 500, ColorHolder(252, 3, 207)), - RESTOCK(500, 500, ColorHolder(252, 3, 207)), - OPEN_CONTAINER(500, 500, ColorHolder(252, 3, 207)), - BREAKING(100, 100, ColorHolder(240, 222, 60)), - BREAK(20, 20, ColorHolder(222, 0, 0)), - PLACE(20, 20, ColorHolder(35, 188, 254)), - PENDING_BREAK(100, 100, ColorHolder(0, 0, 0)), - PENDING_PLACE(100, 100, ColorHolder(0, 0, 0)) - } - } \ No newline at end of file diff --git a/src/main/kotlin/HighwayToolsCommand.kt b/src/main/kotlin/HighwayToolsCommand.kt index 66682e4..ce79396 100644 --- a/src/main/kotlin/HighwayToolsCommand.kt +++ b/src/main/kotlin/HighwayToolsCommand.kt @@ -1,7 +1,7 @@ import com.lambda.client.command.ClientCommand -import com.lambda.client.util.math.CoordinateConverter.asString -import com.lambda.client.util.text.MessageSendHelper import com.lambda.client.util.text.MessageSendHelper.sendChatMessage +import trombone.IO.printSettings +import trombone.Pathfinder.distancePending object HighwayToolsCommand : ClientCommand( name = "highwaytools", @@ -12,10 +12,10 @@ object HighwayToolsCommand : ClientCommand( init { literal("add", "new", "+") { block("block") { blockArg -> - execute("Add a block to ignore list") { + execute("Adds a block to ignore list") { val added = HighwayTools.ignoreBlocks.add(blockArg.value.registryName.toString()) if (added) { - HighwayTools.printSettings() + printSettings() sendChatMessage("Added &7${blockArg.value.localizedName}&r to ignore list.") } else { sendChatMessage("&7${blockArg.value.localizedName}&r is already ignored.") @@ -24,12 +24,12 @@ object HighwayToolsCommand : ClientCommand( } } - literal("del", "rem", "-") { + literal("remove", "rem", "-", "del") { block("block") { blockArg -> - execute("Remove a block from ignore list") { + execute("Removes a block from ignore list") { val removed = HighwayTools.ignoreBlocks.remove(blockArg.value.registryName.toString()) if (removed) { - HighwayTools.printSettings() + printSettings() sendChatMessage("Removed &7${blockArg.value.localizedName}&r from ignore list.") } else { sendChatMessage("&7${blockArg.value.localizedName}&r is not yet ignored.") @@ -38,40 +38,41 @@ object HighwayToolsCommand : ClientCommand( } } - literal("from", "start") { - blockPos("position") { blockPosArg -> - execute("Sets starting coordinates") { - // ToDo: Make starting position for next instance -// HighwayTools.startingPos = blockPosArg.value - } - } - } +// literal("from", "start") { +// blockPos("position") { blockPosArg -> +// execute("Sets starting coordinates") { +// // ToDo: Make starting position for next instance +//// HighwayTools.startingPos = blockPosArg.value +// } +// } +// } - literal("to", "stop") { - blockPos("position") { blockPosArg -> - execute("Sets stopping coordinates and starts bot") { - if (HighwayTools.isEnabled) { - sendChatMessage("Run this command when the bot is not running") - } else { - HighwayTools.targetBlockPos = blockPosArg.value - sendChatMessage("Started HighwayTools with target @(${blockPosArg.value.asString()})") - HighwayTools.enable() - } - } - } - } +// literal("to", "stop") { +// blockPos("position") { blockPosArg -> +// execute("Sets stopping coordinates and starts bot") { +// if (HighwayTools.isEnabled) { +// sendChatMessage("Run this command when the bot is not running") +// } else { +// HighwayTools.targetBlockPos = blockPosArg.value +// sendChatMessage("Started HighwayTools with target @(${blockPosArg.value.asString()})") +// HighwayTools.enable() +// } +// } +// } +// } literal("distance") { int("distance") { distanceArg -> - execute("Set the target distance until the bot stops") { - HighwayTools.distancePending = distanceArg.value + execute("Sets the target distance until the bot stops") { + distancePending = distanceArg.value + sendChatMessage("HighwayTools will stop after (${distanceArg.value}) blocks distance. To remove the limit use distance 0") } } } literal("material", "mat") { block("block") { blockArg -> - execute("Set a block as main material") { + execute("Sets a block as main material") { HighwayTools.material = blockArg.value sendChatMessage("Set your building material to &7${blockArg.value.localizedName}&r.") } @@ -80,15 +81,24 @@ object HighwayToolsCommand : ClientCommand( literal("filler", "fil") { block("block") { blockArg -> - execute("Set a block as filler material") { + execute("Sets a block as filler material") { HighwayTools.fillerMat = blockArg.value sendChatMessage("Set your filling material to &7${blockArg.value.localizedName}&r.") } } } - execute("Print the settings") { - HighwayTools.printSettings() + literal("food", "fd") { + item("item") { itemArg -> + execute("Sets a type of food") { + HighwayTools.food = itemArg.value + sendChatMessage("Set your food item to &7${itemArg.value.registryName}&r.") + } + } + } + + execute("Print settings") { + printSettings() } } } \ No newline at end of file diff --git a/src/main/kotlin/HighwayToolsHud.kt b/src/main/kotlin/HighwayToolsHud.kt index b679c3c..9e86f5d 100644 --- a/src/main/kotlin/HighwayToolsHud.kt +++ b/src/main/kotlin/HighwayToolsHud.kt @@ -1,6 +1,7 @@ -import HighwayTools.gatherStatistics import com.lambda.client.event.SafeClientEvent import com.lambda.client.plugin.api.PluginLabelHud +import trombone.Statistics.gatherStatistics +import trombone.Statistics.resetStats internal object HighwayToolsHud : PluginLabelHud( name = "HighwayToolsHud", @@ -8,6 +9,23 @@ internal object HighwayToolsHud : PluginLabelHud( description = "Hud for HighwayTools module", pluginMain = HighwayToolsPlugin ) { + val simpleMovingAverageRange by setting("Moving Average", 60, 5..600, 5, description = "Sets the timeframe of the average in seconds") + val showSession by setting("Show Session", true, description = "Toggles the Session section in HUD") + val showLifeTime by setting("Show Lifetime", true, description = "Toggles the Lifetime section in HUD") + val showPerformance by setting("Show Performance", true, description = "Toggles the Performance section in HUD") + val showEnvironment by setting("Show Environment", true, description = "Toggles the Environment section in HUD") + val showTask by setting("Show Task", true, description = "Toggles the Task section in HUD") + val showEstimations by setting("Show Estimations", true, description = "Toggles the Estimations section in HUD") + val showQueue by setting("Show Queue", false, description = "Shows task queue in HUD") + private val resetStats = setting("Reset Stats", false, description = "Resets the stats") + + init { + resetStats.consumers.add { _, it -> + if (it) resetStats() + false + } + } + override fun SafeClientEvent.updateText() { gatherStatistics(displayText) } diff --git a/src/main/kotlin/trombone/BaritoneHelper.kt b/src/main/kotlin/trombone/BaritoneHelper.kt new file mode 100644 index 0000000..c8a4f44 --- /dev/null +++ b/src/main/kotlin/trombone/BaritoneHelper.kt @@ -0,0 +1,29 @@ +package trombone + +import HighwayTools.goalRender +import com.lambda.client.util.BaritoneUtils + +object BaritoneHelper { + private var baritoneSettingAllowPlace = false + private var baritoneSettingAllowBreak = false + private var baritoneSettingRenderGoal = false + private var baritoneSettingAllowInventory = false + + fun setupBaritone() { + baritoneSettingAllowPlace = BaritoneUtils.settings?.allowPlace?.value ?: true + baritoneSettingAllowBreak = BaritoneUtils.settings?.allowBreak?.value ?: true + baritoneSettingRenderGoal = BaritoneUtils.settings?.renderGoal?.value ?: true + baritoneSettingAllowInventory = BaritoneUtils.settings?.allowInventory?.value ?: true + BaritoneUtils.settings?.allowPlace?.value = false + BaritoneUtils.settings?.allowBreak?.value = false + BaritoneUtils.settings?.renderGoal?.value = goalRender + BaritoneUtils.settings?.allowInventory?.value = false + } + + fun resetBaritone() { + BaritoneUtils.settings?.allowPlace?.value = baritoneSettingAllowPlace + BaritoneUtils.settings?.allowBreak?.value = baritoneSettingAllowBreak + BaritoneUtils.settings?.renderGoal?.value = baritoneSettingRenderGoal + BaritoneUtils.settings?.allowInventory?.value = baritoneSettingAllowInventory + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/IO.kt b/src/main/kotlin/trombone/IO.kt new file mode 100644 index 0000000..6251d61 --- /dev/null +++ b/src/main/kotlin/trombone/IO.kt @@ -0,0 +1,187 @@ +package trombone + +import HighwayTools.anonymizeStats +import HighwayTools.disableMode +import HighwayTools.disableWarnings +import HighwayTools.fillerMat +import HighwayTools.height +import HighwayTools.ignoreBlocks +import HighwayTools.info +import HighwayTools.material +import HighwayTools.mode +import HighwayTools.multiBuilding +import HighwayTools.proxyCommand +import HighwayTools.rubberbandTimeout +import HighwayTools.usingProxy +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.module.modules.combat.AutoDisconnect +import com.lambda.client.module.modules.misc.AntiAFK +import com.lambda.client.module.modules.misc.AutoObsidian +import com.lambda.client.module.modules.movement.AntiHunger +import com.lambda.client.module.modules.movement.Velocity +import com.lambda.client.module.modules.player.AutoEat +import com.lambda.client.module.modules.player.LagNotifier +import com.lambda.client.module.modules.player.NoGhostItems +import com.lambda.client.process.PauseProcess +import com.lambda.client.util.math.Direction +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.text.MessageSendHelper +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.threads.onMainThreadSafe +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import net.minecraft.client.audio.PositionedSoundRecord +import net.minecraft.init.SoundEvents +import net.minecraft.util.text.TextComponentString +import net.minecraft.util.text.TextFormatting +import net.minecraft.world.EnumDifficulty +import trombone.Pathfinder.currentBlockPos +import trombone.Pathfinder.startingBlockPos +import trombone.Pathfinder.startingDirection +import trombone.Statistics.totalBlocksBroken +import trombone.Statistics.totalBlocksPlaced +import trombone.Trombone.Structure +import trombone.Trombone.module +import kotlin.math.abs + +object IO { + enum class DisableMode { + NONE, ANTI_AFK, LOGOUT + } + + enum class DebugLevel { + OFF, IMPORTANT, VERBOSE + } + + fun SafeClientEvent.pauseCheck(): Boolean = + !Pathfinder.rubberbandTimer.tick(rubberbandTimeout.toLong(), false) + || player.inventory.isEmpty + || PauseProcess.isActive + || AutoObsidian.isActive() + || isInQueue() + || !player.isEntityAlive + || !player.onGround + + private fun SafeClientEvent.isInQueue() = + world.difficulty == EnumDifficulty.PEACEFUL + && player.dimension == 1 + && @Suppress("UNNECESSARY_SAFE_CALL") + player.serverBrand?.contains("2b2t") == true + + fun SafeClientEvent.printEnable() { + MessageSendHelper.sendRawChatMessage(" §9> §7Direction: §a${startingDirection.displayName} / ${startingDirection.displayNameXY}§r") + + if (!anonymizeStats) { + if (startingDirection.isDiagonal) { + MessageSendHelper.sendRawChatMessage(" §9> §7Axis offset: §a%,d %,d§r".format(startingBlockPos.x, startingBlockPos.z)) + + if (abs(startingBlockPos.x) != abs(startingBlockPos.z)) { + MessageSendHelper.sendRawChatMessage(" §c[!] You may have an offset to diagonal highway position!") + } + } else { + if (startingDirection == Direction.NORTH || startingDirection == Direction.SOUTH) { + MessageSendHelper.sendRawChatMessage(" §9> §7Axis offset: §a%,d§r".format(startingBlockPos.x)) + } else { + MessageSendHelper.sendRawChatMessage(" §9> §7Axis offset: §a%,d§r".format(startingBlockPos.z)) + } + } + } + + if (!disableWarnings) { + if (startingBlockPos.y != 120 && mode != Structure.TUNNEL) { + MessageSendHelper.sendRawChatMessage(" §c[!] Check altitude and make sure to build at Y: 120 for the correct height") + } + + if (AntiHunger.isEnabled) { + MessageSendHelper.sendRawChatMessage(" §c[!] AntiHunger does slow down block interactions.") + } + + if (LagNotifier.isDisabled) { + MessageSendHelper.sendRawChatMessage(" §c[!] You should activate LagNotifier to make the bot stop on server lag.") + } + + if (AutoEat.isDisabled) { + MessageSendHelper.sendRawChatMessage(" §c[!] You should activate AutoEat to not die on starvation.") + } + + if (AutoDisconnect.isDisabled) { + MessageSendHelper.sendRawChatMessage(" §c[!] You should activate AutoLog to prevent most deaths when afk.") + } + + if (multiBuilding && Velocity.isDisabled) { + MessageSendHelper.sendRawChatMessage(" §c[!] Make sure to enable Velocity to not get pushed from your mates.") + } + + if (material == fillerMat) { + MessageSendHelper.sendRawChatMessage(" §c[!] Make sure to use §aTunnel Mode§c instead of having same material for both main and filler!") + } + + if (mode == Structure.HIGHWAY && height < 3) { + MessageSendHelper.sendRawChatMessage(" §c[!] You may increase the height to at least 3") + } + + if (isInQueue()) { + MessageSendHelper.sendRawChatMessage(" §c[!] You should not activate the bot in queue! Bot will move to 0 0.") + } + + if (NoGhostItems.isDisabled) { + MessageSendHelper.sendRawChatMessage(" §c[!] Please consider using the module NoGhostItems to minimize item desyncs") + } + } + } + + fun printDisable() { + if (info) { + MessageSendHelper.sendRawChatMessage(" §9> §7Placed blocks: §a%,d§r".format(totalBlocksPlaced)) + MessageSendHelper.sendRawChatMessage(" §9> §7Destroyed blocks: §a%,d§r".format(totalBlocksBroken)) + MessageSendHelper.sendRawChatMessage(" §9> §7Distance: §a%,d§r".format(startingBlockPos.distanceTo(currentBlockPos).toInt())) + } + } + + fun printSettings() { + StringBuilder(ignoreBlocks.size + 1).run { + append("${module.chatName} Settings" + + "\n §9> §rMain material: §7${material.localizedName}" + + "\n §9> §rFiller material: §7${fillerMat.localizedName}" + + "\n §9> §rIgnored Blocks:") + + ignoreBlocks.forEach { + append("\n §9> §7$it") + } + + MessageSendHelper.sendChatMessage(toString()) + } + } + + fun SafeClientEvent.disableError(error: String) { + MessageSendHelper.sendErrorMessage("${module.chatName} §c[!] $error") + mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)) + module.disable() + when (disableMode) { + DisableMode.ANTI_AFK -> { + MessageSendHelper.sendWarningMessage("${module.chatName} §c[!] ${TextFormatting.AQUA}Going into AFK mode.") + AntiAFK.enable() + } + DisableMode.LOGOUT -> { + MessageSendHelper.sendWarningMessage("${module.chatName} §c[!] ${TextFormatting.DARK_RED}CAUTION: Logging off in 1 minute!") + defaultScope.launch { + delay(6000L) + if (disableMode == DisableMode.LOGOUT && module.isDisabled) { + onMainThreadSafe { + if (usingProxy) { + player.sendChatMessage(proxyCommand) + } else { + connection.networkManager.closeChannel(TextComponentString("Done building highways.")) + } + } + } else { + MessageSendHelper.sendChatMessage("${module.chatName} ${TextFormatting.GREEN}Logout canceled.") + } + } + } + DisableMode.NONE -> { + // Nothing + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/Pathfinder.kt b/src/main/kotlin/trombone/Pathfinder.kt new file mode 100644 index 0000000..0361196 --- /dev/null +++ b/src/main/kotlin/trombone/Pathfinder.kt @@ -0,0 +1,161 @@ +package trombone + +import HighwayTools.moveSpeed +import HighwayTools.scaffold +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.BaritoneUtils +import com.lambda.client.util.EntityUtils.flooredPosition +import com.lambda.client.util.TickTimer +import com.lambda.client.util.TimeUnit +import com.lambda.client.util.math.Direction +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.math.VectorUtils.multiply +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import com.lambda.client.util.world.isReplaceable +import net.minecraft.block.BlockLiquid +import net.minecraft.init.Blocks +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import trombone.IO.disableError +import trombone.Statistics.simpleMovingAverageDistance +import trombone.Trombone.active +import trombone.handler.Container.containerTask +import trombone.handler.Container.getCollectingPosition +import trombone.handler.Inventory.lastHitVec +import trombone.task.TaskManager.isBehindPos +import trombone.task.TaskManager.populateTasks +import trombone.task.TaskManager.tasks +import trombone.task.TaskState + +object Pathfinder { + var goal: BlockPos? = null + var moveState = MovementState.RUNNING + + val rubberbandTimer = TickTimer(TimeUnit.TICKS) + + var startingDirection = Direction.NORTH + var currentBlockPos = BlockPos(0, -1, 0) + var startingBlockPos = BlockPos(0, -1, 0) + private var targetBlockPos = BlockPos(0, -1, 0) + var distancePending = 0 + + enum class MovementState { + RUNNING, PICKUP, BRIDGE, RESTOCK + } + + fun SafeClientEvent.setupPathing() { + moveState = MovementState.RUNNING + startingBlockPos = player.flooredPosition + currentBlockPos = startingBlockPos + startingDirection = Direction.fromEntity(player) + } + + fun SafeClientEvent.updatePathing() { + when (moveState) { + MovementState.RUNNING -> { + goal = currentBlockPos + + // ToDo: Rewrite + if (currentBlockPos.distanceTo(targetBlockPos) < 2 || + (distancePending > 0 && + startingBlockPos.add( + startingDirection.directionVec.multiply(distancePending) + ).distanceTo(currentBlockPos) == 0.0)) { + disableError("Reached target destination") + return + } + + val possiblePos = currentBlockPos.add(startingDirection.directionVec) + + if (!isTaskDone(possiblePos.up()) + || !isTaskDone(possiblePos) + || !isTaskDone(possiblePos.down()) + ) return + + if (!checkForResidue(possiblePos.up())) return + + if (world.getBlockState(possiblePos.down()).isReplaceable) return + + if (currentBlockPos != possiblePos + && player.positionVector.distanceTo(currentBlockPos.toVec3dCenter()) < 2 + ) { + simpleMovingAverageDistance.add(System.currentTimeMillis()) + lastHitVec = Vec3d.ZERO + currentBlockPos = possiblePos + populateTasks() + } + } + MovementState.BRIDGE -> { + goal = null + val isAboveAir = world.getBlockState(player.flooredPosition.down()).isReplaceable + if (isAboveAir) player.movementInput?.sneak = true + if (shouldBridge()) { + val target = currentBlockPos.toVec3dCenter().add(Vec3d(startingDirection.directionVec)) + moveTo(target) + } else { + if (!isAboveAir) { + moveState = MovementState.RUNNING + } + } + } + MovementState.PICKUP -> { + goal = getCollectingPosition() + } + MovementState.RESTOCK -> { + val target = currentBlockPos.toVec3dCenter() + if (player.positionVector.distanceTo(target) < 2) { + goal = null + moveTo(target) + } else { + goal = currentBlockPos + } + } + } + } + + private fun checkForResidue(pos: BlockPos) = + containerTask.taskState == TaskState.DONE + && tasks.values.all { + it.taskState == TaskState.DONE || !isBehindPos(pos, it.blockPos) + } + + private fun SafeClientEvent.isTaskDone(pos: BlockPos): Boolean { + val block = world.getBlockState(pos).block + return tasks[pos]?.let { + it.taskState == TaskState.DONE + && block != Blocks.PORTAL + && block != Blocks.END_PORTAL + && block !is BlockLiquid + } ?: false + } + + fun SafeClientEvent.shouldBridge(): Boolean { + return scaffold + && containerTask.taskState == TaskState.DONE + && world.isAirBlock(currentBlockPos.add(startingDirection.directionVec)) + && world.isAirBlock(currentBlockPos.add(startingDirection.directionVec).up()) + && world.getBlockState(currentBlockPos.add(startingDirection.directionVec).down()).isReplaceable + && tasks.values.none { it.taskState == TaskState.PENDING_PLACE } + && tasks.values.filter { + it.taskState == TaskState.PLACE + || it.taskState == TaskState.LIQUID + }.none { it.sequence.isNotEmpty() } + } + + private fun SafeClientEvent.moveTo(target: Vec3d) { + player.motionX = (target.x - player.posX).coerceIn((-moveSpeed).toDouble(), moveSpeed.toDouble()) + player.motionZ = (target.z - player.posZ).coerceIn((-moveSpeed).toDouble(), moveSpeed.toDouble()) + } + + fun updateProcess() { + if (!active) { + active = true + BaritoneUtils.primary?.pathingControlManager?.registerProcess(Process) + } + } + + fun clearProcess() { + active = false + goal = null + } +} \ No newline at end of file diff --git a/src/main/kotlin/HighwayToolsProcess.kt b/src/main/kotlin/trombone/Process.kt similarity index 61% rename from src/main/kotlin/HighwayToolsProcess.kt rename to src/main/kotlin/trombone/Process.kt index a48c287..cc1ae3a 100644 --- a/src/main/kotlin/HighwayToolsProcess.kt +++ b/src/main/kotlin/trombone/Process.kt @@ -1,13 +1,20 @@ +package trombone + +import HighwayTools +import HighwayTools.anonymizeStats +import baritone.api.pathing.goals.GoalNear import baritone.api.process.IBaritoneProcess import baritone.api.process.PathingCommand import baritone.api.process.PathingCommandType import com.lambda.client.util.math.CoordinateConverter.asString +import trombone.Pathfinder.goal +import trombone.task.TaskManager.lastTask /** * @author Avanatiker * @since 26/08/20 */ -object HighwayToolsProcess : IBaritoneProcess { +object Process : IBaritoneProcess { override fun isTemporary(): Boolean { return true @@ -20,17 +27,15 @@ object HighwayToolsProcess : IBaritoneProcess { override fun onLostControl() {} override fun displayName0(): String { - val lastTask = HighwayTools.lastTask - - val processName = if (!HighwayTools.anonymizeStats) { - HighwayTools.goal?.goalPos?.asString() - ?: lastTask?.toString() + val processName = if (!anonymizeStats) { + lastTask?.toString() + ?: goal?.asString() ?: "Thinking" } else { "Running" } - return "HighwayTools: $processName" + return "Trombone: $processName" } override fun isActive(): Boolean { @@ -38,8 +43,8 @@ object HighwayToolsProcess : IBaritoneProcess { } override fun onTick(p0: Boolean, p1: Boolean): PathingCommand { - return HighwayTools.goal?.let { - PathingCommand(it, PathingCommandType.SET_GOAL_AND_PATH) + return goal?.let { + PathingCommand(GoalNear(it, 0), PathingCommandType.SET_GOAL_AND_PATH) } ?: PathingCommand(null, PathingCommandType.REQUEST_PAUSE) } } \ No newline at end of file diff --git a/src/main/kotlin/trombone/Renderer.kt b/src/main/kotlin/trombone/Renderer.kt new file mode 100644 index 0000000..5599b2f --- /dev/null +++ b/src/main/kotlin/trombone/Renderer.kt @@ -0,0 +1,131 @@ +package trombone + +import HighwayTools.aFilled +import HighwayTools.aOutline +import HighwayTools.anonymizeStats +import HighwayTools.filled +import HighwayTools.outline +import HighwayTools.popUp +import HighwayTools.popUpSpeed +import HighwayTools.showCurrentPos +import HighwayTools.showDebugRender +import HighwayTools.textScale +import HighwayTools.thickness +import com.lambda.client.util.color.ColorHolder +import com.lambda.client.util.graphics.ESPRenderer +import com.lambda.client.util.graphics.GlStateUtils +import com.lambda.client.util.graphics.ProjectionUtils +import com.lambda.client.util.graphics.font.FontRenderAdapter +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import net.minecraft.init.Blocks +import net.minecraft.util.math.BlockPos +import org.lwjgl.opengl.GL11 +import trombone.Pathfinder.currentBlockPos +import trombone.handler.Container.containerTask +import trombone.task.BlockTask +import trombone.task.TaskManager.tasks +import trombone.task.TaskState +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin + +object Renderer { + private val renderer = ESPRenderer() + + fun renderWorld() { + renderer.clear() + renderer.aFilled = if (filled) aFilled else 0 + renderer.aOutline = if (outline) aOutline else 0 + renderer.thickness = thickness + val currentTime = System.currentTimeMillis() + + if (showCurrentPos) renderer.add(currentBlockPos, ColorHolder(255, 255, 255)) + + if (containerTask.taskState != TaskState.DONE) { + addToRenderer(containerTask, currentTime) + } + + tasks.values.forEach { + if (it.targetBlock == Blocks.AIR && it.taskState == TaskState.DONE) return@forEach + if (it.toRemove) { + addToRenderer(it, currentTime, true) + } else { + addToRenderer(it, currentTime) + } + } + renderer.render(false) + } + + fun renderOverlay() { + if (!showDebugRender) return + GlStateUtils.rescaleActual() + + if (containerTask.taskState != TaskState.DONE) { + updateOverlay(containerTask.blockPos, containerTask) + } + + tasks.filterValues { + it.taskState != TaskState.DONE + }.forEach { (pos, blockTask) -> + updateOverlay(pos, blockTask) + } + } + + private fun updateOverlay(pos: BlockPos, blockTask: BlockTask) { + GL11.glPushMatrix() + val screenPos = ProjectionUtils.toScreenPos(pos.toVec3dCenter()) + GL11.glTranslated(screenPos.x, screenPos.y, 0.0) + GL11.glScalef(textScale * 2.0f, textScale * 2.0f, 1.0f) + + val color = ColorHolder(255, 255, 255, 255) + + val debugInfos = mutableListOf>() + if (!anonymizeStats) debugInfos.add(Pair("Pos", pos.asString())) + if (blockTask != containerTask) { + debugInfos.add(Pair("Start Distance", "%.2f".format(blockTask.startDistance))) + debugInfos.add(Pair("Eye Distance", "%.2f".format(blockTask.eyeDistance))) + } else { + debugInfos.add(Pair("Item", "${blockTask.item.registryName}")) + } + if (blockTask.taskState == TaskState.PLACE || + blockTask.taskState == TaskState.LIQUID) { + debugInfos.add(Pair("Depth", "${blockTask.sequence.size}")) + if (blockTask.isLiquidSource) debugInfos.add(Pair("Liquid Source", "")) + } + if (blockTask.isOpen) debugInfos.add(Pair("Open", "")) + if (blockTask.isLoaded) debugInfos.add(Pair("Loaded", "")) + if (blockTask.destroy) debugInfos.add(Pair("Destroy", "")) + if (blockTask.stuckTicks > 0) debugInfos.add(Pair("Stuck", "${blockTask.stuckTicks}")) + + debugInfos.forEachIndexed { index, pair -> + val text = if (pair.second == "") { + pair.first + } else { + "${pair.first}: ${pair.second}" + } + val halfWidth = FontRenderAdapter.getStringWidth(text) / -2.0f + FontRenderAdapter.drawString(text, halfWidth, (FontRenderAdapter.getFontHeight() + 2.0f) * index, color = color) + } + + GL11.glPopMatrix() + } + + private fun addToRenderer(blockTask: BlockTask, currentTime: Long, reverse: Boolean = false) { + if (popUp) { + val flip = if (reverse) { + cos(((currentTime - blockTask.timestamp).toDouble() + .coerceAtMost(popUpSpeed * PI / 2) / popUpSpeed)) + } else { + sin(((currentTime - blockTask.timestamp).toDouble() + .coerceAtMost(popUpSpeed * PI / 2) / popUpSpeed)) + } + renderer.add(blockTask.aabb + .shrink((0.5 - flip * 0.5)), + blockTask.taskState.color + ) + } else { + renderer.add(blockTask.aabb, blockTask.taskState.color) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/Statistics.kt b/src/main/kotlin/trombone/Statistics.kt new file mode 100644 index 0000000..7f10476 --- /dev/null +++ b/src/main/kotlin/trombone/Statistics.kt @@ -0,0 +1,382 @@ +package trombone + +import HighwayTools.anonymizeStats +import HighwayTools.breakDelay +import HighwayTools.dynamicDelay +import HighwayTools.fillerMat +import HighwayTools.height +import HighwayTools.material +import HighwayTools.mode +import HighwayTools.placeDelay +import HighwayTools.width +import HighwayToolsHud +import HighwayToolsHud.showEnvironment +import HighwayToolsHud.showEstimations +import HighwayToolsHud.showLifeTime +import HighwayToolsHud.showPerformance +import HighwayToolsHud.showQueue +import HighwayToolsHud.showSession +import HighwayToolsHud.showTask +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.module.modules.client.Hud.primaryColor +import com.lambda.client.module.modules.client.Hud.secondaryColor +import com.lambda.client.util.graphics.font.TextComponent +import com.lambda.client.util.items.countBlock +import com.lambda.client.util.items.countItem +import com.lambda.client.util.items.inventorySlots +import com.lambda.client.util.items.item +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.math.VectorUtils.multiply +import net.minecraft.init.Blocks +import net.minecraft.init.Items +import net.minecraft.item.ItemPickaxe +import net.minecraft.network.play.client.CPacketClientStatus +import net.minecraft.stats.StatList +import trombone.IO.disableError +import trombone.Pathfinder.currentBlockPos +import trombone.Pathfinder.moveState +import trombone.Pathfinder.startingBlockPos +import trombone.Pathfinder.startingDirection +import trombone.Trombone.Structure +import trombone.handler.Container.containerTask +import trombone.handler.Container.grindCycles +import trombone.handler.Inventory.packetLimiter +import trombone.interaction.Place.extraPlaceDelay +import trombone.task.BlockTask +import trombone.task.TaskManager.sortedTasks +import trombone.task.TaskState +import java.util.concurrent.ConcurrentLinkedDeque + +object Statistics { + val simpleMovingAveragePlaces = ConcurrentLinkedDeque() + val simpleMovingAverageBreaks = ConcurrentLinkedDeque() + val simpleMovingAverageDistance = ConcurrentLinkedDeque() + var totalBlocksPlaced = 0 + var totalBlocksBroken = 0 + private var totalDistance = 0.0 + private var runtimeMilliSeconds = 0 + private var prevFood = 0 + private var foodLoss = 1 + private var materialLeft = 0 + private var fillerMatLeft = 0 + private var lastToolDamage = 0 + var durabilityUsages = 0 + private var matPlaced = 0 + private var matMined = 0 + private var enderMined = 0 + private var netherrackMined = 0 + private var pickaxeBroken = 0 + + fun SafeClientEvent.updateStats() { + updateFood() + + /* Update the minecraft statistics all 15 seconds */ + if (runtimeMilliSeconds % 15000 == 0) { + connection.sendPacket(CPacketClientStatus(CPacketClientStatus.State.REQUEST_STATS)) + } + runtimeMilliSeconds += 50 + + updateDequeues() + } + + private fun SafeClientEvent.updateFood() { + val currentFood = player.foodStats.foodLevel + if (currentFood < 7.0) { + disableError("Out of food") + } + if (currentFood != prevFood) { + if (currentFood < prevFood) foodLoss++ + prevFood = currentFood + } + } + + fun updateTotalDistance() { + totalDistance += startingBlockPos.distanceTo(currentBlockPos) + } + + private fun updateDequeues() { + val removeTime = System.currentTimeMillis() - HighwayToolsHud.simpleMovingAverageRange * 1000L + + updateDeque(simpleMovingAveragePlaces, removeTime) + updateDeque(simpleMovingAverageBreaks, removeTime) + updateDeque(simpleMovingAverageDistance, removeTime) + + updateDeque(packetLimiter, System.currentTimeMillis() - 1000L) + } + + private fun updateDeque(deque: ConcurrentLinkedDeque, removeTime: Long) { + while (deque.isNotEmpty() && deque.first() < removeTime) { + deque.removeFirst() + } + } + + fun SafeClientEvent.gatherStatistics(displayText: TextComponent) { + val runtimeSec = (runtimeMilliSeconds / 1000) + 0.0001 + val distanceDone = startingBlockPos.distanceTo(currentBlockPos).toInt() + totalDistance + + if (showSession) gatherSession(displayText, runtimeSec) + + if (showLifeTime) gatherLifeTime(displayText) + + if (showPerformance) gatherPerformance(displayText, runtimeSec, distanceDone) + + if (showEnvironment) gatherEnvironment(displayText) + + if (showTask) gatherTask(displayText) + + if (showEstimations) gatherEstimations(displayText, runtimeSec, distanceDone) + + if (showQueue) gatherQueue(displayText) + + displayText.addLine("by Constructor#9948/Avanatiker", primaryColor, scale = 0.6f) + } + + private fun gatherSession(displayText: TextComponent, runtimeSec: Double) { + val seconds = (runtimeSec % 60.0).toInt().toString().padStart(2, '0') + val minutes = ((runtimeSec % 3600.0) / 60.0).toInt().toString().padStart(2, '0') + val hours = (runtimeSec / 3600.0).toInt().toString().padStart(2, '0') + + displayText.addLine("Session", primaryColor) + + displayText.add(" Runtime:", primaryColor) + displayText.addLine("$hours:$minutes:$seconds", secondaryColor) + + displayText.add(" Direction:", primaryColor) + displayText.addLine("${startingDirection.displayName} / ${startingDirection.displayNameXY}", secondaryColor) + + if (!anonymizeStats) displayText.add(" Start:", primaryColor) + if (!anonymizeStats) displayText.addLine("(${startingBlockPos.asString()})", secondaryColor) + + displayText.add(" Placed / destroyed:", primaryColor) + displayText.addLine("%,d".format(totalBlocksPlaced) + " / " + "%,d".format(totalBlocksBroken), secondaryColor) + + + } + + private fun SafeClientEvent.gatherLifeTime(displayText: TextComponent) { + matPlaced = StatList.getObjectUseStats(material.item)?.let { + player.statFileWriter.readStat(it) + } ?: 0 + matMined = StatList.getBlockStats(material)?.let { + player.statFileWriter.readStat(it) + } ?: 0 + enderMined = StatList.getBlockStats(Blocks.ENDER_CHEST)?.let { + player.statFileWriter.readStat(it) + } ?: 0 + netherrackMined = StatList.getBlockStats(Blocks.NETHERRACK)?.let { + player.statFileWriter.readStat(it) + } ?: 0 + pickaxeBroken = StatList.getObjectBreakStats(Items.DIAMOND_PICKAXE)?.let { + player.statFileWriter.readStat(it) + } ?: 0 + + if (matPlaced + matMined + enderMined + netherrackMined + pickaxeBroken > 0) { + displayText.addLine("Lifetime", primaryColor) + } + + if (mode == Structure.HIGHWAY || mode == Structure.FLAT) { + if (matPlaced > 0) { + displayText.add(" ${material.localizedName} placed:", primaryColor) + displayText.addLine("%,d".format(matPlaced), secondaryColor) + } + + if (matMined > 0) { + displayText.add(" ${material.localizedName} mined:", primaryColor) + displayText.addLine("%,d".format(matMined), secondaryColor) + } + + if (enderMined > 0) { + displayText.add(" ${Blocks.ENDER_CHEST.localizedName} mined:", primaryColor) + displayText.addLine("%,d".format(enderMined), secondaryColor) + } + } + + if (netherrackMined > 0) { + displayText.add(" ${Blocks.NETHERRACK.localizedName} mined:", primaryColor) + displayText.addLine("%,d".format(netherrackMined), secondaryColor) + } + + if (pickaxeBroken > 0) { + displayText.add(" Diamond Pickaxe broken:", primaryColor) + displayText.addLine("%,d".format(pickaxeBroken), secondaryColor) + } + } + + private fun gatherPerformance(displayText: TextComponent, runtimeSec: Double, distanceDone: Double) { + displayText.addLine("Performance", primaryColor) + + displayText.add(" Placements / s: ", primaryColor) + displayText.addLine("%.2f SMA(%.2f)".format(totalBlocksPlaced / runtimeSec, simpleMovingAveragePlaces.size / HighwayToolsHud.simpleMovingAverageRange.toDouble()), secondaryColor) + + displayText.add(" Breaks / s:", primaryColor) + displayText.addLine("%.2f SMA(%.2f)".format(totalBlocksBroken / runtimeSec, simpleMovingAverageBreaks.size / HighwayToolsHud.simpleMovingAverageRange.toDouble()), secondaryColor) + + displayText.add(" Distance km / h:", primaryColor) + displayText.addLine("%.2f SMA(%.2f)".format((distanceDone / runtimeSec * 60.0 * 60.0) / 1000.0, (simpleMovingAverageDistance.size / HighwayToolsHud.simpleMovingAverageRange * 60.0 * 60.0) / 1000.0), secondaryColor) + + displayText.add(" Food level loss / h:", primaryColor) + displayText.addLine("%.2f".format(totalBlocksBroken / foodLoss.toDouble()), secondaryColor) + + displayText.add(" Pickaxes / h:", primaryColor) + displayText.addLine("%.2f".format((durabilityUsages / runtimeSec) * 60.0 * 60.0 / 1561.0), secondaryColor) + + displayText.add(" Mining packets / s:", primaryColor) + displayText.addLine("${packetLimiter.size}", secondaryColor) + } + + private fun gatherEnvironment(displayText: TextComponent) { + displayText.addLine("Environment", primaryColor) + + displayText.add(" Materials:", primaryColor) + displayText.addLine("Main(${material.localizedName}) Filler(${fillerMat.localizedName})", secondaryColor) + + displayText.add(" Dimensions:", primaryColor) + displayText.addLine("Width($width) Height($height)", secondaryColor) + + displayText.add(" Delays:", primaryColor) + if (dynamicDelay) { + displayText.addLine("Place(${placeDelay + extraPlaceDelay}) Break($breakDelay)", secondaryColor) + } else { + displayText.addLine("Place($placeDelay) Break($breakDelay)", secondaryColor) + } + + displayText.add(" Movement:", primaryColor) + displayText.addLine("$moveState", secondaryColor) + } + + private fun gatherTask(displayText: TextComponent) { + val task: BlockTask? = if (containerTask.taskState != TaskState.DONE) { + containerTask + } else { + sortedTasks.firstOrNull() + } + task?.let { + displayText.addLine("Task", primaryColor) + + displayText.add(" Status:", primaryColor) + displayText.addLine("${it.taskState}", secondaryColor) + + displayText.add(" Target block:", primaryColor) + displayText.addLine(it.targetBlock.localizedName, secondaryColor) + + if (it.item != Items.AIR) { + displayText.add(" Target item:", primaryColor) + displayText.addLine(it.targetBlock.localizedName, secondaryColor) + } + + if (!anonymizeStats) { + displayText.add(" Position:", primaryColor) + displayText.addLine("(${it.blockPos.asString()})", secondaryColor) + } + + displayText.add(" Ticks stuck:", primaryColor) + displayText.addLine("${it.stuckTicks}", secondaryColor) + } + } + + private fun SafeClientEvent.gatherEstimations(displayText: TextComponent, runtimeSec: Double, distanceDone: Double) { + when (mode) { + Structure.HIGHWAY, Structure.FLAT -> { + materialLeft = player.inventorySlots.countBlock(material) + fillerMatLeft = player.inventorySlots.countBlock(fillerMat) + val indirectMaterialLeft = 8 * player.inventorySlots.countBlock(Blocks.ENDER_CHEST) + + val pavingLeft = materialLeft / (totalBlocksPlaced.coerceAtLeast(1) / distanceDone.coerceAtLeast(1.0)) + + // ToDo: Cache shulker count + +// val pavingLeftAll = (materialLeft + indirectMaterialLeft) / ((totalBlocksPlaced + 0.001) / (distanceDone + 0.001)) + + val secLeft = (pavingLeft).coerceAtLeast(0.0) / (startingBlockPos.distanceTo(currentBlockPos).toInt() / runtimeSec) + val secondsLeft = (secLeft % 60).toInt().toString().padStart(2, '0') + val minutesLeft = ((secLeft % 3600) / 60).toInt().toString().padStart(2, '0') + val hoursLeft = (secLeft / 3600).toInt().toString().padStart(2, '0') + + displayText.addLine("Refill", primaryColor) + displayText.add(" ${material.localizedName}:", primaryColor) + + if (material == Blocks.OBSIDIAN) { + displayText.addLine("Direct($materialLeft) Indirect($indirectMaterialLeft)", secondaryColor) + } else { + displayText.addLine("$materialLeft", secondaryColor) + } + + displayText.add(" ${fillerMat.localizedName}:", primaryColor) + displayText.addLine("$fillerMatLeft", secondaryColor) + + if (grindCycles > 0) { + displayText.add(" Ender Chest cycles left:", primaryColor) + displayText.addLine("$grindCycles", secondaryColor) + } else { + displayText.add(" Distance left:", primaryColor) + displayText.addLine("${pavingLeft.toInt()}", secondaryColor) + + if (!anonymizeStats) displayText.add(" Destination:", primaryColor) + if (!anonymizeStats) displayText.addLine("(${currentBlockPos.add(startingDirection.directionVec.multiply(pavingLeft.toInt())).asString()})", secondaryColor) + + displayText.add(" ETA:", primaryColor) + displayText.addLine("$hoursLeft:$minutesLeft:$secondsLeft", secondaryColor) + } + } + Structure.TUNNEL -> { + val pickaxesLeft = player.inventorySlots.countItem() + + val tunnelingLeft = (pickaxesLeft * 1561) / (durabilityUsages.coerceAtLeast(1) / distanceDone.coerceAtLeast(1.0)) + + val secLeft = tunnelingLeft.coerceAtLeast(0.0) / (startingBlockPos.distanceTo(currentBlockPos).toInt() / runtimeSec) + val secondsLeft = (secLeft % 60).toInt().toString().padStart(2, '0') + val minutesLeft = ((secLeft % 3600) / 60).toInt().toString().padStart(2, '0') + val hoursLeft = (secLeft / 3600).toInt().toString().padStart(2, '0') + + displayText.addLine("Destination:", primaryColor) + + displayText.add(" Pickaxes:", primaryColor) + displayText.addLine("$pickaxesLeft", secondaryColor) + + displayText.add(" Distance left:", primaryColor) + displayText.addLine("${tunnelingLeft.toInt()}", secondaryColor) + + if (!anonymizeStats) displayText.add(" Destination:", primaryColor) + if (!anonymizeStats) displayText.addLine("(${currentBlockPos.add(startingDirection.directionVec.multiply(tunnelingLeft.toInt())).asString()})", secondaryColor) + + displayText.add(" ETA:", primaryColor) + displayText.addLine("$hoursLeft:$minutesLeft:$secondsLeft", secondaryColor) + } + } + } + + private fun gatherQueue(displayText: TextComponent) { + if (containerTask.taskState != TaskState.DONE) { + displayText.addLine("Container", primaryColor, scale = 0.6f) + displayText.addLine(containerTask.prettyPrint(), primaryColor, scale = 0.6f) + } + + if (sortedTasks.isNotEmpty()) { + displayText.addLine("Pending", primaryColor, scale = 0.6f) + addTaskComponentList(displayText, sortedTasks) + } + } + + private fun addTaskComponentList(displayText: TextComponent, tasks: Collection) { + tasks.forEach { + displayText.addLine(it.prettyPrint(), primaryColor, scale = 0.6f) + } + } + + fun resetStats() { + simpleMovingAveragePlaces.clear() + simpleMovingAverageBreaks.clear() + simpleMovingAverageDistance.clear() + totalBlocksPlaced = 0 + totalBlocksBroken = 0 + totalDistance = 0.0 + runtimeMilliSeconds = 0 + prevFood = 0 + foodLoss = 1 + materialLeft = 0 + fillerMatLeft = 0 + lastToolDamage = 0 + durabilityUsages = 0 + } +} diff --git a/src/main/kotlin/trombone/Trombone.kt b/src/main/kotlin/trombone/Trombone.kt new file mode 100644 index 0000000..23d13ba --- /dev/null +++ b/src/main/kotlin/trombone/Trombone.kt @@ -0,0 +1,54 @@ +package trombone + +import HighwayTools +import HighwayTools.info +import com.lambda.client.event.SafeClientEvent +import trombone.BaritoneHelper.resetBaritone +import trombone.BaritoneHelper.setupBaritone +import trombone.IO.pauseCheck +import trombone.IO.printDisable +import trombone.IO.printEnable +import trombone.Pathfinder.clearProcess +import trombone.Pathfinder.setupPathing +import trombone.Pathfinder.updateProcess +import trombone.Statistics.updateStats +import trombone.Statistics.updateTotalDistance +import trombone.handler.Inventory.updateRotation +import trombone.task.TaskManager.clearTasks +import trombone.task.TaskManager.populateTasks +import trombone.task.TaskManager.runTasks + +object Trombone { + val module = HighwayTools + var active = false + + enum class Structure { + HIGHWAY, FLAT, TUNNEL + } + + fun SafeClientEvent.onEnable() { + clearTasks() + setupPathing() + setupBaritone() + if (info) printEnable() + } + + fun onDisable() { + resetBaritone() + printDisable() + clearProcess() + clearTasks() + updateTotalDistance() + } + + fun SafeClientEvent.tick() { + populateTasks() + updateStats() + + if (pauseCheck()) return + + updateProcess() + runTasks() + updateRotation() + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/blueprint/BlueprintGenerator.kt b/src/main/kotlin/trombone/blueprint/BlueprintGenerator.kt new file mode 100644 index 0000000..b9ce776 --- /dev/null +++ b/src/main/kotlin/trombone/blueprint/BlueprintGenerator.kt @@ -0,0 +1,202 @@ +package trombone.blueprint + +import HighwayTools.backfill +import HighwayTools.cleanCorner +import HighwayTools.cleanFloor +import HighwayTools.cleanLeftWall +import HighwayTools.cleanRightWall +import HighwayTools.cleanRoof +import HighwayTools.clearSpace +import HighwayTools.cornerBlock +import HighwayTools.fillerMat +import HighwayTools.height +import HighwayTools.material +import HighwayTools.maxReach +import HighwayTools.mode +import HighwayTools.railing +import HighwayTools.railingHeight +import HighwayTools.width +import com.lambda.client.commons.extension.ceilToInt +import com.lambda.client.commons.extension.floorToInt +import com.lambda.client.util.math.Direction +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.math.VectorUtils.multiply +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import net.minecraft.init.Blocks +import net.minecraft.util.math.BlockPos +import trombone.Pathfinder.currentBlockPos +import trombone.Pathfinder.startingBlockPos +import trombone.Pathfinder.startingDirection +import trombone.Trombone.Structure + +object BlueprintGenerator { + val blueprint = HashMap() + + fun generateBluePrint() { + blueprint.clear() + val basePos = currentBlockPos.down() + + if (mode == Structure.FLAT) { + generateFlat(basePos) + return + } + + val zDirection = startingDirection + val xDirection = zDirection.clockwise(if (zDirection.isDiagonal) 1 else 2) + + for (x in -maxReach.floorToInt() * 5..maxReach.ceilToInt() * 5) { + val thisPos = basePos.add(zDirection.directionVec.multiply(x)) + if (clearSpace) generateClear(thisPos, xDirection) + if (mode == Structure.TUNNEL) { + if (backfill) { + generateBackfill(thisPos, xDirection) + } else { + if (cleanFloor) generateFloor(thisPos, xDirection) + if (cleanRightWall || cleanLeftWall) generateWalls(thisPos, xDirection) + if (cleanRoof) generateRoof(thisPos, xDirection) + if (cleanCorner && !cornerBlock && width > 2) generateCorner(thisPos, xDirection) + } + } else { + generateBase(thisPos, xDirection) + } + } + + if (mode == Structure.TUNNEL && (!cleanFloor || backfill)) { + if (startingDirection.isDiagonal) { + for (x in 0..maxReach.floorToInt()) { + val pos = basePos.add(zDirection.directionVec.multiply(x)) + blueprint[pos] = BlueprintTask(fillerMat, isFiller = true) + blueprint[pos.add(startingDirection.clockwise(7).directionVec)] = BlueprintTask(fillerMat, isFiller = true) + } + } else { + for (x in 0..maxReach.floorToInt()) { + blueprint[basePos.add(zDirection.directionVec.multiply(x))] = BlueprintTask(fillerMat, isFiller = true) + } + } + } + } + + private fun generateClear(basePos: BlockPos, xDirection: Direction) { + for (w in 0 until width) { + for (h in 0 until height) { + val x = w - width / 2 + val pos = basePos.add(xDirection.directionVec.multiply(x)).up(h) + + if (mode == Structure.HIGHWAY && h == 0 && isRail(w)) { + continue + } + + if (mode == Structure.HIGHWAY) { + blueprint[pos] = BlueprintTask(Blocks.AIR) + } else { + if (!(isRail(w) && h == 0 && !cornerBlock && width > 2)) blueprint[pos.up()] = BlueprintTask(Blocks.AIR) + } + } + } + } + + private fun generateBase(basePos: BlockPos, xDirection: Direction) { + for (w in 0 until width) { + val x = w - width / 2 + val pos = basePos.add(xDirection.directionVec.multiply(x)) + + if (mode == Structure.HIGHWAY && isRail(w)) { + if (!cornerBlock && width > 2 && startingDirection.isDiagonal) blueprint[pos] = BlueprintTask(fillerMat, isSupport = true) + val startHeight = if (cornerBlock && width > 2) 0 else 1 + for (y in startHeight..railingHeight) { + blueprint[pos.up(y)] = BlueprintTask(material) + } + } else { + blueprint[pos] = BlueprintTask(material) + } + } + } + + private fun generateFloor(basePos: BlockPos, xDirection: Direction) { + val wid = if (cornerBlock && width > 2) { + width + } else { + width - 2 + } + for (w in 0 until wid) { + val x = w - wid / 2 + val pos = basePos.add(xDirection.directionVec.multiply(x)) + blueprint[pos] = BlueprintTask(fillerMat, isFiller = true) + } + } + + private fun generateWalls(basePos: BlockPos, xDirection: Direction) { + val cb = if (!cornerBlock && width > 2) { + 1 + } else { + 0 + } + for (h in cb until height) { + if (cleanRightWall) blueprint[basePos.add(xDirection.directionVec.multiply(width - width / 2)).up(h + 1)] = BlueprintTask(fillerMat, isFiller = true) + if (cleanLeftWall) blueprint[basePos.add(xDirection.directionVec.multiply(-1 - width / 2)).up(h + 1)] = BlueprintTask(fillerMat, isFiller = true) + } + } + + private fun generateRoof(basePos: BlockPos, xDirection: Direction) { + for (w in 0 until width) { + val x = w - width / 2 + val pos = basePos.add(xDirection.directionVec.multiply(x)) + blueprint[pos.up(height + 1)] = BlueprintTask(fillerMat, isFiller = true) + } + } + + private fun generateCorner(basePos: BlockPos, xDirection: Direction) { + blueprint[basePos.add(xDirection.directionVec.multiply(-1 - width / 2 + 1)).up()] = BlueprintTask(fillerMat, isFiller = true) + blueprint[basePos.add(xDirection.directionVec.multiply(width - width / 2 - 1)).up()] = BlueprintTask(fillerMat, isFiller = true) + } + + private fun generateBackfill(basePos: BlockPos, xDirection: Direction) { + for (w in 0 until width) { + for (h in 0 until height) { + val x = w - width / 2 + val pos = basePos.add(xDirection.directionVec.multiply(x)).up(h + 1) + + if (startingBlockPos.toVec3dCenter().distanceTo(pos.toVec3dCenter()) + 1 < startingBlockPos.toVec3dCenter().distanceTo(currentBlockPos.toVec3dCenter())) { + blueprint[pos] = BlueprintTask(fillerMat, isFiller = true) + } + } + } + } + + private fun isRail(w: Int) = railing && w !in 1 until width - 1 + + private fun generateFlat(basePos: BlockPos) { + // Base + for (w1 in 0 until width) { + for (w2 in 0 until width) { + val x = w1 - width / 2 + val z = w2 - width / 2 + val pos = basePos.add(x, 0, z) + + blueprint[pos] = BlueprintTask(material) + } + } + + // Clear + if (!clearSpace) return + for (w1 in -width..width) { + for (w2 in -width..width) { + for (y in 1 until height) { + val x = w1 - width / 2 + val z = w2 - width / 2 + val pos = basePos.add(x, y, z) + + blueprint[pos] = BlueprintTask(Blocks.AIR) + } + } + } + } + + fun isInsideBlueprint(pos: BlockPos): Boolean { + return blueprint.containsKey(pos) + } + + fun isInsideBlueprintBuild(pos: BlockPos): Boolean { + return blueprint[pos]?.let { it.targetBlock == material } ?: false + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/blueprint/BlueprintTask.kt b/src/main/kotlin/trombone/blueprint/BlueprintTask.kt new file mode 100644 index 0000000..e74b980 --- /dev/null +++ b/src/main/kotlin/trombone/blueprint/BlueprintTask.kt @@ -0,0 +1,5 @@ +package trombone.blueprint + +import net.minecraft.block.Block + +data class BlueprintTask(val targetBlock: Block, val isFiller: Boolean = false, val isSupport: Boolean = false) diff --git a/src/main/kotlin/trombone/handler/Container.kt b/src/main/kotlin/trombone/handler/Container.kt new file mode 100644 index 0000000..8f983d5 --- /dev/null +++ b/src/main/kotlin/trombone/handler/Container.kt @@ -0,0 +1,228 @@ +package trombone.handler + +import HighwayTools.grindObsidian +import HighwayTools.keepFreeSlots +import HighwayTools.material +import HighwayTools.maxReach +import HighwayTools.minDistance +import HighwayTools.preferEnderChests +import HighwayTools.saveEnder +import HighwayTools.saveFood +import HighwayTools.saveMaterial +import HighwayTools.saveTools +import HighwayTools.searchEChest +import com.lambda.client.commons.extension.ceilToInt +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.module.modules.player.InventoryManager +import com.lambda.client.util.EntityUtils.getDroppedItems +import com.lambda.client.util.TickTimer +import com.lambda.client.util.TimeUnit +import com.lambda.client.util.items.* +import com.lambda.client.util.math.VectorUtils +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import com.lambda.client.util.world.getVisibleSides +import com.lambda.client.util.world.isPlaceable +import com.lambda.client.util.world.isReplaceable +import net.minecraft.init.Blocks +import net.minecraft.init.Items +import net.minecraft.inventory.ItemStackHelper +import net.minecraft.inventory.Slot +import net.minecraft.item.Item +import net.minecraft.item.ItemShulkerBox +import net.minecraft.item.ItemStack +import net.minecraft.util.EnumFacing +import net.minecraft.util.NonNullList +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.BlockPos +import net.minecraft.util.text.TextFormatting +import trombone.blueprint.BlueprintGenerator.isInsideBlueprintBuild +import trombone.IO.disableError +import trombone.Pathfinder.currentBlockPos +import trombone.handler.Inventory.zipInventory +import trombone.task.BlockTask +import trombone.task.TaskState +import kotlin.math.abs + +object Container { + var containerTask = BlockTask(BlockPos.ORIGIN, TaskState.DONE, Blocks.AIR) + val shulkerOpenTimer = TickTimer(TimeUnit.TICKS) + var grindCycles = 0 + + fun SafeClientEvent.handleRestock(item: Item) { + if (preferEnderChests && item.block == Blocks.OBSIDIAN) { + handleEnderChest(item) + } else { + // Case 1: item is in a shulker in the inventory + getShulkerWith(player.inventorySlots, item)?.let { slot -> + getRemotePos()?.let { pos -> + containerTask = BlockTask(pos, TaskState.PLACE, slot.stack.item.block, item = item) + } ?: run { + disableError("Can't find possible container position (Case: 1)") + } + } ?: run { + handleEnderChest(item) + } + } + } + + private fun SafeClientEvent.handleEnderChest(item: Item) { + if (grindObsidian && item.block == Blocks.OBSIDIAN) { + // Case 2: desired item is Obsidian and grinding E-Chests is allowed + + if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) <= saveEnder) { + handleRestock(Blocks.ENDER_CHEST.item) + return + } + + if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) > saveEnder) { + if (grindCycles > 0) { + getRemotePos()?.let { pos -> + containerTask = BlockTask(pos, TaskState.PLACE, Blocks.ENDER_CHEST, item = Blocks.OBSIDIAN.item) + containerTask.destroy = true + if (grindCycles > 1) containerTask.collect = false + containerTask.itemID = Blocks.OBSIDIAN.id + grindCycles-- + } ?: run { + disableError("Can't find possible container position (Case: 3)") + } + } else { + val freeSlots = player.inventorySlots.count { + it.stack.isEmpty + || InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) + } + + val cycles = (freeSlots - 1 - keepFreeSlots) * 8 + + if (cycles > 0) { + grindCycles = cycles + } else { + zipInventory() + } + } + } + } else { + // Case 3: last hope is the ender chest + + if (!searchEChest) { + disableError("${insufficientMaterial(item)}\nTo provide sufficient material, grant access to your ender chest. Activate in settings: ${TextFormatting.GRAY}Storage Management > Search Ender Chest") + return + } + + dispatchEnderChest(item) + } + + } + + private fun SafeClientEvent.dispatchEnderChest(item: Item) { + if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) > saveEnder) { + getRemotePos()?.let { pos -> + containerTask = BlockTask(pos, TaskState.PLACE, Blocks.ENDER_CHEST, item = item) + containerTask.itemID = Blocks.OBSIDIAN.id + } ?: run { + disableError("Can't find possible container position (Case: 4)") + } + } else { + getShulkerWith(player.inventorySlots, Blocks.ENDER_CHEST.item)?.let { slot -> + getRemotePos()?.let { pos -> + containerTask = BlockTask(pos, TaskState.PLACE, slot.stack.item.block, item = Blocks.ENDER_CHEST.item) + } ?: run { + disableError("Can't find possible container position (Case: 5)") + } + } ?: run { + disableError("No ${Blocks.ENDER_CHEST.localizedName} was found in inventory.") + } + } + } + + private fun SafeClientEvent.getRemotePos(): BlockPos? { + val origin = currentBlockPos.up().toVec3dCenter() + + return VectorUtils.getBlockPosInSphere(origin, maxReach).asSequence() + .filter { pos -> + !isInsideBlueprintBuild(pos) + && pos != currentBlockPos + && world.isPlaceable(pos) + && !world.getBlockState(pos.down()).isReplaceable + && world.isAirBlock(pos.up()) + && getVisibleSides(pos.down()).contains(EnumFacing.UP) + && player.positionVector.distanceTo(pos.toVec3dCenter()) > minDistance + && pos.y >= currentBlockPos.y + }.sortedWith( + compareByDescending { + secureScore(it) + }.thenBy { + it.distanceSqToCenter(origin.x, origin.y, origin.z).ceilToInt() + }.thenBy { + abs(it.y - currentBlockPos.y) + } + ).firstOrNull() + } + + private fun SafeClientEvent.secureScore(pos: BlockPos): Int { + var safe = 0 + if (!world.getBlockState(pos.down().north()).isReplaceable) safe++ + if (!world.getBlockState(pos.down().east()).isReplaceable) safe++ + if (!world.getBlockState(pos.down().south()).isReplaceable) safe++ + if (!world.getBlockState(pos.down().west()).isReplaceable) safe++ + return safe + } + + fun getShulkerWith(slots: List, item: Item): Slot? { + return slots.filter { + it.stack.item is ItemShulkerBox && getShulkerData(it.stack, item) > 0 + }.minByOrNull { + getShulkerData(it.stack, item) + } + } + + private fun getShulkerData(stack: ItemStack, item: Item): Int { + val tagCompound = if (stack.item is ItemShulkerBox) stack.tagCompound else return 0 + + if (tagCompound != null && tagCompound.hasKey("BlockEntityTag", 10)) { + val blockEntityTag = tagCompound.getCompoundTag("BlockEntityTag") + if (blockEntityTag.hasKey("Items", 9)) { + val shulkerInventory = NonNullList.withSize(27, ItemStack.EMPTY) + ItemStackHelper.loadAllItems(blockEntityTag, shulkerInventory) + return shulkerInventory.count { it.item == item } + } + } + + return 0 + } + + fun SafeClientEvent.getCollectingPosition(): BlockPos? { + val range = 8f + getDroppedItems(containerTask.itemID, range = range) + .minByOrNull { player.getDistance(it) } + ?.positionVector + ?.let { itemVec -> + return VectorUtils.getBlockPosInSphere(itemVec, range).asSequence() + .filter { pos -> + world.isAirBlock(pos.up()) + && world.isAirBlock(pos) + && !world.isPlaceable(pos.down()) + } + .sortedWith( + compareBy { + it.distanceSqToCenter(itemVec.x, itemVec.y, itemVec.z) + }.thenBy { + it.y + } + ).firstOrNull() + } + return null + } + + private fun SafeClientEvent.insufficientMaterial(item: Item): String { + val itemCount = player.inventorySlots.countItem(item) + var message = "" + if (saveMaterial > 0 && item == material.item) message += insufficientMaterialPrint(itemCount, saveMaterial, material.localizedName) + if (saveEnder > 0 && item.block == Blocks.ENDER_CHEST) message += insufficientMaterialPrint(itemCount, saveEnder, Blocks.ENDER_CHEST.localizedName) + if (saveTools > 0 && item == Items.DIAMOND_PICKAXE) message += insufficientMaterialPrint(itemCount, saveTools, "Diamond Pickaxe(s)") + if (saveFood > 0 && item == Items.GOLDEN_APPLE) message += insufficientMaterialPrint(itemCount, saveFood, "Golden Apple(s)") + return "$message\nTo continue anyways, set setting in ${TextFormatting.GRAY}Storage Management > Save ${TextFormatting.RESET} to zero." + } + + private fun insufficientMaterialPrint(itemCount: Int, settingCount: Int, name: String) = + "For safety purposes you need ${TextFormatting.AQUA}${settingCount - itemCount + 1}${TextFormatting.RED} more $name in your inventory ($itemCount/${settingCount + 1})." +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/handler/Inventory.kt b/src/main/kotlin/trombone/handler/Inventory.kt new file mode 100644 index 0000000..3cd6838 --- /dev/null +++ b/src/main/kotlin/trombone/handler/Inventory.kt @@ -0,0 +1,248 @@ +package trombone.handler + +import HighwayTools.keepFreeSlots +import HighwayTools.material +import HighwayTools.saveMaterial +import HighwayTools.saveTools +import HighwayTools.storageManagement +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.manager.managers.PlayerInventoryManager +import com.lambda.client.manager.managers.PlayerInventoryManager.addInventoryTask +import com.lambda.client.manager.managers.PlayerPacketManager.sendPlayerPacket +import com.lambda.client.module.modules.player.InventoryManager +import com.lambda.client.util.items.* +import com.lambda.client.util.math.RotationUtils.getRotationTo +import net.minecraft.block.Block +import net.minecraft.block.Block.getBlockFromName +import net.minecraft.enchantment.EnchantmentHelper +import net.minecraft.init.Blocks +import net.minecraft.init.Enchantments +import net.minecraft.init.Items +import net.minecraft.inventory.ClickType +import net.minecraft.inventory.Container +import net.minecraft.inventory.Slot +import net.minecraft.item.ItemBlock +import net.minecraft.util.math.Vec3d +import trombone.IO.disableError +import trombone.Trombone.module +import trombone.handler.Container.containerTask +import trombone.handler.Container.getShulkerWith +import trombone.handler.Container.handleRestock +import trombone.task.BlockTask +import trombone.task.TaskState +import java.util.concurrent.ConcurrentLinkedDeque + +object Inventory { + var lastHitVec: Vec3d = Vec3d.ZERO + var waitTicks = 0 + + val packetLimiter = ConcurrentLinkedDeque() + + @Suppress("UNUSED") + enum class RotationMode { + OFF, SPOOF + } + + fun SafeClientEvent.updateRotation() { + if (lastHitVec == Vec3d.ZERO) return + val rotation = getRotationTo(lastHitVec) + + module.sendPlayerPacket { + rotate(rotation) + } + } + + private fun SafeClientEvent.getBestTool(blockTask: BlockTask): Slot? { + return player.inventorySlots.asReversed().maxByOrNull { + val stack = it.stack + if (stack.isEmpty) { + 0.0f + } else { + var speed = stack.getDestroySpeed(world.getBlockState(blockTask.blockPos)) + + if (speed > 1.0f) { + val efficiency = EnchantmentHelper.getEnchantmentLevel(Enchantments.EFFICIENCY, stack) + if (efficiency > 0) { + speed += efficiency * efficiency + 1.0f + } + } + + speed + } + } + } + + fun SafeClientEvent.swapOrMoveBlock(blockTask: BlockTask): Boolean { + if (blockTask.isShulker()) { + getShulkerWith(player.inventorySlots, blockTask.item)?.let { slot -> + blockTask.itemID = slot.stack.item.id + slot.toHotbarSlotOrNull()?.let { + swapToSlot(it) + } ?: run { + val slotTo = player.hotbarSlots.firstEmpty()?.hotbarSlot ?: 0 + moveToHotbar(module, slot.slotNumber, slotTo) + } + } + return true + } else { + val useMat = findMaterial(blockTask) + if (useMat == Blocks.AIR) return false + + val success = swapToBlockOrMove(module, useMat, predicateSlot = { + it.item is ItemBlock + }) + + return if (!success) { + disableError("Inventory transaction of $useMat failed.") + false + } else { + true + } + } + } + + private fun SafeClientEvent.findMaterial(blockTask: BlockTask): Block { + return if (blockTask.targetBlock == material) { + if (player.inventorySlots.countBlock(material) > saveMaterial) { + material + } else { + restockFallback(blockTask) + Blocks.AIR + } + } else { + if (player.inventorySlots.countBlock(blockTask.targetBlock) > 0) { + blockTask.targetBlock + } else { + val possibleMaterials = mutableSetOf() + InventoryManager.ejectList.forEach { stringName -> + getBlockFromName(stringName)?.let { + if (player.inventorySlots.countBlock(it) > 0) possibleMaterials.add(it) + } + } + + if (possibleMaterials.isEmpty()) { + if (player.inventorySlots.countBlock(material) > saveMaterial) { + material + } else { + restockFallback(blockTask) + Blocks.AIR + } + } else { + possibleMaterials.first() + } + } + } + } + + private fun SafeClientEvent.restockFallback(blockTask: BlockTask) { + if (storageManagement) { + handleRestock(blockTask.targetBlock.item) + } else { + disableError("No usable material was found in inventory.") + } + } + + fun SafeClientEvent.swapOrMoveBestTool(blockTask: BlockTask): Boolean { + if (player.inventorySlots.countItem(Items.DIAMOND_PICKAXE) <= saveTools) { + return if (containerTask.taskState == TaskState.DONE && storageManagement) { + handleRestock(Items.DIAMOND_PICKAXE) + false + } else { + swapOrMoveTool(blockTask) + } + } + + return swapOrMoveTool(blockTask) + } + + fun SafeClientEvent.zipInventory() { + val compressibleStacks = player.inventorySlots.filter { comp -> + comp.stack.count < comp.stack.maxStackSize + && player.inventorySlots.countByStack { comp.stack.item == it.item } > 1 + } + + if (compressibleStacks.isEmpty()) { + disableError("Inventory full. (Considering that $keepFreeSlots slots are supposed to stay free)") + return + } + + compressibleStacks.forEach { slot -> + module.addInventoryTask( + PlayerInventoryManager.ClickInfo(slot = slot.slotNumber, type = ClickType.QUICK_MOVE) + ) + } + } + + private fun SafeClientEvent.swapOrMoveTool(blockTask: BlockTask) = + getBestTool(blockTask)?.let { slotFrom -> + blockTask.toolToUse = slotFrom.stack + slotFrom.toHotbarSlotOrNull()?.let { + swapToSlot(it) + } ?: run { + val slotTo = player.hotbarSlots.firstEmpty()?.hotbarSlot ?: 0 + moveToHotbar(module, slotFrom.slotNumber, slotTo) + } + true + } ?: run { + false + } + + fun SafeClientEvent.moveToInventory(originSlot: Slot, container: Container) { + container.getSlots(27..62).firstOrNull { + originSlot.stack.item == it.stack.item + && it.stack.count < originSlot.stack.maxStackSize - originSlot.stack.count + }?.let { _ -> + module.addInventoryTask( + PlayerInventoryManager.ClickInfo( + container.windowId, + originSlot.slotNumber, + 0, + ClickType.QUICK_MOVE + ) + ) + } ?: run { + container.getSlots(54..62).firstOrNull { + InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) + || it.stack.isEmpty + }?.let { freeHotbarSlot -> + module.addInventoryTask( + PlayerInventoryManager.ClickInfo( + container.windowId, + originSlot.slotNumber, + freeHotbarSlot.slotNumber - 54, + ClickType.SWAP + ) + ) + } ?: run { + container.getSlots(27..53).firstOrNull { + InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) + || it.stack.isEmpty + }?.let { freeSlot -> + module.addInventoryTask( + PlayerInventoryManager.ClickInfo( + container.windowId, + 0, + freeSlot.slotNumber, + ClickType.SWAP + ), + PlayerInventoryManager.ClickInfo( + container.windowId, + freeSlot.slotNumber, + 0, + ClickType.SWAP + ) + ) + } ?: run { + zipInventory() + } + } + } + } + + fun SafeClientEvent.getEjectSlot(): Slot? { + return player.inventorySlots.firstByStack { + !it.isEmpty && + InventoryManager.ejectList.contains(it.item.registryName.toString()) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/handler/Liquid.kt b/src/main/kotlin/trombone/handler/Liquid.kt new file mode 100644 index 0000000..8c32517 --- /dev/null +++ b/src/main/kotlin/trombone/handler/Liquid.kt @@ -0,0 +1,61 @@ +package trombone.handler + +import HighwayTools.debugLevel +import HighwayTools.fillerMat +import HighwayTools.illegalPlacements +import HighwayTools.maxReach +import HighwayTools.placementSearch +import com.lambda.client.LambdaMod +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.world.getNeighbourSequence +import net.minecraft.block.BlockLiquid +import net.minecraft.util.EnumFacing +import trombone.IO +import trombone.blueprint.BlueprintTask +import trombone.task.BlockTask +import trombone.task.TaskManager.addTask +import trombone.task.TaskManager.tasks +import trombone.task.TaskState + +object Liquid { + fun SafeClientEvent.handleLiquid(blockTask: BlockTask): Boolean { + var foundLiquid = false + + for (side in EnumFacing.values()) { + if (side == EnumFacing.DOWN) continue + val neighbourPos = blockTask.blockPos.offset(side) + + if (world.getBlockState(neighbourPos).block !is BlockLiquid) continue + + if (player.distanceTo(neighbourPos) > maxReach + || getNeighbourSequence(neighbourPos, placementSearch, maxReach, !illegalPlacements).isEmpty() + ) { + if (debugLevel == IO.DebugLevel.VERBOSE) { + LambdaMod.LOG.info("[Trombone] Skipping liquid block at ${neighbourPos.asString()} due to distance") + } + blockTask.updateState(TaskState.DONE) + return true + } + + foundLiquid = true + + tasks[neighbourPos]?.let { + updateLiquidTask(it) + } ?: run { + val newTask = BlockTask(neighbourPos, TaskState.LIQUID, fillerMat) + val blueprintTask = BlueprintTask(fillerMat, isFiller = true, isSupport = false) + + addTask(newTask, blueprintTask) + } + } + + return foundLiquid + } + + fun SafeClientEvent.updateLiquidTask(blockTask: BlockTask) { + blockTask.updateState(TaskState.LIQUID) + blockTask.updateTask(this) + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/handler/Packet.kt b/src/main/kotlin/trombone/handler/Packet.kt new file mode 100644 index 0000000..7a9b1c8 --- /dev/null +++ b/src/main/kotlin/trombone/handler/Packet.kt @@ -0,0 +1,81 @@ +package trombone.handler + +import com.lambda.client.LambdaMod +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.items.hotbarSlots +import net.minecraft.init.Blocks +import net.minecraft.network.Packet +import net.minecraft.network.play.server.SPacketBlockChange +import net.minecraft.network.play.server.SPacketOpenWindow +import net.minecraft.network.play.server.SPacketPlayerPosLook +import net.minecraft.network.play.server.SPacketSetSlot +import net.minecraft.network.play.server.SPacketWindowItems +import trombone.blueprint.BlueprintGenerator.isInsideBlueprint +import trombone.Pathfinder.rubberbandTimer +import trombone.Statistics.durabilityUsages +import trombone.handler.Container.containerTask +import trombone.task.TaskManager.tasks +import trombone.task.TaskState + +object Packet { + fun SafeClientEvent.handlePacket(packet: Packet<*>) { + when (packet) { + is SPacketBlockChange -> { + val pos = packet.blockPosition + if (!isInsideBlueprint(pos)) return + + val prev = world.getBlockState(pos).block + val new = packet.blockState.block + + if (prev != new) { + val task = if (pos == containerTask.blockPos) { + containerTask + } else { + tasks[pos] ?: return + } + + when (task.taskState) { + TaskState.PENDING_BREAK, TaskState.BREAKING -> { + if (new == Blocks.AIR) { + task.updateState(TaskState.BROKEN) + } + } + TaskState.PENDING_PLACE -> { + if (new != Blocks.AIR + && (task.targetBlock == new || task.isFiller) + ) { + task.updateState(TaskState.PLACED) + } + } + else -> { + // Ignored + } + } + } + } + is SPacketPlayerPosLook -> { + rubberbandTimer.reset() + } + is SPacketOpenWindow -> { + if (containerTask.taskState != TaskState.DONE && + packet.guiId == "minecraft:shulker_box" && containerTask.isShulker() || + packet.guiId == "minecraft:container" && !containerTask.isShulker()) { + containerTask.isOpen = true + } + } + is SPacketWindowItems -> { + if (containerTask.isOpen) containerTask.isLoaded = true + } + is SPacketSetSlot -> { + val currentStack = player.hotbarSlots[player.inventory.currentItem].stack + if (packet.slot == player.inventory.currentItem + 36 + && packet.stack.item == currentStack.item + && packet.stack.itemDamage > currentStack.itemDamage + ) { + durabilityUsages += packet.stack.itemDamage - currentStack.itemDamage + } + } + else -> { /* Ignored */ } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/interaction/Break.kt b/src/main/kotlin/trombone/interaction/Break.kt new file mode 100644 index 0000000..ba370a7 --- /dev/null +++ b/src/main/kotlin/trombone/interaction/Break.kt @@ -0,0 +1,175 @@ +package trombone.interaction + +import HighwayTools.breakDelay +import HighwayTools.illegalPlacements +import HighwayTools.instantMine +import HighwayTools.interactionLimit +import HighwayTools.maxReach +import HighwayTools.miningSpeedFactor +import HighwayTools.multiBreak +import HighwayTools.packetFlood +import HighwayTools.taskTimeout +import com.lambda.client.commons.extension.ceilToInt +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.math.isInSight +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.threads.onMainThreadSafe +import com.lambda.client.util.threads.runSafe +import com.lambda.client.util.world.getHitVec +import com.lambda.client.util.world.getMiningSide +import com.lambda.client.util.world.getNeighbour +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import net.minecraft.init.Blocks +import net.minecraft.network.play.client.CPacketPlayerDigging +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumHand +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.BlockPos +import trombone.handler.Inventory.lastHitVec +import trombone.handler.Inventory.packetLimiter +import trombone.handler.Inventory.waitTicks +import trombone.handler.Liquid.handleLiquid +import trombone.task.BlockTask +import trombone.task.TaskManager.tasks +import trombone.task.TaskState +import kotlin.math.ceil + +object Break { + var prePrimedPos = BlockPos.NULL_VECTOR!! + var primedPos = BlockPos.NULL_VECTOR!! + + fun SafeClientEvent.mineBlock(blockTask: BlockTask) { + val blockState = world.getBlockState(blockTask.blockPos) + + if (blockState.block == Blocks.FIRE) { + getNeighbour(blockTask.blockPos, 1, maxReach, !illegalPlacements)?.let { + lastHitVec = getHitVec(it.pos, it.side) + + extinguishFire(blockTask, it.pos, it.side) + } ?: run { + blockTask.updateState(TaskState.PLACE) + } + } else { + val ticksNeeded = ceil((1 / blockState.getPlayerRelativeBlockHardness(player, world, blockTask.blockPos)) * miningSpeedFactor).toInt() + + var side = getMiningSide(blockTask.blockPos) ?: run { + blockTask.onStuck() + return + } + + if (blockTask.blockPos == primedPos && instantMine) { + side = side.opposite + } + + lastHitVec = getHitVec(blockTask.blockPos, side) + + if (blockTask.ticksMined > ticksNeeded * 1.1 && + blockTask.taskState == TaskState.BREAKING) { + blockTask.updateState(TaskState.BREAK) + blockTask.ticksMined = 0 + } + + if (ticksNeeded == 1 || player.capabilities.isCreativeMode) { + mineBlockInstant(blockTask, side) + } else { + mineBlockNormal(blockTask, side, ticksNeeded) + } + } + + blockTask.ticksMined += 1 + } + + private fun mineBlockInstant(blockTask: BlockTask, side: EnumFacing) { + waitTicks = breakDelay + blockTask.updateState(TaskState.PENDING_BREAK) + + defaultScope.launch { + sendMiningPackets(blockTask.blockPos, side, start = true) + + if (multiBreak) tryMultiBreak(blockTask) + + delay(50L * taskTimeout) + if (blockTask.taskState == TaskState.PENDING_BREAK) { + blockTask.updateState(TaskState.BREAK) + } + } + } + + private fun tryMultiBreak(blockTask: BlockTask) { + runSafe { + val eyePos = player.getPositionEyes(1.0f) + val viewVec = lastHitVec.subtract(eyePos).normalize() + + tasks.values.filter { + it.taskState == TaskState.BREAK + && it != blockTask + }.forEach { task -> + val relativeHardness = world.getBlockState(task.blockPos).getPlayerRelativeBlockHardness(player, world, task.blockPos) + if (ceil((1 / relativeHardness) * miningSpeedFactor).ceilToInt() > 1) return@forEach + + if (packetLimiter.size > interactionLimit || handleLiquid(task)) return@runSafe + + val box = AxisAlignedBB(task.blockPos) + val rayTraceResult = box.isInSight(eyePos, viewVec, range = maxReach.toDouble(), tolerance = 0.0) + ?: return@forEach + + task.updateState(TaskState.PENDING_BREAK) + + defaultScope.launch { + sendMiningPackets(task.blockPos, rayTraceResult.sideHit, start = true) + + delay(50L * taskTimeout) + if (task.taskState == TaskState.PENDING_BREAK) { + task.updateState(TaskState.BREAK) + } + } + } + } + } + + private fun mineBlockNormal(blockTask: BlockTask, side: EnumFacing, ticks: Int) { + defaultScope.launch { + if (blockTask.taskState == TaskState.BREAK) { + blockTask.updateState(TaskState.BREAKING) + sendMiningPackets(blockTask.blockPos, side, start = true) + } else { + if (blockTask.ticksMined >= ticks) { + sendMiningPackets(blockTask.blockPos, side, stop = true) + } else { + sendMiningPackets(blockTask.blockPos, side) + } + } + } + } + + private fun extinguishFire(blockTask: BlockTask, pos: BlockPos, side: EnumFacing) { + waitTicks = breakDelay + blockTask.updateState(TaskState.PENDING_BREAK) + + defaultScope.launch { + sendMiningPackets(pos, side, start = true, abort = true) + + delay(50L * taskTimeout) + if (blockTask.taskState == TaskState.PENDING_BREAK) { + blockTask.updateState(TaskState.BREAK) + } + } + } + + private suspend fun sendMiningPackets(pos: BlockPos, side: EnumFacing, start: Boolean = false, stop: Boolean = false, abort: Boolean = false) { + packetLimiter.add(System.currentTimeMillis()) + onMainThreadSafe { + if (start || packetFlood) { + connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.START_DESTROY_BLOCK, pos, side)) + } + if (abort) { + connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.ABORT_DESTROY_BLOCK, pos, side)) + } + if (stop || packetFlood) { + connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.STOP_DESTROY_BLOCK, pos, side)) + } + player.swingArm(EnumHand.MAIN_HAND) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/interaction/Place.kt b/src/main/kotlin/trombone/interaction/Place.kt new file mode 100644 index 0000000..3a022ed --- /dev/null +++ b/src/main/kotlin/trombone/interaction/Place.kt @@ -0,0 +1,99 @@ +package trombone.interaction + +import HighwayTools.debugLevel +import HighwayTools.dynamicDelay +import HighwayTools.placeDelay +import HighwayTools.taskTimeout +import com.lambda.client.LambdaMod +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.items.blockBlacklist +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.text.MessageSendHelper +import com.lambda.client.util.threads.defaultScope +import com.lambda.client.util.threads.onMainThreadSafe +import com.lambda.client.util.world.getHitVec +import com.lambda.client.util.world.getHitVecOffset +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import net.minecraft.network.play.client.CPacketEntityAction +import net.minecraft.network.play.client.CPacketPlayerTryUseItemOnBlock +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumHand +import net.minecraft.util.math.BlockPos +import trombone.IO.DebugLevel +import trombone.Trombone.module +import trombone.handler.Container.containerTask +import trombone.handler.Inventory.lastHitVec +import trombone.handler.Inventory.waitTicks +import trombone.task.BlockTask +import trombone.task.TaskState + +object Place { + var extraPlaceDelay = 0 + + fun SafeClientEvent.placeBlock(blockTask: BlockTask) { + when (blockTask.sequence.size) { + 0 -> { + if (debugLevel == DebugLevel.VERBOSE) { + LambdaMod.LOG.warn("${module.chatName} No neighbours found for ${blockTask.blockPos.asString()}") + } + if (blockTask == containerTask) { + MessageSendHelper.sendChatMessage("${module.chatName} Can't find neighbour blocks to place down the container.") + } + blockTask.onStuck(21) + blockTask.updateState(TaskState.DONE) + return + } + else -> { + val last = blockTask.sequence.last() + lastHitVec = getHitVec(last.pos, last.side) + + placeBlockNormal(blockTask, last.pos, last.side) + } +// else -> { + // ToDo: Rewrite deep place +// blockTask.sequence.forEach { +// addTaskToPending(it.pos, TaskState.PLACE, fillerMat) +// } +// } + } + } + + private fun SafeClientEvent.placeBlockNormal(blockTask: BlockTask, placePos: BlockPos, side: EnumFacing) { + val hitVecOffset = getHitVecOffset(side) + val currentBlock = world.getBlockState(placePos).block + + waitTicks = if (dynamicDelay) { + placeDelay + extraPlaceDelay + } else { + placeDelay + } + blockTask.updateState(TaskState.PENDING_PLACE) + + if (currentBlock in blockBlacklist) { + connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.START_SNEAKING)) + } + + defaultScope.launch { + delay(20L) // ToDo: Check if necessary + onMainThreadSafe { + val placePacket = CPacketPlayerTryUseItemOnBlock(placePos, side, EnumHand.MAIN_HAND, hitVecOffset.x.toFloat(), hitVecOffset.y.toFloat(), hitVecOffset.z.toFloat()) + connection.sendPacket(placePacket) + player.swingArm(EnumHand.MAIN_HAND) + } + + if (currentBlock in blockBlacklist) { + delay(20L) + onMainThreadSafe { + connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.STOP_SNEAKING)) + } + } + + delay(50L * taskTimeout) + if (blockTask.taskState == TaskState.PENDING_PLACE) { + blockTask.updateState(TaskState.PLACE) + if (dynamicDelay && extraPlaceDelay < 10) extraPlaceDelay += 1 + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/task/BlockTask.kt b/src/main/kotlin/trombone/task/BlockTask.kt new file mode 100644 index 0000000..315efa4 --- /dev/null +++ b/src/main/kotlin/trombone/task/BlockTask.kt @@ -0,0 +1,118 @@ +package trombone.task + +import HighwayTools.illegalPlacements +import HighwayTools.maxReach +import HighwayTools.placementSearch +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import com.lambda.client.util.world.PlaceInfo +import com.lambda.client.util.world.getNeighbourSequence +import net.minecraft.block.Block +import net.minecraft.block.BlockLiquid +import net.minecraft.block.BlockShulkerBox +import net.minecraft.init.Items +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.BlockPos +import trombone.Pathfinder.startingBlockPos +import kotlin.random.Random + +class BlockTask( + val blockPos: BlockPos, + var taskState: TaskState, + var targetBlock: Block, + var isSupport: Boolean = false, + var isFiller: Boolean = false, + var item: Item = Items.AIR +) { + private var ranTicks = 0 + var stuckTicks = 0; private set + var shuffle = 0; private set + var startDistance = 0.0; private set + var eyeDistance = 0.0; private set + + var sequence: List = emptyList(); private set + var isLiquidSource = false + + var isOpen = false + var stopPull = false + var stacksPulled = 0 + var isLoaded = false + var itemID = 0 + var destroy = false + var collect = true + + var timestamp = System.currentTimeMillis() + var aabb = AxisAlignedBB(1.0, 1.0, 1.0, 1.0, 1.0, 1.0) + + var toRemove = false + var ticksMined = 1 + var toolToUse: ItemStack = ItemStack.EMPTY + + fun updateState(state: TaskState) { + if (state != taskState) { + timestamp = System.currentTimeMillis() + + stuckTicks = 0 + ranTicks = 0 + taskState = state + } + } + + fun onTick() { + ranTicks++ + if (ranTicks > taskState.stuckThreshold) { + stuckTicks++ + } + } + + fun onStuck(weight: Int = 1) { + stuckTicks += weight + } + + fun resetStuck() { + stuckTicks = 0 + } + + fun updateTask(event: SafeClientEvent) { + isLiquidSource = event.world.getBlockState(blockPos).let { + it.block is BlockLiquid && it.getValue(BlockLiquid.LEVEL) == 0 + } + + when (taskState) { + TaskState.PLACE, TaskState.LIQUID -> { + sequence = event.getNeighbourSequence(blockPos, placementSearch, maxReach, !illegalPlacements) + } + else -> {} + } + + startDistance = startingBlockPos.toVec3dCenter().distanceTo(blockPos.toVec3dCenter()) + eyeDistance = event.player.getPositionEyes(1f).distanceTo(blockPos.toVec3dCenter()) + + aabb = event.world + .getBlockState(blockPos) + .getSelectedBoundingBox(event.world, blockPos) + } + + fun isShulker() = targetBlock is BlockShulkerBox + + fun shuffle() { + shuffle = Random.nextInt(0, 1000) + } + + fun prettyPrint(): String { + return " ${targetBlock.localizedName}@(${blockPos.asString()}) State: $taskState Timings: (Threshold: ${taskState.stuckThreshold} Timeout: ${taskState.stuckTimeout}) Priority: ${taskState.ordinal} Stuck: $stuckTicks" + } + + override fun toString(): String { + return "Block: ${targetBlock.localizedName} @ Position: (${blockPos.asString()}) State: ${taskState.name}" + } + + override fun equals(other: Any?) = this === other + || (other is BlockTask + && blockPos == other.blockPos) + + override fun hashCode() = blockPos.hashCode() +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/task/TaskExecutor.kt b/src/main/kotlin/trombone/task/TaskExecutor.kt new file mode 100644 index 0000000..d43715c --- /dev/null +++ b/src/main/kotlin/trombone/task/TaskExecutor.kt @@ -0,0 +1,460 @@ +package trombone.task + +import HighwayTools.anonymizeStats +import HighwayTools.breakDelay +import HighwayTools.debugLevel +import HighwayTools.dynamicDelay +import HighwayTools.fakeSounds +import HighwayTools.fastFill +import HighwayTools.fillerMat +import HighwayTools.ignoreBlocks +import HighwayTools.interactionLimit +import HighwayTools.keepFreeSlots +import HighwayTools.leaveEmptyShulkers +import HighwayTools.material +import HighwayTools.mode +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.module.modules.player.InventoryManager +import com.lambda.client.util.TickTimer +import com.lambda.client.util.items.* +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import com.lambda.client.util.text.MessageSendHelper +import com.lambda.client.util.world.* +import net.minecraft.block.BlockLiquid +import net.minecraft.client.gui.inventory.GuiContainer +import net.minecraft.init.Blocks +import net.minecraft.item.ItemPickaxe +import net.minecraft.network.play.client.CPacketPlayerTryUseItemOnBlock +import net.minecraft.util.EnumFacing +import net.minecraft.util.EnumHand +import net.minecraft.util.SoundCategory +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.BlockPos +import trombone.* +import trombone.IO.disableError +import trombone.Pathfinder.moveState +import trombone.Pathfinder.shouldBridge +import trombone.Trombone.module +import trombone.blueprint.BlueprintGenerator +import trombone.handler.Container +import trombone.handler.Container.containerTask +import trombone.handler.Container.getCollectingPosition +import trombone.handler.Inventory +import trombone.handler.Inventory.getEjectSlot +import trombone.handler.Inventory.moveToInventory +import trombone.handler.Inventory.swapOrMoveBestTool +import trombone.handler.Inventory.swapOrMoveBlock +import trombone.handler.Liquid.handleLiquid +import trombone.handler.Liquid.updateLiquidTask +import trombone.interaction.Break +import trombone.interaction.Break.mineBlock +import trombone.interaction.Place +import trombone.interaction.Place.placeBlock + +object TaskExecutor { + private val restockTimer = TickTimer() + + fun SafeClientEvent.doTask(blockTask: BlockTask, updateOnly: Boolean = false) { + if (!updateOnly) blockTask.onTick() + + when (blockTask.taskState) { + TaskState.RESTOCK -> { + if (!updateOnly) doRestock() + } + TaskState.PICKUP -> { + if (!updateOnly) doPickup() + } + TaskState.OPEN_CONTAINER -> { + if (!updateOnly) doOpenContainer() + } + TaskState.BREAKING -> { + doBreaking(blockTask, updateOnly) + } + TaskState.BROKEN -> { + doBroken(blockTask) + } + TaskState.PLACED -> { + doPlaced(blockTask) + } + TaskState.BREAK -> { + doBreak(blockTask, updateOnly) + } + TaskState.PLACE, TaskState.LIQUID -> { + doPlace(blockTask, updateOnly) + } + TaskState.PENDING_BREAK, TaskState.PENDING_PLACE -> { + blockTask.onStuck() + } + TaskState.IMPOSSIBLE_PLACE -> { + if (!updateOnly) doImpossiblePlace() + } + TaskState.DONE -> { /* do nothing */ } + } + } + + private fun SafeClientEvent.doRestock() { + val container = player.openContainer + + if (mc.currentScreen !is GuiContainer && !containerTask.isLoaded) { + containerTask.updateState(TaskState.OPEN_CONTAINER) + return + } + + if (container.inventorySlots.size != 63) { + disableError("Inventory container changed. Current: ${player.openContainer.windowId} and saved ${container.windowId}") + return + } + + if (leaveEmptyShulkers + && !InventoryManager.ejectList.contains(containerTask.item.registryName.toString()) + && containerTask.isShulker() + && container.getSlots(0..26).all { + it.stack.isEmpty + || InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) + } + ) { + if (debugLevel != IO.DebugLevel.OFF) { + if (!anonymizeStats) { + MessageSendHelper.sendChatMessage("${module.chatName} Left empty ${containerTask.targetBlock.localizedName}@(${containerTask.blockPos.asString()})") + } else { + MessageSendHelper.sendChatMessage("${module.chatName} Left empty ${containerTask.targetBlock.localizedName}") + } + } + + containerTask.isOpen = false + player.closeScreen() + containerTask.updateState(TaskState.DONE) + moveState = Pathfinder.MovementState.RUNNING + return + } + + val freeSlots = container.getSlots(27..62).count { + InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) + || it.stack.isEmpty + } - 1 - keepFreeSlots + + if (containerTask.stopPull || freeSlots < 1) { + containerTask.updateState(TaskState.BREAK) + containerTask.isOpen = false + player.closeScreen() + return + } + + container.getSlots(0..26).firstItem(containerTask.item)?.let { + moveToInventory(it, container) + containerTask.stacksPulled++ + containerTask.stopPull = true + if (fastFill) { + if (mode == Trombone.Structure.TUNNEL && containerTask.item is ItemPickaxe) { + containerTask.stopPull = false + } else if (mode != Trombone.Structure.TUNNEL && containerTask.item == material.item) { + containerTask.stopPull = false + } + } + } ?: run { + if (containerTask.stacksPulled == 0) { + Container.getShulkerWith(container.getSlots(0..26), containerTask.item)?.let { + moveToInventory(it, container) + containerTask.stopPull = true + } ?: run { + disableError("No ${containerTask.item.registryName} left in any container.") + } + } else { + containerTask.updateState(TaskState.BREAK) + containerTask.isOpen = false + player.closeScreen() + } + } + } + + private fun SafeClientEvent.doPickup() { + if (getCollectingPosition() == null) { + containerTask.updateState(TaskState.DONE) + moveState = Pathfinder.MovementState.RUNNING + return + } + + if (player.inventorySlots.firstEmpty() == null && restockTimer.tick(20)) { + getEjectSlot()?.let { + throwAllInSlot(module, it) + } + } else { + containerTask.onStuck() + } + } + + private fun SafeClientEvent.doOpenContainer() { + moveState = Pathfinder.MovementState.RESTOCK + + if (containerTask.isOpen) { + containerTask.updateState(TaskState.RESTOCK) + return + } + + if (Container.shulkerOpenTimer.tick(20)) { + val center = containerTask.blockPos.toVec3dCenter() + val diff = player.getPositionEyes(1f).subtract(center) + val normalizedVec = diff.normalize() + + val side = EnumFacing.getFacingFromVector(normalizedVec.x.toFloat(), normalizedVec.y.toFloat(), normalizedVec.z.toFloat()) + val hitVecOffset = getHitVecOffset(side) + + Inventory.lastHitVec = getHitVec(containerTask.blockPos, side) + + connection.sendPacket(CPacketPlayerTryUseItemOnBlock(containerTask.blockPos, side, EnumHand.MAIN_HAND, hitVecOffset.x.toFloat(), hitVecOffset.y.toFloat(), hitVecOffset.z.toFloat())) + player.swingArm(EnumHand.MAIN_HAND) + } + } + + private fun SafeClientEvent.doBreaking(blockTask: BlockTask, updateOnly: Boolean) { + val block = world.getBlockState(blockTask.blockPos).block + + if (block == Blocks.AIR) { + Inventory.waitTicks = + breakDelay + blockTask.updateState(TaskState.BROKEN) + return + } + + if (block is BlockLiquid) { + updateLiquidTask(blockTask) + return + } + + if (!updateOnly + && swapOrMoveBestTool(blockTask) + && Inventory.packetLimiter.size < interactionLimit + ) { + mineBlock(blockTask) + } + } + + private fun SafeClientEvent.doBroken(blockTask: BlockTask) { + if (world.getBlockState(blockTask.blockPos).block != Blocks.AIR) { + blockTask.updateState(TaskState.BREAK) + return + } + + Statistics.totalBlocksBroken++ + + TaskManager.tasks.forEach { (_, task) -> + if (task.taskState == TaskState.BREAK) task.resetStuck() + } + + // Instant break exploit + if (blockTask.blockPos == Break.prePrimedPos) { + Break.primedPos = Break.prePrimedPos + Break.prePrimedPos = BlockPos.NULL_VECTOR + } + + Statistics.simpleMovingAverageBreaks.add(System.currentTimeMillis()) + + // Sound + if (fakeSounds) { + val soundType = blockTask.targetBlock.getSoundType(world.getBlockState(blockTask.blockPos), world, blockTask.blockPos, player) + world.playSound(player, blockTask.blockPos, soundType.breakSound, SoundCategory.BLOCKS, (soundType.getVolume() + 1.0f) / 2.0f, soundType.getPitch() * 0.8f) + } + + if (blockTask == containerTask) { + if (containerTask.collect) { + moveState = Pathfinder.MovementState.PICKUP + blockTask.updateState(TaskState.PICKUP) + } else { + blockTask.updateState(TaskState.DONE) + } + return + } + + if (blockTask.targetBlock == Blocks.AIR) { + blockTask.updateState(TaskState.DONE) + } else { + blockTask.updateState(TaskState.PLACE) + } + } + + private fun SafeClientEvent.doPlaced(blockTask: BlockTask) { + val currentState = world.getBlockState(blockTask.blockPos) + + when { + (blockTask.targetBlock == currentState.block || blockTask.isFiller) && !currentState.isReplaceable -> { + Statistics.totalBlocksPlaced++ + Break.prePrimedPos = blockTask.blockPos + Statistics.simpleMovingAveragePlaces.add(System.currentTimeMillis()) + + if ( + dynamicDelay && Place.extraPlaceDelay > 0) Place.extraPlaceDelay /= 2 + + if (blockTask == containerTask) { + if (containerTask.destroy) { + containerTask.updateState(TaskState.BREAK) + } else { + containerTask.updateState(TaskState.OPEN_CONTAINER) + } + } else { + blockTask.updateState(TaskState.DONE) + } + + TaskManager.tasks.values.filter { it.taskState == TaskState.PLACE }.forEach { it.resetStuck() } + + if (fakeSounds) { + val soundType = currentState.block.getSoundType(currentState, world, blockTask.blockPos, player) + world.playSound(player, blockTask.blockPos, soundType.placeSound, SoundCategory.BLOCKS, (soundType.getVolume() + 1.0f) / 2.0f, soundType.getPitch() * 0.8f) + } + } + blockTask.targetBlock == currentState.block && currentState.block == Blocks.AIR -> { + blockTask.updateState(TaskState.BREAK) + } + blockTask.targetBlock == Blocks.AIR && currentState.block != Blocks.AIR -> { + blockTask.updateState(TaskState.BREAK) + } + else -> { + blockTask.updateState(TaskState.PLACE) + } + } + } + + private fun SafeClientEvent.doBreak(blockTask: BlockTask, updateOnly: Boolean) { + val currentBlock = world.getBlockState(blockTask.blockPos).block + + if (ignoreBlocks.contains(currentBlock.registryName.toString()) + && !blockTask.isShulker() + && !BlueprintGenerator.isInsideBlueprintBuild(blockTask.blockPos) + || currentBlock in arrayOf(Blocks.PORTAL, Blocks.END_PORTAL, Blocks.END_PORTAL_FRAME, Blocks.BEDROCK) + ) { + blockTask.updateState(TaskState.DONE) + return + } + + // ToDo: Fix this +// if (blockTask.targetBlock == fillerMat +// && world.getBlockState(blockTask.blockPos.up()).block == material +// || (!world.isPlaceable(blockTask.blockPos) +// && world.getCollisionBox(blockTask.blockPos) != null) +// ) { +// blockTask.updateState(TaskState.DONE) +// return +// } + + when (blockTask.targetBlock) { + fillerMat -> { + if (world.getBlockState(blockTask.blockPos.up()).block == material || + (!world.isPlaceable(blockTask.blockPos) && + world.getCollisionBox(blockTask.blockPos) != null)) { + blockTask.updateState(TaskState.DONE) + return + } + } + material -> { + if (currentBlock == material) { + blockTask.updateState(TaskState.DONE) + return + } + } + } + + when (currentBlock) { + Blocks.AIR -> { + if (blockTask.targetBlock == Blocks.AIR) { + blockTask.updateState(TaskState.BROKEN) + return + } else { + blockTask.updateState(TaskState.PLACE) + return + } + } + is BlockLiquid -> { + updateLiquidTask(blockTask) + return + } + } + + if (!updateOnly + && player.onGround + && swapOrMoveBestTool(blockTask) + && !handleLiquid(blockTask) + && Inventory.packetLimiter.size < interactionLimit + ) { + mineBlock(blockTask) + } + } + + private fun SafeClientEvent.doPlace(blockTask: BlockTask, updateOnly: Boolean) { + val currentBlock = world.getBlockState(blockTask.blockPos).block + + if (blockTask.taskState == TaskState.LIQUID + && world.getBlockState(blockTask.blockPos).block !is BlockLiquid + ) { + blockTask.updateState(TaskState.DONE) + return + } + + when (blockTask.targetBlock) { + material -> { + if (currentBlock == material) { + blockTask.updateState(TaskState.PLACED) + return + } + } + fillerMat -> { + if (currentBlock == fillerMat) { + blockTask.updateState(TaskState.PLACED) + return + } else if (currentBlock != fillerMat + && mode == Trombone.Structure.HIGHWAY + && world.getBlockState(blockTask.blockPos.up()).block == material + ) { + blockTask.updateState(TaskState.DONE) + return + } + } + Blocks.AIR -> { + if (world.getBlockState(blockTask.blockPos).block !is BlockLiquid) { + if (currentBlock != Blocks.AIR) { + blockTask.updateState(TaskState.BREAK) + } else { + blockTask.updateState(TaskState.BROKEN) + } + return + } + } + } + + if (updateOnly) return + + if (!world.isPlaceable(blockTask.blockPos)) { + if (debugLevel == IO.DebugLevel.VERBOSE) { + if (!anonymizeStats) { + MessageSendHelper.sendChatMessage("${module.chatName} Invalid place position @(${blockTask.blockPos.asString()}) Removing task") + } else { + MessageSendHelper.sendChatMessage("${module.chatName} Invalid place position. Removing task") + } + } + + if (blockTask == containerTask) { + MessageSendHelper.sendChatMessage("${module.chatName} Failed container task. Trying to break block.") + containerTask.updateState(TaskState.BREAK) + } else { + TaskManager.tasks.remove(blockTask.blockPos) + } + + return + } + + if (!swapOrMoveBlock(blockTask)) { + blockTask.onStuck() + return + } + + placeBlock(blockTask) + } + + private fun SafeClientEvent.doImpossiblePlace() { + if (shouldBridge() + && moveState != Pathfinder.MovementState.RESTOCK + && player.positionVector.distanceTo(Pathfinder.currentBlockPos.toVec3dCenter()) < 1 + ) { + moveState = Pathfinder.MovementState.BRIDGE + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/task/TaskManager.kt b/src/main/kotlin/trombone/task/TaskManager.kt new file mode 100644 index 0000000..e4f72af --- /dev/null +++ b/src/main/kotlin/trombone/task/TaskManager.kt @@ -0,0 +1,332 @@ +package trombone.task + +import HighwayTools.anonymizeStats +import HighwayTools.debugLevel +import HighwayTools.dynamicDelay +import HighwayTools.food +import HighwayTools.ignoreBlocks +import HighwayTools.manageFood +import HighwayTools.material +import HighwayTools.maxReach +import HighwayTools.multiBuilding +import HighwayTools.saveFood +import HighwayTools.saveTools +import HighwayTools.storageManagement +import HighwayTools.width +import com.lambda.client.event.SafeClientEvent +import com.lambda.client.manager.managers.PlayerInventoryManager +import com.lambda.client.util.items.countItem +import com.lambda.client.util.items.inventorySlots +import com.lambda.client.util.items.item +import com.lambda.client.util.math.CoordinateConverter.asString +import com.lambda.client.util.math.VectorUtils.distanceTo +import com.lambda.client.util.math.VectorUtils.multiply +import com.lambda.client.util.math.VectorUtils.toVec3dCenter +import com.lambda.client.util.text.MessageSendHelper +import com.lambda.client.util.world.isPlaceable +import com.lambda.client.util.world.isReplaceable +import net.minecraft.block.BlockLiquid +import net.minecraft.block.state.IBlockState +import net.minecraft.init.Blocks +import net.minecraft.init.Items +import net.minecraft.item.ItemFood +import net.minecraft.item.ItemPickaxe +import net.minecraft.util.math.AxisAlignedBB +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Vec3d +import trombone.blueprint.BlueprintGenerator.blueprint +import trombone.blueprint.BlueprintGenerator.generateBluePrint +import trombone.blueprint.BlueprintGenerator.isInsideBlueprintBuild +import trombone.IO.DebugLevel +import trombone.Pathfinder.MovementState +import trombone.Pathfinder.currentBlockPos +import trombone.Pathfinder.moveState +import trombone.Pathfinder.startingBlockPos +import trombone.Pathfinder.startingDirection +import trombone.Trombone.module +import trombone.blueprint.BlueprintTask +import trombone.handler.Container.containerTask +import trombone.handler.Container.grindCycles +import trombone.handler.Container.handleRestock +import trombone.handler.Inventory.waitTicks +import trombone.interaction.Place.extraPlaceDelay +import trombone.task.TaskExecutor.doTask +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentSkipListSet + +object TaskManager { + val tasks = ConcurrentHashMap() + val sortedTasks = ConcurrentSkipListSet(blockTaskComparator()) + var lastTask: BlockTask? = null + + fun SafeClientEvent.populateTasks() { + generateBluePrint() + + /* Generate tasks based on the blueprint */ + blueprint.forEach { (pos, blueprintTask) -> + generateTask(pos, blueprintTask) + } + + /* Remove old tasks */ + tasks.filter { + it.value.taskState == TaskState.DONE + && currentBlockPos.distanceTo(it.key) > maxReach + 2 + }.forEach { + if (it.value.toRemove) { + if (System.currentTimeMillis() - it.value.timestamp > 1000L) tasks.remove(it.key) + } else { + it.value.toRemove = true + it.value.timestamp = System.currentTimeMillis() + } + } + } + + private fun SafeClientEvent.generateTask(blockPos: BlockPos, blueprintTask: BlueprintTask) { + val currentState = world.getBlockState(blockPos) + val eyePos = player.getPositionEyes(1f) + + when { + /* start padding */ + startPadding(blockPos) -> { /* Ignore task */ } + + /* out of reach */ + eyePos.distanceTo(blockPos.toVec3dCenter()) >= maxReach + 1 -> { /* Ignore task */ } + + /* do not override container task */ + containerTask.blockPos == blockPos -> { /* Ignore task */ } + + /* ignored blocks */ + shouldBeIgnored(blockPos, currentState) -> { + val blockTask = BlockTask(blockPos, TaskState.DONE, currentState.block) + addTask(blockTask, blueprintTask) + } + + /* is in desired state */ + currentState.block == blueprintTask.targetBlock -> { + val blockTask = BlockTask(blockPos, TaskState.DONE, currentState.block) + addTask(blockTask, blueprintTask) + } + + /* is liquid */ + currentState.block is BlockLiquid -> { + val blockTask = BlockTask(blockPos, TaskState.LIQUID, blueprintTask.targetBlock) + blockTask.updateTask(this) + + if (blockTask.sequence.isNotEmpty()) { + addTask(blockTask, blueprintTask) + } + } + + /* to place */ + currentState.isReplaceable && blueprintTask.targetBlock != Blocks.AIR -> { + /* support not needed */ + if (blueprintTask.isSupport && world.getBlockState(blockPos.up()).block == material) { + val blockTask = BlockTask(blockPos, TaskState.DONE, currentState.block) + addTask(blockTask, blueprintTask) + return + } + + /* is blocked by entity */ + if (!world.checkNoEntityCollision(AxisAlignedBB(blockPos), null)) { + val blockTask = BlockTask(blockPos, TaskState.DONE, currentState.block) + addTask(blockTask, blueprintTask) + return + } + + val blockTask = BlockTask(blockPos, TaskState.PLACE, blueprintTask.targetBlock) + blockTask.updateTask(this) + + if (blockTask.sequence.isNotEmpty()) { + addTask(blockTask, blueprintTask) + } else { + blockTask.updateState(TaskState.IMPOSSIBLE_PLACE) + addTask(blockTask, blueprintTask) + } + } + + /* To break */ + else -> { + /* Is already filled */ + if (blueprintTask.isFiller) { + val blockTask = BlockTask(blockPos, TaskState.DONE, currentState.block) + addTask(blockTask, blueprintTask) + return + } + + val blockTask = BlockTask(blockPos, TaskState.BREAK, blueprintTask.targetBlock) + blockTask.updateTask(this) + + if (blockTask.eyeDistance < maxReach) { + addTask(blockTask, blueprintTask) + } + } + } + } + + fun SafeClientEvent.runTasks() { + when { + /* Finish the container task first */ + containerTask.taskState != TaskState.DONE -> { + containerTask.updateTask(this) + if (containerTask.stuckTicks > containerTask.taskState.stuckTimeout) { + if (containerTask.taskState == TaskState.PICKUP) moveState = MovementState.RUNNING + + MessageSendHelper.sendWarningMessage("${module.chatName} Failed container action ${containerTask.taskState.name} with ${containerTask.item.registryName}@(${containerTask.blockPos.asString()}) stuck for ${containerTask.stuckTicks} ticks") + containerTask.updateState(TaskState.DONE) + } else { + tasks.values.forEach { + doTask(it, updateOnly = true) + } + doTask(containerTask) + } + } + + /* Check tools */ + storageManagement + && player.inventorySlots.countItem() <= saveTools -> { + // TODO: ItemPickaxe support + handleRestock(Items.DIAMOND_PICKAXE) + } + + /* Fulfill basic needs */ + storageManagement + && manageFood + && player.inventorySlots.countItem() <= saveFood -> { + // TODO: ItemFood support + handleRestock(food) + } + + /* Restock obsidian if needed */ + storageManagement && grindCycles > 0 && material == Blocks.OBSIDIAN -> { + handleRestock(material.item) + } + + /* Actually run the tasks */ + else -> { + waitTicks-- + + /* Only update tasks to check for changed circumstances */ + tasks.values.forEach { + doTask(it, updateOnly = true) + + it.updateTask(this) + if (multiBuilding) it.shuffle() + } + + sortedTasks.clear() + sortedTasks.addAll(tasks.values) + + sortedTasks.forEach taskExecution@{ task -> + if (!checkStuckTimeout(task)) return + if (task.taskState != TaskState.DONE && waitTicks > 0) return + + doTask(task) + when (task.taskState) { + TaskState.DONE, TaskState.BROKEN, TaskState.PLACED -> return@taskExecution + else -> return + } + } + } + } + } + + fun SafeClientEvent.addTask(blockTask: BlockTask, blueprintTask: BlueprintTask) { + blockTask.updateTask(this) + blockTask.isFiller = blueprintTask.isFiller + blockTask.isSupport = blueprintTask.isSupport + + tasks[blockTask.blockPos]?.let { + if (it.stuckTicks > it.taskState.stuckTimeout + || blockTask.taskState == TaskState.LIQUID + || (it.taskState != blockTask.taskState + && (it.taskState == TaskState.DONE + || it.taskState == TaskState.IMPOSSIBLE_PLACE + || (it.taskState == TaskState.PLACE + && !world.isPlaceable(it.blockPos) + )))) { + tasks[blockTask.blockPos] = blockTask + } + } ?: run { + tasks[blockTask.blockPos] = blockTask + } + } + + private fun checkStuckTimeout(blockTask: BlockTask): Boolean { + val timeout = blockTask.taskState.stuckTimeout + + if (blockTask.stuckTicks < timeout) return true + + if (blockTask.taskState == TaskState.DONE) return true + + if (blockTask.taskState == TaskState.PENDING_BREAK) { + blockTask.updateState(TaskState.BREAK) + return false + } + + if (blockTask.taskState == TaskState.PENDING_PLACE) { + blockTask.updateState(TaskState.PLACE) + return false + } + + if (debugLevel != DebugLevel.OFF) { + if (!anonymizeStats) { + MessageSendHelper.sendChatMessage("${module.chatName} Stuck while ${blockTask.taskState}@(${blockTask.blockPos.asString()}) for more than $timeout ticks (${blockTask.stuckTicks}), refreshing data.") + } else { + MessageSendHelper.sendChatMessage("${module.chatName} Stuck while ${blockTask.taskState} for more than $timeout ticks (${blockTask.stuckTicks}), refreshing data.") + } + } + + when (blockTask.taskState) { + TaskState.PLACE -> { + if (dynamicDelay && extraPlaceDelay < 10 && moveState != MovementState.BRIDGE) extraPlaceDelay += 1 + } + TaskState.PICKUP -> { + MessageSendHelper.sendChatMessage("${module.chatName} Can't pickup ${containerTask.item.registryName}@(${containerTask.blockPos.asString()})") + blockTask.updateState(TaskState.DONE) + moveState = MovementState.RUNNING + } + else -> { + blockTask.updateState(TaskState.DONE) + } + } + return false + } + + private fun startPadding(c: BlockPos) = isBehindPos(startingBlockPos.add(startingDirection.directionVec), c) + + fun isBehindPos(origin: BlockPos, check: BlockPos): Boolean { + val a = origin.add(startingDirection.counterClockwise(2).directionVec.multiply(width)) + val b = origin.add(startingDirection.clockwise(2).directionVec.multiply(width)) + + return ((b.x - a.x) * (check.z - a.z) - (b.z - a.z) * (check.x - a.x)) > 0 + } + + private fun shouldBeIgnored(blockPos: BlockPos, currentState: IBlockState) = + ignoreBlocks.contains(currentState.block.registryName.toString()) + && !isInsideBlueprintBuild(blockPos) + && currentBlockPos.add(startingDirection.directionVec) != blockPos + + fun clearTasks() { + tasks.clear() + sortedTasks.clear() + containerTask.updateState(TaskState.DONE) + lastTask = null + grindCycles = 0 + } + + private fun blockTaskComparator() = compareBy { + it.taskState.ordinal + }.thenBy { + it.stuckTicks + }.thenBy { + if (it.isLiquidSource) 0 else 1 + }.thenBy { + if (moveState == MovementState.BRIDGE) { + if (it.sequence.isEmpty()) 69 else it.sequence.size + } else { + if (multiBuilding) it.shuffle else it.startDistance + } + }.thenBy { + it.eyeDistance + } +} \ No newline at end of file diff --git a/src/main/kotlin/trombone/task/TaskState.kt b/src/main/kotlin/trombone/task/TaskState.kt new file mode 100644 index 0000000..f49884f --- /dev/null +++ b/src/main/kotlin/trombone/task/TaskState.kt @@ -0,0 +1,19 @@ +package trombone.task + +import com.lambda.client.util.color.ColorHolder + +enum class TaskState(val stuckThreshold: Int, val stuckTimeout: Int, val color: ColorHolder) { + BROKEN(1000, 1000, ColorHolder(111, 0, 0)), + PLACED(1000, 1000, ColorHolder(53, 222, 66)), + LIQUID(100, 100, ColorHolder(114, 27, 255)), + PICKUP(500, 500, ColorHolder(252, 3, 207)), + RESTOCK(500, 500, ColorHolder(252, 3, 207)), + OPEN_CONTAINER(500, 500, ColorHolder(252, 3, 207)), + BREAKING(100, 100, ColorHolder(240, 222, 60)), + BREAK(20, 20, ColorHolder(222, 0, 0)), + PLACE(20, 20, ColorHolder(35, 188, 254)), + PENDING_BREAK(100, 100, ColorHolder(0, 0, 0)), + PENDING_PLACE(100, 100, ColorHolder(0, 0, 0)), + IMPOSSIBLE_PLACE(100, 100, ColorHolder(16, 74, 94)), + DONE(69420, 0x22, ColorHolder(50, 50, 50)) +} \ No newline at end of file diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info deleted file mode 100644 index e406b6c..0000000 --- a/src/main/resources/mcmod.info +++ /dev/null @@ -1,19 +0,0 @@ -[ - { - "modid": "highwaytools", - "name": "HighwayTools", - "description": "Build highways with ease", - "version": "${version}", - "mcversion": "${mcversion}", - "url": "lambda-client.com", - "updateUrl": "", - "authorList": [ - "Constructor" - ], - "credits": "HWU, MEG", - "logoFile": "", - "screenshots": [], - "dependencies": [], - "test": "" - } -] diff --git a/src/main/resources/plugin_info.json b/src/main/resources/plugin_info.json index 838629c..b0b0266 100644 --- a/src/main/resources/plugin_info.json +++ b/src/main/resources/plugin_info.json @@ -1,10 +1,9 @@ { "name": "HighwayTools", - "version": "9.9", - "authors": [ - "Constructor" - ], + "version": "${version}", + "authors": [ "Constructor" ], "description": "Build highways with ease", - "main_class": "HighwayToolsPlugin", - "min_api_version": "2.04.01" + "url": "https://github.com/lambda-plugins/HighwayTools", + "min_api_version": "3.2", + "main_class": "HighwayToolsPlugin" } \ No newline at end of file