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]+))`)
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
}