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:
nicholas-eden 2020-06-24 05:09:00 -07:00 committed by GitHub
parent a24c05efa9
commit 329e2f0fab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 47 additions and 24 deletions

View File

@ -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
------

View File

@ -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
}

View File

@ -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

View File

@ -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 {