Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Item Pickup Improvements #666

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
38 changes: 30 additions & 8 deletions internal/action/item_pickup.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"log/slog"
"slices"
"time"

"github.com/hectorgimenez/d2go/pkg/data"
"github.com/hectorgimenez/d2go/pkg/data/area"
Expand Down Expand Up @@ -47,9 +46,12 @@ func ItemPickup(maxDistance int) error {
ctx := context.Get()
ctx.SetLastAction("ItemPickup")

const maxRetries = 3
const maxRetries = 5
const maxItemTooFarAttempts = 5

for {
ctx.PauseIfNotPriority()

itemsToPickup := GetItemsToPickup(maxDistance)
if len(itemsToPickup) == 0 {
return nil
Expand Down Expand Up @@ -81,6 +83,7 @@ func ItemPickup(maxDistance int) error {
// Try to pick up the item with retries
var lastError error
attempt := 1
attemptItemTooFar := 1
for attempt <= maxRetries {
// Clear monsters on each attempt
ClearAreaAroundPosition(itemToPickup.Position, 4, data.MonsterAnyFilter())
Expand All @@ -101,6 +104,13 @@ func ItemPickup(maxDistance int) error {
X: itemToPickup.Position.X - moveDistance,
Y: itemToPickup.Position.Y + 1,
}
case 4:
pickupPosition = data.Position{
X: itemToPickup.Position.X + moveDistance + 2,
Y: itemToPickup.Position.Y - 3,
}
case 5:
ctx.PathFinder.BeyondPosition(ctx.Data.PlayerUnit.Position, itemToPickup.Position, 4)
}
}

Expand All @@ -119,27 +129,42 @@ func ItemPickup(maxDistance int) error {
}

// Try to pick up the item
err := step.PickupItem(itemToPickup)
err := step.PickupItem(itemToPickup, attempt)
if err == nil {
break // Success!
}

lastError = err
// Skip logging when casting moving error and don't count these specific errors as retry attempts
if errors.Is(err, step.ErrCastingMoving) {
continue
}
ctx.Logger.Debug(fmt.Sprintf("Pickup attempt %d failed: %v", attempt, err))

// Don't count these specific errors as retry attempts
if errors.Is(err, step.ErrMonsterAroundItem) || errors.Is(err, step.ErrItemTooFar) {
if errors.Is(err, step.ErrMonsterAroundItem) {
continue
}

// Item too far retry logic
if errors.Is(err, step.ErrItemTooFar) {
// Use default retries first, if we hit last attempt retry add random movement and continue until maxItemTooFarAttempts
if attempt >= maxRetries && attemptItemTooFar <= maxItemTooFarAttempts {
ctx.Logger.Debug(fmt.Sprintf("Item too far pickup attempt %d", attemptItemTooFar))
attemptItemTooFar++
ctx.PathFinder.RandomMovement()
continue
}
}

if errors.Is(err, step.ErrNoLOSToItem) {
ctx.Logger.Debug("No line of sight to item, moving closer",
slog.String("item", itemToPickup.Desc().Name))

// Try moving beyond the item for better line of sight
beyondPos := ctx.PathFinder.BeyondPosition(ctx.Data.PlayerUnit.Position, itemToPickup.Position, 2+attempt)
if mvErr := MoveToCoords(beyondPos); mvErr == nil {
err = step.PickupItem(itemToPickup)
err = step.PickupItem(itemToPickup, attempt)
if err == nil {
break
}
Expand All @@ -151,9 +176,6 @@ func ItemPickup(maxDistance int) error {

attempt++

if attempt <= maxRetries {
time.Sleep(150 * time.Duration(attempt-1) * time.Millisecond)
}
}

// If all attempts failed, blacklist the item
Expand Down
26 changes: 16 additions & 10 deletions internal/action/step/pickup_item.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/hectorgimenez/d2go/pkg/data"
"github.com/hectorgimenez/d2go/pkg/data/item"
"github.com/hectorgimenez/d2go/pkg/data/mode"
"github.com/hectorgimenez/d2go/pkg/data/stat"
"github.com/hectorgimenez/koolo/internal/context"
"github.com/hectorgimenez/koolo/internal/game"
Expand All @@ -15,22 +16,29 @@ import (
)

const (
maxInteractions = 45
spiralDelay = 50 * time.Millisecond
maxInteractions = 24 // 25 attempts since we start at 0
clickDelay = 25 * time.Millisecond
spiralDelay = 25 * time.Millisecond
pickupTimeout = 3 * time.Second
)

var (
ErrItemTooFar = errors.New("item is too far away")
ErrNoLOSToItem = errors.New("no line of sight to item")
ErrMonsterAroundItem = errors.New("monsters detected around item")
ErrCastingMoving = errors.New("char casting or moving")
)

func PickupItem(it data.Item) error {
func PickupItem(it data.Item, itemPickupAttempt int) error {
ctx := context.Get()
ctx.SetLastStep("PickupItem")

// Casting skill/moving return back
for ctx.Data.PlayerUnit.Mode == mode.CastingSkill || ctx.Data.PlayerUnit.Mode == mode.Running || ctx.Data.PlayerUnit.Mode == mode.Walking || ctx.Data.PlayerUnit.Mode == mode.WalkingInTown {
time.Sleep(25 * time.Millisecond)
return ErrCastingMoving
}

// Calculate base screen position for item
baseX := it.Position.X - 1
baseY := it.Position.Y - 1
Expand Down Expand Up @@ -59,7 +67,7 @@ func PickupItem(it data.Item) error {
spiralAttempt := 0
targetItem := it
lastMonsterCheck := time.Now()
const monsterCheckInterval = 250 * time.Millisecond
const monsterCheckInterval = 150 * time.Millisecond

startTime := time.Now()

Expand All @@ -78,14 +86,14 @@ func PickupItem(it data.Item) error {
// Check if item still exists
currentItem, exists := findItemOnGround(targetItem.UnitID)
if !exists {
ctx.Logger.Info(fmt.Sprintf("Picked up: %s [%s]", targetItem.Desc().Name, targetItem.Quality.ToString()))
ctx.Logger.Info(fmt.Sprintf("Picked up: %s [%s] | Item Pickup Attempt:%d | Spiral Attempt:%d", targetItem.Desc().Name, targetItem.Quality.ToString(), itemPickupAttempt, spiralAttempt))
return nil // Success!
}

// Check timeout conditions
if spiralAttempt > maxInteractions ||
(!waitingForInteraction.IsZero() && time.Since(waitingForInteraction) > pickupTimeout) ||
time.Since(startTime) > pickupTimeout*2 {
time.Since(startTime) > pickupTimeout {
return fmt.Errorf("failed to pick up %s after %d attempts", it.Desc().Name, spiralAttempt)
}

Expand All @@ -97,10 +105,8 @@ func PickupItem(it data.Item) error {
ctx.HID.MovePointer(cursorX, cursorY)
time.Sleep(spiralDelay)

// Refresh game state and check hover
ctx.RefreshGameData()

if currentItem.IsHovered {
// Click on item if mouse is hovering over
if currentItem.UnitID == ctx.GameReader.GameReader.GetData().HoverData.UnitID {
ctx.HID.Click(game.LeftButton, cursorX, cursorY)
time.Sleep(clickDelay)

Expand Down