├── settings.gradle ├── lib ├── lambda-3.3.0-api.jar └── lambda-3.3.0-api-source.jar ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ ├── kotlin │ ├── trombone │ │ ├── blueprint │ │ │ ├── BlueprintTask.kt │ │ │ └── BlueprintGenerator.kt │ │ ├── task │ │ │ ├── TaskState.kt │ │ │ ├── BlockTask.kt │ │ │ ├── TaskManager.kt │ │ │ └── TaskExecutor.kt │ │ ├── BaritoneHelper.kt │ │ ├── Process.kt │ │ ├── Trombone.kt │ │ ├── handler │ │ │ ├── Liquid.kt │ │ │ ├── Packet.kt │ │ │ ├── Inventory.kt │ │ │ └── Container.kt │ │ ├── interaction │ │ │ ├── Place.kt │ │ │ └── Break.kt │ │ ├── Renderer.kt │ │ ├── Pathfinder.kt │ │ ├── IO.kt │ │ └── Statistics.kt │ ├── HighwayToolsPlugin.kt │ ├── HighwayToolsHud.kt │ ├── HighwayToolsCommand.kt │ └── HighwayTools.kt │ └── resources │ └── plugin_info.json ├── gradle.properties ├── setupWorkspace.sh ├── .github └── workflows │ └── nightly_build.yml ├── gradlew.bat ├── README.md ├── .gitignore └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'HighwayTools' -------------------------------------------------------------------------------- /lib/lambda-3.3.0-api.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambda-plugins/HighwayTools/HEAD/lib/lambda-3.3.0-api.jar -------------------------------------------------------------------------------- /lib/lambda-3.3.0-api-source.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambda-plugins/HighwayTools/HEAD/lib/lambda-3.3.0-api-source.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lambda-plugins/HighwayTools/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/kotlin/trombone/blueprint/BlueprintTask.kt: -------------------------------------------------------------------------------- 1 | package trombone.blueprint 2 | 3 | import net.minecraft.block.Block 4 | 5 | data class BlueprintTask(val targetBlock: Block, val isFiller: Boolean = false, val isSupport: Boolean = false) 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx3G 2 | org.gradle.parallel=true 3 | 4 | modGroup=com.lambda 5 | modVersion=10.3.1 6 | 7 | minecraftVersion=1.12.2 8 | forgeVersion=14.23.5.2860 9 | mappingsChannel=stable 10 | mappingsVersion=39-1.12 11 | 12 | kotlinVersion=1.8.10 13 | kotlinxCoroutinesVersion=1.6.4 -------------------------------------------------------------------------------- /src/main/resources/plugin_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HighwayTools", 3 | "version": "${version}", 4 | "authors": [ "Constructor" ], 5 | "description": "Build highways with ease", 6 | "url": "https://github.com/lambda-plugins/HighwayTools", 7 | "min_api_version": "3.2", 8 | "main_class": "HighwayToolsPlugin" 9 | } -------------------------------------------------------------------------------- /src/main/kotlin/HighwayToolsPlugin.kt: -------------------------------------------------------------------------------- 1 | import com.lambda.client.plugin.api.Plugin 2 | 3 | internal object HighwayToolsPlugin : Plugin() { 4 | 5 | override fun onLoad() { 6 | modules.add(HighwayTools) 7 | commands.add(HighwayToolsCommand) 8 | hudElements.add(HighwayToolsHud) 9 | } 10 | 11 | override fun onUnload() { 12 | modules.forEach { 13 | it.disable() 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /setupWorkspace.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Used to setup workspace and fix building on unix / Git BASH 4 | # 5 | # Usage: "./setupWorkspace.sh" 6 | 7 | # 8 | 9 | # To allow use from outside the lambda directory 10 | cd "$(dirname "$0")" || exit 11 | 12 | echo "[$(date +"%H:%M:%S")] Running gradlew classes without daemon..." 13 | ./gradlew --no-daemon classes || { 14 | echo "[$(date +"%H:%M:%S")] ERROR: Running gradlew build failed! Run './gradlew --no-daemon classes' manually" 15 | exit 1 16 | } 17 | 18 | cat logo_ascii.txt 2>/dev/null 19 | echo "==========================================================================" 20 | echo "" 21 | echo "[$(date +"%H:%M:%S")] Build succeeded! All checks passed, you can build normally now! Welcome to Lambda." 22 | echo "" 23 | echo "==========================================================================" 24 | -------------------------------------------------------------------------------- /src/main/kotlin/trombone/task/TaskState.kt: -------------------------------------------------------------------------------- 1 | package trombone.task 2 | 3 | import com.lambda.client.util.color.ColorHolder 4 | 5 | enum class TaskState(val stuckThreshold: Int, val stuckTimeout: Int, val color: ColorHolder) { 6 | BROKEN(1000, 1000, ColorHolder(111, 0, 0)), 7 | PLACED(1000, 1000, ColorHolder(53, 222, 66)), 8 | LIQUID(100, 100, ColorHolder(114, 27, 255)), 9 | PICKUP(500, 500, ColorHolder(252, 3, 207)), 10 | RESTOCK(500, 500, ColorHolder(252, 3, 207)), 11 | OPEN_CONTAINER(500, 500, ColorHolder(252, 3, 207)), 12 | BREAKING(100, 100, ColorHolder(240, 222, 60)), 13 | BREAK(20, 20, ColorHolder(222, 0, 0)), 14 | PLACE(20, 20, ColorHolder(35, 188, 254)), 15 | PENDING_BREAK(100, 100, ColorHolder(0, 0, 0)), 16 | PENDING_PLACE(100, 100, ColorHolder(0, 0, 0)), 17 | IMPOSSIBLE_PLACE(100, 100, ColorHolder(16, 74, 94)), 18 | DONE(69420, 0x22, ColorHolder(50, 50, 50)) 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/trombone/BaritoneHelper.kt: -------------------------------------------------------------------------------- 1 | package trombone 2 | 3 | import HighwayTools.goalRender 4 | import com.lambda.client.util.BaritoneUtils 5 | 6 | object BaritoneHelper { 7 | private var baritoneSettingAllowPlace = false 8 | private var baritoneSettingAllowBreak = false 9 | private var baritoneSettingRenderGoal = false 10 | private var baritoneSettingAllowInventory = false 11 | 12 | fun setupBaritone() { 13 | baritoneSettingAllowPlace = BaritoneUtils.settings?.allowPlace?.value ?: true 14 | baritoneSettingAllowBreak = BaritoneUtils.settings?.allowBreak?.value ?: true 15 | baritoneSettingRenderGoal = BaritoneUtils.settings?.renderGoal?.value ?: true 16 | baritoneSettingAllowInventory = BaritoneUtils.settings?.allowInventory?.value ?: true 17 | BaritoneUtils.settings?.allowPlace?.value = false 18 | BaritoneUtils.settings?.allowBreak?.value = false 19 | BaritoneUtils.settings?.renderGoal?.value = goalRender 20 | BaritoneUtils.settings?.allowInventory?.value = false 21 | } 22 | 23 | fun resetBaritone() { 24 | BaritoneUtils.settings?.allowPlace?.value = baritoneSettingAllowPlace 25 | BaritoneUtils.settings?.allowBreak?.value = baritoneSettingAllowBreak 26 | BaritoneUtils.settings?.renderGoal?.value = baritoneSettingRenderGoal 27 | BaritoneUtils.settings?.allowInventory?.value = baritoneSettingAllowInventory 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/kotlin/trombone/Process.kt: -------------------------------------------------------------------------------- 1 | package trombone 2 | 3 | import HighwayTools 4 | import HighwayTools.anonymizeStats 5 | import baritone.api.pathing.goals.GoalNear 6 | import baritone.api.process.IBaritoneProcess 7 | import baritone.api.process.PathingCommand 8 | import baritone.api.process.PathingCommandType 9 | import com.lambda.client.util.math.CoordinateConverter.asString 10 | import trombone.Pathfinder.goal 11 | import trombone.task.TaskManager.lastTask 12 | 13 | /** 14 | * @author Avanatiker 15 | * @since 26/08/20 16 | */ 17 | object Process : IBaritoneProcess { 18 | 19 | override fun isTemporary(): Boolean { 20 | return true 21 | } 22 | 23 | override fun priority(): Double { 24 | return 2.0 25 | } 26 | 27 | override fun onLostControl() {} 28 | 29 | override fun displayName0(): String { 30 | val processName = if (!anonymizeStats) { 31 | lastTask?.toString() 32 | ?: goal?.asString() 33 | ?: "Thinking" 34 | } else { 35 | "Running" 36 | } 37 | 38 | return "Trombone: $processName" 39 | } 40 | 41 | override fun isActive(): Boolean { 42 | return HighwayTools.isActive() 43 | } 44 | 45 | override fun onTick(p0: Boolean, p1: Boolean): PathingCommand { 46 | return goal?.let { 47 | PathingCommand(GoalNear(it, 0), PathingCommandType.SET_GOAL_AND_PATH) 48 | } ?: PathingCommand(null, PathingCommandType.REQUEST_PAUSE) 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/kotlin/trombone/Trombone.kt: -------------------------------------------------------------------------------- 1 | package trombone 2 | 3 | import HighwayTools 4 | import HighwayTools.info 5 | import com.lambda.client.event.SafeClientEvent 6 | import trombone.BaritoneHelper.resetBaritone 7 | import trombone.BaritoneHelper.setupBaritone 8 | import trombone.IO.pauseCheck 9 | import trombone.IO.printDisable 10 | import trombone.IO.printEnable 11 | import trombone.Pathfinder.clearProcess 12 | import trombone.Pathfinder.setupPathing 13 | import trombone.Pathfinder.updateProcess 14 | import trombone.Statistics.updateStats 15 | import trombone.Statistics.updateTotalDistance 16 | import trombone.handler.Inventory.updateRotation 17 | import trombone.task.TaskManager.clearTasks 18 | import trombone.task.TaskManager.populateTasks 19 | import trombone.task.TaskManager.runTasks 20 | 21 | object Trombone { 22 | val module = HighwayTools 23 | var active = false 24 | 25 | enum class Structure { 26 | HIGHWAY, FLAT, TUNNEL 27 | } 28 | 29 | fun SafeClientEvent.onEnable() { 30 | clearTasks() 31 | setupPathing() 32 | setupBaritone() 33 | if (info) printEnable() 34 | } 35 | 36 | fun onDisable() { 37 | resetBaritone() 38 | printDisable() 39 | clearProcess() 40 | clearTasks() 41 | updateTotalDistance() 42 | } 43 | 44 | fun SafeClientEvent.tick() { 45 | populateTasks() 46 | updateStats() 47 | 48 | if (pauseCheck()) return 49 | 50 | updateProcess() 51 | runTasks() 52 | updateRotation() 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/kotlin/HighwayToolsHud.kt: -------------------------------------------------------------------------------- 1 | import com.lambda.client.event.SafeClientEvent 2 | import com.lambda.client.plugin.api.PluginLabelHud 3 | import trombone.Statistics.gatherStatistics 4 | import trombone.Statistics.resetStats 5 | 6 | internal object HighwayToolsHud : PluginLabelHud( 7 | name = "HighwayToolsHud", 8 | category = Category.MISC, 9 | description = "Hud for HighwayTools module", 10 | pluginMain = HighwayToolsPlugin 11 | ) { 12 | val simpleMovingAverageRange by setting("Moving Average", 60, 5..600, 5, description = "Sets the timeframe of the average in seconds") 13 | val showSession by setting("Show Session", true, description = "Toggles the Session section in HUD") 14 | val showLifeTime by setting("Show Lifetime", true, description = "Toggles the Lifetime section in HUD") 15 | val showPerformance by setting("Show Performance", true, description = "Toggles the Performance section in HUD") 16 | val showEnvironment by setting("Show Environment", true, description = "Toggles the Environment section in HUD") 17 | val showTask by setting("Show Task", true, description = "Toggles the Task section in HUD") 18 | val showEstimations by setting("Show Estimations", true, description = "Toggles the Estimations section in HUD") 19 | val showQueue by setting("Show Queue", false, description = "Shows task queue in HUD") 20 | private val resetStats = setting("Reset Stats", false, description = "Resets the stats") 21 | 22 | init { 23 | resetStats.consumers.add { _, it -> 24 | if (it) resetStats() 25 | false 26 | } 27 | } 28 | 29 | override fun SafeClientEvent.updateText() { 30 | gatherStatistics(displayText) 31 | } 32 | } -------------------------------------------------------------------------------- /.github/workflows/nightly_build.yml: -------------------------------------------------------------------------------- 1 | name: Nightly Build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on : ubuntu-latest 11 | env : 12 | BUILD : ${{ github.run_number }} 13 | WEBHOOK : ${{ secrets.HIGHWAYTOOLS_NIGHTLY }} 14 | 15 | steps: 16 | - name: Check out repository 17 | uses: actions/checkout@v2 18 | 19 | - name: Set up JDK 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 1.8 23 | 24 | - name: Change wrapper permissions 25 | run: chmod +x ./gradlew 26 | 27 | - name: Gradle cache 28 | uses: actions/cache@v2 29 | with: 30 | path: | 31 | ~/.gradle/caches 32 | ~/.gradle/wrapper 33 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 34 | restore-keys: | 35 | ${{ runner.os }}-gradle- 36 | 37 | - name: Gradle build 38 | run: ./gradlew --build-cache build 39 | 40 | - name: Archive artifact 41 | uses: actions/upload-artifact@v2 42 | with: 43 | name: HighwayTools-${{ github.sha }} 44 | path: build/libs/ 45 | 46 | - name: Get branch name 47 | uses: nelonoel/branch-name@v1.0.1 48 | 49 | - name: Send Discord build message 50 | if: github.ref == 'refs/heads/master' 51 | run: | 52 | COMMITMESSAGE=`git log --pretty=format:'- \`%h\` %s' -5 --reverse` && 53 | (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\"}") -------------------------------------------------------------------------------- /src/main/kotlin/trombone/handler/Liquid.kt: -------------------------------------------------------------------------------- 1 | package trombone.handler 2 | 3 | import HighwayTools.debugLevel 4 | import HighwayTools.fillerMat 5 | import HighwayTools.illegalPlacements 6 | import HighwayTools.maxReach 7 | import HighwayTools.placementSearch 8 | import com.lambda.client.LambdaMod 9 | import com.lambda.client.event.SafeClientEvent 10 | import com.lambda.client.util.math.CoordinateConverter.asString 11 | import com.lambda.client.util.math.VectorUtils.distanceTo 12 | import com.lambda.client.util.world.getNeighbourSequence 13 | import net.minecraft.block.BlockLiquid 14 | import net.minecraft.util.EnumFacing 15 | import trombone.IO 16 | import trombone.blueprint.BlueprintTask 17 | import trombone.task.BlockTask 18 | import trombone.task.TaskManager.addTask 19 | import trombone.task.TaskManager.tasks 20 | import trombone.task.TaskState 21 | 22 | object Liquid { 23 | fun SafeClientEvent.handleLiquid(blockTask: BlockTask): Boolean { 24 | var foundLiquid = false 25 | 26 | for (side in EnumFacing.values()) { 27 | if (side == EnumFacing.DOWN) continue 28 | val neighbourPos = blockTask.blockPos.offset(side) 29 | 30 | if (world.getBlockState(neighbourPos).block !is BlockLiquid) continue 31 | 32 | if (player.distanceTo(neighbourPos) > maxReach 33 | || getNeighbourSequence(neighbourPos, placementSearch, maxReach, !illegalPlacements).isEmpty() 34 | ) { 35 | if (debugLevel == IO.DebugLevel.VERBOSE) { 36 | LambdaMod.LOG.info("[Trombone] Skipping liquid block at ${neighbourPos.asString()} due to distance") 37 | } 38 | blockTask.updateState(TaskState.DONE) 39 | return true 40 | } 41 | 42 | foundLiquid = true 43 | 44 | tasks[neighbourPos]?.let { 45 | updateLiquidTask(it) 46 | } ?: run { 47 | val newTask = BlockTask(neighbourPos, TaskState.LIQUID, fillerMat) 48 | val blueprintTask = BlueprintTask(fillerMat, isFiller = true, isSupport = false) 49 | 50 | addTask(newTask, blueprintTask) 51 | } 52 | } 53 | 54 | return foundLiquid 55 | } 56 | 57 | fun SafeClientEvent.updateLiquidTask(blockTask: BlockTask) { 58 | blockTask.updateState(TaskState.LIQUID) 59 | blockTask.updateTask(this) 60 | } 61 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/main/kotlin/trombone/handler/Packet.kt: -------------------------------------------------------------------------------- 1 | package trombone.handler 2 | 3 | import com.lambda.client.LambdaMod 4 | import com.lambda.client.event.SafeClientEvent 5 | import com.lambda.client.util.items.hotbarSlots 6 | import net.minecraft.init.Blocks 7 | import net.minecraft.network.Packet 8 | import net.minecraft.network.play.server.SPacketBlockChange 9 | import net.minecraft.network.play.server.SPacketOpenWindow 10 | import net.minecraft.network.play.server.SPacketPlayerPosLook 11 | import net.minecraft.network.play.server.SPacketSetSlot 12 | import net.minecraft.network.play.server.SPacketWindowItems 13 | import trombone.blueprint.BlueprintGenerator.isInsideBlueprint 14 | import trombone.Pathfinder.rubberbandTimer 15 | import trombone.Statistics.durabilityUsages 16 | import trombone.handler.Container.containerTask 17 | import trombone.task.TaskManager.tasks 18 | import trombone.task.TaskState 19 | 20 | object Packet { 21 | fun SafeClientEvent.handlePacket(packet: Packet<*>) { 22 | when (packet) { 23 | is SPacketBlockChange -> { 24 | val pos = packet.blockPosition 25 | if (!isInsideBlueprint(pos)) return 26 | 27 | val prev = world.getBlockState(pos).block 28 | val new = packet.blockState.block 29 | 30 | if (prev != new) { 31 | val task = if (pos == containerTask.blockPos) { 32 | containerTask 33 | } else { 34 | tasks[pos] ?: return 35 | } 36 | 37 | when (task.taskState) { 38 | TaskState.PENDING_BREAK, TaskState.BREAKING -> { 39 | if (new == Blocks.AIR) { 40 | task.updateState(TaskState.BROKEN) 41 | } 42 | } 43 | TaskState.PENDING_PLACE -> { 44 | if (new != Blocks.AIR 45 | && (task.targetBlock == new || task.isFiller) 46 | ) { 47 | task.updateState(TaskState.PLACED) 48 | } 49 | } 50 | else -> { 51 | // Ignored 52 | } 53 | } 54 | } 55 | } 56 | is SPacketPlayerPosLook -> { 57 | rubberbandTimer.reset() 58 | } 59 | is SPacketOpenWindow -> { 60 | if (containerTask.taskState != TaskState.DONE && 61 | packet.guiId == "minecraft:shulker_box" && containerTask.isShulker() || 62 | packet.guiId == "minecraft:container" && !containerTask.isShulker()) { 63 | containerTask.isOpen = true 64 | } 65 | } 66 | is SPacketWindowItems -> { 67 | if (containerTask.isOpen) containerTask.isLoaded = true 68 | } 69 | is SPacketSetSlot -> { 70 | val currentStack = player.hotbarSlots[player.inventory.currentItem].stack 71 | if (packet.slot == player.inventory.currentItem + 36 72 | && packet.stack.item == currentStack.item 73 | && packet.stack.itemDamage > currentStack.itemDamage 74 | ) { 75 | durabilityUsages += packet.stack.itemDamage - currentStack.itemDamage 76 | } 77 | } 78 | else -> { /* Ignored */ } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/kotlin/trombone/task/BlockTask.kt: -------------------------------------------------------------------------------- 1 | package trombone.task 2 | 3 | import HighwayTools.illegalPlacements 4 | import HighwayTools.maxReach 5 | import HighwayTools.placementSearch 6 | import com.lambda.client.event.SafeClientEvent 7 | import com.lambda.client.util.math.CoordinateConverter.asString 8 | import com.lambda.client.util.math.VectorUtils.toVec3dCenter 9 | import com.lambda.client.util.world.PlaceInfo 10 | import com.lambda.client.util.world.getNeighbourSequence 11 | import net.minecraft.block.Block 12 | import net.minecraft.block.BlockLiquid 13 | import net.minecraft.block.BlockShulkerBox 14 | import net.minecraft.init.Items 15 | import net.minecraft.item.Item 16 | import net.minecraft.item.ItemStack 17 | import net.minecraft.util.math.AxisAlignedBB 18 | import net.minecraft.util.math.BlockPos 19 | import trombone.Pathfinder.startingBlockPos 20 | import kotlin.random.Random 21 | 22 | class BlockTask( 23 | val blockPos: BlockPos, 24 | var taskState: TaskState, 25 | var targetBlock: Block, 26 | var isSupport: Boolean = false, 27 | var isFiller: Boolean = false, 28 | var item: Item = Items.AIR 29 | ) { 30 | private var ranTicks = 0 31 | var stuckTicks = 0; private set 32 | var shuffle = 0; private set 33 | var startDistance = 0.0; private set 34 | var eyeDistance = 0.0; private set 35 | 36 | var sequence: List = emptyList(); private set 37 | var isLiquidSource = false 38 | 39 | var isOpen = false 40 | var stopPull = false 41 | var stacksPulled = 0 42 | var isLoaded = false 43 | var itemID = 0 44 | var destroy = false 45 | var collect = true 46 | 47 | var timestamp = System.currentTimeMillis() 48 | var aabb = AxisAlignedBB(1.0, 1.0, 1.0, 1.0, 1.0, 1.0) 49 | 50 | var toRemove = false 51 | var ticksMined = 1 52 | var toolToUse: ItemStack = ItemStack.EMPTY 53 | 54 | fun updateState(state: TaskState) { 55 | if (state != taskState) { 56 | timestamp = System.currentTimeMillis() 57 | 58 | stuckTicks = 0 59 | ranTicks = 0 60 | taskState = state 61 | } 62 | } 63 | 64 | fun onTick() { 65 | ranTicks++ 66 | if (ranTicks > taskState.stuckThreshold) { 67 | stuckTicks++ 68 | } 69 | } 70 | 71 | fun onStuck(weight: Int = 1) { 72 | stuckTicks += weight 73 | } 74 | 75 | fun resetStuck() { 76 | stuckTicks = 0 77 | } 78 | 79 | fun updateTask(event: SafeClientEvent) { 80 | isLiquidSource = event.world.getBlockState(blockPos).let { 81 | it.block is BlockLiquid && it.getValue(BlockLiquid.LEVEL) == 0 82 | } 83 | 84 | when (taskState) { 85 | TaskState.PLACE, TaskState.LIQUID -> { 86 | sequence = event.getNeighbourSequence(blockPos, placementSearch, maxReach, !illegalPlacements) 87 | } 88 | else -> {} 89 | } 90 | 91 | startDistance = startingBlockPos.toVec3dCenter().distanceTo(blockPos.toVec3dCenter()) 92 | eyeDistance = event.player.getPositionEyes(1f).distanceTo(blockPos.toVec3dCenter()) 93 | 94 | aabb = event.world 95 | .getBlockState(blockPos) 96 | .getSelectedBoundingBox(event.world, blockPos) 97 | } 98 | 99 | fun isShulker() = targetBlock is BlockShulkerBox 100 | 101 | fun shuffle() { 102 | shuffle = Random.nextInt(0, 1000) 103 | } 104 | 105 | fun prettyPrint(): String { 106 | return " ${targetBlock.localizedName}@(${blockPos.asString()}) State: $taskState Timings: (Threshold: ${taskState.stuckThreshold} Timeout: ${taskState.stuckTimeout}) Priority: ${taskState.ordinal} Stuck: $stuckTicks" 107 | } 108 | 109 | override fun toString(): String { 110 | return "Block: ${targetBlock.localizedName} @ Position: (${blockPos.asString()}) State: ${taskState.name}" 111 | } 112 | 113 | override fun equals(other: Any?) = this === other 114 | || (other is BlockTask 115 | && blockPos == other.blockPos) 116 | 117 | override fun hashCode() = blockPos.hashCode() 118 | } -------------------------------------------------------------------------------- /src/main/kotlin/trombone/interaction/Place.kt: -------------------------------------------------------------------------------- 1 | package trombone.interaction 2 | 3 | import HighwayTools.debugLevel 4 | import HighwayTools.dynamicDelay 5 | import HighwayTools.placeDelay 6 | import HighwayTools.taskTimeout 7 | import com.lambda.client.LambdaMod 8 | import com.lambda.client.event.SafeClientEvent 9 | import com.lambda.client.util.items.blockBlacklist 10 | import com.lambda.client.util.math.CoordinateConverter.asString 11 | import com.lambda.client.util.text.MessageSendHelper 12 | import com.lambda.client.util.threads.defaultScope 13 | import com.lambda.client.util.threads.onMainThreadSafe 14 | import com.lambda.client.util.world.getHitVec 15 | import com.lambda.client.util.world.getHitVecOffset 16 | import kotlinx.coroutines.delay 17 | import kotlinx.coroutines.launch 18 | import net.minecraft.network.play.client.CPacketEntityAction 19 | import net.minecraft.network.play.client.CPacketPlayerTryUseItemOnBlock 20 | import net.minecraft.util.EnumFacing 21 | import net.minecraft.util.EnumHand 22 | import net.minecraft.util.math.BlockPos 23 | import trombone.IO.DebugLevel 24 | import trombone.Trombone.module 25 | import trombone.handler.Container.containerTask 26 | import trombone.handler.Inventory.lastHitVec 27 | import trombone.handler.Inventory.waitTicks 28 | import trombone.task.BlockTask 29 | import trombone.task.TaskState 30 | 31 | object Place { 32 | var extraPlaceDelay = 0 33 | 34 | fun SafeClientEvent.placeBlock(blockTask: BlockTask) { 35 | when (blockTask.sequence.size) { 36 | 0 -> { 37 | if (debugLevel == DebugLevel.VERBOSE) { 38 | LambdaMod.LOG.warn("${module.chatName} No neighbours found for ${blockTask.blockPos.asString()}") 39 | } 40 | if (blockTask == containerTask) { 41 | MessageSendHelper.sendChatMessage("${module.chatName} Can't find neighbour blocks to place down the container.") 42 | } 43 | blockTask.onStuck(21) 44 | blockTask.updateState(TaskState.DONE) 45 | return 46 | } 47 | else -> { 48 | val last = blockTask.sequence.last() 49 | lastHitVec = getHitVec(last.pos, last.side) 50 | 51 | placeBlockNormal(blockTask, last.pos, last.side) 52 | } 53 | // else -> { 54 | // ToDo: Rewrite deep place 55 | // blockTask.sequence.forEach { 56 | // addTaskToPending(it.pos, TaskState.PLACE, fillerMat) 57 | // } 58 | // } 59 | } 60 | } 61 | 62 | private fun SafeClientEvent.placeBlockNormal(blockTask: BlockTask, placePos: BlockPos, side: EnumFacing) { 63 | val hitVecOffset = getHitVecOffset(side) 64 | val currentBlock = world.getBlockState(placePos).block 65 | 66 | waitTicks = if (dynamicDelay) { 67 | placeDelay + extraPlaceDelay 68 | } else { 69 | placeDelay 70 | } 71 | blockTask.updateState(TaskState.PENDING_PLACE) 72 | 73 | if (currentBlock in blockBlacklist) { 74 | connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.START_SNEAKING)) 75 | } 76 | 77 | defaultScope.launch { 78 | delay(20L) // ToDo: Check if necessary 79 | onMainThreadSafe { 80 | val placePacket = CPacketPlayerTryUseItemOnBlock(placePos, side, EnumHand.MAIN_HAND, hitVecOffset.x.toFloat(), hitVecOffset.y.toFloat(), hitVecOffset.z.toFloat()) 81 | connection.sendPacket(placePacket) 82 | player.swingArm(EnumHand.MAIN_HAND) 83 | } 84 | 85 | if (currentBlock in blockBlacklist) { 86 | delay(20L) 87 | onMainThreadSafe { 88 | connection.sendPacket(CPacketEntityAction(player, CPacketEntityAction.Action.STOP_SNEAKING)) 89 | } 90 | } 91 | 92 | delay(50L * taskTimeout) 93 | if (blockTask.taskState == TaskState.PENDING_PLACE) { 94 | blockTask.updateState(TaskState.PLACE) 95 | if (dynamicDelay && extraPlaceDelay < 10) extraPlaceDelay += 1 96 | } 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/main/kotlin/HighwayToolsCommand.kt: -------------------------------------------------------------------------------- 1 | import com.lambda.client.command.ClientCommand 2 | import com.lambda.client.util.text.MessageSendHelper.sendChatMessage 3 | import trombone.IO.printSettings 4 | import trombone.Pathfinder.distancePending 5 | 6 | object HighwayToolsCommand : ClientCommand( 7 | name = "highwaytools", 8 | alias = arrayOf("ht", "hwt", "high"), 9 | description = "Customize settings of HighwayTools." 10 | ) { 11 | 12 | init { 13 | literal("add", "new", "+") { 14 | block("block") { blockArg -> 15 | execute("Adds a block to ignore list") { 16 | val added = HighwayTools.ignoreBlocks.add(blockArg.value.registryName.toString()) 17 | if (added) { 18 | printSettings() 19 | sendChatMessage("Added &7${blockArg.value.localizedName}&r to ignore list.") 20 | } else { 21 | sendChatMessage("&7${blockArg.value.localizedName}&r is already ignored.") 22 | } 23 | } 24 | } 25 | } 26 | 27 | literal("remove", "rem", "-", "del") { 28 | block("block") { blockArg -> 29 | execute("Removes a block from ignore list") { 30 | val removed = HighwayTools.ignoreBlocks.remove(blockArg.value.registryName.toString()) 31 | if (removed) { 32 | printSettings() 33 | sendChatMessage("Removed &7${blockArg.value.localizedName}&r from ignore list.") 34 | } else { 35 | sendChatMessage("&7${blockArg.value.localizedName}&r is not yet ignored.") 36 | } 37 | } 38 | } 39 | } 40 | 41 | // literal("from", "start") { 42 | // blockPos("position") { blockPosArg -> 43 | // execute("Sets starting coordinates") { 44 | // // ToDo: Make starting position for next instance 45 | //// HighwayTools.startingPos = blockPosArg.value 46 | // } 47 | // } 48 | // } 49 | 50 | // literal("to", "stop") { 51 | // blockPos("position") { blockPosArg -> 52 | // execute("Sets stopping coordinates and starts bot") { 53 | // if (HighwayTools.isEnabled) { 54 | // sendChatMessage("Run this command when the bot is not running") 55 | // } else { 56 | // HighwayTools.targetBlockPos = blockPosArg.value 57 | // sendChatMessage("Started HighwayTools with target @(${blockPosArg.value.asString()})") 58 | // HighwayTools.enable() 59 | // } 60 | // } 61 | // } 62 | // } 63 | 64 | literal("distance") { 65 | int("distance") { distanceArg -> 66 | execute("Sets the target distance until the bot stops") { 67 | distancePending = distanceArg.value 68 | sendChatMessage("HighwayTools will stop after (${distanceArg.value}) blocks distance. To remove the limit use distance 0") 69 | } 70 | } 71 | } 72 | 73 | literal("material", "mat") { 74 | block("block") { blockArg -> 75 | execute("Sets a block as main material") { 76 | HighwayTools.material = blockArg.value 77 | sendChatMessage("Set your building material to &7${blockArg.value.localizedName}&r.") 78 | } 79 | } 80 | } 81 | 82 | literal("filler", "fil") { 83 | block("block") { blockArg -> 84 | execute("Sets a block as filler material") { 85 | HighwayTools.fillerMat = blockArg.value 86 | sendChatMessage("Set your filling material to &7${blockArg.value.localizedName}&r.") 87 | } 88 | } 89 | } 90 | 91 | literal("food", "fd") { 92 | item("item") { itemArg -> 93 | execute("Sets a type of food") { 94 | HighwayTools.food = itemArg.value 95 | sendChatMessage("Set your food item to &7${itemArg.value.registryName}&r.") 96 | } 97 | } 98 | } 99 | 100 | execute("Print settings") { 101 | printSettings() 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Description 4 | 5 | Adding fully automated highway building as plugin to Lambda. 6 | The tool places building material and destroys wrong blocks in high frequency using well-timed packets. 7 | 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. 8 | Advanced inventory management allows the bot to fully utilize materials stocked in shulker boxes or in the ender chest. 9 | It is highly customizable and allows deep changes in system using configurations. 10 | It is fully compatible with most modules of Lambda like `LagNotifier`, `AutoEat` etc and also is confirmed to work with `Rusherhack`, `Future`, `Impact` etc. 11 | 12 | ### Features 13 | - [x] Digs tunnel and paves Obsidian floor at the same time 14 | - [x] Faster block breaking and placing then any other solution 15 | - Reaches `20+ blocks per second` mining interactions on 2b2t 16 | - Reaches `7+ blocks per second` placing interactions on 2b2t 17 | - Confirmed over `300%` faster than previous cutting edge digging solutions (source: MEG) 18 | - [x] Intelligent liquid handling 19 | - Reacts on liquid pockets using cutting edge placing exploits to patch lava pockets before even opening them 20 | - Reacts on complex flow structures and cleans up 21 | - [x] Long term inventory management 22 | - Dynamic material restock from shulker boxes and ender chests 23 | - Built in native AutoObsidian. Enable option Storage Management > Grind Obsidian 24 | - Saves minimum requirements of materials 25 | - No mouse grab on container open 26 | - [x] Diagonal highway mode 27 | - [x] Intelligent repair mode 28 | - [x] The built-in Anti-Anti-Cheat works with 2b2t's anti-cheat and `NoCheatPlus` 29 | - [x] Pauses on lag to avoid kick (enable `LagNotifier` for this feature) 30 | - [x] Ignore Blocks: `Signs, Portals, Banners, Bedrock` and more 31 | - [x] Choose custom-building materials 32 | - [x] Auto clipping to starting coordinates 33 | - [x] Commands: 34 | - `;highwaytools` - alias: `;ht` 35 | - `;ht ignore add ` Adds block to ignore list 36 | - `;ht ignore del ` Removes block from ignore list 37 | - `;ht material ` Choose an alternative building block (default: Obsidian) 38 | - `;ht filler ` Choose an alternative filler block to fill liquids (default: Netherrack) 39 | - `;ht settings` or `;ht` Shows detailed settings of the module 40 | - `;ht distance 500` for running the bot for a limited distance. (e.g. 500 blocks) 41 | - [x] Compatible with: 42 | - `LagNotifier (Baritone mode)` To stop while lagging to not get kicked 43 | - `AutoObsidian` to automatically get new Obsidian from Ender Chests even from shulker boxes 44 | - `AutoEat` set `PauseBaritone` on false and below health 19.0, and you're safe from lava and other threads having gapples in inventory 45 | - `AutoLog` to logout on any given danger 46 | - `AutoReconnect (Support pending)` to get back on server after waiting (for example a player comes in range) 47 | - `AntiHunger` slows food level decrease but makes block breaking slower 48 | - [x] Highly dynamical generated blueprints 49 | - Three Modes: `Highway` (for full highways), `Tunnel` (optimized for digging), `Flat` (for repair obsidian sky) 50 | - `ClearSpace` Choose to break wrong blocks and tunneling 51 | - `ClearHeight` Choose the height of tunnel 52 | - `BuildWidth` Choose the width of the highway 53 | - `Railing` Choose if the highway has rims/guardrails 54 | - `RailingHeight` Choose height of the rims/guardrails 55 | - `CornerBlock` Choose if u want to have a corner block or not 56 | 57 | 58 | 59 | 60 | ### Installation 61 | 1. Get the latest Lambda release here https://github.com/lambda-client/lambda/releases 62 | 2. Open Lambda menu in main menu to open plugin settings 63 | 3. Press `Open Plugin Folder` 64 | 4. Move plugin `HighwayTools-*.jar` into the folder `.minecraft/lambda/plugins` 65 | 66 | ### Known issues 67 | - `AutoLog` is not compatible with `AutoReconnect` 68 | 69 | ### Troubleshooting 70 | - Deactivate `AntiHunger` for faster block breaking because it makes you basically float. 71 | - If stuck check, if AutoCenter is on `MOTION` to get moved to middle of the block (Baritone can't move exactly to block center) 72 | - If placed block disappear increase the `TickDelayPlace` until it works 73 | - Deactivate `IllegalPlacements` if the server requires (like 2b2t) 74 | 75 | Any suggestions and questions: Constructor#9948 on Discord Made by @Avanatiker 76 | Report bugs on [Issues](https://github.com/lambda-plugins/HighwayTools/issues) and if not possible message EnigmA_008#1505 on Discord. 77 | 78 | `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. 79 | 80 | By downloading this software you agree to be bound by the terms of service. -------------------------------------------------------------------------------- /src/main/kotlin/trombone/Renderer.kt: -------------------------------------------------------------------------------- 1 | package trombone 2 | 3 | import HighwayTools.aFilled 4 | import HighwayTools.aOutline 5 | import HighwayTools.anonymizeStats 6 | import HighwayTools.filled 7 | import HighwayTools.outline 8 | import HighwayTools.popUp 9 | import HighwayTools.popUpSpeed 10 | import HighwayTools.showCurrentPos 11 | import HighwayTools.showDebugRender 12 | import HighwayTools.textScale 13 | import HighwayTools.thickness 14 | import com.lambda.client.util.color.ColorHolder 15 | import com.lambda.client.util.graphics.ESPRenderer 16 | import com.lambda.client.util.graphics.GlStateUtils 17 | import com.lambda.client.util.graphics.ProjectionUtils 18 | import com.lambda.client.util.graphics.font.FontRenderAdapter 19 | import com.lambda.client.util.math.CoordinateConverter.asString 20 | import com.lambda.client.util.math.VectorUtils.toVec3dCenter 21 | import net.minecraft.init.Blocks 22 | import net.minecraft.util.math.BlockPos 23 | import org.lwjgl.opengl.GL11 24 | import trombone.Pathfinder.currentBlockPos 25 | import trombone.handler.Container.containerTask 26 | import trombone.task.BlockTask 27 | import trombone.task.TaskManager.tasks 28 | import trombone.task.TaskState 29 | import kotlin.math.PI 30 | import kotlin.math.cos 31 | import kotlin.math.sin 32 | 33 | object Renderer { 34 | private val renderer = ESPRenderer() 35 | 36 | fun renderWorld() { 37 | renderer.clear() 38 | renderer.aFilled = if (filled) aFilled else 0 39 | renderer.aOutline = if (outline) aOutline else 0 40 | renderer.thickness = thickness 41 | val currentTime = System.currentTimeMillis() 42 | 43 | if (showCurrentPos) renderer.add(currentBlockPos, ColorHolder(255, 255, 255)) 44 | 45 | if (containerTask.taskState != TaskState.DONE) { 46 | addToRenderer(containerTask, currentTime) 47 | } 48 | 49 | tasks.values.forEach { 50 | if (it.targetBlock == Blocks.AIR && it.taskState == TaskState.DONE) return@forEach 51 | if (it.toRemove) { 52 | addToRenderer(it, currentTime, true) 53 | } else { 54 | addToRenderer(it, currentTime) 55 | } 56 | } 57 | renderer.render(false) 58 | } 59 | 60 | fun renderOverlay() { 61 | if (!showDebugRender) return 62 | GlStateUtils.rescaleActual() 63 | 64 | if (containerTask.taskState != TaskState.DONE) { 65 | updateOverlay(containerTask.blockPos, containerTask) 66 | } 67 | 68 | tasks.filterValues { 69 | it.taskState != TaskState.DONE 70 | }.forEach { (pos, blockTask) -> 71 | updateOverlay(pos, blockTask) 72 | } 73 | } 74 | 75 | private fun updateOverlay(pos: BlockPos, blockTask: BlockTask) { 76 | GL11.glPushMatrix() 77 | val screenPos = ProjectionUtils.toScreenPos(pos.toVec3dCenter()) 78 | GL11.glTranslated(screenPos.x, screenPos.y, 0.0) 79 | GL11.glScalef(textScale * 2.0f, textScale * 2.0f, 1.0f) 80 | 81 | val color = ColorHolder(255, 255, 255, 255) 82 | 83 | val debugInfos = mutableListOf>() 84 | if (!anonymizeStats) debugInfos.add(Pair("Pos", pos.asString())) 85 | if (blockTask != containerTask) { 86 | debugInfos.add(Pair("Start Distance", "%.2f".format(blockTask.startDistance))) 87 | debugInfos.add(Pair("Eye Distance", "%.2f".format(blockTask.eyeDistance))) 88 | } else { 89 | debugInfos.add(Pair("Item", "${blockTask.item.registryName}")) 90 | } 91 | if (blockTask.taskState == TaskState.PLACE || 92 | blockTask.taskState == TaskState.LIQUID) { 93 | debugInfos.add(Pair("Depth", "${blockTask.sequence.size}")) 94 | if (blockTask.isLiquidSource) debugInfos.add(Pair("Liquid Source", "")) 95 | } 96 | if (blockTask.isOpen) debugInfos.add(Pair("Open", "")) 97 | if (blockTask.isLoaded) debugInfos.add(Pair("Loaded", "")) 98 | if (blockTask.destroy) debugInfos.add(Pair("Destroy", "")) 99 | if (blockTask.stuckTicks > 0) debugInfos.add(Pair("Stuck", "${blockTask.stuckTicks}")) 100 | 101 | debugInfos.forEachIndexed { index, pair -> 102 | val text = if (pair.second == "") { 103 | pair.first 104 | } else { 105 | "${pair.first}: ${pair.second}" 106 | } 107 | val halfWidth = FontRenderAdapter.getStringWidth(text) / -2.0f 108 | FontRenderAdapter.drawString(text, halfWidth, (FontRenderAdapter.getFontHeight() + 2.0f) * index, color = color) 109 | } 110 | 111 | GL11.glPopMatrix() 112 | } 113 | 114 | private fun addToRenderer(blockTask: BlockTask, currentTime: Long, reverse: Boolean = false) { 115 | if (popUp) { 116 | val flip = if (reverse) { 117 | cos(((currentTime - blockTask.timestamp).toDouble() 118 | .coerceAtMost(popUpSpeed * PI / 2) / popUpSpeed)) 119 | } else { 120 | sin(((currentTime - blockTask.timestamp).toDouble() 121 | .coerceAtMost(popUpSpeed * PI / 2) / popUpSpeed)) 122 | } 123 | renderer.add(blockTask.aabb 124 | .shrink((0.5 - flip * 0.5)), 125 | blockTask.taskState.color 126 | ) 127 | } else { 128 | renderer.add(blockTask.aabb, blockTask.taskState.color) 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/linux,gradle,eclipse,windows,forgegradle,intellij+all,emacs,vim 2 | 3 | # Logs 4 | run/ 5 | scripts/jar-shrink/log.txt 6 | logs/ 7 | 8 | ### Eclipse ### 9 | .metadata 10 | bin/ 11 | tmp/ 12 | *.tmp 13 | *.bak 14 | *.swp 15 | *~.nib 16 | local.properties 17 | .settings/ 18 | .loadpath 19 | .recommenders 20 | kami_Client.launch 21 | kami_Server.launch 22 | client_Client.launch 23 | client_Server.launch 24 | .classpath 25 | .project 26 | 27 | # External tool builders 28 | .externalToolBuilders/ 29 | 30 | # Locally stored "Eclipse launch configurations" 31 | *.launch 32 | 33 | # PyDev specific (Python IDE for Eclipse) 34 | *.pydevproject 35 | 36 | # CDT-specific (C/C++ Development Tooling) 37 | .cproject 38 | 39 | # CDT- autotools 40 | .autotools 41 | 42 | # Java annotation processor (APT) 43 | .factorypath 44 | 45 | # PDT-specific (PHP Development Tools) 46 | .buildpath 47 | 48 | # sbteclipse plugin 49 | .target 50 | 51 | # Tern plugin 52 | .tern-project 53 | 54 | # TeXlipse plugin 55 | .texlipse 56 | 57 | # STS (Spring Tool Suite) 58 | .springBeans 59 | 60 | # Code Recommenders 61 | .recommenders/ 62 | 63 | # Annotation Processing 64 | .apt_generated/ 65 | 66 | # Scala IDE specific (Scala & Java development for Eclipse) 67 | .cache-main 68 | .scala_dependencies 69 | .worksheet 70 | 71 | ### Eclipse Patch ### 72 | 73 | # Annotation Processing 74 | .apt_generated 75 | 76 | ### Emacs ### 77 | # -*- mode: gitignore; -*- 78 | *~ 79 | \#*\# 80 | /.emacs.desktop 81 | /.emacs.desktop.lock 82 | *.elc 83 | auto-save-list 84 | tramp 85 | .\#* 86 | 87 | # Org-mode 88 | .org-id-locations 89 | *_archive 90 | 91 | # flymake-mode 92 | *_flymake.* 93 | 94 | # eshell files 95 | /eshell/history 96 | /eshell/lastdir 97 | 98 | # elpa packages 99 | /elpa/ 100 | 101 | # reftex files 102 | *.rel 103 | 104 | # AUCTeX auto folder 105 | /auto/ 106 | 107 | # cask packages 108 | .cask/ 109 | dist/ 110 | 111 | # Flycheck 112 | flycheck_*.el 113 | 114 | # server auth directory 115 | /server/ 116 | 117 | # projectiles files 118 | .projectile 119 | 120 | # directory configuration 121 | .dir-locals.el 122 | 123 | ### Intellij+all ### 124 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 125 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 126 | 127 | # User-specific stuff 128 | .idea/**/workspace.xml 129 | .idea/**/tasks.xml 130 | .idea/**/usage.statistics.xml 131 | .idea/**/dictionaries 132 | .idea/**/shelf 133 | 134 | # Sensitive or high-churn files 135 | .idea/**/dataSources/ 136 | .idea/**/dataSources.ids 137 | .idea/**/dataSources.local.xml 138 | .idea/**/sqlDataSources.xml 139 | .idea/**/dynamic.xml 140 | .idea/**/uiDesigner.xml 141 | .idea/**/dbnavigator.xml 142 | 143 | # Gradle 144 | .idea/**/gradle.xml 145 | .idea/**/libraries 146 | 147 | # Building 148 | classes 149 | 150 | # Gradle and Maven with auto-import 151 | # When using Gradle or Maven with auto-import, you should exclude module files, 152 | # since they will be recreated, and may cause churn. Uncomment if using 153 | # auto-import. 154 | # .idea/modules.xml 155 | # .idea/*.iml 156 | # .idea/modules 157 | 158 | # CMake 159 | cmake-build-*/ 160 | 161 | # Mongo Explorer plugin 162 | .idea/**/mongoSettings.xml 163 | 164 | # File-based project format 165 | *.iws 166 | 167 | # IntelliJ 168 | out/ 169 | 170 | # mpeltonen/sbt-idea plugin 171 | .idea_modules/ 172 | 173 | # JIRA plugin 174 | atlassian-ide-plugin.xml 175 | 176 | # Cursive Clojure plugin 177 | .idea/replstate.xml 178 | 179 | # Crashlytics plugin (for Android Studio and IntelliJ) 180 | com_crashlytics_export_strings.xml 181 | crashlytics.properties 182 | crashlytics-build.properties 183 | fabric.properties 184 | 185 | # Editor-based Rest Client 186 | .idea/httpRequests 187 | 188 | ### Intellij+all Patch ### 189 | # Ignores the whole .idea folder and all .iml files 190 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 191 | 192 | .idea/ 193 | 194 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 195 | 196 | *.iml 197 | modules.xml 198 | .idea/misc.xml 199 | *.ipr 200 | 201 | ### Linux ### 202 | 203 | # temporary files which can be created if a process still has a handle open of a deleted file 204 | .fuse_hidden* 205 | 206 | # KDE directory preferences 207 | .directory 208 | 209 | # Linux trash folder which might appear on any partition or disk 210 | .Trash-* 211 | 212 | # .nfs files are created when an open file is removed but is still being accessed 213 | .nfs* 214 | 215 | ### Vim ### 216 | # Swap 217 | [._]*.s[a-v][a-z] 218 | [._]*.sw[a-p] 219 | [._]s[a-rt-v][a-z] 220 | [._]ss[a-gi-z] 221 | [._]sw[a-p] 222 | 223 | # Session 224 | Session.vim 225 | 226 | # Temporary 227 | .netrwhist 228 | # Auto-generated tag files 229 | tags 230 | # Persistent undo 231 | [._]*.un~ 232 | 233 | ### Windows ### 234 | # Windows thumbnail cache files 235 | Thumbs.db 236 | ehthumbs.db 237 | ehthumbs_vista.db 238 | 239 | # Dump file 240 | *.stackdump 241 | 242 | # Folder config file 243 | [Dd]esktop.ini 244 | 245 | # Recycle Bin used on file shares 246 | $RECYCLE.BIN/ 247 | 248 | # Windows Installer files 249 | *.cab 250 | *.msi 251 | *.msix 252 | *.msm 253 | *.msp 254 | 255 | # Windows shortcuts 256 | *.lnk 257 | 258 | ### Gradle ### 259 | .gradle 260 | /build/ 261 | 262 | # Ignore Gradle GUI config 263 | gradle-app.setting 264 | 265 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 266 | !gradle-wrapper.jar 267 | 268 | # Cache of project 269 | .gradletasknamecache 270 | 271 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 272 | # gradle/wrapper/gradle-wrapper.properties 273 | 274 | # End of https://www.gitignore.io/api/linux,gradle,eclipse,windows,forgegradle,intellij+all,emacs,vim 275 | /target/ 276 | 277 | # macOS system generate files 278 | .DS_Store -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /src/main/kotlin/trombone/Pathfinder.kt: -------------------------------------------------------------------------------- 1 | package trombone 2 | 3 | import HighwayTools.moveSpeed 4 | import HighwayTools.scaffold 5 | import com.lambda.client.event.SafeClientEvent 6 | import com.lambda.client.util.BaritoneUtils 7 | import com.lambda.client.util.EntityUtils.flooredPosition 8 | import com.lambda.client.util.TickTimer 9 | import com.lambda.client.util.TimeUnit 10 | import com.lambda.client.util.math.Direction 11 | import com.lambda.client.util.math.VectorUtils.distanceTo 12 | import com.lambda.client.util.math.VectorUtils.multiply 13 | import com.lambda.client.util.math.VectorUtils.toVec3dCenter 14 | import com.lambda.client.util.world.isReplaceable 15 | import net.minecraft.block.BlockLiquid 16 | import net.minecraft.init.Blocks 17 | import net.minecraft.util.math.BlockPos 18 | import net.minecraft.util.math.Vec3d 19 | import trombone.IO.disableError 20 | import trombone.Statistics.simpleMovingAverageDistance 21 | import trombone.Trombone.active 22 | import trombone.handler.Container.containerTask 23 | import trombone.handler.Container.getCollectingPosition 24 | import trombone.handler.Inventory.lastHitVec 25 | import trombone.task.TaskManager.isBehindPos 26 | import trombone.task.TaskManager.populateTasks 27 | import trombone.task.TaskManager.tasks 28 | import trombone.task.TaskState 29 | 30 | object Pathfinder { 31 | var goal: BlockPos? = null 32 | var moveState = MovementState.RUNNING 33 | 34 | val rubberbandTimer = TickTimer(TimeUnit.TICKS) 35 | 36 | var startingDirection = Direction.NORTH 37 | var currentBlockPos = BlockPos(0, -1, 0) 38 | var startingBlockPos = BlockPos(0, -1, 0) 39 | private var targetBlockPos = BlockPos(0, -1, 0) 40 | var distancePending = 0 41 | 42 | enum class MovementState { 43 | RUNNING, PICKUP, BRIDGE, RESTOCK 44 | } 45 | 46 | fun SafeClientEvent.setupPathing() { 47 | moveState = MovementState.RUNNING 48 | startingBlockPos = player.flooredPosition 49 | currentBlockPos = startingBlockPos 50 | startingDirection = Direction.fromEntity(player) 51 | } 52 | 53 | fun SafeClientEvent.updatePathing() { 54 | when (moveState) { 55 | MovementState.RUNNING -> { 56 | goal = currentBlockPos 57 | 58 | // ToDo: Rewrite 59 | if (currentBlockPos.distanceTo(targetBlockPos) < 2 || 60 | (distancePending > 0 && 61 | startingBlockPos.add( 62 | startingDirection.directionVec.multiply(distancePending) 63 | ).distanceTo(currentBlockPos) == 0.0)) { 64 | disableError("Reached target destination") 65 | return 66 | } 67 | 68 | val possiblePos = currentBlockPos.add(startingDirection.directionVec) 69 | 70 | if (!isTaskDone(possiblePos.up()) 71 | || !isTaskDone(possiblePos) 72 | || !isTaskDone(possiblePos.down()) 73 | ) return 74 | 75 | if (!checkForResidue(possiblePos.up())) return 76 | 77 | if (world.getBlockState(possiblePos.down()).isReplaceable) return 78 | 79 | if (currentBlockPos != possiblePos 80 | && player.positionVector.distanceTo(currentBlockPos.toVec3dCenter()) < 2 81 | ) { 82 | simpleMovingAverageDistance.add(System.currentTimeMillis()) 83 | lastHitVec = Vec3d.ZERO 84 | currentBlockPos = possiblePos 85 | populateTasks() 86 | } 87 | } 88 | MovementState.BRIDGE -> { 89 | goal = null 90 | val isAboveAir = world.getBlockState(player.flooredPosition.down()).isReplaceable 91 | if (isAboveAir) player.movementInput?.sneak = true 92 | if (shouldBridge()) { 93 | val target = currentBlockPos.toVec3dCenter().add(Vec3d(startingDirection.directionVec)) 94 | moveTo(target) 95 | } else { 96 | if (!isAboveAir) { 97 | moveState = MovementState.RUNNING 98 | } 99 | } 100 | } 101 | MovementState.PICKUP -> { 102 | goal = getCollectingPosition() 103 | } 104 | MovementState.RESTOCK -> { 105 | val target = currentBlockPos.toVec3dCenter() 106 | if (player.positionVector.distanceTo(target) < 2) { 107 | goal = null 108 | moveTo(target) 109 | } else { 110 | goal = currentBlockPos 111 | } 112 | } 113 | } 114 | } 115 | 116 | private fun checkForResidue(pos: BlockPos) = 117 | containerTask.taskState == TaskState.DONE 118 | && tasks.values.all { 119 | it.taskState == TaskState.DONE || !isBehindPos(pos, it.blockPos) 120 | } 121 | 122 | private fun SafeClientEvent.isTaskDone(pos: BlockPos): Boolean { 123 | val block = world.getBlockState(pos).block 124 | return tasks[pos]?.let { 125 | it.taskState == TaskState.DONE 126 | && block != Blocks.PORTAL 127 | && block != Blocks.END_PORTAL 128 | && block !is BlockLiquid 129 | } ?: false 130 | } 131 | 132 | fun SafeClientEvent.shouldBridge(): Boolean { 133 | return scaffold 134 | && containerTask.taskState == TaskState.DONE 135 | && world.isAirBlock(currentBlockPos.add(startingDirection.directionVec)) 136 | && world.isAirBlock(currentBlockPos.add(startingDirection.directionVec).up()) 137 | && world.getBlockState(currentBlockPos.add(startingDirection.directionVec).down()).isReplaceable 138 | && tasks.values.none { it.taskState == TaskState.PENDING_PLACE } 139 | && tasks.values.filter { 140 | it.taskState == TaskState.PLACE 141 | || it.taskState == TaskState.LIQUID 142 | }.none { it.sequence.isNotEmpty() } 143 | } 144 | 145 | private fun SafeClientEvent.moveTo(target: Vec3d) { 146 | player.motionX = (target.x - player.posX).coerceIn((-moveSpeed).toDouble(), moveSpeed.toDouble()) 147 | player.motionZ = (target.z - player.posZ).coerceIn((-moveSpeed).toDouble(), moveSpeed.toDouble()) 148 | } 149 | 150 | fun updateProcess() { 151 | if (!active) { 152 | active = true 153 | BaritoneUtils.primary?.pathingControlManager?.registerProcess(Process) 154 | } 155 | } 156 | 157 | fun clearProcess() { 158 | active = false 159 | goal = null 160 | } 161 | } -------------------------------------------------------------------------------- /src/main/kotlin/trombone/interaction/Break.kt: -------------------------------------------------------------------------------- 1 | package trombone.interaction 2 | 3 | import HighwayTools.breakDelay 4 | import HighwayTools.illegalPlacements 5 | import HighwayTools.instantMine 6 | import HighwayTools.interactionLimit 7 | import HighwayTools.maxReach 8 | import HighwayTools.miningSpeedFactor 9 | import HighwayTools.multiBreak 10 | import HighwayTools.packetFlood 11 | import HighwayTools.taskTimeout 12 | import com.lambda.client.commons.extension.ceilToInt 13 | import com.lambda.client.event.SafeClientEvent 14 | import com.lambda.client.util.math.isInSight 15 | import com.lambda.client.util.threads.defaultScope 16 | import com.lambda.client.util.threads.onMainThreadSafe 17 | import com.lambda.client.util.threads.runSafe 18 | import com.lambda.client.util.world.getHitVec 19 | import com.lambda.client.util.world.getMiningSide 20 | import com.lambda.client.util.world.getNeighbour 21 | import kotlinx.coroutines.delay 22 | import kotlinx.coroutines.launch 23 | import net.minecraft.init.Blocks 24 | import net.minecraft.network.play.client.CPacketPlayerDigging 25 | import net.minecraft.util.EnumFacing 26 | import net.minecraft.util.EnumHand 27 | import net.minecraft.util.math.AxisAlignedBB 28 | import net.minecraft.util.math.BlockPos 29 | import trombone.handler.Inventory.lastHitVec 30 | import trombone.handler.Inventory.packetLimiter 31 | import trombone.handler.Inventory.waitTicks 32 | import trombone.handler.Liquid.handleLiquid 33 | import trombone.task.BlockTask 34 | import trombone.task.TaskManager.tasks 35 | import trombone.task.TaskState 36 | import kotlin.math.ceil 37 | 38 | object Break { 39 | var prePrimedPos = BlockPos.NULL_VECTOR!! 40 | var primedPos = BlockPos.NULL_VECTOR!! 41 | 42 | fun SafeClientEvent.mineBlock(blockTask: BlockTask) { 43 | val blockState = world.getBlockState(blockTask.blockPos) 44 | 45 | if (blockState.block == Blocks.FIRE) { 46 | getNeighbour(blockTask.blockPos, 1, maxReach, !illegalPlacements)?.let { 47 | lastHitVec = getHitVec(it.pos, it.side) 48 | 49 | extinguishFire(blockTask, it.pos, it.side) 50 | } ?: run { 51 | blockTask.updateState(TaskState.PLACE) 52 | } 53 | } else { 54 | val ticksNeeded = ceil((1 / blockState.getPlayerRelativeBlockHardness(player, world, blockTask.blockPos)) * miningSpeedFactor).toInt() 55 | 56 | var side = getMiningSide(blockTask.blockPos) ?: run { 57 | blockTask.onStuck() 58 | return 59 | } 60 | 61 | if (blockTask.blockPos == primedPos && instantMine) { 62 | side = side.opposite 63 | } 64 | 65 | lastHitVec = getHitVec(blockTask.blockPos, side) 66 | 67 | if (blockTask.ticksMined > ticksNeeded * 1.1 && 68 | blockTask.taskState == TaskState.BREAKING) { 69 | blockTask.updateState(TaskState.BREAK) 70 | blockTask.ticksMined = 0 71 | } 72 | 73 | if (ticksNeeded == 1 || player.capabilities.isCreativeMode) { 74 | mineBlockInstant(blockTask, side) 75 | } else { 76 | mineBlockNormal(blockTask, side, ticksNeeded) 77 | } 78 | } 79 | 80 | blockTask.ticksMined += 1 81 | } 82 | 83 | private fun mineBlockInstant(blockTask: BlockTask, side: EnumFacing) { 84 | waitTicks = breakDelay 85 | blockTask.updateState(TaskState.PENDING_BREAK) 86 | 87 | defaultScope.launch { 88 | sendMiningPackets(blockTask.blockPos, side, start = true) 89 | 90 | if (multiBreak) tryMultiBreak(blockTask) 91 | 92 | delay(50L * taskTimeout) 93 | if (blockTask.taskState == TaskState.PENDING_BREAK) { 94 | blockTask.updateState(TaskState.BREAK) 95 | } 96 | } 97 | } 98 | 99 | private fun tryMultiBreak(blockTask: BlockTask) { 100 | runSafe { 101 | val eyePos = player.getPositionEyes(1.0f) 102 | val viewVec = lastHitVec.subtract(eyePos).normalize() 103 | 104 | tasks.values.filter { 105 | it.taskState == TaskState.BREAK 106 | && it != blockTask 107 | }.forEach { task -> 108 | val relativeHardness = world.getBlockState(task.blockPos).getPlayerRelativeBlockHardness(player, world, task.blockPos) 109 | if (ceil((1 / relativeHardness) * miningSpeedFactor).ceilToInt() > 1) return@forEach 110 | 111 | if (packetLimiter.size > interactionLimit || handleLiquid(task)) return@runSafe 112 | 113 | val box = AxisAlignedBB(task.blockPos) 114 | val rayTraceResult = box.isInSight(eyePos, viewVec, range = maxReach.toDouble(), tolerance = 0.0) 115 | ?: return@forEach 116 | 117 | task.updateState(TaskState.PENDING_BREAK) 118 | 119 | defaultScope.launch { 120 | sendMiningPackets(task.blockPos, rayTraceResult.sideHit, start = true) 121 | 122 | delay(50L * taskTimeout) 123 | if (task.taskState == TaskState.PENDING_BREAK) { 124 | task.updateState(TaskState.BREAK) 125 | } 126 | } 127 | } 128 | } 129 | } 130 | 131 | private fun mineBlockNormal(blockTask: BlockTask, side: EnumFacing, ticks: Int) { 132 | defaultScope.launch { 133 | if (blockTask.taskState == TaskState.BREAK) { 134 | blockTask.updateState(TaskState.BREAKING) 135 | sendMiningPackets(blockTask.blockPos, side, start = true) 136 | } else { 137 | if (blockTask.ticksMined >= ticks) { 138 | sendMiningPackets(blockTask.blockPos, side, stop = true) 139 | } else { 140 | sendMiningPackets(blockTask.blockPos, side) 141 | } 142 | } 143 | } 144 | } 145 | 146 | private fun extinguishFire(blockTask: BlockTask, pos: BlockPos, side: EnumFacing) { 147 | waitTicks = breakDelay 148 | blockTask.updateState(TaskState.PENDING_BREAK) 149 | 150 | defaultScope.launch { 151 | sendMiningPackets(pos, side, start = true, abort = true) 152 | 153 | delay(50L * taskTimeout) 154 | if (blockTask.taskState == TaskState.PENDING_BREAK) { 155 | blockTask.updateState(TaskState.BREAK) 156 | } 157 | } 158 | } 159 | 160 | private suspend fun sendMiningPackets(pos: BlockPos, side: EnumFacing, start: Boolean = false, stop: Boolean = false, abort: Boolean = false) { 161 | packetLimiter.add(System.currentTimeMillis()) 162 | onMainThreadSafe { 163 | if (start || packetFlood) { 164 | connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.START_DESTROY_BLOCK, pos, side)) 165 | } 166 | if (abort) { 167 | connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.ABORT_DESTROY_BLOCK, pos, side)) 168 | } 169 | if (stop || packetFlood) { 170 | connection.sendPacket(CPacketPlayerDigging(CPacketPlayerDigging.Action.STOP_DESTROY_BLOCK, pos, side)) 171 | } 172 | player.swingArm(EnumHand.MAIN_HAND) 173 | } 174 | } 175 | } -------------------------------------------------------------------------------- /src/main/kotlin/trombone/blueprint/BlueprintGenerator.kt: -------------------------------------------------------------------------------- 1 | package trombone.blueprint 2 | 3 | import HighwayTools.backfill 4 | import HighwayTools.cleanCorner 5 | import HighwayTools.cleanFloor 6 | import HighwayTools.cleanLeftWall 7 | import HighwayTools.cleanRightWall 8 | import HighwayTools.cleanRoof 9 | import HighwayTools.clearSpace 10 | import HighwayTools.cornerBlock 11 | import HighwayTools.fillerMat 12 | import HighwayTools.height 13 | import HighwayTools.material 14 | import HighwayTools.maxReach 15 | import HighwayTools.mode 16 | import HighwayTools.railing 17 | import HighwayTools.railingHeight 18 | import HighwayTools.width 19 | import com.lambda.client.commons.extension.ceilToInt 20 | import com.lambda.client.commons.extension.floorToInt 21 | import com.lambda.client.util.math.Direction 22 | import com.lambda.client.util.math.VectorUtils.distanceTo 23 | import com.lambda.client.util.math.VectorUtils.multiply 24 | import com.lambda.client.util.math.VectorUtils.toVec3dCenter 25 | import net.minecraft.init.Blocks 26 | import net.minecraft.util.math.BlockPos 27 | import trombone.Pathfinder.currentBlockPos 28 | import trombone.Pathfinder.startingBlockPos 29 | import trombone.Pathfinder.startingDirection 30 | import trombone.Trombone.Structure 31 | 32 | object BlueprintGenerator { 33 | val blueprint = HashMap() 34 | 35 | fun generateBluePrint() { 36 | blueprint.clear() 37 | val basePos = currentBlockPos.down() 38 | 39 | if (mode == Structure.FLAT) { 40 | generateFlat(basePos) 41 | return 42 | } 43 | 44 | val zDirection = startingDirection 45 | val xDirection = zDirection.clockwise(if (zDirection.isDiagonal) 1 else 2) 46 | 47 | for (x in -maxReach.floorToInt() * 5..maxReach.ceilToInt() * 5) { 48 | val thisPos = basePos.add(zDirection.directionVec.multiply(x)) 49 | if (clearSpace) generateClear(thisPos, xDirection) 50 | if (mode == Structure.TUNNEL) { 51 | if (backfill) { 52 | generateBackfill(thisPos, xDirection) 53 | } else { 54 | if (cleanFloor) generateFloor(thisPos, xDirection) 55 | if (cleanRightWall || cleanLeftWall) generateWalls(thisPos, xDirection) 56 | if (cleanRoof) generateRoof(thisPos, xDirection) 57 | if (cleanCorner && !cornerBlock && width > 2) generateCorner(thisPos, xDirection) 58 | } 59 | } else { 60 | generateBase(thisPos, xDirection) 61 | } 62 | } 63 | 64 | if (mode == Structure.TUNNEL && (!cleanFloor || backfill)) { 65 | if (startingDirection.isDiagonal) { 66 | for (x in 0..maxReach.floorToInt()) { 67 | val pos = basePos.add(zDirection.directionVec.multiply(x)) 68 | blueprint[pos] = BlueprintTask(fillerMat, isFiller = true) 69 | blueprint[pos.add(startingDirection.clockwise(7).directionVec)] = BlueprintTask(fillerMat, isFiller = true) 70 | } 71 | } else { 72 | for (x in 0..maxReach.floorToInt()) { 73 | blueprint[basePos.add(zDirection.directionVec.multiply(x))] = BlueprintTask(fillerMat, isFiller = true) 74 | } 75 | } 76 | } 77 | } 78 | 79 | private fun generateClear(basePos: BlockPos, xDirection: Direction) { 80 | for (w in 0 until width) { 81 | for (h in 0 until height) { 82 | val x = w - width / 2 83 | val pos = basePos.add(xDirection.directionVec.multiply(x)).up(h) 84 | 85 | if (mode == Structure.HIGHWAY && h == 0 && isRail(w)) { 86 | continue 87 | } 88 | 89 | if (mode == Structure.HIGHWAY) { 90 | blueprint[pos] = BlueprintTask(Blocks.AIR) 91 | } else { 92 | if (!(isRail(w) && h == 0 && !cornerBlock && width > 2)) blueprint[pos.up()] = BlueprintTask(Blocks.AIR) 93 | } 94 | } 95 | } 96 | } 97 | 98 | private fun generateBase(basePos: BlockPos, xDirection: Direction) { 99 | for (w in 0 until width) { 100 | val x = w - width / 2 101 | val pos = basePos.add(xDirection.directionVec.multiply(x)) 102 | 103 | if (mode == Structure.HIGHWAY && isRail(w)) { 104 | if (!cornerBlock && width > 2 && startingDirection.isDiagonal) blueprint[pos] = BlueprintTask(fillerMat, isSupport = true) 105 | val startHeight = if (cornerBlock && width > 2) 0 else 1 106 | for (y in startHeight..railingHeight) { 107 | blueprint[pos.up(y)] = BlueprintTask(material) 108 | } 109 | } else { 110 | blueprint[pos] = BlueprintTask(material) 111 | } 112 | } 113 | } 114 | 115 | private fun generateFloor(basePos: BlockPos, xDirection: Direction) { 116 | val wid = if (cornerBlock && width > 2) { 117 | width 118 | } else { 119 | width - 2 120 | } 121 | for (w in 0 until wid) { 122 | val x = w - wid / 2 123 | val pos = basePos.add(xDirection.directionVec.multiply(x)) 124 | blueprint[pos] = BlueprintTask(fillerMat, isFiller = true) 125 | } 126 | } 127 | 128 | private fun generateWalls(basePos: BlockPos, xDirection: Direction) { 129 | val cb = if (!cornerBlock && width > 2) { 130 | 1 131 | } else { 132 | 0 133 | } 134 | for (h in cb until height) { 135 | if (cleanRightWall) blueprint[basePos.add(xDirection.directionVec.multiply(width - width / 2)).up(h + 1)] = BlueprintTask(fillerMat, isFiller = true) 136 | if (cleanLeftWall) blueprint[basePos.add(xDirection.directionVec.multiply(-1 - width / 2)).up(h + 1)] = BlueprintTask(fillerMat, isFiller = true) 137 | } 138 | } 139 | 140 | private fun generateRoof(basePos: BlockPos, xDirection: Direction) { 141 | for (w in 0 until width) { 142 | val x = w - width / 2 143 | val pos = basePos.add(xDirection.directionVec.multiply(x)) 144 | blueprint[pos.up(height + 1)] = BlueprintTask(fillerMat, isFiller = true) 145 | } 146 | } 147 | 148 | private fun generateCorner(basePos: BlockPos, xDirection: Direction) { 149 | blueprint[basePos.add(xDirection.directionVec.multiply(-1 - width / 2 + 1)).up()] = BlueprintTask(fillerMat, isFiller = true) 150 | blueprint[basePos.add(xDirection.directionVec.multiply(width - width / 2 - 1)).up()] = BlueprintTask(fillerMat, isFiller = true) 151 | } 152 | 153 | private fun generateBackfill(basePos: BlockPos, xDirection: Direction) { 154 | for (w in 0 until width) { 155 | for (h in 0 until height) { 156 | val x = w - width / 2 157 | val pos = basePos.add(xDirection.directionVec.multiply(x)).up(h + 1) 158 | 159 | if (startingBlockPos.toVec3dCenter().distanceTo(pos.toVec3dCenter()) + 1 < startingBlockPos.toVec3dCenter().distanceTo(currentBlockPos.toVec3dCenter())) { 160 | blueprint[pos] = BlueprintTask(fillerMat, isFiller = true) 161 | } 162 | } 163 | } 164 | } 165 | 166 | private fun isRail(w: Int) = railing && w !in 1 until width - 1 167 | 168 | private fun generateFlat(basePos: BlockPos) { 169 | // Base 170 | for (w1 in 0 until width) { 171 | for (w2 in 0 until width) { 172 | val x = w1 - width / 2 173 | val z = w2 - width / 2 174 | val pos = basePos.add(x, 0, z) 175 | 176 | blueprint[pos] = BlueprintTask(material) 177 | } 178 | } 179 | 180 | // Clear 181 | if (!clearSpace) return 182 | for (w1 in -width..width) { 183 | for (w2 in -width..width) { 184 | for (y in 1 until height) { 185 | val x = w1 - width / 2 186 | val z = w2 - width / 2 187 | val pos = basePos.add(x, y, z) 188 | 189 | blueprint[pos] = BlueprintTask(Blocks.AIR) 190 | } 191 | } 192 | } 193 | } 194 | 195 | fun isInsideBlueprint(pos: BlockPos): Boolean { 196 | return blueprint.containsKey(pos) 197 | } 198 | 199 | fun isInsideBlueprintBuild(pos: BlockPos): Boolean { 200 | return blueprint[pos]?.let { it.targetBlock == material } ?: false 201 | } 202 | } -------------------------------------------------------------------------------- /src/main/kotlin/trombone/IO.kt: -------------------------------------------------------------------------------- 1 | package trombone 2 | 3 | import HighwayTools.anonymizeStats 4 | import HighwayTools.disableMode 5 | import HighwayTools.disableWarnings 6 | import HighwayTools.fillerMat 7 | import HighwayTools.height 8 | import HighwayTools.ignoreBlocks 9 | import HighwayTools.info 10 | import HighwayTools.material 11 | import HighwayTools.mode 12 | import HighwayTools.multiBuilding 13 | import HighwayTools.proxyCommand 14 | import HighwayTools.rubberbandTimeout 15 | import HighwayTools.usingProxy 16 | import com.lambda.client.event.SafeClientEvent 17 | import com.lambda.client.module.modules.combat.AutoDisconnect 18 | import com.lambda.client.module.modules.misc.AntiAFK 19 | import com.lambda.client.module.modules.misc.AutoObsidian 20 | import com.lambda.client.module.modules.movement.AntiHunger 21 | import com.lambda.client.module.modules.movement.Velocity 22 | import com.lambda.client.module.modules.player.AutoEat 23 | import com.lambda.client.module.modules.player.LagNotifier 24 | import com.lambda.client.module.modules.player.NoGhostItems 25 | import com.lambda.client.process.PauseProcess 26 | import com.lambda.client.util.math.Direction 27 | import com.lambda.client.util.math.VectorUtils.distanceTo 28 | import com.lambda.client.util.text.MessageSendHelper 29 | import com.lambda.client.util.threads.defaultScope 30 | import com.lambda.client.util.threads.onMainThreadSafe 31 | import kotlinx.coroutines.delay 32 | import kotlinx.coroutines.launch 33 | import net.minecraft.client.audio.PositionedSoundRecord 34 | import net.minecraft.init.SoundEvents 35 | import net.minecraft.util.text.TextComponentString 36 | import net.minecraft.util.text.TextFormatting 37 | import net.minecraft.world.EnumDifficulty 38 | import trombone.Pathfinder.currentBlockPos 39 | import trombone.Pathfinder.startingBlockPos 40 | import trombone.Pathfinder.startingDirection 41 | import trombone.Statistics.totalBlocksBroken 42 | import trombone.Statistics.totalBlocksPlaced 43 | import trombone.Trombone.Structure 44 | import trombone.Trombone.module 45 | import kotlin.math.abs 46 | 47 | object IO { 48 | enum class DisableMode { 49 | NONE, ANTI_AFK, LOGOUT 50 | } 51 | 52 | enum class DebugLevel { 53 | OFF, IMPORTANT, VERBOSE 54 | } 55 | 56 | fun SafeClientEvent.pauseCheck(): Boolean = 57 | !Pathfinder.rubberbandTimer.tick(rubberbandTimeout.toLong(), false) 58 | || player.inventory.isEmpty 59 | || PauseProcess.isActive 60 | || AutoObsidian.isActive() 61 | || isInQueue() 62 | || !player.isEntityAlive 63 | || !player.onGround 64 | 65 | private fun SafeClientEvent.isInQueue() = 66 | world.difficulty == EnumDifficulty.PEACEFUL 67 | && player.dimension == 1 68 | && @Suppress("UNNECESSARY_SAFE_CALL") 69 | player.serverBrand?.contains("2b2t") == true 70 | 71 | fun SafeClientEvent.printEnable() { 72 | MessageSendHelper.sendRawChatMessage(" §9> §7Direction: §a${startingDirection.displayName} / ${startingDirection.displayNameXY}§r") 73 | 74 | if (!anonymizeStats) { 75 | if (startingDirection.isDiagonal) { 76 | MessageSendHelper.sendRawChatMessage(" §9> §7Axis offset: §a%,d %,d§r".format(startingBlockPos.x, startingBlockPos.z)) 77 | 78 | if (abs(startingBlockPos.x) != abs(startingBlockPos.z)) { 79 | MessageSendHelper.sendRawChatMessage(" §c[!] You may have an offset to diagonal highway position!") 80 | } 81 | } else { 82 | if (startingDirection == Direction.NORTH || startingDirection == Direction.SOUTH) { 83 | MessageSendHelper.sendRawChatMessage(" §9> §7Axis offset: §a%,d§r".format(startingBlockPos.x)) 84 | } else { 85 | MessageSendHelper.sendRawChatMessage(" §9> §7Axis offset: §a%,d§r".format(startingBlockPos.z)) 86 | } 87 | } 88 | } 89 | 90 | if (!disableWarnings) { 91 | if (startingBlockPos.y != 120 && mode != Structure.TUNNEL) { 92 | MessageSendHelper.sendRawChatMessage(" §c[!] Check altitude and make sure to build at Y: 120 for the correct height") 93 | } 94 | 95 | if (AntiHunger.isEnabled) { 96 | MessageSendHelper.sendRawChatMessage(" §c[!] AntiHunger does slow down block interactions.") 97 | } 98 | 99 | if (LagNotifier.isDisabled) { 100 | MessageSendHelper.sendRawChatMessage(" §c[!] You should activate LagNotifier to make the bot stop on server lag.") 101 | } 102 | 103 | if (AutoEat.isDisabled) { 104 | MessageSendHelper.sendRawChatMessage(" §c[!] You should activate AutoEat to not die on starvation.") 105 | } 106 | 107 | if (AutoDisconnect.isDisabled) { 108 | MessageSendHelper.sendRawChatMessage(" §c[!] You should activate AutoLog to prevent most deaths when afk.") 109 | } 110 | 111 | if (multiBuilding && Velocity.isDisabled) { 112 | MessageSendHelper.sendRawChatMessage(" §c[!] Make sure to enable Velocity to not get pushed from your mates.") 113 | } 114 | 115 | if (material == fillerMat) { 116 | MessageSendHelper.sendRawChatMessage(" §c[!] Make sure to use §aTunnel Mode§c instead of having same material for both main and filler!") 117 | } 118 | 119 | if (mode == Structure.HIGHWAY && height < 3) { 120 | MessageSendHelper.sendRawChatMessage(" §c[!] You may increase the height to at least 3") 121 | } 122 | 123 | if (isInQueue()) { 124 | MessageSendHelper.sendRawChatMessage(" §c[!] You should not activate the bot in queue! Bot will move to 0 0.") 125 | } 126 | 127 | if (NoGhostItems.isDisabled) { 128 | MessageSendHelper.sendRawChatMessage(" §c[!] Please consider using the module NoGhostItems to minimize item desyncs") 129 | } 130 | } 131 | } 132 | 133 | fun printDisable() { 134 | if (info) { 135 | MessageSendHelper.sendRawChatMessage(" §9> §7Placed blocks: §a%,d§r".format(totalBlocksPlaced)) 136 | MessageSendHelper.sendRawChatMessage(" §9> §7Destroyed blocks: §a%,d§r".format(totalBlocksBroken)) 137 | MessageSendHelper.sendRawChatMessage(" §9> §7Distance: §a%,d§r".format(startingBlockPos.distanceTo(currentBlockPos).toInt())) 138 | } 139 | } 140 | 141 | fun printSettings() { 142 | StringBuilder(ignoreBlocks.size + 1).run { 143 | append("${module.chatName} Settings" + 144 | "\n §9> §rMain material: §7${material.localizedName}" + 145 | "\n §9> §rFiller material: §7${fillerMat.localizedName}" + 146 | "\n §9> §rIgnored Blocks:") 147 | 148 | ignoreBlocks.forEach { 149 | append("\n §9> §7$it") 150 | } 151 | 152 | MessageSendHelper.sendChatMessage(toString()) 153 | } 154 | } 155 | 156 | fun SafeClientEvent.disableError(error: String) { 157 | MessageSendHelper.sendErrorMessage("${module.chatName} §c[!] $error") 158 | mc.soundHandler.playSound(PositionedSoundRecord.getRecord(SoundEvents.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.0f)) 159 | module.disable() 160 | when (disableMode) { 161 | DisableMode.ANTI_AFK -> { 162 | MessageSendHelper.sendWarningMessage("${module.chatName} §c[!] ${TextFormatting.AQUA}Going into AFK mode.") 163 | AntiAFK.enable() 164 | } 165 | DisableMode.LOGOUT -> { 166 | MessageSendHelper.sendWarningMessage("${module.chatName} §c[!] ${TextFormatting.DARK_RED}CAUTION: Logging off in 1 minute!") 167 | defaultScope.launch { 168 | delay(6000L) 169 | if (disableMode == DisableMode.LOGOUT && module.isDisabled) { 170 | onMainThreadSafe { 171 | if (usingProxy) { 172 | player.sendChatMessage(proxyCommand) 173 | } else { 174 | connection.networkManager.closeChannel(TextComponentString("Done building highways.")) 175 | } 176 | } 177 | } else { 178 | MessageSendHelper.sendChatMessage("${module.chatName} ${TextFormatting.GREEN}Logout canceled.") 179 | } 180 | } 181 | } 182 | DisableMode.NONE -> { 183 | // Nothing 184 | } 185 | } 186 | } 187 | } -------------------------------------------------------------------------------- /src/main/kotlin/trombone/handler/Inventory.kt: -------------------------------------------------------------------------------- 1 | package trombone.handler 2 | 3 | import HighwayTools.keepFreeSlots 4 | import HighwayTools.material 5 | import HighwayTools.saveMaterial 6 | import HighwayTools.saveTools 7 | import HighwayTools.storageManagement 8 | import com.lambda.client.event.SafeClientEvent 9 | import com.lambda.client.manager.managers.PlayerInventoryManager 10 | import com.lambda.client.manager.managers.PlayerInventoryManager.addInventoryTask 11 | import com.lambda.client.manager.managers.PlayerPacketManager.sendPlayerPacket 12 | import com.lambda.client.module.modules.player.InventoryManager 13 | import com.lambda.client.util.items.* 14 | import com.lambda.client.util.math.RotationUtils.getRotationTo 15 | import net.minecraft.block.Block 16 | import net.minecraft.block.Block.getBlockFromName 17 | import net.minecraft.enchantment.EnchantmentHelper 18 | import net.minecraft.init.Blocks 19 | import net.minecraft.init.Enchantments 20 | import net.minecraft.init.Items 21 | import net.minecraft.inventory.ClickType 22 | import net.minecraft.inventory.Container 23 | import net.minecraft.inventory.Slot 24 | import net.minecraft.item.ItemBlock 25 | import net.minecraft.util.math.Vec3d 26 | import trombone.IO.disableError 27 | import trombone.Trombone.module 28 | import trombone.handler.Container.containerTask 29 | import trombone.handler.Container.getShulkerWith 30 | import trombone.handler.Container.handleRestock 31 | import trombone.task.BlockTask 32 | import trombone.task.TaskState 33 | import java.util.concurrent.ConcurrentLinkedDeque 34 | 35 | object Inventory { 36 | var lastHitVec: Vec3d = Vec3d.ZERO 37 | var waitTicks = 0 38 | 39 | val packetLimiter = ConcurrentLinkedDeque() 40 | 41 | @Suppress("UNUSED") 42 | enum class RotationMode { 43 | OFF, SPOOF 44 | } 45 | 46 | fun SafeClientEvent.updateRotation() { 47 | if (lastHitVec == Vec3d.ZERO) return 48 | val rotation = getRotationTo(lastHitVec) 49 | 50 | module.sendPlayerPacket { 51 | rotate(rotation) 52 | } 53 | } 54 | 55 | private fun SafeClientEvent.getBestTool(blockTask: BlockTask): Slot? { 56 | return player.inventorySlots.asReversed().maxByOrNull { 57 | val stack = it.stack 58 | if (stack.isEmpty) { 59 | 0.0f 60 | } else { 61 | var speed = stack.getDestroySpeed(world.getBlockState(blockTask.blockPos)) 62 | 63 | if (speed > 1.0f) { 64 | val efficiency = EnchantmentHelper.getEnchantmentLevel(Enchantments.EFFICIENCY, stack) 65 | if (efficiency > 0) { 66 | speed += efficiency * efficiency + 1.0f 67 | } 68 | } 69 | 70 | speed 71 | } 72 | } 73 | } 74 | 75 | fun SafeClientEvent.swapOrMoveBlock(blockTask: BlockTask): Boolean { 76 | if (blockTask.isShulker()) { 77 | getShulkerWith(player.inventorySlots, blockTask.item)?.let { slot -> 78 | blockTask.itemID = slot.stack.item.id 79 | slot.toHotbarSlotOrNull()?.let { 80 | swapToSlot(it) 81 | } ?: run { 82 | val slotTo = player.hotbarSlots.firstEmpty()?.hotbarSlot ?: 0 83 | moveToHotbar(module, slot.slotNumber, slotTo) 84 | } 85 | } 86 | return true 87 | } else { 88 | val useMat = findMaterial(blockTask) 89 | if (useMat == Blocks.AIR) return false 90 | 91 | val success = swapToBlockOrMove(module, useMat, predicateSlot = { 92 | it.item is ItemBlock 93 | }) 94 | 95 | return if (!success) { 96 | disableError("Inventory transaction of $useMat failed.") 97 | false 98 | } else { 99 | true 100 | } 101 | } 102 | } 103 | 104 | private fun SafeClientEvent.findMaterial(blockTask: BlockTask): Block { 105 | return if (blockTask.targetBlock == material) { 106 | if (player.inventorySlots.countBlock(material) > saveMaterial) { 107 | material 108 | } else { 109 | restockFallback(blockTask) 110 | Blocks.AIR 111 | } 112 | } else { 113 | if (player.inventorySlots.countBlock(blockTask.targetBlock) > 0) { 114 | blockTask.targetBlock 115 | } else { 116 | val possibleMaterials = mutableSetOf() 117 | InventoryManager.ejectList.forEach { stringName -> 118 | getBlockFromName(stringName)?.let { 119 | if (player.inventorySlots.countBlock(it) > 0) possibleMaterials.add(it) 120 | } 121 | } 122 | 123 | if (possibleMaterials.isEmpty()) { 124 | if (player.inventorySlots.countBlock(material) > saveMaterial) { 125 | material 126 | } else { 127 | restockFallback(blockTask) 128 | Blocks.AIR 129 | } 130 | } else { 131 | possibleMaterials.first() 132 | } 133 | } 134 | } 135 | } 136 | 137 | private fun SafeClientEvent.restockFallback(blockTask: BlockTask) { 138 | if (storageManagement) { 139 | handleRestock(blockTask.targetBlock.item) 140 | } else { 141 | disableError("No usable material was found in inventory.") 142 | } 143 | } 144 | 145 | fun SafeClientEvent.swapOrMoveBestTool(blockTask: BlockTask): Boolean { 146 | if (player.inventorySlots.countItem(Items.DIAMOND_PICKAXE) <= saveTools) { 147 | return if (containerTask.taskState == TaskState.DONE && storageManagement) { 148 | handleRestock(Items.DIAMOND_PICKAXE) 149 | false 150 | } else { 151 | swapOrMoveTool(blockTask) 152 | } 153 | } 154 | 155 | return swapOrMoveTool(blockTask) 156 | } 157 | 158 | fun SafeClientEvent.zipInventory() { 159 | val compressibleStacks = player.inventorySlots.filter { comp -> 160 | comp.stack.count < comp.stack.maxStackSize 161 | && player.inventorySlots.countByStack { comp.stack.item == it.item } > 1 162 | } 163 | 164 | if (compressibleStacks.isEmpty()) { 165 | disableError("Inventory full. (Considering that $keepFreeSlots slots are supposed to stay free)") 166 | return 167 | } 168 | 169 | compressibleStacks.forEach { slot -> 170 | module.addInventoryTask( 171 | PlayerInventoryManager.ClickInfo(slot = slot.slotNumber, type = ClickType.QUICK_MOVE) 172 | ) 173 | } 174 | } 175 | 176 | private fun SafeClientEvent.swapOrMoveTool(blockTask: BlockTask) = 177 | getBestTool(blockTask)?.let { slotFrom -> 178 | blockTask.toolToUse = slotFrom.stack 179 | slotFrom.toHotbarSlotOrNull()?.let { 180 | swapToSlot(it) 181 | } ?: run { 182 | val slotTo = player.hotbarSlots.firstEmpty()?.hotbarSlot ?: 0 183 | moveToHotbar(module, slotFrom.slotNumber, slotTo) 184 | } 185 | true 186 | } ?: run { 187 | false 188 | } 189 | 190 | fun SafeClientEvent.moveToInventory(originSlot: Slot, container: Container) { 191 | container.getSlots(27..62).firstOrNull { 192 | originSlot.stack.item == it.stack.item 193 | && it.stack.count < originSlot.stack.maxStackSize - originSlot.stack.count 194 | }?.let { _ -> 195 | module.addInventoryTask( 196 | PlayerInventoryManager.ClickInfo( 197 | container.windowId, 198 | originSlot.slotNumber, 199 | 0, 200 | ClickType.QUICK_MOVE 201 | ) 202 | ) 203 | } ?: run { 204 | container.getSlots(54..62).firstOrNull { 205 | InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) 206 | || it.stack.isEmpty 207 | }?.let { freeHotbarSlot -> 208 | module.addInventoryTask( 209 | PlayerInventoryManager.ClickInfo( 210 | container.windowId, 211 | originSlot.slotNumber, 212 | freeHotbarSlot.slotNumber - 54, 213 | ClickType.SWAP 214 | ) 215 | ) 216 | } ?: run { 217 | container.getSlots(27..53).firstOrNull { 218 | InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) 219 | || it.stack.isEmpty 220 | }?.let { freeSlot -> 221 | module.addInventoryTask( 222 | PlayerInventoryManager.ClickInfo( 223 | container.windowId, 224 | 0, 225 | freeSlot.slotNumber, 226 | ClickType.SWAP 227 | ), 228 | PlayerInventoryManager.ClickInfo( 229 | container.windowId, 230 | freeSlot.slotNumber, 231 | 0, 232 | ClickType.SWAP 233 | ) 234 | ) 235 | } ?: run { 236 | zipInventory() 237 | } 238 | } 239 | } 240 | } 241 | 242 | fun SafeClientEvent.getEjectSlot(): Slot? { 243 | return player.inventorySlots.firstByStack { 244 | !it.isEmpty && 245 | InventoryManager.ejectList.contains(it.item.registryName.toString()) 246 | } 247 | } 248 | } -------------------------------------------------------------------------------- /src/main/kotlin/trombone/handler/Container.kt: -------------------------------------------------------------------------------- 1 | package trombone.handler 2 | 3 | import HighwayTools.grindObsidian 4 | import HighwayTools.keepFreeSlots 5 | import HighwayTools.material 6 | import HighwayTools.maxReach 7 | import HighwayTools.minDistance 8 | import HighwayTools.preferEnderChests 9 | import HighwayTools.saveEnder 10 | import HighwayTools.saveFood 11 | import HighwayTools.saveMaterial 12 | import HighwayTools.saveTools 13 | import HighwayTools.searchEChest 14 | import com.lambda.client.commons.extension.ceilToInt 15 | import com.lambda.client.event.SafeClientEvent 16 | import com.lambda.client.module.modules.player.InventoryManager 17 | import com.lambda.client.util.EntityUtils.getDroppedItems 18 | import com.lambda.client.util.TickTimer 19 | import com.lambda.client.util.TimeUnit 20 | import com.lambda.client.util.items.* 21 | import com.lambda.client.util.math.VectorUtils 22 | import com.lambda.client.util.math.VectorUtils.toVec3dCenter 23 | import com.lambda.client.util.world.getVisibleSides 24 | import com.lambda.client.util.world.isPlaceable 25 | import com.lambda.client.util.world.isReplaceable 26 | import net.minecraft.init.Blocks 27 | import net.minecraft.init.Items 28 | import net.minecraft.inventory.ItemStackHelper 29 | import net.minecraft.inventory.Slot 30 | import net.minecraft.item.Item 31 | import net.minecraft.item.ItemShulkerBox 32 | import net.minecraft.item.ItemStack 33 | import net.minecraft.util.EnumFacing 34 | import net.minecraft.util.NonNullList 35 | import net.minecraft.util.math.AxisAlignedBB 36 | import net.minecraft.util.math.BlockPos 37 | import net.minecraft.util.text.TextFormatting 38 | import trombone.blueprint.BlueprintGenerator.isInsideBlueprintBuild 39 | import trombone.IO.disableError 40 | import trombone.Pathfinder.currentBlockPos 41 | import trombone.handler.Inventory.zipInventory 42 | import trombone.task.BlockTask 43 | import trombone.task.TaskState 44 | import kotlin.math.abs 45 | 46 | object Container { 47 | var containerTask = BlockTask(BlockPos.ORIGIN, TaskState.DONE, Blocks.AIR) 48 | val shulkerOpenTimer = TickTimer(TimeUnit.TICKS) 49 | var grindCycles = 0 50 | 51 | fun SafeClientEvent.handleRestock(item: Item) { 52 | if (preferEnderChests && item.block == Blocks.OBSIDIAN) { 53 | handleEnderChest(item) 54 | } else { 55 | // Case 1: item is in a shulker in the inventory 56 | getShulkerWith(player.inventorySlots, item)?.let { slot -> 57 | getRemotePos()?.let { pos -> 58 | containerTask = BlockTask(pos, TaskState.PLACE, slot.stack.item.block, item = item) 59 | } ?: run { 60 | disableError("Can't find possible container position (Case: 1)") 61 | } 62 | } ?: run { 63 | handleEnderChest(item) 64 | } 65 | } 66 | } 67 | 68 | private fun SafeClientEvent.handleEnderChest(item: Item) { 69 | if (grindObsidian && item.block == Blocks.OBSIDIAN) { 70 | // Case 2: desired item is Obsidian and grinding E-Chests is allowed 71 | 72 | if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) <= saveEnder) { 73 | handleRestock(Blocks.ENDER_CHEST.item) 74 | return 75 | } 76 | 77 | if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) > saveEnder) { 78 | if (grindCycles > 0) { 79 | getRemotePos()?.let { pos -> 80 | containerTask = BlockTask(pos, TaskState.PLACE, Blocks.ENDER_CHEST, item = Blocks.OBSIDIAN.item) 81 | containerTask.destroy = true 82 | if (grindCycles > 1) containerTask.collect = false 83 | containerTask.itemID = Blocks.OBSIDIAN.id 84 | grindCycles-- 85 | } ?: run { 86 | disableError("Can't find possible container position (Case: 3)") 87 | } 88 | } else { 89 | val freeSlots = player.inventorySlots.count { 90 | it.stack.isEmpty 91 | || InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) 92 | } 93 | 94 | val cycles = (freeSlots - 1 - keepFreeSlots) * 8 95 | 96 | if (cycles > 0) { 97 | grindCycles = cycles 98 | } else { 99 | zipInventory() 100 | } 101 | } 102 | } 103 | } else { 104 | // Case 3: last hope is the ender chest 105 | 106 | if (!searchEChest) { 107 | disableError("${insufficientMaterial(item)}\nTo provide sufficient material, grant access to your ender chest. Activate in settings: ${TextFormatting.GRAY}Storage Management > Search Ender Chest") 108 | return 109 | } 110 | 111 | dispatchEnderChest(item) 112 | } 113 | 114 | } 115 | 116 | private fun SafeClientEvent.dispatchEnderChest(item: Item) { 117 | if (player.inventorySlots.countBlock(Blocks.ENDER_CHEST) > saveEnder) { 118 | getRemotePos()?.let { pos -> 119 | containerTask = BlockTask(pos, TaskState.PLACE, Blocks.ENDER_CHEST, item = item) 120 | containerTask.itemID = Blocks.OBSIDIAN.id 121 | } ?: run { 122 | disableError("Can't find possible container position (Case: 4)") 123 | } 124 | } else { 125 | getShulkerWith(player.inventorySlots, Blocks.ENDER_CHEST.item)?.let { slot -> 126 | getRemotePos()?.let { pos -> 127 | containerTask = BlockTask(pos, TaskState.PLACE, slot.stack.item.block, item = Blocks.ENDER_CHEST.item) 128 | } ?: run { 129 | disableError("Can't find possible container position (Case: 5)") 130 | } 131 | } ?: run { 132 | disableError("No ${Blocks.ENDER_CHEST.localizedName} was found in inventory.") 133 | } 134 | } 135 | } 136 | 137 | private fun SafeClientEvent.getRemotePos(): BlockPos? { 138 | val origin = currentBlockPos.up().toVec3dCenter() 139 | 140 | return VectorUtils.getBlockPosInSphere(origin, maxReach).asSequence() 141 | .filter { pos -> 142 | !isInsideBlueprintBuild(pos) 143 | && pos != currentBlockPos 144 | && world.isPlaceable(pos) 145 | && !world.getBlockState(pos.down()).isReplaceable 146 | && world.isAirBlock(pos.up()) 147 | && getVisibleSides(pos.down()).contains(EnumFacing.UP) 148 | && player.positionVector.distanceTo(pos.toVec3dCenter()) > minDistance 149 | && pos.y >= currentBlockPos.y 150 | }.sortedWith( 151 | compareByDescending { 152 | secureScore(it) 153 | }.thenBy { 154 | it.distanceSqToCenter(origin.x, origin.y, origin.z).ceilToInt() 155 | }.thenBy { 156 | abs(it.y - currentBlockPos.y) 157 | } 158 | ).firstOrNull() 159 | } 160 | 161 | private fun SafeClientEvent.secureScore(pos: BlockPos): Int { 162 | var safe = 0 163 | if (!world.getBlockState(pos.down().north()).isReplaceable) safe++ 164 | if (!world.getBlockState(pos.down().east()).isReplaceable) safe++ 165 | if (!world.getBlockState(pos.down().south()).isReplaceable) safe++ 166 | if (!world.getBlockState(pos.down().west()).isReplaceable) safe++ 167 | return safe 168 | } 169 | 170 | fun getShulkerWith(slots: List, item: Item): Slot? { 171 | return slots.filter { 172 | it.stack.item is ItemShulkerBox && getShulkerData(it.stack, item) > 0 173 | }.minByOrNull { 174 | getShulkerData(it.stack, item) 175 | } 176 | } 177 | 178 | private fun getShulkerData(stack: ItemStack, item: Item): Int { 179 | val tagCompound = if (stack.item is ItemShulkerBox) stack.tagCompound else return 0 180 | 181 | if (tagCompound != null && tagCompound.hasKey("BlockEntityTag", 10)) { 182 | val blockEntityTag = tagCompound.getCompoundTag("BlockEntityTag") 183 | if (blockEntityTag.hasKey("Items", 9)) { 184 | val shulkerInventory = NonNullList.withSize(27, ItemStack.EMPTY) 185 | ItemStackHelper.loadAllItems(blockEntityTag, shulkerInventory) 186 | return shulkerInventory.count { it.item == item } 187 | } 188 | } 189 | 190 | return 0 191 | } 192 | 193 | fun SafeClientEvent.getCollectingPosition(): BlockPos? { 194 | val range = 8f 195 | getDroppedItems(containerTask.itemID, range = range) 196 | .minByOrNull { player.getDistance(it) } 197 | ?.positionVector 198 | ?.let { itemVec -> 199 | return VectorUtils.getBlockPosInSphere(itemVec, range).asSequence() 200 | .filter { pos -> 201 | world.isAirBlock(pos.up()) 202 | && world.isAirBlock(pos) 203 | && !world.isPlaceable(pos.down()) 204 | } 205 | .sortedWith( 206 | compareBy { 207 | it.distanceSqToCenter(itemVec.x, itemVec.y, itemVec.z) 208 | }.thenBy { 209 | it.y 210 | } 211 | ).firstOrNull() 212 | } 213 | return null 214 | } 215 | 216 | private fun SafeClientEvent.insufficientMaterial(item: Item): String { 217 | val itemCount = player.inventorySlots.countItem(item) 218 | var message = "" 219 | if (saveMaterial > 0 && item == material.item) message += insufficientMaterialPrint(itemCount, saveMaterial, material.localizedName) 220 | if (saveEnder > 0 && item.block == Blocks.ENDER_CHEST) message += insufficientMaterialPrint(itemCount, saveEnder, Blocks.ENDER_CHEST.localizedName) 221 | if (saveTools > 0 && item == Items.DIAMOND_PICKAXE) message += insufficientMaterialPrint(itemCount, saveTools, "Diamond Pickaxe(s)") 222 | if (saveFood > 0 && item == Items.GOLDEN_APPLE) message += insufficientMaterialPrint(itemCount, saveFood, "Golden Apple(s)") 223 | return "$message\nTo continue anyways, set setting in ${TextFormatting.GRAY}Storage Management > Save ${TextFormatting.RESET} to zero." 224 | } 225 | 226 | private fun insufficientMaterialPrint(itemCount: Int, settingCount: Int, name: String) = 227 | "For safety purposes you need ${TextFormatting.AQUA}${settingCount - itemCount + 1}${TextFormatting.RED} more $name in your inventory ($itemCount/${settingCount + 1})." 228 | } -------------------------------------------------------------------------------- /src/main/kotlin/HighwayTools.kt: -------------------------------------------------------------------------------- 1 | import com.lambda.client.event.events.PacketEvent 2 | import com.lambda.client.event.events.PlayerTravelEvent 3 | import com.lambda.client.event.events.RenderOverlayEvent 4 | import com.lambda.client.event.events.RenderWorldEvent 5 | import com.lambda.client.event.listener.listener 6 | import com.lambda.client.module.Category 7 | import com.lambda.client.plugin.api.PluginModule 8 | import com.lambda.client.setting.settings.impl.collection.CollectionSetting 9 | import com.lambda.client.util.items.shulkerList 10 | import com.lambda.client.util.threads.* 11 | import net.minecraft.block.Block 12 | import net.minecraft.init.Blocks 13 | import net.minecraft.init.Items 14 | import net.minecraft.item.Item 15 | import net.minecraftforge.fml.common.gameevent.TickEvent 16 | import trombone.IO.DebugLevel 17 | import trombone.IO.DisableMode 18 | import trombone.IO.pauseCheck 19 | import trombone.Pathfinder.updatePathing 20 | import trombone.Renderer.renderOverlay 21 | import trombone.Renderer.renderWorld 22 | import trombone.Trombone.Structure 23 | import trombone.Trombone.active 24 | import trombone.Trombone.tick 25 | import trombone.Trombone.onDisable 26 | import trombone.Trombone.onEnable 27 | import trombone.handler.Packet.handlePacket 28 | 29 | /** 30 | * @author Avanatiker 31 | * @since 20/08/2020 32 | */ 33 | object HighwayTools : PluginModule( 34 | name = "HighwayTools", 35 | description = "Be the grief a step a head.", 36 | category = Category.MISC, 37 | alias = arrayOf("HT", "HWT"), 38 | modulePriority = 10, 39 | pluginMain = HighwayToolsPlugin 40 | ) { 41 | private val page by setting("Page", Page.BLUEPRINT, description = "Switch between setting pages") 42 | 43 | private val defaultIgnoreBlocks = linkedSetOf( 44 | "minecraft:standing_sign", 45 | "minecraft:wall_sign", 46 | "minecraft:standing_banner", 47 | "minecraft:wall_banner", 48 | "minecraft:bedrock", 49 | "minecraft:end_portal", 50 | "minecraft:end_portal_frame", 51 | "minecraft:portal", 52 | "minecraft:piston_extension", 53 | "minecraft:barrier" 54 | ) 55 | 56 | // blueprint 57 | val mode by setting("Mode", Structure.HIGHWAY, { page == Page.BLUEPRINT }, description = "Choose the structure") 58 | val width by setting("Width", 6, 1..11, 1, { page == Page.BLUEPRINT }, description = "Sets the width of blueprint", unit = " blocks") 59 | val height by setting("Height", 4, 2..6, 1, { page == Page.BLUEPRINT && clearSpace }, description = "Sets height of blueprint", unit = " blocks") 60 | val backfill by setting("Backfill", false, { page == Page.BLUEPRINT && mode == Structure.TUNNEL }, description = "Fills the tunnel behind you") 61 | val clearSpace by setting("Clear Space", true, { page == Page.BLUEPRINT && mode == Structure.HIGHWAY }, description = "Clears out the tunnel if necessary") 62 | val cleanFloor by setting("Clean Floor", false, { page == Page.BLUEPRINT && mode == Structure.TUNNEL && !backfill }, description = "Cleans up the tunnels floor") 63 | val cleanRightWall by setting("Clean Right Wall", false, { page == Page.BLUEPRINT && mode == Structure.TUNNEL && !backfill }, description = "Cleans up the right wall") 64 | val cleanLeftWall by setting("Clean Left Wall", false, { page == Page.BLUEPRINT && mode == Structure.TUNNEL && !backfill }, description = "Cleans up the left wall") 65 | val cleanRoof by setting("Clean Roof", false, { page == Page.BLUEPRINT && mode == Structure.TUNNEL && !backfill }, description = "Cleans up the tunnels roof") 66 | val cleanCorner by setting("Clean Corner", false, { page == Page.BLUEPRINT && mode == Structure.TUNNEL && !cornerBlock && !backfill && width > 2 }, description = "Cleans up the tunnels corner") 67 | 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") 68 | val railing by setting("Railing", true, { page == Page.BLUEPRINT && mode == Structure.HIGHWAY }, description = "Adds a railing/rim/border to the highway") 69 | val railingHeight by setting("Railing Height", 1, 1..4, 1, { railing && page == Page.BLUEPRINT && mode == Structure.HIGHWAY }, description = "Sets height of railing", unit = " blocks") 70 | private val materialSaved = setting("Material", "minecraft:obsidian", { false }) 71 | private val fillerMatSaved = setting("FillerMat", "minecraft:netherrack", { false }) 72 | private val foodItem = setting("FoodItem", "minecraft:golden_apple", { false }) 73 | val ignoreBlocks = setting(CollectionSetting("IgnoreList", defaultIgnoreBlocks, { false })) 74 | 75 | // behavior 76 | 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") 77 | val multiBuilding by setting("Shuffle Tasks", false, { page == Page.PLACING }, description = "Only activate when working with several players") 78 | val rubberbandTimeout by setting("Rubberband Timeout", 50, 5..100, 5, { page == Page.BEHAVIOR }, description = "Timeout for pausing after a lag") 79 | 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") 80 | 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") 81 | 82 | // mining 83 | val breakDelay by setting("Break Delay", 1, 1..20, 1, { page == Page.MINING }, description = "Sets the delay ticks between break tasks", unit = " ticks") 84 | 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") 85 | val interactionLimit by setting("Interaction Limit", 20, 1..100, 1, { page == Page.MINING }, description = "Set the interaction limit per second", unit = " interactions/s") 86 | val multiBreak by setting("Multi Break", true, { page == Page.MINING }, description = "Breaks multiple instant breaking blocks intersecting with view vector") 87 | val packetFlood by setting("Packet Flood", false, { page == Page.MINING }, description = "Exploit for faster packet breaks. Sends START and STOP packet on same tick.") 88 | val instantMine by setting("Ender Chest Instant Mine", false, { page == Page.MINING && packetFlood }, description = "Instant mine NCP exploit") 89 | 90 | // placing 91 | val placeDelay by setting("Place Delay", 3, 1..20, 1, { page == Page.PLACING }, description = "Sets the delay ticks between placement tasks") 92 | val dynamicDelay by setting("Dynamic Place Delay", true, { page == Page.PLACING }, description = "Slows down on failed placement attempts") 93 | val illegalPlacements by setting("Illegal Placements", false, { page == Page.PLACING }, description = "Do not use on 2b2t. Tries to interact with invisible surfaces") 94 | val scaffold by setting("Scaffold", true, { page == Page.PLACING }, description = "Tries to bridge / scaffold when stuck placing") 95 | 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") 96 | 97 | // storage management 98 | val storageManagement by setting("Manage Storage", true, { page == Page.STORAGE_MANAGEMENT }, description = "Choose to interact with container using only packets") 99 | val searchEChest by setting("Search Ender Chest", false, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Allow access to your ender chest") 100 | val leaveEmptyShulkers by setting("Leave Empty Shulkers", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Does not break empty shulkers") 101 | val grindObsidian by setting("Grind Obsidian", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Destroy Ender Chests to obtain Obsidian") 102 | val fastFill by setting("Fast Fill", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Moves as many item stacks to inventory as possible") 103 | 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") 104 | val preferEnderChests by setting("Prefer Ender Chests", false, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Prevent using raw material shulkers") 105 | val manageFood by setting("Manage Food", true, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "Choose to manage food") 106 | val saveMaterial by setting("Save Material", 12, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many material blocks are saved") 107 | val saveTools by setting("Save Tools", 1, 0..36, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many tools are saved") 108 | val saveEnder by setting("Save Ender Chests", 1, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && storageManagement }, description = "How many ender chests are saved") 109 | val saveFood by setting("Save Food", 1, 0..64, 1, { page == Page.STORAGE_MANAGEMENT && manageFood && storageManagement}, description = "How many food items are saved") 110 | 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") 111 | val disableMode by setting("Disable Mode", DisableMode.NONE, { page == Page.STORAGE_MANAGEMENT }, description = "Choose action when bot is out of materials or tools") 112 | 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") 113 | val proxyCommand by setting("Proxy Command", "/dc", { usingProxy && disableMode == DisableMode.LOGOUT && page == Page.STORAGE_MANAGEMENT }, description = "Command to be sent to log out") 114 | 115 | // render 116 | val anonymizeStats by setting("Anonymize", false, { page == Page.RENDER }, description = "Censors all coordinates in HUD and Chat") 117 | val fakeSounds by setting("Fake Sounds", true, { page == Page.RENDER }, description = "Adds artificial sounds to the actions") 118 | val info by setting("Show Info", true, { page == Page.RENDER }, description = "Prints session stats in chat") 119 | val debugLevel by setting("Debug Level", DebugLevel.IMPORTANT, { page == Page.RENDER }, description = "Sets the debug log depth level") 120 | val goalRender by setting("Baritone Goal", false, { page == Page.RENDER }, description = "Renders the baritone goal") 121 | val showCurrentPos by setting("Current Pos", false, { page == Page.RENDER }, description = "Renders the current position") 122 | val filled by setting("Filled", true, { page == Page.RENDER }, description = "Renders colored task surfaces") 123 | val outline by setting("Outline", true, { page == Page.RENDER }, description = "Renders colored task outlines") 124 | val popUp by setting("Pop up", true, { page == Page.RENDER }, description = "Funny render effect") 125 | 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") 126 | val showDebugRender by setting("Debug Render", false, { page == Page.RENDER }, description = "Render debug info on tasks") 127 | val textScale by setting("Text Scale", 1.0f, 0.0f..4.0f, 0.25f, { showDebugRender && page == Page.RENDER }, description = "Scale of debug text") 128 | val disableWarnings by setting("Disable Warnings", false, { page == Page.RENDER }, description = "DANGEROUS: Disable warnings on enable") 129 | val aFilled by setting("Filled Alpha", 26, 0..255, 1, { filled && page == Page.RENDER }, description = "Sets the opacity") 130 | val aOutline by setting("Outline Alpha", 91, 0..255, 1, { outline && page == Page.RENDER }, description = "Sets the opacity") 131 | val thickness by setting("Thickness", 2.0f, 0.25f..4.0f, 0.25f, { outline && page == Page.RENDER }, description = "Sets thickness of outline") 132 | 133 | private enum class Page { 134 | BLUEPRINT, BEHAVIOR, MINING, PLACING, STORAGE_MANAGEMENT, RENDER 135 | } 136 | 137 | // internal settings 138 | var material: Block 139 | get() = Block.getBlockFromName(materialSaved.value) ?: Blocks.OBSIDIAN 140 | set(value) { 141 | materialSaved.value = value.registryName.toString() 142 | } 143 | var fillerMat: Block 144 | get() = Block.getBlockFromName(fillerMatSaved.value) ?: Blocks.NETHERRACK 145 | set(value) { 146 | fillerMatSaved.value = value.registryName.toString() 147 | } 148 | var food: Item 149 | get() = Item.getByNameOrId(foodItem.value) ?: Items.GOLDEN_APPLE 150 | set(value) { 151 | foodItem.value = value.registryName.toString() 152 | } 153 | 154 | 155 | override fun isActive(): Boolean { 156 | return isEnabled && active 157 | } 158 | 159 | init { 160 | shulkerList.forEach { 161 | ignoreBlocks.add(it.registryName.toString()) 162 | } 163 | 164 | onEnable { 165 | runSafeR { 166 | onEnable() 167 | } ?: disable() 168 | } 169 | 170 | onDisable { 171 | runSafe { 172 | onDisable() 173 | } 174 | } 175 | } 176 | 177 | init { 178 | safeListener { 179 | handlePacket(it.packet) 180 | } 181 | 182 | listener { 183 | renderWorld() 184 | } 185 | 186 | listener { 187 | renderOverlay() 188 | } 189 | 190 | safeListener { 191 | if (it.phase == TickEvent.Phase.START) tick() 192 | } 193 | 194 | safeListener { 195 | if (!pauseCheck()) updatePathing() 196 | } 197 | } 198 | } -------------------------------------------------------------------------------- /src/main/kotlin/trombone/task/TaskManager.kt: -------------------------------------------------------------------------------- 1 | package trombone.task 2 | 3 | import HighwayTools.anonymizeStats 4 | import HighwayTools.debugLevel 5 | import HighwayTools.dynamicDelay 6 | import HighwayTools.food 7 | import HighwayTools.ignoreBlocks 8 | import HighwayTools.manageFood 9 | import HighwayTools.material 10 | import HighwayTools.maxReach 11 | import HighwayTools.multiBuilding 12 | import HighwayTools.saveFood 13 | import HighwayTools.saveTools 14 | import HighwayTools.storageManagement 15 | import HighwayTools.width 16 | import com.lambda.client.event.SafeClientEvent 17 | import com.lambda.client.manager.managers.PlayerInventoryManager 18 | import com.lambda.client.util.items.countItem 19 | import com.lambda.client.util.items.inventorySlots 20 | import com.lambda.client.util.items.item 21 | import com.lambda.client.util.math.CoordinateConverter.asString 22 | import com.lambda.client.util.math.VectorUtils.distanceTo 23 | import com.lambda.client.util.math.VectorUtils.multiply 24 | import com.lambda.client.util.math.VectorUtils.toVec3dCenter 25 | import com.lambda.client.util.text.MessageSendHelper 26 | import com.lambda.client.util.world.isPlaceable 27 | import com.lambda.client.util.world.isReplaceable 28 | import net.minecraft.block.BlockLiquid 29 | import net.minecraft.block.state.IBlockState 30 | import net.minecraft.init.Blocks 31 | import net.minecraft.init.Items 32 | import net.minecraft.item.ItemFood 33 | import net.minecraft.item.ItemPickaxe 34 | import net.minecraft.util.math.AxisAlignedBB 35 | import net.minecraft.util.math.BlockPos 36 | import net.minecraft.util.math.Vec3d 37 | import trombone.blueprint.BlueprintGenerator.blueprint 38 | import trombone.blueprint.BlueprintGenerator.generateBluePrint 39 | import trombone.blueprint.BlueprintGenerator.isInsideBlueprintBuild 40 | import trombone.IO.DebugLevel 41 | import trombone.Pathfinder.MovementState 42 | import trombone.Pathfinder.currentBlockPos 43 | import trombone.Pathfinder.moveState 44 | import trombone.Pathfinder.startingBlockPos 45 | import trombone.Pathfinder.startingDirection 46 | import trombone.Trombone.module 47 | import trombone.blueprint.BlueprintTask 48 | import trombone.handler.Container.containerTask 49 | import trombone.handler.Container.grindCycles 50 | import trombone.handler.Container.handleRestock 51 | import trombone.handler.Inventory.waitTicks 52 | import trombone.interaction.Place.extraPlaceDelay 53 | import trombone.task.TaskExecutor.doTask 54 | import java.util.concurrent.ConcurrentHashMap 55 | import java.util.concurrent.ConcurrentSkipListSet 56 | 57 | object TaskManager { 58 | val tasks = ConcurrentHashMap() 59 | val sortedTasks = ConcurrentSkipListSet(blockTaskComparator()) 60 | var lastTask: BlockTask? = null 61 | 62 | fun SafeClientEvent.populateTasks() { 63 | generateBluePrint() 64 | 65 | /* Generate tasks based on the blueprint */ 66 | blueprint.forEach { (pos, blueprintTask) -> 67 | generateTask(pos, blueprintTask) 68 | } 69 | 70 | /* Remove old tasks */ 71 | tasks.filter { 72 | it.value.taskState == TaskState.DONE 73 | && currentBlockPos.distanceTo(it.key) > maxReach + 2 74 | }.forEach { 75 | if (it.value.toRemove) { 76 | if (System.currentTimeMillis() - it.value.timestamp > 1000L) tasks.remove(it.key) 77 | } else { 78 | it.value.toRemove = true 79 | it.value.timestamp = System.currentTimeMillis() 80 | } 81 | } 82 | } 83 | 84 | private fun SafeClientEvent.generateTask(blockPos: BlockPos, blueprintTask: BlueprintTask) { 85 | val currentState = world.getBlockState(blockPos) 86 | val eyePos = player.getPositionEyes(1f) 87 | 88 | when { 89 | /* start padding */ 90 | startPadding(blockPos) -> { /* Ignore task */ } 91 | 92 | /* out of reach */ 93 | eyePos.distanceTo(blockPos.toVec3dCenter()) >= maxReach + 1 -> { /* Ignore task */ } 94 | 95 | /* do not override container task */ 96 | containerTask.blockPos == blockPos -> { /* Ignore task */ } 97 | 98 | /* ignored blocks */ 99 | shouldBeIgnored(blockPos, currentState) -> { 100 | val blockTask = BlockTask(blockPos, TaskState.DONE, currentState.block) 101 | addTask(blockTask, blueprintTask) 102 | } 103 | 104 | /* is in desired state */ 105 | currentState.block == blueprintTask.targetBlock -> { 106 | val blockTask = BlockTask(blockPos, TaskState.DONE, currentState.block) 107 | addTask(blockTask, blueprintTask) 108 | } 109 | 110 | /* is liquid */ 111 | currentState.block is BlockLiquid -> { 112 | val blockTask = BlockTask(blockPos, TaskState.LIQUID, blueprintTask.targetBlock) 113 | blockTask.updateTask(this) 114 | 115 | if (blockTask.sequence.isNotEmpty()) { 116 | addTask(blockTask, blueprintTask) 117 | } 118 | } 119 | 120 | /* to place */ 121 | currentState.isReplaceable && blueprintTask.targetBlock != Blocks.AIR -> { 122 | /* support not needed */ 123 | if (blueprintTask.isSupport && world.getBlockState(blockPos.up()).block == material) { 124 | val blockTask = BlockTask(blockPos, TaskState.DONE, currentState.block) 125 | addTask(blockTask, blueprintTask) 126 | return 127 | } 128 | 129 | /* is blocked by entity */ 130 | if (!world.checkNoEntityCollision(AxisAlignedBB(blockPos), null)) { 131 | val blockTask = BlockTask(blockPos, TaskState.DONE, currentState.block) 132 | addTask(blockTask, blueprintTask) 133 | return 134 | } 135 | 136 | val blockTask = BlockTask(blockPos, TaskState.PLACE, blueprintTask.targetBlock) 137 | blockTask.updateTask(this) 138 | 139 | if (blockTask.sequence.isNotEmpty()) { 140 | addTask(blockTask, blueprintTask) 141 | } else { 142 | blockTask.updateState(TaskState.IMPOSSIBLE_PLACE) 143 | addTask(blockTask, blueprintTask) 144 | } 145 | } 146 | 147 | /* To break */ 148 | else -> { 149 | /* Is already filled */ 150 | if (blueprintTask.isFiller) { 151 | val blockTask = BlockTask(blockPos, TaskState.DONE, currentState.block) 152 | addTask(blockTask, blueprintTask) 153 | return 154 | } 155 | 156 | val blockTask = BlockTask(blockPos, TaskState.BREAK, blueprintTask.targetBlock) 157 | blockTask.updateTask(this) 158 | 159 | if (blockTask.eyeDistance < maxReach) { 160 | addTask(blockTask, blueprintTask) 161 | } 162 | } 163 | } 164 | } 165 | 166 | fun SafeClientEvent.runTasks() { 167 | when { 168 | /* Finish the container task first */ 169 | containerTask.taskState != TaskState.DONE -> { 170 | containerTask.updateTask(this) 171 | if (containerTask.stuckTicks > containerTask.taskState.stuckTimeout) { 172 | if (containerTask.taskState == TaskState.PICKUP) moveState = MovementState.RUNNING 173 | 174 | MessageSendHelper.sendWarningMessage("${module.chatName} Failed container action ${containerTask.taskState.name} with ${containerTask.item.registryName}@(${containerTask.blockPos.asString()}) stuck for ${containerTask.stuckTicks} ticks") 175 | containerTask.updateState(TaskState.DONE) 176 | } else { 177 | tasks.values.forEach { 178 | doTask(it, updateOnly = true) 179 | } 180 | doTask(containerTask) 181 | } 182 | } 183 | 184 | /* Check tools */ 185 | storageManagement 186 | && player.inventorySlots.countItem() <= saveTools -> { 187 | // TODO: ItemPickaxe support 188 | handleRestock(Items.DIAMOND_PICKAXE) 189 | } 190 | 191 | /* Fulfill basic needs */ 192 | storageManagement 193 | && manageFood 194 | && player.inventorySlots.countItem() <= saveFood -> { 195 | // TODO: ItemFood support 196 | handleRestock(food) 197 | } 198 | 199 | /* Restock obsidian if needed */ 200 | storageManagement && grindCycles > 0 && material == Blocks.OBSIDIAN -> { 201 | handleRestock(material.item) 202 | } 203 | 204 | /* Actually run the tasks */ 205 | else -> { 206 | waitTicks-- 207 | 208 | /* Only update tasks to check for changed circumstances */ 209 | tasks.values.forEach { 210 | doTask(it, updateOnly = true) 211 | 212 | it.updateTask(this) 213 | if (multiBuilding) it.shuffle() 214 | } 215 | 216 | sortedTasks.clear() 217 | sortedTasks.addAll(tasks.values) 218 | 219 | sortedTasks.forEach taskExecution@{ task -> 220 | if (!checkStuckTimeout(task)) return 221 | if (task.taskState != TaskState.DONE && waitTicks > 0) return 222 | 223 | doTask(task) 224 | when (task.taskState) { 225 | TaskState.DONE, TaskState.BROKEN, TaskState.PLACED -> return@taskExecution 226 | else -> return 227 | } 228 | } 229 | } 230 | } 231 | } 232 | 233 | fun SafeClientEvent.addTask(blockTask: BlockTask, blueprintTask: BlueprintTask) { 234 | blockTask.updateTask(this) 235 | blockTask.isFiller = blueprintTask.isFiller 236 | blockTask.isSupport = blueprintTask.isSupport 237 | 238 | tasks[blockTask.blockPos]?.let { 239 | if (it.stuckTicks > it.taskState.stuckTimeout 240 | || blockTask.taskState == TaskState.LIQUID 241 | || (it.taskState != blockTask.taskState 242 | && (it.taskState == TaskState.DONE 243 | || it.taskState == TaskState.IMPOSSIBLE_PLACE 244 | || (it.taskState == TaskState.PLACE 245 | && !world.isPlaceable(it.blockPos) 246 | )))) { 247 | tasks[blockTask.blockPos] = blockTask 248 | } 249 | } ?: run { 250 | tasks[blockTask.blockPos] = blockTask 251 | } 252 | } 253 | 254 | private fun checkStuckTimeout(blockTask: BlockTask): Boolean { 255 | val timeout = blockTask.taskState.stuckTimeout 256 | 257 | if (blockTask.stuckTicks < timeout) return true 258 | 259 | if (blockTask.taskState == TaskState.DONE) return true 260 | 261 | if (blockTask.taskState == TaskState.PENDING_BREAK) { 262 | blockTask.updateState(TaskState.BREAK) 263 | return false 264 | } 265 | 266 | if (blockTask.taskState == TaskState.PENDING_PLACE) { 267 | blockTask.updateState(TaskState.PLACE) 268 | return false 269 | } 270 | 271 | if (debugLevel != DebugLevel.OFF) { 272 | if (!anonymizeStats) { 273 | MessageSendHelper.sendChatMessage("${module.chatName} Stuck while ${blockTask.taskState}@(${blockTask.blockPos.asString()}) for more than $timeout ticks (${blockTask.stuckTicks}), refreshing data.") 274 | } else { 275 | MessageSendHelper.sendChatMessage("${module.chatName} Stuck while ${blockTask.taskState} for more than $timeout ticks (${blockTask.stuckTicks}), refreshing data.") 276 | } 277 | } 278 | 279 | when (blockTask.taskState) { 280 | TaskState.PLACE -> { 281 | if (dynamicDelay && extraPlaceDelay < 10 && moveState != MovementState.BRIDGE) extraPlaceDelay += 1 282 | } 283 | TaskState.PICKUP -> { 284 | MessageSendHelper.sendChatMessage("${module.chatName} Can't pickup ${containerTask.item.registryName}@(${containerTask.blockPos.asString()})") 285 | blockTask.updateState(TaskState.DONE) 286 | moveState = MovementState.RUNNING 287 | } 288 | else -> { 289 | blockTask.updateState(TaskState.DONE) 290 | } 291 | } 292 | return false 293 | } 294 | 295 | private fun startPadding(c: BlockPos) = isBehindPos(startingBlockPos.add(startingDirection.directionVec), c) 296 | 297 | fun isBehindPos(origin: BlockPos, check: BlockPos): Boolean { 298 | val a = origin.add(startingDirection.counterClockwise(2).directionVec.multiply(width)) 299 | val b = origin.add(startingDirection.clockwise(2).directionVec.multiply(width)) 300 | 301 | return ((b.x - a.x) * (check.z - a.z) - (b.z - a.z) * (check.x - a.x)) > 0 302 | } 303 | 304 | private fun shouldBeIgnored(blockPos: BlockPos, currentState: IBlockState) = 305 | ignoreBlocks.contains(currentState.block.registryName.toString()) 306 | && !isInsideBlueprintBuild(blockPos) 307 | && currentBlockPos.add(startingDirection.directionVec) != blockPos 308 | 309 | fun clearTasks() { 310 | tasks.clear() 311 | sortedTasks.clear() 312 | containerTask.updateState(TaskState.DONE) 313 | lastTask = null 314 | grindCycles = 0 315 | } 316 | 317 | private fun blockTaskComparator() = compareBy { 318 | it.taskState.ordinal 319 | }.thenBy { 320 | it.stuckTicks 321 | }.thenBy { 322 | if (it.isLiquidSource) 0 else 1 323 | }.thenBy { 324 | if (moveState == MovementState.BRIDGE) { 325 | if (it.sequence.isEmpty()) 69 else it.sequence.size 326 | } else { 327 | if (multiBuilding) it.shuffle else it.startDistance 328 | } 329 | }.thenBy { 330 | it.eyeDistance 331 | } 332 | } -------------------------------------------------------------------------------- /src/main/kotlin/trombone/Statistics.kt: -------------------------------------------------------------------------------- 1 | package trombone 2 | 3 | import HighwayTools.anonymizeStats 4 | import HighwayTools.breakDelay 5 | import HighwayTools.dynamicDelay 6 | import HighwayTools.fillerMat 7 | import HighwayTools.height 8 | import HighwayTools.material 9 | import HighwayTools.mode 10 | import HighwayTools.placeDelay 11 | import HighwayTools.width 12 | import HighwayToolsHud 13 | import HighwayToolsHud.showEnvironment 14 | import HighwayToolsHud.showEstimations 15 | import HighwayToolsHud.showLifeTime 16 | import HighwayToolsHud.showPerformance 17 | import HighwayToolsHud.showQueue 18 | import HighwayToolsHud.showSession 19 | import HighwayToolsHud.showTask 20 | import com.lambda.client.event.SafeClientEvent 21 | import com.lambda.client.module.modules.client.Hud.primaryColor 22 | import com.lambda.client.module.modules.client.Hud.secondaryColor 23 | import com.lambda.client.util.graphics.font.TextComponent 24 | import com.lambda.client.util.items.countBlock 25 | import com.lambda.client.util.items.countItem 26 | import com.lambda.client.util.items.inventorySlots 27 | import com.lambda.client.util.items.item 28 | import com.lambda.client.util.math.CoordinateConverter.asString 29 | import com.lambda.client.util.math.VectorUtils.distanceTo 30 | import com.lambda.client.util.math.VectorUtils.multiply 31 | import net.minecraft.init.Blocks 32 | import net.minecraft.init.Items 33 | import net.minecraft.item.ItemPickaxe 34 | import net.minecraft.network.play.client.CPacketClientStatus 35 | import net.minecraft.stats.StatList 36 | import trombone.IO.disableError 37 | import trombone.Pathfinder.currentBlockPos 38 | import trombone.Pathfinder.moveState 39 | import trombone.Pathfinder.startingBlockPos 40 | import trombone.Pathfinder.startingDirection 41 | import trombone.Trombone.Structure 42 | import trombone.handler.Container.containerTask 43 | import trombone.handler.Container.grindCycles 44 | import trombone.handler.Inventory.packetLimiter 45 | import trombone.interaction.Place.extraPlaceDelay 46 | import trombone.task.BlockTask 47 | import trombone.task.TaskManager.sortedTasks 48 | import trombone.task.TaskState 49 | import java.util.concurrent.ConcurrentLinkedDeque 50 | 51 | object Statistics { 52 | val simpleMovingAveragePlaces = ConcurrentLinkedDeque() 53 | val simpleMovingAverageBreaks = ConcurrentLinkedDeque() 54 | val simpleMovingAverageDistance = ConcurrentLinkedDeque() 55 | var totalBlocksPlaced = 0 56 | var totalBlocksBroken = 0 57 | private var totalDistance = 0.0 58 | private var runtimeMilliSeconds = 0 59 | private var prevFood = 0 60 | private var foodLoss = 1 61 | private var materialLeft = 0 62 | private var fillerMatLeft = 0 63 | private var lastToolDamage = 0 64 | var durabilityUsages = 0 65 | private var matPlaced = 0 66 | private var matMined = 0 67 | private var enderMined = 0 68 | private var netherrackMined = 0 69 | private var pickaxeBroken = 0 70 | 71 | fun SafeClientEvent.updateStats() { 72 | updateFood() 73 | 74 | /* Update the minecraft statistics all 15 seconds */ 75 | if (runtimeMilliSeconds % 15000 == 0) { 76 | connection.sendPacket(CPacketClientStatus(CPacketClientStatus.State.REQUEST_STATS)) 77 | } 78 | runtimeMilliSeconds += 50 79 | 80 | updateDequeues() 81 | } 82 | 83 | private fun SafeClientEvent.updateFood() { 84 | val currentFood = player.foodStats.foodLevel 85 | if (currentFood < 7.0) { 86 | disableError("Out of food") 87 | } 88 | if (currentFood != prevFood) { 89 | if (currentFood < prevFood) foodLoss++ 90 | prevFood = currentFood 91 | } 92 | } 93 | 94 | fun updateTotalDistance() { 95 | totalDistance += startingBlockPos.distanceTo(currentBlockPos) 96 | } 97 | 98 | private fun updateDequeues() { 99 | val removeTime = System.currentTimeMillis() - HighwayToolsHud.simpleMovingAverageRange * 1000L 100 | 101 | updateDeque(simpleMovingAveragePlaces, removeTime) 102 | updateDeque(simpleMovingAverageBreaks, removeTime) 103 | updateDeque(simpleMovingAverageDistance, removeTime) 104 | 105 | updateDeque(packetLimiter, System.currentTimeMillis() - 1000L) 106 | } 107 | 108 | private fun updateDeque(deque: ConcurrentLinkedDeque, removeTime: Long) { 109 | while (deque.isNotEmpty() && deque.first() < removeTime) { 110 | deque.removeFirst() 111 | } 112 | } 113 | 114 | fun SafeClientEvent.gatherStatistics(displayText: TextComponent) { 115 | val runtimeSec = (runtimeMilliSeconds / 1000) + 0.0001 116 | val distanceDone = startingBlockPos.distanceTo(currentBlockPos).toInt() + totalDistance 117 | 118 | if (showSession) gatherSession(displayText, runtimeSec) 119 | 120 | if (showLifeTime) gatherLifeTime(displayText) 121 | 122 | if (showPerformance) gatherPerformance(displayText, runtimeSec, distanceDone) 123 | 124 | if (showEnvironment) gatherEnvironment(displayText) 125 | 126 | if (showTask) gatherTask(displayText) 127 | 128 | if (showEstimations) gatherEstimations(displayText, runtimeSec, distanceDone) 129 | 130 | if (showQueue) gatherQueue(displayText) 131 | 132 | displayText.addLine("by Constructor#9948/Avanatiker", primaryColor, scale = 0.6f) 133 | } 134 | 135 | private fun gatherSession(displayText: TextComponent, runtimeSec: Double) { 136 | val seconds = (runtimeSec % 60.0).toInt().toString().padStart(2, '0') 137 | val minutes = ((runtimeSec % 3600.0) / 60.0).toInt().toString().padStart(2, '0') 138 | val hours = (runtimeSec / 3600.0).toInt().toString().padStart(2, '0') 139 | 140 | displayText.addLine("Session", primaryColor) 141 | 142 | displayText.add(" Runtime:", primaryColor) 143 | displayText.addLine("$hours:$minutes:$seconds", secondaryColor) 144 | 145 | displayText.add(" Direction:", primaryColor) 146 | displayText.addLine("${startingDirection.displayName} / ${startingDirection.displayNameXY}", secondaryColor) 147 | 148 | if (!anonymizeStats) displayText.add(" Start:", primaryColor) 149 | if (!anonymizeStats) displayText.addLine("(${startingBlockPos.asString()})", secondaryColor) 150 | 151 | displayText.add(" Placed / destroyed:", primaryColor) 152 | displayText.addLine("%,d".format(totalBlocksPlaced) + " / " + "%,d".format(totalBlocksBroken), secondaryColor) 153 | 154 | 155 | } 156 | 157 | private fun SafeClientEvent.gatherLifeTime(displayText: TextComponent) { 158 | matPlaced = StatList.getObjectUseStats(material.item)?.let { 159 | player.statFileWriter.readStat(it) 160 | } ?: 0 161 | matMined = StatList.getBlockStats(material)?.let { 162 | player.statFileWriter.readStat(it) 163 | } ?: 0 164 | enderMined = StatList.getBlockStats(Blocks.ENDER_CHEST)?.let { 165 | player.statFileWriter.readStat(it) 166 | } ?: 0 167 | netherrackMined = StatList.getBlockStats(Blocks.NETHERRACK)?.let { 168 | player.statFileWriter.readStat(it) 169 | } ?: 0 170 | pickaxeBroken = StatList.getObjectBreakStats(Items.DIAMOND_PICKAXE)?.let { 171 | player.statFileWriter.readStat(it) 172 | } ?: 0 173 | 174 | if (matPlaced + matMined + enderMined + netherrackMined + pickaxeBroken > 0) { 175 | displayText.addLine("Lifetime", primaryColor) 176 | } 177 | 178 | if (mode == Structure.HIGHWAY || mode == Structure.FLAT) { 179 | if (matPlaced > 0) { 180 | displayText.add(" ${material.localizedName} placed:", primaryColor) 181 | displayText.addLine("%,d".format(matPlaced), secondaryColor) 182 | } 183 | 184 | if (matMined > 0) { 185 | displayText.add(" ${material.localizedName} mined:", primaryColor) 186 | displayText.addLine("%,d".format(matMined), secondaryColor) 187 | } 188 | 189 | if (enderMined > 0) { 190 | displayText.add(" ${Blocks.ENDER_CHEST.localizedName} mined:", primaryColor) 191 | displayText.addLine("%,d".format(enderMined), secondaryColor) 192 | } 193 | } 194 | 195 | if (netherrackMined > 0) { 196 | displayText.add(" ${Blocks.NETHERRACK.localizedName} mined:", primaryColor) 197 | displayText.addLine("%,d".format(netherrackMined), secondaryColor) 198 | } 199 | 200 | if (pickaxeBroken > 0) { 201 | displayText.add(" Diamond Pickaxe broken:", primaryColor) 202 | displayText.addLine("%,d".format(pickaxeBroken), secondaryColor) 203 | } 204 | } 205 | 206 | private fun gatherPerformance(displayText: TextComponent, runtimeSec: Double, distanceDone: Double) { 207 | displayText.addLine("Performance", primaryColor) 208 | 209 | displayText.add(" Placements / s: ", primaryColor) 210 | displayText.addLine("%.2f SMA(%.2f)".format(totalBlocksPlaced / runtimeSec, simpleMovingAveragePlaces.size / HighwayToolsHud.simpleMovingAverageRange.toDouble()), secondaryColor) 211 | 212 | displayText.add(" Breaks / s:", primaryColor) 213 | displayText.addLine("%.2f SMA(%.2f)".format(totalBlocksBroken / runtimeSec, simpleMovingAverageBreaks.size / HighwayToolsHud.simpleMovingAverageRange.toDouble()), secondaryColor) 214 | 215 | displayText.add(" Distance km / h:", primaryColor) 216 | 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) 217 | 218 | displayText.add(" Food level loss / h:", primaryColor) 219 | displayText.addLine("%.2f".format(totalBlocksBroken / foodLoss.toDouble()), secondaryColor) 220 | 221 | displayText.add(" Pickaxes / h:", primaryColor) 222 | displayText.addLine("%.2f".format((durabilityUsages / runtimeSec) * 60.0 * 60.0 / 1561.0), secondaryColor) 223 | 224 | displayText.add(" Mining packets / s:", primaryColor) 225 | displayText.addLine("${packetLimiter.size}", secondaryColor) 226 | } 227 | 228 | private fun gatherEnvironment(displayText: TextComponent) { 229 | displayText.addLine("Environment", primaryColor) 230 | 231 | displayText.add(" Materials:", primaryColor) 232 | displayText.addLine("Main(${material.localizedName}) Filler(${fillerMat.localizedName})", secondaryColor) 233 | 234 | displayText.add(" Dimensions:", primaryColor) 235 | displayText.addLine("Width($width) Height($height)", secondaryColor) 236 | 237 | displayText.add(" Delays:", primaryColor) 238 | if (dynamicDelay) { 239 | displayText.addLine("Place(${placeDelay + extraPlaceDelay}) Break($breakDelay)", secondaryColor) 240 | } else { 241 | displayText.addLine("Place($placeDelay) Break($breakDelay)", secondaryColor) 242 | } 243 | 244 | displayText.add(" Movement:", primaryColor) 245 | displayText.addLine("$moveState", secondaryColor) 246 | } 247 | 248 | private fun gatherTask(displayText: TextComponent) { 249 | val task: BlockTask? = if (containerTask.taskState != TaskState.DONE) { 250 | containerTask 251 | } else { 252 | sortedTasks.firstOrNull() 253 | } 254 | task?.let { 255 | displayText.addLine("Task", primaryColor) 256 | 257 | displayText.add(" Status:", primaryColor) 258 | displayText.addLine("${it.taskState}", secondaryColor) 259 | 260 | displayText.add(" Target block:", primaryColor) 261 | displayText.addLine(it.targetBlock.localizedName, secondaryColor) 262 | 263 | if (it.item != Items.AIR) { 264 | displayText.add(" Target item:", primaryColor) 265 | displayText.addLine(it.targetBlock.localizedName, secondaryColor) 266 | } 267 | 268 | if (!anonymizeStats) { 269 | displayText.add(" Position:", primaryColor) 270 | displayText.addLine("(${it.blockPos.asString()})", secondaryColor) 271 | } 272 | 273 | displayText.add(" Ticks stuck:", primaryColor) 274 | displayText.addLine("${it.stuckTicks}", secondaryColor) 275 | } 276 | } 277 | 278 | private fun SafeClientEvent.gatherEstimations(displayText: TextComponent, runtimeSec: Double, distanceDone: Double) { 279 | when (mode) { 280 | Structure.HIGHWAY, Structure.FLAT -> { 281 | materialLeft = player.inventorySlots.countBlock(material) 282 | fillerMatLeft = player.inventorySlots.countBlock(fillerMat) 283 | val indirectMaterialLeft = 8 * player.inventorySlots.countBlock(Blocks.ENDER_CHEST) 284 | 285 | val pavingLeft = materialLeft / (totalBlocksPlaced.coerceAtLeast(1) / distanceDone.coerceAtLeast(1.0)) 286 | 287 | // ToDo: Cache shulker count 288 | 289 | // val pavingLeftAll = (materialLeft + indirectMaterialLeft) / ((totalBlocksPlaced + 0.001) / (distanceDone + 0.001)) 290 | 291 | val secLeft = (pavingLeft).coerceAtLeast(0.0) / (startingBlockPos.distanceTo(currentBlockPos).toInt() / runtimeSec) 292 | val secondsLeft = (secLeft % 60).toInt().toString().padStart(2, '0') 293 | val minutesLeft = ((secLeft % 3600) / 60).toInt().toString().padStart(2, '0') 294 | val hoursLeft = (secLeft / 3600).toInt().toString().padStart(2, '0') 295 | 296 | displayText.addLine("Refill", primaryColor) 297 | displayText.add(" ${material.localizedName}:", primaryColor) 298 | 299 | if (material == Blocks.OBSIDIAN) { 300 | displayText.addLine("Direct($materialLeft) Indirect($indirectMaterialLeft)", secondaryColor) 301 | } else { 302 | displayText.addLine("$materialLeft", secondaryColor) 303 | } 304 | 305 | displayText.add(" ${fillerMat.localizedName}:", primaryColor) 306 | displayText.addLine("$fillerMatLeft", secondaryColor) 307 | 308 | if (grindCycles > 0) { 309 | displayText.add(" Ender Chest cycles left:", primaryColor) 310 | displayText.addLine("$grindCycles", secondaryColor) 311 | } else { 312 | displayText.add(" Distance left:", primaryColor) 313 | displayText.addLine("${pavingLeft.toInt()}", secondaryColor) 314 | 315 | if (!anonymizeStats) displayText.add(" Destination:", primaryColor) 316 | if (!anonymizeStats) displayText.addLine("(${currentBlockPos.add(startingDirection.directionVec.multiply(pavingLeft.toInt())).asString()})", secondaryColor) 317 | 318 | displayText.add(" ETA:", primaryColor) 319 | displayText.addLine("$hoursLeft:$minutesLeft:$secondsLeft", secondaryColor) 320 | } 321 | } 322 | Structure.TUNNEL -> { 323 | val pickaxesLeft = player.inventorySlots.countItem() 324 | 325 | val tunnelingLeft = (pickaxesLeft * 1561) / (durabilityUsages.coerceAtLeast(1) / distanceDone.coerceAtLeast(1.0)) 326 | 327 | val secLeft = tunnelingLeft.coerceAtLeast(0.0) / (startingBlockPos.distanceTo(currentBlockPos).toInt() / runtimeSec) 328 | val secondsLeft = (secLeft % 60).toInt().toString().padStart(2, '0') 329 | val minutesLeft = ((secLeft % 3600) / 60).toInt().toString().padStart(2, '0') 330 | val hoursLeft = (secLeft / 3600).toInt().toString().padStart(2, '0') 331 | 332 | displayText.addLine("Destination:", primaryColor) 333 | 334 | displayText.add(" Pickaxes:", primaryColor) 335 | displayText.addLine("$pickaxesLeft", secondaryColor) 336 | 337 | displayText.add(" Distance left:", primaryColor) 338 | displayText.addLine("${tunnelingLeft.toInt()}", secondaryColor) 339 | 340 | if (!anonymizeStats) displayText.add(" Destination:", primaryColor) 341 | if (!anonymizeStats) displayText.addLine("(${currentBlockPos.add(startingDirection.directionVec.multiply(tunnelingLeft.toInt())).asString()})", secondaryColor) 342 | 343 | displayText.add(" ETA:", primaryColor) 344 | displayText.addLine("$hoursLeft:$minutesLeft:$secondsLeft", secondaryColor) 345 | } 346 | } 347 | } 348 | 349 | private fun gatherQueue(displayText: TextComponent) { 350 | if (containerTask.taskState != TaskState.DONE) { 351 | displayText.addLine("Container", primaryColor, scale = 0.6f) 352 | displayText.addLine(containerTask.prettyPrint(), primaryColor, scale = 0.6f) 353 | } 354 | 355 | if (sortedTasks.isNotEmpty()) { 356 | displayText.addLine("Pending", primaryColor, scale = 0.6f) 357 | addTaskComponentList(displayText, sortedTasks) 358 | } 359 | } 360 | 361 | private fun addTaskComponentList(displayText: TextComponent, tasks: Collection) { 362 | tasks.forEach { 363 | displayText.addLine(it.prettyPrint(), primaryColor, scale = 0.6f) 364 | } 365 | } 366 | 367 | fun resetStats() { 368 | simpleMovingAveragePlaces.clear() 369 | simpleMovingAverageBreaks.clear() 370 | simpleMovingAverageDistance.clear() 371 | totalBlocksPlaced = 0 372 | totalBlocksBroken = 0 373 | totalDistance = 0.0 374 | runtimeMilliSeconds = 0 375 | prevFood = 0 376 | foodLoss = 1 377 | materialLeft = 0 378 | fillerMatLeft = 0 379 | lastToolDamage = 0 380 | durabilityUsages = 0 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /src/main/kotlin/trombone/task/TaskExecutor.kt: -------------------------------------------------------------------------------- 1 | package trombone.task 2 | 3 | import HighwayTools.anonymizeStats 4 | import HighwayTools.breakDelay 5 | import HighwayTools.debugLevel 6 | import HighwayTools.dynamicDelay 7 | import HighwayTools.fakeSounds 8 | import HighwayTools.fastFill 9 | import HighwayTools.fillerMat 10 | import HighwayTools.ignoreBlocks 11 | import HighwayTools.interactionLimit 12 | import HighwayTools.keepFreeSlots 13 | import HighwayTools.leaveEmptyShulkers 14 | import HighwayTools.material 15 | import HighwayTools.mode 16 | import com.lambda.client.event.SafeClientEvent 17 | import com.lambda.client.module.modules.player.InventoryManager 18 | import com.lambda.client.util.TickTimer 19 | import com.lambda.client.util.items.* 20 | import com.lambda.client.util.math.CoordinateConverter.asString 21 | import com.lambda.client.util.math.VectorUtils.toVec3dCenter 22 | import com.lambda.client.util.text.MessageSendHelper 23 | import com.lambda.client.util.world.* 24 | import net.minecraft.block.BlockLiquid 25 | import net.minecraft.client.gui.inventory.GuiContainer 26 | import net.minecraft.init.Blocks 27 | import net.minecraft.item.ItemPickaxe 28 | import net.minecraft.network.play.client.CPacketPlayerTryUseItemOnBlock 29 | import net.minecraft.util.EnumFacing 30 | import net.minecraft.util.EnumHand 31 | import net.minecraft.util.SoundCategory 32 | import net.minecraft.util.math.AxisAlignedBB 33 | import net.minecraft.util.math.BlockPos 34 | import trombone.* 35 | import trombone.IO.disableError 36 | import trombone.Pathfinder.moveState 37 | import trombone.Pathfinder.shouldBridge 38 | import trombone.Trombone.module 39 | import trombone.blueprint.BlueprintGenerator 40 | import trombone.handler.Container 41 | import trombone.handler.Container.containerTask 42 | import trombone.handler.Container.getCollectingPosition 43 | import trombone.handler.Inventory 44 | import trombone.handler.Inventory.getEjectSlot 45 | import trombone.handler.Inventory.moveToInventory 46 | import trombone.handler.Inventory.swapOrMoveBestTool 47 | import trombone.handler.Inventory.swapOrMoveBlock 48 | import trombone.handler.Liquid.handleLiquid 49 | import trombone.handler.Liquid.updateLiquidTask 50 | import trombone.interaction.Break 51 | import trombone.interaction.Break.mineBlock 52 | import trombone.interaction.Place 53 | import trombone.interaction.Place.placeBlock 54 | 55 | object TaskExecutor { 56 | private val restockTimer = TickTimer() 57 | 58 | fun SafeClientEvent.doTask(blockTask: BlockTask, updateOnly: Boolean = false) { 59 | if (!updateOnly) blockTask.onTick() 60 | 61 | when (blockTask.taskState) { 62 | TaskState.RESTOCK -> { 63 | if (!updateOnly) doRestock() 64 | } 65 | TaskState.PICKUP -> { 66 | if (!updateOnly) doPickup() 67 | } 68 | TaskState.OPEN_CONTAINER -> { 69 | if (!updateOnly) doOpenContainer() 70 | } 71 | TaskState.BREAKING -> { 72 | doBreaking(blockTask, updateOnly) 73 | } 74 | TaskState.BROKEN -> { 75 | doBroken(blockTask) 76 | } 77 | TaskState.PLACED -> { 78 | doPlaced(blockTask) 79 | } 80 | TaskState.BREAK -> { 81 | doBreak(blockTask, updateOnly) 82 | } 83 | TaskState.PLACE, TaskState.LIQUID -> { 84 | doPlace(blockTask, updateOnly) 85 | } 86 | TaskState.PENDING_BREAK, TaskState.PENDING_PLACE -> { 87 | blockTask.onStuck() 88 | } 89 | TaskState.IMPOSSIBLE_PLACE -> { 90 | if (!updateOnly) doImpossiblePlace() 91 | } 92 | TaskState.DONE -> { /* do nothing */ } 93 | } 94 | } 95 | 96 | private fun SafeClientEvent.doRestock() { 97 | val container = player.openContainer 98 | 99 | if (mc.currentScreen !is GuiContainer && !containerTask.isLoaded) { 100 | containerTask.updateState(TaskState.OPEN_CONTAINER) 101 | return 102 | } 103 | 104 | if (container.inventorySlots.size != 63) { 105 | disableError("Inventory container changed. Current: ${player.openContainer.windowId} and saved ${container.windowId}") 106 | return 107 | } 108 | 109 | if (leaveEmptyShulkers 110 | && !InventoryManager.ejectList.contains(containerTask.item.registryName.toString()) 111 | && containerTask.isShulker() 112 | && container.getSlots(0..26).all { 113 | it.stack.isEmpty 114 | || InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) 115 | } 116 | ) { 117 | if (debugLevel != IO.DebugLevel.OFF) { 118 | if (!anonymizeStats) { 119 | MessageSendHelper.sendChatMessage("${module.chatName} Left empty ${containerTask.targetBlock.localizedName}@(${containerTask.blockPos.asString()})") 120 | } else { 121 | MessageSendHelper.sendChatMessage("${module.chatName} Left empty ${containerTask.targetBlock.localizedName}") 122 | } 123 | } 124 | 125 | containerTask.isOpen = false 126 | player.closeScreen() 127 | containerTask.updateState(TaskState.DONE) 128 | moveState = Pathfinder.MovementState.RUNNING 129 | return 130 | } 131 | 132 | val freeSlots = container.getSlots(27..62).count { 133 | InventoryManager.ejectList.contains(it.stack.item.registryName.toString()) 134 | || it.stack.isEmpty 135 | } - 1 - keepFreeSlots 136 | 137 | if (containerTask.stopPull || freeSlots < 1) { 138 | containerTask.updateState(TaskState.BREAK) 139 | containerTask.isOpen = false 140 | player.closeScreen() 141 | return 142 | } 143 | 144 | container.getSlots(0..26).firstItem(containerTask.item)?.let { 145 | moveToInventory(it, container) 146 | containerTask.stacksPulled++ 147 | containerTask.stopPull = true 148 | if (fastFill) { 149 | if (mode == Trombone.Structure.TUNNEL && containerTask.item is ItemPickaxe) { 150 | containerTask.stopPull = false 151 | } else if (mode != Trombone.Structure.TUNNEL && containerTask.item == material.item) { 152 | containerTask.stopPull = false 153 | } 154 | } 155 | } ?: run { 156 | if (containerTask.stacksPulled == 0) { 157 | Container.getShulkerWith(container.getSlots(0..26), containerTask.item)?.let { 158 | moveToInventory(it, container) 159 | containerTask.stopPull = true 160 | } ?: run { 161 | disableError("No ${containerTask.item.registryName} left in any container.") 162 | } 163 | } else { 164 | containerTask.updateState(TaskState.BREAK) 165 | containerTask.isOpen = false 166 | player.closeScreen() 167 | } 168 | } 169 | } 170 | 171 | private fun SafeClientEvent.doPickup() { 172 | if (getCollectingPosition() == null) { 173 | containerTask.updateState(TaskState.DONE) 174 | moveState = Pathfinder.MovementState.RUNNING 175 | return 176 | } 177 | 178 | if (player.inventorySlots.firstEmpty() == null && restockTimer.tick(20)) { 179 | getEjectSlot()?.let { 180 | throwAllInSlot(module, it) 181 | } 182 | } else { 183 | containerTask.onStuck() 184 | } 185 | } 186 | 187 | private fun SafeClientEvent.doOpenContainer() { 188 | moveState = Pathfinder.MovementState.RESTOCK 189 | 190 | if (containerTask.isOpen) { 191 | containerTask.updateState(TaskState.RESTOCK) 192 | return 193 | } 194 | 195 | if (Container.shulkerOpenTimer.tick(20)) { 196 | val center = containerTask.blockPos.toVec3dCenter() 197 | val diff = player.getPositionEyes(1f).subtract(center) 198 | val normalizedVec = diff.normalize() 199 | 200 | val side = EnumFacing.getFacingFromVector(normalizedVec.x.toFloat(), normalizedVec.y.toFloat(), normalizedVec.z.toFloat()) 201 | val hitVecOffset = getHitVecOffset(side) 202 | 203 | Inventory.lastHitVec = getHitVec(containerTask.blockPos, side) 204 | 205 | connection.sendPacket(CPacketPlayerTryUseItemOnBlock(containerTask.blockPos, side, EnumHand.MAIN_HAND, hitVecOffset.x.toFloat(), hitVecOffset.y.toFloat(), hitVecOffset.z.toFloat())) 206 | player.swingArm(EnumHand.MAIN_HAND) 207 | } 208 | } 209 | 210 | private fun SafeClientEvent.doBreaking(blockTask: BlockTask, updateOnly: Boolean) { 211 | val block = world.getBlockState(blockTask.blockPos).block 212 | 213 | if (block == Blocks.AIR) { 214 | Inventory.waitTicks = 215 | breakDelay 216 | blockTask.updateState(TaskState.BROKEN) 217 | return 218 | } 219 | 220 | if (block is BlockLiquid) { 221 | updateLiquidTask(blockTask) 222 | return 223 | } 224 | 225 | if (!updateOnly 226 | && swapOrMoveBestTool(blockTask) 227 | && Inventory.packetLimiter.size < interactionLimit 228 | ) { 229 | mineBlock(blockTask) 230 | } 231 | } 232 | 233 | private fun SafeClientEvent.doBroken(blockTask: BlockTask) { 234 | if (world.getBlockState(blockTask.blockPos).block != Blocks.AIR) { 235 | blockTask.updateState(TaskState.BREAK) 236 | return 237 | } 238 | 239 | Statistics.totalBlocksBroken++ 240 | 241 | TaskManager.tasks.forEach { (_, task) -> 242 | if (task.taskState == TaskState.BREAK) task.resetStuck() 243 | } 244 | 245 | // Instant break exploit 246 | if (blockTask.blockPos == Break.prePrimedPos) { 247 | Break.primedPos = Break.prePrimedPos 248 | Break.prePrimedPos = BlockPos.NULL_VECTOR 249 | } 250 | 251 | Statistics.simpleMovingAverageBreaks.add(System.currentTimeMillis()) 252 | 253 | // Sound 254 | if (fakeSounds) { 255 | val soundType = blockTask.targetBlock.getSoundType(world.getBlockState(blockTask.blockPos), world, blockTask.blockPos, player) 256 | world.playSound(player, blockTask.blockPos, soundType.breakSound, SoundCategory.BLOCKS, (soundType.getVolume() + 1.0f) / 2.0f, soundType.getPitch() * 0.8f) 257 | } 258 | 259 | if (blockTask == containerTask) { 260 | if (containerTask.collect) { 261 | moveState = Pathfinder.MovementState.PICKUP 262 | blockTask.updateState(TaskState.PICKUP) 263 | } else { 264 | blockTask.updateState(TaskState.DONE) 265 | } 266 | return 267 | } 268 | 269 | if (blockTask.targetBlock == Blocks.AIR) { 270 | blockTask.updateState(TaskState.DONE) 271 | } else { 272 | blockTask.updateState(TaskState.PLACE) 273 | } 274 | } 275 | 276 | private fun SafeClientEvent.doPlaced(blockTask: BlockTask) { 277 | val currentState = world.getBlockState(blockTask.blockPos) 278 | 279 | when { 280 | (blockTask.targetBlock == currentState.block || blockTask.isFiller) && !currentState.isReplaceable -> { 281 | Statistics.totalBlocksPlaced++ 282 | Break.prePrimedPos = blockTask.blockPos 283 | Statistics.simpleMovingAveragePlaces.add(System.currentTimeMillis()) 284 | 285 | if ( 286 | dynamicDelay && Place.extraPlaceDelay > 0) Place.extraPlaceDelay /= 2 287 | 288 | if (blockTask == containerTask) { 289 | if (containerTask.destroy) { 290 | containerTask.updateState(TaskState.BREAK) 291 | } else { 292 | containerTask.updateState(TaskState.OPEN_CONTAINER) 293 | } 294 | } else { 295 | blockTask.updateState(TaskState.DONE) 296 | } 297 | 298 | TaskManager.tasks.values.filter { it.taskState == TaskState.PLACE }.forEach { it.resetStuck() } 299 | 300 | if (fakeSounds) { 301 | val soundType = currentState.block.getSoundType(currentState, world, blockTask.blockPos, player) 302 | world.playSound(player, blockTask.blockPos, soundType.placeSound, SoundCategory.BLOCKS, (soundType.getVolume() + 1.0f) / 2.0f, soundType.getPitch() * 0.8f) 303 | } 304 | } 305 | blockTask.targetBlock == currentState.block && currentState.block == Blocks.AIR -> { 306 | blockTask.updateState(TaskState.BREAK) 307 | } 308 | blockTask.targetBlock == Blocks.AIR && currentState.block != Blocks.AIR -> { 309 | blockTask.updateState(TaskState.BREAK) 310 | } 311 | else -> { 312 | blockTask.updateState(TaskState.PLACE) 313 | } 314 | } 315 | } 316 | 317 | private fun SafeClientEvent.doBreak(blockTask: BlockTask, updateOnly: Boolean) { 318 | val currentBlock = world.getBlockState(blockTask.blockPos).block 319 | 320 | if (ignoreBlocks.contains(currentBlock.registryName.toString()) 321 | && !blockTask.isShulker() 322 | && !BlueprintGenerator.isInsideBlueprintBuild(blockTask.blockPos) 323 | || currentBlock in arrayOf(Blocks.PORTAL, Blocks.END_PORTAL, Blocks.END_PORTAL_FRAME, Blocks.BEDROCK) 324 | ) { 325 | blockTask.updateState(TaskState.DONE) 326 | return 327 | } 328 | 329 | // ToDo: Fix this 330 | // if (blockTask.targetBlock == fillerMat 331 | // && world.getBlockState(blockTask.blockPos.up()).block == material 332 | // || (!world.isPlaceable(blockTask.blockPos) 333 | // && world.getCollisionBox(blockTask.blockPos) != null) 334 | // ) { 335 | // blockTask.updateState(TaskState.DONE) 336 | // return 337 | // } 338 | 339 | when (blockTask.targetBlock) { 340 | fillerMat -> { 341 | if (world.getBlockState(blockTask.blockPos.up()).block == material || 342 | (!world.isPlaceable(blockTask.blockPos) && 343 | world.getCollisionBox(blockTask.blockPos) != null)) { 344 | blockTask.updateState(TaskState.DONE) 345 | return 346 | } 347 | } 348 | material -> { 349 | if (currentBlock == material) { 350 | blockTask.updateState(TaskState.DONE) 351 | return 352 | } 353 | } 354 | } 355 | 356 | when (currentBlock) { 357 | Blocks.AIR -> { 358 | if (blockTask.targetBlock == Blocks.AIR) { 359 | blockTask.updateState(TaskState.BROKEN) 360 | return 361 | } else { 362 | blockTask.updateState(TaskState.PLACE) 363 | return 364 | } 365 | } 366 | is BlockLiquid -> { 367 | updateLiquidTask(blockTask) 368 | return 369 | } 370 | } 371 | 372 | if (!updateOnly 373 | && player.onGround 374 | && swapOrMoveBestTool(blockTask) 375 | && !handleLiquid(blockTask) 376 | && Inventory.packetLimiter.size < interactionLimit 377 | ) { 378 | mineBlock(blockTask) 379 | } 380 | } 381 | 382 | private fun SafeClientEvent.doPlace(blockTask: BlockTask, updateOnly: Boolean) { 383 | val currentBlock = world.getBlockState(blockTask.blockPos).block 384 | 385 | if (blockTask.taskState == TaskState.LIQUID 386 | && world.getBlockState(blockTask.blockPos).block !is BlockLiquid 387 | ) { 388 | blockTask.updateState(TaskState.DONE) 389 | return 390 | } 391 | 392 | when (blockTask.targetBlock) { 393 | material -> { 394 | if (currentBlock == material) { 395 | blockTask.updateState(TaskState.PLACED) 396 | return 397 | } 398 | } 399 | fillerMat -> { 400 | if (currentBlock == fillerMat) { 401 | blockTask.updateState(TaskState.PLACED) 402 | return 403 | } else if (currentBlock != fillerMat 404 | && mode == Trombone.Structure.HIGHWAY 405 | && world.getBlockState(blockTask.blockPos.up()).block == material 406 | ) { 407 | blockTask.updateState(TaskState.DONE) 408 | return 409 | } 410 | } 411 | Blocks.AIR -> { 412 | if (world.getBlockState(blockTask.blockPos).block !is BlockLiquid) { 413 | if (currentBlock != Blocks.AIR) { 414 | blockTask.updateState(TaskState.BREAK) 415 | } else { 416 | blockTask.updateState(TaskState.BROKEN) 417 | } 418 | return 419 | } 420 | } 421 | } 422 | 423 | if (updateOnly) return 424 | 425 | if (!world.isPlaceable(blockTask.blockPos)) { 426 | if (debugLevel == IO.DebugLevel.VERBOSE) { 427 | if (!anonymizeStats) { 428 | MessageSendHelper.sendChatMessage("${module.chatName} Invalid place position @(${blockTask.blockPos.asString()}) Removing task") 429 | } else { 430 | MessageSendHelper.sendChatMessage("${module.chatName} Invalid place position. Removing task") 431 | } 432 | } 433 | 434 | if (blockTask == containerTask) { 435 | MessageSendHelper.sendChatMessage("${module.chatName} Failed container task. Trying to break block.") 436 | containerTask.updateState(TaskState.BREAK) 437 | } else { 438 | TaskManager.tasks.remove(blockTask.blockPos) 439 | } 440 | 441 | return 442 | } 443 | 444 | if (!swapOrMoveBlock(blockTask)) { 445 | blockTask.onStuck() 446 | return 447 | } 448 | 449 | placeBlock(blockTask) 450 | } 451 | 452 | private fun SafeClientEvent.doImpossiblePlace() { 453 | if (shouldBridge() 454 | && moveState != Pathfinder.MovementState.RESTOCK 455 | && player.positionVector.distanceTo(Pathfinder.currentBlockPos.toVec3dCenter()) < 1 456 | ) { 457 | moveState = Pathfinder.MovementState.BRIDGE 458 | } 459 | } 460 | } --------------------------------------------------------------------------------