diff --git a/README.md b/README.md index a79b0a3dd..a9b626ae3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ --- -Koolo is a small bot for Diablo II: Resurrected (Expension). Koolo project was built for informational and educational purposes +Koolo is a small bot for Diablo II: Resurrected (Expansion). Koolo project was built for informational and educational purposes only, it's not intended for online usage. Feel free to contribute opening pull requests with new features or bugfixes. Koolo reads game memory and interacts with the game injecting clicks/keystrokes to the game window. As good as it can. diff --git a/internal/action/stash.go b/internal/action/stash.go index a1a982cf9..4a1eff294 100644 --- a/internal/action/stash.go +++ b/internal/action/stash.go @@ -290,9 +290,23 @@ func stashItemAction(i data.Item, rule string, ruleFile string, skipLogging bool } } + dropLocation := "unknown" + + // log the contents of picked up items + ctx.Logger.Info(fmt.Sprintf("Picked up items: %v", ctx.CurrentGame.PickedUpItems)) + + if _, found := ctx.CurrentGame.PickedUpItems[int(i.UnitID)]; found { + areaId := ctx.CurrentGame.PickedUpItems[int(i.UnitID)] + dropLocation = area.ID(ctx.CurrentGame.PickedUpItems[int(i.UnitID)]).Area().Name + + if slices.Contains(ctx.Data.TerrorZones, area.ID(areaId)) { + dropLocation += " (terrorized)" + } + } + // Don't log items that we already have in inventory during first run or that we don't want to notify about (gems, low runes .. etc) if !skipLogging && shouldNotifyAboutStashing(i) && ruleFile != "" { - event.Send(event.ItemStashed(event.WithScreenshot(ctx.Name, fmt.Sprintf("Item %s [%d] stashed", i.Name, i.Quality), screenshot), data.Drop{Item: i, Rule: rule, RuleFile: ruleFile})) + event.Send(event.ItemStashed(event.WithScreenshot(ctx.Name, fmt.Sprintf("Item %s [%d] stashed", i.Name, i.Quality), screenshot), data.Drop{Item: i, Rule: rule, RuleFile: ruleFile, DropLocation: dropLocation})) } return true diff --git a/internal/action/step/pickup_item.go b/internal/action/step/pickup_item.go index 2a909cd95..3b5f5e319 100644 --- a/internal/action/step/pickup_item.go +++ b/internal/action/step/pickup_item.go @@ -79,6 +79,7 @@ func PickupItem(it data.Item) error { currentItem, exists := findItemOnGround(targetItem.UnitID) if !exists { ctx.Logger.Info(fmt.Sprintf("Picked up: %s [%s]", targetItem.Desc().Name, targetItem.Quality.ToString())) + ctx.CurrentGame.PickedUpItems[int(targetItem.UnitID)] = int(ctx.Data.PlayerUnit.Area.Area().ID) return nil // Success! } diff --git a/internal/bot/bot.go b/internal/bot/bot.go index a834f4c48..985d15ae5 100644 --- a/internal/bot/bot.go +++ b/internal/bot/bot.go @@ -40,6 +40,10 @@ func (b *Bot) Run(ctx context.Context, firstRun bool, runs []run.Run) error { // Let's make sure we have updated game data also fully loaded before performing anything b.ctx.WaitForGameToLoad() + + // Cleanup the current game helper structure + b.ctx.Cleanup() + // Switch to legacy mode if configured action.SwitchToLegacyMode() b.ctx.RefreshGameData() diff --git a/internal/context/context.go b/internal/context/context.go index 85320f508..92d5f9b68 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -62,6 +62,7 @@ type Debug struct { type CurrentGameHelper struct { BlacklistedItems []data.Item + PickedUpItems map[int]int AreaCorrection struct { Enabled bool ExpectedArea area.ID @@ -81,7 +82,7 @@ func NewContext(name string) *Status { PriorityPause: {}, PriorityStop: {}, }, - CurrentGame: &CurrentGameHelper{}, + CurrentGame: NewGameHelper(), } botContexts[getGoroutineID()] = &Status{Priority: PriorityNormal, Context: ctx} @@ -90,7 +91,9 @@ func NewContext(name string) *Status { func NewGameHelper() *CurrentGameHelper { return &CurrentGameHelper{ - PickupItems: true, + PickupItems: true, + PickedUpItems: make(map[int]int), + BlacklistedItems: []data.Item{}, } } @@ -168,3 +171,16 @@ func (ctx *Context) WaitForGameToLoad() { // Add a small buffer to ensure everything is fully loaded time.Sleep(300 * time.Millisecond) } + +func (ctx *Context) Cleanup() { + ctx.Logger.Debug("Resetting blacklisted items") + + // Remove all items from the blacklisted items list + ctx.CurrentGame.BlacklistedItems = []data.Item{} + + // Remove all items from the picked up items map if it exceeds 200 items + if len(ctx.CurrentGame.PickedUpItems) > 200 { + ctx.Logger.Debug("Resetting picked up items map due to exceeding 200 items") + ctx.CurrentGame.PickedUpItems = make(map[int]int) + } +} diff --git a/internal/game/area.go b/internal/game/area.go index 5fa8d0d0e..2a219e517 100644 --- a/internal/game/area.go +++ b/internal/game/area.go @@ -1,6 +1,8 @@ package game import ( + "slices" + "github.com/hectorgimenez/d2go/pkg/data" "github.com/hectorgimenez/d2go/pkg/data/area" ) @@ -18,3 +20,41 @@ type AreaData struct { func (ad AreaData) IsInside(pos data.Position) bool { return pos.X > ad.OffsetX && pos.Y > ad.OffsetY && pos.X < ad.OffsetX+ad.Width && pos.Y < ad.OffsetY+ad.Height } + +var _85Zones = []area.ID{ + area.Mausoleum, + area.UndergroundPassageLevel2, + area.PitLevel1, + area.PitLevel2, + area.StonyTombLevel1, + area.StonyTombLevel2, + area.MaggotLairLevel3, + area.AncientTunnels, + area.SwampyPitLevel1, + area.SwampyPitLevel2, + area.SwampyPitLevel3, + area.SpiderCave, + area.SewersLevel1Act3, + area.SewersLevel2Act3, + area.DisusedFane, + area.RuinedTemple, + area.ForgottenReliquary, + area.ForgottenTemple, + area.RuinedFane, + area.DisusedReliquary, + area.RiverOfFlame, + area.ChaosSanctuary, + area.Abaddon, + area.PitOfAcheron, + area.InfernalPit, + area.DrifterCavern, + area.IcyCellar, + area.TheWorldStoneKeepLevel1, + area.TheWorldStoneKeepLevel2, + area.TheWorldStoneKeepLevel3, + area.ThroneOfDestruction, +} + +func (ad AreaData) Is85Zone() bool { + return slices.Contains(_85Zones, ad.Area.Area().ID) +} diff --git a/internal/run/spider_cavern.go b/internal/run/spider_cavern.go index 0b6b73015..7646c12fe 100644 --- a/internal/run/spider_cavern.go +++ b/internal/run/spider_cavern.go @@ -27,7 +27,7 @@ func (run SpiderCavern) Run() error { monsterFilter := data.MonsterAnyFilter() // Update filter if we selected to clear only elites - if run.ctx.CharacterCfg.Game.DrifterCavern.FocusOnElitePacks { + if run.ctx.CharacterCfg.Game.SpiderCavern.FocusOnElitePacks { monsterFilter = data.MonsterEliteFilter() } diff --git a/internal/server/templates/drops.gohtml b/internal/server/templates/drops.gohtml index 3ca39c967..7b847f66b 100644 --- a/internal/server/templates/drops.gohtml +++ b/internal/server/templates/drops.gohtml @@ -4,184 +4,328 @@ - - - + Drops for {{.Character}} - -
- ← Back -

