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:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/.idea
|
||||||
@@ -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{
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user