update nip rule
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package nip
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/hectorgimenez/d2go/pkg/data"
|
||||
@@ -755,6 +757,112 @@ func TestRule_Evaluate(t *testing.T) {
|
||||
},
|
||||
want: RuleResultNoMatch,
|
||||
},
|
||||
// Regression tests for the ethereal+runeword [flag] bitmask interaction.
|
||||
// Previously "[flag] == ethereal" rewrote to a scalar compare and missed
|
||||
// ethereal runewords (flag = eth | rw). The rewriter now enumerates both
|
||||
// states the chosen bit can produce.
|
||||
{
|
||||
name: "Flag: == ethereal matches a plain ethereal item",
|
||||
fields: fields{
|
||||
RawLine: "[type] == armor && [flag] == ethereal",
|
||||
Enabled: true,
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
ID: 373,
|
||||
Name: "mageplate",
|
||||
Identified: true,
|
||||
Ethereal: true,
|
||||
IsRuneword: false,
|
||||
},
|
||||
},
|
||||
want: RuleResultFullMatch,
|
||||
},
|
||||
{
|
||||
name: "Flag: == ethereal also matches an ethereal RUNEWORD (regression: was NoMatch)",
|
||||
fields: fields{
|
||||
RawLine: "[type] == armor && [flag] == ethereal",
|
||||
Enabled: true,
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
ID: 373,
|
||||
Name: "mageplate",
|
||||
Identified: true,
|
||||
Ethereal: true,
|
||||
IsRuneword: true,
|
||||
},
|
||||
},
|
||||
want: RuleResultFullMatch,
|
||||
},
|
||||
{
|
||||
name: "Flag: != ethereal correctly excludes ethereal runeword",
|
||||
fields: fields{
|
||||
RawLine: "[type] == armor && [flag] != ethereal",
|
||||
Enabled: true,
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
ID: 373,
|
||||
Name: "mageplate",
|
||||
Identified: true,
|
||||
Ethereal: true,
|
||||
IsRuneword: true,
|
||||
},
|
||||
},
|
||||
want: RuleResultNoMatch,
|
||||
},
|
||||
{
|
||||
name: "Flag: != ethereal matches a non-ethereal runeword",
|
||||
fields: fields{
|
||||
RawLine: "[type] == armor && [flag] != ethereal",
|
||||
Enabled: true,
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
ID: 373,
|
||||
Name: "mageplate",
|
||||
Identified: true,
|
||||
Ethereal: false,
|
||||
IsRuneword: true,
|
||||
},
|
||||
},
|
||||
want: RuleResultFullMatch,
|
||||
},
|
||||
{
|
||||
name: "Flag: == runeword matches an ethereal runeword",
|
||||
fields: fields{
|
||||
RawLine: "[type] == armor && [flag] == runeword",
|
||||
Enabled: true,
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
ID: 373,
|
||||
Name: "mageplate",
|
||||
Identified: true,
|
||||
Ethereal: true,
|
||||
IsRuneword: true,
|
||||
},
|
||||
},
|
||||
want: RuleResultFullMatch,
|
||||
},
|
||||
{
|
||||
name: "Flag: != runeword excludes ethereal runeword",
|
||||
fields: fields{
|
||||
RawLine: "[type] == armor && [flag] != runeword",
|
||||
Enabled: true,
|
||||
},
|
||||
args: args{
|
||||
item: data.Item{
|
||||
ID: 373,
|
||||
Name: "mageplate",
|
||||
Identified: true,
|
||||
Ethereal: true,
|
||||
IsRuneword: true,
|
||||
},
|
||||
},
|
||||
want: RuleResultNoMatch,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -804,6 +912,227 @@ func TestNew(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateSkillRulesIncludeHighestAliasLayers(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
rule string
|
||||
stat stat.ID
|
||||
layer int
|
||||
}{
|
||||
{
|
||||
name: "class skills include warlock layer",
|
||||
rule: "[quality] >= normal # [itemaddclassskills] >= 3",
|
||||
stat: stat.AddClassSkills,
|
||||
layer: 7,
|
||||
},
|
||||
{
|
||||
name: "skill tabs include demon layer",
|
||||
rule: "[quality] >= normal # [itemaddskilltab] >= 3",
|
||||
stat: stat.AddSkillTab,
|
||||
layer: 56,
|
||||
},
|
||||
{
|
||||
name: "skill tabs include eldritch layer",
|
||||
rule: "[quality] >= normal # [itemaddskilltab] >= 3",
|
||||
stat: stat.AddSkillTab,
|
||||
layer: 57,
|
||||
},
|
||||
{
|
||||
name: "skill tabs include chaos layer",
|
||||
rule: "[quality] >= normal # [itemaddskilltab] >= 3",
|
||||
stat: stat.AddSkillTab,
|
||||
layer: 58,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rule, err := NewRule(tt.rule, "test.nip", 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := rule.Evaluate(data.Item{
|
||||
ID: 516,
|
||||
Name: "healingpotion",
|
||||
Quality: item.QualityMagic,
|
||||
Identified: true,
|
||||
Stats: []stat.Data{
|
||||
{ID: tt.stat, Layer: tt.layer, Value: 3},
|
||||
},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, RuleResultFullMatch, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarlockSkillAliasesReadExactLayers(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
rule string
|
||||
stat stat.ID
|
||||
layer int
|
||||
}{
|
||||
{
|
||||
name: "warlock class skills",
|
||||
rule: "[quality] >= normal # [warlockskills] >= 3",
|
||||
stat: stat.AddClassSkills,
|
||||
layer: 7,
|
||||
},
|
||||
{
|
||||
name: "itemadd warlock class skills",
|
||||
rule: "[quality] >= normal # [itemaddwarlockskills] >= 3",
|
||||
stat: stat.AddClassSkills,
|
||||
layer: 7,
|
||||
},
|
||||
{
|
||||
name: "warlock chaos skill tab",
|
||||
rule: "[quality] >= normal # [chaosskilltab] >= 3",
|
||||
stat: stat.AddSkillTab,
|
||||
layer: 58,
|
||||
},
|
||||
{
|
||||
name: "itemadd warlock chaos skill tab",
|
||||
rule: "[quality] >= normal # [itemaddchaosskilltab] >= 3",
|
||||
stat: stat.AddSkillTab,
|
||||
layer: 58,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rule, err := NewRule(tt.rule, "test.nip", 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := rule.Evaluate(data.Item{
|
||||
ID: 516,
|
||||
Name: "healingpotion",
|
||||
Quality: item.QualityMagic,
|
||||
Identified: true,
|
||||
Stats: []stat.Data{
|
||||
{ID: tt.stat, Layer: tt.layer, Value: 3},
|
||||
},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, RuleResultFullMatch, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllResUsesLowestElementalResistance(t *testing.T) {
|
||||
rule, err := NewRule("[quality] >= normal # [allres] >= 20", "test.nip", 1)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := rule.Evaluate(data.Item{
|
||||
ID: 516,
|
||||
Name: "healingpotion",
|
||||
Quality: item.QualityMagic,
|
||||
Identified: true,
|
||||
Stats: stat.Stats{
|
||||
{ID: stat.FireResist, Value: 25},
|
||||
{ID: stat.LightningResist, Value: 20},
|
||||
{ID: stat.ColdResist, Value: 30},
|
||||
{ID: stat.PoisonResist, Value: 35},
|
||||
},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, RuleResultFullMatch, result)
|
||||
}
|
||||
|
||||
func TestAllStatAliasesReadFromItemStats(t *testing.T) {
|
||||
aliases := make([]string, 0, len(statAliases))
|
||||
for alias := range statAliases {
|
||||
aliases = append(aliases, alias)
|
||||
}
|
||||
sort.Strings(aliases)
|
||||
|
||||
for _, alias := range aliases {
|
||||
t.Run(alias, func(t *testing.T) {
|
||||
if alias == "allres" {
|
||||
itemToCheck := data.Item{
|
||||
ID: 516,
|
||||
Name: "healingpotion",
|
||||
Quality: item.QualityMagic,
|
||||
Identified: true,
|
||||
Stats: stat.Stats{
|
||||
{ID: stat.FireResist, Value: 321},
|
||||
{ID: stat.LightningResist, Value: 321},
|
||||
{ID: stat.ColdResist, Value: 321},
|
||||
{ID: stat.PoisonResist, Value: 321},
|
||||
},
|
||||
}
|
||||
requireRuleFullMatch(t, alias, 321, itemToCheck)
|
||||
return
|
||||
}
|
||||
|
||||
statData := statAliases[alias]
|
||||
require.NotEmpty(t, statData)
|
||||
|
||||
statID := stat.ID(statData[0])
|
||||
layer := 0
|
||||
if len(statData) > 1 {
|
||||
layer = statData[1]
|
||||
}
|
||||
|
||||
statsItem, expected := itemWithStat(statID, layer, false)
|
||||
requireRuleFullMatch(t, alias, expected, statsItem)
|
||||
|
||||
baseStatsItem, expected := itemWithStat(statID, layer, true)
|
||||
requireRuleFullMatch(t, alias, expected, baseStatsItem)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func itemWithStat(statID stat.ID, layer int, base bool) (data.Item, int) {
|
||||
expected := 321
|
||||
statLayer := layer
|
||||
statValue := expected
|
||||
|
||||
switch statID {
|
||||
case stat.SkillOnAttack, stat.SkillOnKill, stat.SkillOnDeath, stat.SkillOnHit, stat.SkillOnLevelUp, stat.SkillOnGetHit, stat.ItemChargedSkill:
|
||||
if layer == 1 || layer == 2 {
|
||||
skillID := 47
|
||||
level := 13
|
||||
statLayer = skillID<<6 | level
|
||||
statValue = 5
|
||||
if layer == 1 {
|
||||
expected = skillID
|
||||
} else {
|
||||
expected = level
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
itemToCheck := data.Item{
|
||||
ID: 516,
|
||||
Name: "healingpotion",
|
||||
Quality: item.QualityMagic,
|
||||
Identified: true,
|
||||
}
|
||||
statToCheck := stat.Data{ID: statID, Layer: statLayer, Value: statValue}
|
||||
if base {
|
||||
itemToCheck.BaseStats = stat.Stats{statToCheck}
|
||||
} else {
|
||||
itemToCheck.Stats = stat.Stats{statToCheck}
|
||||
}
|
||||
|
||||
return itemToCheck, expected
|
||||
}
|
||||
|
||||
func requireRuleFullMatch(t *testing.T, alias string, expected int, itemToCheck data.Item) {
|
||||
t.Helper()
|
||||
|
||||
rule, err := NewRule("[quality] >= normal # ["+alias+"] == "+strconv.Itoa(expected), "test.nip", 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, rule.ValidateStats())
|
||||
|
||||
result, err := rule.Evaluate(itemToCheck)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, RuleResultFullMatch, result)
|
||||
}
|
||||
|
||||
func BenchmarkEvaluate(b *testing.B) {
|
||||
it := data.Item{
|
||||
ID: 0,
|
||||
|
||||
Reference in New Issue
Block a user