Skip to content
17 changes: 16 additions & 1 deletion src/main/kotlin/com/lambda/interaction/BaritoneManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ object BaritoneManager : Configurable(LambdaConfig), Automated by AutomationConf
get() = isBaritoneLoaded &&
(primary?.customGoalProcess?.isActive == true ||
primary?.pathingBehavior?.isPathing == true ||
primary?.pathingControlManager?.mostRecentInControl()?.orElse(null)?.isActive == true)
primary?.pathingControlManager?.mostRecentInControl()?.orElse(null)?.isActive == true ||
primary?.elytraProcess?.isActive == true)

/**
* Sets the current Baritone goal and starts pathing
Expand All @@ -365,11 +366,25 @@ object BaritoneManager : Configurable(LambdaConfig), Automated by AutomationConf
primary?.customGoalProcess?.setGoalAndPath(goal)
}

/**
* Sets the current Baritone goal without starting pathing
*/
fun setGoal(goal: Goal) {
if (!isBaritoneLoaded || primary?.elytraProcess?.isLoaded != true) return
primary.customGoalProcess?.goal = goal
}

fun setGoalAndElytraPath(goal: Goal) {
if (!isBaritoneLoaded || primary?.elytraProcess?.isLoaded != true) return
primary.elytraProcess?.pathTo(goal)
}

/**
* Force cancel Baritone
*/
fun cancel() {
if (!isBaritoneLoaded) return
primary?.pathingBehavior?.cancelEverything()
primary?.elytraProcess?.resetState()
}
}
107 changes: 107 additions & 0 deletions src/main/kotlin/com/lambda/module/modules/movement/AutoSpiral.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright 2026 Lambda
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.lambda.module.modules.movement

import baritone.api.pathing.goals.GoalXZ
import com.lambda.context.SafeContext
import com.lambda.event.events.TickEvent
import com.lambda.event.listener.SafeListener.Companion.listen
import com.lambda.interaction.BaritoneManager
import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest
import com.lambda.interaction.managers.rotating.visibilty.lookAt
import com.lambda.module.Module
import com.lambda.module.tag.ModuleTag
import com.lambda.threading.runSafe
import com.lambda.util.BlockPosIterators
import com.lambda.util.extension.isNether
import net.minecraft.util.math.BlockPos
import kotlin.math.sqrt

@Suppress("unused")
object AutoSpiral : Module(
name = "AutoSpiral",
description = "Automatically flies in a spiral pattern. Uses Baritone elytra pathing in the Nether.",
tag = ModuleTag.MOVEMENT,
) {
var iterator: BlockPosIterators.SpiralIterator2d? = null
var currentWaypoint: BlockPos? = null

var spiralSpacing by setting("Spiral Spacing", 128, 16..1024, description = "The distance between each loop of the spiral")
var waypointTriggerDistance by setting("Waypoint Trigger Distance", 4, 2..64, description = "The distance to the waypoint at which a new waypoint is generated. Put in 50-60 range when in the Nether.")
var setCenterOnEnable by setting("Set Center On Enable", true, description = "Whether to set the center of the spiral to your current position when enabling the module.")
var setBaritoneGoal by setting("Set Baritone Goal", true, description = "Whether to set Baritone's goal to the current waypoint. Mostly so you can see where the next waypoint is.")

var center by setting("Center", BlockPos.ORIGIN, description = "Center position for the spiral")

init {
onEnable {
if (iterator == null) {
iterator = BlockPosIterators.SpiralIterator2d(10000)
if (setCenterOnEnable) {
center = player.blockPos
}
}
}

onDisable {
iterator = null
currentWaypoint = null
BaritoneManager.cancel()
}

listen<TickEvent.Pre> {
if (currentWaypoint == null || waypointReached()) {
nextWaypoint()
}

currentWaypoint?.let { waypoint ->
if (!world.isNether) {
rotationRequest {
yaw(lookAt(waypoint.toCenterPos()).yaw)
}.submit(true)
}
}
}
}

private fun SafeContext.waypointReached() =
currentWaypoint?.let {
val distance = distanceXZ(player.blockPos, it)
distance <= waypointTriggerDistance
} ?: false


private fun distanceXZ(a: BlockPos, b: BlockPos): Double {
val dx = (a.x - b.x).toDouble()
val dz = (a.z - b.z).toDouble()
return sqrt(dx * dx + dz * dz)
}

private fun SafeContext.nextWaypoint() {
iterator?.next()?.let { pos ->
val scaled = pos.multiply(spiralSpacing)
val w = scaled.add(center)
if (world.isNether) {
BaritoneManager.setGoalAndElytraPath(GoalXZ(w.x, w.z))
} else {
if (setBaritoneGoal) BaritoneManager.setGoal(GoalXZ(w.x, w.z))
}
currentWaypoint = w
}
}
}
81 changes: 81 additions & 0 deletions src/main/kotlin/com/lambda/util/BlockPosIterators.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2026 Lambda
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.lambda.util

import net.minecraft.util.math.BlockPos
import kotlin.math.floor
import kotlin.math.pow

/**
* A collection of Block position iterator implementations for various purposes.
*/
object BlockPosIterators {
/**
* Spiral outwards from a central position in growing squares.
* Every point has a constant distance to its previous and following position of 1. First point returned is the starting position.
* Generates positions like this:
* ```text
* 16 15 14 13 12
* 17 4 3 2 11
* 18 5 0 1 10
* 19 6 7 8 9
* 20 21 22 23 24
* (maxDistance = 2; points returned = 25)
* ```
*
* @see <a href="https://stackoverflow.com/questions/3706219/algorithm-for-iterating-over-an-outward-spiral-on-a-discrete-2d-grid-from-the-or">StackOverflow: Algorithm for iterating over an outward spiral on a discrete 2d grid</a>
*
*/
class SpiralIterator2d(maxDistance: Int) : MutableIterator<BlockPos?> {
val totalPoints: Int = floor(((floor(maxDistance.toDouble()) - 0.5) * 2).pow(2.0)).toInt()
private var deltaX: Int = 1
private var deltaZ: Int = 0
private var segmentLength: Int = 1
private var currentX: Int = 0
private var currentZ: Int = 0
private var stepsInCurrentSegment: Int = 0
var pointsGenerated: Int = 0

override fun next(): BlockPos? {
if (this.pointsGenerated >= this.totalPoints) return null
val output = BlockPos(this.currentX, 0, this.currentZ)
this.currentX += this.deltaX
this.currentZ += this.deltaZ
this.stepsInCurrentSegment += 1
if (this.stepsInCurrentSegment == this.segmentLength) {
this.stepsInCurrentSegment = 0
val buffer = this.deltaX
this.deltaX = -this.deltaZ
this.deltaZ = buffer
if (this.deltaZ == 0) {
this.segmentLength += 1
}
}
this.pointsGenerated += 1
return output
}

override fun hasNext(): Boolean {
return this.pointsGenerated < this.totalPoints
}

override fun remove() {
throw UnsupportedOperationException("remove")
}
}
}