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

Improve keep distance while attacking #645

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 31 additions & 17 deletions internal/action/step/attack.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package step
import (
"errors"
"fmt"
"sync"
"time"

"github.com/hectorgimenez/d2go/pkg/data"
Expand All @@ -16,6 +17,11 @@ import (

const attackCycleDuration = 120 * time.Millisecond

var (
statesMutex sync.RWMutex
monsterStates = make(map[data.UnitID]*attackState)
)

// Contains all configuration for an attack sequence
type attackSettings struct {
primaryAttack bool // Whether this is a primary (left click) attack
Expand All @@ -41,8 +47,6 @@ type attackState struct {
position data.Position
}

var monsterAttackStates = make(map[data.UnitID]*attackState)

// Distance configures attack to follow enemy within specified range
func Distance(minimum, maximum int) AttackOption {
return func(step *attackSettings) {
Expand Down Expand Up @@ -296,7 +300,7 @@ func ensureEnemyIsInRange(monster data.Monster, maxDistance, minDistance int, ne
hasLoS := ctx.PathFinder.LineOfSight(currentPos, monster.Position)

// We have line of sight, and we are inside the attack range, we can skip
if hasLoS && distanceToMonster <= maxDistance && distanceToMonster >= minDistance && !needsRepositioning {
if hasLoS && distanceToMonster <= maxDistance && !needsRepositioning {
return nil
}
// Handle repositioning if needed
Expand Down Expand Up @@ -334,13 +338,16 @@ func ensureEnemyIsInRange(monster data.Monster, maxDistance, minDistance int, ne
Y: pos.Y + ctx.Data.AreaData.OffsetY,
}

// Handle overshooting for short distances (Nova distances)
distanceToMove := ctx.PathFinder.DistanceFromMe(dest)
if distanceToMove <= DistanceToFinishMoving {
dest = ctx.PathFinder.BeyondPosition(currentPos, dest, 9)
}

if ctx.PathFinder.LineOfSight(dest, monster.Position) {
distanceToMove := ctx.PathFinder.DistanceFromMe(dest)
if distanceToMove < DistanceToFinishMoving {
// Allow precise close movements
if distanceToMove >= 2 {
return MoveTo(dest, WithDistanceToFinish(2))
}
// Otherwise overshoot to avoid micro-movements
dest = ctx.PathFinder.BeyondPosition(currentPos, dest, 9)
}
return MoveTo(dest)
}
}
Expand All @@ -349,37 +356,44 @@ func ensureEnemyIsInRange(monster data.Monster, maxDistance, minDistance int, ne
}

func checkMonsterDamage(monster data.Monster) (bool, *attackState) {
state, exists := monsterAttackStates[monster.UnitID]
statesMutex.Lock()
defer statesMutex.Unlock()

state, exists := monsterStates[monster.UnitID]
if !exists {
state = &attackState{
lastHealth: monster.Stats[stat.Life],
lastHealthCheckTime: time.Now(),
position: monster.Position,
}
monsterAttackStates[monster.UnitID] = state
monsterStates[monster.UnitID] = state
}

didDamage := false
currentHealth := monster.Stats[stat.Life]

// Only update health check if some time has passed
if time.Since(state.lastHealthCheckTime) > 100*time.Millisecond {
if currentHealth < state.lastHealth {
didDamage = true
state.failedAttemptStartTime = time.Time{}
} else if state.failedAttemptStartTime.IsZero() &&
monster.Position == state.position { // only start failing if monster hasn't moved
monster.Position == state.position {
state.failedAttemptStartTime = time.Now()
}

state.lastHealth = currentHealth
state.lastHealthCheckTime = time.Now()
state.position = monster.Position
}

// Clean up state map occasionally
if len(monsterAttackStates) > 100 {
monsterAttackStates = make(map[data.UnitID]*attackState)
// Clean up old entries periodically
if len(monsterStates) > 100 {
now := time.Now()
for id, s := range monsterStates {
if now.Sub(s.lastHealthCheckTime) > 5*time.Minute {
delete(monsterStates, id)
}
}
}
}

return didDamage, state
Expand Down