mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-10 14:26:15 -05:00
Stats refactor (#617)
* add interface for stats, d2 is an implementation * fix incorrect comment, remove ennecessary int * simplified description functions, remove duplicates * moved default stringer functions * fixed incorrect stat combine method * change `Create` to `New` in method names * d2stats + diablo2stats refactored again - simplified `NewStat` provider function - added initializer for stat values that sets the stringer functions, value types, and combination types for values when created - removed redundant description functions - added stat value combination types `sum` and `static` `static` stat values which are not altered when stats are combined. this makes sense for stats like proc-on-hit or +skills to class example: Stat A: `10% reanimate as: skeleton mage` Stat B: `8% reanimate as: skeleton archer` Stat C: `6% reanimate as: skeleton archer` A and B can not be combined B and C can be combined to `14% reanimate as: skeleton archer`
This commit is contained in:
parent
c114ab9eb7
commit
9e61079e93
@ -6,16 +6,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewStat creates a stat instance with the given record and values
|
// NewStat creates a stat instance with the given record and values
|
||||||
func NewStat(record *d2datadict.ItemStatCostRecord, values ...d2stats.StatValue) d2stats.Stat {
|
func NewStat(key string, values ...float64) d2stats.Stat {
|
||||||
|
record := d2datadict.ItemStatCosts[key]
|
||||||
|
|
||||||
if record == nil {
|
if record == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
stat := &Diablo2Stat{
|
stat := &diablo2Stat{
|
||||||
record: record,
|
record: record,
|
||||||
values: values,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stat.init(values...) // init stat values, value types, and value combination rules
|
||||||
|
|
||||||
return stat
|
return stat
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,22 +27,21 @@ func NewStatList(stats ...d2stats.Stat) d2stats.StatList {
|
|||||||
return &Diablo2StatList{stats}
|
return &Diablo2StatList{stats}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStatValue creates a stat value of the given type
|
// NewValue creates a stat value of the given type
|
||||||
func NewStatValue(t d2stats.StatValueType) d2stats.StatValue {
|
func NewValue(t d2stats.StatNumberType, c d2stats.ValueCombineType) d2stats.StatValue {
|
||||||
sv := &Diablo2StatValue{_type: t}
|
sv := &Diablo2StatValue{
|
||||||
|
numberType: t,
|
||||||
|
combineType: c,
|
||||||
|
}
|
||||||
|
|
||||||
switch t {
|
switch t {
|
||||||
case d2stats.StatValueFloat:
|
case d2stats.StatValueFloat:
|
||||||
sv._stringer = stringerUnsignedFloat
|
sv.stringerFn = stringerUnsignedFloat
|
||||||
case d2stats.StatValueInt:
|
case d2stats.StatValueInt:
|
||||||
sv._stringer = stringerUnsignedInt
|
sv.stringerFn = stringerUnsignedInt
|
||||||
default:
|
default:
|
||||||
sv._stringer = stringerEmpty
|
sv.stringerFn = stringerEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
return sv
|
return sv
|
||||||
}
|
}
|
||||||
|
|
||||||
func intVal(i int) d2stats.StatValue {
|
|
||||||
return NewStatValue(d2stats.StatValueInt).SetInt(i)
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,6 @@ package diablo2stats
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||||
@ -10,8 +9,8 @@ import (
|
|||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
// static check that Diablo2Stat implements Stat
|
// static check that diablo2Stat implements Stat
|
||||||
var _ d2stats.Stat = &Diablo2Stat{}
|
var _ d2stats.Stat = &diablo2Stat{}
|
||||||
|
|
||||||
type descValPosition int
|
type descValPosition int
|
||||||
|
|
||||||
@ -23,6 +22,10 @@ const (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
maxSkillTabIndex = 2
|
maxSkillTabIndex = 2
|
||||||
|
oneValue = 1
|
||||||
|
twoValue = 2
|
||||||
|
threeValue = 3
|
||||||
|
fourValue = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -31,47 +34,188 @@ const (
|
|||||||
fourComponentStr = "%s %s %s %s"
|
fourComponentStr = "%s %s %s %s"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Diablo2Stat is an instance of a Diablo2Stat, with a set of values
|
const (
|
||||||
type Diablo2Stat struct {
|
intVal = d2stats.StatValueInt
|
||||||
|
sum = d2stats.StatValueCombineSum
|
||||||
|
static = d2stats.StatValueCombineStatic
|
||||||
|
)
|
||||||
|
|
||||||
|
// diablo2Stat is an implementation of an OpenDiablo2 Stat, with a set of values.
|
||||||
|
// It is pretty tightly coupled to the data files for d2
|
||||||
|
type diablo2Stat struct {
|
||||||
record *d2datadict.ItemStatCostRecord
|
record *d2datadict.ItemStatCostRecord
|
||||||
values []d2stats.StatValue
|
values []d2stats.StatValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// depending on the stat record, sets up the proper number of values,
|
||||||
|
// as well as set up the stat value number types, value combination types, and
|
||||||
|
// the value stringer functions used
|
||||||
|
func (s *diablo2Stat) init(numbers ...float64) {
|
||||||
|
if s.record == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gomdn introducing a const for these would be worse
|
||||||
|
switch s.record.DescFnID {
|
||||||
|
case 1:
|
||||||
|
// +31 to Strength
|
||||||
|
// Replenish Life +20 || Drain Life -8
|
||||||
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned)
|
||||||
|
case 2:
|
||||||
|
// +16% Increased Chance of Blocking
|
||||||
|
// Lightning Absorb +10%
|
||||||
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageSigned)
|
||||||
|
case 3:
|
||||||
|
// Damage Reduced by 25
|
||||||
|
// Slain Monsters Rest in Peace
|
||||||
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum)
|
||||||
|
case 4:
|
||||||
|
// Poison Resist +25%
|
||||||
|
// +25% Faster Run/Walk
|
||||||
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageSigned)
|
||||||
|
case 5:
|
||||||
|
// Hit Causes Monster to Flee 25%
|
||||||
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum)
|
||||||
|
s.values[0].SetStringer(stringerIntPercentageUnsigned)
|
||||||
|
case 6:
|
||||||
|
// +25 to Life (Based on Character Level)
|
||||||
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned)
|
||||||
|
case 7:
|
||||||
|
// Lightning Resist +25% (Based on Character Level)
|
||||||
|
// +25% Better Chance of Getting Magic Items (Based on Character Level)
|
||||||
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageSigned)
|
||||||
|
case 8:
|
||||||
|
// +25% Enhanced Defense (Based on Character Level)
|
||||||
|
// Heal Stamina Plus +25% (Based on Character Level)
|
||||||
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageSigned)
|
||||||
|
case 9:
|
||||||
|
// Attacker Takes Damage of 25 (Based on Character Level)
|
||||||
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum)
|
||||||
|
case 11:
|
||||||
|
// Repairs 2 durability per second
|
||||||
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum)
|
||||||
|
case 12:
|
||||||
|
// Hit Blinds Target +5
|
||||||
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned)
|
||||||
|
case 13:
|
||||||
|
// +5 to Paladin Skill Levels
|
||||||
|
s.values = make([]d2stats.StatValue, twoValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned)
|
||||||
|
s.values[1] = NewValue(intVal, sum).SetStringer(stringerClassAllSkills)
|
||||||
|
case 14:
|
||||||
|
// +5 to Combat Skills (Paladin Only)
|
||||||
|
s.values = make([]d2stats.StatValue, threeValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned)
|
||||||
|
s.values[1] = NewValue(intVal, sum).SetStringer(stringerClassOnly)
|
||||||
|
s.values[2] = NewValue(intVal, static)
|
||||||
|
case 15:
|
||||||
|
// 5% Chance to cast level 7 Frozen Orb on attack
|
||||||
|
s.values = make([]d2stats.StatValue, threeValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum)
|
||||||
|
s.values[1] = NewValue(intVal, static)
|
||||||
|
s.values[2] = NewValue(intVal, static).SetStringer(stringerSkillName)
|
||||||
|
case 16:
|
||||||
|
// Level 3 Warmth Aura When Equipped
|
||||||
|
s.values = make([]d2stats.StatValue, twoValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum)
|
||||||
|
s.values[1] = NewValue(intVal, static).SetStringer(stringerSkillName)
|
||||||
|
case 20:
|
||||||
|
// -25% Target Defense
|
||||||
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageSigned)
|
||||||
|
case 22:
|
||||||
|
// 25% to Attack Rating versus Specter
|
||||||
|
s.values = make([]d2stats.StatValue, twoValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageUnsigned)
|
||||||
|
s.values[1] = NewValue(intVal, static).SetStringer(stringerMonsterName)
|
||||||
|
case 23:
|
||||||
|
// 25% Reanimate as: Specter
|
||||||
|
s.values = make([]d2stats.StatValue, twoValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageUnsigned)
|
||||||
|
s.values[1] = NewValue(intVal, static).SetStringer(stringerMonsterName)
|
||||||
|
case 24:
|
||||||
|
// Level 25 Frozen Orb (19/20 Charges)
|
||||||
|
s.values = make([]d2stats.StatValue, fourValue)
|
||||||
|
s.values[0] = NewValue(intVal, static)
|
||||||
|
s.values[1] = NewValue(intVal, static).SetStringer(stringerSkillName)
|
||||||
|
s.values[2] = NewValue(intVal, static)
|
||||||
|
s.values[3] = NewValue(intVal, static)
|
||||||
|
case 27:
|
||||||
|
// +25 to Frozen Orb (Paladin Only)
|
||||||
|
s.values = make([]d2stats.StatValue, threeValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned)
|
||||||
|
s.values[1] = NewValue(intVal, static).SetStringer(stringerSkillName)
|
||||||
|
s.values[2] = NewValue(intVal, static).SetStringer(stringerClassOnly)
|
||||||
|
case 28:
|
||||||
|
// +25 to Frozen Orb
|
||||||
|
s.values = make([]d2stats.StatValue, twoValue)
|
||||||
|
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned)
|
||||||
|
s.values[1] = NewValue(intVal, static).SetStringer(stringerSkillName)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx := range numbers {
|
||||||
|
if idx > len(s.values) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
s.values[idx].SetFloat(numbers[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Name returns the name of the stat (the key in itemstatcosts)
|
// Name returns the name of the stat (the key in itemstatcosts)
|
||||||
func (s *Diablo2Stat) Name() string {
|
func (s *diablo2Stat) Name() string {
|
||||||
return s.record.Name
|
return s.record.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priority returns the description printing priority
|
// Priority returns the description printing priority
|
||||||
func (s *Diablo2Stat) Priority() int {
|
func (s *diablo2Stat) Priority() int {
|
||||||
return s.record.DescPriority
|
return s.record.DescPriority
|
||||||
}
|
}
|
||||||
|
|
||||||
// Values returns the stat values of the stat
|
// Values returns the stat values of the stat
|
||||||
func (s *Diablo2Stat) Values() []d2stats.StatValue {
|
func (s *diablo2Stat) Values() []d2stats.StatValue {
|
||||||
return s.values
|
return s.values
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetValues sets the stat values
|
// SetValues sets the stat values
|
||||||
func (s *Diablo2Stat) SetValues(values ...d2stats.StatValue) {
|
func (s *diablo2Stat) SetValues(values ...d2stats.StatValue) {
|
||||||
s.values = make([]d2stats.StatValue, len(values))
|
s.values = make([]d2stats.StatValue, len(values))
|
||||||
for idx := range values {
|
for idx := range values {
|
||||||
s.values[idx] = values[idx]
|
s.values[idx] = values[idx]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone returns a deep copy of the Diablo2Stat
|
// Clone returns a deep copy of the diablo2Stat
|
||||||
func (s *Diablo2Stat) Clone() d2stats.Stat {
|
func (s *diablo2Stat) Clone() d2stats.Stat {
|
||||||
clone := &Diablo2Stat{
|
clone := &diablo2Stat{
|
||||||
record: s.record,
|
record: s.record,
|
||||||
values: make([]d2stats.StatValue, len(s.Values())),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clone.init()
|
||||||
|
|
||||||
for idx := range s.values {
|
for idx := range s.values {
|
||||||
srcVal := s.values[idx]
|
srcVal := s.values[idx]
|
||||||
dstVal := reflect.New(reflect.ValueOf(srcVal).Elem().Type()).Interface().(d2stats.StatValue)
|
dstVal := &Diablo2StatValue{
|
||||||
|
numberType: srcVal.NumberType(),
|
||||||
|
combineType: srcVal.CombineType(),
|
||||||
|
}
|
||||||
|
|
||||||
switch srcVal.Type() {
|
dstVal.SetStringer(srcVal.Stringer())
|
||||||
|
|
||||||
|
switch srcVal.NumberType() {
|
||||||
case d2stats.StatValueInt:
|
case d2stats.StatValueInt:
|
||||||
dstVal.SetInt(srcVal.Int())
|
dstVal.SetInt(srcVal.Int())
|
||||||
case d2stats.StatValueFloat:
|
case d2stats.StatValueFloat:
|
||||||
@ -85,14 +229,14 @@ func (s *Diablo2Stat) Clone() d2stats.Stat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy to this stat value the values of the given stat value
|
// Copy to this stat value the values of the given stat value
|
||||||
func (s *Diablo2Stat) Copy(from d2stats.Stat) d2stats.Stat {
|
func (s *diablo2Stat) Copy(from d2stats.Stat) d2stats.Stat {
|
||||||
srcValues := from.Values()
|
srcValues := from.Values()
|
||||||
s.values = make([]d2stats.StatValue, len(srcValues))
|
s.values = make([]d2stats.StatValue, len(srcValues))
|
||||||
|
|
||||||
for idx := range srcValues {
|
for idx := range srcValues {
|
||||||
src := srcValues[idx]
|
src := srcValues[idx]
|
||||||
valType := src.Type()
|
valType := src.NumberType()
|
||||||
dst := &Diablo2StatValue{_type: valType}
|
dst := &Diablo2StatValue{numberType: valType}
|
||||||
dst.SetStringer(src.Stringer())
|
dst.SetStringer(src.Stringer())
|
||||||
|
|
||||||
switch valType {
|
switch valType {
|
||||||
@ -109,7 +253,7 @@ func (s *Diablo2Stat) Copy(from d2stats.Stat) d2stats.Stat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Combine sums the other stat with this one (does not alter this stat, returns altered clone!)
|
// Combine sums the other stat with this one (does not alter this stat, returns altered clone!)
|
||||||
func (s *Diablo2Stat) Combine(other d2stats.Stat) (result d2stats.Stat, err error) {
|
func (s *diablo2Stat) Combine(other d2stats.Stat) (result d2stats.Stat, err error) {
|
||||||
cantBeCombinedErr := fmt.Errorf("cannot combine %s with %s", s.Name(), other.Name())
|
cantBeCombinedErr := fmt.Errorf("cannot combine %s with %s", s.Name(), other.Name())
|
||||||
|
|
||||||
if !s.canBeCombinedWith(other) {
|
if !s.canBeCombinedWith(other) {
|
||||||
@ -121,20 +265,31 @@ func (s *Diablo2Stat) Combine(other d2stats.Stat) (result d2stats.Stat, err erro
|
|||||||
|
|
||||||
for idx := range result.Values() {
|
for idx := range result.Values() {
|
||||||
v1, v2 := dstValues[idx], srcValues[idx]
|
v1, v2 := dstValues[idx], srcValues[idx]
|
||||||
|
combinationRule := v1.CombineType()
|
||||||
|
|
||||||
valType := v1.Type()
|
if combinationRule == d2stats.StatValueCombineStatic {
|
||||||
switch valType {
|
// we do not add the values, they remain the same
|
||||||
case d2stats.StatValueInt:
|
// for things like monster/class/skill index or on proc stats
|
||||||
v1.SetInt(v1.Int() + v2.Int())
|
// where the level of a skill isn't summed, but the
|
||||||
case d2stats.StatValueFloat:
|
// chance to cast values are
|
||||||
v1.SetFloat(v1.Float() + v2.Float())
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if combinationRule == d2stats.StatValueCombineSum {
|
||||||
|
valType := v1.NumberType()
|
||||||
|
switch valType {
|
||||||
|
case d2stats.StatValueInt:
|
||||||
|
v1.SetInt(v1.Int() + v2.Int())
|
||||||
|
case d2stats.StatValueFloat:
|
||||||
|
v1.SetFloat(v1.Float() + v2.Float())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Diablo2Stat) canBeCombinedWith(other d2stats.Stat) bool {
|
func (s *diablo2Stat) canBeCombinedWith(other d2stats.Stat) bool {
|
||||||
if s.Name() != other.Name() {
|
if s.Name() != other.Name() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -145,86 +300,65 @@ func (s *Diablo2Stat) canBeCombinedWith(other d2stats.Stat) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for idx := range values1 {
|
for idx := range values1 {
|
||||||
if values1[idx].Type() != values2[idx].Type() {
|
if values1[idx].NumberType() != values2[idx].NumberType() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if values1[idx].CombineType() != values2[idx].CombineType() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// in the case that we are trying to combine stats like:
|
||||||
|
// +1 to Paladin Skills
|
||||||
|
// +1 to Sorceress Skills
|
||||||
|
// the numeric value (an index) that denotes the class skill type knows not to be summed
|
||||||
|
// with the other index, even though the format of the stat and stat value is pretty much
|
||||||
|
// the same.
|
||||||
|
if values1[idx].CombineType() == d2stats.StatValueCombineStatic {
|
||||||
|
if values1[idx].Float() != values2[idx].Float() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the formatted description string
|
// String returns the formatted description string
|
||||||
func (s *Diablo2Stat) String() string { //nolint:gocyclo switch statement is not so bad
|
func (s *diablo2Stat) String() string { //nolint:gocyclo switch statement is not so bad
|
||||||
var result string
|
var result string
|
||||||
|
|
||||||
|
for idx := range s.values {
|
||||||
|
if s.values[idx].Stringer() == nil {
|
||||||
|
s.values[idx].SetStringer(stringerUnsignedInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:gomdn introducing a const for these would be worse
|
//nolint:gomdn introducing a const for these would be worse
|
||||||
switch s.record.DescFnID {
|
switch s.record.DescFnID {
|
||||||
case 1:
|
case 1, 2, 3, 4, 5, 12, 20:
|
||||||
s.values[0].SetStringer(stringerIntSigned)
|
|
||||||
result = s.descFn1()
|
result = s.descFn1()
|
||||||
case 2:
|
case 6, 7, 8:
|
||||||
s.values[0].SetStringer(stringerIntPercentageSigned)
|
|
||||||
result = s.descFn2()
|
|
||||||
case 3:
|
|
||||||
result = s.descFn3()
|
|
||||||
case 4:
|
|
||||||
s.values[0].SetStringer(stringerIntPercentageSigned)
|
|
||||||
result = s.descFn4()
|
|
||||||
case 5:
|
|
||||||
s.values[0].SetStringer(stringerIntPercentageUnsigned)
|
|
||||||
result = s.descFn5()
|
|
||||||
case 6:
|
|
||||||
s.values[0].SetStringer(stringerIntSigned)
|
|
||||||
result = s.descFn6()
|
result = s.descFn6()
|
||||||
case 7:
|
|
||||||
s.values[0].SetStringer(stringerIntPercentageSigned)
|
|
||||||
result = s.descFn7()
|
|
||||||
case 8:
|
|
||||||
s.values[0].SetStringer(stringerIntPercentageSigned)
|
|
||||||
result = s.descFn8()
|
|
||||||
case 9:
|
case 9:
|
||||||
result = s.descFn9()
|
result = s.descFn9()
|
||||||
case 11:
|
case 11:
|
||||||
result = s.descFn11()
|
result = s.descFn11()
|
||||||
case 12:
|
|
||||||
s.values[0].SetStringer(stringerIntSigned)
|
|
||||||
result = s.descFn12()
|
|
||||||
case 13:
|
case 13:
|
||||||
s.values[0].SetStringer(stringerIntSigned)
|
|
||||||
s.values[1].SetStringer(stringerClassAllSkills)
|
|
||||||
result = s.descFn13()
|
result = s.descFn13()
|
||||||
case 14:
|
case 14:
|
||||||
s.values[0].SetStringer(stringerIntSigned)
|
|
||||||
s.values[1].SetStringer(stringerClassOnly)
|
|
||||||
result = s.descFn14()
|
result = s.descFn14()
|
||||||
case 15:
|
case 15:
|
||||||
s.values[2].SetStringer(stringerSkillName)
|
|
||||||
result = s.descFn15()
|
result = s.descFn15()
|
||||||
case 16:
|
case 16:
|
||||||
s.values[1].SetStringer(stringerSkillName)
|
|
||||||
result = s.descFn16()
|
result = s.descFn16()
|
||||||
case 20:
|
case 22, 23:
|
||||||
s.values[0].SetStringer(stringerIntPercentageSigned)
|
|
||||||
result = s.descFn20()
|
|
||||||
case 22:
|
|
||||||
s.values[0].SetStringer(stringerIntPercentageUnsigned)
|
|
||||||
s.values[1].SetStringer(stringerMonsterName)
|
|
||||||
result = s.descFn22()
|
result = s.descFn22()
|
||||||
case 23:
|
|
||||||
s.values[0].SetStringer(stringerIntPercentageUnsigned)
|
|
||||||
s.values[1].SetStringer(stringerMonsterName)
|
|
||||||
result = s.descFn23()
|
|
||||||
case 24:
|
case 24:
|
||||||
s.values[1].SetStringer(stringerSkillName)
|
|
||||||
result = s.descFn24()
|
result = s.descFn24()
|
||||||
case 27:
|
case 27:
|
||||||
s.values[0].SetStringer(stringerIntSigned)
|
|
||||||
s.values[1].SetStringer(stringerSkillName)
|
|
||||||
s.values[2].SetStringer(stringerClassOnly)
|
|
||||||
result = s.descFn27()
|
result = s.descFn27()
|
||||||
case 28:
|
case 28:
|
||||||
s.values[0].SetStringer(stringerIntSigned)
|
|
||||||
s.values[1].SetStringer(stringerSkillName)
|
|
||||||
result = s.descFn28()
|
result = s.descFn28()
|
||||||
default:
|
default:
|
||||||
result = ""
|
result = ""
|
||||||
@ -233,9 +367,7 @@ func (s *Diablo2Stat) String() string { //nolint:gocyclo switch statement is not
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// +31 to Strength
|
func (s *diablo2Stat) descFn1() string {
|
||||||
// Replenish Life +20 || Drain Life -8
|
|
||||||
func (s *Diablo2Stat) descFn1() string {
|
|
||||||
var stringTableKey, result string
|
var stringTableKey, result string
|
||||||
|
|
||||||
value := s.values[0]
|
value := s.values[0]
|
||||||
@ -264,35 +396,7 @@ func (s *Diablo2Stat) descFn1() string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// +16% Increased Chance of Blocking
|
func (s *diablo2Stat) descFn6() string {
|
||||||
// Lightning Absorb +10%
|
|
||||||
func (s *Diablo2Stat) descFn2() string {
|
|
||||||
// for now, same as fn1
|
|
||||||
return s.descFn1()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Damage Reduced by 25
|
|
||||||
// Slain Monsters Rest in Peace
|
|
||||||
func (s *Diablo2Stat) descFn3() string {
|
|
||||||
// for now, same as fn1
|
|
||||||
return s.descFn1()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Poison Resist +25%
|
|
||||||
// +25% Faster Run/Walk
|
|
||||||
func (s *Diablo2Stat) descFn4() string {
|
|
||||||
// for now, same as fn1
|
|
||||||
return s.descFn1()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hit Causes Monster to Flee 25%
|
|
||||||
func (s *Diablo2Stat) descFn5() string {
|
|
||||||
// for now, same as fn1
|
|
||||||
return s.descFn1()
|
|
||||||
}
|
|
||||||
|
|
||||||
// +25 to Life (Based on Character Level)
|
|
||||||
func (s *Diablo2Stat) descFn6() string {
|
|
||||||
var stringTableKey, result string
|
var stringTableKey, result string
|
||||||
|
|
||||||
value := s.values[0]
|
value := s.values[0]
|
||||||
@ -323,22 +427,7 @@ func (s *Diablo2Stat) descFn6() string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lightning Resist +25% (Based on Character Level)
|
func (s *diablo2Stat) descFn9() string {
|
||||||
// +25% Better Chance of Getting Magic Items (Based on Character Level)
|
|
||||||
func (s *Diablo2Stat) descFn7() string {
|
|
||||||
// for now, same as fn6
|
|
||||||
return s.descFn6()
|
|
||||||
}
|
|
||||||
|
|
||||||
// +25% Enhanced Defense (Based on Character Level)
|
|
||||||
// Heal Stamina Plus +25% (Based on Character Level)
|
|
||||||
func (s *Diablo2Stat) descFn8() string {
|
|
||||||
// for now, same as fn6
|
|
||||||
return s.descFn6()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attacker Takes Damage of 25 (Based on Character Level)
|
|
||||||
func (s *Diablo2Stat) descFn9() string {
|
|
||||||
var stringTableKey, result string
|
var stringTableKey, result string
|
||||||
|
|
||||||
value := s.values[0]
|
value := s.values[0]
|
||||||
@ -368,8 +457,7 @@ func (s *Diablo2Stat) descFn9() string {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repairs 2 durability per second
|
func (s *diablo2Stat) descFn11() string {
|
||||||
func (s *Diablo2Stat) descFn11() string {
|
|
||||||
var stringTableKey string
|
var stringTableKey string
|
||||||
|
|
||||||
value := s.values[0]
|
value := s.values[0]
|
||||||
@ -387,14 +475,7 @@ func (s *Diablo2Stat) descFn11() string {
|
|||||||
return fmt.Sprintf(formatString, value)
|
return fmt.Sprintf(formatString, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hit Blinds Target +5
|
func (s *diablo2Stat) descFn13() string {
|
||||||
func (s *Diablo2Stat) descFn12() string {
|
|
||||||
// for now, same as fn1
|
|
||||||
return s.descFn1()
|
|
||||||
}
|
|
||||||
|
|
||||||
// +5 to Paladin Skill Levels
|
|
||||||
func (s *Diablo2Stat) descFn13() string {
|
|
||||||
value := s.values[0]
|
value := s.values[0]
|
||||||
allSkills := s.values[1]
|
allSkills := s.values[1]
|
||||||
|
|
||||||
@ -412,8 +493,7 @@ func (s *Diablo2Stat) descFn13() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// +5 to Combat Skills (Paladin Only)
|
func (s *diablo2Stat) descFn14() string {
|
||||||
func (s *Diablo2Stat) descFn14() string {
|
|
||||||
// strings come out like `+5 to Combat Skills (Paladin Only)`
|
// strings come out like `+5 to Combat Skills (Paladin Only)`
|
||||||
numSkills, hero, skillTab := s.values[0], s.values[1], s.values[2]
|
numSkills, hero, skillTab := s.values[0], s.values[1], s.values[2]
|
||||||
heroMap := getHeroMap()
|
heroMap := getHeroMap()
|
||||||
@ -440,8 +520,7 @@ func (s *Diablo2Stat) descFn14() string {
|
|||||||
return fmt.Sprintf(threeComponentStr, numSkillsStr, skillTabStr, classOnlyStr)
|
return fmt.Sprintf(threeComponentStr, numSkillsStr, skillTabStr, classOnlyStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5% Chance to cast level 7 Frozen Orb on attack
|
func (s *diablo2Stat) descFn15() string {
|
||||||
func (s *Diablo2Stat) descFn15() string {
|
|
||||||
chance, lvl, skill := s.values[0], s.values[1], s.values[2]
|
chance, lvl, skill := s.values[0], s.values[1], s.values[2]
|
||||||
|
|
||||||
// Special case, `chance to cast` format is actually in the string table!
|
// Special case, `chance to cast` format is actually in the string table!
|
||||||
@ -450,8 +529,7 @@ func (s *Diablo2Stat) descFn15() string {
|
|||||||
return fmt.Sprintf(chanceToCastStr, chance.Int(), lvl.Int(), skill)
|
return fmt.Sprintf(chanceToCastStr, chance.Int(), lvl.Int(), skill)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Level 3 Warmth Aura When Equipped
|
func (s *diablo2Stat) descFn16() string {
|
||||||
func (s *Diablo2Stat) descFn16() string {
|
|
||||||
skillLevel, skillIndex := s.values[0], s.values[1]
|
skillLevel, skillIndex := s.values[0], s.values[1]
|
||||||
|
|
||||||
// Special case, `Level # XYZ Aura When Equipped`, format is actually in the string table!
|
// Special case, `Level # XYZ Aura When Equipped`, format is actually in the string table!
|
||||||
@ -460,28 +538,14 @@ func (s *Diablo2Stat) descFn16() string {
|
|||||||
return fmt.Sprintf(format, skillLevel.Int(), skillIndex)
|
return fmt.Sprintf(format, skillLevel.Int(), skillIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// -25% Target Defense
|
func (s *diablo2Stat) descFn22() string {
|
||||||
func (s *Diablo2Stat) descFn20() string {
|
|
||||||
// for now, same as fn2
|
|
||||||
return s.descFn2()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 25% to Attack Rating versus Specter
|
|
||||||
func (s *Diablo2Stat) descFn22() string {
|
|
||||||
arBonus, monsterIndex := s.values[0], s.values[1]
|
arBonus, monsterIndex := s.values[0], s.values[1]
|
||||||
arVersus := d2common.TranslateString(s.record.DescStrPos)
|
arVersus := d2common.TranslateString(s.record.DescStrPos)
|
||||||
|
|
||||||
return fmt.Sprintf(threeComponentStr, arBonus, arVersus, monsterIndex)
|
return fmt.Sprintf(threeComponentStr, arBonus, arVersus, monsterIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 25% Reanimate as: Specter
|
func (s *diablo2Stat) descFn24() string {
|
||||||
func (s *Diablo2Stat) descFn23() string {
|
|
||||||
// for now, same as fn22
|
|
||||||
return s.descFn22()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Level 25 Frozen Orb (19/20 Charges)
|
|
||||||
func (s *Diablo2Stat) descFn24() string {
|
|
||||||
// Special case formatting
|
// Special case formatting
|
||||||
format := "Level " + threeComponentStr
|
format := "Level " + threeComponentStr
|
||||||
|
|
||||||
@ -496,22 +560,20 @@ func (s *Diablo2Stat) descFn24() string {
|
|||||||
return fmt.Sprintf(format, lvl, skill, chargeStr)
|
return fmt.Sprintf(format, lvl, skill, chargeStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// +25 to Frozen Orb (Paladin Only)
|
func (s *diablo2Stat) descFn27() string {
|
||||||
func (s *Diablo2Stat) descFn27() string {
|
|
||||||
amount, skill, hero := s.values[0], s.values[1], s.values[2]
|
amount, skill, hero := s.values[0], s.values[1], s.values[2]
|
||||||
|
|
||||||
return fmt.Sprintf(fourComponentStr, amount, "to", skill, hero)
|
return fmt.Sprintf(fourComponentStr, amount, "to", skill, hero)
|
||||||
}
|
}
|
||||||
|
|
||||||
// +25 to Frozen Orb
|
func (s *diablo2Stat) descFn28() string {
|
||||||
func (s *Diablo2Stat) descFn28() string {
|
|
||||||
amount, skill := s.values[0], s.values[1]
|
amount, skill := s.values[0], s.values[1]
|
||||||
|
|
||||||
return fmt.Sprintf(threeComponentStr, amount, "to", skill)
|
return fmt.Sprintf(threeComponentStr, amount, "to", skill)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DescGroupString return a string based on the DescGroupFuncID
|
// DescGroupString return a string based on the DescGroupFuncID
|
||||||
func (s *Diablo2Stat) DescGroupString(a ...interface{}) string {
|
func (s *diablo2Stat) DescGroupString(a ...interface{}) string {
|
||||||
if s.record.DescGroupFuncID < 0 {
|
if s.record.DescGroupFuncID < 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package diablo2stats
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
||||||
@ -261,8 +260,7 @@ func TestStat_InitMockData(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStat_Clone(t *testing.T) {
|
func TestStat_Clone(t *testing.T) {
|
||||||
r := d2datadict.ItemStatCosts["strength"]
|
s1 := NewStat("strength", 5)
|
||||||
s1 := NewStat(r, intVal(5))
|
|
||||||
s2 := s1.Clone()
|
s2 := s1.Clone()
|
||||||
|
|
||||||
// make sure the stats are distinct
|
// make sure the stats are distinct
|
||||||
@ -288,89 +286,94 @@ func TestStat_Clone(t *testing.T) {
|
|||||||
func TestStat_Descriptions(t *testing.T) {
|
func TestStat_Descriptions(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
recordKey string
|
recordKey string
|
||||||
vals []d2stats.StatValue
|
vals []float64
|
||||||
expect string
|
expect string
|
||||||
}{
|
}{
|
||||||
// DescFn1
|
// DescFn1
|
||||||
{"strength", []d2stats.StatValue{intVal(31)}, "+31 to Strength"},
|
{"strength", []float64{31}, "+31 to Strength"},
|
||||||
{"hpregen", []d2stats.StatValue{intVal(20)}, "Replenish Life +20"},
|
{"hpregen", []float64{20}, "Replenish Life +20"},
|
||||||
{"hpregen", []d2stats.StatValue{intVal(-8)}, "Drain Life -8"},
|
{"hpregen", []float64{-8}, "Drain Life -8"},
|
||||||
|
|
||||||
// DescFn2
|
// DescFn2
|
||||||
{"toblock", []d2stats.StatValue{intVal(16)}, "+16% Increased Chance of Blocking"},
|
{"toblock", []float64{16}, "+16% Increased Chance of Blocking"},
|
||||||
{"item_absorblight_percent", []d2stats.StatValue{intVal(10)}, "Lightning Absorb +10%"},
|
{"item_absorblight_percent", []float64{10}, "Lightning Absorb +10%"},
|
||||||
|
|
||||||
// DescFn3
|
// DescFn3
|
||||||
{"normal_damage_reduction", []d2stats.StatValue{intVal(25)}, "Damage Reduced by 25"},
|
{"normal_damage_reduction", []float64{25}, "Damage Reduced by 25"},
|
||||||
{"item_restinpeace", []d2stats.StatValue{intVal(25)}, "Slain Monsters Rest in Peace"},
|
{"item_restinpeace", []float64{25}, "Slain Monsters Rest in Peace"},
|
||||||
|
|
||||||
// DescFn4
|
// DescFn4
|
||||||
{"poisonresist", []d2stats.StatValue{intVal(25)}, "Poison Resist +25%"},
|
{"poisonresist", []float64{25}, "Poison Resist +25%"},
|
||||||
{"item_fastermovevelocity", []d2stats.StatValue{intVal(25)}, "+25% Faster Run/Walk"},
|
{"item_fastermovevelocity", []float64{25}, "+25% Faster Run/Walk"},
|
||||||
|
|
||||||
// DescFn5
|
// DescFn5
|
||||||
{"item_howl", []d2stats.StatValue{intVal(25)}, "Hit Causes Monster to Flee 25%"},
|
{"item_howl", []float64{25}, "Hit Causes Monster to Flee 25%"},
|
||||||
|
|
||||||
// DescFn6
|
// DescFn6
|
||||||
{"item_hp_perlevel", []d2stats.StatValue{intVal(25)}, "+25 to Life (Based on Character Level)"},
|
{"item_hp_perlevel", []float64{25}, "+25 to Life (Based on Character Level)"},
|
||||||
|
|
||||||
// DescFn7
|
// DescFn7
|
||||||
{"item_resist_ltng_perlevel", []d2stats.StatValue{intVal(25)}, "Lightning Resist +25% (Based on Character Level)"},
|
{"item_resist_ltng_perlevel", []float64{25},
|
||||||
{"item_find_magic_perlevel", []d2stats.StatValue{intVal(25)}, "+25% Better Chance of Getting Magic Items (" +
|
"Lightning Resist +25% (Based on Character Level)"},
|
||||||
|
{"item_find_magic_perlevel", []float64{25}, "+25% Better Chance of Getting Magic Items (" +
|
||||||
"Based on Character Level)"},
|
"Based on Character Level)"},
|
||||||
|
|
||||||
// DescFn8
|
// DescFn8
|
||||||
{"item_armorpercent_perlevel", []d2stats.StatValue{intVal(25)}, "+25% Enhanced Defense (Based on Character Level)"},
|
{"item_armorpercent_perlevel", []float64{25},
|
||||||
{"item_regenstamina_perlevel", []d2stats.StatValue{intVal(25)},
|
"+25% Enhanced Defense (Based on Character Level)"},
|
||||||
|
{"item_regenstamina_perlevel", []float64{25},
|
||||||
"Heal Stamina Plus +25% (Based on Character Level)"},
|
"Heal Stamina Plus +25% (Based on Character Level)"},
|
||||||
|
|
||||||
// DescFn9
|
// DescFn9
|
||||||
{"item_thorns_perlevel", []d2stats.StatValue{intVal(25)}, "Attacker Takes Damage of 25 (Based on Character Level)"},
|
{"item_thorns_perlevel", []float64{25}, "Attacker Takes Damage of 25 (" +
|
||||||
|
"Based on Character Level)"},
|
||||||
|
|
||||||
// DescFn11
|
// DescFn11
|
||||||
{"item_replenish_durability", []d2stats.StatValue{intVal(2)}, "Repairs 2 durability per second"},
|
{"item_replenish_durability", []float64{2}, "Repairs 2 durability per second"},
|
||||||
|
|
||||||
// DescFn12
|
// DescFn12
|
||||||
{"item_stupidity", []d2stats.StatValue{intVal(5)}, "Hit Blinds Target +5"},
|
{"item_stupidity", []float64{5}, "Hit Blinds Target +5"},
|
||||||
|
|
||||||
// DescFn13
|
// DescFn13
|
||||||
{"item_addclassskills", []d2stats.StatValue{intVal(5), intVal(3)}, "+5 to Paladin Skill Levels"},
|
{"item_addclassskills", []float64{5, 3}, "+5 to Paladin Skill Levels"},
|
||||||
|
|
||||||
// DescFn14
|
// DescFn14
|
||||||
{"item_addskill_tab", []d2stats.StatValue{intVal(5), intVal(3), intVal(0)}, "+5 to Combat Skills (Paladin Only)"},
|
{"item_addskill_tab", []float64{5, 3, 0}, "+5 to Combat Skills (Paladin Only)"},
|
||||||
{"item_addskill_tab", []d2stats.StatValue{intVal(5), intVal(3), intVal(1)}, "+5 to Offensive Auras (Paladin Only)"},
|
{"item_addskill_tab", []float64{5, 3, 1}, "+5 to Offensive Auras (Paladin Only)"},
|
||||||
{"item_addskill_tab", []d2stats.StatValue{intVal(5), intVal(3), intVal(2)}, "+5 to Defensive Auras (Paladin Only)"},
|
{"item_addskill_tab", []float64{5, 3, 2}, "+5 to Defensive Auras (Paladin Only)"},
|
||||||
|
|
||||||
// DescFn15
|
// DescFn15
|
||||||
{"item_skillonattack", []d2stats.StatValue{intVal(5), intVal(7), intVal(64)}, "5% Chance to cast level 7 Frozen Orb on attack"},
|
{"item_skillonattack", []float64{5, 7, 64},
|
||||||
|
"5% Chance to cast level 7 Frozen Orb on attack"},
|
||||||
|
|
||||||
// DescFn16
|
// DescFn16
|
||||||
{"item_aura", []d2stats.StatValue{intVal(3), intVal(37)}, "Level 3 Warmth Aura When Equipped"},
|
{"item_aura", []float64{3, 37}, "Level 3 Warmth Aura When Equipped"},
|
||||||
|
|
||||||
// DescFn20
|
// DescFn20
|
||||||
{"item_fractionaltargetac", []d2stats.StatValue{intVal(-25)}, "-25% Target Defense"},
|
{"item_fractionaltargetac", []float64{-25}, "-25% Target Defense"},
|
||||||
|
|
||||||
// DescFn22
|
// DescFn22
|
||||||
{"attack_vs_montype", []d2stats.StatValue{intVal(25), intVal(40)}, "25% to Attack Rating versus Specter"},
|
{"attack_vs_montype", []float64{25, 40}, "25% to Attack Rating versus Specter"},
|
||||||
|
|
||||||
// DescFn23
|
// DescFn23
|
||||||
{"item_reanimate", []d2stats.StatValue{intVal(25), intVal(40)}, "25% Reanimate as: Specter"},
|
{"item_reanimate", []float64{25, 40}, "25% Reanimate as: Specter"},
|
||||||
|
|
||||||
// DescFn24
|
// DescFn24
|
||||||
{"item_charged_skill", []d2stats.StatValue{intVal(25), intVal(64), intVal(20), intVal(19)}, "Level 25 Frozen Orb (19/20 Charges)"},
|
{"item_charged_skill", []float64{25, 64, 20, 19}, "Level 25 Frozen Orb (19/20 Charges)"},
|
||||||
|
|
||||||
// DescFn27
|
// DescFn27
|
||||||
{"item_singleskill", []d2stats.StatValue{intVal(25), intVal(64), intVal(3)}, "+25 to Frozen Orb (Paladin Only)"},
|
{"item_singleskill", []float64{25, 64, 3}, "+25 to Frozen Orb (Paladin Only)"},
|
||||||
|
|
||||||
// DescFn28
|
// DescFn28
|
||||||
{"item_nonclassskill", []d2stats.StatValue{intVal(25), intVal(64)}, "+25 to Frozen Orb"},
|
{"item_nonclassskill", []float64{25, 64}, "+25 to Frozen Orb"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx := range tests {
|
for idx := range tests {
|
||||||
test := tests[idx]
|
test := tests[idx]
|
||||||
record := d2datadict.ItemStatCosts[test.recordKey]
|
key := test.recordKey
|
||||||
|
record := d2datadict.ItemStatCosts[key]
|
||||||
expect := test.expect
|
expect := test.expect
|
||||||
stat := NewStat(record, test.vals...)
|
stat := NewStat(key, test.vals...)
|
||||||
|
|
||||||
if got := stat.String(); got != expect {
|
if got := stat.String(); got != expect {
|
||||||
t.Errorf(errFmt, errStr, record.DescFnID, test.recordKey, test.vals, expect, got)
|
t.Errorf(errFmt, errStr, record.DescFnID, test.recordKey, test.vals, expect, got)
|
||||||
@ -381,3 +384,21 @@ func TestStat_Descriptions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDiablo2Stat_Combine(t *testing.T) {
|
||||||
|
a := NewStat("item_nonclassskill", 25, 64) // "+25 to Frozen Orb"
|
||||||
|
b := NewStat("item_nonclassskill", 5, 64) // "+5 to Frozen Orb"
|
||||||
|
|
||||||
|
c, err := a.Combine(b)
|
||||||
|
|
||||||
|
if err != nil || c.String() != "+30 to Frozen Orb" {
|
||||||
|
t.Errorf("stats combination failed\r%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := NewStat("item_nonclassskill", 5, 37) // "+5 to Warmth"
|
||||||
|
_, err = c.Combine(d)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Error("stats were combined when they should not have been.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,28 +9,34 @@ var _ d2stats.StatValue = &Diablo2StatValue{}
|
|||||||
|
|
||||||
// Diablo2StatValue is a diablo 2 implementation of a stat value
|
// Diablo2StatValue is a diablo 2 implementation of a stat value
|
||||||
type Diablo2StatValue struct {
|
type Diablo2StatValue struct {
|
||||||
number float64
|
number float64
|
||||||
_stringer func(d2stats.StatValue) string
|
stringerFn func(d2stats.StatValue) string
|
||||||
_type d2stats.StatValueType
|
numberType d2stats.StatNumberType
|
||||||
|
combineType d2stats.ValueCombineType
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type returns the stat value type
|
// NumberType returns the stat value type
|
||||||
func (sv *Diablo2StatValue) Type() d2stats.StatValueType {
|
func (sv *Diablo2StatValue) NumberType() d2stats.StatNumberType {
|
||||||
return sv._type
|
return sv.numberType
|
||||||
|
}
|
||||||
|
|
||||||
|
// CombineType returns the stat value combination type
|
||||||
|
func (sv *Diablo2StatValue) CombineType() d2stats.ValueCombineType {
|
||||||
|
return sv.combineType
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone returns a deep copy of the stat value
|
// Clone returns a deep copy of the stat value
|
||||||
func (sv Diablo2StatValue) Clone() d2stats.StatValue {
|
func (sv Diablo2StatValue) Clone() d2stats.StatValue {
|
||||||
clone := &Diablo2StatValue{}
|
clone := &Diablo2StatValue{}
|
||||||
|
|
||||||
switch sv._type {
|
switch sv.numberType {
|
||||||
case d2stats.StatValueInt:
|
case d2stats.StatValueInt:
|
||||||
clone.SetInt(sv.Int())
|
clone.SetInt(sv.Int())
|
||||||
case d2stats.StatValueFloat:
|
case d2stats.StatValueFloat:
|
||||||
clone.SetFloat(sv.Float())
|
clone.SetFloat(sv.Float())
|
||||||
}
|
}
|
||||||
|
|
||||||
clone._stringer = sv._stringer
|
clone.stringerFn = sv.stringerFn
|
||||||
|
|
||||||
return clone
|
return clone
|
||||||
}
|
}
|
||||||
@ -42,7 +48,7 @@ func (sv *Diablo2StatValue) Int() int {
|
|||||||
|
|
||||||
// String returns a string version of the value
|
// String returns a string version of the value
|
||||||
func (sv *Diablo2StatValue) String() string {
|
func (sv *Diablo2StatValue) String() string {
|
||||||
return sv._stringer(sv)
|
return sv.stringerFn(sv)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Float returns a float64 version of the value
|
// Float returns a float64 version of the value
|
||||||
@ -66,11 +72,11 @@ func (sv *Diablo2StatValue) SetFloat(f float64) d2stats.StatValue {
|
|||||||
|
|
||||||
// Stringer returns the string evaluation function
|
// Stringer returns the string evaluation function
|
||||||
func (sv *Diablo2StatValue) Stringer() func(d2stats.StatValue) string {
|
func (sv *Diablo2StatValue) Stringer() func(d2stats.StatValue) string {
|
||||||
return sv._stringer
|
return sv.stringerFn
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetStringer sets the string evaluation function
|
// SetStringer sets the string evaluation function
|
||||||
func (sv *Diablo2StatValue) SetStringer(f func(d2stats.StatValue) string) d2stats.StatValue {
|
func (sv *Diablo2StatValue) SetStringer(f func(d2stats.StatValue) string) d2stats.StatValue {
|
||||||
sv._stringer = f
|
sv.stringerFn = f
|
||||||
return sv
|
return sv
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
// static check that Diablo2Stat implements Stat
|
// static check that diablo2Stat implements Stat
|
||||||
var _ d2stats.StatList = &Diablo2StatList{}
|
var _ d2stats.StatList = &Diablo2StatList{}
|
||||||
|
|
||||||
// Diablo2StatList is a diablo 2 implementation of a stat list
|
// Diablo2StatList is a diablo 2 implementation of a stat list
|
||||||
|
@ -3,13 +3,11 @@ package diablo2stats
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDiablo2StatList_Index(t *testing.T) {
|
func TestDiablo2StatList_Index(t *testing.T) {
|
||||||
record := d2datadict.ItemStatCosts["strength"]
|
strength := NewStat("strength", 10)
|
||||||
strength := NewStat(record, intVal(10))
|
|
||||||
|
|
||||||
list1 := &Diablo2StatList{stats: []d2stats.Stat{strength}}
|
list1 := &Diablo2StatList{stats: []d2stats.Stat{strength}}
|
||||||
if list1.Index(0) != strength {
|
if list1.Index(0) != strength {
|
||||||
@ -18,8 +16,7 @@ func TestDiablo2StatList_Index(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStatList_Clone(t *testing.T) {
|
func TestStatList_Clone(t *testing.T) {
|
||||||
record := d2datadict.ItemStatCosts["strength"]
|
strength := NewStat("strength", 10)
|
||||||
strength := NewStat(record, intVal(10))
|
|
||||||
|
|
||||||
list1 := &Diablo2StatList{}
|
list1 := &Diablo2StatList{}
|
||||||
list1.Push(strength)
|
list1.Push(strength)
|
||||||
@ -40,56 +37,42 @@ func TestStatList_Clone(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestStatList_Reduce(t *testing.T) {
|
func TestStatList_Reduce(t *testing.T) {
|
||||||
records := []*d2datadict.ItemStatCostRecord{
|
|
||||||
d2datadict.ItemStatCosts["strength"],
|
|
||||||
d2datadict.ItemStatCosts["energy"],
|
|
||||||
d2datadict.ItemStatCosts["dexterity"],
|
|
||||||
d2datadict.ItemStatCosts["vitality"],
|
|
||||||
}
|
|
||||||
|
|
||||||
stats := []d2stats.Stat{
|
stats := []d2stats.Stat{
|
||||||
NewStat(records[0], intVal(1)),
|
NewStat("strength", 1),
|
||||||
NewStat(records[0], intVal(1)),
|
NewStat("strength", 1),
|
||||||
NewStat(records[0], intVal(1)),
|
NewStat("strength", 1),
|
||||||
NewStat(records[0], intVal(1)),
|
NewStat("strength", 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
list := NewStatList(stats...)
|
list := NewStatList(stats...)
|
||||||
reduction := list.ReduceStats()
|
reduction := list.ReduceStats()
|
||||||
|
|
||||||
if len(reduction.Stats()) != 1 || reduction.Index(0).String() != "+4 to Strength" {
|
if len(reduction.Stats()) != 1 || reduction.Index(0).String() != "+4 to Strength" {
|
||||||
t.Errorf("Diablo2Stat reduction failed")
|
t.Errorf("diablo2Stat reduction failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
stats = []d2stats.Stat{
|
stats = []d2stats.Stat{
|
||||||
NewStat(records[0], intVal(1)),
|
NewStat("strength", 1),
|
||||||
NewStat(records[1], intVal(1)),
|
NewStat("energy", 1),
|
||||||
NewStat(records[2], intVal(1)),
|
NewStat("dexterity", 1),
|
||||||
NewStat(records[3], intVal(1)),
|
NewStat("vitality", 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
list = NewStatList(stats...)
|
list = NewStatList(stats...)
|
||||||
reduction = list.ReduceStats()
|
reduction = list.ReduceStats()
|
||||||
|
|
||||||
if len(reduction.Stats()) != 4 {
|
if len(reduction.Stats()) != 4 {
|
||||||
t.Errorf("Diablo2Stat reduction failed")
|
t.Errorf("diablo2Stat reduction failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStatList_Append(t *testing.T) {
|
func TestStatList_Append(t *testing.T) {
|
||||||
records := []*d2datadict.ItemStatCostRecord{
|
|
||||||
d2datadict.ItemStatCosts["strength"],
|
|
||||||
d2datadict.ItemStatCosts["energy"],
|
|
||||||
d2datadict.ItemStatCosts["dexterity"],
|
|
||||||
d2datadict.ItemStatCosts["vitality"],
|
|
||||||
}
|
|
||||||
|
|
||||||
list1 := &Diablo2StatList{
|
list1 := &Diablo2StatList{
|
||||||
[]d2stats.Stat{
|
[]d2stats.Stat{
|
||||||
NewStat(records[0], intVal(1)),
|
NewStat("strength", 1),
|
||||||
NewStat(records[1], intVal(1)),
|
NewStat("energy", 1),
|
||||||
NewStat(records[2], intVal(1)),
|
NewStat("dexterity", 1),
|
||||||
NewStat(records[3], intVal(1)),
|
NewStat("vitality", 1),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
list2 := list1.Clone()
|
list2 := list1.Clone()
|
||||||
@ -97,10 +80,10 @@ func TestStatList_Append(t *testing.T) {
|
|||||||
list3 := list1.AppendStatList(list2)
|
list3 := list1.AppendStatList(list2)
|
||||||
|
|
||||||
if len(list3.Stats()) != 8 {
|
if len(list3.Stats()) != 8 {
|
||||||
t.Errorf("Diablo2Stat append failed")
|
t.Errorf("diablo2Stat append failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(list3.ReduceStats().Stats()) != 4 {
|
if len(list3.ReduceStats().Stats()) != 4 {
|
||||||
t.Errorf("Diablo2Stat append failed")
|
t.Errorf("diablo2Stat append failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,51 @@
|
|||||||
package d2stats
|
package d2stats
|
||||||
|
|
||||||
// StatValueType is a value type for a stat value
|
// StatNumberType is a value type for a stat value
|
||||||
type StatValueType int
|
type StatNumberType int
|
||||||
|
|
||||||
// Stat value types
|
// Stat value types
|
||||||
const (
|
const (
|
||||||
StatValueInt StatValueType = iota
|
StatValueInt StatNumberType = iota
|
||||||
StatValueFloat
|
StatValueFloat
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ValueCombineType is a rule for combining stat values
|
||||||
|
type ValueCombineType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StatValueCombineSum means that the values are simply summed
|
||||||
|
StatValueCombineSum ValueCombineType = iota
|
||||||
|
|
||||||
|
// StatValueCombineStatic means that values can be combined only if they
|
||||||
|
// have the same number value, and that the combination does not alter
|
||||||
|
// the number value. This is typically for things like static skill level
|
||||||
|
// monster/skill index for on proc stats where it doesnt make sense to sum
|
||||||
|
// the values
|
||||||
|
// example 1:
|
||||||
|
// if
|
||||||
|
// Stat_A := `25% chance to cast level 2 Frozen Orb on attack`
|
||||||
|
// Stat_B := `25% chance to cast level 3 Frozen Orb on attack`
|
||||||
|
// then
|
||||||
|
// Stat_A can NOT be combined with Stat_B
|
||||||
|
// even though the skills are the same, the levels are different
|
||||||
|
//
|
||||||
|
// example 2:
|
||||||
|
// if
|
||||||
|
// Stat_A := `25% chance to cast level 20 Frost Nova on attack`
|
||||||
|
// Stat_B := `25% chance to cast level 20 Frost Nova on attack`
|
||||||
|
// then
|
||||||
|
// the skills and skill levels are the same, so it can be combined
|
||||||
|
// (Stat_A + Stat_B) == `50% chance to cast level 20 Frost Nova on attack`
|
||||||
|
StatValueCombineStatic
|
||||||
|
)
|
||||||
|
|
||||||
// StatValue is something that can have both integer and float
|
// StatValue is something that can have both integer and float
|
||||||
// number components, as well as a means of retrieving a string for
|
// number components, as well as a means of retrieving a string for
|
||||||
// its values.
|
// its values.
|
||||||
type StatValue interface {
|
type StatValue interface {
|
||||||
Type() StatValueType
|
NumberType() StatNumberType
|
||||||
|
CombineType() ValueCombineType
|
||||||
|
|
||||||
Clone() StatValue
|
Clone() StatValue
|
||||||
|
|
||||||
SetInt(int) StatValue
|
SetInt(int) StatValue
|
||||||
|
Loading…
Reference in New Issue
Block a user