improve 1
This commit is contained in:
151
pkg/nip/rule.go
151
pkg/nip/rule.go
@@ -25,6 +25,7 @@ var (
|
||||
maxQtyRegexp = regexp.MustCompile(`(\[maxquantity]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`)
|
||||
tierRegexp = regexp.MustCompile(`(\[tier]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`)
|
||||
mercTierRegexp = regexp.MustCompile(`(\[merctier]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`)
|
||||
whitespaceRegexp = regexp.MustCompile(`\s+`)
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
@@ -38,6 +39,10 @@ type Rule struct {
|
||||
stage1 *vm.Program
|
||||
stage2 *vm.Program
|
||||
requiredStats []string
|
||||
skillTabSum bool
|
||||
classSkillSum bool
|
||||
resistSum bool
|
||||
nonResistStat bool
|
||||
}
|
||||
|
||||
type RuleResult int
|
||||
@@ -188,7 +193,7 @@ func NewRule(rawRule string, filename string, lineNumber int) (Rule, error) {
|
||||
mercTier: mercTier,
|
||||
}
|
||||
|
||||
parts := strings.Split(rule, "#")
|
||||
parts := strings.SplitN(rule, "#", 3)
|
||||
|
||||
if len(parts) > 0 {
|
||||
stage1 := strings.TrimSpace(parts[0])
|
||||
@@ -213,10 +218,24 @@ func NewRule(rawRule string, filename string, lineNumber int) (Rule, error) {
|
||||
if stage2 != "" {
|
||||
// Extract stats before removing brackets for compilation
|
||||
r.requiredStats = getRequiredStatsForRule(stage2)
|
||||
stage2Source := ""
|
||||
rawParts := strings.SplitN(rawRule, "#", 3)
|
||||
if len(rawParts) > 1 {
|
||||
stage2Source = strings.ToLower(rawParts[1])
|
||||
}
|
||||
r.skillTabSum = strings.Contains(stage2Source, "[itemaddskilltab]")
|
||||
r.classSkillSum = strings.Contains(stage2Source, "[itemaddclassskills]")
|
||||
if strings.Contains(stage2Source, "resist") {
|
||||
r.resistSum = strings.Contains(stage2Source, "+") || strings.Contains(stage2Source, "-") ||
|
||||
(strings.Contains(stage2Source, "(") && strings.Contains(stage2Source, ")"))
|
||||
}
|
||||
|
||||
statsMap := make(map[string]int)
|
||||
for _, prop := range r.requiredStats {
|
||||
statsMap[prop] = 0
|
||||
if !strings.Contains(prop, "resist") {
|
||||
r.nonResistStat = true
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize whitespace around operators in parenthesized expressions
|
||||
@@ -247,7 +266,7 @@ func normalizeParenthesizedExpressions(expr string) string {
|
||||
expr = strings.ReplaceAll(expr, "<=", " <= ")
|
||||
|
||||
// Fix extra spaces
|
||||
expr = regexp.MustCompile(`\s+`).ReplaceAllString(expr, " ")
|
||||
expr = whitespaceRegexp.ReplaceAllString(expr, " ")
|
||||
|
||||
// Normalize parentheses spacing
|
||||
expr = strings.ReplaceAll(expr, "( ", "(")
|
||||
@@ -258,56 +277,45 @@ func normalizeParenthesizedExpressions(expr string) string {
|
||||
|
||||
func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
|
||||
// Stage 1: Basic properties evaluation
|
||||
stage1Props := make(map[string]int)
|
||||
for prop := range fixedPropsList {
|
||||
switch prop {
|
||||
case "type":
|
||||
stage1Props["type"] = it.Type().ID
|
||||
case "quality":
|
||||
stage1Props["quality"] = int(it.Quality)
|
||||
case "class":
|
||||
stage1Props["class"] = int(it.Desc().Tier())
|
||||
case "name":
|
||||
stage1Props["name"] = it.ID
|
||||
case "flag":
|
||||
// 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
|
||||
|
||||
if it.Ethereal {
|
||||
currentFlag |= flagBitEthereal
|
||||
}
|
||||
if it.IsRuneword {
|
||||
currentFlag |= flagBitRuneword
|
||||
}
|
||||
|
||||
stage1Props["flag"] = currentFlag
|
||||
case "prefix":
|
||||
if it.Affixes.Rare.Prefix != 0 {
|
||||
stage1Props["prefix"] = int(it.Affixes.Rare.Prefix)
|
||||
}
|
||||
for _, prefix := range it.Affixes.Magic.Prefixes {
|
||||
if prefix != 0 {
|
||||
stage1Props["prefix"] = int(prefix)
|
||||
break
|
||||
}
|
||||
}
|
||||
case "suffix":
|
||||
if it.Affixes.Rare.Suffix != 0 {
|
||||
stage1Props["suffix"] = int(it.Affixes.Rare.Suffix)
|
||||
}
|
||||
for _, suffix := range it.Affixes.Magic.Suffixes {
|
||||
if suffix != 0 {
|
||||
stage1Props["suffix"] = int(suffix)
|
||||
break
|
||||
}
|
||||
}
|
||||
case "color":
|
||||
// TODO: Not supported yet
|
||||
desc := it.Desc()
|
||||
/*
|
||||
// 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
|
||||
if it.Ethereal {
|
||||
currentFlag |= flagBitEthereal
|
||||
}
|
||||
if it.IsRuneword {
|
||||
currentFlag |= flagBitRuneword
|
||||
}
|
||||
stage1Props := map[string]int{
|
||||
"type": desc.GetType().ID,
|
||||
"quality": int(it.Quality),
|
||||
"class": int(desc.Tier()),
|
||||
"name": it.ID,
|
||||
"flag": currentFlag,
|
||||
}
|
||||
if it.Affixes.Rare.Prefix != 0 {
|
||||
stage1Props["prefix"] = int(it.Affixes.Rare.Prefix)
|
||||
}
|
||||
for _, prefix := range it.Affixes.Magic.Prefixes {
|
||||
if prefix != 0 {
|
||||
stage1Props["prefix"] = int(prefix)
|
||||
break
|
||||
}
|
||||
}
|
||||
if it.Affixes.Rare.Suffix != 0 {
|
||||
stage1Props["suffix"] = int(it.Affixes.Rare.Suffix)
|
||||
}
|
||||
for _, suffix := range it.Affixes.Magic.Suffixes {
|
||||
if suffix != 0 {
|
||||
stage1Props["suffix"] = int(suffix)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,51 +345,22 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
|
||||
return RuleResultPartial, nil
|
||||
}
|
||||
|
||||
stage2Props := make(map[string]int)
|
||||
stage2 := ""
|
||||
if len(strings.Split(r.RawLine, "#")) > 1 {
|
||||
stage2 = strings.ToLower(strings.Split(r.RawLine, "#")[1])
|
||||
}
|
||||
stage2Props := make(map[string]int, len(r.requiredStats))
|
||||
|
||||
// Special handling for skill tabs
|
||||
if strings.Contains(stage2, "[itemaddskilltab]") {
|
||||
if r.skillTabSum {
|
||||
stage2Props["itemaddskilltab"] = evaluateSkillTabSum(it)
|
||||
}
|
||||
|
||||
// Special handling for class skills
|
||||
if strings.Contains(stage2, "[itemaddclassskills]") {
|
||||
if r.classSkillSum {
|
||||
stage2Props["itemaddclassskills"] = evaluateClassSkillsSum(it)
|
||||
}
|
||||
|
||||
// Preprocess stage2 to see if certain stats are being compared to zero
|
||||
zeroCheckStats := make(map[string]bool)
|
||||
for _, statName := range r.requiredStats {
|
||||
if strings.Contains(stage2, "["+statName+"] == 0") ||
|
||||
strings.Contains(stage2, "["+statName+"]==0") {
|
||||
zeroCheckStats[statName] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Handle resist sums - check if item has any resist stats (including negative values)
|
||||
hasAnyResist := false
|
||||
|
||||
// Detect if this is a rule with a resist sum expression
|
||||
isResistSum := false
|
||||
if strings.Contains(stage2, "resist") {
|
||||
isResistSum = strings.Contains(stage2, "+") || strings.Contains(stage2, "-") ||
|
||||
(strings.Contains(stage2, "(") && strings.Contains(stage2, ")"))
|
||||
}
|
||||
|
||||
// Check if rule ONLY checks resist sums (no other OR conditions)
|
||||
hasNonResistConditions := false
|
||||
for _, statName := range r.requiredStats {
|
||||
if !strings.Contains(statName, "resist") {
|
||||
hasNonResistConditions = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isResistSum {
|
||||
if r.resistSum {
|
||||
// Check if the item has any resist stats at all (including negative values like sunders)
|
||||
for _, statName := range r.requiredStats {
|
||||
if !strings.Contains(statName, "resist") {
|
||||
@@ -474,7 +453,7 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
|
||||
isResistStat := strings.Contains(statName, "resist")
|
||||
// Special case: Pure resist-sum rules (like sunders) should fail early if no resists exist
|
||||
// But mixed rules (resists OR other stats) should continue to check other conditions
|
||||
if isResistStat && isResistSum && !hasAnyResist && !hasNonResistConditions {
|
||||
if isResistStat && r.resistSum && !hasAnyResist && !r.nonResistStat {
|
||||
// This is a pure resist rule and item has no resists - can't match
|
||||
return RuleResultNoMatch, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user