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 <d.melia@outlook.fr>
Co-authored-by: Arto Simonyan <artosimonyan@protonmail.com>
This commit is contained in:
SoundsLegit
2024-08-05 16:48:06 +02:00
committed by GitHub
parent 67692abea3
commit 938bfeb753
4 changed files with 89 additions and 36 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/.idea

View File

@@ -3,6 +3,7 @@ package memory
import ( import (
"github.com/hectorgimenez/d2go/pkg/data" "github.com/hectorgimenez/d2go/pkg/data"
"github.com/hectorgimenez/d2go/pkg/data/stat" "github.com/hectorgimenez/d2go/pkg/data/stat"
"math"
) )
type GameReader struct { type GameReader struct {
@@ -158,16 +159,18 @@ func (gd *GameReader) getStatsList(statListPtr uintptr) stat.Stats {
stat.DexterityPerLevel, stat.DexterityPerLevel,
stat.VitalityPerLevel, stat.VitalityPerLevel,
stat.ThornsPerLevel: stat.ThornsPerLevel:
value = int(statValue / 8) value = int(math.Max(float64(statValue/8), 1))
case stat.LifePerLevel, case stat.LifePerLevel,
stat.ManaPerLevel: stat.ManaPerLevel:
value = int(statValue / 2048) value = int(math.Max(float64(statValue/2048), 1))
case stat.ReplenishDurability, stat.ReplenishQuantity: case stat.ReplenishDurability, stat.ReplenishQuantity:
value = 2 / int(statValue) value = int(math.Max(float64(2/statValue), 1))
case stat.RegenStaminaPerLevel: case stat.RegenStaminaPerLevel:
value = int(statValue) * 10 value = int(statValue) * 10
case stat.LevelRequirePercent: case stat.LevelRequirePercent:
value = int(statValue) * -1 value = int(statValue) * -1
case stat.AttackRatingPerLevel:
value = int(math.Max(float64(statValue), 15))
} }
stats = append(stats, stat.Data{ stats = append(stats, stat.Data{

View File

@@ -181,54 +181,80 @@ func (gd *GameReader) Inventory(rawPlayerUnits RawPlayerUnits, hover data.HoverD
} }
func (gd *GameReader) getItemStats(statsListExPtr uintptr) (stat.Stats, stat.Stats) { func (gd *GameReader) getItemStats(statsListExPtr uintptr) (stat.Stats, stat.Stats) {
// Initial full and base stats extraction
fullStats := gd.getStatsList(statsListExPtr + 0x88) fullStats := gd.getStatsList(statsListExPtr + 0x88)
baseStats := gd.getStatsList(statsListExPtr + 0x30) baseStats := gd.getStatsList(statsListExPtr + 0x30)
// Flags and last stat list pointers
flags := gd.Process.ReadUInt(statsListExPtr+0x1C, Uint64) flags := gd.Process.ReadUInt(statsListExPtr+0x1C, Uint64)
lastStatsList := uintptr(gd.Process.ReadUInt(statsListExPtr+0x70, 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 { if (flags & 0x80000000) == 0 {
return baseStats, fullStats return baseStats, fullStats
} }
attempts := 0
statListFlags := uintptr(0) // Prepare for additional stats processing
statListPrev := lastStatsList additionalStats := stat.Stats{}
for (0x40 & statListFlags & 0xFFFFDFFF) == 0 { statListPtr := lastStatsList
attempts++
if attempts == 10 { // Traverse the stat lists to accumulate additional stats
return baseStats, fullStats 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)) // Move to the previous stat list
if statListPrev == 0 { statListPtr = uintptr(gd.Process.ReadUInt(statListPtr+0x48, Uint64))
return baseStats, fullStats }
// 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 { // Merge additional stats into fullStats (but not into baseStats)
for _, mStat := range modifierBaseStats { fullStats = append(fullStats, additionalStats...)
if _, found := fullStats.FindStat(mStat.ID, mStat.Layer); !found {
fullStats = append(fullStats, mStat)
}
}
modifierStatsPrevEx := uintptr(gd.Process.ReadUInt(statListPrev+0x48, Uint64)) // Handle base stats from potential modifiers and ensure they don't pollute baseStats
modifierBaseStatsPrev := gd.getStatsList(modifierStatsPrevEx + 0x30) statListPtr = lastStatsList
for _, mStat := range modifierBaseStatsPrev { for statListPtr != 0 {
for i, st := range fullStats { statListFlags := gd.Process.ReadUInt(statListPtr+0x1C, Uint64)
if st.ID == mStat.ID && st.Layer == mStat.Layer && st.Value != mStat.Value {
fullStats[i].Value = st.Value + mStat.Value 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 return baseStats, fullStats

View File

@@ -123,14 +123,14 @@ func TestRule_Evaluate(t *testing.T) {
{ {
name: "Basic rule without stats or maxquantity", name: "Basic rule without stats or maxquantity",
fields: fields{ fields: fields{
RawLine: "[type] == armor && [quality] == magic # #", RawLine: "[type] == assassinclaw && [class] == elite && [quality] == magic # #",
Enabled: true, Enabled: true,
}, },
args: args{ args: args{
item: data.Item{ item: data.Item{
ID: 373, ID: 187,
Identified: false, Identified: false,
Name: "mageplate", Name: "GreaterTalons",
Quality: item.QualityMagic, Quality: item.QualityMagic,
}, },
}, },
@@ -156,6 +156,29 @@ func TestRule_Evaluate(t *testing.T) {
}, },
want: RuleResultFullMatch, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {