Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/main/kotlin/org/cobalt/api/pathfinder/Node.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.cobalt.api.pathfinder


import org.cobalt.api.pathfinder.pathing.heuristic.HeuristicContext
import org.cobalt.api.pathfinder.pathing.heuristic.HeuristicWeights
import org.cobalt.api.pathfinder.pathing.heuristic.IHeuristicStrategy
import org.cobalt.api.pathfinder.wrapper.PathPosition

class Node(
private val position: PathPosition,
start: PathPosition,
target: PathPosition,
heuristicWeights: HeuristicWeights,
heuristicStrategy: IHeuristicStrategy,
private val depth: Int,
) : Comparable<Node> {

private val hCost: Double = heuristicStrategy.calculate(
HeuristicContext(position, start, target, heuristicWeights)
)

private var gCost: Double = 0.0
private var parent: Node? = null

fun getPosition(): PathPosition = position

fun getHeuristic(): Double = hCost

fun getParent(): Node? = parent

fun getDepth(): Int = depth

fun setGCost(gCost: Double) {
this.gCost = gCost
}

fun setParent(parent: Node?) {
this.parent = parent
}

fun isTarget(target: PathPosition): Boolean = this.position == target

fun getFCost(): Double = getGCost() + getHeuristic()

fun getGCost(): Double {
return if (this.parent == null) 0.0 else this.gCost
}

override fun equals(other: Any?): Boolean {
if (other == null || this::class != other::class) return false
other as Node
return position == other.position
}

override fun hashCode(): Int = position.hashCode()

override fun compareTo(other: Node): Int {
val fCostComparison = this.getFCost().compareTo(other.getFCost())
if (fCostComparison != 0) {
return fCostComparison
}

val heuristicComparison = this.getHeuristic().compareTo(other.getHeuristic())
if (heuristicComparison != 0) {
return heuristicComparison
}

return this.depth.compareTo(other.depth)
}
}
163 changes: 163 additions & 0 deletions src/main/kotlin/org/cobalt/api/pathfinder/PathExecutor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package org.cobalt.api.pathfinder

import java.awt.Color
import net.minecraft.client.Minecraft
import net.minecraft.world.phys.AABB
import net.minecraft.world.phys.Vec3
import org.cobalt.api.event.EventBus
import org.cobalt.api.event.annotation.SubscribeEvent
import org.cobalt.api.event.impl.client.TickEvent
import org.cobalt.api.event.impl.render.WorldRenderEvent
import org.cobalt.api.pathfinder.factory.impl.AStarPathfinderFactory
import org.cobalt.api.pathfinder.pathing.NeighborStrategies
import org.cobalt.api.pathfinder.pathing.configuration.PathfinderConfiguration
import org.cobalt.api.pathfinder.pathing.processing.impl.MinecraftPathProcessor
import org.cobalt.api.pathfinder.pathing.result.Path
import org.cobalt.api.pathfinder.pathing.result.PathState
import org.cobalt.api.pathfinder.provider.impl.MinecraftNavigationProvider
import org.cobalt.api.pathfinder.wrapper.PathPosition
import org.cobalt.api.util.ChatUtils
import org.cobalt.api.util.render.Render3D

