Skip to content

Commit

Permalink
remove redundant storage of path data across two separate systems
Browse files Browse the repository at this point in the history
The global cache in pather/path.go serves as the primary storage for path data and metadata.
The PathCache structure in context.go is maintained but modified to act as a reference to the global cache
  • Loading branch information
elobo91 committed Feb 25, 2025
1 parent 59e93a4 commit 7b2bd1b
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 103 deletions.
52 changes: 36 additions & 16 deletions internal/action/clear_area.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,54 +34,74 @@ func ClearAreaAroundPosition(pos data.Position, radius int, filter data.MonsterF
func ClearThroughPath(pos data.Position, radius int, filter data.MonsterFilter) error {
ctx := context.Get()
lastMovement := false
currentArea := ctx.Data.PlayerUnit.Area
canTeleport := ctx.Data.CanTeleport()

for {
ctx.PauseIfNotPriority()

// Clear enemies at current position
ClearAreaAroundPosition(ctx.Data.PlayerUnit.Position, radius, filter)

if lastMovement {
return nil
}

var path []data.Position
// Get current position for path calculation
currentPos := ctx.Data.PlayerUnit.Position

// Get path to destination, leveraging both context cache and global cache
var path pather.Path
var found bool

// Try context cache first for UI consistency
if ctx.CurrentGame.PathCache != nil &&
ctx.CurrentGame.PathCache.DestPosition == pos &&
step.IsPathValid(ctx.Data.PlayerUnit.Position, ctx.CurrentGame.PathCache) {
ctx.CurrentGame.PathCache.IsPathValid(currentPos) {
// Use existing path from context cache
path = ctx.CurrentGame.PathCache.Path
found = true
} else {

path, _, found = ctx.PathFinder.GetPath(pos)
if found {
ctx.CurrentGame.PathCache = &context.PathCache{
Path: path,
DestPosition: pos,
StartPosition: ctx.Data.PlayerUnit.Position,
DistanceToFinish: step.DistanceToFinishMoving,
// Try to get path from global cache
path, found = pather.GetCachedPath(currentPos, pos, currentArea, canTeleport)
if !found {
// Calculate new path if not in cache
path, _, found = ctx.PathFinder.GetPath(pos)
if !found {
return fmt.Errorf("path could not be calculated")
}

// Store in global cache
pather.StorePath(currentPos, pos, currentArea, canTeleport, path, currentPos)
}
}

if !found {
return fmt.Errorf("path could not be calculated")
// Update context cache for UI reference
ctx.CurrentGame.PathCache = &context.PathCache{
Path: path,
DestPosition: pos,
StartPosition: currentPos,
DistanceToFinish: step.DistanceToFinishMoving,
}
}

// Calculate movement distance for this segment
movementDistance := radius
if radius > len(path) {
if movementDistance > len(path) {
movementDistance = len(path)
}

// Set destination to next path segment
dest := data.Position{
X: path[movementDistance-1].X + ctx.Data.AreaData.OffsetX,
Y: path[movementDistance-1].Y + ctx.Data.AreaData.OffsetY,
X: path[movementDistance-1].X + ctx.Data.AreaOrigin.X,
Y: path[movementDistance-1].Y + ctx.Data.AreaOrigin.Y,
}

// Check if this is the last movement segment
if len(path)-movementDistance <= step.DistanceToFinishMoving {
lastMovement = true
}

// Move to next segment
if err := step.MoveTo(dest); err != nil {
return err
}
Expand Down
41 changes: 24 additions & 17 deletions internal/action/move.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,6 @@ func ensureAreaSync(ctx *context.Status, expectedArea area.ID) error {
return fmt.Errorf("area sync timeout - expected: %v, current: %v", expectedArea, ctx.Data.PlayerUnit.Area)
}

//TODO something is off with Diablo action.MoveToArea(area.ChaosSanctuary) , the transition between movetoarea and clearthrough path makes
// bot telestomp until timeout before resuming with the run

func MoveToArea(dst area.ID) error {
ctx := context.Get()
ctx.SetLastAction("MoveToArea")
Expand Down Expand Up @@ -215,30 +212,41 @@ func MoveTo(toFunc func() (data.Position, bool)) error {
ctx.PauseIfNotPriority()

currentPos := ctx.Data.PlayerUnit.Position
currentArea := ctx.Data.PlayerUnit.Area
canTeleport := ctx.Data.CanTeleport()

// Get current path segment
var path []data.Position
var path pather.Path
var found bool

// Handle path caching using both context.PathCache (for UI) and global cache (for storage)
if ctx.CurrentGame.PathCache != nil &&
ctx.CurrentGame.PathCache.DestPosition == to &&
step.IsPathValid(currentPos, ctx.CurrentGame.PathCache) {
ctx.CurrentGame.PathCache.IsPathValid(currentPos) {
// Use existing path from context cache
path = ctx.CurrentGame.PathCache.Path
found = true
} else {

path, _, found = ctx.PathFinder.GetPath(to)
if found {
ctx.CurrentGame.PathCache = &context.PathCache{
Path: path,
DestPosition: to,
StartPosition: currentPos,
DistanceToFinish: step.DistanceToFinishMoving,
// Try to get path from global cache
path, found = pather.GetCachedPath(currentPos, to, currentArea, canTeleport)
if !found {
// Calculate new path if not in cache
path, _, found = ctx.PathFinder.GetPath(to)
if !found {
return fmt.Errorf("path could not be calculated")
}

// Store in global cache
pather.StorePath(currentPos, to, currentArea, canTeleport, path, currentPos)
}
}

if !found {
return fmt.Errorf("path could not be calculated")
// Update context cache for UI reference
ctx.CurrentGame.PathCache = &context.PathCache{
Path: path,
DestPosition: to,
StartPosition: currentPos,
DistanceToFinish: step.DistanceToFinishMoving,
}
}

//TODO if character detects monster on other side of the wall and we are past door detection
Expand Down Expand Up @@ -289,7 +297,6 @@ func MoveTo(toFunc func() (data.Position, bool)) error {
}
}
// Continue moving

// WaitForAllMembersWhenLeveling is breaking walkable logic, lets not use it for now.

if lastMovement {
Expand Down
79 changes: 37 additions & 42 deletions internal/action/step/move.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,27 +51,35 @@ func MoveTo(dest data.Position, options ...MoveOption) error {

startedAt := time.Now()

//TODO use the pathcache directly from path.go ?

// Initialize or reuse path cache
var pathCache *context.PathCache
currentPos := ctx.Data.PlayerUnit.Position
currentArea := ctx.Data.PlayerUnit.Area
canTeleport := ctx.Data.CanTeleport()

if ctx.CurrentGame.PathCache != nil &&
ctx.CurrentGame.PathCache.DestPosition == dest &&
IsPathValid(ctx.Data.PlayerUnit.Position, ctx.CurrentGame.PathCache) {
ctx.CurrentGame.PathCache.IsPathValid(currentPos) {
// Reuse existing path cache
pathCache = ctx.CurrentGame.PathCache
} else {
// Create new path cache
start := ctx.Data.PlayerUnit.Position
path, _, found := ctx.PathFinder.GetPath(dest)
// Get path from global cache or calculate new one
path, found := pather.GetCachedPath(currentPos, dest, currentArea, canTeleport)
if !found {
return fmt.Errorf("path not found to %v", dest)
// Calculate new path if not found in cache
path, _, found = ctx.PathFinder.GetPath(dest)
if !found {
return fmt.Errorf("path not found to %v", dest)
}
// Store in global cache
pather.StorePath(currentPos, dest, currentArea, canTeleport, path, currentPos)
}

// Create new PathCache reference
pathCache = &context.PathCache{
Path: path,
DestPosition: dest,
StartPosition: start,
StartPosition: currentPos,
DistanceToFinish: DistanceToFinishMoving,
}
ctx.CurrentGame.PathCache = pathCache
Expand All @@ -85,6 +93,11 @@ func MoveTo(dest data.Position, options ...MoveOption) error {
// Add some delay between clicks to let the character move to destination
walkDuration := utils.RandomDurationMs(700, 900)

// Get last check time from global cache
lastCheck := pathCache.GetLastCheck(currentArea, canTeleport)
lastRun := pathCache.GetLastRun(currentArea, canTeleport)
previousPosition := pathCache.GetPreviousPosition(currentArea, canTeleport)

for {
time.Sleep(50 * time.Millisecond)
// Pause the execution if the priority is not the same as the execution priority
Expand All @@ -93,9 +106,9 @@ func MoveTo(dest data.Position, options ...MoveOption) error {
now := time.Now()

// Refresh data and perform checks periodically
if now.Sub(pathCache.LastCheck) > refreshInterval {
if now.Sub(lastCheck) > refreshInterval {
ctx.RefreshGameData()
currentPos := ctx.Data.PlayerUnit.Position
currentPos = ctx.Data.PlayerUnit.Position
distanceToDest := pather.DistanceFromPoint(currentPos, dest)

// Check if we've reached destination
Expand All @@ -104,14 +117,14 @@ func MoveTo(dest data.Position, options ...MoveOption) error {
}

// Check for stuck in same position (direct equality check is efficient)
isSamePosition := pathCache.PreviousPosition.X == currentPos.X && pathCache.PreviousPosition.Y == currentPos.Y
isSamePosition := previousPosition.X == currentPos.X && previousPosition.Y == currentPos.Y

// Only recalculate path in specific cases to reduce CPU usage
if isSamePosition && !ctx.Data.CanTeleport() {
// If stuck in same position without teleport, make random movement
ctx.PathFinder.RandomMovement()
} else if pathCache.Path == nil ||
!IsPathValid(currentPos, pathCache) ||
!pathCache.IsPathValid(currentPos) ||
(distanceToDest <= 15 && distanceToDest > pathCache.DistanceToFinish) {
//TODO this looks like the telestomp issue, IsSamePosition is true but it never enter this condition, need something else to force refresh

Expand All @@ -123,23 +136,28 @@ func MoveTo(dest data.Position, options ...MoveOption) error {
}
return fmt.Errorf("failed to calculate path")
}

// Update global cache and local PathCache
pather.StorePath(currentPos, dest, currentArea, canTeleport, path, currentPos)
pathCache.Path = path
pathCache.StartPosition = currentPos
}

pathCache.PreviousPosition = currentPos
pathCache.LastCheck = now
// Update previous position and check time
previousPosition = currentPos
lastCheck = now
pathCache.UpdateLastCheck(currentArea, canTeleport, currentPos)

if now.Sub(startedAt) > timeout {
return fmt.Errorf("movement timeout")
}
}

if !ctx.Data.CanTeleport() {
if time.Since(pathCache.LastRun) < walkDuration {
if time.Since(lastRun) < walkDuration {
continue
}
} else if time.Since(pathCache.LastRun) < ctx.Data.PlayerCastDuration() {
} else if time.Since(lastRun) < ctx.Data.PlayerCastDuration() {
continue
}

Expand All @@ -159,34 +177,11 @@ func MoveTo(dest data.Position, options ...MoveOption) error {
}
}

pathCache.LastRun = time.Now()
lastRun = time.Now()
pathCache.UpdateLastRun(currentArea, canTeleport)

if len(pathCache.Path) > 0 {
ctx.PathFinder.MoveThroughPath(pathCache.Path, walkDuration)
}
}
}

// Validate if the path is still valid based on current position
func IsPathValid(currentPos data.Position, cache *context.PathCache) bool {
if cache == nil {
return false
}

// Valid if we're close to start, destination, or current path
if pather.DistanceFromPoint(currentPos, cache.StartPosition) < 20 ||
pather.DistanceFromPoint(currentPos, cache.DestPosition) < 20 {
return true
}

// Check if we're near any point on the path
minDistance := 20
for _, pathPoint := range cache.Path {
dist := pather.DistanceFromPoint(currentPos, pathPoint)
if dist < minDistance {
minDistance = dist
break
}
}

return minDistance < 20
}
44 changes: 41 additions & 3 deletions internal/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,53 @@ type Debug struct {
LastAction string `json:"lastAction"`
LastStep string `json:"lastStep"`
}

type PathCache struct {
Path []data.Position
DestPosition data.Position
StartPosition data.Position
PreviousPosition data.Position
LastCheck time.Time
LastRun time.Time
DistanceToFinish int
}

func (pc *PathCache) IsPathValid(currentPos data.Position) bool {
if pc == nil {
return false
}
return pather.IsPathValid(currentPos, pc.StartPosition, pc.DestPosition, pc.Path)
}

func (pc *PathCache) GetLastRun(currentArea area.ID, canTeleport bool) time.Time {
entry, found := pather.GetCacheEntry(pc.StartPosition, pc.DestPosition, currentArea, canTeleport)
if found {
return entry.LastRun
}
return time.Time{}
}

func (pc *PathCache) UpdateLastRun(currentArea area.ID, canTeleport bool) {
pather.UpdatePathLastRun(pc.StartPosition, pc.DestPosition, currentArea, canTeleport)
}

func (pc *PathCache) GetLastCheck(currentArea area.ID, canTeleport bool) time.Time {
entry, found := pather.GetCacheEntry(pc.StartPosition, pc.DestPosition, currentArea, canTeleport)
if found {
return entry.LastCheck
}
return time.Time{}
}

func (pc *PathCache) UpdateLastCheck(currentArea area.ID, canTeleport bool, currentPos data.Position) {
pather.UpdatePathLastCheck(pc.StartPosition, pc.DestPosition, currentArea, canTeleport, currentPos)
}

func (pc *PathCache) GetPreviousPosition(currentArea area.ID, canTeleport bool) data.Position {
entry, found := pather.GetCacheEntry(pc.StartPosition, pc.DestPosition, currentArea, canTeleport)
if found {
return entry.PreviousPosition
}
return data.Position{}
}

type CurrentGameHelper struct {
BlacklistedItems []data.Item
PickedUpItems map[int]int
Expand Down Expand Up @@ -174,6 +211,7 @@ func (s *Status) PauseIfNotPriority() {
time.Sleep(time.Millisecond * 10)
}
}

func (ctx *Context) WaitForGameToLoad() {
// Get only the loading screen state from OpenMenus
for ctx.GameReader.GetData().OpenMenus.LoadingScreen {
Expand Down
Loading

0 comments on commit 7b2bd1b

Please sign in to comment.