Repair Broken rules ( white base items mostly) (#88)

* Repair Broken rules  ( white base items mostly)

* Removed double find stat check
This commit is contained in:
elb
2025-03-13 12:48:18 -04:00
committed by GitHub
parent 844439002c
commit b8765f634f

View File

@@ -61,7 +61,7 @@ func (r Rules) EvaluateAll(it data.Item) (Rule, RuleResult) {
return bestMatchingRule, bestMatch return bestMatchingRule, bestMatch
} }
var fixedPropsList = map[string]int{"type": 0, "quality": 0, "class": 0, "name": 0, "flag": 0, "color": 0, "prefix": 0, "suffix": 0, "levelreq": 0} var fixedPropsList = map[string]int{"type": 0, "quality": 0, "class": 0, "name": 0, "flag": 0, "color": 0, "prefix": 0, "suffix": 0}
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)
@@ -91,14 +91,12 @@ func NewRule(rawRule string, filename string, lineNumber int) (Rule, error) {
maxQuantity: maxQuantity, maxQuantity: maxQuantity,
} }
for i, stg := range strings.Split(rule, "#") { parts := strings.Split(rule, "#")
line := strings.TrimSpace(stg)
if line == "" {
break
}
if i == 0 { if len(parts) > 0 {
line, err := replaceStringPropertiesInStage1(line) stage1 := strings.TrimSpace(parts[0])
if stage1 != "" {
line, err := replaceStringPropertiesInStage1(stage1)
if err != nil { if err != nil {
return Rule{}, err return Rule{}, err
} }
@@ -107,34 +105,60 @@ func NewRule(rawRule string, filename string, lineNumber int) (Rule, error) {
line = strings.ReplaceAll(line, "]", "") line = strings.ReplaceAll(line, "]", "")
program, err := expr.Compile(line, expr.Env(fixedPropsList)) program, err := expr.Compile(line, expr.Env(fixedPropsList))
if err != nil { if err != nil {
return Rule{}, fmt.Errorf("error compiling rule: %w", err) return Rule{}, fmt.Errorf("error compiling rule stage1: %w", err)
} }
r.stage1 = program r.stage1 = program
} }
}
if i == 1 { if len(parts) > 1 {
r.requiredStats = getRequiredStatsForRule(line) stage2 := strings.TrimSpace(parts[1])
if stage2 != "" {
// Extract stats before removing brackets for compilation
r.requiredStats = getRequiredStatsForRule(stage2)
statsMap := make(map[string]int) statsMap := make(map[string]int)
for _, prop := range r.requiredStats { for _, prop := range r.requiredStats {
statsMap[prop] = 0 statsMap[prop] = 0
} }
line = strings.ReplaceAll(line, "[", "") // Normalize whitespace around operators in parenthesized expressions
line = strings.ReplaceAll(line, "]", "") stage2 = normalizeParenthesizedExpressions(stage2)
program, err := expr.Compile(line, expr.Env(statsMap))
// Remove brackets for compilation
compileReady := strings.ReplaceAll(stage2, "[", "")
compileReady = strings.ReplaceAll(compileReady, "]", "")
program, err := expr.Compile(compileReady, expr.Env(statsMap))
if err != nil { if err != nil {
return Rule{}, fmt.Errorf("error compiling rule: %w", err) return Rule{}, fmt.Errorf("error compiling rule stage2: %w, expression: %s", err, compileReady)
} }
r.stage2 = program r.stage2 = program
// We only care about first and second part, third one is maxquantity and was already parsed
break
} }
} }
return r, nil return r, nil
} }
func normalizeParenthesizedExpressions(expr string) string {
// Normalize common operators
expr = strings.ReplaceAll(expr, "||", " || ")
expr = strings.ReplaceAll(expr, "&&", " && ")
expr = strings.ReplaceAll(expr, "==", " == ")
expr = strings.ReplaceAll(expr, "!=", " != ")
expr = strings.ReplaceAll(expr, ">=", " >= ")
expr = strings.ReplaceAll(expr, "<=", " <= ")
// Fix extra spaces
expr = regexp.MustCompile(`\s+`).ReplaceAllString(expr, " ")
// Normalize parentheses spacing
expr = strings.ReplaceAll(expr, "( ", "(")
expr = strings.ReplaceAll(expr, " )", ")")
return expr
}
func (r Rule) Evaluate(it data.Item) (RuleResult, error) { func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
// Stage 1: Basic properties evaluation // Stage 1: Basic properties evaluation
stage1Props := make(map[string]int) stage1Props := make(map[string]int)
@@ -175,10 +199,15 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
} }
} }
// Check if stage1 exists before evaluating
if r.stage1 == nil {
return RuleResultNoMatch, fmt.Errorf("stage1 program is nil")
}
// Let's evaluate first stage // Let's evaluate first stage
stage1Result, err := expr.Run(r.stage1, stage1Props) stage1Result, err := expr.Run(r.stage1, stage1Props)
if err != nil { if err != nil {
return RuleResultNoMatch, fmt.Errorf("error evaluating rule: %w", err) return RuleResultNoMatch, fmt.Errorf("error evaluating rule stage1: %w", err)
} }
// If stage1 does not match, we can stop here, nothing else to match // If stage1 does not match, we can stop here, nothing else to match
@@ -197,15 +226,37 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
} }
stage2Props := make(map[string]int) stage2Props := make(map[string]int)
stage2 := ""
if len(strings.Split(r.RawLine, "#")) > 1 {
stage2 = strings.ToLower(strings.Split(r.RawLine, "#")[1])
}
// We only want to default to 0 if we find at least one of the required resists // 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
hasAnyResist := false hasAnyResist := false
stage2 := strings.ToLower(strings.Split(r.RawLine, "#")[1])
isResistSum := strings.Contains(stage2, "resist") && (strings.Contains(stage2, "+") || strings.Contains(stage2, "-"))
// First pass - check if we have any of the required resists // Detect if this is a rule with a resist sum expression
// We need to check both for direct addition/subtraction and parenthesized expressions
isResistSum := false
if strings.Contains(stage2, "resist") {
isResistSum = strings.Contains(stage2, "+") || strings.Contains(stage2, "-") ||
(strings.Contains(stage2, "(") && strings.Contains(stage2, ")"))
}
if isResistSum { if isResistSum {
// Check if the item has any resist stats at all
for _, statName := range r.requiredStats { for _, statName := range r.requiredStats {
if !strings.Contains(statName, "resist") {
continue
}
statData, found := statAliases[statName] statData, found := statAliases[statName]
if !found { if !found {
continue continue
@@ -214,14 +265,15 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
if len(statData) > 1 { if len(statData) > 1 {
layer = statData[1] layer = statData[1]
} }
if _, found := it.FindStat(stat.ID(statData[0]), layer); found {
if itemStat, found := it.FindStat(stat.ID(statData[0]), layer); found && itemStat.Value != 0 {
hasAnyResist = true hasAnyResist = true
break break
} }
} }
} }
// Second pass - evaluate stats // Evaluate each required stat
for _, statName := range r.requiredStats { for _, statName := range r.requiredStats {
statData, found := statAliases[statName] statData, found := statAliases[statName]
if !found { if !found {
@@ -233,20 +285,35 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
layer = statData[1] layer = statData[1]
} }
itemStat, found := it.FindStat(stat.ID(statData[0]), layer) // Use the FindStat method which handles both Stats and BaseStats
if found { statFound := false
stage2Props[statName] = itemStat.Value var statValue int
} else if isResistSum && hasAnyResist {
// Only default to 0 for resist sums if we found at least one resist if itemStat, found := it.FindStat(stat.ID(statData[0]), layer); found {
statValue = itemStat.Value
statFound = true
}
// Special handling for stats not found
if !statFound {
// Check if this is a resist stat
isResistStat := strings.Contains(statName, "resist")
// For resist stats in a sum expression when no resists are present, don't match
if isResistStat && isResistSum && !hasAnyResist {
return RuleResultNoMatch, nil
}
// For all other stats, default to 0 to allow proper evaluation of OR conditions
stage2Props[statName] = 0 stage2Props[statName] = 0
} else { } else {
return RuleResultNoMatch, nil // Stat was found, use its value
stage2Props[statName] = statValue
} }
} }
res, err := expr.Run(r.stage2, stage2Props) res, err := expr.Run(r.stage2, stage2Props)
if err != nil { if err != nil {
return RuleResultNoMatch, fmt.Errorf("error evaluating rule: %w", err) return RuleResultNoMatch, fmt.Errorf("error evaluating rule stage2: %w", err)
} }
// 100% rule match, we can return here // 100% rule match, we can return here
@@ -273,7 +340,7 @@ func replaceStringPropertiesInStage1(stage1 string) (string, error) {
case "flag": case "flag":
replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", 1)) replaceWith = strings.ReplaceAll(prop[0], prop[4], fmt.Sprintf("%d", 1))
case "prefix", "suffix": case "prefix", "suffix":
// Handle prefix/suffix IDs and levelreq // Handle prefix/suffix IDs
replaceWith = strings.ReplaceAll(prop[0], prop[4], prop[4]) replaceWith = strings.ReplaceAll(prop[0], prop[4], prop[4])
case "color": case "color":
// TODO: Not supported yet // TODO: Not supported yet
@@ -290,8 +357,13 @@ func replaceStringPropertiesInStage1(stage1 string) (string, error) {
func getRequiredStatsForRule(line string) []string { func getRequiredStatsForRule(line string) []string {
statsList := make([]string, 0) statsList := make([]string, 0)
statsFound := make(map[string]bool)
for _, statName := range statsRegexp.FindAllStringSubmatch(line, -1) { for _, statName := range statsRegexp.FindAllStringSubmatch(line, -1) {
statsList = append(statsList, statName[1]) if !statsFound[statName[1]] {
statsList = append(statsList, statName[1])
statsFound[statName[1]] = true
}
} }
return statsList return statsList
} }