/*
* TODO: im lazy right now, but chunk and world caching would be alot better,
* if someone could help me do this it would be a great help :))
*/
object PathExecutor {

private val mc: Minecraft = Minecraft.getInstance()
private var currentPath: Path? = null
private var currentWaypointIndex: Int = 0

init {
EventBus.register(this)
}

fun start(x: Double, y: Double, z: Double) {
val player = mc.player ?: return
val start = PathPosition(player.x, player.y, player.z)
val target = PathPosition(x, y, z)

val factory = AStarPathfinderFactory()

val processor = MinecraftPathProcessor()
val config =
PathfinderConfiguration.builder()
.provider(MinecraftNavigationProvider())
.maxIterations(20000)
.async(true)
.neighborStrategy(NeighborStrategies.HORIZONTAL_DIAGONAL_AND_VERTICAL)
.nodeValidationProcessors(listOf(processor))
.nodeCostProcessors(listOf(processor))
.build()

val pathfinder = factory.createPathfinder(config)

ChatUtils.sendDebug("Calculating path to $x, $y, $z...")
val startTime = System.currentTimeMillis()
pathfinder.findPath(start, target).thenAccept { result ->
val duration = System.currentTimeMillis() - startTime
if (result.successful()) {
currentPath = result.getPath()
currentWaypointIndex = 0

val state = result.getPathState()
val path = result.getPath()

if (state == PathState.FOUND) {
ChatUtils.sendMessage(
"§aPath found! §7Calculated in §f${duration}ms §8(${path.length()} nodes)"
)
} else {
/*
* partial paths can happen, i would recommend improving this functionality
* to whoever maintainer wants to maintain my horrible shitcode of a pathfinder.
* i think partial paths should be refactored, i will write all the cases now
* iteration limit -> self explanatory xd (or if u cant use ur brain
* then its just if the algorithm takes too many iterations to find a goal.
* this is set literaaally like 20 lines above you...)
* fallback -> pf searches everywhere and couldnt find a possible way to get there.
* this can happen in the case of unloaded chunks, or an obstruction
*/
ChatUtils.sendMessage(
"§ePartial path found! §7Calculated in §f${duration}ms §8(${path.length()} nodes)"
)
}
} else {
ChatUtils.sendMessage("§cFailed to find path: ${result.getPathState()}")
}
}
}

fun stop() {
currentPath = null
currentWaypointIndex = 0
}

/*
* as of writing this, there is intentionally no moving, rotations,
* or jumping yet. i ask that you make sure that the algo works good
* before implementing them, aswell as rots
*/
@SubscribeEvent
fun onTick(@Suppress("UNUSED_PARAMETER") event: TickEvent.Start) {
val path = currentPath ?: return
val player = mc.player ?: return

val waypoints = path.collect().toList()
if (currentWaypointIndex >= waypoints.size) {
ChatUtils.sendMessage("Reached the end!") // this is kinda icky, someone change this lol
stop()
return
}

val targetPos = waypoints[currentWaypointIndex].mid()
val targetVec = Vec3(targetPos.x, targetPos.y, targetPos.z)

val horizontalDistSq =
(player.x - targetVec.x) * (player.x - targetVec.x) +
(player.z - targetVec.z) * (player.z - targetVec.z)
if (horizontalDistSq < 0.25) {
currentWaypointIndex++
}
}

@SubscribeEvent
fun onRender(event: WorldRenderEvent.Last) {
val path = currentPath ?: return
val waypoints = path.collect().toList()

if (waypoints.size < 2) return

for (i in 0 until waypoints.size - 1) {
val start = waypoints[i].mid()
val end = waypoints[i + 1].mid()

Render3D.drawLine(
event.context,
Vec3(start.x, start.y, start.z),
Vec3(end.x, end.y, end.z),
Color.CYAN,
true,
2.0f
)
}

if (currentWaypointIndex < waypoints.size) {
val currentPos = waypoints[currentWaypointIndex].mid()
Render3D.drawBox(
event.context,
AABB(
currentPos.x - 0.25,
currentPos.y - 0.25,
currentPos.z - 0.25,
currentPos.x + 0.25,
currentPos.y + 0.25,
currentPos.z + 0.25
),
Color.GREEN,
true
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.cobalt.api.pathfinder.factory

import org.cobalt.api.pathfinder.pathing.Pathfinder
import org.cobalt.api.pathfinder.pathing.configuration.PathfinderConfiguration

interface PathfinderFactory {
fun createPathfinder(): Pathfinder = createPathfinder(PathfinderConfiguration.DEFAULT)

fun createPathfinder(configuration: PathfinderConfiguration): Pathfinder =
throw UnsupportedOperationException(
"This factory does not support creating pathfinders with a configuration."
)

fun createPathfinder(
configuration: PathfinderConfiguration,
initializer: PathfinderInitializer,
): Pathfinder {
val pathfinder = createPathfinder(configuration)
initializer.initialize(pathfinder, configuration)
return pathfinder
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.cobalt.api.pathfinder.factory

import org.cobalt.api.pathfinder.pathing.Pathfinder
import org.cobalt.api.pathfinder.pathing.configuration.PathfinderConfiguration

fun interface PathfinderInitializer {
fun initialize(pathfinder: Pathfinder, configuration: PathfinderConfiguration)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.cobalt.api.pathfinder.factory.impl

import org.cobalt.api.pathfinder.factory.PathfinderFactory
import org.cobalt.api.pathfinder.pathfinder.AStarPathfinder
import org.cobalt.api.pathfinder.pathing.Pathfinder
import org.cobalt.api.pathfinder.pathing.configuration.PathfinderConfiguration

class AStarPathfinderFactory : PathfinderFactory {

override fun createPathfinder(configuration: PathfinderConfiguration): Pathfinder {
return AStarPathfinder(configuration)
}
}
Loading