Use closest path if path not found (#431)
When clicking on the other side of a wall or in an an inaccessible area, route the player the closest possible node. This allows running along walls and matches the original closely. Co-authored-by: Nicholas Eden <neden@zigzagame.com>
This commit is contained in:
parent
a24c05efa9
commit
329e2f0fab
|
@ -7,9 +7,9 @@ D2 A*
|
|||
|
||||
Changes
|
||||
-------
|
||||
* Used [sync.Pool](https://golang.org/pkg/sync/#Pool) to reuse objects created during pathfinding. This improves performance by roughly 30% by reducing allocations.
|
||||
* Added a check on the target for neighbors to identify if the user clicked an inaccessible area.
|
||||
* Used [sync.Pool](https://golang.org/pkg/sync/#Pool) to reuse objects created during path-finding. This improves performance by roughly 30% by reducing allocations.
|
||||
* Added a max cost to prevent searching the entire region for a path.
|
||||
* If there is no path the target within the max cost, the path found that gets closest to target will be returned. This allows the player to click in inaccessible areas causing the character to run along the edge.
|
||||
|
||||
TODO
|
||||
------
|
||||
|
|
|
@ -2,28 +2,28 @@ package d2astar
|
|||
|
||||
import (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var nodePool *sync.Pool
|
||||
var nodeMapPool *sync.Pool
|
||||
var priorityQueuePool *sync.Pool
|
||||
func init() {
|
||||
nodePool = &sync.Pool {
|
||||
New: func()interface{} {
|
||||
|
||||
func init() {
|
||||
nodePool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &node{}
|
||||
},
|
||||
}
|
||||
|
||||
nodeMapPool = &sync.Pool {
|
||||
New: func()interface{} {
|
||||
nodeMapPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make(nodeMap, 128)
|
||||
},
|
||||
}
|
||||
|
||||
priorityQueuePool = &sync.Pool {
|
||||
New: func()interface{} {
|
||||
priorityQueuePool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return priorityQueue{}
|
||||
},
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ type node struct {
|
|||
index int
|
||||
}
|
||||
|
||||
func (n *node) reset () {
|
||||
func (n *node) reset() {
|
||||
n.pather = nil
|
||||
n.cost = 0
|
||||
n.rank = 0
|
||||
|
@ -81,14 +81,8 @@ func (nm nodeMap) get(p Pather) *node {
|
|||
|
||||
// Path calculates a short path and the distance between the two Pather nodes.
|
||||
//
|
||||
// If no path is found, found will be false.
|
||||
// If no path is found, found will be false and path will be the closest node to the target with a valid path.
|
||||
func Path(from, to Pather, maxCost float64) (path []Pather, distance float64, found bool) {
|
||||
// Quick escape for inaccessible areas.
|
||||
toNeighbors := to.PathNeighbors()
|
||||
if len(toNeighbors) == 0 {
|
||||
return nil, 0, false
|
||||
}
|
||||
|
||||
nm := nodeMapPool.Get().(nodeMap)
|
||||
nq := priorityQueuePool.Get().(priorityQueue)
|
||||
defer func() {
|
||||
|
@ -109,8 +103,8 @@ func Path(from, to Pather, maxCost float64) (path []Pather, distance float64, fo
|
|||
heap.Push(&nq, fromNode)
|
||||
for {
|
||||
if nq.Len() == 0 {
|
||||
// There's no path, return found false.
|
||||
return
|
||||
// There's no path, fallback to closest.
|
||||
break
|
||||
}
|
||||
current := heap.Pop(&nq).(*node)
|
||||
current.open = false
|
||||
|
@ -130,7 +124,7 @@ func Path(from, to Pather, maxCost float64) (path []Pather, distance float64, fo
|
|||
for _, neighbor := range current.pather.PathNeighbors() {
|
||||
cost := current.cost + current.pather.PathNeighborCost(neighbor)
|
||||
if cost > maxCost {
|
||||
fmt.Println("Canceling path")
|
||||
// Out of range, tweak maxCost if this is cutting off too soon.
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -151,4 +145,27 @@ func Path(from, to Pather, maxCost float64) (path []Pather, distance float64, fo
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No path found, use closest node available, with found false to indicate the path doesn't reach the target.
|
||||
closestNode := nm.get(from)
|
||||
closestNode.rank = closestNode.pather.PathEstimatedCost(to)
|
||||
for _, current := range nm {
|
||||
if current.parent == nil {
|
||||
// This node wasn't evaluated while path finding, probably isn't a good option.
|
||||
continue
|
||||
}
|
||||
|
||||
current.rank = current.pather.PathEstimatedCost(to)
|
||||
if current.rank < closestNode.rank {
|
||||
closestNode = current
|
||||
}
|
||||
}
|
||||
|
||||
p := make([]Pather, 0, 16)
|
||||
curr := closestNode
|
||||
for curr != nil {
|
||||
p = append(p, curr.pather)
|
||||
curr = curr.parent
|
||||
}
|
||||
return p, closestNode.cost, false
|
||||
}
|
||||
|
|
|
@ -97,8 +97,14 @@ func (m *MapEngine) PathFind(startX, startY, endX, endY float64) (path []d2astar
|
|||
}
|
||||
endNode := &m.walkMesh[endNodeIndex]
|
||||
|
||||
path, distance, found = d2astar.Path(endNode, startNode, 80)
|
||||
path, distance, found = d2astar.Path(startNode, endNode, 80)
|
||||
if path != nil {
|
||||
// Reverse the path to fit what the game expects.
|
||||
for i := len(path)/2-1; i >= 0; i-- {
|
||||
opp := len(path)-1-i
|
||||
path[i], path[opp] = path[opp], path[i]
|
||||
}
|
||||
|
||||
path = path[1:]
|
||||
}
|
||||
return
|
||||
|
|
|
@ -86,8 +86,8 @@ func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
|
|||
case d2netpackettype.MovePlayer:
|
||||
movePlayer := packet.PacketData.(d2netpacket.MovePlayerPacket)
|
||||
player := g.Players[movePlayer.PlayerId]
|
||||
path, _, found := g.MapEngine.PathFind(movePlayer.StartX, movePlayer.StartY, movePlayer.DestX, movePlayer.DestY)
|
||||
if found {
|
||||
path, _, _ := g.MapEngine.PathFind(movePlayer.StartX, movePlayer.StartY, movePlayer.DestX, movePlayer.DestY)
|
||||
if len(path) > 0 {
|
||||
player.AnimatedComposite.SetPath(path, func() {
|
||||
tile := g.MapEngine.TileAt(player.TileX, player.TileY)
|
||||
if tile == nil {
|
||||
|
|
Loading…
Reference in New Issue