Drops for {{.Character}}

-

Total Drops: {{.NumberOfDrops}}

-
-
-
-
-
    - {{ range .Drops }} -
  • -
    - {{ .Item.Name }} - + +
    + + +
    + +
    +

    Drops for {{.Character}}

    +

    Total Drops: {{.NumberOfDrops}}

    +
    +
    +
    + + +
    + +
    + + +
    + {{ range .Drops }} +
    +
    +
    + {{ .Item.Name }}
    -
    +
    +
    + + {{ if not .Item.Identified }} +
    +
    Unidentified
    +
    + {{ else }} + + {{ range .Item.Stats }} + {{ if eq .ID 31 }} +
    + Defense: {{ .Value }} +
    + {{ end }} + {{ end }} + + + {{ $currentDur := 0 }} + {{ $maxDur := 0 }} + {{ range .Item.Stats }} + {{ if eq .ID 72 }} + {{ $currentDur = .Value }} + {{ end }} + {{ if eq .ID 73 }} + {{ $maxDur = .Value }} + {{ end }} + {{ end }} + {{ if and (ne $currentDur 0) (ne $maxDur 0) }} +
    + Durability: {{ $currentDur }} of {{ $maxDur }} +
    + {{ end }} + + +
    -

    Quality: {{ .Item.Quality.ToString }}

    -

    Ethereal: {{ if .Item.Ethereal }}True{{ else }}False{{ end }}

    -

    Identified: {{ if .Item.Identified }}True{{ else }}False{{ end }}

    -

    Stats:

    -
      - {{ range .Item.Stats }} -
    • {{ .ID | statIDToText }}: {{ .Value }}
    • + + {{ range .Item.Stats }} + {{ if and (ne .ID 31) (ne .ID 72) (ne .ID 73)}} +
      {{ .String }}
      {{ end }} -
    -

    Matched Rule: {{ .Rule }}

    -

    Rule File: {{ .RuleFile }}

    + {{ end }} + + {{ if .Item.Ethereal }} +
    Ethereal (Cannot be Repaired)
    + {{ end }}
    -
  • {{ end }} -
-
+ + {{ if .DropLocation }} +
+ Dropped in: {{ .DropLocation }} +
+ {{ end }} + + {{ if .Rule }} +
+ Stashed due to rule: {{ .Rule }} +
Rule location: {{ .RuleFile }} +
+ {{ end }} + +
+ {{ end }} -
+