From 1aa957dd96129376b522ad8175c5dadab8dabcd6 Mon Sep 17 00:00:00 2001 From: elb <51070858+elobo91@users.noreply.github.com> Date: Mon, 17 Feb 2025 12:28:27 -0500 Subject: [PATCH] Linking Item.LevelReq to stat(92) stat.LevelRequire + nip parser, Upped item levelreq (#78) * Linking Item.LevelReq to stat(92) stat.LevelRequire . Update nip parser * LevelReq for upgraded items LevelReq is now accurate for upgraded and double upgraded set/unique items Fixed an issue with levelreq not working with socketed jewels on items. --- pkg/memory/item.go | 136 ++++++++++++++++++++++++++++++++++----------- pkg/nip/rule.go | 6 +- 2 files changed, 106 insertions(+), 36 deletions(-) diff --git a/pkg/memory/item.go b/pkg/memory/item.go index 476598a..eb18598 100644 --- a/pkg/memory/item.go +++ b/pkg/memory/item.go @@ -346,8 +346,16 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD // Process socket information if location == item.LocationSocket { - // Set level requirement for socketed item - if itm.Quality == item.QualityUnique { + if itm.Desc().Code == "jew" { + // Base requirement for jewels + itm.LevelReq = item.Desc[itm.ID].RequiredLevel + + // For magic/rare jewels, check affixes + if itm.Quality == item.QualityMagic || itm.Quality == item.QualityRare { + itm.LevelReq = updateMaxReqFromAffixes(itm.LevelReq, itm.Affixes) + } + // Rainbow facets + } else if itm.Quality == item.QualityUnique { for _, uniqueInfo := range item.UniqueItems { if uniqueInfo.Code == itm.Desc().Code { itm.LevelReq = uniqueInfo.LevelReq @@ -355,7 +363,7 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD } } } else { - // Normal socketed items (like runes) just use base requirement + // Normal socketed items (runes,gems) just use base requirement itm.LevelReq = item.Desc[itm.ID].RequiredLevel } itemExtraData := uintptr(gd.Process.ReadUInt(unitDataPtr+0xA0, Uint64)) @@ -424,35 +432,21 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD // Build final inventory inventory.AllItems = make([]data.Item, len(allItems)) for i, itm := range allItems { - maxReq := item.Desc[itm.ID].RequiredLevel + baseDesc := item.Desc[itm.ID] + maxReq := calculateItemLevelReq(itm, baseDesc) - // 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 magic/rare affixes + if itm.Identified && (itm.Quality == item.QualityMagic || itm.Quality == item.QualityRare) { + maxReq = updateMaxReqFromAffixes(maxReq, itm.Affixes) } // Check socketed items for _, socketItem := range itm.Sockets { - if socketItem.LevelReq > maxReq { - maxReq = socketItem.LevelReq + socketBaseDesc := item.Desc[socketItem.ID] + socketReq := calculateItemLevelReq(&socketItem, socketBaseDesc) + + if socketReq > maxReq { + maxReq = socketReq } // Check affixes on magic socketed items @@ -461,12 +455,14 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD } } - // 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 + // Update stat 92 (LevelRequire) so we can use with .nip + for it, s := range itm.Stats { + if s.ID == stat.LevelRequire { + itm.Stats[it].Value = maxReq + break + } + } inventory.AllItems[i] = *itm } @@ -492,6 +488,15 @@ func (gd *GameReader) getItemStats(statsListExPtr uintptr) (stat.Stats, stat.Sta fullStats := gd.getStatsList(statsListExPtr + 0xA8) baseStats := gd.getStatsList(statsListExPtr + 0x30) + // Create empty LevelRequire stat .We will update it from inventory + if _, found := fullStats.FindStat(stat.LevelRequire, 0); !found { + fullStats = append(fullStats, stat.Data{ + ID: stat.LevelRequire, + Value: 0, + Layer: 0, + }) + } + // Flags and last stat list pointers flags := gd.Process.ReadUInt(statsListExPtr+0x1C, Uint64) lastStatsList := uintptr(gd.Process.ReadUInt(statsListExPtr+0x90, Uint64)) @@ -622,3 +627,70 @@ func updateMaxReqFromAffixes(currentMax int, affixes data.ItemAffixes) int { } return maxReq } +func calculateItemLevelReq(itm *data.Item, baseDesc item.Description) int { + // Start with base item's requirement + maxReq := baseDesc.RequiredLevel + + if itm.Quality == item.QualityUnique || itm.Quality == item.QualitySet { + itemCode := baseDesc.Code + normalCode := baseDesc.NormalCode + exceptionalCode := baseDesc.UberCode + eliteCode := baseDesc.UltraCode + + // Find the original item tier by looking up the unique/set item + var originalCode string + if itm.Quality == item.QualityUnique { + for _, uniqueInfo := range item.UniqueItems { + if uniqueInfo.ID == int(itm.UniqueSetID) { + originalCode = uniqueInfo.Code + break + } + } + } else { + for _, setItemInfo := range item.SetItems { + if setItemInfo.ID == int(itm.UniqueSetID) { + originalCode = setItemInfo.Code + break + } + } + } + + if itemCode == eliteCode { + if originalCode == normalCode { + // Double upgraded: Normal -> Exceptional -> Elite + maxReq += 12 // +5 for exceptional, +7 for elite + } else if originalCode == exceptionalCode { + // Single upgraded: Exceptional -> Elite + maxReq += 7 + } + } else if itemCode == exceptionalCode && originalCode == normalCode { + // Single upgraded: Normal -> Exceptional + maxReq += 5 + } + + // Check unique/set specific level requirement + if itm.Identified { + if itm.Quality == item.QualityUnique { + for _, uniqueInfo := range item.UniqueItems { + if uniqueInfo.ID == int(itm.UniqueSetID) { + if uniqueInfo.LevelReq > maxReq { + maxReq = uniqueInfo.LevelReq + } + break + } + } + } else { // Set item + for _, setItemInfo := range item.SetItems { + if setItemInfo.ID == int(itm.UniqueSetID) { + if setItemInfo.LevelReq > maxReq { + maxReq = setItemInfo.LevelReq + } + break + } + } + } + } + } + + return maxReq +} diff --git a/pkg/nip/rule.go b/pkg/nip/rule.go index 87c7172..451a835 100644 --- a/pkg/nip/rule.go +++ b/pkg/nip/rule.go @@ -20,7 +20,7 @@ const ( ) var ( - fixedPropsRegexp = regexp.MustCompile(`(\[(type|quality|class|name|flag|color|prefix|suffix|levelreq)]\s*(<=|<|>|>=|!=|==)\s*([a-zA-Z0-9]+))`) + fixedPropsRegexp = regexp.MustCompile(`(\[(type|quality|class|name|flag|color|prefix|suffix)]\s*(<=|<|>|>=|!=|==)\s*([a-zA-Z0-9]+))`) statsRegexp = regexp.MustCompile(`\[(.*?)]`) maxQtyRegexp = regexp.MustCompile(`(\[maxquantity]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`) ) @@ -169,8 +169,6 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) { break } } - case "levelreq": - stage1Props["levelreq"] = it.LevelReq case "color": // TODO: Not supported yet } @@ -238,7 +236,7 @@ func replaceStringPropertiesInStage1(stage1 string) (string, error) { replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", item.GetIDByName(prop[4]))) case "flag": replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", 1)) - case "prefix", "suffix", "levelreq": + case "prefix", "suffix": // Handle prefix/suffix IDs and levelreq replaceWith = strings.ReplaceAll(prop[0], prop[4], prop[4]) case "color":