Prefix,Suffix,LevelReq Socketed items on bases, bodyloc, Merc equipment (#74)
* Prefix,Suffix, Socketed with base, bodyloc, Merc equipment Will add better description later: Item that has socketed items now display their linked runes/jewels/gems in accurate order Rare prefix, rare suffix id Magic prefix1,2,3 magic suffix1,2,3 Prefix1 also used to tell wich runeword it is Spirit 20635, cta 20519 , infinity 20566 see : getlocalestring.txt Mercenary equipment now displaying in inventory list . Also has custom location : mercenary instead of equipped Bodyloc : // Body locations LocNone LocationType = "none" LocHead LocationType = "head" LocNeck LocationType = "neck" LocTorso LocationType = "torso" LocLeftArm LocationType = "left_arm" LocRightArm LocationType = "right_arm" LocLeftRing LocationType = "left_ring" LocRightRing LocationType = "right_ring" LocBelt LocationType = "belt" LocFeet LocationType = "feet" LocGloves LocationType = "gloves" LocLeftArmSecondary LocationType = "left_arm_secondary" LocRightArmSecondary LocationType = "right_arm_secondary" Note that if you begin game on secondary slot it will be left_arm then primary will be left_arm_secondary (inversed): With pr 62 (activeweaponslot) we will be able to solve this potential issue. * small optimization * Socketed items to base from pointer new helper functions. AutoAffix offset. * remove helpers fnc, flag isEquipped to HasBeenEquipped * Runewords names mapped * helper function not needed * Optimization * adjust pre-allocations * readd removed itemloc comment * Addex prefix/suffix to rules .nip new structure: [Type], [Quality],[Class], [Name], [Flag], [Color], [Prefix], [Suffix] # Stats Exemple : [name] == ring && [quality] == rare && [suffix] == 174 # Any magic ring with suffix 'of The Apprentice' ( 10 faster cast rate) Will be useful for trophy items with fool/cruel [prefix] == 1416 [prefix] == 1273 * LevelReq + Affixes mapping rare/magic/base/runewords. Nip parser LevelReq * Unique and set items mapping + LevelReq
This commit is contained in:
@@ -3,6 +3,7 @@ package memory
|
||||
import (
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hectorgimenez/d2go/pkg/data"
|
||||
"github.com/hectorgimenez/d2go/pkg/data/item"
|
||||
@@ -16,8 +17,9 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD
|
||||
baseAddr := gd.Process.moduleBaseAddressPtr + gd.offset.UnitTable + (4 * 1024)
|
||||
unitTableBuffer := gd.Process.ReadBytesFromMemory(baseAddr, 128*8)
|
||||
|
||||
// Process shared stash data
|
||||
stashPlayerUnits := make(map[uint]RawPlayerUnit)
|
||||
stashPlayerUnitOrder := make([]uint, 0)
|
||||
stashPlayerUnitOrder := make([]uint, 0, 3) // Pre-allocate with expected size
|
||||
for _, pu := range rawPlayerUnits {
|
||||
if pu.States.HasState(state.Sharedstash) {
|
||||
order := gd.ReadUInt(pu.Address+0xD8, Uint64)
|
||||
@@ -30,14 +32,15 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD
|
||||
// Gold
|
||||
inventoryGold, _ := mainPlayer.BaseStats.FindStat(stat.Gold, 0)
|
||||
mainPlayerStashedGold, _ := mainPlayer.BaseStats.FindStat(stat.StashGold, 0)
|
||||
stashedGold := [4]int{}
|
||||
stashedGold[0] = mainPlayerStashedGold.Value
|
||||
stashedGold := [4]int{mainPlayerStashedGold.Value, 0, 0, 0}
|
||||
|
||||
for i, puKey := range stashPlayerUnitOrder {
|
||||
if i > 2 {
|
||||
break
|
||||
}
|
||||
stashGold, _ := stashPlayerUnits[puKey].BaseStats.FindStat(stat.StashGold, 0)
|
||||
stashedGold[i+1] = stashGold.Value
|
||||
if stashGold, found := stashPlayerUnits[puKey].BaseStats.FindStat(stat.StashGold, 0); found {
|
||||
stashedGold[i+1] = stashGold.Value
|
||||
}
|
||||
}
|
||||
|
||||
inventory := data.Inventory{
|
||||
@@ -45,97 +48,240 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD
|
||||
StashedGold: stashedGold,
|
||||
}
|
||||
belt := data.Belt{}
|
||||
|
||||
type socketInfo struct {
|
||||
item *data.Item
|
||||
position int // Store X position
|
||||
}
|
||||
socketedItemsMap := make(map[data.UnitID][]socketInfo, 120) // Up to ~120 items could be socketable
|
||||
baseItemsMap := make(map[data.UnitID]*data.Item, 120) // Same number of potential base items
|
||||
allItems := make([]*data.Item, 0, 480) // max capacity: 400 (stashes) + 40 (inv) + 12 (cube) + 12 (equipped) + 16 (belt)
|
||||
|
||||
// Pre-allocate buffers for repeated use
|
||||
var itemDataBuffer = make([]byte, 144)
|
||||
var unitDataBuffer = make([]byte, 144)
|
||||
var pathBuffer = make([]byte, 144)
|
||||
|
||||
// Process all items in a single pass
|
||||
for i := 0; i < 128; i++ {
|
||||
itemOffset := 8 * i
|
||||
itemUnitPtr := uintptr(ReadUIntFromBuffer(unitTableBuffer, uint(itemOffset), Uint64))
|
||||
for itemUnitPtr > 0 {
|
||||
itemDataBuffer := gd.Process.ReadBytesFromMemory(itemUnitPtr, 144)
|
||||
itemType := ReadUIntFromBuffer(itemDataBuffer, 0x00, Uint32)
|
||||
txtFileNo := ReadUIntFromBuffer(itemDataBuffer, 0x04, Uint32)
|
||||
unitID := ReadUIntFromBuffer(itemDataBuffer, 0x08, Uint32)
|
||||
// itemLoc = 0 in inventory, 1 equipped, 2 in belt, 3 on ground, 4 cursor, 5 dropping, 6 socketed
|
||||
itemLoc := ReadUIntFromBuffer(itemDataBuffer, 0x0C, Uint32)
|
||||
|
||||
if itemType != 4 {
|
||||
itemUnitPtr = uintptr(gd.Process.ReadUInt(itemUnitPtr+0x158, Uint64))
|
||||
for itemUnitPtr > 0 {
|
||||
nextItemPtr := uintptr(gd.Process.ReadUInt(itemUnitPtr+0x158, Uint64))
|
||||
|
||||
// Read basic item data into pre-allocated buffer
|
||||
if err := gd.Process.ReadIntoBuffer(itemUnitPtr, itemDataBuffer); err != nil {
|
||||
itemUnitPtr = nextItemPtr
|
||||
continue
|
||||
}
|
||||
|
||||
itemType := ReadUIntFromBuffer(itemDataBuffer, 0x00, Uint32)
|
||||
|
||||
// Skip non-item entries early
|
||||
if itemType != 4 {
|
||||
itemUnitPtr = nextItemPtr
|
||||
continue
|
||||
}
|
||||
|
||||
txtFileNo := ReadUIntFromBuffer(itemDataBuffer, 0x04, Uint32)
|
||||
unitID := ReadUIntFromBuffer(itemDataBuffer, 0x08, Uint32)
|
||||
|
||||
// itemLoc = 0 in inventory, 1 equipped, 2 in belt, 3 on ground, 4 cursor, 5 dropping, 6 socketed
|
||||
itemLoc := ReadUIntFromBuffer(itemDataBuffer, 0x0C, Uint32)
|
||||
|
||||
unitDataPtr := uintptr(ReadUIntFromBuffer(itemDataBuffer, 0x10, Uint64))
|
||||
unitDataBuffer := gd.Process.ReadBytesFromMemory(unitDataPtr, 144)
|
||||
if err := gd.Process.ReadIntoBuffer(unitDataPtr, unitDataBuffer); err != nil {
|
||||
itemUnitPtr = nextItemPtr
|
||||
continue
|
||||
}
|
||||
|
||||
flags := ReadUIntFromBuffer(unitDataBuffer, 0x18, Uint32)
|
||||
invPage := ReadUIntFromBuffer(unitDataBuffer, 0x55, Uint8)
|
||||
itemQuality := ReadUIntFromBuffer(unitDataBuffer, 0x00, Uint32)
|
||||
itemOwnerNPC := ReadUIntFromBuffer(unitDataBuffer, 0x0C, Uint32)
|
||||
|
||||
// Item coordinates (X, Y)
|
||||
// Link to uniqueitems.txt, setitems.txt
|
||||
txtUniqueSet := int32(gd.Process.ReadUInt(unitDataPtr+0x34, Uint32))
|
||||
|
||||
pathPtr := uintptr(ReadUIntFromBuffer(itemDataBuffer, 0x38, Uint64))
|
||||
pathBuffer := gd.Process.ReadBytesFromMemory(pathPtr, 144)
|
||||
if err := gd.Process.ReadIntoBuffer(pathPtr, pathBuffer); err != nil {
|
||||
itemUnitPtr = nextItemPtr
|
||||
continue
|
||||
}
|
||||
// Item coordinates (X, Y)
|
||||
itemX := ReadUIntFromBuffer(pathBuffer, 0x10, Uint16)
|
||||
itemY := ReadUIntFromBuffer(pathBuffer, 0x14, Uint16)
|
||||
|
||||
name := item.GetNameByEnum(txtFileNo)
|
||||
itemHovered := false
|
||||
if hover.IsHovered && hover.UnitType == 4 && hover.UnitID == data.UnitID(unitID) {
|
||||
itemHovered = true
|
||||
}
|
||||
|
||||
itm := data.Item{
|
||||
// Create item structure
|
||||
itm := &data.Item{
|
||||
ID: int(txtFileNo),
|
||||
UnitID: data.UnitID(unitID),
|
||||
Name: name,
|
||||
Name: item.GetNameByEnum(txtFileNo),
|
||||
Quality: item.Quality(itemQuality),
|
||||
Position: data.Position{
|
||||
X: int(itemX),
|
||||
Y: int(itemY),
|
||||
},
|
||||
IsHovered: itemHovered,
|
||||
IsHovered: hover.IsHovered && hover.UnitType == 4 && hover.UnitID == data.UnitID(unitID),
|
||||
Sockets: make([]data.Item, 0),
|
||||
UniqueSetID: txtUniqueSet,
|
||||
}
|
||||
setProperties(&itm, uint32(flags))
|
||||
|
||||
// Set item properties
|
||||
setProperties(itm, uint32(flags))
|
||||
|
||||
// Read rare affixes
|
||||
rarePrefix := int16(gd.Process.ReadUInt(unitDataPtr+0x42, Uint16))
|
||||
rareSuffix := int16(gd.Process.ReadUInt(unitDataPtr+0x44, Uint16))
|
||||
//autoAffix := int16(gd.Process.ReadUInt(unitDataPtr+0x46, Uint16))
|
||||
|
||||
// Read magic affixes
|
||||
var prefixes [3]int16
|
||||
var suffixes [3]int16
|
||||
for i := 0; i < 3; i++ {
|
||||
prefixes[i] = int16(gd.Process.ReadUInt(unitDataPtr+0x48+uintptr(i*2), Uint16))
|
||||
suffixes[i] = int16(gd.Process.ReadUInt(unitDataPtr+0x4E+uintptr(i*2), Uint16))
|
||||
}
|
||||
|
||||
itm.Affixes = data.ItemAffixes{
|
||||
Rare: struct {
|
||||
Prefix int16
|
||||
Suffix int16
|
||||
}{
|
||||
Prefix: rarePrefix,
|
||||
Suffix: rareSuffix,
|
||||
},
|
||||
Magic: struct {
|
||||
Prefixes [3]int16
|
||||
Suffixes [3]int16
|
||||
}{
|
||||
Prefixes: prefixes,
|
||||
Suffixes: suffixes,
|
||||
},
|
||||
}
|
||||
|
||||
maxAffixReq := 0
|
||||
if itm.Identified {
|
||||
switch itm.Quality {
|
||||
case item.QualityUnique:
|
||||
// find matching item (uniqueitems.txt)
|
||||
for _, uniqueInfo := range item.UniqueItems {
|
||||
if uniqueInfo.ID == int(txtUniqueSet) {
|
||||
itm.IdentifiedName = uniqueInfo.Name
|
||||
itm.LevelReq = uniqueInfo.LevelReq
|
||||
break
|
||||
}
|
||||
}
|
||||
case item.QualitySet:
|
||||
// find matching item (setitems.txt)
|
||||
for setItemName, setItemInfo := range item.SetItems {
|
||||
if setItemInfo.ID == int(txtUniqueSet) {
|
||||
itm.IdentifiedName = string(setItemName)
|
||||
itm.LevelReq = setItemInfo.LevelReq
|
||||
break
|
||||
}
|
||||
}
|
||||
case item.QualityRare:
|
||||
// Set item name from rare affixes
|
||||
if prefix, exists := item.RarePrefixDesc[int(rarePrefix)]; exists {
|
||||
if suffix, exists := item.RareSuffixDesc[int(rareSuffix)]; exists {
|
||||
itm.IdentifiedName = prefix.Name + " " + suffix.Name
|
||||
}
|
||||
}
|
||||
// Get level requirements from magic affixes
|
||||
for _, prefixID := range prefixes {
|
||||
if prefix, exists := item.MagicPrefixDesc[int(prefixID)]; exists && prefixID != 0 {
|
||||
if prefix.LevelReq > maxAffixReq {
|
||||
maxAffixReq = prefix.LevelReq
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, suffixID := range suffixes {
|
||||
if suffix, exists := item.MagicSuffixDesc[int(suffixID)]; exists && suffixID != 0 {
|
||||
if suffix.LevelReq > maxAffixReq {
|
||||
maxAffixReq = suffix.LevelReq
|
||||
}
|
||||
}
|
||||
}
|
||||
case item.QualityMagic:
|
||||
var prefixParts []string
|
||||
var suffixParts []string
|
||||
|
||||
// Get all prefixes
|
||||
for _, prefixID := range prefixes {
|
||||
if prefix, exists := item.MagicPrefixDesc[int(prefixID)]; exists && prefixID != 0 {
|
||||
prefixParts = append(prefixParts, prefix.Name)
|
||||
if prefix.LevelReq > maxAffixReq {
|
||||
maxAffixReq = prefix.LevelReq
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all suffixes
|
||||
for _, suffixID := range suffixes {
|
||||
if suffix, exists := item.MagicSuffixDesc[int(suffixID)]; exists && suffixID != 0 {
|
||||
suffixParts = append(suffixParts, suffix.Name)
|
||||
if suffix.LevelReq > maxAffixReq {
|
||||
maxAffixReq = suffix.LevelReq
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct name: prefixes + base name + suffixes
|
||||
var nameParts []string
|
||||
if len(prefixParts) > 0 {
|
||||
nameParts = append(nameParts, prefixParts...)
|
||||
}
|
||||
nameParts = append(nameParts, string(itm.Name))
|
||||
if len(suffixParts) > 0 {
|
||||
nameParts = append(nameParts, suffixParts...)
|
||||
}
|
||||
itm.IdentifiedName = strings.Join(nameParts, " ")
|
||||
}
|
||||
}
|
||||
|
||||
// Set runeword name if applicable
|
||||
if itm.IsRuneword {
|
||||
if runeword, exists := item.RunewordIDMap[prefixes[0]]; exists {
|
||||
itm.RunewordName = runeword
|
||||
}
|
||||
}
|
||||
// Determine item location
|
||||
location := item.LocationUnknown
|
||||
switch itemLoc {
|
||||
case 0:
|
||||
if itemOwnerNPC == 2 || itemOwnerNPC == uint(stashPlayerUnits[stashPlayerUnitOrder[0]].UnitID) {
|
||||
location = item.LocationSharedStash
|
||||
invPage = 1
|
||||
break
|
||||
}
|
||||
if itemOwnerNPC == 3 || itemOwnerNPC == uint(stashPlayerUnits[stashPlayerUnitOrder[1]].UnitID) {
|
||||
} else if itemOwnerNPC == 3 || itemOwnerNPC == uint(stashPlayerUnits[stashPlayerUnitOrder[1]].UnitID) {
|
||||
location = item.LocationSharedStash
|
||||
invPage = 2
|
||||
break
|
||||
}
|
||||
if itemOwnerNPC == 4 || itemOwnerNPC == uint(stashPlayerUnits[stashPlayerUnitOrder[2]].UnitID) {
|
||||
} else if itemOwnerNPC == 4 || itemOwnerNPC == uint(stashPlayerUnits[stashPlayerUnitOrder[2]].UnitID) {
|
||||
location = item.LocationSharedStash
|
||||
invPage = 3
|
||||
break
|
||||
}
|
||||
|
||||
if 0x00002000&flags != 0 && itemOwnerNPC == 4294967295 {
|
||||
} else if 0x00002000&flags != 0 && itemOwnerNPC == 4294967295 {
|
||||
location = item.LocationVendor
|
||||
break
|
||||
}
|
||||
if data.UnitID(itemOwnerNPC) == mainPlayer.UnitID || itemOwnerNPC == 1 {
|
||||
} else if data.UnitID(itemOwnerNPC) == mainPlayer.UnitID || itemOwnerNPC == 1 {
|
||||
if invPage == 0 {
|
||||
location = item.LocationInventory
|
||||
break
|
||||
}
|
||||
if invPage == 3 {
|
||||
} else if invPage == 3 {
|
||||
location = item.LocationCube
|
||||
invPage = 0
|
||||
break
|
||||
} else {
|
||||
location = item.LocationStash
|
||||
invPage = 0
|
||||
}
|
||||
location = item.LocationStash
|
||||
invPage = 0
|
||||
break
|
||||
}
|
||||
case 1:
|
||||
isMercItem := (flags & 0x800000) != 0
|
||||
if data.UnitID(itemOwnerNPC) == mainPlayer.UnitID || itemOwnerNPC == 1 {
|
||||
location = item.LocationEquipped
|
||||
if itm.Type().Code == item.TypeBelt {
|
||||
belt.Name = itm.Name
|
||||
}
|
||||
} else if isMercItem {
|
||||
location = item.LocationMercenary
|
||||
}
|
||||
case 2:
|
||||
if data.UnitID(itemOwnerNPC) == mainPlayer.UnitID || itemOwnerNPC == 1 {
|
||||
@@ -149,39 +295,195 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD
|
||||
location = item.LocationCursor
|
||||
}
|
||||
|
||||
// Set body location if equipped
|
||||
bodyLoc := item.LocNone
|
||||
equipSlotFlags := uint16(gd.Process.ReadUInt(unitDataPtr+uintptr(0x54), Uint16))
|
||||
if equipSlotFlags&0xFF00 == 0xFF00 {
|
||||
equipSlot := uint8(equipSlotFlags & 0xFF)
|
||||
switch equipSlot {
|
||||
case 0x01:
|
||||
bodyLoc = item.LocHead
|
||||
case 0x02:
|
||||
bodyLoc = item.LocNeck
|
||||
case 0x03:
|
||||
bodyLoc = item.LocTorso
|
||||
case 0x04:
|
||||
bodyLoc = item.LocLeftArm
|
||||
case 0x05:
|
||||
bodyLoc = item.LocRightArm
|
||||
case 0x06:
|
||||
bodyLoc = item.LocLeftRing
|
||||
case 0x07:
|
||||
bodyLoc = item.LocRightRing
|
||||
case 0x08:
|
||||
bodyLoc = item.LocBelt
|
||||
case 0x09:
|
||||
bodyLoc = item.LocFeet
|
||||
case 0x0A:
|
||||
bodyLoc = item.LocGloves
|
||||
case 0x0B:
|
||||
bodyLoc = item.LocLeftArmSecondary
|
||||
case 0x0C:
|
||||
bodyLoc = item.LocRightArmSecondary
|
||||
}
|
||||
}
|
||||
|
||||
itm.Location = item.Location{
|
||||
LocationType: location,
|
||||
BodyLocation: bodyLoc,
|
||||
Page: int(invPage),
|
||||
}
|
||||
|
||||
// We don't care about the inventory we don't know where they are, probably previous games or random crap
|
||||
if location != item.LocationUnknown {
|
||||
// Item Stats
|
||||
statsListExPtr := uintptr(ReadUIntFromBuffer(itemDataBuffer, 0x88, Uint64))
|
||||
baseStats, stats := gd.getItemStats(statsListExPtr)
|
||||
itm.Stats = stats
|
||||
itm.BaseStats = baseStats
|
||||
if location == item.LocationUnknown {
|
||||
itemUnitPtr = nextItemPtr
|
||||
continue
|
||||
}
|
||||
|
||||
if location == item.LocationBelt {
|
||||
belt.Items = append(belt.Items, itm)
|
||||
// Read item stats
|
||||
statsListExPtr := uintptr(ReadUIntFromBuffer(itemDataBuffer, 0x88, Uint64))
|
||||
itm.BaseStats, itm.Stats = gd.getItemStats(statsListExPtr)
|
||||
|
||||
// Process socket information
|
||||
if location == item.LocationSocket {
|
||||
// Set level requirement for socketed item
|
||||
if itm.Quality == item.QualityUnique {
|
||||
for _, uniqueInfo := range item.UniqueItems {
|
||||
if uniqueInfo.Code == itm.Desc().Code {
|
||||
itm.LevelReq = uniqueInfo.LevelReq
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inventory.AllItems = append(inventory.AllItems, itm)
|
||||
// Normal socketed items (like runes) just use base requirement
|
||||
itm.LevelReq = item.Desc[itm.ID].RequiredLevel
|
||||
}
|
||||
itemExtraData := uintptr(gd.Process.ReadUInt(unitDataPtr+0xA0, Uint64))
|
||||
if itemExtraData != 0 {
|
||||
parentInfoPtr := uintptr(gd.Process.ReadUInt(itemExtraData+0x08, Uint64))
|
||||
if parentInfoPtr != 0 {
|
||||
// Read parent unit ID directly from base item memory structure
|
||||
if err := gd.Process.ReadIntoBuffer(parentInfoPtr, itemDataBuffer); err == nil {
|
||||
parentUnitID := data.UnitID(ReadUIntFromBuffer(itemDataBuffer, 0x08, Uint32))
|
||||
socketedItemsMap[parentUnitID] = append(socketedItemsMap[parentUnitID], socketInfo{
|
||||
item: itm,
|
||||
position: itm.Position.X,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check if item has sockets
|
||||
if numSockets, _ := itm.Stats.FindStat(stat.NumSockets, 0); numSockets.Value > 0 {
|
||||
baseItemsMap[itm.UnitID] = itm
|
||||
}
|
||||
}
|
||||
|
||||
itemUnitPtr = uintptr(gd.Process.ReadUInt(itemUnitPtr+0x158, Uint64))
|
||||
// Add to appropriate collections
|
||||
if location == item.LocationBelt {
|
||||
belt.Items = append(belt.Items, *itm)
|
||||
} else if location != item.LocationSocket {
|
||||
allItems = append(allItems, itm)
|
||||
}
|
||||
|
||||
itemUnitPtr = nextItemPtr
|
||||
}
|
||||
}
|
||||
|
||||
// Link sockets to base items
|
||||
for baseUnitID, baseItem := range baseItemsMap {
|
||||
numSockets, _ := baseItem.Stats.FindStat(stat.NumSockets, 0)
|
||||
if numSockets.Value == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get socket list for this base item
|
||||
sockets := socketedItemsMap[baseUnitID]
|
||||
if len(sockets) != numSockets.Value {
|
||||
continue
|
||||
}
|
||||
|
||||
// Sort by position if we have multiple sockets
|
||||
if len(sockets) > 1 {
|
||||
sort.Slice(sockets, func(i, j int) bool {
|
||||
return sockets[i].position < sockets[j].position
|
||||
})
|
||||
}
|
||||
|
||||
// Validate socket positions and build final list in one pass
|
||||
baseItem.Sockets = make([]data.Item, 0, numSockets.Value)
|
||||
for i, socket := range sockets {
|
||||
if socket.position != i {
|
||||
baseItem.Sockets = nil // Reset if positions are invalid
|
||||
break
|
||||
}
|
||||
baseItem.Sockets = append(baseItem.Sockets, *socket.item)
|
||||
}
|
||||
}
|
||||
|
||||
// Build final inventory
|
||||
inventory.AllItems = make([]data.Item, len(allItems))
|
||||
for i, itm := range allItems {
|
||||
maxReq := item.Desc[itm.ID].RequiredLevel
|
||||
|
||||
// Check unique/set level requirements
|
||||
if itm.Quality == item.QualityUnique {
|
||||
for _, uniqueInfo := range item.UniqueItems {
|
||||
if uniqueInfo.ID == int(itm.UniqueSetID) && itm.Identified {
|
||||
if uniqueInfo.LevelReq > maxReq {
|
||||
maxReq = uniqueInfo.LevelReq
|
||||
}
|
||||
itm.IdentifiedName = uniqueInfo.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if itm.Quality == item.QualitySet {
|
||||
for setItemName, setItemInfo := range item.SetItems {
|
||||
if setItemInfo.ID == int(itm.UniqueSetID) && itm.Identified {
|
||||
if setItemInfo.LevelReq > maxReq {
|
||||
maxReq = setItemInfo.LevelReq
|
||||
}
|
||||
itm.IdentifiedName = string(setItemName)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check socketed items
|
||||
for _, socketItem := range itm.Sockets {
|
||||
if socketItem.LevelReq > maxReq {
|
||||
maxReq = socketItem.LevelReq
|
||||
}
|
||||
|
||||
// Check affixes on magic socketed items
|
||||
if socketItem.Quality == item.QualityMagic {
|
||||
maxReq = updateMaxReqFromAffixes(maxReq, socketItem.Affixes)
|
||||
}
|
||||
}
|
||||
|
||||
// Check item's own affixes for magic/rare items
|
||||
if itm.Identified && (itm.Quality == item.QualityMagic || itm.Quality == item.QualityRare) {
|
||||
maxReq = updateMaxReqFromAffixes(maxReq, itm.Affixes)
|
||||
}
|
||||
|
||||
itm.LevelReq = maxReq
|
||||
inventory.AllItems[i] = *itm
|
||||
}
|
||||
|
||||
// Sort items by distance if needed, using pre-calculated distances
|
||||
if len(inventory.AllItems) > 0 {
|
||||
// Pre-calculate distances to avoid recalculating in sort comparisons
|
||||
distances := make([]int, len(inventory.AllItems))
|
||||
for i, invItem := range inventory.AllItems {
|
||||
distances[i] = utils.DistanceFromPoint(mainPlayer.Position, invItem.Position)
|
||||
}
|
||||
|
||||
sort.SliceStable(inventory.AllItems, func(i, j int) bool {
|
||||
return distances[i] < distances[j]
|
||||
})
|
||||
}
|
||||
|
||||
inventory.Belt = belt
|
||||
|
||||
sort.SliceStable(inventory.AllItems, func(i, j int) bool {
|
||||
distanceI := utils.DistanceFromPoint(mainPlayer.Position, inventory.AllItems[i].Position)
|
||||
distanceJ := utils.DistanceFromPoint(mainPlayer.Position, inventory.AllItems[j].Position)
|
||||
|
||||
return distanceI < distanceJ
|
||||
})
|
||||
|
||||
return inventory
|
||||
}
|
||||
|
||||
@@ -282,7 +584,7 @@ func setProperties(item *data.Item, flags uint32) {
|
||||
item.IsEar = true
|
||||
}
|
||||
if 0x2000&flags != 0 {
|
||||
item.InTradeOrStoreScreen = true
|
||||
item.InTradeOrStoreScreen = true // If item is sold to vendor or placed in trade with another player
|
||||
}
|
||||
if 0x800&flags != 0 {
|
||||
item.HasSockets = true
|
||||
@@ -298,6 +600,25 @@ func setProperties(item *data.Item, flags uint32) {
|
||||
item.IsInSocket = true
|
||||
}
|
||||
if 0x1&flags != 0 {
|
||||
item.IsEquipped = true
|
||||
item.HasBeenEquipped = true // Item was recently equipped (same game)
|
||||
}
|
||||
}
|
||||
|
||||
func updateMaxReqFromAffixes(currentMax int, affixes data.ItemAffixes) int {
|
||||
maxReq := currentMax
|
||||
for _, prefixID := range affixes.Magic.Prefixes {
|
||||
if prefix, exists := item.MagicPrefixDesc[int(prefixID)]; exists && prefixID != 0 {
|
||||
if prefix.LevelReq > maxReq {
|
||||
maxReq = prefix.LevelReq
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, suffixID := range affixes.Magic.Suffixes {
|
||||
if suffix, exists := item.MagicSuffixDesc[int(suffixID)]; exists && suffixID != 0 {
|
||||
if suffix.LevelReq > maxReq {
|
||||
maxReq = suffix.LevelReq
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxReq
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user