From 938bfeb753b7506fdfe89799ecee81f12086e968 Mon Sep 17 00:00:00 2001 From: SoundsLegit <107037010+SoundsLegit@users.noreply.github.com> Date: Mon, 5 Aug 2024 16:48:06 +0200 Subject: [PATCH] Minimum value per level and enhanced damage item stats fix (#26) * Added a temporary fix to have a minimum value of 1 for per level mods * Expose Stat Aliases and Type Aliases (#25) * Fixed setting a minimum value of 1 for per level item stats * Added minimum value of attack rating per level to be 15 * Added min value for replenish durability/quantity * Added fix for reading item modifiers - enhanced damage now works * Delete .idea/.gitignore * Delete .idea/d2go.iml * Delete .idea/modules.xml * Delete .idea/vcs.xml * Added gitignore file * Refactored code --------- Co-authored-by: dmeli Co-authored-by: Arto Simonyan --- .gitignore | 1 + pkg/memory/game_reader.go | 9 ++-- pkg/memory/item.go | 86 +++++++++++++++++++++++++-------------- pkg/nip/rule_test.go | 29 +++++++++++-- 4 files changed, 89 insertions(+), 36 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..757fee3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea \ No newline at end of file diff --git a/pkg/memory/game_reader.go b/pkg/memory/game_reader.go index 279c344..f36aedf 100644 --- a/pkg/memory/game_reader.go +++ b/pkg/memory/game_reader.go @@ -3,6 +3,7 @@ package memory import ( "github.com/hectorgimenez/d2go/pkg/data" "github.com/hectorgimenez/d2go/pkg/data/stat" + "math" ) type GameReader struct { @@ -158,16 +159,18 @@ func (gd *GameReader) getStatsList(statListPtr uintptr) stat.Stats { stat.DexterityPerLevel, stat.VitalityPerLevel, stat.ThornsPerLevel: - value = int(statValue / 8) + value = int(math.Max(float64(statValue/8), 1)) case stat.LifePerLevel, stat.ManaPerLevel: - value = int(statValue / 2048) + value = int(math.Max(float64(statValue/2048), 1)) case stat.ReplenishDurability, stat.ReplenishQuantity: - value = 2 / int(statValue) + value = int(math.Max(float64(2/statValue), 1)) case stat.RegenStaminaPerLevel: value = int(statValue) * 10 case stat.LevelRequirePercent: value = int(statValue) * -1 + case stat.AttackRatingPerLevel: + value = int(math.Max(float64(statValue), 15)) } stats = append(stats, stat.Data{ diff --git a/pkg/memory/item.go b/pkg/memory/item.go index 93b0d74..b407bf3 100644 --- a/pkg/memory/item.go +++ b/pkg/memory/item.go @@ -181,54 +181,80 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD } func (gd *GameReader) getItemStats(statsListExPtr uintptr) (stat.Stats, stat.Stats) { + // Initial full and base stats extraction fullStats := gd.getStatsList(statsListExPtr + 0x88) baseStats := gd.getStatsList(statsListExPtr + 0x30) + // Flags and last stat list pointers flags := gd.Process.ReadUInt(statsListExPtr+0x1C, Uint64) lastStatsList := uintptr(gd.Process.ReadUInt(statsListExPtr+0x70, Uint64)) + // If the special flag isn't set, return the current base and full stats if (flags & 0x80000000) == 0 { return baseStats, fullStats } - attempts := 0 - statListFlags := uintptr(0) - statListPrev := lastStatsList - for (0x40 & statListFlags & 0xFFFFDFFF) == 0 { - attempts++ - if attempts == 10 { - return baseStats, fullStats + + // Prepare for additional stats processing + additionalStats := stat.Stats{} + statListPtr := lastStatsList + + // Traverse the stat lists to accumulate additional stats + for statListPtr != 0 { + + statListFlags := gd.Process.ReadUInt(statListPtr+0x1C, Uint64) + + // If we hit a condition where no further traversal is needed, break + if (0x40 & statListFlags & 0xFFFFDFFF) != 0 { + break } - statListPrev = uintptr(gd.Process.ReadUInt(statListPrev+0x48, Uint64)) - statListFlags = uintptr(gd.Process.ReadUInt(statListPrev+0x1C, Uint64)) - if statListPrev == 0 { - return baseStats, fullStats + + // Move to the previous stat list + statListPtr = uintptr(gd.Process.ReadUInt(statListPtr+0x48, Uint64)) + } + + // If we found a valid previous stat list + if statListPtr != 0 { + additionalBaseStats := gd.getStatsList(statListPtr + 0x30) + + // Add only the additional stats that are not already present in fullStats + for _, statM := range additionalBaseStats { + if _, found := fullStats.FindStat(statM.ID, statM.Layer); !found { + additionalStats = append(additionalStats, statM) + } } } - modifierBaseStats := gd.getStatsList(statListPrev + 0x30) - if len(modifierBaseStats) != 0 { - for _, mStat := range modifierBaseStats { - if _, found := fullStats.FindStat(mStat.ID, mStat.Layer); !found { - fullStats = append(fullStats, mStat) - } - } + // Merge additional stats into fullStats (but not into baseStats) + fullStats = append(fullStats, additionalStats...) - modifierStatsPrevEx := uintptr(gd.Process.ReadUInt(statListPrev+0x48, Uint64)) - modifierBaseStatsPrev := gd.getStatsList(modifierStatsPrevEx + 0x30) + // Handle base stats from potential modifiers and ensure they don't pollute baseStats + statListPtr = lastStatsList - for _, mStat := range modifierBaseStatsPrev { - for i, st := range fullStats { - if st.ID == mStat.ID && st.Layer == mStat.Layer && st.Value != mStat.Value { - fullStats[i].Value = st.Value + mStat.Value + for statListPtr != 0 { + statListFlags := gd.Process.ReadUInt(statListPtr+0x1C, Uint64) + + if statListFlags != 0 { + modifierBaseStats := gd.getStatsList(statListPtr + 0x30) + + for _, mStat := range modifierBaseStats { + // Update fullStats + if existingStat, found := fullStats.FindStat(mStat.ID, mStat.Layer); found { + existingStat.Value = mStat.Value + } else { + fullStats = append(fullStats, mStat) + } + + // Add to baseStats only if this stat qualifies as a base stat + if (flags&0x80000000) != 0 && statListFlags == 0x80000000 { + if _, found := baseStats.FindStat(mStat.ID, mStat.Layer); !found { + baseStats = append(baseStats, mStat) + } } } - if _, found := fullStats.FindStat(mStat.ID, mStat.Layer); !found { - fullStats = append(fullStats, mStat) - } - if _, found := baseStats.FindStat(mStat.ID, mStat.Layer); !found { - baseStats = append(baseStats, mStat) - } } + + // Move to the previous stat list + statListPtr = uintptr(gd.Process.ReadUInt(statListPtr+0x48, Uint64)) } return baseStats, fullStats diff --git a/pkg/nip/rule_test.go b/pkg/nip/rule_test.go index f2d837b..80e87ef 100644 --- a/pkg/nip/rule_test.go +++ b/pkg/nip/rule_test.go @@ -123,14 +123,14 @@ func TestRule_Evaluate(t *testing.T) { { name: "Basic rule without stats or maxquantity", fields: fields{ - RawLine: "[type] == armor && [quality] == magic # #", + RawLine: "[type] == assassinclaw && [class] == elite && [quality] == magic # #", Enabled: true, }, args: args{ item: data.Item{ - ID: 373, + ID: 187, Identified: false, - Name: "mageplate", + Name: "GreaterTalons", Quality: item.QualityMagic, }, }, @@ -156,6 +156,29 @@ func TestRule_Evaluate(t *testing.T) { }, want: RuleResultFullMatch, }, + { + name: "Basic rule for a white superior item with enhanceddamage", + fields: fields{ + RawLine: "[type] == sword # [enhanceddamage] >= 15 #", + Enabled: true, + }, + args: args{ + item: data.Item{ + Identified: true, + ID: 234, + Name: "colossusblade", + Quality: item.QualitySuperior, + Stats: []stat.Data{ + {ID: stat.EnhancedDamage, Value: 15}, + {ID: stat.MinDamage, Value: 28}, + {ID: stat.MaxDamage, Value: 74}, + {ID: stat.TwoHandedMinDamage, Value: 66}, + {ID: stat.TwoHandedMaxDamage, Value: 132}, + }, + }, + }, + want: RuleResultFullMatch, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {