126 lines
2.9 KiB
Go
126 lines
2.9 KiB
Go
package itemwatcher
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/faiface/beep"
|
|
"github.com/faiface/beep/speaker"
|
|
"github.com/faiface/beep/wav"
|
|
"github.com/hectorgimenez/d2go/pkg/data"
|
|
"github.com/hectorgimenez/d2go/pkg/data/area"
|
|
"github.com/hectorgimenez/d2go/pkg/data/item"
|
|
"github.com/hectorgimenez/d2go/pkg/memory"
|
|
"github.com/hectorgimenez/d2go/pkg/nip"
|
|
)
|
|
|
|
type Watcher struct {
|
|
gr *memory.GameReader
|
|
rules []nip.Rule
|
|
alreadyNotifiedItemIDs []itemFootprint
|
|
}
|
|
|
|
type itemFootprint struct {
|
|
detectedAt time.Time
|
|
area area.ID
|
|
position data.Position
|
|
name item.Name
|
|
quality item.Quality
|
|
}
|
|
|
|
func (fp itemFootprint) Match(area area.ID, i data.Item) bool {
|
|
return fp.area == area && fp.position == i.Position && fp.name == i.Name && fp.quality == i.Quality
|
|
}
|
|
|
|
func NewWatcher(gr *memory.GameReader, rules []nip.Rule) *Watcher {
|
|
return &Watcher{gr: gr, rules: rules}
|
|
}
|
|
|
|
func (w *Watcher) Start(ctx context.Context) error {
|
|
w.alreadyNotifiedItemIDs = make([]itemFootprint, 0)
|
|
audioBuffer, err := initAudio()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
default:
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
d := w.gr.GetData()
|
|
for _, i := range d.Items.ByLocation(item.LocationGround) {
|
|
for _, r := range w.rules {
|
|
match, err := r.Evaluate(i)
|
|
if err != nil {
|
|
log.Printf("error evaluating rule: %v", err)
|
|
continue
|
|
}
|
|
if !match {
|
|
continue
|
|
}
|
|
}
|
|
|
|
found := false
|
|
for _, fp := range w.alreadyNotifiedItemIDs {
|
|
if fp.Match(d.PlayerUnit.Area, i) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if found {
|
|
continue
|
|
}
|
|
|
|
log.Printf("%s: Item detected: %s. Quality: %s", time.Now().Format(time.RFC3339), i.Name, i.Quality.ToString())
|
|
|
|
w.alreadyNotifiedItemIDs = append(w.alreadyNotifiedItemIDs, itemFootprint{
|
|
detectedAt: time.Now(),
|
|
area: d.PlayerUnit.Area,
|
|
position: i.Position,
|
|
name: i.Name,
|
|
quality: i.Quality,
|
|
})
|
|
speaker.Play(audioBuffer.Streamer(0, audioBuffer.Len()))
|
|
}
|
|
|
|
// Cleanup after 10 minute AND out of range
|
|
purgedNotifiedItems := make([]itemFootprint, 0)
|
|
for _, t := range w.alreadyNotifiedItemIDs {
|
|
found := false
|
|
for _, it := range d.Items.ByLocation(item.LocationGround) {
|
|
if t.Match(d.PlayerUnit.Area, it) {
|
|
found = true
|
|
}
|
|
}
|
|
if found || time.Since(t.detectedAt) < time.Minute*10 {
|
|
purgedNotifiedItems = append(purgedNotifiedItems, t)
|
|
}
|
|
}
|
|
w.alreadyNotifiedItemIDs = purgedNotifiedItems
|
|
}
|
|
}
|
|
}
|
|
|
|
func initAudio() (*beep.Buffer, error) {
|
|
f, err := os.Open("assets/ching.wav")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
streamer, format, err := wav.Decode(f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10))
|
|
buffer := beep.NewBuffer(format)
|
|
buffer.Append(streamer)
|
|
streamer.Close()
|
|
|
|
return buffer, nil
|
|
}
|