Item refactor and more (#1)
This commit is contained in:
@@ -2,6 +2,10 @@ package itemwatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/faiface/beep"
|
||||
"github.com/faiface/beep/speaker"
|
||||
"github.com/faiface/beep/wav"
|
||||
@@ -11,9 +15,6 @@ import (
|
||||
"github.com/hectorgimenez/d2go/pkg/itemfilter"
|
||||
"github.com/hectorgimenez/d2go/pkg/memory"
|
||||
"github.com/hectorgimenez/d2go/pkg/nip"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Watcher struct {
|
||||
@@ -53,7 +54,7 @@ func (w *Watcher) Start(ctx context.Context) error {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
d := w.gr.GetData()
|
||||
for _, i := range d.Items.Ground {
|
||||
for _, i := range d.Items.ByLocation(item.LocationGround) {
|
||||
if !itemfilter.Evaluate(i, w.rules) {
|
||||
continue
|
||||
}
|
||||
@@ -85,7 +86,7 @@ func (w *Watcher) Start(ctx context.Context) error {
|
||||
purgedNotifiedItems := make([]itemFootprint, 0)
|
||||
for _, t := range w.alreadyNotifiedItemIDs {
|
||||
found := false
|
||||
for _, it := range d.Items.Ground {
|
||||
for _, it := range d.Items.ByLocation(item.LocationGround) {
|
||||
if t.Match(d.PlayerUnit.Area, it) {
|
||||
found = true
|
||||
}
|
||||
|
||||
@@ -11,6 +11,23 @@ func (a Area) IsTown() bool {
|
||||
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 (
|
||||
Abaddon Area = 125
|
||||
AncientTunnels Area = 65
|
||||
|
||||
@@ -40,6 +40,7 @@ type Data struct {
|
||||
Rooms []Room
|
||||
OpenMenus OpenMenus
|
||||
Roster Roster
|
||||
HoverData HoverData
|
||||
}
|
||||
|
||||
type Room struct {
|
||||
@@ -48,6 +49,12 @@ type Room struct {
|
||||
Height int
|
||||
}
|
||||
|
||||
type HoverData struct {
|
||||
IsHovered bool
|
||||
UnitID
|
||||
UnitType int
|
||||
}
|
||||
|
||||
func (r Room) GetCenter() Position {
|
||||
return Position{
|
||||
X: r.Position.X + r.Width/2,
|
||||
@@ -100,8 +107,7 @@ func (r Roster) FindByName(name string) (RosterMember, bool) {
|
||||
type Level struct {
|
||||
Area area.Area
|
||||
Position Position
|
||||
IsGoodExit bool
|
||||
CanInteract bool
|
||||
IsEntrance bool // This means the area can not be accessed just walking through it, needs to be clicked
|
||||
}
|
||||
|
||||
type Class uint
|
||||
@@ -129,6 +135,7 @@ type Position struct {
|
||||
|
||||
type PlayerUnit struct {
|
||||
Name string
|
||||
ID UnitID
|
||||
Area area.Area
|
||||
Position Position
|
||||
Stats map[stat.ID]int
|
||||
@@ -226,4 +233,5 @@ type OpenMenus struct {
|
||||
QuitMenu bool
|
||||
Cube bool
|
||||
SkillSelect bool
|
||||
Anvil bool
|
||||
}
|
||||
|
||||
18
pkg/data/item/location.go
Normal file
18
pkg/data/item/location.go
Normal 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"
|
||||
)
|
||||
@@ -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"},
|
||||
}
|
||||
|
||||
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) {
|
||||
for t, itemNames := range typeMapping {
|
||||
for _, name := range itemNames {
|
||||
|
||||
@@ -9,13 +9,42 @@ import (
|
||||
|
||||
type Items struct {
|
||||
Belt Belt
|
||||
Inventory Inventory
|
||||
Shop []Item
|
||||
Ground []Item
|
||||
Equipped []Item
|
||||
AllItems []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 Item struct {
|
||||
@@ -23,11 +52,11 @@ type Item struct {
|
||||
Name item.Name
|
||||
Quality item.Quality
|
||||
Position Position
|
||||
Location item.Location
|
||||
Ethereal bool
|
||||
IsHovered bool
|
||||
Stats map[stat.ID]stat.Data
|
||||
Identified bool
|
||||
IsVendor bool
|
||||
}
|
||||
|
||||
func (i Item) Type() string {
|
||||
@@ -47,6 +76,17 @@ func (i Item) IsHealingPotion() bool {
|
||||
func (i Item) IsManaPotion() bool {
|
||||
return strings.Contains(string(i.Name), string(ManaPotion))
|
||||
}
|
||||
|
||||
func (i Item) IsRejuvPotion() bool {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func (m Monsters) FindOne(id npc.ID, t MonsterType) (Monster, bool) {
|
||||
func (m Monsters) Enemies(filters ...MonsterFilter) []Monster {
|
||||
monsters := make([]Monster, 0)
|
||||
for _, mo := range m {
|
||||
if !mo.IsMerc() && !mo.IsGoodNPC() {
|
||||
if !mo.IsMerc() && !mo.IsGoodNPC() && mo.Stats[stat.Life] > 0 {
|
||||
monsters = append(monsters, mo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,16 @@ func (o Object) IsWaypoint() bool {
|
||||
object.Act2Waypoint,
|
||||
object.Act3TownWaypoint,
|
||||
object.PandamoniumFortressWaypoint,
|
||||
object.Act2CellerWaypoint,
|
||||
object.Act2SewerWaypoint,
|
||||
object.Act3TravincalWaypoint,
|
||||
object.ValleyWaypoint,
|
||||
object.WorldstoneWaypoint,
|
||||
object.ExpansionWildernessWaypoint,
|
||||
object.IceCaveWaypoint,
|
||||
object.TempleWaypoint,
|
||||
object.InnerHellWaypoint,
|
||||
object.WaypointH,
|
||||
object.ExpansionWaypoint:
|
||||
return true
|
||||
}
|
||||
@@ -85,7 +95,8 @@ func (o Object) IsDoor() bool {
|
||||
object.AndarielDoor,
|
||||
object.PenBreakableDoor,
|
||||
object.ArreatSummitDoorToWorldstone,
|
||||
object.SecretDoor1:
|
||||
object.SecretDoor1,
|
||||
object.ExpansionTownGate:
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -28,15 +28,17 @@ func (gd *GameReader) GetData() data.Data {
|
||||
playerUnitPtr, corpse := gd.GetPlayerUnitPtr(roster)
|
||||
|
||||
pu := gd.GetPlayerUnit(playerUnitPtr)
|
||||
hover := gd.hoveredData()
|
||||
|
||||
d := data.Data{
|
||||
Corpse: corpse,
|
||||
Monsters: gd.Monsters(pu.Position),
|
||||
Monsters: gd.Monsters(pu.Position, hover),
|
||||
PlayerUnit: pu,
|
||||
Items: gd.Items(pu.Position),
|
||||
Objects: gd.Objects(pu.Position),
|
||||
Items: gd.Items(pu, hover),
|
||||
Objects: gd.Objects(pu.Position, hover),
|
||||
OpenMenus: gd.openMenus(),
|
||||
Roster: roster,
|
||||
HoverData: hover,
|
||||
}
|
||||
|
||||
if playerUnitPtr == 0 {
|
||||
@@ -74,21 +76,26 @@ func (gd *GameReader) openMenus() data.OpenMenus {
|
||||
QuitMenu: buffer[0x09] != 0,
|
||||
Cube: buffer[0x19] != 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
|
||||
hoverBuffer := gd.Process.ReadBytesFromMemory(hoverAddressPtr, 12)
|
||||
isUnitHovered := ReadUIntFromBuffer(hoverBuffer, 0, Uint16)
|
||||
if isUnitHovered > 0 {
|
||||
hoveredType = ReadUIntFromBuffer(hoverBuffer, 0x04, Uint32)
|
||||
hoveredUnitID = ReadUIntFromBuffer(hoverBuffer, 0x08, Uint32)
|
||||
hoveredType := ReadUIntFromBuffer(hoverBuffer, 0x04, 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 {
|
||||
@@ -124,15 +131,3 @@ func (gd *GameReader) getStatsData(statCount uint, statPtr uintptr) []stat.Data
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@ import (
|
||||
"github.com/hectorgimenez/d2go/pkg/utils"
|
||||
)
|
||||
|
||||
func (gd *GameReader) Items(playerPosition data.Position) data.Items {
|
||||
hoveredUnitID, hoveredType, isHovered := gd.hoveredData()
|
||||
|
||||
func (gd *GameReader) Items(pu data.PlayerUnit, hover data.HoverData) data.Items {
|
||||
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (4 * 1024)
|
||||
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)
|
||||
invPage := ReadUIntFromBuffer(unitDataBuffer, 0x55, Uint8)
|
||||
itemQuality := ReadUIntFromBuffer(unitDataBuffer, 0x00, Uint32)
|
||||
//itemOwnerNPC := ReadUIntFromBuffer(unitDataBuffer, 0x0C, Uint32)
|
||||
itemOwnerNPC := ReadUIntFromBuffer(unitDataBuffer, 0x0C, Uint32)
|
||||
|
||||
// Item coordinates (X, Y)
|
||||
pathPtr := uintptr(ReadUIntFromBuffer(itemDataBuffer, 0x38, Uint64))
|
||||
@@ -58,7 +56,7 @@ func (gd *GameReader) Items(playerPosition data.Position) data.Items {
|
||||
|
||||
name := item.GetNameByEnum(txtFileNo)
|
||||
itemHovered := false
|
||||
if isHovered && hoveredType == 4 && hoveredUnitID == unitID {
|
||||
if hover.IsHovered && hover.UnitType == 4 && hover.UnitID == data.UnitID(unitID) {
|
||||
itemHovered = true
|
||||
}
|
||||
|
||||
@@ -75,22 +73,53 @@ func (gd *GameReader) Items(playerPosition data.Position) data.Items {
|
||||
}
|
||||
setProperties(&itm, uint32(flags))
|
||||
|
||||
location := item.LocationUnknown
|
||||
switch itemLoc {
|
||||
case 0:
|
||||
if itm.IsVendor {
|
||||
items.Shop = append(items.Shop, itm)
|
||||
if 0x00002000&flags != 0 {
|
||||
location = item.LocationVendor
|
||||
break
|
||||
} 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:
|
||||
items.Equipped = append(items.Equipped, itm)
|
||||
location = item.LocationEquipped
|
||||
if itm.Type() == "belt" {
|
||||
belt.Name = itm.Name
|
||||
}
|
||||
case 2:
|
||||
belt.Items = append(belt.Items, itm)
|
||||
location = item.LocationBelt
|
||||
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))
|
||||
@@ -99,9 +128,9 @@ func (gd *GameReader) Items(playerPosition data.Position) data.Items {
|
||||
|
||||
items.Belt = belt
|
||||
|
||||
sort.SliceStable(items.Ground, func(i, j int) bool {
|
||||
distanceI := utils.DistanceFromPoint(playerPosition, items.Ground[i].Position)
|
||||
distanceJ := utils.DistanceFromPoint(playerPosition, items.Ground[j].Position)
|
||||
sort.SliceStable(items.AllItems, func(i, j int) bool {
|
||||
distanceI := utils.DistanceFromPoint(pu.Position, items.AllItems[i].Position)
|
||||
distanceJ := utils.DistanceFromPoint(pu.Position, items.AllItems[j].Position)
|
||||
|
||||
return distanceI < distanceJ
|
||||
})
|
||||
@@ -127,3 +156,12 @@ func (gd *GameReader) getItemStats(statCount uint, statPtr uintptr, statExCount
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
func setProperties(item *data.Item, flags uint32) {
|
||||
if 0x00400000&flags != 0 {
|
||||
item.Ethereal = true
|
||||
}
|
||||
if 0x00000010&flags != 0 {
|
||||
item.Identified = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/hectorgimenez/d2go/pkg/data"
|
||||
"github.com/hectorgimenez/d2go/pkg/data/npc"
|
||||
"github.com/hectorgimenez/d2go/pkg/data/stat"
|
||||
"github.com/hectorgimenez/d2go/pkg/utils"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func (gd *GameReader) Monsters(playerPosition data.Position) data.Monsters {
|
||||
hoveredUnitID, hoveredType, isHovered := gd.hoveredData()
|
||||
|
||||
func (gd *GameReader) Monsters(playerPosition data.Position, hover data.HoverData) data.Monsters {
|
||||
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + 1024
|
||||
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)
|
||||
|
||||
hovered := false
|
||||
if isHovered && hoveredType == 1 && hoveredUnitID == unitID {
|
||||
if hover.IsHovered && hover.UnitType == 1 && hover.UnitID == data.UnitID(unitID) {
|
||||
hovered = true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/hectorgimenez/d2go/pkg/data"
|
||||
"github.com/hectorgimenez/d2go/pkg/data/object"
|
||||
"github.com/hectorgimenez/d2go/pkg/utils"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func (gd *GameReader) Objects(playerPosition data.Position) []data.Object {
|
||||
hoveredUnitID, hoveredType, isHovered := gd.hoveredData()
|
||||
|
||||
func (gd *GameReader) Objects(playerPosition data.Position, hover data.HoverData) []data.Object {
|
||||
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (2 * 1024)
|
||||
unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8)
|
||||
|
||||
@@ -34,7 +33,7 @@ func (gd *GameReader) Objects(playerPosition data.Position) []data.Object {
|
||||
|
||||
obj := data.Object{
|
||||
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),
|
||||
Selectable: mode == 0,
|
||||
Position: data.Position{
|
||||
|
||||
@@ -34,10 +34,10 @@ func (gd *GameReader) GetPlayerUnitPtr(roster data.Roster) (playerUnitPtr uintpt
|
||||
isCorpse := gd.Process.ReadUInt(playerUnit+0x1A6, Uint8)
|
||||
if isCorpse == 1 {
|
||||
unitID := gd.Process.ReadUInt(playerUnit+0x08, Uint32)
|
||||
hoveredUnitID, hoveredType, isHovered := gd.hoveredData()
|
||||
hover := gd.hoveredData()
|
||||
corpse = data.Corpse{
|
||||
Found: true,
|
||||
IsHovered: isHovered && hoveredUnitID == unitID && hoveredType == 0,
|
||||
IsHovered: hover.IsHovered && hover.UnitID == data.UnitID(unitID) && hover.UnitType == 0,
|
||||
Position: data.Position{
|
||||
X: int(xPos),
|
||||
Y: int(yPos),
|
||||
@@ -76,6 +76,8 @@ func (gd *GameReader) GetPlayerUnitPtr(roster data.Roster) (playerUnitPtr uintpt
|
||||
}
|
||||
|
||||
func (gd *GameReader) GetPlayerUnit(playerUnit uintptr) data.PlayerUnit {
|
||||
unitID := gd.Process.ReadUInt(playerUnit+0x08, Uint32)
|
||||
|
||||
// Read X and Y Positions
|
||||
pPath := playerUnit + 0x38
|
||||
pathAddress := uintptr(gd.Process.ReadUInt(pPath, Uint64))
|
||||
@@ -127,6 +129,7 @@ func (gd *GameReader) GetPlayerUnit(playerUnit uintptr) data.PlayerUnit {
|
||||
|
||||
return data.PlayerUnit{
|
||||
Name: name,
|
||||
ID: data.UnitID(unitID),
|
||||
Area: area.Area(levelNo),
|
||||
Position: data.Position{
|
||||
X: int(xPos),
|
||||
|
||||
Reference in New Issue
Block a user