Skip to content

Commit

Permalink
ItemPickupspiral,FindNearbyWalkablePosition, cleanup
Browse files Browse the repository at this point in the history
Spiral ( used by item pickup) is now super precise.

FindNearbyWalkablePosition now accept a max radius default to 3.  This is the new version used by clear_level optimization pr but it benefit whole bot for pathing. Way more accurate.

Code cleanup, optimization
  • Loading branch information
elobo91 committed Dec 24, 2024
1 parent b5f16dc commit c0ef1ca
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 53 deletions.
65 changes: 32 additions & 33 deletions internal/action/step/interact_entrance.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
const (
maxMoveRetries = 3
maxAttempts = 15
hoverDelay = 100
interactDelay = 300
hoverDelay = 25
interactDelay = 100
)

func InteractEntrance(targetArea area.ID) error {
Expand All @@ -36,45 +36,63 @@ func InteractEntrance(targetArea area.ID) error {

attempts := 0
currentMouseCoords := data.Position{}
lastAttempt := time.Time{}
lastAttempt := time.Now()

for {
ctx.PauseIfNotPriority()
ctx.RefreshGameData()

// Handle loading screen early
if ctx.Data.OpenMenus.LoadingScreen {
ctx.WaitForGameToLoad()
continue
}

if hasReachedArea(ctx, targetArea, lastAttempt) {
// Check if we've reached the target area
if ctx.Data.AreaData.Area == targetArea &&
time.Since(lastAttempt) > interactDelay &&
ctx.Data.AreaData.IsInside(ctx.Data.PlayerUnit.Position) {
return nil
}

if attempts >= maxAttempts {
return fmt.Errorf("failed to enter area %s after all attempts", targetArea.Area().Name)
return fmt.Errorf("failed to enter area %s after %d attempts", targetArea.Area().Name, maxAttempts)
}

if time.Since(lastAttempt) < interactDelay {
time.Sleep(50 * time.Millisecond)
continue
}
lastAttempt = time.Now()

// Ensure we're in range of the entrance
if err := ensureInRange(ctx, targetLevel.Position); err != nil {
return err
}

// Handle hovering and interaction . We also need UnitType 2 here because sometimes entrances like ancient tunnel is both (unittype 2 the trap, unittype 5 to enter area)
if ctx.Data.HoverData.UnitType == 5 || (ctx.Data.HoverData.UnitType == 2 && ctx.Data.HoverData.IsHovered) {
attemptInteraction(ctx, currentMouseCoords)
ctx.HID.Click(game.LeftButton, currentMouseCoords.X, currentMouseCoords.Y)
lastAttempt = time.Now()
attempts++

// Wait for loading screen after click
startTime := time.Now()
for time.Since(startTime) < time.Second {
if ctx.Data.OpenMenus.LoadingScreen {
ctx.WaitForGameToLoad()
break
}
time.Sleep(50 * time.Millisecond)
}
continue
}

// Calculate and set new mouse position using spiral pattern
// Calculate new mouse position using spiral pattern
currentMouseCoords = calculateMouseCoords(ctx, targetLevel.Position, attempts, entranceDesc)
ctx.HID.MovePointer(currentMouseCoords.X, currentMouseCoords.Y)
attempts++
utils.Sleep(hoverDelay)

time.Sleep(hoverDelay * time.Millisecond)
}
}

Expand Down Expand Up @@ -111,18 +129,6 @@ func findClosestEntrance(ctx *context.Status, targetArea area.ID) *data.Level {
return closest
}

func hasReachedArea(ctx *context.Status, targetArea area.ID, lastAttempt time.Time) bool {
return ctx.Data.AreaData.Area == targetArea &&
time.Since(lastAttempt) > interactDelay &&
ctx.Data.AreaData.IsInside(ctx.Data.PlayerUnit.Position)
}

func calculateMouseCoords(ctx *context.Status, pos data.Position, attempts int, desc entrance.Description) data.Position {
baseX, baseY := ctx.PathFinder.GameCoordsToScreenCords(pos.X, pos.Y)
x, y := utils.EntranceSpiral(attempts, desc)
return data.Position{X: baseX + x, Y: baseY + y}
}

func ensureInRange(ctx *context.Status, pos data.Position) error {
for retry := 0; retry < maxMoveRetries; retry++ {
distance := ctx.PathFinder.DistanceFromMe(pos)
Expand All @@ -134,23 +140,16 @@ func ensureInRange(ctx *context.Status, pos data.Position) error {
continue
}

// Check distance after movement
if ctx.PathFinder.DistanceFromMe(pos) <= DistanceToFinishMoving {
return nil
}
utils.Sleep(200)
}
return fmt.Errorf("failed to get in range of entrance after %d attempts", maxMoveRetries)
}

func attemptInteraction(ctx *context.Status, pos data.Position) {
ctx.HID.Click(game.LeftButton, pos.X, pos.Y)
startTime := time.Now()
for time.Since(startTime) < 2*time.Second {
if ctx.Data.OpenMenus.LoadingScreen {
ctx.WaitForGameToLoad()
break
}
utils.Sleep(50)
}
utils.Sleep(200)
func calculateMouseCoords(ctx *context.Status, pos data.Position, attempts int, desc entrance.Description) data.Position {
baseX, baseY := ctx.PathFinder.GameCoordsToScreenCords(pos.X, pos.Y)
x, y := utils.EntranceSpiral(attempts, desc)
return data.Position{X: baseX + x, Y: baseY + y}
}
1 change: 0 additions & 1 deletion internal/action/step/interact_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ func InteractObject(obj data.Object, isCompletedFn func() bool) error {
utils.Sleep(50)
}

utils.Sleep(500)
for attempts := 0; attempts < maxPortalSyncAttempts; attempts++ {
if ctx.Data.PlayerUnit.Area == o.PortalData.DestArea {
if areaData, ok := ctx.Data.Areas[o.PortalData.DestArea]; ok {
Expand Down
45 changes: 35 additions & 10 deletions internal/pather/path_finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func (pf *PathFinder) GetPath(to data.Position) (Path, int, bool) {
return path, distance, true
}

// If direct path fails, try to find nearby walkable position
if walkableTo, found := pf.findNearbyWalkablePosition(to); found {
// If direct path fails, try to find nearby walkable position with default radius for pathing
if walkableTo, found := pf.FindNearbyWalkablePosition(to, 0); found {
return pf.GetPathFrom(pf.data.PlayerUnit.Position, walkableTo)
}

Expand Down Expand Up @@ -148,7 +148,6 @@ func copyGrid(dest [][]game.CollisionType, src [][]game.CollisionType, offsetX,
}
}
}

func (pf *PathFinder) GetClosestWalkablePath(dest data.Position) (Path, int, bool) {
return pf.GetClosestWalkablePathFrom(pf.data.PlayerUnit.Position, dest)
}
Expand Down Expand Up @@ -187,20 +186,46 @@ func (pf *PathFinder) GetClosestWalkablePathFrom(from, dest data.Position) (Path
return nil, 0, false
}

func (pf *PathFinder) findNearbyWalkablePosition(target data.Position) (data.Position, bool) {
func (pf *PathFinder) FindNearbyWalkablePosition(target data.Position, radius int) (data.Position, bool) {
// Use default radius of 3 if none specified
searchRadius := 3
if radius > 0 {
searchRadius = radius
}

// Convert target to grid coordinates
gridTarget := pf.data.AreaData.Grid.RelativePosition(target)

// Search in expanding squares around the target position
for radius := 1; radius <= 3; radius++ {
for x := -radius; x <= radius; x++ {
for y := -radius; y <= radius; y++ {
for r := 1; r <= searchRadius; r++ {
for x := -r; x <= r; x++ {
for y := -r; y <= r; y++ {
if x == 0 && y == 0 {
continue
}
pos := data.Position{X: target.X + x, Y: target.Y + y}
if pf.data.AreaData.IsWalkable(pos) {
return pos, true

// Work in grid coordinates
gridPos := data.Position{
X: gridTarget.X + x,
Y: gridTarget.Y + y,
}

// Check boundaries and walkability in grid coordinates
if gridPos.X < 0 || gridPos.X >= pf.data.AreaData.Width ||
gridPos.Y < 0 || gridPos.Y >= pf.data.AreaData.Height ||
pf.data.AreaData.CollisionGrid[gridPos.Y][gridPos.X] == game.CollisionTypeNonWalkable {
continue
}

// Convert back to world coordinates before returning
worldPos := data.Position{
X: gridPos.X + pf.data.AreaData.OffsetX,
Y: gridPos.Y + pf.data.AreaData.OffsetY,
}
return worldPos, true
}
}
}

return data.Position{}, false
}
26 changes: 17 additions & 9 deletions internal/utils/spiral.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@ import (
)

func Spiral(position int) (int, int) {
t := position * 40
a := 4.0
b := -2.0
t := position * 25

a := 3.0 // - a controls the starting radius
b := -1.5 // - b controls how quickly the spiral expands

// Convert to radians and calculate position
trad := float64(t) * math.Pi / 180.0

// Calculate spiral coordinates with a slight vertical bias since
// D2 uses isometric projection (items appear higher than their actual position)
x := (a + b*trad) * math.Cos(trad)
y := (a + b*trad) * math.Sin(trad)
y := (a + b*trad) * math.Sin(trad) * 0.9 // Slight vertical compression

return int(x), int(y)
}
Expand All @@ -25,14 +31,16 @@ func ObjectSpiral(attempt int, desc object.Description) (x, y int) {

// Special handling for portals
if desc.Width == 80 && desc.Height == 110 {
xScale := 1.0
yScale := 110.0 / 80.0

xScale := 1.2
yScale := 1.4

x = int(baseRadius * math.Cos(angle) * xScale)
y = int(baseRadius*math.Sin(angle)*yScale) - 50
y = int(baseRadius*math.Sin(angle)*yScale) - 40

x = Clamp(x, -40, 40)
y = Clamp(y, -100, 10)
// Ensure we stay within reasonable bounds while providing wider coverage
x = Clamp(x, -50, 50)
y = Clamp(y, -110, 20)

return x, y
}
Expand Down

0 comments on commit c0ef1ca

Please sign in to comment.