diff --git a/src/main/kotlin/net/idylls/pathos/Overlay.kt b/src/main/kotlin/net/idylls/pathos/Overlay.kt index ac7db30..0138358 100644 --- a/src/main/kotlin/net/idylls/pathos/Overlay.kt +++ b/src/main/kotlin/net/idylls/pathos/Overlay.kt @@ -63,16 +63,17 @@ class Overlay val tiles = client.scene.tiles[z] val _hoveredTile = client.selectedSceneTile + val hoveredTarget = getHoveredTarget(client) val hoveredPathTiles = if ( config.highlightHoveredPath() - && _hoveredTile != null + && hoveredTarget != null ) { - val hoveredTile = _hoveredTile.sceneLocation + val requestedTiles = hoveredTarget.getRequestedTiles() - findPath( + findShortestPathAll( playerTrueTile, - hoveredTile, + requestedTiles, collisionFlags, tiles, ) diff --git a/src/main/kotlin/net/idylls/pathos/Pathfinding.kt b/src/main/kotlin/net/idylls/pathos/Pathfinding.kt index 58df808..3ed5c62 100644 --- a/src/main/kotlin/net/idylls/pathos/Pathfinding.kt +++ b/src/main/kotlin/net/idylls/pathos/Pathfinding.kt @@ -28,6 +28,25 @@ fun buildPath( return list } +fun findShortestPathAll( + from: ScenePoint, + to: Set, + collisionFlags: Array, + tiles: Array>, +): List? { + return to + .mapNotNull({ to_ -> findPath(from, to_, collisionFlags, tiles) }) + .minByOrNull({ p -> + // minimize zig-zaggedness (manhattan distance) when choosing + // between multiple possible minimal paths + p.windowed(2).fold(0, { acc, w -> + val (a, b) = w + + acc + abs(a.x - b.x) + abs(a.y - b.y) + }) + p.size + }) +} + fun findPath( from: ScenePoint, to: ScenePoint, diff --git a/src/main/kotlin/net/idylls/pathos/Targetting.kt b/src/main/kotlin/net/idylls/pathos/Targetting.kt new file mode 100644 index 0000000..c61ef27 --- /dev/null +++ b/src/main/kotlin/net/idylls/pathos/Targetting.kt @@ -0,0 +1,130 @@ +package net.idylls.pathos + +import net.runelite.api.Client +import net.runelite.api.MenuAction +import net.runelite.api.Point +import net.runelite.api.TileObject +import net.runelite.api.GameObject +import net.runelite.api.Actor as RLActor +import net.runelite.api.Tile as RLTile + +sealed class Target { + class Actor(val actor: RLActor) : Target() + class Tile(val tile: RLTile) : Target() + class Object(val obj: TileObject) : Target() + + fun getRequestedTiles(): Set { + val meleeTiles = { x: Int, y: Int -> + val points = mutableSetOf() + + if (x > 0) { + points.add(Point(x - 1, y)) + } + + if (y > 0) { + points.add(Point(x, y - 1)) + } + + if (x < 128) { + points.add(Point(x + 1, y)) + } + + if (y < 128) { + points.add(Point(x, y + 1)) + } + + points + } + + return when (this) { + is Tile -> setOf(this.tile.sceneLocation) + is Actor -> { + val lp = this.actor.localLocation + + meleeTiles(lp.sceneX, lp.sceneY) + } + is Object -> when (this.obj) { + is GameObject -> { + val min = this.obj.sceneMinLocation + val max = this.obj.sceneMaxLocation + + val points = mutableSetOf() + for (y in min.y..max.y) { + for (x in min.x..max.x) { + points.add(Point(x, y)) + } + } + + points + } + else -> { + val lp = this.obj.localLocation + + setOf(Point(lp.sceneX, lp.sceneY)) + } + } + } + } +} + +fun findTileObject(client: Client, x: Int, y: Int, id: Int): TileObject? { + val scene = client.scene + val tiles = scene.tiles + val plane = tiles[client.plane] + if (x < 0 || y < 0 || x > plane.size || y > plane[0].size) { + return null + } + val tile = tiles[client.plane][x][y] ?: return null + + val objects = tile.gameObjects + objects.find({ g -> g?.id == id})?.let { return it } + + tile.wallObject?.let { + if (it.id == id) { + return it + } + } + + tile.decorativeObject?.let { + if (it.id == id) { + return it + } + } + + tile.groundObject?.let { + if (it.id == id) { + return it + } + } + + return null +} + +fun getHoveredTarget(client: Client): Target? { + val entries = client.menuEntries + if (entries.size == 0) { + throw Error("Entries should never be 0") + } + + // TODO: Handle menu open + + val entry = entries[entries.size - 1] + if (entry.type == MenuAction.CANCEL) { + return null + } + + if (entry.type == MenuAction.WALK) { + return client.selectedSceneTile?.let { Target.Tile(it) } + } + + entry.actor?.let { return Target.Actor(it) } + + val tileObject = findTileObject( + client, + entry.param0, + entry.param1, + entry.identifier, + ) ?: return null + + return Target.Object(tileObject) +}