add new location types and enhance inventory structure for shared stash support and DLC support.
This commit is contained in:
@@ -10,18 +10,21 @@ type Location struct {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// Storage locations
|
// Storage locations
|
||||||
LocationUnknown LocationType = "unknown"
|
LocationUnknown LocationType = "unknown"
|
||||||
LocationInventory LocationType = "inventory"
|
LocationInventory LocationType = "inventory"
|
||||||
LocationStash LocationType = "stash"
|
LocationStash LocationType = "stash"
|
||||||
LocationSharedStash LocationType = "shared_stash"
|
LocationSharedStash LocationType = "shared_stash"
|
||||||
LocationBelt LocationType = "belt"
|
LocationBelt LocationType = "belt"
|
||||||
LocationCube LocationType = "cube"
|
LocationCube LocationType = "cube"
|
||||||
LocationVendor LocationType = "vendor"
|
LocationVendor LocationType = "vendor"
|
||||||
LocationGround LocationType = "ground"
|
LocationGround LocationType = "ground"
|
||||||
LocationSocket LocationType = "socket"
|
LocationSocket LocationType = "socket"
|
||||||
LocationCursor LocationType = "cursor"
|
LocationCursor LocationType = "cursor"
|
||||||
LocationEquipped LocationType = "equipped"
|
LocationEquipped LocationType = "equipped"
|
||||||
LocationMercenary LocationType = "mercenary"
|
LocationMercenary LocationType = "mercenary"
|
||||||
|
LocationGemsTab LocationType = "gems_tab"
|
||||||
|
LocationMaterialsTab LocationType = "materials_tab"
|
||||||
|
LocationRunesTab LocationType = "runes_tab"
|
||||||
|
|
||||||
// Body locations
|
// Body locations
|
||||||
LocNone LocationType = "none"
|
LocNone LocationType = "none"
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Inventory struct {
|
type Inventory struct {
|
||||||
Belt Belt
|
Belt Belt
|
||||||
AllItems []Item
|
AllItems []Item
|
||||||
Gold int
|
Gold int
|
||||||
StashedGold [4]int
|
StashedGold [6]int // [0]=personal, [1..5]=shared stash pages
|
||||||
|
SharedStashPages int // Number of shared stash units detected (3=non-DLC, 5+=DLC)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i Inventory) Find(name item.Name, locations ...item.LocationType) (Item, bool) {
|
func (i Inventory) Find(name item.Name, locations ...item.LocationType) (Item, bool) {
|
||||||
|
|||||||
@@ -19,9 +19,11 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD
|
|||||||
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)
|
||||||
|
|
||||||
// Process shared stash data
|
// Process shared stash data - build a UnitID→page mapping for ALL shared stash units.
|
||||||
stashPlayerUnits := make(map[uint]RawPlayerUnit)
|
// Post-patch D2R has 5 shared stash units (3 original shared tabs + DLC tabs).
|
||||||
stashPlayerUnitOrder := make([]uint, 0, 3) // Pre-allocate with expected size
|
stashUnitIDToPage := make(map[uint]uint) // UnitID → 1-based page number
|
||||||
|
stashPlayerUnitOrder := make([]uint, 0, 6) // order values, sorted
|
||||||
|
stashPlayerUnits := make(map[uint]RawPlayerUnit) // order → unit
|
||||||
for _, pu := range rawPlayerUnits {
|
for _, pu := range rawPlayerUnits {
|
||||||
if pu.States.HasState(state.Sharedstash) {
|
if pu.States.HasState(state.Sharedstash) {
|
||||||
order := gd.ReadUInt(pu.Address+0xD8, Uint64)
|
order := gd.ReadUInt(pu.Address+0xD8, Uint64)
|
||||||
@@ -30,15 +32,19 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
slices.Sort(stashPlayerUnitOrder)
|
slices.Sort(stashPlayerUnitOrder)
|
||||||
|
for i, orderKey := range stashPlayerUnitOrder {
|
||||||
|
uid := uint(stashPlayerUnits[orderKey].UnitID)
|
||||||
|
stashUnitIDToPage[uid] = uint(i + 1) // pages are 1-based
|
||||||
|
}
|
||||||
|
|
||||||
// Gold
|
// Gold
|
||||||
inventoryGold, _ := mainPlayer.BaseStats.FindStat(stat.Gold, 0)
|
inventoryGold, _ := mainPlayer.BaseStats.FindStat(stat.Gold, 0)
|
||||||
mainPlayerStashedGold, _ := mainPlayer.BaseStats.FindStat(stat.StashGold, 0)
|
mainPlayerStashedGold, _ := mainPlayer.BaseStats.FindStat(stat.StashGold, 0)
|
||||||
stashedGold := [4]int{mainPlayerStashedGold.Value, 0, 0, 0}
|
stashedGold := [6]int{mainPlayerStashedGold.Value, 0, 0, 0, 0, 0}
|
||||||
|
|
||||||
for i, puKey := range stashPlayerUnitOrder {
|
for i, puKey := range stashPlayerUnitOrder {
|
||||||
if i > 2 {
|
if i > 4 {
|
||||||
break
|
break // max 5 shared stash pages
|
||||||
}
|
}
|
||||||
if stashGold, found := stashPlayerUnits[puKey].BaseStats.FindStat(stat.StashGold, 0); found {
|
if stashGold, found := stashPlayerUnits[puKey].BaseStats.FindStat(stat.StashGold, 0); found {
|
||||||
stashedGold[i+1] = stashGold.Value
|
stashedGold[i+1] = stashGold.Value
|
||||||
@@ -46,8 +52,9 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD
|
|||||||
}
|
}
|
||||||
|
|
||||||
inventory := data.Inventory{
|
inventory := data.Inventory{
|
||||||
Gold: inventoryGold.Value,
|
Gold: inventoryGold.Value,
|
||||||
StashedGold: stashedGold,
|
StashedGold: stashedGold,
|
||||||
|
SharedStashPages: len(stashPlayerUnitOrder),
|
||||||
}
|
}
|
||||||
belt := data.Belt{}
|
belt := data.Belt{}
|
||||||
|
|
||||||
@@ -259,17 +266,28 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD
|
|||||||
location := item.LocationUnknown
|
location := item.LocationUnknown
|
||||||
switch itemLoc {
|
switch itemLoc {
|
||||||
case 0:
|
case 0:
|
||||||
if itemOwnerNPC == 2 || itemOwnerNPC == uint(stashPlayerUnits[stashPlayerUnitOrder[0]].UnitID) {
|
// Check shared stash ownership dynamically via UnitID map.
|
||||||
|
// Also keep legacy checks (ownerNPC 2/3/4) for older D2R versions.
|
||||||
|
if page, ok := stashUnitIDToPage[itemOwnerNPC]; ok {
|
||||||
|
location = item.LocationSharedStash
|
||||||
|
invPage = page
|
||||||
|
} else if itemOwnerNPC == 2 && len(stashUnitIDToPage) == 0 {
|
||||||
location = item.LocationSharedStash
|
location = item.LocationSharedStash
|
||||||
invPage = 1
|
invPage = 1
|
||||||
} else if itemOwnerNPC == 3 || itemOwnerNPC == uint(stashPlayerUnits[stashPlayerUnitOrder[1]].UnitID) {
|
} else if itemOwnerNPC == 3 && len(stashUnitIDToPage) == 0 {
|
||||||
location = item.LocationSharedStash
|
location = item.LocationSharedStash
|
||||||
invPage = 2
|
invPage = 2
|
||||||
} else if itemOwnerNPC == 4 || itemOwnerNPC == uint(stashPlayerUnits[stashPlayerUnitOrder[2]].UnitID) {
|
} else if itemOwnerNPC == 4 && len(stashUnitIDToPage) == 0 {
|
||||||
location = item.LocationSharedStash
|
location = item.LocationSharedStash
|
||||||
invPage = 3
|
invPage = 3
|
||||||
} else if 0x00002000&flags != 0 && itemOwnerNPC == 4294967295 {
|
} else if itemOwnerNPC == 4294967295 {
|
||||||
location = item.LocationVendor
|
if 0x00002000&flags != 0 {
|
||||||
|
location = item.LocationVendor
|
||||||
|
} else {
|
||||||
|
// DLC stash tab items: ownerNPC=0xFFFFFFFF, no vendor flag.
|
||||||
|
// Classify by item type: gems, runes, or materials.
|
||||||
|
location = classifyDLCTabItem(txtFileNo)
|
||||||
|
}
|
||||||
} else if data.UnitID(itemOwnerNPC) == mainPlayer.UnitID || itemOwnerNPC == 1 {
|
} else if data.UnitID(itemOwnerNPC) == mainPlayer.UnitID || itemOwnerNPC == 1 {
|
||||||
if invPage == 0 {
|
if invPage == 0 {
|
||||||
location = item.LocationInventory
|
location = item.LocationInventory
|
||||||
@@ -781,3 +799,49 @@ func calculateItemLevelReq(itm *data.Item, baseDesc item.Description) int {
|
|||||||
|
|
||||||
return maxReq
|
return maxReq
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gemTypes is the set of d2go item type codes that correspond to gems.
|
||||||
|
var gemTypes = map[string]bool{
|
||||||
|
item.TypeAmethyst: true,
|
||||||
|
item.TypeDiamond: true,
|
||||||
|
item.TypeEmerald: true,
|
||||||
|
item.TypeRuby: true,
|
||||||
|
item.TypeSapphire: true,
|
||||||
|
item.TypeTopaz: true,
|
||||||
|
item.TypeSkull: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// materialIDs is the set of txt file IDs that the DLC Materials tab auto-sorts.
|
||||||
|
// Keys (pk1-pk3), Organs (dhn, bey, mbr), Token (toa), Essences (tes, ceh, bet, fed).
|
||||||
|
var materialIDs = map[uint]bool{
|
||||||
|
662: true, // KeyOfTerror
|
||||||
|
663: true, // KeyOfHate
|
||||||
|
664: true, // KeyOfDestruction
|
||||||
|
665: true, // DiablosHorn
|
||||||
|
666: true, // BaalsEye
|
||||||
|
667: true, // MephistosBrain
|
||||||
|
668: true, // TokenOfAbsolution
|
||||||
|
669: true, // TwistedEssenceOfSuffering
|
||||||
|
670: true, // ChargedEssenceOfHatred
|
||||||
|
671: true, // BurningEssenceOfTerror
|
||||||
|
672: true, // FesteringEssenceOfDestruction
|
||||||
|
}
|
||||||
|
|
||||||
|
// classifyDLCTabItem determines which DLC stash tab an item belongs to
|
||||||
|
// based on its txt file number. Items with ownerNPC=0xFFFFFFFF and no
|
||||||
|
// vendor flag are DLC tab items; the game auto-sorts them by category.
|
||||||
|
func classifyDLCTabItem(txtFileNo uint) item.LocationType {
|
||||||
|
if desc, ok := item.Desc[int(txtFileNo)]; ok {
|
||||||
|
if desc.Type == item.TypeRune {
|
||||||
|
return item.LocationRunesTab
|
||||||
|
}
|
||||||
|
if gemTypes[desc.Type] {
|
||||||
|
return item.LocationGemsTab
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if materialIDs[txtFileNo] {
|
||||||
|
return item.LocationMaterialsTab
|
||||||
|
}
|
||||||
|
// Fallback: unknown DLC item — treat as materials tab
|
||||||
|
return item.LocationMaterialsTab
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ func (p *Process) Close() error {
|
|||||||
return windows.CloseHandle(p.handler)
|
return windows.CloseHandle(p.handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ModuleBaseAddress returns the base address of the D2R module.
|
||||||
|
func (p *Process) ModuleBaseAddress() uintptr {
|
||||||
|
return p.moduleBaseAddressPtr
|
||||||
|
}
|
||||||
|
|
||||||
func getGameModule() (ModuleInfo, error) {
|
func getGameModule() (ModuleInfo, error) {
|
||||||
processes := make([]uint32, 2048)
|
processes := make([]uint32, 2048)
|
||||||
length := uint32(0)
|
length := uint32(0)
|
||||||
|
|||||||
Reference in New Issue
Block a user