Item refactor and more (#1)

This commit is contained in:
Héctor Giménez
2023-08-18 22:33:54 +09:00
committed by GitHub
parent 9a3b131826
commit 1c8aab1580
13 changed files with 213 additions and 64 deletions

View File

@@ -2,6 +2,10 @@ package itemwatcher
import ( import (
"context" "context"
"log"
"os"
"time"
"github.com/faiface/beep" "github.com/faiface/beep"
"github.com/faiface/beep/speaker" "github.com/faiface/beep/speaker"
"github.com/faiface/beep/wav" "github.com/faiface/beep/wav"
@@ -11,9 +15,6 @@ import (
"github.com/hectorgimenez/d2go/pkg/itemfilter" "github.com/hectorgimenez/d2go/pkg/itemfilter"
"github.com/hectorgimenez/d2go/pkg/memory" "github.com/hectorgimenez/d2go/pkg/memory"
"github.com/hectorgimenez/d2go/pkg/nip" "github.com/hectorgimenez/d2go/pkg/nip"
"log"
"os"
"time"
) )
type Watcher struct { type Watcher struct {
@@ -53,7 +54,7 @@ func (w *Watcher) Start(ctx context.Context) error {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
d := w.gr.GetData() d := w.gr.GetData()
for _, i := range d.Items.Ground { for _, i := range d.Items.ByLocation(item.LocationGround) {
if !itemfilter.Evaluate(i, w.rules) { if !itemfilter.Evaluate(i, w.rules) {
continue continue
} }
@@ -85,7 +86,7 @@ func (w *Watcher) Start(ctx context.Context) error {
purgedNotifiedItems := make([]itemFootprint, 0) purgedNotifiedItems := make([]itemFootprint, 0)
for _, t := range w.alreadyNotifiedItemIDs { for _, t := range w.alreadyNotifiedItemIDs {
found := false found := false
for _, it := range d.Items.Ground { for _, it := range d.Items.ByLocation(item.LocationGround) {
if t.Match(d.PlayerUnit.Area, it) { if t.Match(d.PlayerUnit.Area, it) {
found = true found = true
} }

View File

@@ -11,6 +11,23 @@ func (a Area) IsTown() bool {
return false return false
} }
func (a Area) Act() int {
if a < 40 {
return 1
}
if a >= 40 && a < 75 {
return 2
}
if a >= 75 && a < 103 {
return 3
}
if a >= 103 && a < 109 {
return 4
}
return 5
}
const ( const (
Abaddon Area = 125 Abaddon Area = 125
AncientTunnels Area = 65 AncientTunnels Area = 65

View File

@@ -40,6 +40,7 @@ type Data struct {
Rooms []Room Rooms []Room
OpenMenus OpenMenus OpenMenus OpenMenus
Roster Roster Roster Roster
HoverData HoverData
} }
type Room struct { type Room struct {
@@ -48,6 +49,12 @@ type Room struct {
Height int Height int
} }
type HoverData struct {
IsHovered bool
UnitID
UnitType int
}
func (r Room) GetCenter() Position { func (r Room) GetCenter() Position {
return Position{ return Position{
X: r.Position.X + r.Width/2, X: r.Position.X + r.Width/2,
@@ -98,10 +105,9 @@ func (r Roster) FindByName(name string) (RosterMember, bool) {
} }
type Level struct { type Level struct {
Area area.Area Area area.Area
Position Position Position Position
IsGoodExit bool IsEntrance bool // This means the area can not be accessed just walking through it, needs to be clicked
CanInteract bool
} }
type Class uint type Class uint
@@ -129,6 +135,7 @@ type Position struct {
type PlayerUnit struct { type PlayerUnit struct {
Name string Name string
ID UnitID
Area area.Area Area area.Area
Position Position Position Position
Stats map[stat.ID]int Stats map[stat.ID]int
@@ -226,4 +233,5 @@ type OpenMenus struct {
QuitMenu bool QuitMenu bool
Cube bool Cube bool
SkillSelect bool SkillSelect bool
Anvil bool
} }

18
pkg/data/item/location.go Normal file
View File

@@ -0,0 +1,18 @@
package item
type Location string
const (
LocationEquipped Location = "equipped"
LocationStash Location = "stash"
LocationSharedStash1 Location = "shared_stash_1"
LocationSharedStash2 Location = "shared_stash_2"
LocationSharedStash3 Location = "shared_stash_3"
LocationBelt Location = "belt"
LocationInventory Location = "inventory"
LocationCube Location = "cube"
LocationVendor Location = "vendor"
LocationGround Location = "ground"
LocationSocket Location = "socket"
LocationUnknown Location = "unknown"
)

View File

@@ -41,6 +41,26 @@ var typeMapping = map[string][]string{
"voodooheads": {"PreservedHead", "ZombieHead", "UnravellerHead", "GargoyleHead", "DemonHeadShield", "MummifiedTrophy", "FetishTrophy", "SextonTrophy", "CantorTrophy", "HierophantTrophy", "MinionSkull", "HellspawnSkull", "OverseerSkull", "SuccubusSkull", "BloodlordSkull"}, "voodooheads": {"PreservedHead", "ZombieHead", "UnravellerHead", "GargoyleHead", "DemonHeadShield", "MummifiedTrophy", "FetishTrophy", "SextonTrophy", "CantorTrophy", "HierophantTrophy", "MinionSkull", "HellspawnSkull", "OverseerSkull", "SuccubusSkull", "BloodlordSkull"},
} }
var QuestItems = []string{
"TheGidbinn",
"WirtsLeg",
"HoradricMalus",
"HellforgeHammer",
"HoradricStaff",
"StaffOfKings",
"KhalimsFlail",
"AmuletOfTheViper",
"KhalimsEye",
"KhalimsHeart",
"KhalimsBrain",
"KhalimsWill",
"ScrollOfInifuss",
"HoradricCube",
"HoradricScroll",
"MephistosSoulstone",
"BookOfSkill",
}
func TypeForItemName(itemName string) (string, bool) { func TypeForItemName(itemName string) (string, bool) {
for t, itemNames := range typeMapping { for t, itemNames := range typeMapping {
for _, name := range itemNames { for _, name := range itemNames {

View File

@@ -8,14 +8,43 @@ import (
) )
type Items struct { type Items struct {
Belt Belt Belt Belt
Inventory Inventory AllItems []Item
Shop []Item }
Ground []Item
Equipped []Item func (i Items) Find(name item.Name, locations ...item.Location) (Item, bool) {
for _, it := range i.AllItems {
if strings.EqualFold(string(it.Name), string(name)) {
// If no locations are specified, return the first item found
if len(locations) == 0 {
return it, true
}
for _, l := range locations {
if it.Location == l {
return it, true
}
}
}
}
return Item{}, false
}
func (i Items) ByLocation(locations ...item.Location) []Item {
var items []Item
for _, it := range i.AllItems {
for _, l := range locations {
if it.Location == l {
items = append(items, it)
}
}
}
return items
} }
type Inventory []Item
type UnitID int type UnitID int
type Item struct { type Item struct {
@@ -23,11 +52,11 @@ type Item struct {
Name item.Name Name item.Name
Quality item.Quality Quality item.Quality
Position Position Position Position
Location item.Location
Ethereal bool Ethereal bool
IsHovered bool IsHovered bool
Stats map[stat.ID]stat.Data Stats map[stat.ID]stat.Data
Identified bool Identified bool
IsVendor bool
} }
func (i Item) Type() string { func (i Item) Type() string {
@@ -47,6 +76,17 @@ func (i Item) IsHealingPotion() bool {
func (i Item) IsManaPotion() bool { func (i Item) IsManaPotion() bool {
return strings.Contains(string(i.Name), string(ManaPotion)) return strings.Contains(string(i.Name), string(ManaPotion))
} }
func (i Item) IsRejuvPotion() bool { func (i Item) IsRejuvPotion() bool {
return strings.Contains(string(i.Name), string(RejuvenationPotion)) return strings.Contains(string(i.Name), string(RejuvenationPotion))
} }
func (i Item) IsFromQuest() bool {
for _, q := range item.QuestItems {
if strings.EqualFold(string(i.Name), q) {
return true
}
}
return false
}

View File

@@ -50,7 +50,7 @@ func (m Monsters) FindOne(id npc.ID, t MonsterType) (Monster, bool) {
func (m Monsters) Enemies(filters ...MonsterFilter) []Monster { func (m Monsters) Enemies(filters ...MonsterFilter) []Monster {
monsters := make([]Monster, 0) monsters := make([]Monster, 0)
for _, mo := range m { for _, mo := range m {
if !mo.IsMerc() && !mo.IsGoodNPC() { if !mo.IsMerc() && !mo.IsGoodNPC() && mo.Stats[stat.Life] > 0 {
monsters = append(monsters, mo) monsters = append(monsters, mo)
} }
} }

View File

@@ -29,6 +29,16 @@ func (o Object) IsWaypoint() bool {
object.Act2Waypoint, object.Act2Waypoint,
object.Act3TownWaypoint, object.Act3TownWaypoint,
object.PandamoniumFortressWaypoint, object.PandamoniumFortressWaypoint,
object.Act2CellerWaypoint,
object.Act2SewerWaypoint,
object.Act3TravincalWaypoint,
object.ValleyWaypoint,
object.WorldstoneWaypoint,
object.ExpansionWildernessWaypoint,
object.IceCaveWaypoint,
object.TempleWaypoint,
object.InnerHellWaypoint,
object.WaypointH,
object.ExpansionWaypoint: object.ExpansionWaypoint:
return true return true
} }
@@ -85,7 +95,8 @@ func (o Object) IsDoor() bool {
object.AndarielDoor, object.AndarielDoor,
object.PenBreakableDoor, object.PenBreakableDoor,
object.ArreatSummitDoorToWorldstone, object.ArreatSummitDoorToWorldstone,
object.SecretDoor1: object.SecretDoor1,
object.ExpansionTownGate:
return true return true
} }

View File

@@ -28,15 +28,17 @@ func (gd *GameReader) GetData() data.Data {
playerUnitPtr, corpse := gd.GetPlayerUnitPtr(roster) playerUnitPtr, corpse := gd.GetPlayerUnitPtr(roster)
pu := gd.GetPlayerUnit(playerUnitPtr) pu := gd.GetPlayerUnit(playerUnitPtr)
hover := gd.hoveredData()
d := data.Data{ d := data.Data{
Corpse: corpse, Corpse: corpse,
Monsters: gd.Monsters(pu.Position), Monsters: gd.Monsters(pu.Position, hover),
PlayerUnit: pu, PlayerUnit: pu,
Items: gd.Items(pu.Position), Items: gd.Items(pu, hover),
Objects: gd.Objects(pu.Position), Objects: gd.Objects(pu.Position, hover),
OpenMenus: gd.openMenus(), OpenMenus: gd.openMenus(),
Roster: roster, Roster: roster,
HoverData: hover,
} }
if playerUnitPtr == 0 { if playerUnitPtr == 0 {
@@ -74,21 +76,26 @@ func (gd *GameReader) openMenus() data.OpenMenus {
QuitMenu: buffer[0x09] != 0, QuitMenu: buffer[0x09] != 0,
Cube: buffer[0x19] != 0, Cube: buffer[0x19] != 0,
SkillSelect: buffer[0x03] != 0, SkillSelect: buffer[0x03] != 0,
Anvil: buffer[0x0D] != 0,
} }
} }
func (gd *GameReader) hoveredData() (hoveredUnitID uint, hoveredType uint, isHovered bool) { func (gd *GameReader) hoveredData() data.HoverData {
hoverAddressPtr := gd.Process.moduleBaseAddressPtr + gd.offset.Hover hoverAddressPtr := gd.Process.moduleBaseAddressPtr + gd.offset.Hover
hoverBuffer := gd.Process.ReadBytesFromMemory(hoverAddressPtr, 12) hoverBuffer := gd.Process.ReadBytesFromMemory(hoverAddressPtr, 12)
isUnitHovered := ReadUIntFromBuffer(hoverBuffer, 0, Uint16) isUnitHovered := ReadUIntFromBuffer(hoverBuffer, 0, Uint16)
if isUnitHovered > 0 { if isUnitHovered > 0 {
hoveredType = ReadUIntFromBuffer(hoverBuffer, 0x04, Uint32) hoveredType := ReadUIntFromBuffer(hoverBuffer, 0x04, Uint32)
hoveredUnitID = ReadUIntFromBuffer(hoverBuffer, 0x08, Uint32) hoveredUnitID := ReadUIntFromBuffer(hoverBuffer, 0x08, Uint32)
return hoveredUnitID, hoveredType, true return data.HoverData{
IsHovered: true,
UnitID: data.UnitID(hoveredUnitID),
UnitType: int(hoveredType),
}
} }
return 0, 0, false return data.HoverData{}
} }
func (gd *GameReader) getStatsData(statCount uint, statPtr uintptr) []stat.Data { func (gd *GameReader) getStatsData(statCount uint, statPtr uintptr) []stat.Data {
@@ -124,15 +131,3 @@ func (gd *GameReader) getStatsData(statCount uint, statPtr uintptr) []stat.Data
return stats return stats
} }
func setProperties(item *data.Item, flags uint32) {
if 0x00400000&flags != 0 {
item.Ethereal = true
}
if 0x00000010&flags != 0 {
item.Identified = true
}
if 0x00002000&flags != 0 {
item.IsVendor = true
}
}

View File

@@ -9,9 +9,7 @@ import (
"github.com/hectorgimenez/d2go/pkg/utils" "github.com/hectorgimenez/d2go/pkg/utils"
) )
func (gd *GameReader) Items(playerPosition data.Position) data.Items { func (gd *GameReader) Items(pu data.PlayerUnit, hover data.HoverData) data.Items {
hoveredUnitID, hoveredType, isHovered := gd.hoveredData()
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (4 * 1024) baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (4 * 1024)
unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8) unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8)
@@ -38,7 +36,7 @@ func (gd *GameReader) Items(playerPosition data.Position) data.Items {
flags := ReadUIntFromBuffer(unitDataBuffer, 0x18, Uint32) flags := ReadUIntFromBuffer(unitDataBuffer, 0x18, Uint32)
invPage := ReadUIntFromBuffer(unitDataBuffer, 0x55, Uint8) invPage := ReadUIntFromBuffer(unitDataBuffer, 0x55, Uint8)
itemQuality := ReadUIntFromBuffer(unitDataBuffer, 0x00, Uint32) itemQuality := ReadUIntFromBuffer(unitDataBuffer, 0x00, Uint32)
//itemOwnerNPC := ReadUIntFromBuffer(unitDataBuffer, 0x0C, Uint32) itemOwnerNPC := ReadUIntFromBuffer(unitDataBuffer, 0x0C, Uint32)
// Item coordinates (X, Y) // Item coordinates (X, Y)
pathPtr := uintptr(ReadUIntFromBuffer(itemDataBuffer, 0x38, Uint64)) pathPtr := uintptr(ReadUIntFromBuffer(itemDataBuffer, 0x38, Uint64))
@@ -58,7 +56,7 @@ func (gd *GameReader) Items(playerPosition data.Position) data.Items {
name := item.GetNameByEnum(txtFileNo) name := item.GetNameByEnum(txtFileNo)
itemHovered := false itemHovered := false
if isHovered && hoveredType == 4 && hoveredUnitID == unitID { if hover.IsHovered && hover.UnitType == 4 && hover.UnitID == data.UnitID(unitID) {
itemHovered = true itemHovered = true
} }
@@ -75,22 +73,53 @@ func (gd *GameReader) Items(playerPosition data.Position) data.Items {
} }
setProperties(&itm, uint32(flags)) setProperties(&itm, uint32(flags))
location := item.LocationUnknown
switch itemLoc { switch itemLoc {
case 0: case 0:
if itm.IsVendor { if 0x00002000&flags != 0 {
items.Shop = append(items.Shop, itm) location = item.LocationVendor
break
} else if invPage == 0 { } else if invPage == 0 {
items.Inventory = append(items.Inventory, itm) location = item.LocationInventory
break
}
if data.UnitID(itemOwnerNPC) == pu.ID || itemOwnerNPC == 1 {
location = item.LocationStash
break
}
// Offline only
if itemOwnerNPC == 2 {
location = item.LocationSharedStash1
break
}
if itemOwnerNPC == 3 {
location = item.LocationSharedStash2
break
}
if itemOwnerNPC == 4 {
location = item.LocationSharedStash3
break
} }
case 1: case 1:
items.Equipped = append(items.Equipped, itm) location = item.LocationEquipped
if itm.Type() == "belt" { if itm.Type() == "belt" {
belt.Name = itm.Name belt.Name = itm.Name
} }
case 2: case 2:
belt.Items = append(belt.Items, itm) location = item.LocationBelt
case 3, 5: case 3, 5:
items.Ground = append(items.Ground, itm) location = item.LocationGround
case 6:
location = item.LocationSocket
}
itm.Location = location
if location == item.LocationBelt {
belt.Items = append(belt.Items, itm)
} else {
items.AllItems = append(items.AllItems, itm)
} }
itemUnitPtr = uintptr(gd.Process.ReadUInt(itemUnitPtr+0x150, Uint64)) itemUnitPtr = uintptr(gd.Process.ReadUInt(itemUnitPtr+0x150, Uint64))
@@ -99,9 +128,9 @@ func (gd *GameReader) Items(playerPosition data.Position) data.Items {
items.Belt = belt items.Belt = belt
sort.SliceStable(items.Ground, func(i, j int) bool { sort.SliceStable(items.AllItems, func(i, j int) bool {
distanceI := utils.DistanceFromPoint(playerPosition, items.Ground[i].Position) distanceI := utils.DistanceFromPoint(pu.Position, items.AllItems[i].Position)
distanceJ := utils.DistanceFromPoint(playerPosition, items.Ground[j].Position) distanceJ := utils.DistanceFromPoint(pu.Position, items.AllItems[j].Position)
return distanceI < distanceJ return distanceI < distanceJ
}) })
@@ -127,3 +156,12 @@ func (gd *GameReader) getItemStats(statCount uint, statPtr uintptr, statExCount
return stats return stats
} }
func setProperties(item *data.Item, flags uint32) {
if 0x00400000&flags != 0 {
item.Ethereal = true
}
if 0x00000010&flags != 0 {
item.Identified = true
}
}

View File

@@ -1,16 +1,15 @@
package memory package memory
import ( import (
"sort"
"github.com/hectorgimenez/d2go/pkg/data" "github.com/hectorgimenez/d2go/pkg/data"
"github.com/hectorgimenez/d2go/pkg/data/npc" "github.com/hectorgimenez/d2go/pkg/data/npc"
"github.com/hectorgimenez/d2go/pkg/data/stat" "github.com/hectorgimenez/d2go/pkg/data/stat"
"github.com/hectorgimenez/d2go/pkg/utils" "github.com/hectorgimenez/d2go/pkg/utils"
"sort"
) )
func (gd *GameReader) Monsters(playerPosition data.Position) data.Monsters { func (gd *GameReader) Monsters(playerPosition data.Position, hover data.HoverData) data.Monsters {
hoveredUnitID, hoveredType, isHovered := gd.hoveredData()
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + 1024 baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + 1024
unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8) unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8)
@@ -41,7 +40,7 @@ func (gd *GameReader) Monsters(playerPosition data.Position) data.Monsters {
posY := gd.Process.ReadUInt(pathPtr+0x06, Uint16) posY := gd.Process.ReadUInt(pathPtr+0x06, Uint16)
hovered := false hovered := false
if isHovered && hoveredType == 1 && hoveredUnitID == unitID { if hover.IsHovered && hover.UnitType == 1 && hover.UnitID == data.UnitID(unitID) {
hovered = true hovered = true
} }

View File

@@ -1,15 +1,14 @@
package memory package memory
import ( import (
"sort"
"github.com/hectorgimenez/d2go/pkg/data" "github.com/hectorgimenez/d2go/pkg/data"
"github.com/hectorgimenez/d2go/pkg/data/object" "github.com/hectorgimenez/d2go/pkg/data/object"
"github.com/hectorgimenez/d2go/pkg/utils" "github.com/hectorgimenez/d2go/pkg/utils"
"sort"
) )
func (gd *GameReader) Objects(playerPosition data.Position) []data.Object { func (gd *GameReader) Objects(playerPosition data.Position, hover data.HoverData) []data.Object {
hoveredUnitID, hoveredType, isHovered := gd.hoveredData()
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (2 * 1024) baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (2 * 1024)
unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8) unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8)
@@ -34,7 +33,7 @@ func (gd *GameReader) Objects(playerPosition data.Position) []data.Object {
obj := data.Object{ obj := data.Object{
Name: object.Name(int(txtFileNo)), Name: object.Name(int(txtFileNo)),
IsHovered: unitID == hoveredUnitID && hoveredType == 2 && isHovered, IsHovered: data.UnitID(unitID) == hover.UnitID && hover.UnitType == 2 && hover.IsHovered,
InteractType: object.InteractType(interactType), InteractType: object.InteractType(interactType),
Selectable: mode == 0, Selectable: mode == 0,
Position: data.Position{ Position: data.Position{

View File

@@ -34,10 +34,10 @@ func (gd *GameReader) GetPlayerUnitPtr(roster data.Roster) (playerUnitPtr uintpt
isCorpse := gd.Process.ReadUInt(playerUnit+0x1A6, Uint8) isCorpse := gd.Process.ReadUInt(playerUnit+0x1A6, Uint8)
if isCorpse == 1 { if isCorpse == 1 {
unitID := gd.Process.ReadUInt(playerUnit+0x08, Uint32) unitID := gd.Process.ReadUInt(playerUnit+0x08, Uint32)
hoveredUnitID, hoveredType, isHovered := gd.hoveredData() hover := gd.hoveredData()
corpse = data.Corpse{ corpse = data.Corpse{
Found: true, Found: true,
IsHovered: isHovered && hoveredUnitID == unitID && hoveredType == 0, IsHovered: hover.IsHovered && hover.UnitID == data.UnitID(unitID) && hover.UnitType == 0,
Position: data.Position{ Position: data.Position{
X: int(xPos), X: int(xPos),
Y: int(yPos), Y: int(yPos),
@@ -76,6 +76,8 @@ func (gd *GameReader) GetPlayerUnitPtr(roster data.Roster) (playerUnitPtr uintpt
} }
func (gd *GameReader) GetPlayerUnit(playerUnit uintptr) data.PlayerUnit { func (gd *GameReader) GetPlayerUnit(playerUnit uintptr) data.PlayerUnit {
unitID := gd.Process.ReadUInt(playerUnit+0x08, Uint32)
// Read X and Y Positions // Read X and Y Positions
pPath := playerUnit + 0x38 pPath := playerUnit + 0x38
pathAddress := uintptr(gd.Process.ReadUInt(pPath, Uint64)) pathAddress := uintptr(gd.Process.ReadUInt(pPath, Uint64))
@@ -127,6 +129,7 @@ func (gd *GameReader) GetPlayerUnit(playerUnit uintptr) data.PlayerUnit {
return data.PlayerUnit{ return data.PlayerUnit{
Name: name, Name: name,
ID: data.UnitID(unitID),
Area: area.Area(levelNo), Area: area.Area(levelNo),
Position: data.Position{ Position: data.Position{
X: int(xPos), X: int(xPos),