Skip to content

Commit

Permalink
Cain identify fix + improved npc interaction (#631)
Browse files Browse the repository at this point in the history
  • Loading branch information
elobo91 authored Jan 26, 2025
1 parent 8623088 commit 5c53c0d
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 49 deletions.
42 changes: 29 additions & 13 deletions internal/action/identify.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package action

import (
"fmt"
"time"

"github.com/hectorgimenez/d2go/pkg/data"
"github.com/hectorgimenez/d2go/pkg/data/item"
Expand Down Expand Up @@ -30,6 +31,10 @@ func IdentifyAll(skipIdentify bool) error {

if ctx.CharacterCfg.Game.UseCainIdentify {
ctx.Logger.Debug("Identifying all item with Cain...")
// Close any open menus first
step.CloseAllMenus()
utils.Sleep(500)

err := CainIdentify()
// if identifying with cain fails then we should continue to identify using tome
if err == nil {
Expand Down Expand Up @@ -72,29 +77,40 @@ func CainIdentify() error {

stayAwhileAndListen := town.GetTownByArea(ctx.Data.PlayerUnit.Area).IdentifyNPC()

// Close any open menus first
step.CloseAllMenus()
utils.Sleep(200)

err := InteractNPC(stayAwhileAndListen)
if err != nil {
ctx.Logger.Error("Error interacting with Cain: ", "error", err.Error())
return err
return fmt.Errorf("error interacting with Cain: %w", err)
}

// Select the identify option
ctx.HID.KeySequence(win.VK_HOME, win.VK_DOWN, win.VK_RETURN)
utils.Sleep(500)

if len(itemsToIdentify()) > 0 {

// Close the NPC interact menu if it's open
// Verify menu opened
menuWait := time.Now().Add(2 * time.Second)
for time.Now().Before(menuWait) {
ctx.PauseIfNotPriority()
ctx.RefreshGameData()
if ctx.Data.OpenMenus.NPCInteract {
ctx.HID.KeySequence(win.VK_ESCAPE)
break
}
utils.Sleep(100)
}

return fmt.Errorf("failed to identify items")
if !ctx.Data.OpenMenus.NPCInteract {
return fmt.Errorf("NPC menu did not open")
}

utils.Sleep(500)
// Select identify option
ctx.HID.KeySequence(win.VK_HOME, win.VK_DOWN, win.VK_RETURN)
utils.Sleep(800)

return step.CloseAllMenus()
// Close menu if still open
if ctx.Data.OpenMenus.NPCInteract {
step.CloseAllMenus()
}

return nil
}

func itemsToIdentify() (items []data.Item) {
Expand Down
91 changes: 55 additions & 36 deletions internal/action/step/interact_npc.go
Original file line number Diff line number Diff line change
@@ -1,69 +1,88 @@
package step

import (
"errors"
"fmt"
"time"

"github.com/hectorgimenez/d2go/pkg/data"
"github.com/hectorgimenez/d2go/pkg/data/npc"
"github.com/hectorgimenez/koolo/internal/context"
"github.com/hectorgimenez/koolo/internal/game"
"github.com/hectorgimenez/koolo/internal/pather"
"github.com/hectorgimenez/koolo/internal/ui"
)

func InteractNPC(npcID npc.ID) error {
maxInteractionAttempts := 5
interactionAttempts := 0
waitingForInteraction := false
currentMouseCoords := data.Position{}
lastRun := time.Time{}

ctx := context.Get()
ctx.SetLastStep("InteractNPC")

for {
ctx.RefreshGameData()
const (
maxAttempts = 8
minMenuOpenWait = 300 * time.Millisecond
maxDistance = 15
hoverWait = 800 * time.Millisecond
)

var targetNPCID data.UnitID

for attempts := 0; attempts < maxAttempts; attempts++ {
// Pause the execution if the priority is not the same as the execution priority
ctx.PauseIfNotPriority()

if ctx.Data.OpenMenus.NPCInteract {
return nil
}
// Check if interaction succeeded and menu is open
if ctx.Data.OpenMenus.NPCInteract || ctx.Data.OpenMenus.NPCShop {
// Find current NPC position
if targetNPCID != 0 {
if currentNPC, found := ctx.Data.Monsters.FindByID(targetNPCID); found {
currentDistance := pather.DistanceFromPoint(currentNPC.Position, ctx.Data.PlayerUnit.Position)
if currentDistance <= maxDistance {
time.Sleep(minMenuOpenWait)
return nil
}
}
}

if interactionAttempts >= maxInteractionAttempts {
return errors.New("failed interacting with NPC")
// Wrong NPC, too far, or NPC moved - close menu and retry
CloseAllMenus()
time.Sleep(200 * time.Millisecond)
targetNPCID = 0
continue
}

// Give some time before retrying the interaction
if waitingForInteraction && time.Since(lastRun) < time.Millisecond*200 {
townNPC, found := ctx.Data.Monsters.FindOne(npcID, data.MonsterTypeNone)
if !found {
if attempts == maxAttempts-1 {
return fmt.Errorf("NPC %d not found after %d attempts", npcID, maxAttempts)
}
time.Sleep(200 * time.Millisecond)
continue
}

lastRun = time.Now()
m, found := ctx.Data.Monsters.FindOne(npcID, data.MonsterTypeNone)
if found {
if m.IsHovered {
ctx.HID.Click(game.LeftButton, currentMouseCoords.X, currentMouseCoords.Y)
waitingForInteraction = true
interactionAttempts++
continue
}
distance := ctx.PathFinder.DistanceFromMe(townNPC.Position)
if distance > maxDistance {
return fmt.Errorf("NPC %d is too far away (distance: %d)", npcID, distance)
}

distance := ctx.PathFinder.DistanceFromMe(m.Position)
if distance > 15 {
return fmt.Errorf("NPC is too far away: %d. Current distance: %d", npcID, distance)
}
// Calculate click position
x, y := ui.GameCoordsToScreenCords(townNPC.Position.X, townNPC.Position.Y)
if npcID == npc.Tyrael2 {
y = y - 40 // Act 4 Tyrael has a super weird hitbox
}

// Move mouse and wait for hover
ctx.HID.MovePointer(x, y)
hoverStart := time.Now()

x, y := ui.GameCoordsToScreenCords(m.Position.X, m.Position.Y)
// Act 4 Tyrael has a super weird hitbox
if npcID == npc.Tyrael2 {
y = y - 40
for time.Since(hoverStart) < hoverWait {
if currentNPC, found := ctx.Data.Monsters.FindOne(npcID, data.MonsterTypeNone); found && currentNPC.IsHovered {
targetNPCID = currentNPC.UnitID
ctx.HID.Click(game.LeftButton, x, y)
time.Sleep(minMenuOpenWait)
break
}
currentMouseCoords = data.Position{X: x, Y: y}
ctx.HID.MovePointer(x, y)
interactionAttempts++
time.Sleep(50 * time.Millisecond)
}
}

return fmt.Errorf("failed to interact with NPC after %d attempts", maxAttempts)
}

0 comments on commit 5c53c0d

Please sign in to comment.