1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-13 19:20:43 +00:00

Add initial calculation string parser (#706)

* Add objgroup.txt loader

* Add parser

* Add parser

* Add tests
This commit is contained in:
AndrejMijic 2020-08-17 03:56:28 +02:00 committed by GitHub
parent dccb930f5c
commit 2ceba68c73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1502 additions and 255 deletions

View File

@ -0,0 +1,105 @@
// Package d2calculation contains code for calculation nodes.
package d2calculation
import (
"fmt"
"strconv"
)
// Calculation is the interface of every evaluatable calculation.
type Calculation interface {
fmt.Stringer
Eval() int
}
// BinaryCalculation is a calculation with a binary function or operator.
type BinaryCalculation struct {
// Left is the left operand.
Left Calculation
// Right is the right operand.
Right Calculation
// Op is the actual operation.
Op func(v1, v2 int) int
}
// Eval evaluates the calculation.
func (node *BinaryCalculation) Eval() int {
return node.Op(node.Left.Eval(), node.Right.Eval())
}
func (node *BinaryCalculation) String() string {
return "Binary(" + node.Left.String() + "," + node.Right.String() + ")"
}
// UnaryCalculation is a calculation with a unary function or operator.
type UnaryCalculation struct {
// Child is the operand.
Child Calculation
// Op is the operation.
Op func(v int) int
}
// Eval evaluates the calculation.
func (node *UnaryCalculation) Eval() int {
return node.Op(node.Child.Eval())
}
func (node *UnaryCalculation) String() string {
return "Unary(" + node.Child.String() + ")"
}
// TernaryCalculation is a calculation with a ternary function or operator.
type TernaryCalculation struct {
// Left is the left operand.
Left Calculation
// Middle is the middle operand.
Middle Calculation
// Right is the right operand.
Right Calculation
Op func(v1, v2, v3 int) int
}
// Eval evaluates the calculation.
func (node *TernaryCalculation) Eval() int {
return node.Op(node.Left.Eval(), node.Middle.Eval(), node.Right.Eval())
}
func (node *TernaryCalculation) String() string {
return "Ternary(" + node.Left.String() + "," + node.Middle.String() + "," + node.Right.String() + ")"
}
// PropertyReferenceCalculation is the calculation representing a property.
type PropertyReferenceCalculation struct {
Type string
Name string
Qualifier string
}
// Eval evaluates the calculation.
func (node *PropertyReferenceCalculation) Eval() int {
return 1
}
func (node *PropertyReferenceCalculation) String() string {
return "Property(" + node.Type + "," + node.Name + "," + node.Qualifier + ")"
}
// ConstantCalculation is a constant value.
type ConstantCalculation struct {
// Value is the constant value.
Value int
}
// Eval evaluates the calculation.
func (node *ConstantCalculation) Eval() int {
return node.Value
}
func (node *ConstantCalculation) String() string {
return strconv.Itoa(node.Value)
}

View File

@ -0,0 +1,188 @@
// Package d2lexer contains the code for tokenizing calculation strings.
package d2lexer
import (
"errors"
"strconv"
"strings"
"unicode"
)
type tokenType int
const (
// Name represents a name token, such as skill, par1 etc.
Name tokenType = iota
// String represents a quoted string token, such as "Sacrifice".
String
// Symbol represents a symbol token, such as '+', '-', '?, '.' etc.
Symbol
// Number represents an integer token.
Number
// EOF is the end-of-file token, generated when the end of data is reached.
EOF
)
func (t tokenType) String() string {
return []string{
"Name",
"String",
"Symbol",
"Number",
"EOF",
}[t]
}
// Token is a lexical token of a calculation string.
type Token struct {
Type tokenType
Value string
}
func (t *Token) String() string {
return "(" + t.Type.String() + ", " + t.Value + ")\n"
}
// Lexer is the tokenizer for calculation strings.
type Lexer struct {
data []byte
CurrentToken Token
index int
peeked bool
nextToken Token
}
// New creates a new Lexer for tokenizing the given data.
func New(input []byte) *Lexer {
return &Lexer{
data: input,
}
}
func (l *Lexer) peekNext() (byte, error) {
if l.index+1 >= len(l.data) {
return 0, errors.New("cannot peek")
}
return l.data[l.index+1], nil
}
func (l *Lexer) extractOpToken() Token {
c := l.data[l.index]
if c == '=' || c == '!' {
next, ok := l.peekNext()
if ok != nil || next != '=' {
panic("Invalid operator at index!" + strconv.Itoa(l.index))
} else {
l.index += 2
return Token{Symbol, string(c) + "="}
}
}
if c == '<' || c == '>' {
next, ok := l.peekNext()
if ok == nil && next == '=' {
l.index += 2
return Token{Symbol, string(c) + "="}
}
l.index++
return Token{Symbol, string(c)}
}
l.index++
return Token{Symbol, string(c)}
}
func (l *Lexer) extractNumber() Token {
var sb strings.Builder
for l.index < len(l.data) && unicode.IsDigit(rune(l.data[l.index])) {
sb.WriteByte(l.data[l.index])
l.index++
}
return Token{Number, sb.String()}
}
func (l *Lexer) extractString() Token {
var sb strings.Builder
l.index++
for l.index < len(l.data) && l.data[l.index] != '\'' {
sb.WriteByte(l.data[l.index])
l.index++
}
l.index++
return Token{String, sb.String()}
}
func (l *Lexer) extractName() Token {
var sb strings.Builder
for l.index < len(l.data) &&
(unicode.IsLetter(rune(l.data[l.index])) ||
unicode.IsDigit(rune(l.data[l.index]))) {
sb.WriteByte(l.data[l.index])
l.index++
}
return Token{Name, sb.String()}
}
// Peek returns the next token, but does not advance the tokenizer.
// The peeked token is cached until the tokenizer advances.
func (l *Lexer) Peek() Token {
if l.peeked {
return l.nextToken
}
if l.index == len(l.data) {
l.nextToken = Token{EOF, ""}
return l.nextToken
}
for l.index < len(l.data) && unicode.IsSpace(rune(l.data[l.index])) {
l.index++
}
if l.index == len(l.data) {
l.nextToken = Token{EOF, ""}
return l.nextToken
}
switch {
case strings.IndexByte("^=!><+-/*.,:?()", l.data[l.index]) != -1:
l.nextToken = l.extractOpToken()
case unicode.IsDigit(rune(l.data[l.index])):
l.nextToken = l.extractNumber()
case l.data[l.index] == '\'':
l.nextToken = l.extractString()
case unicode.IsLetter(rune(l.data[l.index])):
l.nextToken = l.extractName()
default:
panic("Invalid token at index: " + strconv.Itoa(l.index))
}
l.peeked = true
return l.nextToken
}
// NextToken returns the next token and advances the tokenizer.
func (l *Lexer) NextToken() Token {
if l.peeked {
l.CurrentToken = l.nextToken
} else {
l.CurrentToken = l.Peek()
}
l.peeked = false
return l.CurrentToken
}

View File

@ -0,0 +1,166 @@
package d2lexer
import (
"testing"
)
func TestName(t *testing.T) {
lexer := New([]byte("correct horse battery staple andromeda13142 n1n2n4"))
expected := []Token{
{Name, "correct"},
{Name, "horse"},
{Name, "battery"},
{Name, "staple"},
{Name, "andromeda13142"},
{Name, "n1n2n4"},
}
for _, want := range expected {
got := lexer.NextToken()
if got.Type != Name || got.Value != want.Value {
t.Errorf("Got: %v, want %v", got, want)
}
}
eof := lexer.NextToken()
if eof.Type != EOF {
t.Errorf("Did not reach EOF")
}
}
func TestNumber(t *testing.T) {
lexer := New([]byte("12 2325 53252 312 3411"))
expected := []Token{
{Number, "12"},
{Number, "2325"},
{Number, "53252"},
{Number, "312"},
{Number, "3411"},
}
for _, want := range expected {
got := lexer.NextToken()
if got.Type != Number || got.Value != want.Value {
t.Errorf("Got: %v, want %v", got, want)
}
}
eof := lexer.NextToken()
if eof.Type != EOF {
t.Errorf("Did not reach EOF")
}
}
func TestSymbol(t *testing.T) {
lexer := New([]byte("((+-==>>>=!=<=<=<*//*)?(::.,.:?"))
expected := []Token{
{Symbol, "("},
{Symbol, "("},
{Symbol, "+"},
{Symbol, "-"},
{Symbol, "=="},
{Symbol, ">"},
{Symbol, ">"},
{Symbol, ">="},
{Symbol, "!="},
{Symbol, "<="},
{Symbol, "<="},
{Symbol, "<"},
{Symbol, "*"},
{Symbol, "/"},
{Symbol, "/"},
{Symbol, "*"},
{Symbol, ")"},
{Symbol, "?"},
{Symbol, "("},
{Symbol, ":"},
{Symbol, ":"},
{Symbol, "."},
{Symbol, ","},
{Symbol, "."},
{Symbol, ":"},
{Symbol, "?"},
}
for _, want := range expected {
got := lexer.NextToken()
if got.Type != Symbol || got.Value != want.Value {
t.Errorf("Got: %v, want %v", got, want)
}
}
eof := lexer.NextToken()
if eof.Type != EOF {
t.Errorf("Did not reach EOF")
}
}
func TestString(t *testing.T) {
lexer := New([]byte(`correct 'horse' 'battery staple' 'andromeda13142 ' n1n2n4`))
expected := []Token{
{Name, "correct"},
{String, "horse"},
{String, "battery staple"},
{String, "andromeda13142 "},
{Name, "n1n2n4"},
}
for _, want := range expected {
got := lexer.NextToken()
if got.Type != want.Type || got.Value != want.Value {
t.Errorf("Got: %v, want %v", got, want)
}
}
eof := lexer.NextToken()
if eof.Type != EOF {
t.Errorf("Did not reach EOF")
}
}
func TestActualConstructions(t *testing.T) {
lexer := New([]byte("skill('Sacrifice'.blvl) > 3 ? min(50, lvl) : skill('Sacrifice'.lvl) * ln12"))
expected := []Token{
{Name, "skill"},
{Symbol, "("},
{String, "Sacrifice"},
{Symbol, "."},
{Name, "blvl"},
{Symbol, ")"},
{Symbol, ">"},
{Number, "3"},
{Symbol, "?"},
{Name, "min"},
{Symbol, "("},
{Number, "50"},
{Symbol, ","},
{Name, "lvl"},
{Symbol, ")"},
{Symbol, ":"},
{Name, "skill"},
{Symbol, "("},
{String, "Sacrifice"},
{Symbol, "."},
{Name, "lvl"},
{Symbol, ")"},
{Symbol, "*"},
{Name, "ln12"},
}
for _, want := range expected {
got := lexer.NextToken()
if got.Type != want.Type || got.Value != want.Value {
t.Errorf("Got: %v, want %v", got, want)
}
}
eof := lexer.NextToken()
if eof.Type != EOF {
t.Errorf("Did not reach EOF")
}
}

View File

@ -0,0 +1,2 @@
// Package d2parser contains the code for parsing calculation strings.
package d2parser

View File

@ -0,0 +1,197 @@
package d2parser
import (
"math"
"math/rand"
)
type binaryOperation struct {
Operator string
Precedence int
IsRightAssociated bool
Function func(v1, v2 int) int
}
type unaryOperation struct {
Operator string
Precedence int
Function func(v int) int
}
type ternaryOperation struct {
Operator string
Marker string
Precedence int
IsRightAssociated bool
Function func(v1, v2, v3 int) int
}
func getUnaryOperations() map[string]unaryOperation {
return map[string]unaryOperation{
"+": {
"+",
4,
func(v int) int {
return v
},
},
"-": {
"-",
4,
func(v int) int {
return -v
},
},
}
}
func getTernaryOperations() map[string]ternaryOperation {
return map[string]ternaryOperation{
"?": {
"?",
":",
0,
true,
func(v1, v2, v3 int) int {
if v1 != 0 {
return v2
}
return v3
},
},
}
}
func getBinaryOperations() map[string]binaryOperation { //nolint:funlen // No reason to split function, just creates the operations.
return map[string]binaryOperation{
"==": {
"==",
1,
false,
func(v1, v2 int) int {
if v1 == v2 {
return 1
}
return 0
},
},
"!=": {
"!=",
1,
false,
func(v1, v2 int) int {
if v1 != v2 {
return 1
}
return 0
},
},
"<": {
"<",
2,
false,
func(v1, v2 int) int {
if v1 < v2 {
return 1
}
return 0
},
},
">": {
">",
2,
false,
func(v1, v2 int) int {
if v1 > v2 {
return 1
}
return 0
},
},
"<=": {
"<=",
2,
false,
func(v1, v2 int) int {
if v1 <= v2 {
return 1
}
return 0
},
},
">=": {
">=",
2,
false,
func(v1, v2 int) int {
if v1 >= v2 {
return 1
}
return 0
},
},
"+": {
"+",
3,
false,
func(v1, v2 int) int {
return v1 + v2
},
},
"-": {
"-",
3,
false,
func(v1, v2 int) int {
return v1 - v2
},
},
"*": {
"*",
5,
false,
func(v1, v2 int) int {
return v1 * v2
},
},
"/": {
"/",
5,
false,
func(v1, v2 int) int {
return v1 / v2
},
},
"^": {
"^",
6,
true,
func(v1, v2 int) int {
return int(math.Pow(float64(v1), float64(v2)))
},
},
}
}
func getFunctions() map[string]func(v1, v2 int) int {
return map[string]func(v1, v2 int) int{
"min": func(v1, v2 int) int {
if v1 < v2 {
return v1
}
return v2
},
"max": func(v1, v2 int) int {
if v1 > v2 {
return v1
}
return v2
},
"rand": func(v1, v2 int) int {
if rand.Int()%2 == 0 { //nolint:gosec // Secure random not necessary.
return v1
}
return v2
},
}
}

View File

@ -0,0 +1,312 @@
package d2parser
import (
"log"
"strconv"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation/d2lexer"
)
// Parser is a parser for calculations used for skill and missiles.
type Parser struct {
lex *d2lexer.Lexer
binaryOperations map[string]binaryOperation
unaryOperations map[string]unaryOperation
ternaryOperations map[string]ternaryOperation
fixedFunctions map[string]func(v1, v2 int) int
currentType string
currentName string
}
// New creates a new parser.
func New() *Parser {
return &Parser{
binaryOperations: getBinaryOperations(),
unaryOperations: getUnaryOperations(),
ternaryOperations: getTernaryOperations(),
fixedFunctions: getFunctions(),
}
}
// SetCurrentReference sets the current reference type and name, such as "skill" and skill name.
func (parser *Parser) SetCurrentReference(propType, propName string) {
parser.currentType = propType
parser.currentName = propName
}
// Parse parses the calculation string and creates a Calculation tree.
func (parser *Parser) Parse(calc string) d2calculation.Calculation {
calc = strings.TrimSpace(calc)
if calc == "" {
return &d2calculation.ConstantCalculation{Value: 0}
}
defer func() {
if r := recover(); r != nil {
log.Printf("Error parsing calculation: %v", calc)
}
}()
parser.lex = d2lexer.New([]byte(calc))
return parser.parseLevel(0)
}
func (parser *Parser) peek() d2lexer.Token {
return parser.lex.Peek()
}
func (parser *Parser) consume() d2lexer.Token {
return parser.lex.NextToken()
}
func (parser *Parser) parseLevel(level int) d2calculation.Calculation {
node := parser.parseProduction()
t := parser.peek()
if t.Type == d2lexer.EOF {
return node
}
for {
if t.Type != d2lexer.Symbol {
break
}
op, ok := parser.binaryOperations[t.Value]
if !ok || op.Precedence < level {
break
}
parser.consume()
var nextLevel int
if op.IsRightAssociated {
nextLevel = op.Precedence
} else {
nextLevel = op.Precedence + 1
}
otherCalculation := parser.parseLevel(nextLevel)
node = &d2calculation.BinaryCalculation{
Left: node,
Right: otherCalculation,
Op: op.Function,
}
t = parser.peek()
}
for {
if t.Type != d2lexer.Symbol {
break
}
op, ok := parser.ternaryOperations[t.Value]
if !ok || op.Precedence < level {
break
}
parser.consume()
var nextLevel int
if op.IsRightAssociated {
nextLevel = op.Precedence
} else {
nextLevel = op.Precedence + 1
}
middleCalculation := parser.parseLevel(nextLevel)
t = parser.peek()
if t.Type != d2lexer.Symbol || t.Value != op.Marker {
panic("Invalid ternary! " + t.Value + ", expected: " + op.Marker)
}
parser.consume()
rightCalculation := parser.parseLevel(nextLevel)
node = &d2calculation.TernaryCalculation{
Left: node,
Middle: middleCalculation,
Right: rightCalculation,
Op: op.Function,
}
t = parser.peek()
}
return node
}
func (parser *Parser) parseProduction() d2calculation.Calculation {
t := parser.peek()
switch {
case t.Type == d2lexer.Symbol:
if t.Value == "(" {
parser.consume()
node := parser.parseLevel(0)
t = parser.peek()
if t.Type != d2lexer.Symbol ||
t.Value != ")" {
if t.Type == d2lexer.EOF { // Ignore unclosed final parenthesis due to syntax error in original Fire Wall calculation.
return node
}
panic("Parenthesis not closed!")
}
parser.consume()
return node
}
op, ok := parser.unaryOperations[t.Value]
if !ok {
panic("Invalid unary symbol: " + t.Value)
}
parser.consume()
node := parser.parseLevel(op.Precedence)
return &d2calculation.UnaryCalculation{
Child: node,
Op: op.Function,
}
case t.Type == d2lexer.Name || t.Type == d2lexer.Number:
return parser.parseLeafCalculation()
default:
panic("Expected parenthesis, unary operator, function or value!")
}
}
func (parser *Parser) parseLeafCalculation() d2calculation.Calculation {
t := parser.peek()
if t.Type == d2lexer.Number {
val, err := strconv.Atoi(t.Value)
if err != nil {
panic("Invalid number: " + t.Value)
}
parser.consume()
return &d2calculation.ConstantCalculation{Value: val}
}
if t.Value == "skill" ||
t.Value == "miss" ||
t.Value == "stat" {
return parser.parseProperty()
}
if parser.fixedFunctions[t.Value] != nil {
return parser.parseFunction(t.Value)
}
if t.Type == d2lexer.Name {
parser.consume()
return &d2calculation.PropertyReferenceCalculation{
Type: parser.currentType,
Name: parser.currentName,
Qualifier: t.Value,
}
}
panic(t.Value + " is not a function, property, or number!")
}
func (parser *Parser) parseFunction(name string) d2calculation.Calculation {
function := parser.fixedFunctions[name]
parser.consume()
t := parser.peek()
if t.Value != "(" {
panic("Invalid function!")
}
parser.consume()
firstParam := parser.parseLevel(0)
t = parser.peek()
if t.Type != d2lexer.Symbol || t.Value != "," {
panic("Invalid function!")
}
parser.consume()
secondParam := parser.parseLevel(0)
t = parser.peek()
if t.Value != ")" {
panic("Invalid function!")
}
parser.consume()
return &d2calculation.BinaryCalculation{
Left: firstParam,
Right: secondParam,
Op: function,
}
}
func (parser *Parser) parseProperty() d2calculation.Calculation {
t := parser.peek()
propType := t.Value
t = parser.consume()
t = parser.peek()
if t.Value != "(" {
panic("Invalid property: " + propType + ", open parenthesis missing.")
}
parser.consume()
t = parser.peek()
if t.Type != d2lexer.String {
panic("Property name must be in quotes: " + propType)
}
propName := t.Value
parser.consume()
t = parser.peek()
if t.Type != d2lexer.Symbol || t.Value != "." {
panic("Property name must be followed by dot: " + propType)
}
parser.consume()
t = parser.peek()
if t.Type != d2lexer.Name {
panic("Invalid propery qualifier: " + propType)
}
propQual := t.Value
parser.consume()
t = parser.peek()
if t.Value != ")" {
panic("Invalid property: " + propType + ", closed parenthesis missing.")
}
parser.consume()
return &d2calculation.PropertyReferenceCalculation{
Type: propType,
Name: propName,
Qualifier: propQual,
}
}

View File

@ -0,0 +1,295 @@
package d2parser
import (
"math"
"math/rand"
"testing"
)
func TestEmptyInput(t *testing.T) {
parser := New()
table := []struct {
expr string
result int
}{
{"", 0},
{" ", 0},
{"\t\t \t\t \t", 0},
}
for _, row := range table {
c := parser.Parse(row.expr)
res := c.Eval()
if res != row.result {
t.Errorf("Expression %v gave wrong result, got %d, want %d", row.expr, res, row.result)
}
}
}
func TestConstantExpression(t *testing.T) {
parser := New()
table := []struct {
expr string
result int
}{
{"0", 0},
{"5", 5},
{"455", 455},
{"789", 789},
{"3242", 3242},
{"45454", 45454},
}
for _, row := range table {
c := parser.Parse(row.expr)
res := c.Eval()
if res != row.result {
t.Errorf("Expression %v gave wrong result, got %d, want %d", row.expr, res, row.result)
}
}
}
func TestUnaryOperations(t *testing.T) {
parser := New()
table := []struct {
expr string
result int
}{
{"+0", 0},
{"-0", 0},
{"+455", 455},
{"++455", 455},
{"+++455", 455},
{"-455", -455},
{"--455", 455},
{"---455", -455},
{"+-+789", -789},
{"-++3242", -3242},
{"++--+-+45454", -45454},
}
for _, row := range table {
c := parser.Parse(row.expr)
res := c.Eval()
if res != row.result {
t.Errorf("Expression %v gave wrong result, got %d, want %d", row.expr, res, row.result)
}
}
}
func TestArithmeticBinaryOperations(t *testing.T) {
parser := New()
table := []struct {
expr string
result int
}{
{"1 + 2", 3},
{"54+56", 54 + 56},
{"9212-2121", 9212 - 2121},
{"1+2-5", -2},
{"5-3-1", 1},
{"4*5", 20},
{"512/2", 256},
{"10/9", 1},
{"1/3*5", 0},
{"2^3^2", int(math.Pow(2., 9.))},
{"4^2*2+1", 33},
}
for _, row := range table {
c := parser.Parse(row.expr)
res := c.Eval()
if res != row.result {
t.Errorf("Expression %v gave wrong result, got %d, want %d", row.expr, res, row.result)
}
}
}
func TestParentheses(t *testing.T) {
parser := New()
table := []struct {
expr string
result int
}{
{"(1+2)*5", 15},
{"(99-98)/2", 0},
{"((3+2)*6)/3", 10},
{"(3+2)*(6/3)", 10},
{"(20+10)/6/5", 1},
{"(20+10)/(6/5)", 30},
}
for _, row := range table {
c := parser.Parse(row.expr)
res := c.Eval()
if res != row.result {
t.Errorf("Expression %v gave wrong result, got %d, want %d", row.expr, res, row.result)
}
}
}
func TestLackFinalParethesis(t *testing.T) {
parser := New()
table := []struct {
expr string
result int
}{
{"(3+2)*(6/3", 10},
{"(20+10)/(6/5", 30},
}
for _, row := range table {
c := parser.Parse(row.expr)
res := c.Eval()
if res != row.result {
t.Errorf("Expression %v gave wrong result, got %d, want %d", row.expr, res, row.result)
}
}
}
func TestLogicalBinaryOperations(t *testing.T) {
parser := New()
table := []struct {
expr string
result bool
}{
{"1 < 2", true},
{"1 < -5", false},
{"1 <= 5", true},
{"1 <= 10", true},
{"5 <= 1", false},
{"1 <= 1", true},
{"5 > 10", false},
{"54 >= 100", false},
{"45 >= 45", true},
{"10 == 10", true},
{"10 == 1", false},
{"10 != 1", true},
{"10 != 10", false},
}
for _, row := range table {
c := parser.Parse(row.expr)
res := c.Eval()
if (res == 0 && row.result) || (res != 0 && !row.result) {
t.Errorf("Expression %v gave wrong result, got %d, want %v", row.expr, res, row.result)
}
}
}
func TestLogicalAndArithmetic(t *testing.T) {
parser := New()
table := []struct {
expr string
result bool
}{
{"(1 < 2)*(5 < 10)", true},
{"(1 < -5)+(1 == 5)", false},
{"(5 > 10)*(10 == 10)", false},
{"(45 >= 45)+(1 > 500000)", true},
{"(10 == 10)*(30 > 50)+(5 >= 5)", true},
}
for _, row := range table {
c := parser.Parse(row.expr)
res := c.Eval()
if (res == 0 && row.result) || (res != 0 && !row.result) {
t.Errorf("Expression %v gave wrong result, got %d, want %v", row.expr, res, row.result)
}
}
}
func TestTernaryOperator(t *testing.T) {
parser := New()
table := []struct {
expr string
result int
}{
{"5 > 1 ? 3 : 5", 3},
{"5 <= 1 ? 3 : 5", 5},
{"(1 < 10)*(5 < 3) ? 43 : 5 == 5 ? 1 : 2", 1},
{"(1 < 10)*(5 < 3) ? 43 : 5 != 5 ? 1 : 2", 2},
{"(1 < 10)*(5 > 3) ? 43 : 5 == 5 ? 1 : 2", 43},
{"(1 < 10)*(5 > 3) ? 43 != 0 ? 65 : 32 : 5 == 5 ? 1 : 2", 65},
{"(1 < 10)*(5 > 3) ? 43 == 0 ? 65 : 32 : 5 == 5 ? 1 : 2", 32},
}
for _, row := range table {
c := parser.Parse(row.expr)
res := c.Eval()
if res != row.result {
t.Errorf("Expression %v gave wrong result, got %d, want %d", row.expr, res, row.result)
}
}
}
func TestBuiltinFunctions(t *testing.T) {
parser := New()
table := []struct {
expr string
result int
}{
{"min(5, 2)", 2},
{"min(4^6, 5+10)", 15},
{"max(10, 4*3)", 12},
{"max(50-2, 50-3)", 48},
}
for _, row := range table {
c := parser.Parse(row.expr)
res := c.Eval()
if res != row.result {
t.Errorf("Expression %v gave wrong result, got %d, want %d", row.expr, res, row.result)
}
}
}
func TestRandFunction(t *testing.T) {
parser := New()
c := parser.Parse("rand(1,5)")
rand.Seed(1)
res1 := []int{c.Eval(), c.Eval(), c.Eval(), c.Eval(), c.Eval()}
rand.Seed(1)
res2 := []int{c.Eval(), c.Eval(), c.Eval(), c.Eval(), c.Eval()}
for i := 0; i < len(res1); i++ {
t.Logf("%d, %d", res1[i], res2[i])
if res1[i] != res2[i] {
t.Error("Results not equal.")
}
}
}
func BenchmarkSimpleExpression(b *testing.B) {
parser := New()
expr := "(1 < 10)*(5 > 3) ? 43 == 0 ? 65 : 32 : 5 == 5 ? 1 : 2"
for n := 0; n < b.N; n++ {
parser.Parse(expr)
}
}

View File

@ -4,6 +4,8 @@ import (
"log" "log"
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation/d2parser"
) )
// SkillDescriptionRecord is a single row from skilldesc.txt and is used for // SkillDescriptionRecord is a single row from skilldesc.txt and is used for
@ -22,17 +24,17 @@ type SkillDescriptionRecord struct {
AltKey string // str alt AltKey string // str alt
ManaKey string // str mana ManaKey string // str mana
Descdam string // descdam Descdam string // descdam
DdamCalc1 string // ddam calc1 DdamCalc1 d2calculation.Calculation // ddam calc1
DdamCalc2 string // ddam calc2 DdamCalc2 d2calculation.Calculation // ddam calc2
P1dmelem string // p1dmelem P1dmelem string // p1dmelem
P1dmmin string // p1dmmin P1dmmin d2calculation.Calculation // p1dmmin
P1dmmax string // p1dmmax P1dmmax d2calculation.Calculation // p1dmmax
P2dmelem string // p2dmelem P2dmelem string // p2dmelem
P2dmmin string // p2dmmin P2dmmin d2calculation.Calculation // p2dmmin
P2dmmax string // p2dmmax P2dmmax d2calculation.Calculation // p2dmmax
P3dmelem string // p3dmelem P3dmelem string // p3dmelem
P3dmmin string // p3dmmin P3dmmin d2calculation.Calculation // p3dmmin
P3dmmax string // p3dmmax P3dmmax d2calculation.Calculation // p3dmmax
Descatt string // descatt Descatt string // descatt
Descmissile1 string // descmissile1 Descmissile1 string // descmissile1
Descmissile2 string // descmissile2 Descmissile2 string // descmissile2
@ -40,88 +42,88 @@ type SkillDescriptionRecord struct {
Descline1 string // descline1 Descline1 string // descline1
Desctexta1 string // desctexta1 Desctexta1 string // desctexta1
Desctextb1 string // desctextb1 Desctextb1 string // desctextb1
Desccalca1 string // desccalca1 Desccalca1 d2calculation.Calculation // desccalca1
Desccalcb1 string // desccalcb1 Desccalcb1 d2calculation.Calculation // desccalcb1
Descline2 string // descline2 Descline2 string // descline2
Desctexta2 string // desctexta2 Desctexta2 string // desctexta2
Desctextb2 string // desctextb2 Desctextb2 string // desctextb2
Desccalca2 string // desccalca2 Desccalca2 d2calculation.Calculation // desccalca2
Desccalcb2 string // desccalcb2 Desccalcb2 d2calculation.Calculation // desccalcb2
Descline3 string // descline3 Descline3 string // descline3
Desctexta3 string // desctexta3 Desctexta3 string // desctexta3
Desctextb3 string // desctextb3 Desctextb3 string // desctextb3
Desccalca3 string // desccalca3 Desccalca3 d2calculation.Calculation // desccalca3
Desccalcb3 string // desccalcb3 Desccalcb3 d2calculation.Calculation // desccalcb3
Descline4 string // descline4 Descline4 string // descline4
Desctexta4 string // desctexta4 Desctexta4 string // desctexta4
Desctextb4 string // desctextb4 Desctextb4 string // desctextb4
Desccalca4 string // desccalca4 Desccalca4 d2calculation.Calculation // desccalca4
Desccalcb4 string // desccalcb4 Desccalcb4 d2calculation.Calculation // desccalcb4
Descline5 string // descline5 Descline5 string // descline5
Desctexta5 string // desctexta5 Desctexta5 string // desctexta5
Desctextb5 string // desctextb5 Desctextb5 string // desctextb5
Desccalca5 string // desccalca5 Desccalca5 d2calculation.Calculation // desccalca5
Desccalcb5 string // desccalcb5 Desccalcb5 d2calculation.Calculation // desccalcb5
Descline6 string // descline6 Descline6 string // descline6
Desctexta6 string // desctexta6 Desctexta6 string // desctexta6
Desctextb6 string // desctextb6 Desctextb6 string // desctextb6
Desccalca6 string // desccalca6 Desccalca6 d2calculation.Calculation // desccalca6
Desccalcb6 string // desccalcb6 Desccalcb6 d2calculation.Calculation // desccalcb6
Dsc2line1 string // dsc2line1 Dsc2line1 string // dsc2line1
Dsc2texta1 string // dsc2texta1 Dsc2texta1 string // dsc2texta1
Dsc2textb1 string // dsc2textb1 Dsc2textb1 string // dsc2textb1
Dsc2calca1 string // dsc2calca1 Dsc2calca1 d2calculation.Calculation // dsc2calca1
Dsc2calcb1 string // dsc2calcb1 Dsc2calcb1 d2calculation.Calculation // dsc2calcb1
Dsc2line2 string // dsc2line2 Dsc2line2 string // dsc2line2
Dsc2texta2 string // dsc2texta2 Dsc2texta2 string // dsc2texta2
Dsc2textb2 string // dsc2textb2 Dsc2textb2 string // dsc2textb2
Dsc2calca2 string // dsc2calca2 Dsc2calca2 d2calculation.Calculation // dsc2calca2
Dsc2calcb2 string // dsc2calcb2 Dsc2calcb2 d2calculation.Calculation // dsc2calcb2
Dsc2line3 string // dsc2line3 Dsc2line3 string // dsc2line3
Dsc2texta3 string // dsc2texta3 Dsc2texta3 string // dsc2texta3
Dsc2textb3 string // dsc2textb3 Dsc2textb3 string // dsc2textb3
Dsc2calca3 string // dsc2calca3 Dsc2calca3 d2calculation.Calculation // dsc2calca3
Dsc2calcb3 string // dsc2calcb3 Dsc2calcb3 d2calculation.Calculation // dsc2calcb3
Dsc2line4 string // dsc2line4 Dsc2line4 string // dsc2line4
Dsc2texta4 string // dsc2texta4 Dsc2texta4 string // dsc2texta4
Dsc2textb4 string // dsc2textb4 Dsc2textb4 string // dsc2textb4
Dsc2calca4 string // dsc2calca4 Dsc2calca4 d2calculation.Calculation // dsc2calca4
Dsc2calcb4 string // dsc2calcb4 Dsc2calcb4 d2calculation.Calculation // dsc2calcb4
Dsc3line1 string // dsc3line1 Dsc3line1 string // dsc3line1
Dsc3texta1 string // dsc3texta1 Dsc3texta1 string // dsc3texta1
Dsc3textb1 string // dsc3textb1 Dsc3textb1 string // dsc3textb1
Dsc3calca1 string // dsc3calca1 Dsc3calca1 d2calculation.Calculation // dsc3calca1
Dsc3calcb1 string // dsc3calcb1 Dsc3calcb1 d2calculation.Calculation // dsc3calcb1
Dsc3line2 string // dsc3line2 Dsc3line2 string // dsc3line2
Dsc3texta2 string // dsc3texta2 Dsc3texta2 string // dsc3texta2
Dsc3textb2 string // dsc3textb2 Dsc3textb2 string // dsc3textb2
Dsc3calca2 string // dsc3calca2 Dsc3calca2 d2calculation.Calculation // dsc3calca2
Dsc3calcb2 string // dsc3calcb2 Dsc3calcb2 d2calculation.Calculation // dsc3calcb2
Dsc3line3 string // dsc3line3 Dsc3line3 string // dsc3line3
Dsc3texta3 string // dsc3texta3 Dsc3texta3 string // dsc3texta3
Dsc3textb3 string // dsc3textb3 Dsc3textb3 string // dsc3textb3
Dsc3calca3 string // dsc3calca3 Dsc3calca3 d2calculation.Calculation // dsc3calca3
Dsc3calcb3 string // dsc3calcb3 Dsc3calcb3 d2calculation.Calculation // dsc3calcb3
Dsc3line4 string // dsc3line4 Dsc3line4 string // dsc3line4
Dsc3texta4 string // dsc3texta4 Dsc3texta4 string // dsc3texta4
Dsc3textb4 string // dsc3textb4 Dsc3textb4 string // dsc3textb4
Dsc3calca4 string // dsc3calca4 Dsc3calca4 d2calculation.Calculation // dsc3calca4
Dsc3calcb4 string // dsc3calcb4 Dsc3calcb4 d2calculation.Calculation // dsc3calcb4
Dsc3line5 string // dsc3line5 Dsc3line5 string // dsc3line5
Dsc3texta5 string // dsc3texta5 Dsc3texta5 string // dsc3texta5
Dsc3textb5 string // dsc3textb5 Dsc3textb5 string // dsc3textb5
Dsc3calca5 string // dsc3calca5 Dsc3calca5 d2calculation.Calculation // dsc3calca5
Dsc3calcb5 string // dsc3calcb5 Dsc3calcb5 d2calculation.Calculation // dsc3calcb5
Dsc3line6 string // dsc3line6 Dsc3line6 string // dsc3line6
Dsc3texta6 string // dsc3texta6 Dsc3texta6 string // dsc3texta6
Dsc3textb6 string // dsc3textb6 Dsc3textb6 string // dsc3textb6
Dsc3calca6 string // dsc3calca6 Dsc3calca6 d2calculation.Calculation // dsc3calca6
Dsc3calcb6 string // dsc3calcb6 Dsc3calcb6 d2calculation.Calculation // dsc3calcb6
Dsc3line7 string // dsc3line7 Dsc3line7 string // dsc3line7
Dsc3texta7 string // dsc3texta7 Dsc3texta7 string // dsc3texta7
Dsc3textb7 string // dsc3textb7 Dsc3textb7 string // dsc3textb7
Dsc3calca7 string // dsc3calca7 Dsc3calca7 d2calculation.Calculation // dsc3calca7
Dsc3calcb7 string // dsc3calcb7 Dsc3calcb7 d2calculation.Calculation // dsc3calcb7
} }
// SkillDescriptions stores all of the SkillDescriptionRecords // SkillDescriptions stores all of the SkillDescriptionRecords
@ -132,6 +134,9 @@ var SkillDescriptions map[string]*SkillDescriptionRecord
func LoadSkillDescriptions(file []byte) { //nolint:funlen // doesn't make sense to split func LoadSkillDescriptions(file []byte) { //nolint:funlen // doesn't make sense to split
SkillDescriptions = make(map[string]*SkillDescriptionRecord) SkillDescriptions = make(map[string]*SkillDescriptionRecord)
parser := d2parser.New()
parser.SetCurrentReference("skill", "TODO: connect skill with description!") //nolint:godox // TODO: Connect skill with description.
d := d2common.LoadDataDictionary(file) d := d2common.LoadDataDictionary(file)
for d.Next() { for d.Next() {
record := &SkillDescriptionRecord{ record := &SkillDescriptionRecord{
@ -148,17 +153,17 @@ func LoadSkillDescriptions(file []byte) { //nolint:funlen // doesn't make sense
d.String("str alt"), d.String("str alt"),
d.String("str mana"), d.String("str mana"),
d.String("descdam"), d.String("descdam"),
d.String("ddam calc1"), parser.Parse(d.String("ddam calc1")),
d.String("ddam calc2"), parser.Parse(d.String("ddam calc2")),
d.String("p1dmelem"), d.String("p1dmelem"),
d.String("p1dmmin"), parser.Parse(d.String("p1dmmin")),
d.String("p1dmmax"), parser.Parse(d.String("p1dmmax")),
d.String("p2dmelem"), d.String("p2dmelem"),
d.String("p2dmmin"), parser.Parse(d.String("p2dmmin")),
d.String("p2dmmax"), parser.Parse(d.String("p2dmmax")),
d.String("p3dmelem"), d.String("p3dmelem"),
d.String("p3dmmin"), parser.Parse(d.String("p3dmmin")),
d.String("p3dmmax"), parser.Parse(d.String("p3dmmax")),
d.String("descatt"), d.String("descatt"),
d.String("descmissile1"), d.String("descmissile1"),
d.String("descmissile2"), d.String("descmissile2"),
@ -166,88 +171,88 @@ func LoadSkillDescriptions(file []byte) { //nolint:funlen // doesn't make sense
d.String("descline1"), d.String("descline1"),
d.String("desctexta1"), d.String("desctexta1"),
d.String("desctextb1"), d.String("desctextb1"),
d.String("desccalca1"), parser.Parse(d.String("desccalca1")),
d.String("desccalcb1"), parser.Parse(d.String("desccalcb1")),
d.String("descline2"), d.String("descline2"),
d.String("desctexta2"), d.String("desctexta2"),
d.String("desctextb2"), d.String("desctextb2"),
d.String("desccalca2"), parser.Parse(d.String("desccalca2")),
d.String("desccalcb2"), parser.Parse(d.String("desccalcb2")),
d.String("descline3"), d.String("descline3"),
d.String("desctexta3"), d.String("desctexta3"),
d.String("desctextb3"), d.String("desctextb3"),
d.String("desccalca3"), parser.Parse(d.String("desccalca3")),
d.String("desccalcb3"), parser.Parse(d.String("desccalcb3")),
d.String("descline4"), d.String("descline4"),
d.String("desctexta4"), d.String("desctexta4"),
d.String("desctextb4"), d.String("desctextb4"),
d.String("desccalca4"), parser.Parse(d.String("desccalca4")),
d.String("desccalcb4"), parser.Parse(d.String("desccalcb4")),
d.String("descline5"), d.String("descline5"),
d.String("desctexta5"), d.String("desctexta5"),
d.String("desctextb5"), d.String("desctextb5"),
d.String("desccalca5"), parser.Parse(d.String("desccalca5")),
d.String("desccalcb5"), parser.Parse(d.String("desccalcb5")),
d.String("descline6"), d.String("descline6"),
d.String("desctexta6"), d.String("desctexta6"),
d.String("desctextb6"), d.String("desctextb6"),
d.String("desccalca6"), parser.Parse(d.String("desccalca6")),
d.String("desccalcb6"), parser.Parse(d.String("desccalcb6")),
d.String("dsc2line1"), d.String("dsc2line1"),
d.String("dsc2texta1"), d.String("dsc2texta1"),
d.String("dsc2textb1"), d.String("dsc2textb1"),
d.String("dsc2calca1"), parser.Parse(d.String("dsc2calca1")),
d.String("dsc2calcb1"), parser.Parse(d.String("dsc2calcb1")),
d.String("dsc2line2"), d.String("dsc2line2"),
d.String("dsc2texta2"), d.String("dsc2texta2"),
d.String("dsc2textb2"), d.String("dsc2textb2"),
d.String("dsc2calca2"), parser.Parse(d.String("dsc2calca2")),
d.String("dsc2calcb2"), parser.Parse(d.String("dsc2calcb2")),
d.String("dsc2line3"), d.String("dsc2line3"),
d.String("dsc2texta3"), d.String("dsc2texta3"),
d.String("dsc2textb3"), d.String("dsc2textb3"),
d.String("dsc2calca3"), parser.Parse(d.String("dsc2calca3")),
d.String("dsc2calcb3"), parser.Parse(d.String("dsc2calcb3")),
d.String("dsc2line4"), d.String("dsc2line4"),
d.String("dsc2texta4"), d.String("dsc2texta4"),
d.String("dsc2textb4"), d.String("dsc2textb4"),
d.String("dsc2calca4"), parser.Parse(d.String("dsc2calca4")),
d.String("dsc2calcb4"), parser.Parse(d.String("dsc2calcb4")),
d.String("dsc3line1"), d.String("dsc3line1"),
d.String("dsc3texta1"), d.String("dsc3texta1"),
d.String("dsc3textb1"), d.String("dsc3textb1"),
d.String("dsc3calca1"), parser.Parse(d.String("dsc3calca1")),
d.String("dsc3calcb1"), parser.Parse(d.String("dsc3calcb1")),
d.String("dsc3line2"), d.String("dsc3line2"),
d.String("dsc3texta2"), d.String("dsc3texta2"),
d.String("dsc3textb2"), d.String("dsc3textb2"),
d.String("dsc3calca2"), parser.Parse(d.String("dsc3calca2")),
d.String("dsc3calcb2"), parser.Parse(d.String("dsc3calcb2")),
d.String("dsc3line3"), d.String("dsc3line3"),
d.String("dsc3texta3"), d.String("dsc3texta3"),
d.String("dsc3textb3"), d.String("dsc3textb3"),
d.String("dsc3calca3"), parser.Parse(d.String("dsc3calca3")),
d.String("dsc3calcb3"), parser.Parse(d.String("dsc3calcb3")),
d.String("dsc3line4"), d.String("dsc3line4"),
d.String("dsc3texta4"), d.String("dsc3texta4"),
d.String("dsc3textb4"), d.String("dsc3textb4"),
d.String("dsc3calca4"), parser.Parse(d.String("dsc3calca4")),
d.String("dsc3calcb4"), parser.Parse(d.String("dsc3calcb4")),
d.String("dsc3line5"), d.String("dsc3line5"),
d.String("dsc3texta5"), d.String("dsc3texta5"),
d.String("dsc3textb5"), d.String("dsc3textb5"),
d.String("dsc3calca5"), parser.Parse(d.String("dsc3calca5")),
d.String("dsc3calcb5"), parser.Parse(d.String("dsc3calcb5")),
d.String("dsc3line6"), d.String("dsc3line6"),
d.String("dsc3texta6"), d.String("dsc3texta6"),
d.String("dsc3textb6"), d.String("dsc3textb6"),
d.String("dsc3calca6"), parser.Parse(d.String("dsc3calca6")),
d.String("dsc3calcb6"), parser.Parse(d.String("dsc3calcb6")),
d.String("dsc3line7"), d.String("dsc3line7"),
d.String("dsc3texta7"), d.String("dsc3texta7"),
d.String("dsc3textb7"), d.String("dsc3textb7"),
d.String("dsc3calca7"), parser.Parse(d.String("dsc3calca7")),
d.String("dsc3calcb7"), parser.Parse(d.String("dsc3calcb7")),
} }
SkillDescriptions[record.Name] = record SkillDescriptions[record.Name] = record

View File

@ -4,6 +4,8 @@ import (
"log" "log"
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation/d2parser"
) )
// SkillDetails has all of the SkillRecords // SkillDetails has all of the SkillRecords
@ -16,9 +18,9 @@ type SkillRecord struct {
Skill string Skill string
Charclass string Charclass string
Skilldesc string Skilldesc string
Prgcalc1 string Prgcalc1 d2calculation.Calculation
Prgcalc2 string Prgcalc2 d2calculation.Calculation
Prgcalc3 string Prgcalc3 d2calculation.Calculation
Srvmissile string Srvmissile string
Srvmissilea string Srvmissilea string
Srvmissileb string Srvmissileb string
@ -26,20 +28,20 @@ type SkillRecord struct {
Srvoverlay string Srvoverlay string
Aurastate string Aurastate string
Auratargetstate string Auratargetstate string
Auralencalc string Auralencalc d2calculation.Calculation
Aurarangecalc string Aurarangecalc d2calculation.Calculation
Aurastat1 string Aurastat1 string
Aurastatcalc1 string Aurastatcalc1 d2calculation.Calculation
Aurastat2 string Aurastat2 string
Aurastatcalc2 string Aurastatcalc2 d2calculation.Calculation
Aurastat3 string Aurastat3 string
Aurastatcalc3 string Aurastatcalc3 d2calculation.Calculation
Aurastat4 string Aurastat4 string
Aurastatcalc4 string Aurastatcalc4 d2calculation.Calculation
Aurastat5 string Aurastat5 string
Aurastatcalc5 string Aurastatcalc5 d2calculation.Calculation
Aurastat6 string Aurastat6 string
Aurastatcalc6 string Aurastatcalc6 d2calculation.Calculation
Auraevent1 string Auraevent1 string
Auraevent2 string Auraevent2 string
Auraevent3 string Auraevent3 string
@ -48,31 +50,31 @@ type SkillRecord struct {
Passivestate string Passivestate string
Passiveitype string Passiveitype string
Passivestat1 string Passivestat1 string
Passivecalc1 string Passivecalc1 d2calculation.Calculation
Passivestat2 string Passivestat2 string
Passivecalc2 string Passivecalc2 d2calculation.Calculation
Passivestat3 string Passivestat3 string
Passivecalc3 string Passivecalc3 d2calculation.Calculation
Passivestat4 string Passivestat4 string
Passivecalc4 string Passivecalc4 d2calculation.Calculation
Passivestat5 string Passivestat5 string
Passivecalc5 string Passivecalc5 d2calculation.Calculation
Passiveevent string Passiveevent string
Passiveeventfunc string Passiveeventfunc string
Summon string Summon string
Pettype string Pettype string
Petmax string Petmax d2calculation.Calculation
Summode string Summode string
Sumskill1 string Sumskill1 string
Sumsk1calc string Sumsk1calc d2calculation.Calculation
Sumskill2 string Sumskill2 string
Sumsk2calc string Sumsk2calc d2calculation.Calculation
Sumskill3 string Sumskill3 string
Sumsk3calc string Sumsk3calc d2calculation.Calculation
Sumskill4 string Sumskill4 string
Sumsk4calc string Sumsk4calc d2calculation.Calculation
Sumskill5 string Sumskill5 string
Sumsk5calc string Sumsk5calc d2calculation.Calculation
Sumoverlay string Sumoverlay string
Stsound string Stsound string
Stsoundclass string Stsoundclass string
@ -91,12 +93,9 @@ type SkillRecord struct {
Cltmissileb string Cltmissileb string
Cltmissilec string Cltmissilec string
Cltmissiled string Cltmissiled string
Cltcalc1 string Cltcalc1 d2calculation.Calculation
Cltcalc1Desc string Cltcalc2 d2calculation.Calculation
Cltcalc2 string Cltcalc3 d2calculation.Calculation
Cltcalc2Desc string
Cltcalc3 string
Cltcalc3Desc string
Range string Range string
Itypea1 string Itypea1 string
Itypea2 string Itypea2 string
@ -113,35 +112,23 @@ type SkillRecord struct {
Monanim string Monanim string
ItemCastSound string ItemCastSound string
ItemCastOverlay string ItemCastOverlay string
Skpoints string Skpoints d2calculation.Calculation
Reqskill1 string Reqskill1 string
Reqskill2 string Reqskill2 string
Reqskill3 string Reqskill3 string
State1 string State1 string
State2 string State2 string
State3 string State3 string
Perdelay string Perdelay d2calculation.Calculation
Calc1 string Calc1 d2calculation.Calculation
Calc1Desc string Calc2 d2calculation.Calculation
Calc2 string Calc3 d2calculation.Calculation
Calc2Desc string Calc4 d2calculation.Calculation
Calc3 string ToHitCalc d2calculation.Calculation
Calc3Desc string DmgSymPerCalc d2calculation.Calculation
Calc4 string
Calc4Desc string
Param1Desc string
Param2Desc string
Param3Desc string
Param4Desc string
Param5Desc string
Param6Desc string
Param7Desc string
Param8Desc string
ToHitCalc string
DmgSymPerCalc string
EType string EType string
EDmgSymPerCalc string EDmgSymPerCalc d2calculation.Calculation
ELenSymPerCalc string ELenSymPerCalc d2calculation.Calculation
ID int ID int
Srvstfunc int Srvstfunc int
Srvdofunc int Srvdofunc int
@ -277,8 +264,13 @@ type SkillRecord struct {
func LoadSkills(file []byte) { func LoadSkills(file []byte) {
SkillDetails = make(map[int]*SkillRecord) SkillDetails = make(map[int]*SkillRecord)
parser := d2parser.New()
d := d2common.LoadDataDictionary(file) d := d2common.LoadDataDictionary(file)
for d.Next() { for d.Next() {
name := d.String("skill")
parser.SetCurrentReference("skill", name)
record := &SkillRecord{ record := &SkillRecord{
Skill: d.String("skill"), Skill: d.String("skill"),
ID: d.Number("Id"), ID: d.Number("Id"),
@ -290,9 +282,9 @@ func LoadSkills(file []byte) {
Srvprgfunc1: d.Number("srvprgfunc1"), Srvprgfunc1: d.Number("srvprgfunc1"),
Srvprgfunc2: d.Number("srvprgfunc2"), Srvprgfunc2: d.Number("srvprgfunc2"),
Srvprgfunc3: d.Number("srvprgfunc3"), Srvprgfunc3: d.Number("srvprgfunc3"),
Prgcalc1: d.String("prgcalc1"), Prgcalc1: parser.Parse(d.String("prgcalc1")),
Prgcalc2: d.String("prgcalc2"), Prgcalc2: parser.Parse(d.String("prgcalc2")),
Prgcalc3: d.String("prgcalc3"), Prgcalc3: parser.Parse(d.String("prgcalc3")),
Prgdam: d.Number("prgdam"), Prgdam: d.Number("prgdam"),
Srvmissile: d.String("srvmissile"), Srvmissile: d.String("srvmissile"),
Decquant: d.Bool("decquant"), Decquant: d.Bool("decquant"),
@ -304,20 +296,20 @@ func LoadSkills(file []byte) {
Aurafilter: d.Number("aurafilter"), Aurafilter: d.Number("aurafilter"),
Aurastate: d.String("aurastate"), Aurastate: d.String("aurastate"),
Auratargetstate: d.String("auratargetstate"), Auratargetstate: d.String("auratargetstate"),
Auralencalc: d.String("auralencalc"), Auralencalc: parser.Parse(d.String("auralencalc")),
Aurarangecalc: d.String("aurarangecalc"), Aurarangecalc: parser.Parse(d.String("aurarangecalc")),
Aurastat1: d.String("aurastat1"), Aurastat1: d.String("aurastat1"),
Aurastatcalc1: d.String("aurastatcalc1"), Aurastatcalc1: parser.Parse(d.String("aurastatcalc1")),
Aurastat2: d.String("aurastat2"), Aurastat2: d.String("aurastat2"),
Aurastatcalc2: d.String("aurastatcalc2"), Aurastatcalc2: parser.Parse(d.String("aurastatcalc2")),
Aurastat3: d.String("aurastat3"), Aurastat3: d.String("aurastat3"),
Aurastatcalc3: d.String("aurastatcalc3"), Aurastatcalc3: parser.Parse(d.String("aurastatcalc3")),
Aurastat4: d.String("aurastat4"), Aurastat4: d.String("aurastat4"),
Aurastatcalc4: d.String("aurastatcalc4"), Aurastatcalc4: parser.Parse(d.String("aurastatcalc4")),
Aurastat5: d.String("aurastat5"), Aurastat5: d.String("aurastat5"),
Aurastatcalc5: d.String("aurastatcalc5"), Aurastatcalc5: parser.Parse(d.String("aurastatcalc5")),
Aurastat6: d.String("aurastat6"), Aurastat6: d.String("aurastat6"),
Aurastatcalc6: d.String("aurastatcalc6"), Aurastatcalc6: parser.Parse(d.String("aurastatcalc6")),
Auraevent1: d.String("auraevent1"), Auraevent1: d.String("auraevent1"),
Auraeventfunc1: d.Number("auraeventfunc1"), Auraeventfunc1: d.Number("auraeventfunc1"),
Auraevent2: d.String("auraevent2"), Auraevent2: d.String("auraevent2"),
@ -329,31 +321,31 @@ func LoadSkills(file []byte) {
Passivestate: d.String("passivestate"), Passivestate: d.String("passivestate"),
Passiveitype: d.String("passiveitype"), Passiveitype: d.String("passiveitype"),
Passivestat1: d.String("passivestat1"), Passivestat1: d.String("passivestat1"),
Passivecalc1: d.String("passivecalc1"), Passivecalc1: parser.Parse(d.String("passivecalc1")),
Passivestat2: d.String("passivestat2"), Passivestat2: d.String("passivestat2"),
Passivecalc2: d.String("passivecalc2"), Passivecalc2: parser.Parse(d.String("passivecalc2")),
Passivestat3: d.String("passivestat3"), Passivestat3: d.String("passivestat3"),
Passivecalc3: d.String("passivecalc3"), Passivecalc3: parser.Parse(d.String("passivecalc3")),
Passivestat4: d.String("passivestat4"), Passivestat4: d.String("passivestat4"),
Passivecalc4: d.String("passivecalc4"), Passivecalc4: parser.Parse(d.String("passivecalc4")),
Passivestat5: d.String("passivestat5"), Passivestat5: d.String("passivestat5"),
Passivecalc5: d.String("passivecalc5"), Passivecalc5: parser.Parse(d.String("passivecalc5")),
Passiveevent: d.String("passiveevent"), Passiveevent: d.String("passiveevent"),
Passiveeventfunc: d.String("passiveeventfunc"), Passiveeventfunc: d.String("passiveeventfunc"),
Summon: d.String("summon"), Summon: d.String("summon"),
Pettype: d.String("pettype"), Pettype: d.String("pettype"),
Petmax: d.String("petmax"), Petmax: parser.Parse(d.String("petmax")),
Summode: d.String("summode"), Summode: d.String("summode"),
Sumskill1: d.String("sumskill1"), Sumskill1: d.String("sumskill1"),
Sumsk1calc: d.String("sumsk1calc"), Sumsk1calc: parser.Parse(d.String("sumsk1calc")),
Sumskill2: d.String("sumskill2"), Sumskill2: d.String("sumskill2"),
Sumsk2calc: d.String("sumsk2calc"), Sumsk2calc: parser.Parse(d.String("sumsk2calc")),
Sumskill3: d.String("sumskill3"), Sumskill3: d.String("sumskill3"),
Sumsk3calc: d.String("sumsk3calc"), Sumsk3calc: parser.Parse(d.String("sumsk3calc")),
Sumskill4: d.String("sumskill4"), Sumskill4: d.String("sumskill4"),
Sumsk4calc: d.String("sumsk4calc"), Sumsk4calc: parser.Parse(d.String("sumsk4calc")),
Sumskill5: d.String("sumskill5"), Sumskill5: d.String("sumskill5"),
Sumsk5calc: d.String("sumsk5calc"), Sumsk5calc: parser.Parse(d.String("sumsk5calc")),
Sumumod: d.Number("sumumod"), Sumumod: d.Number("sumumod"),
Sumoverlay: d.String("sumoverlay"), Sumoverlay: d.String("sumoverlay"),
Stsuccessonly: d.Bool("stsuccessonly"), Stsuccessonly: d.Bool("stsuccessonly"),
@ -381,12 +373,9 @@ func LoadSkills(file []byte) {
Cltmissileb: d.String("cltmissileb"), Cltmissileb: d.String("cltmissileb"),
Cltmissilec: d.String("cltmissilec"), Cltmissilec: d.String("cltmissilec"),
Cltmissiled: d.String("cltmissiled"), Cltmissiled: d.String("cltmissiled"),
Cltcalc1: d.String("cltcalc1"), Cltcalc1: parser.Parse(d.String("cltcalc1")),
Cltcalc1Desc: d.String("*cltcalc1 desc"), Cltcalc2: parser.Parse(d.String("cltcalc2")),
Cltcalc2: d.String("cltcalc2"), Cltcalc3: parser.Parse(d.String("cltcalc3")),
Cltcalc2Desc: d.String("*cltcalc2 desc"),
Cltcalc3: d.String("cltcalc3"),
Cltcalc3Desc: d.String("*cltcalc3 desc"),
Warp: d.Bool("warp"), Warp: d.Bool("warp"),
Immediate: d.Bool("immediate"), Immediate: d.Bool("immediate"),
Enhanceable: d.Bool("enhanceable"), Enhanceable: d.Bool("enhanceable"),
@ -431,7 +420,7 @@ func LoadSkills(file []byte) {
ItemCltCheckStart: d.Bool("ItemCltCheckStart"), ItemCltCheckStart: d.Bool("ItemCltCheckStart"),
ItemCastSound: d.String("ItemCastSound"), ItemCastSound: d.String("ItemCastSound"),
ItemCastOverlay: d.String("ItemCastOverlay"), ItemCastOverlay: d.String("ItemCastOverlay"),
Skpoints: d.String("skpoints"), Skpoints: parser.Parse(d.String("skpoints")),
Reqlevel: d.Number("reqlevel"), Reqlevel: d.Number("reqlevel"),
Maxlvl: d.Number("maxlvl"), Maxlvl: d.Number("maxlvl"),
Reqstr: d.Number("reqstr"), Reqstr: d.Number("reqstr"),
@ -460,40 +449,28 @@ func LoadSkills(file []byte) {
InTown: d.Bool("InTown"), InTown: d.Bool("InTown"),
Aura: d.Bool("aura"), Aura: d.Bool("aura"),
Periodic: d.Bool("periodic"), Periodic: d.Bool("periodic"),
Perdelay: d.String("perdelay"), Perdelay: parser.Parse(d.String("perdelay")),
Finishing: d.Bool("finishing"), Finishing: d.Bool("finishing"),
Passive: d.Bool("passive"), Passive: d.Bool("passive"),
Progressive: d.Bool("progressive"), Progressive: d.Bool("progressive"),
General: d.Bool("general"), General: d.Bool("general"),
Scroll: d.Bool("scroll"), Scroll: d.Bool("scroll"),
Calc1: d.String("calc1"), Calc1: parser.Parse(d.String("calc1")),
Calc1Desc: d.String("*calc1 desc"), Calc2: parser.Parse(d.String("calc2")),
Calc2: d.String("calc2"), Calc3: parser.Parse(d.String("calc3")),
Calc2Desc: d.String("*calc2 desc"), Calc4: parser.Parse(d.String("calc4")),
Calc3: d.String("calc3"),
Calc3Desc: d.String("*calc3 desc"),
Calc4: d.String("calc4"),
Calc4Desc: d.String("*calc4 desc"),
Param1: d.Number("Param1"), Param1: d.Number("Param1"),
Param1Desc: d.String("*Param1 Description"),
Param2: d.Number("Param2"), Param2: d.Number("Param2"),
Param2Desc: d.String("*Param2 Description"),
Param3: d.Number("Param3"), Param3: d.Number("Param3"),
Param3Desc: d.String("*Param3 Description"),
Param4: d.Number("Param4"), Param4: d.Number("Param4"),
Param4Desc: d.String("*Param4 Description"),
Param5: d.Number("Param5"), Param5: d.Number("Param5"),
Param5Desc: d.String("*Param5 Description"),
Param6: d.Number("Param6"), Param6: d.Number("Param6"),
Param6Desc: d.String("*Param6 Description"),
Param7: d.Number("Param7"), Param7: d.Number("Param7"),
Param7Desc: d.String("*Param7 Description"),
Param8: d.Number("Param8"), Param8: d.Number("Param8"),
Param8Desc: d.String("*Param8 Description"),
InGame: d.Bool("InGame"), InGame: d.Bool("InGame"),
ToHit: d.Number("ToHit"), ToHit: d.Number("ToHit"),
LevToHit: d.Number("LevToHit"), LevToHit: d.Number("LevToHit"),
ToHitCalc: d.String("ToHitCalc"), ToHitCalc: parser.Parse(d.String("ToHitCalc")),
ResultFlags: d.Number("ResultFlags"), ResultFlags: d.Number("ResultFlags"),
HitFlags: d.Number("HitFlags"), HitFlags: d.Number("HitFlags"),
HitClass: d.Number("HitClass"), HitClass: d.Number("HitClass"),
@ -512,7 +489,7 @@ func LoadSkills(file []byte) {
MaxLevDam3: d.Number("MaxLevDam3"), MaxLevDam3: d.Number("MaxLevDam3"),
MaxLevDam4: d.Number("MaxLevDam4"), MaxLevDam4: d.Number("MaxLevDam4"),
MaxLevDam5: d.Number("MaxLevDam5"), MaxLevDam5: d.Number("MaxLevDam5"),
DmgSymPerCalc: d.String("DmgSymPerCalc"), DmgSymPerCalc: parser.Parse(d.String("DmgSymPerCalc")),
EType: d.String("EType"), EType: d.String("EType"),
EMin: d.Number("EMin"), EMin: d.Number("EMin"),
EMinLev1: d.Number("EMinLev1"), EMinLev1: d.Number("EMinLev1"),
@ -526,12 +503,12 @@ func LoadSkills(file []byte) {
EMaxLev3: d.Number("EMaxLev3"), EMaxLev3: d.Number("EMaxLev3"),
EMaxLev4: d.Number("EMaxLev4"), EMaxLev4: d.Number("EMaxLev4"),
EMaxLev5: d.Number("EMaxLev5"), EMaxLev5: d.Number("EMaxLev5"),
EDmgSymPerCalc: d.String("EDmgSymPerCalc"), EDmgSymPerCalc: parser.Parse(d.String("EDmgSymPerCalc")),
ELen: d.Number("ELen"), ELen: d.Number("ELen"),
ELevLen1: d.Number("ELevLen1"), ELevLen1: d.Number("ELevLen1"),
ELevLen2: d.Number("ELevLen2"), ELevLen2: d.Number("ELevLen2"),
ELevLen3: d.Number("ELevLen3"), ELevLen3: d.Number("ELevLen3"),
ELenSymPerCalc: d.String("ELenSymPerCalc"), ELenSymPerCalc: parser.Parse(d.String("ELenSymPerCalc")),
Aitype: d.Number("aitype"), Aitype: d.Number("aitype"),
Aibonus: d.Number("aibonus"), Aibonus: d.Number("aibonus"),
CostMult: d.Number("cost mult"), CostMult: d.Number("cost mult"),