basic item filter wip

This commit is contained in:
Héctor Giménez
2023-03-12 19:32:50 +09:00
parent 306150e873
commit 66dd506ba7
12 changed files with 273 additions and 26 deletions

View File

@@ -3,16 +3,20 @@ package item
type Quality int type Quality int
const ( const (
QualityNormal Quality = 0x02 QualityLowQuality Quality = 0x01
QualitySuperior Quality = 0x03 QualityNormal Quality = 0x02
QualityMagic Quality = 0x04 QualitySuperior Quality = 0x03
QualitySet Quality = 0x05 QualityMagic Quality = 0x04
QualityRare Quality = 0x06 QualitySet Quality = 0x05
QualityUnique Quality = 0x07 QualityRare Quality = 0x06
QualityUnique Quality = 0x07
QualityCrafted Quality = 0x08
) )
func (q Quality) ToString() string { func (q Quality) ToString() string {
switch q { switch q {
case QualityLowQuality:
return "LowQuality"
case QualityNormal: case QualityNormal:
return "Normal" return "Normal"
case QualitySuperior: case QualitySuperior:

View File

@@ -2,7 +2,7 @@ package item
import "strings" import "strings"
var classMapping = map[string][]string{ var typeMapping = map[string][]string{
"Axes": {"HandAxe", "Axe", "DoubleAxe", "MilitaryPick", "WarAxe", "LargeAxe", "BroadAxe", "BattleAxe", "GreatAxe", "GiantAxe", "Hatchet", "Cleaver", "TwinAxe", "Crowbill", "Naga", "MilitaryAxe", "BeardedAxe", "Tabar", "GothicAxe", "AncientAxe", "Tomahawk", "SmallCrescent", "EttinAxe", "WarSpike", "BerserkerAxe", "FeralAxe", "SilverEdgedAxe", "Decapitator", "ChampionAxe", "GloriousAxe"}, "Axes": {"HandAxe", "Axe", "DoubleAxe", "MilitaryPick", "WarAxe", "LargeAxe", "BroadAxe", "BattleAxe", "GreatAxe", "GiantAxe", "Hatchet", "Cleaver", "TwinAxe", "Crowbill", "Naga", "MilitaryAxe", "BeardedAxe", "Tabar", "GothicAxe", "AncientAxe", "Tomahawk", "SmallCrescent", "EttinAxe", "WarSpike", "BerserkerAxe", "FeralAxe", "SilverEdgedAxe", "Decapitator", "ChampionAxe", "GloriousAxe"},
"Wands": {"Wand", "YewWand", "BoneWand", "GrimWand", "BurntWand", "PetrifiedWand", "TombWand", "GraveWand", "PolishedWand", "GhostWand", "LichWand", "UnearthedWand"}, "Wands": {"Wand", "YewWand", "BoneWand", "GrimWand", "BurntWand", "PetrifiedWand", "TombWand", "GraveWand", "PolishedWand", "GhostWand", "LichWand", "UnearthedWand"},
"Clubs": {"Club", "SpikedClub", "Cudgel", "BarbedClub", "Truncheon", "TyrantClub"}, "Clubs": {"Club", "SpikedClub", "Cudgel", "BarbedClub", "Truncheon", "TyrantClub"},
@@ -37,21 +37,11 @@ var classMapping = map[string][]string{
"NecromancerShields": {"PreservedHead", "ZombieHead", "UnravellerHead", "GargoyleHead", "DemonHeadShield", "MummifiedTrophy", "FetishTrophy", "SextonTrophy", "CantorTrophy", "HierophantTrophy", "MinionSkull", "HellspawnSkull", "OverseerSkull", "SuccubusSkull", "BloodlordSkull"}, "NecromancerShields": {"PreservedHead", "ZombieHead", "UnravellerHead", "GargoyleHead", "DemonHeadShield", "MummifiedTrophy", "FetishTrophy", "SextonTrophy", "CantorTrophy", "HierophantTrophy", "MinionSkull", "HellspawnSkull", "OverseerSkull", "SuccubusSkull", "BloodlordSkull"},
} }
func IsClass(itemClass string) bool { func TypeForItemName(itemName string) (string, bool) {
for class := range classMapping { for t, itemNames := range typeMapping {
if strings.EqualFold(itemClass, class) {
return true
}
}
return false
}
func ClassForItemName(itemName string) (string, bool) {
for class, itemNames := range classMapping {
for _, name := range itemNames { for _, name := range itemNames {
if strings.EqualFold(itemName, name) { if strings.EqualFold(itemName, name) {
return class, true return t, true
} }
} }
} }

View File

@@ -1,9 +1,10 @@
package data package data
import ( import (
"strings"
"github.com/hectorgimenez/d2go/pkg/data/item" "github.com/hectorgimenez/d2go/pkg/data/item"
"github.com/hectorgimenez/d2go/pkg/data/stat" "github.com/hectorgimenez/d2go/pkg/data/stat"
"strings"
) )
type Items struct { type Items struct {
@@ -29,6 +30,12 @@ type Item struct {
IsVendor bool IsVendor bool
} }
func (i Item) Type() string {
t, _ := item.TypeForItemName(string(i.Name))
return t
}
func (i Item) IsPotion() bool { func (i Item) IsPotion() bool {
return i.IsHealingPotion() || i.IsManaPotion() || i.IsRejuvPotion() return i.IsHealingPotion() || i.IsManaPotion() || i.IsRejuvPotion()
} }

45
pkg/itemfilter/chain.go Normal file
View File

@@ -0,0 +1,45 @@
package itemfilter
import "github.com/hectorgimenez/d2go/pkg/nip"
type evaluationChain struct {
links []link
}
type link struct {
Result bool
Operand nip.Operand
}
func (ch *evaluationChain) Add(result bool, operand nip.Operand) {
ch.links = append(ch.links, link{
Result: result,
Operand: operand,
})
}
func (ch *evaluationChain) Evaluate() bool {
if len(ch.links) == 1 {
return ch.links[0].Result
}
result := ch.links[0].Result
operand := ch.links[0].Operand
for i, eval := range ch.links {
if i == 0 {
continue
}
if i < len(ch.links) {
switch operand {
case nip.OperandAnd:
result = result && eval.Result
case nip.OperandOr:
result = result || eval.Result
}
}
operand = eval.Operand
}
return result
}

View File

@@ -0,0 +1,64 @@
package itemfilter
import (
"testing"
"github.com/hectorgimenez/d2go/pkg/nip"
)
func Test_evaluationChain_Evaluate(t *testing.T) {
tests := []struct {
name string
links []link
want bool
}{
{
name: "Given one single link with true value should return true",
links: []link{
{true, nip.OperandNone},
},
want: true,
},
{
name: "Given one single link with false value should return false",
links: []link{
{false, nip.OperandNone},
},
want: false,
},
{
name: "Given different links using OR and one valid value should be true",
links: []link{
{false, nip.OperandOr},
{true, nip.OperandNone},
},
want: true,
},
{
name: "Given different links using OR and no valid value should be false",
links: []link{
{false, nip.OperandOr},
{false, nip.OperandNone},
},
want: false,
},
{
name: "Given different links using AND and one valid value should be false",
links: []link{
{true, nip.OperandAnd},
{false, nip.OperandNone},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ch := &evaluationChain{
links: tt.links,
}
if got := ch.Evaluate(); got != tt.want {
t.Errorf("Evaluate() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -0,0 +1,90 @@
package itemfilter
import (
"strings"
"github.com/hectorgimenez/d2go/pkg/data"
"github.com/hectorgimenez/d2go/pkg/nip"
)
func Evaluate(i data.Item, rules []nip.Rule) bool {
for _, r := range rules {
if !checkProperties(i, r.Properties) {
// Properties not matching, skipping
continue
}
// We can not check stats, item is not identified, but properties matching
if !i.Identified {
return true
}
}
return false
}
func checkProperties(i data.Item, properties []nip.Group) bool {
groupChain := evaluationChain{}
for _, propGroup := range properties {
propChain := evaluationChain{}
for _, prop := range propGroup.Comparable {
propChain.Add(checkProperty(i, prop), prop.Operand)
}
groupChain.Add(propChain.Evaluate(), propGroup.Operand)
}
return groupChain.Evaluate()
}
func checkProperty(i data.Item, prop nip.Comparable) bool {
switch prop.Keyword {
case nip.PropertyType:
return strings.EqualFold(i.Type(), prop.ValueString)
case nip.PropertyName:
return strings.EqualFold(string(i.Name), prop.ValueString)
case nip.PropertyClass:
// TODO: Implement
case nip.PropertyQuality:
quality, found := qualities[prop.ValueString]
if !found {
return false
}
return compare(int(i.Quality), int(quality), prop.Comparison)
case nip.PropertyFlag:
if prop.Comparison == nip.OperandEqual && !i.Ethereal {
return false
}
if prop.Comparison == nip.OperandNotEqualTo && i.Ethereal {
return false
}
case nip.PropertyLevel:
// TODO: Implement
case nip.PropertyPrefix:
// TODO: Implement
case nip.PropertySuffix:
// TODO: Implement
}
return true
}
func compare(val1, val2 int, operand nip.Operand) bool {
switch operand {
case nip.OperandEqual:
return val1 == val2
case nip.OperandGreaterThan:
return val1 > val2
case nip.OperandGreaterOrEqualTo:
return val1 >= val2
case nip.OperandLessThan:
return val1 < val2
case nip.OperandLessThanOrEqualTo:
return val1 <= val2
case nip.OperandNotEqualTo:
return val1 != val2
}
return false
}

17
pkg/itemfilter/mapping.go Normal file
View File

@@ -0,0 +1,17 @@
package itemfilter
import (
"github.com/hectorgimenez/d2go/pkg/data/item"
"github.com/hectorgimenez/d2go/pkg/nip"
)
var qualities = map[string]item.Quality{
nip.QualityLowQuality: item.QualityLowQuality,
nip.QualityNormal: item.QualityNormal,
nip.QualitySuperior: item.QualitySuperior,
nip.QualityMagic: item.QualityMagic,
nip.QualitySet: item.QualitySet,
nip.QualityRare: item.QualityRare,
nip.QualityUnique: item.QualityUnique,
nip.QualityCrafted: item.QualityCrafted,
}

View File

@@ -1,4 +1,4 @@
package pickit package nip
import ( import (
"bufio" "bufio"

View File

@@ -1,4 +1,4 @@
package pickit package nip
import ( import (
"errors" "errors"

View File

@@ -1,9 +1,10 @@
package pickit package nip
import ( import (
"testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"testing"
) )
const nipLine = "[type] == boots && [quality] == rare # [frw] >= 10 && [fireresist] >= 10 && ([lightresist]+[coldresist] >= 10 && [dexterity] >= 1 && [fireresist]+[poisonresist] >= 10) // this is a comment" const nipLine = "[type] == boots && [quality] == rare # [frw] >= 10 && [fireresist] >= 10 && ([lightresist]+[coldresist] >= 10 && [dexterity] >= 1 && [fireresist]+[poisonresist] >= 10) // this is a comment"

28
pkg/nip/properties.go Normal file
View File

@@ -0,0 +1,28 @@
package nip
const (
// Properties
PropertyType = "type"
PropertyName = "name"
PropertyClass = "class"
PropertyQuality = "quality"
PropertyFlag = "flag"
PropertyLevel = "level"
PropertyPrefix = "prefix"
PropertySuffix = "suffix"
// Types
TypeNormal = "normal"
TypeExceptional = "exceptional"
TypeElite = "elite"
// Quality
QualityLowQuality = "lowquality"
QualityNormal = "normal"
QualitySuperior = "superior"
QualityMagic = "magic"
QualitySet = "set"
QualityRare = "rare"
QualityUnique = "unique"
QualityCrafted = "crafted"
)

View File

@@ -1,4 +1,4 @@
package pickit package nip
const ( const (
OperandEqual Operand = "==" OperandEqual Operand = "=="
@@ -9,6 +9,7 @@ const (
OperandNotEqualTo Operand = "!=" OperandNotEqualTo Operand = "!="
OperandAnd Operand = "&&" OperandAnd Operand = "&&"
OperandOr Operand = "||" OperandOr Operand = "||"
OperandNone Operand = ""
) )
type Rule struct { type Rule struct {