update nip rule
This commit is contained in:
@@ -118,6 +118,24 @@ func (r Rules) EvaluateTiers(it data.Item, tierRulesIndexes []int) (Rule, Rule)
|
|||||||
|
|
||||||
var fixedPropsList = map[string]int{"type": 0, "quality": 0, "class": 0, "name": 0, "flag": 0, "color": 0, "prefix": 0, "suffix": 0}
|
var fixedPropsList = map[string]int{"type": 0, "quality": 0, "class": 0, "name": 0, "flag": 0, "color": 0, "prefix": 0, "suffix": 0}
|
||||||
|
|
||||||
|
// flagBit* are the bits Evaluate sets on the runtime `flag` value AND the
|
||||||
|
// rewriter (replaceStringPropertiesInStage1) recognises. flagAllBits is the
|
||||||
|
// disjunction of every supported flag bit: the rewriter enumerates all subsets
|
||||||
|
// of flagAllBits when expanding bit tests for the `==`/`!=` operators (expr-lang
|
||||||
|
// has no bitwise operators). To add a new flag bit, update Evaluate to set it,
|
||||||
|
// add a case in the rewriter's name->bit switch, AND fold it into flagAllBits
|
||||||
|
// so the enumeration sees it.
|
||||||
|
const (
|
||||||
|
flagBitEthereal = 0x400000
|
||||||
|
flagBitRuneword = 0x4000000
|
||||||
|
flagAllBits = flagBitEthereal | flagBitRuneword
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
maxClassSkillsLayer = maxAliasLayer(stat.AddClassSkills)
|
||||||
|
maxSkillTabLayer = maxAliasLayer(stat.AddSkillTab)
|
||||||
|
)
|
||||||
|
|
||||||
func NewRule(rawRule string, filename string, lineNumber int) (Rule, error) {
|
func NewRule(rawRule string, filename string, lineNumber int) (Rule, error) {
|
||||||
rule := sanitizeLine(rawRule)
|
rule := sanitizeLine(rawRule)
|
||||||
|
|
||||||
@@ -252,14 +270,19 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
|
|||||||
case "name":
|
case "name":
|
||||||
stage1Props["name"] = it.ID
|
stage1Props["name"] = it.ID
|
||||||
case "flag":
|
case "flag":
|
||||||
// 0x400000 (eth) | 0x4000000 (runeword) kolbot style
|
// 0x400000 (eth) | 0x4000000 (runeword) kolbot style. Bit values are
|
||||||
|
// shared with the rewriter (replaceStringPropertiesInStage1) via the
|
||||||
|
// package-level flagBit* constants so the rewriter's enumeration of
|
||||||
|
// possible flag-states stays in lock-step with the values written
|
||||||
|
// here — adding a third bit requires editing both this writer AND
|
||||||
|
// the rewriter, but the constants make the dependency discoverable.
|
||||||
currentFlag := 0
|
currentFlag := 0
|
||||||
|
|
||||||
if it.Ethereal {
|
if it.Ethereal {
|
||||||
currentFlag |= 0x400000
|
currentFlag |= flagBitEthereal
|
||||||
}
|
}
|
||||||
if it.IsRuneword {
|
if it.IsRuneword {
|
||||||
currentFlag |= 0x4000000
|
currentFlag |= flagBitRuneword
|
||||||
}
|
}
|
||||||
|
|
||||||
stage1Props["flag"] = currentFlag
|
stage1Props["flag"] = currentFlag
|
||||||
@@ -489,16 +512,47 @@ func replaceStringPropertiesInStage1(stage1 string) (string, error) {
|
|||||||
case "name":
|
case "name":
|
||||||
replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", item.GetIDByName(prop[4])))
|
replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", item.GetIDByName(prop[4])))
|
||||||
case "flag":
|
case "flag":
|
||||||
val := 0
|
// [flag] is a bitmask at runtime (ethereal | runeword), so equality must be
|
||||||
switch strings.ToLower(prop[4]) {
|
// a bit test, not a scalar compare. Previously "[flag] == ethereal" rewrote
|
||||||
|
// to "flag == 0x400000", which evaluated FALSE on an ethereal runeword (whose
|
||||||
|
// flag is 0x4400000 == eth|rw). expr-lang has no bitwise operators, so we
|
||||||
|
// enumerate the two states the chosen bit can produce. flagAllBits below
|
||||||
|
// MUST stay in sync with the writer in Evaluate — every bit Evaluate can
|
||||||
|
// set must appear here, otherwise the negation form may miss valid items.
|
||||||
|
name := strings.ToLower(prop[4])
|
||||||
|
var bit int
|
||||||
|
switch name {
|
||||||
case "runeword":
|
case "runeword":
|
||||||
val = 0x4000000
|
bit = flagBitRuneword
|
||||||
case "ethereal":
|
case "ethereal":
|
||||||
val = 0x400000
|
bit = flagBitEthereal
|
||||||
default:
|
default:
|
||||||
val = 1
|
bit = 1
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case bit == 1:
|
||||||
|
// Unknown flag name — preserve legacy behaviour (replace value with 1).
|
||||||
|
replaceWith = strings.ReplaceAll(prop[0], prop[4], "1")
|
||||||
|
case prop[3] == "==":
|
||||||
|
switch bit {
|
||||||
|
case flagBitRuneword:
|
||||||
|
replaceWith = fmt.Sprintf("(flag == %d || flag == %d)", flagBitRuneword, flagBitRuneword|flagBitEthereal)
|
||||||
|
case flagBitEthereal:
|
||||||
|
replaceWith = fmt.Sprintf("(flag == %d || flag == %d)", flagBitEthereal, flagBitRuneword|flagBitEthereal)
|
||||||
|
}
|
||||||
|
|
||||||
|
case prop[3] == "!=":
|
||||||
|
switch bit {
|
||||||
|
case flagBitRuneword:
|
||||||
|
replaceWith = fmt.Sprintf("(flag == 0 || flag == %d)", flagBitEthereal)
|
||||||
|
case flagBitEthereal:
|
||||||
|
replaceWith = fmt.Sprintf("(flag == 0 || flag == %d)", flagBitRuneword)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Comparison operators other than ==/!= are nonsensical for a bitmask
|
||||||
|
// but kept as scalar compares so exotic legacy rules don't suddenly fail.
|
||||||
|
replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", bit))
|
||||||
}
|
}
|
||||||
replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", val))
|
|
||||||
case "prefix", "suffix":
|
case "prefix", "suffix":
|
||||||
// Handle prefix/suffix IDs
|
// Handle prefix/suffix IDs
|
||||||
replaceWith = strings.ReplaceAll(prop[0], prop[4], prop[4])
|
replaceWith = strings.ReplaceAll(prop[0], prop[4], prop[4])
|
||||||
@@ -541,9 +595,8 @@ func (r Rule) ValidateStats() error {
|
|||||||
func evaluateClassSkillsSum(it data.Item) int {
|
func evaluateClassSkillsSum(it data.Item) int {
|
||||||
// Check all class skills stats
|
// Check all class skills stats
|
||||||
totalClassSkills := 0
|
totalClassSkills := 0
|
||||||
maxLayer := 6 // in aliases.go the max layer for class skills is 6 (itemaddassassinskills)
|
|
||||||
|
|
||||||
for layer := 0; layer <= maxLayer; layer++ {
|
for layer := 0; layer <= maxClassSkillsLayer; layer++ {
|
||||||
if itemStat, found := it.FindStat(stat.AddClassSkills, layer); found && itemStat.Value > 0 {
|
if itemStat, found := it.FindStat(stat.AddClassSkills, layer); found && itemStat.Value > 0 {
|
||||||
totalClassSkills += itemStat.Value
|
totalClassSkills += itemStat.Value
|
||||||
}
|
}
|
||||||
@@ -554,9 +607,8 @@ func evaluateClassSkillsSum(it data.Item) int {
|
|||||||
func evaluateSkillTabSum(it data.Item) int {
|
func evaluateSkillTabSum(it data.Item) int {
|
||||||
// Check all skill tab stats
|
// Check all skill tab stats
|
||||||
totalSkillTabs := 0
|
totalSkillTabs := 0
|
||||||
maxLayer := 50 // in aliases.go the max layer for skill tabs is 50 (itemaddmartialartsskilltab)
|
|
||||||
|
|
||||||
for layer := 0; layer <= maxLayer; layer++ {
|
for layer := 0; layer <= maxSkillTabLayer; layer++ {
|
||||||
if itemStat, found := it.FindStat(stat.AddSkillTab, layer); found && itemStat.Value > 0 {
|
if itemStat, found := it.FindStat(stat.AddSkillTab, layer); found && itemStat.Value > 0 {
|
||||||
totalSkillTabs += itemStat.Value
|
totalSkillTabs += itemStat.Value
|
||||||
}
|
}
|
||||||
@@ -565,6 +617,23 @@ func evaluateSkillTabSum(it data.Item) int {
|
|||||||
return totalSkillTabs
|
return totalSkillTabs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func maxAliasLayer(statID stat.ID) int {
|
||||||
|
maxLayer := 0
|
||||||
|
for _, statData := range statAliases {
|
||||||
|
if len(statData) == 0 || stat.ID(statData[0]) != statID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
layer := 0
|
||||||
|
if len(statData) > 1 {
|
||||||
|
layer = statData[1]
|
||||||
|
}
|
||||||
|
if layer > maxLayer {
|
||||||
|
maxLayer = layer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxLayer
|
||||||
|
}
|
||||||
|
|
||||||
// MaxQuantity returns the maximum quantity of items that character can have, 0 means no limit
|
// MaxQuantity returns the maximum quantity of items that character can have, 0 means no limit
|
||||||
func (r Rule) MaxQuantity() int {
|
func (r Rule) MaxQuantity() int {
|
||||||
return r.maxQuantity
|
return r.maxQuantity
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package nip
|
package nip
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hectorgimenez/d2go/pkg/data"
|
"github.com/hectorgimenez/d2go/pkg/data"
|
||||||
@@ -755,6 +757,112 @@ func TestRule_Evaluate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: RuleResultNoMatch,
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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) {
|
func BenchmarkEvaluate(b *testing.B) {
|
||||||
it := data.Item{
|
it := data.Item{
|
||||||
ID: 0,
|
ID: 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user