Skip to content

Commit 83338a7

Browse files
authored
AutoSpiral module (#234)
1 parent bcb3790 commit 83338a7

3 files changed

Lines changed: 204 additions & 1 deletion

File tree

src/main/kotlin/com/lambda/interaction/BaritoneManager.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,8 @@ object BaritoneManager : Configurable(LambdaConfig), Automated by AutomationConf
354354
get() = isBaritoneLoaded &&
355355
(primary?.customGoalProcess?.isActive == true ||
356356
primary?.pathingBehavior?.isPathing == true ||
357-
primary?.pathingControlManager?.mostRecentInControl()?.orElse(null)?.isActive == true)
357+
primary?.pathingControlManager?.mostRecentInControl()?.orElse(null)?.isActive == true ||
358+
primary?.elytraProcess?.isActive == true)
358359

359360
/**
360361
* Sets the current Baritone goal and starts pathing
@@ -364,11 +365,25 @@ object BaritoneManager : Configurable(LambdaConfig), Automated by AutomationConf
364365
primary?.customGoalProcess?.setGoalAndPath(goal)
365366
}
366367

368+
/**
369+
* Sets the current Baritone goal without starting pathing
370+
*/
371+
fun setGoal(goal: Goal) {
372+
if (!isBaritoneLoaded || primary?.elytraProcess?.isLoaded != true) return
373+
primary.customGoalProcess?.goal = goal
374+
}
375+
376+
fun setGoalAndElytraPath(goal: Goal) {
377+
if (!isBaritoneLoaded || primary?.elytraProcess?.isLoaded != true) return
378+
primary.elytraProcess?.pathTo(goal)
379+
}
380+
367381
/**
368382
* Force cancel Baritone
369383
*/
370384
fun cancel() {
371385
if (!isBaritoneLoaded) return
372386
primary?.pathingBehavior?.cancelEverything()
387+
primary?.elytraProcess?.resetState()
373388
}
374389
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2026 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.module.modules.movement
19+
20+
import baritone.api.pathing.goals.GoalXZ
21+
import com.lambda.context.SafeContext
22+
import com.lambda.event.events.TickEvent
23+
import com.lambda.event.listener.SafeListener.Companion.listen
24+
import com.lambda.interaction.BaritoneManager
25+
import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest
26+
import com.lambda.interaction.managers.rotating.visibilty.lookAt
27+
import com.lambda.module.Module
28+
import com.lambda.module.tag.ModuleTag
29+
import com.lambda.threading.runSafe
30+
import com.lambda.util.BlockPosIterators
31+
import com.lambda.util.extension.isNether
32+
import net.minecraft.util.math.BlockPos
33+
import kotlin.math.sqrt
34+
35+
@Suppress("unused")
36+
object AutoSpiral : Module(
37+
name = "AutoSpiral",
38+
description = "Automatically flies in a spiral pattern. Uses Baritone elytra pathing in the Nether.",
39+
tag = ModuleTag.MOVEMENT,
40+
) {
41+
var iterator: BlockPosIterators.SpiralIterator2d? = null
42+
var currentWaypoint: BlockPos? = null
43+
44+
var spiralSpacing by setting("Spiral Spacing", 128, 16..1024, description = "The distance between each loop of the spiral")
45+
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.")
46+
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.")
47+
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.")
48+
49+
var center by setting("Center", BlockPos.ORIGIN, description = "Center position for the spiral")
50+
51+
init {
52+
onEnable {
53+
if (iterator == null) {
54+
iterator = BlockPosIterators.SpiralIterator2d(10000)
55+
if (setCenterOnEnable) {
56+
center = player.blockPos
57+
}
58+
}
59+
}
60+
61+
onDisable {
62+
iterator = null
63+
currentWaypoint = null
64+
BaritoneManager.cancel()
65+
}
66+
67+
listen<TickEvent.Pre> {
68+
if (currentWaypoint == null || waypointReached()) {
69+
nextWaypoint()
70+
}
71+
72+
currentWaypoint?.let { waypoint ->
73+
if (!world.isNether) {
74+
rotationRequest {
75+
yaw(lookAt(waypoint.toCenterPos()).yaw)
76+
}.submit(true)
77+
}
78+
}
79+
}
80+
}
81+
82+
private fun SafeContext.waypointReached() =
83+
currentWaypoint?.let {
84+
val distance = distanceXZ(player.blockPos, it)
85+
distance <= waypointTriggerDistance
86+
} ?: false
87+
88+
89+
private fun distanceXZ(a: BlockPos, b: BlockPos): Double {
90+
val dx = (a.x - b.x).toDouble()
91+
val dz = (a.z - b.z).toDouble()
92+
return sqrt(dx * dx + dz * dz)
93+
}
94+
95+
private fun SafeContext.nextWaypoint() {
96+
iterator?.next()?.let { pos ->
97+
val scaled = pos.multiply(spiralSpacing)
98+
val w = scaled.add(center)
99+
if (world.isNether) {
100+
BaritoneManager.setGoalAndElytraPath(GoalXZ(w.x, w.z))
101+
} else {
102+
if (setBaritoneGoal) BaritoneManager.setGoal(GoalXZ(w.x, w.z))
103+
}
104+
currentWaypoint = w
105+
}
106+
}
107+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2026 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.util
19+
20+
import net.minecraft.util.math.BlockPos
21+
import kotlin.math.floor
22+
import kotlin.math.pow
23+
24+
/**
25+
* A collection of Block position iterator implementations for various purposes.
26+
*/
27+
object BlockPosIterators {
28+
/**
29+
* Spiral outwards from a central position in growing squares.
30+
* Every point has a constant distance to its previous and following position of 1. First point returned is the starting position.
31+
* Generates positions like this:
32+
* ```text
33+
* 16 15 14 13 12
34+
* 17 4 3 2 11
35+
* 18 5 0 1 10
36+
* 19 6 7 8 9
37+
* 20 21 22 23 24
38+
* (maxDistance = 2; points returned = 25)
39+
* ```
40+
*
41+
* @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>
42+
*
43+
*/
44+
class SpiralIterator2d(maxDistance: Int) : MutableIterator<BlockPos?> {
45+
val totalPoints: Int = floor(((floor(maxDistance.toDouble()) - 0.5) * 2).pow(2.0)).toInt()
46+
private var deltaX: Int = 1
47+
private var deltaZ: Int = 0
48+
private var segmentLength: Int = 1
49+
private var currentX: Int = 0
50+
private var currentZ: Int = 0
51+
private var stepsInCurrentSegment: Int = 0
52+
var pointsGenerated: Int = 0
53+
54+
override fun next(): BlockPos? {
55+
if (this.pointsGenerated >= this.totalPoints) return null
56+
val output = BlockPos(this.currentX, 0, this.currentZ)
57+
this.currentX += this.deltaX
58+
this.currentZ += this.deltaZ
59+
this.stepsInCurrentSegment += 1
60+
if (this.stepsInCurrentSegment == this.segmentLength) {
61+
this.stepsInCurrentSegment = 0
62+
val buffer = this.deltaX
63+
this.deltaX = -this.deltaZ
64+
this.deltaZ = buffer
65+
if (this.deltaZ == 0) {
66+
this.segmentLength += 1
67+
}
68+
}
69+
this.pointsGenerated += 1
70+
return output
71+
}
72+
73+
override fun hasNext(): Boolean {
74+
return this.pointsGenerated < this.totalPoints
75+
}
76+
77+
override fun remove() {
78+
throw UnsupportedOperationException("remove")
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)