basic item filter wip
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
45
pkg/itemfilter/chain.go
Normal 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
|
||||||
|
}
|
||||||
64
pkg/itemfilter/chain_test.go
Normal file
64
pkg/itemfilter/chain_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
90
pkg/itemfilter/itemfilter.go
Normal file
90
pkg/itemfilter/itemfilter.go
Normal 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
17
pkg/itemfilter/mapping.go
Normal 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,
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package pickit
|
package nip
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package pickit
|
package nip
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|||||||
@@ -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
28
pkg/nip/properties.go
Normal 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"
|
||||||
|
)
|
||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user