improve 1

This commit is contained in:
vietdungdev
2026-05-25 11:20:22 +07:00
parent c4efbdfcf1
commit 1e3cd850d8

View File

@@ -25,6 +25,7 @@ var (
maxQtyRegexp = regexp.MustCompile(`(\[maxquantity]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`) maxQtyRegexp = regexp.MustCompile(`(\[maxquantity]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`)
tierRegexp = regexp.MustCompile(`(\[tier]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`) tierRegexp = regexp.MustCompile(`(\[tier]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`)
mercTierRegexp = regexp.MustCompile(`(\[merctier]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`) mercTierRegexp = regexp.MustCompile(`(\[merctier]\s*(<=|<|>|>=|!=|==)\s*([0-9]+))`)
whitespaceRegexp = regexp.MustCompile(`\s+`)
) )
type Rule struct { type Rule struct {
@@ -38,6 +39,10 @@ type Rule struct {
stage1 *vm.Program stage1 *vm.Program
stage2 *vm.Program stage2 *vm.Program
requiredStats []string requiredStats []string
skillTabSum bool
classSkillSum bool
resistSum bool
nonResistStat bool
} }
type RuleResult int type RuleResult int
@@ -188,7 +193,7 @@ func NewRule(rawRule string, filename string, lineNumber int) (Rule, error) {
mercTier: mercTier, mercTier: mercTier,
} }
parts := strings.Split(rule, "#") parts := strings.SplitN(rule, "#", 3)
if len(parts) > 0 { if len(parts) > 0 {
stage1 := strings.TrimSpace(parts[0]) stage1 := strings.TrimSpace(parts[0])
@@ -213,10 +218,24 @@ func NewRule(rawRule string, filename string, lineNumber int) (Rule, error) {
if stage2 != "" { if stage2 != "" {
// Extract stats before removing brackets for compilation // Extract stats before removing brackets for compilation
r.requiredStats = getRequiredStatsForRule(stage2) 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) statsMap := make(map[string]int)
for _, prop := range r.requiredStats { for _, prop := range r.requiredStats {
statsMap[prop] = 0 statsMap[prop] = 0
if !strings.Contains(prop, "resist") {
r.nonResistStat = true
}
} }
// Normalize whitespace around operators in parenthesized expressions // Normalize whitespace around operators in parenthesized expressions
@@ -247,7 +266,7 @@ func normalizeParenthesizedExpressions(expr string) string {
expr = strings.ReplaceAll(expr, "<=", " <= ") expr = strings.ReplaceAll(expr, "<=", " <= ")
// Fix extra spaces // Fix extra spaces
expr = regexp.MustCompile(`\s+`).ReplaceAllString(expr, " ") expr = whitespaceRegexp.ReplaceAllString(expr, " ")
// Normalize parentheses spacing // Normalize parentheses spacing
expr = strings.ReplaceAll(expr, "( ", "(") expr = strings.ReplaceAll(expr, "( ", "(")
@@ -258,56 +277,45 @@ func normalizeParenthesizedExpressions(expr string) string {
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) desc := it.Desc()
for prop := range fixedPropsList { /*
switch prop { // 0x400000 (eth) | 0x4000000 (runeword) kolbot style. Bit values are
case "type": // shared with the rewriter (replaceStringPropertiesInStage1) via the
stage1Props["type"] = it.Type().ID // package-level flagBit* constants so the rewriter's enumeration of
case "quality": // possible flag-states stays in lock-step with the values written
stage1Props["quality"] = int(it.Quality) // here — adding a third bit requires editing both this writer AND
case "class": // the rewriter, but the constants make the dependency discoverable.
stage1Props["class"] = int(it.Desc().Tier()) */
case "name": currentFlag := 0
stage1Props["name"] = it.ID if it.Ethereal {
case "flag": currentFlag |= flagBitEthereal
// 0x400000 (eth) | 0x4000000 (runeword) kolbot style. Bit values are }
// shared with the rewriter (replaceStringPropertiesInStage1) via the if it.IsRuneword {
// package-level flagBit* constants so the rewriter's enumeration of currentFlag |= flagBitRuneword
// possible flag-states stays in lock-step with the values written }
// here — adding a third bit requires editing both this writer AND stage1Props := map[string]int{
// the rewriter, but the constants make the dependency discoverable. "type": desc.GetType().ID,
currentFlag := 0 "quality": int(it.Quality),
"class": int(desc.Tier()),
if it.Ethereal { "name": it.ID,
currentFlag |= flagBitEthereal "flag": currentFlag,
} }
if it.IsRuneword { if it.Affixes.Rare.Prefix != 0 {
currentFlag |= flagBitRuneword stage1Props["prefix"] = int(it.Affixes.Rare.Prefix)
} }
for _, prefix := range it.Affixes.Magic.Prefixes {
stage1Props["flag"] = currentFlag if prefix != 0 {
case "prefix": stage1Props["prefix"] = int(prefix)
if it.Affixes.Rare.Prefix != 0 { break
stage1Props["prefix"] = int(it.Affixes.Rare.Prefix) }
} }
for _, prefix := range it.Affixes.Magic.Prefixes { if it.Affixes.Rare.Suffix != 0 {
if prefix != 0 { stage1Props["suffix"] = int(it.Affixes.Rare.Suffix)
stage1Props["prefix"] = int(prefix) }
break for _, suffix := range it.Affixes.Magic.Suffixes {
} if suffix != 0 {
} stage1Props["suffix"] = int(suffix)
case "suffix": 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
}
}
case "color":
// TODO: Not supported yet
} }
} }
@@ -337,51 +345,22 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
return RuleResultPartial, nil return RuleResultPartial, nil
} }
stage2Props := make(map[string]int) stage2Props := make(map[string]int, len(r.requiredStats))
stage2 := ""
if len(strings.Split(r.RawLine, "#")) > 1 {
stage2 = strings.ToLower(strings.Split(r.RawLine, "#")[1])
}
// Special handling for skill tabs // Special handling for skill tabs
if strings.Contains(stage2, "[itemaddskilltab]") { if r.skillTabSum {
stage2Props["itemaddskilltab"] = evaluateSkillTabSum(it) stage2Props["itemaddskilltab"] = evaluateSkillTabSum(it)
} }
// Special handling for class skills // Special handling for class skills
if strings.Contains(stage2, "[itemaddclassskills]") { if r.classSkillSum {
stage2Props["itemaddclassskills"] = evaluateClassSkillsSum(it) 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) // Handle resist sums - check if item has any resist stats (including negative values)
hasAnyResist := false hasAnyResist := false
// Detect if this is a rule with a resist sum expression if r.resistSum {
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 {
// Check if the item has any resist stats at all (including negative values like sunders) // Check if the item has any resist stats at all (including negative values like sunders)
for _, statName := range r.requiredStats { for _, statName := range r.requiredStats {
if !strings.Contains(statName, "resist") { if !strings.Contains(statName, "resist") {
@@ -474,7 +453,7 @@ func (r Rule) Evaluate(it data.Item) (RuleResult, error) {
isResistStat := strings.Contains(statName, "resist") isResistStat := strings.Contains(statName, "resist")
// Special case: Pure resist-sum rules (like sunders) should fail early if no resists exist // 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 // 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 // This is a pure resist rule and item has no resists - can't match
return RuleResultNoMatch, nil return RuleResultNoMatch, nil
} }