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,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

View File

@ -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"),