diff --git a/d2common/d2calculation/calculation.go b/d2common/d2calculation/calculation.go new file mode 100644 index 00000000..04aa84bf --- /dev/null +++ b/d2common/d2calculation/calculation.go @@ -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) +} diff --git a/d2common/d2calculation/d2lexer/lexer.go b/d2common/d2calculation/d2lexer/lexer.go new file mode 100644 index 00000000..1ae1c7a0 --- /dev/null +++ b/d2common/d2calculation/d2lexer/lexer.go @@ -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 +} diff --git a/d2common/d2calculation/d2lexer/lexer_test.go b/d2common/d2calculation/d2lexer/lexer_test.go new file mode 100644 index 00000000..06b55f29 --- /dev/null +++ b/d2common/d2calculation/d2lexer/lexer_test.go @@ -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") + } +} diff --git a/d2common/d2calculation/d2parser/d2parser.go b/d2common/d2calculation/d2parser/d2parser.go new file mode 100644 index 00000000..2c7b25e1 --- /dev/null +++ b/d2common/d2calculation/d2parser/d2parser.go @@ -0,0 +1,2 @@ +// Package d2parser contains the code for parsing calculation strings. +package d2parser diff --git a/d2common/d2calculation/d2parser/operations.go b/d2common/d2calculation/d2parser/operations.go new file mode 100644 index 00000000..915b2d3f --- /dev/null +++ b/d2common/d2calculation/d2parser/operations.go @@ -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 + }, + } +} diff --git a/d2common/d2calculation/d2parser/parser.go b/d2common/d2calculation/d2parser/parser.go new file mode 100644 index 00000000..5cfc6ceb --- /dev/null +++ b/d2common/d2calculation/d2parser/parser.go @@ -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, + } +} diff --git a/d2common/d2calculation/d2parser/parser_test.go b/d2common/d2calculation/d2parser/parser_test.go new file mode 100644 index 00000000..084c3360 --- /dev/null +++ b/d2common/d2calculation/d2parser/parser_test.go @@ -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) + } +} diff --git a/d2common/d2data/d2datadict/skilldesc.go b/d2common/d2data/d2datadict/skilldesc.go index 23b739d5..a59a9fbd 100644 --- a/d2common/d2data/d2datadict/skilldesc.go +++ b/d2common/d2data/d2datadict/skilldesc.go @@ -4,124 +4,126 @@ import ( "log" "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 // generating text strings for skills. type SkillDescriptionRecord struct { - Name string // skilldesc - SkillPage string // SkillPage - SkillRow string // SkillRow - SkillColumn string // SkillColumn - ListRow string // ListRow - ListPool string // ListPool - IconCel string // IconCel - NameKey string // str name - ShortKey string // str short - LongKey string // str long - AltKey string // str alt - ManaKey string // str mana - Descdam string // descdam - DdamCalc1 string // ddam calc1 - DdamCalc2 string // ddam calc2 - P1dmelem string // p1dmelem - P1dmmin string // p1dmmin - P1dmmax string // p1dmmax - P2dmelem string // p2dmelem - P2dmmin string // p2dmmin - P2dmmax string // p2dmmax - P3dmelem string // p3dmelem - P3dmmin string // p3dmmin - P3dmmax string // p3dmmax - Descatt string // descatt - Descmissile1 string // descmissile1 - Descmissile2 string // descmissile2 - Descmissile3 string // descmissile3 - Descline1 string // descline1 - Desctexta1 string // desctexta1 - Desctextb1 string // desctextb1 - Desccalca1 string // desccalca1 - Desccalcb1 string // desccalcb1 - Descline2 string // descline2 - Desctexta2 string // desctexta2 - Desctextb2 string // desctextb2 - Desccalca2 string // desccalca2 - Desccalcb2 string // desccalcb2 - Descline3 string // descline3 - Desctexta3 string // desctexta3 - Desctextb3 string // desctextb3 - Desccalca3 string // desccalca3 - Desccalcb3 string // desccalcb3 - Descline4 string // descline4 - Desctexta4 string // desctexta4 - Desctextb4 string // desctextb4 - Desccalca4 string // desccalca4 - Desccalcb4 string // desccalcb4 - Descline5 string // descline5 - Desctexta5 string // desctexta5 - Desctextb5 string // desctextb5 - Desccalca5 string // desccalca5 - Desccalcb5 string // desccalcb5 - Descline6 string // descline6 - Desctexta6 string // desctexta6 - Desctextb6 string // desctextb6 - Desccalca6 string // desccalca6 - Desccalcb6 string // desccalcb6 - Dsc2line1 string // dsc2line1 - Dsc2texta1 string // dsc2texta1 - Dsc2textb1 string // dsc2textb1 - Dsc2calca1 string // dsc2calca1 - Dsc2calcb1 string // dsc2calcb1 - Dsc2line2 string // dsc2line2 - Dsc2texta2 string // dsc2texta2 - Dsc2textb2 string // dsc2textb2 - Dsc2calca2 string // dsc2calca2 - Dsc2calcb2 string // dsc2calcb2 - Dsc2line3 string // dsc2line3 - Dsc2texta3 string // dsc2texta3 - Dsc2textb3 string // dsc2textb3 - Dsc2calca3 string // dsc2calca3 - Dsc2calcb3 string // dsc2calcb3 - Dsc2line4 string // dsc2line4 - Dsc2texta4 string // dsc2texta4 - Dsc2textb4 string // dsc2textb4 - Dsc2calca4 string // dsc2calca4 - Dsc2calcb4 string // dsc2calcb4 - Dsc3line1 string // dsc3line1 - Dsc3texta1 string // dsc3texta1 - Dsc3textb1 string // dsc3textb1 - Dsc3calca1 string // dsc3calca1 - Dsc3calcb1 string // dsc3calcb1 - Dsc3line2 string // dsc3line2 - Dsc3texta2 string // dsc3texta2 - Dsc3textb2 string // dsc3textb2 - Dsc3calca2 string // dsc3calca2 - Dsc3calcb2 string // dsc3calcb2 - Dsc3line3 string // dsc3line3 - Dsc3texta3 string // dsc3texta3 - Dsc3textb3 string // dsc3textb3 - Dsc3calca3 string // dsc3calca3 - Dsc3calcb3 string // dsc3calcb3 - Dsc3line4 string // dsc3line4 - Dsc3texta4 string // dsc3texta4 - Dsc3textb4 string // dsc3textb4 - Dsc3calca4 string // dsc3calca4 - Dsc3calcb4 string // dsc3calcb4 - Dsc3line5 string // dsc3line5 - Dsc3texta5 string // dsc3texta5 - Dsc3textb5 string // dsc3textb5 - Dsc3calca5 string // dsc3calca5 - Dsc3calcb5 string // dsc3calcb5 - Dsc3line6 string // dsc3line6 - Dsc3texta6 string // dsc3texta6 - Dsc3textb6 string // dsc3textb6 - Dsc3calca6 string // dsc3calca6 - Dsc3calcb6 string // dsc3calcb6 - Dsc3line7 string // dsc3line7 - Dsc3texta7 string // dsc3texta7 - Dsc3textb7 string // dsc3textb7 - Dsc3calca7 string // dsc3calca7 - Dsc3calcb7 string // dsc3calcb7 + Name string // skilldesc + SkillPage string // SkillPage + SkillRow string // SkillRow + SkillColumn string // SkillColumn + ListRow string // ListRow + ListPool string // ListPool + IconCel string // IconCel + NameKey string // str name + ShortKey string // str short + LongKey string // str long + AltKey string // str alt + ManaKey string // str mana + Descdam string // descdam + DdamCalc1 d2calculation.Calculation // ddam calc1 + DdamCalc2 d2calculation.Calculation // ddam calc2 + P1dmelem string // p1dmelem + P1dmmin d2calculation.Calculation // p1dmmin + P1dmmax d2calculation.Calculation // p1dmmax + P2dmelem string // p2dmelem + P2dmmin d2calculation.Calculation // p2dmmin + P2dmmax d2calculation.Calculation // p2dmmax + P3dmelem string // p3dmelem + P3dmmin d2calculation.Calculation // p3dmmin + P3dmmax d2calculation.Calculation // p3dmmax + Descatt string // descatt + Descmissile1 string // descmissile1 + Descmissile2 string // descmissile2 + Descmissile3 string // descmissile3 + Descline1 string // descline1 + Desctexta1 string // desctexta1 + Desctextb1 string // desctextb1 + Desccalca1 d2calculation.Calculation // desccalca1 + Desccalcb1 d2calculation.Calculation // desccalcb1 + Descline2 string // descline2 + Desctexta2 string // desctexta2 + Desctextb2 string // desctextb2 + Desccalca2 d2calculation.Calculation // desccalca2 + Desccalcb2 d2calculation.Calculation // desccalcb2 + Descline3 string // descline3 + Desctexta3 string // desctexta3 + Desctextb3 string // desctextb3 + Desccalca3 d2calculation.Calculation // desccalca3 + Desccalcb3 d2calculation.Calculation // desccalcb3 + Descline4 string // descline4 + Desctexta4 string // desctexta4 + Desctextb4 string // desctextb4 + Desccalca4 d2calculation.Calculation // desccalca4 + Desccalcb4 d2calculation.Calculation // desccalcb4 + Descline5 string // descline5 + Desctexta5 string // desctexta5 + Desctextb5 string // desctextb5 + Desccalca5 d2calculation.Calculation // desccalca5 + Desccalcb5 d2calculation.Calculation // desccalcb5 + Descline6 string // descline6 + Desctexta6 string // desctexta6 + Desctextb6 string // desctextb6 + Desccalca6 d2calculation.Calculation // desccalca6 + Desccalcb6 d2calculation.Calculation // desccalcb6 + Dsc2line1 string // dsc2line1 + Dsc2texta1 string // dsc2texta1 + Dsc2textb1 string // dsc2textb1 + Dsc2calca1 d2calculation.Calculation // dsc2calca1 + Dsc2calcb1 d2calculation.Calculation // dsc2calcb1 + Dsc2line2 string // dsc2line2 + Dsc2texta2 string // dsc2texta2 + Dsc2textb2 string // dsc2textb2 + Dsc2calca2 d2calculation.Calculation // dsc2calca2 + Dsc2calcb2 d2calculation.Calculation // dsc2calcb2 + Dsc2line3 string // dsc2line3 + Dsc2texta3 string // dsc2texta3 + Dsc2textb3 string // dsc2textb3 + Dsc2calca3 d2calculation.Calculation // dsc2calca3 + Dsc2calcb3 d2calculation.Calculation // dsc2calcb3 + Dsc2line4 string // dsc2line4 + Dsc2texta4 string // dsc2texta4 + Dsc2textb4 string // dsc2textb4 + Dsc2calca4 d2calculation.Calculation // dsc2calca4 + Dsc2calcb4 d2calculation.Calculation // dsc2calcb4 + Dsc3line1 string // dsc3line1 + Dsc3texta1 string // dsc3texta1 + Dsc3textb1 string // dsc3textb1 + Dsc3calca1 d2calculation.Calculation // dsc3calca1 + Dsc3calcb1 d2calculation.Calculation // dsc3calcb1 + Dsc3line2 string // dsc3line2 + Dsc3texta2 string // dsc3texta2 + Dsc3textb2 string // dsc3textb2 + Dsc3calca2 d2calculation.Calculation // dsc3calca2 + Dsc3calcb2 d2calculation.Calculation // dsc3calcb2 + Dsc3line3 string // dsc3line3 + Dsc3texta3 string // dsc3texta3 + Dsc3textb3 string // dsc3textb3 + Dsc3calca3 d2calculation.Calculation // dsc3calca3 + Dsc3calcb3 d2calculation.Calculation // dsc3calcb3 + Dsc3line4 string // dsc3line4 + Dsc3texta4 string // dsc3texta4 + Dsc3textb4 string // dsc3textb4 + Dsc3calca4 d2calculation.Calculation // dsc3calca4 + Dsc3calcb4 d2calculation.Calculation // dsc3calcb4 + Dsc3line5 string // dsc3line5 + Dsc3texta5 string // dsc3texta5 + Dsc3textb5 string // dsc3textb5 + Dsc3calca5 d2calculation.Calculation // dsc3calca5 + Dsc3calcb5 d2calculation.Calculation // dsc3calcb5 + Dsc3line6 string // dsc3line6 + Dsc3texta6 string // dsc3texta6 + Dsc3textb6 string // dsc3textb6 + Dsc3calca6 d2calculation.Calculation // dsc3calca6 + Dsc3calcb6 d2calculation.Calculation // dsc3calcb6 + Dsc3line7 string // dsc3line7 + Dsc3texta7 string // dsc3texta7 + Dsc3textb7 string // dsc3textb7 + Dsc3calca7 d2calculation.Calculation // dsc3calca7 + Dsc3calcb7 d2calculation.Calculation // dsc3calcb7 } // 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 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) for d.Next() { record := &SkillDescriptionRecord{ @@ -148,17 +153,17 @@ func LoadSkillDescriptions(file []byte) { //nolint:funlen // doesn't make sense d.String("str alt"), d.String("str mana"), d.String("descdam"), - d.String("ddam calc1"), - d.String("ddam calc2"), + parser.Parse(d.String("ddam calc1")), + parser.Parse(d.String("ddam calc2")), d.String("p1dmelem"), - d.String("p1dmmin"), - d.String("p1dmmax"), + parser.Parse(d.String("p1dmmin")), + parser.Parse(d.String("p1dmmax")), d.String("p2dmelem"), - d.String("p2dmmin"), - d.String("p2dmmax"), + parser.Parse(d.String("p2dmmin")), + parser.Parse(d.String("p2dmmax")), d.String("p3dmelem"), - d.String("p3dmmin"), - d.String("p3dmmax"), + parser.Parse(d.String("p3dmmin")), + parser.Parse(d.String("p3dmmax")), d.String("descatt"), d.String("descmissile1"), d.String("descmissile2"), @@ -166,88 +171,88 @@ func LoadSkillDescriptions(file []byte) { //nolint:funlen // doesn't make sense d.String("descline1"), d.String("desctexta1"), d.String("desctextb1"), - d.String("desccalca1"), - d.String("desccalcb1"), + parser.Parse(d.String("desccalca1")), + parser.Parse(d.String("desccalcb1")), d.String("descline2"), d.String("desctexta2"), d.String("desctextb2"), - d.String("desccalca2"), - d.String("desccalcb2"), + parser.Parse(d.String("desccalca2")), + parser.Parse(d.String("desccalcb2")), d.String("descline3"), d.String("desctexta3"), d.String("desctextb3"), - d.String("desccalca3"), - d.String("desccalcb3"), + parser.Parse(d.String("desccalca3")), + parser.Parse(d.String("desccalcb3")), d.String("descline4"), d.String("desctexta4"), d.String("desctextb4"), - d.String("desccalca4"), - d.String("desccalcb4"), + parser.Parse(d.String("desccalca4")), + parser.Parse(d.String("desccalcb4")), d.String("descline5"), d.String("desctexta5"), d.String("desctextb5"), - d.String("desccalca5"), - d.String("desccalcb5"), + parser.Parse(d.String("desccalca5")), + parser.Parse(d.String("desccalcb5")), d.String("descline6"), d.String("desctexta6"), d.String("desctextb6"), - d.String("desccalca6"), - d.String("desccalcb6"), + parser.Parse(d.String("desccalca6")), + parser.Parse(d.String("desccalcb6")), d.String("dsc2line1"), d.String("dsc2texta1"), d.String("dsc2textb1"), - d.String("dsc2calca1"), - d.String("dsc2calcb1"), + parser.Parse(d.String("dsc2calca1")), + parser.Parse(d.String("dsc2calcb1")), d.String("dsc2line2"), d.String("dsc2texta2"), d.String("dsc2textb2"), - d.String("dsc2calca2"), - d.String("dsc2calcb2"), + parser.Parse(d.String("dsc2calca2")), + parser.Parse(d.String("dsc2calcb2")), d.String("dsc2line3"), d.String("dsc2texta3"), d.String("dsc2textb3"), - d.String("dsc2calca3"), - d.String("dsc2calcb3"), + parser.Parse(d.String("dsc2calca3")), + parser.Parse(d.String("dsc2calcb3")), d.String("dsc2line4"), d.String("dsc2texta4"), d.String("dsc2textb4"), - d.String("dsc2calca4"), - d.String("dsc2calcb4"), + parser.Parse(d.String("dsc2calca4")), + parser.Parse(d.String("dsc2calcb4")), d.String("dsc3line1"), d.String("dsc3texta1"), d.String("dsc3textb1"), - d.String("dsc3calca1"), - d.String("dsc3calcb1"), + parser.Parse(d.String("dsc3calca1")), + parser.Parse(d.String("dsc3calcb1")), d.String("dsc3line2"), d.String("dsc3texta2"), d.String("dsc3textb2"), - d.String("dsc3calca2"), - d.String("dsc3calcb2"), + parser.Parse(d.String("dsc3calca2")), + parser.Parse(d.String("dsc3calcb2")), d.String("dsc3line3"), d.String("dsc3texta3"), d.String("dsc3textb3"), - d.String("dsc3calca3"), - d.String("dsc3calcb3"), + parser.Parse(d.String("dsc3calca3")), + parser.Parse(d.String("dsc3calcb3")), d.String("dsc3line4"), d.String("dsc3texta4"), d.String("dsc3textb4"), - d.String("dsc3calca4"), - d.String("dsc3calcb4"), + parser.Parse(d.String("dsc3calca4")), + parser.Parse(d.String("dsc3calcb4")), d.String("dsc3line5"), d.String("dsc3texta5"), d.String("dsc3textb5"), - d.String("dsc3calca5"), - d.String("dsc3calcb5"), + parser.Parse(d.String("dsc3calca5")), + parser.Parse(d.String("dsc3calcb5")), d.String("dsc3line6"), d.String("dsc3texta6"), d.String("dsc3textb6"), - d.String("dsc3calca6"), - d.String("dsc3calcb6"), + parser.Parse(d.String("dsc3calca6")), + parser.Parse(d.String("dsc3calcb6")), d.String("dsc3line7"), d.String("dsc3texta7"), d.String("dsc3textb7"), - d.String("dsc3calca7"), - d.String("dsc3calcb7"), + parser.Parse(d.String("dsc3calca7")), + parser.Parse(d.String("dsc3calcb7")), } SkillDescriptions[record.Name] = record diff --git a/d2common/d2data/d2datadict/skills.go b/d2common/d2data/d2datadict/skills.go index 10f70ac9..c36f1696 100644 --- a/d2common/d2data/d2datadict/skills.go +++ b/d2common/d2data/d2datadict/skills.go @@ -4,6 +4,8 @@ import ( "log" "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation/d2parser" ) // SkillDetails has all of the SkillRecords @@ -16,9 +18,9 @@ type SkillRecord struct { Skill string Charclass string Skilldesc string - Prgcalc1 string - Prgcalc2 string - Prgcalc3 string + Prgcalc1 d2calculation.Calculation + Prgcalc2 d2calculation.Calculation + Prgcalc3 d2calculation.Calculation Srvmissile string Srvmissilea string Srvmissileb string @@ -26,20 +28,20 @@ type SkillRecord struct { Srvoverlay string Aurastate string Auratargetstate string - Auralencalc string - Aurarangecalc string + Auralencalc d2calculation.Calculation + Aurarangecalc d2calculation.Calculation Aurastat1 string - Aurastatcalc1 string + Aurastatcalc1 d2calculation.Calculation Aurastat2 string - Aurastatcalc2 string + Aurastatcalc2 d2calculation.Calculation Aurastat3 string - Aurastatcalc3 string + Aurastatcalc3 d2calculation.Calculation Aurastat4 string - Aurastatcalc4 string + Aurastatcalc4 d2calculation.Calculation Aurastat5 string - Aurastatcalc5 string + Aurastatcalc5 d2calculation.Calculation Aurastat6 string - Aurastatcalc6 string + Aurastatcalc6 d2calculation.Calculation Auraevent1 string Auraevent2 string Auraevent3 string @@ -48,31 +50,31 @@ type SkillRecord struct { Passivestate string Passiveitype string Passivestat1 string - Passivecalc1 string + Passivecalc1 d2calculation.Calculation Passivestat2 string - Passivecalc2 string + Passivecalc2 d2calculation.Calculation Passivestat3 string - Passivecalc3 string + Passivecalc3 d2calculation.Calculation Passivestat4 string - Passivecalc4 string + Passivecalc4 d2calculation.Calculation Passivestat5 string - Passivecalc5 string + Passivecalc5 d2calculation.Calculation Passiveevent string Passiveeventfunc string Summon string Pettype string - Petmax string + Petmax d2calculation.Calculation Summode string Sumskill1 string - Sumsk1calc string + Sumsk1calc d2calculation.Calculation Sumskill2 string - Sumsk2calc string + Sumsk2calc d2calculation.Calculation Sumskill3 string - Sumsk3calc string + Sumsk3calc d2calculation.Calculation Sumskill4 string - Sumsk4calc string + Sumsk4calc d2calculation.Calculation Sumskill5 string - Sumsk5calc string + Sumsk5calc d2calculation.Calculation Sumoverlay string Stsound string Stsoundclass string @@ -91,12 +93,9 @@ type SkillRecord struct { Cltmissileb string Cltmissilec string Cltmissiled string - Cltcalc1 string - Cltcalc1Desc string - Cltcalc2 string - Cltcalc2Desc string - Cltcalc3 string - Cltcalc3Desc string + Cltcalc1 d2calculation.Calculation + Cltcalc2 d2calculation.Calculation + Cltcalc3 d2calculation.Calculation Range string Itypea1 string Itypea2 string @@ -113,35 +112,23 @@ type SkillRecord struct { Monanim string ItemCastSound string ItemCastOverlay string - Skpoints string + Skpoints d2calculation.Calculation Reqskill1 string Reqskill2 string Reqskill3 string State1 string State2 string State3 string - Perdelay string - Calc1 string - Calc1Desc string - Calc2 string - Calc2Desc string - Calc3 string - Calc3Desc string - Calc4 string - Calc4Desc string - Param1Desc string - Param2Desc string - Param3Desc string - Param4Desc string - Param5Desc string - Param6Desc string - Param7Desc string - Param8Desc string - ToHitCalc string - DmgSymPerCalc string + Perdelay d2calculation.Calculation + Calc1 d2calculation.Calculation + Calc2 d2calculation.Calculation + Calc3 d2calculation.Calculation + Calc4 d2calculation.Calculation + ToHitCalc d2calculation.Calculation + DmgSymPerCalc d2calculation.Calculation EType string - EDmgSymPerCalc string - ELenSymPerCalc string + EDmgSymPerCalc d2calculation.Calculation + ELenSymPerCalc d2calculation.Calculation ID int Srvstfunc int Srvdofunc int @@ -277,8 +264,13 @@ type SkillRecord struct { func LoadSkills(file []byte) { SkillDetails = make(map[int]*SkillRecord) + parser := d2parser.New() + d := d2common.LoadDataDictionary(file) for d.Next() { + name := d.String("skill") + parser.SetCurrentReference("skill", name) + record := &SkillRecord{ Skill: d.String("skill"), ID: d.Number("Id"), @@ -290,9 +282,9 @@ func LoadSkills(file []byte) { Srvprgfunc1: d.Number("srvprgfunc1"), Srvprgfunc2: d.Number("srvprgfunc2"), Srvprgfunc3: d.Number("srvprgfunc3"), - Prgcalc1: d.String("prgcalc1"), - Prgcalc2: d.String("prgcalc2"), - Prgcalc3: d.String("prgcalc3"), + Prgcalc1: parser.Parse(d.String("prgcalc1")), + Prgcalc2: parser.Parse(d.String("prgcalc2")), + Prgcalc3: parser.Parse(d.String("prgcalc3")), Prgdam: d.Number("prgdam"), Srvmissile: d.String("srvmissile"), Decquant: d.Bool("decquant"), @@ -304,20 +296,20 @@ func LoadSkills(file []byte) { Aurafilter: d.Number("aurafilter"), Aurastate: d.String("aurastate"), Auratargetstate: d.String("auratargetstate"), - Auralencalc: d.String("auralencalc"), - Aurarangecalc: d.String("aurarangecalc"), + Auralencalc: parser.Parse(d.String("auralencalc")), + Aurarangecalc: parser.Parse(d.String("aurarangecalc")), Aurastat1: d.String("aurastat1"), - Aurastatcalc1: d.String("aurastatcalc1"), + Aurastatcalc1: parser.Parse(d.String("aurastatcalc1")), Aurastat2: d.String("aurastat2"), - Aurastatcalc2: d.String("aurastatcalc2"), + Aurastatcalc2: parser.Parse(d.String("aurastatcalc2")), Aurastat3: d.String("aurastat3"), - Aurastatcalc3: d.String("aurastatcalc3"), + Aurastatcalc3: parser.Parse(d.String("aurastatcalc3")), Aurastat4: d.String("aurastat4"), - Aurastatcalc4: d.String("aurastatcalc4"), + Aurastatcalc4: parser.Parse(d.String("aurastatcalc4")), Aurastat5: d.String("aurastat5"), - Aurastatcalc5: d.String("aurastatcalc5"), + Aurastatcalc5: parser.Parse(d.String("aurastatcalc5")), Aurastat6: d.String("aurastat6"), - Aurastatcalc6: d.String("aurastatcalc6"), + Aurastatcalc6: parser.Parse(d.String("aurastatcalc6")), Auraevent1: d.String("auraevent1"), Auraeventfunc1: d.Number("auraeventfunc1"), Auraevent2: d.String("auraevent2"), @@ -329,31 +321,31 @@ func LoadSkills(file []byte) { Passivestate: d.String("passivestate"), Passiveitype: d.String("passiveitype"), Passivestat1: d.String("passivestat1"), - Passivecalc1: d.String("passivecalc1"), + Passivecalc1: parser.Parse(d.String("passivecalc1")), Passivestat2: d.String("passivestat2"), - Passivecalc2: d.String("passivecalc2"), + Passivecalc2: parser.Parse(d.String("passivecalc2")), Passivestat3: d.String("passivestat3"), - Passivecalc3: d.String("passivecalc3"), + Passivecalc3: parser.Parse(d.String("passivecalc3")), Passivestat4: d.String("passivestat4"), - Passivecalc4: d.String("passivecalc4"), + Passivecalc4: parser.Parse(d.String("passivecalc4")), Passivestat5: d.String("passivestat5"), - Passivecalc5: d.String("passivecalc5"), + Passivecalc5: parser.Parse(d.String("passivecalc5")), Passiveevent: d.String("passiveevent"), Passiveeventfunc: d.String("passiveeventfunc"), Summon: d.String("summon"), Pettype: d.String("pettype"), - Petmax: d.String("petmax"), + Petmax: parser.Parse(d.String("petmax")), Summode: d.String("summode"), Sumskill1: d.String("sumskill1"), - Sumsk1calc: d.String("sumsk1calc"), + Sumsk1calc: parser.Parse(d.String("sumsk1calc")), Sumskill2: d.String("sumskill2"), - Sumsk2calc: d.String("sumsk2calc"), + Sumsk2calc: parser.Parse(d.String("sumsk2calc")), Sumskill3: d.String("sumskill3"), - Sumsk3calc: d.String("sumsk3calc"), + Sumsk3calc: parser.Parse(d.String("sumsk3calc")), Sumskill4: d.String("sumskill4"), - Sumsk4calc: d.String("sumsk4calc"), + Sumsk4calc: parser.Parse(d.String("sumsk4calc")), Sumskill5: d.String("sumskill5"), - Sumsk5calc: d.String("sumsk5calc"), + Sumsk5calc: parser.Parse(d.String("sumsk5calc")), Sumumod: d.Number("sumumod"), Sumoverlay: d.String("sumoverlay"), Stsuccessonly: d.Bool("stsuccessonly"), @@ -381,12 +373,9 @@ func LoadSkills(file []byte) { Cltmissileb: d.String("cltmissileb"), Cltmissilec: d.String("cltmissilec"), Cltmissiled: d.String("cltmissiled"), - Cltcalc1: d.String("cltcalc1"), - Cltcalc1Desc: d.String("*cltcalc1 desc"), - Cltcalc2: d.String("cltcalc2"), - Cltcalc2Desc: d.String("*cltcalc2 desc"), - Cltcalc3: d.String("cltcalc3"), - Cltcalc3Desc: d.String("*cltcalc3 desc"), + Cltcalc1: parser.Parse(d.String("cltcalc1")), + Cltcalc2: parser.Parse(d.String("cltcalc2")), + Cltcalc3: parser.Parse(d.String("cltcalc3")), Warp: d.Bool("warp"), Immediate: d.Bool("immediate"), Enhanceable: d.Bool("enhanceable"), @@ -431,7 +420,7 @@ func LoadSkills(file []byte) { ItemCltCheckStart: d.Bool("ItemCltCheckStart"), ItemCastSound: d.String("ItemCastSound"), ItemCastOverlay: d.String("ItemCastOverlay"), - Skpoints: d.String("skpoints"), + Skpoints: parser.Parse(d.String("skpoints")), Reqlevel: d.Number("reqlevel"), Maxlvl: d.Number("maxlvl"), Reqstr: d.Number("reqstr"), @@ -460,40 +449,28 @@ func LoadSkills(file []byte) { InTown: d.Bool("InTown"), Aura: d.Bool("aura"), Periodic: d.Bool("periodic"), - Perdelay: d.String("perdelay"), + Perdelay: parser.Parse(d.String("perdelay")), Finishing: d.Bool("finishing"), Passive: d.Bool("passive"), Progressive: d.Bool("progressive"), General: d.Bool("general"), Scroll: d.Bool("scroll"), - Calc1: d.String("calc1"), - Calc1Desc: d.String("*calc1 desc"), - Calc2: d.String("calc2"), - Calc2Desc: d.String("*calc2 desc"), - Calc3: d.String("calc3"), - Calc3Desc: d.String("*calc3 desc"), - Calc4: d.String("calc4"), - Calc4Desc: d.String("*calc4 desc"), + Calc1: parser.Parse(d.String("calc1")), + Calc2: parser.Parse(d.String("calc2")), + Calc3: parser.Parse(d.String("calc3")), + Calc4: parser.Parse(d.String("calc4")), Param1: d.Number("Param1"), - Param1Desc: d.String("*Param1 Description"), Param2: d.Number("Param2"), - Param2Desc: d.String("*Param2 Description"), Param3: d.Number("Param3"), - Param3Desc: d.String("*Param3 Description"), Param4: d.Number("Param4"), - Param4Desc: d.String("*Param4 Description"), Param5: d.Number("Param5"), - Param5Desc: d.String("*Param5 Description"), Param6: d.Number("Param6"), - Param6Desc: d.String("*Param6 Description"), Param7: d.Number("Param7"), - Param7Desc: d.String("*Param7 Description"), Param8: d.Number("Param8"), - Param8Desc: d.String("*Param8 Description"), InGame: d.Bool("InGame"), ToHit: d.Number("ToHit"), LevToHit: d.Number("LevToHit"), - ToHitCalc: d.String("ToHitCalc"), + ToHitCalc: parser.Parse(d.String("ToHitCalc")), ResultFlags: d.Number("ResultFlags"), HitFlags: d.Number("HitFlags"), HitClass: d.Number("HitClass"), @@ -512,7 +489,7 @@ func LoadSkills(file []byte) { MaxLevDam3: d.Number("MaxLevDam3"), MaxLevDam4: d.Number("MaxLevDam4"), MaxLevDam5: d.Number("MaxLevDam5"), - DmgSymPerCalc: d.String("DmgSymPerCalc"), + DmgSymPerCalc: parser.Parse(d.String("DmgSymPerCalc")), EType: d.String("EType"), EMin: d.Number("EMin"), EMinLev1: d.Number("EMinLev1"), @@ -526,12 +503,12 @@ func LoadSkills(file []byte) { EMaxLev3: d.Number("EMaxLev3"), EMaxLev4: d.Number("EMaxLev4"), EMaxLev5: d.Number("EMaxLev5"), - EDmgSymPerCalc: d.String("EDmgSymPerCalc"), + EDmgSymPerCalc: parser.Parse(d.String("EDmgSymPerCalc")), ELen: d.Number("ELen"), ELevLen1: d.Number("ELevLen1"), ELevLen2: d.Number("ELevLen2"), ELevLen3: d.Number("ELevLen3"), - ELenSymPerCalc: d.String("ELenSymPerCalc"), + ELenSymPerCalc: parser.Parse(d.String("ELenSymPerCalc")), Aitype: d.Number("aitype"), Aibonus: d.Number("aibonus"), CostMult: d.Number("cost mult"),