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