mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-10 14:26:15 -05:00
cf6029eb95
* adding ranged number type, for use in stats * Loaded Skills.txt * asset manager only binds terminal commands if terminal != nil * WIP stats * cache getter and clear methods were not implemented * asset manager handles a nil terminal pointer * adding skilldesc.txt loader (needs work) * ctc stat descriptions functions working * moving description functionality out of itemstatcost loader and into stats * stats seem like a central part of diablo, moving into d2core. * stats seem like a central part of diablo, moving into d2core. * delint * adding statlist, statlist reduction, unit tests * minor edits to stat.go * lint error in statlist.go * Remove dependency on actual data from mpq files stats unit tests now use mock data * fixing some lint errors, formatting Co-authored-by: Maxime Lavigne (malavv) <duguigne@gmail.com>
725 lines
18 KiB
Go
725 lines
18 KiB
Go
package d2stats
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
|
)
|
|
|
|
type descValPosition int
|
|
|
|
const (
|
|
descValHide descValPosition = iota
|
|
descValPrefix
|
|
descValPostfix
|
|
)
|
|
|
|
// CreateStat creates a stat instance with the given ID and number of values
|
|
func CreateStat(record *d2datadict.ItemStatCostRecord, values ...int) *Stat {
|
|
if record == nil {
|
|
return nil
|
|
}
|
|
|
|
stat := &Stat{
|
|
Record: record,
|
|
Values: values,
|
|
}
|
|
|
|
return stat
|
|
}
|
|
|
|
// Stat is an instance of a Stat, with a set of Values
|
|
type Stat struct {
|
|
Record *d2datadict.ItemStatCostRecord
|
|
Values []int
|
|
}
|
|
|
|
// Clone returns a deep copy of the Stat
|
|
func (s Stat) Clone() *Stat {
|
|
clone := &Stat{
|
|
Record: s.Record,
|
|
Values: make([]int, len(s.Values)),
|
|
}
|
|
|
|
for idx := range s.Values {
|
|
clone.Values[idx] = s.Values[idx]
|
|
}
|
|
|
|
return clone
|
|
}
|
|
|
|
// Combine sums the other stat with this one, altering the
|
|
// values of this one.
|
|
func (s *Stat) combine(other *Stat) (success bool) {
|
|
if !s.canBeCombinedWith(other) {
|
|
return false
|
|
}
|
|
|
|
for idx := range s.Values {
|
|
// todo different combination logic per descfnid
|
|
s.Values[idx] += other.Values[idx]
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (s *Stat) canBeCombinedWith(other *Stat) bool {
|
|
if s.Record != other.Record {
|
|
return false
|
|
}
|
|
|
|
if len(s.Values) != len(other.Values) {
|
|
return false
|
|
}
|
|
// todo `10% reanimate as: foo` is not the same as `10% reanimate as: bar`
|
|
|
|
return true
|
|
}
|
|
|
|
// Description returns the formatted description string
|
|
func (s *Stat) Description() string {
|
|
return s.DescString(s.Values...)
|
|
}
|
|
|
|
// StatDescriptionFormatStrings is an array of the base format strings used
|
|
// by the `descfn` methods for stats. The records in itemstatcost.txt have a
|
|
// number field which denotes which of these functions is used for formatting
|
|
// the stat description.
|
|
// These came from phrozen keep:
|
|
// https://d2mods.info/forum/kb/viewarticle?a=448
|
|
//nolint:gochecknoglobals // better for lookup
|
|
var baseFormatStrings = []string{
|
|
"",
|
|
"%v %s",
|
|
"%v%% %s",
|
|
"%v %s",
|
|
"%v%% %s",
|
|
"%v%% %s",
|
|
"%v %s %s",
|
|
"%v%% %v %s",
|
|
"%v%% %s %s",
|
|
"%v %s %s",
|
|
"%v %s %s",
|
|
"Repairs 1 Durability In %v Seconds",
|
|
"%v +%v",
|
|
"+%v %s",
|
|
"+%v to %s %s",
|
|
"%v%% %s",
|
|
"%v %s",
|
|
"%v %s (Increases near %v)",
|
|
"%v%% %s (Increases near %v)",
|
|
"",
|
|
"%v%% %s",
|
|
"%v %s",
|
|
"%v%% %s %s",
|
|
"%v%% %s %s",
|
|
"Level %v %s %s",
|
|
"",
|
|
"",
|
|
"+%v to %s %s",
|
|
"+%v to %s",
|
|
}
|
|
|
|
var statValueCountLookup map[int]int //nolint:gochecknoglobals // lookup
|
|
|
|
// DescString return a string based on the DescFnID
|
|
func (s *Stat) DescString(values ...int) string {// nolint:gocyclo switch statement is not so bad
|
|
if s.Record.DescFnID < 0 || s.Record.DescFnID > len(baseFormatStrings) {
|
|
return ""
|
|
}
|
|
|
|
var result string
|
|
|
|
//nolint:gomdn introducing a const for these would be worse
|
|
switch s.Record.DescFnID {
|
|
case 1:
|
|
result = s.descFn1(values...)
|
|
case 2:
|
|
result = s.descFn2(values...)
|
|
case 3:
|
|
result = s.descFn3(values...)
|
|
case 4:
|
|
result = s.descFn4(values...)
|
|
case 5:
|
|
result = s.descFn5(values...)
|
|
case 6:
|
|
result = s.descFn6(values...)
|
|
case 7:
|
|
result = s.descFn7(values...)
|
|
case 8:
|
|
result = s.descFn8(values...)
|
|
case 9:
|
|
result = s.descFn9(values...)
|
|
case 11:
|
|
result = s.descFn11(values...)
|
|
case 12:
|
|
result = s.descFn12(values...)
|
|
case 13:
|
|
result = s.descFn13(values...)
|
|
case 14:
|
|
result = s.descFn14(values...)
|
|
case 15:
|
|
result = s.descFn15(values...)
|
|
case 16:
|
|
result = s.descFn16(values...)
|
|
case 20:
|
|
result = s.descFn20(values...)
|
|
case 22:
|
|
result = s.descFn22(values...)
|
|
case 23:
|
|
result = s.descFn23(values...)
|
|
case 24:
|
|
result = s.descFn24(values...)
|
|
case 27:
|
|
result = s.descFn27(values...)
|
|
case 28:
|
|
result = s.descFn28(values...)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *Stat) descFn1(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
|
|
// we know there is only one value for this stat
|
|
value := values[0]
|
|
|
|
var stringTableKey string
|
|
if value < 0 {
|
|
stringTableKey = s.Record.DescStrNeg
|
|
} else {
|
|
format = strings.Join([]string{"+", format}, "")
|
|
stringTableKey = s.Record.DescStrPos
|
|
}
|
|
|
|
stringTableString := d2common.TranslateString(stringTableKey)
|
|
|
|
var result string
|
|
|
|
switch descValPosition(s.Record.DescVal) {
|
|
case descValHide:
|
|
result = fmt.Sprintf(format, stringTableString)
|
|
case descValPrefix:
|
|
result = fmt.Sprintf(format, value, stringTableString)
|
|
case descValPostfix:
|
|
formatSplit := strings.Split(format, " ")
|
|
format = strings.Join(reverseStringSlice(formatSplit), " ")
|
|
result = fmt.Sprintf(format, stringTableString, value)
|
|
default:
|
|
result = ""
|
|
}
|
|
|
|
result = strings.ReplaceAll(result, "+-", "-")
|
|
result = strings.ReplaceAll(result, " +%d", "")
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *Stat) descFn2(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
|
|
// we know there is only one value for this stat
|
|
value := values[0]
|
|
|
|
var stringTableKey string
|
|
if value < 0 {
|
|
stringTableKey = s.Record.DescStrNeg
|
|
} else {
|
|
format = strings.Join([]string{"+", format}, "")
|
|
stringTableKey = s.Record.DescStrPos
|
|
}
|
|
|
|
stringTableString := d2common.TranslateString(stringTableKey)
|
|
|
|
var result string
|
|
|
|
switch descValPosition(s.Record.DescVal) {
|
|
case descValHide:
|
|
result = fmt.Sprintf(format, stringTableString)
|
|
case descValPrefix:
|
|
result = fmt.Sprintf(format, value, stringTableString)
|
|
case descValPostfix:
|
|
formatSplit := strings.Split(format, " ")
|
|
format = strings.Join(reverseStringSlice(formatSplit), " ")
|
|
result = fmt.Sprintf(format, stringTableString, value)
|
|
default:
|
|
result = ""
|
|
}
|
|
|
|
return fixString(result)
|
|
}
|
|
|
|
func (s *Stat) descFn3(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
|
|
// we know there is only one value for this stat
|
|
value := values[0]
|
|
|
|
var stringTableKey string
|
|
if value < 0 {
|
|
stringTableKey = s.Record.DescStrNeg
|
|
} else {
|
|
stringTableKey = s.Record.DescStrPos
|
|
}
|
|
|
|
stringTableString := d2common.TranslateString(stringTableKey)
|
|
|
|
var result string
|
|
|
|
switch descValPosition(s.Record.DescVal) {
|
|
case descValHide:
|
|
format = strings.Split(format, " ")[0]
|
|
result = fmt.Sprintf(format, stringTableString)
|
|
case descValPrefix:
|
|
result = fmt.Sprintf(format, value, stringTableString)
|
|
case descValPostfix:
|
|
formatSplit := strings.Split(format, " ")
|
|
format = strings.Join(reverseStringSlice(formatSplit), " ")
|
|
result = fmt.Sprintf(format, stringTableString, value)
|
|
default:
|
|
result = ""
|
|
}
|
|
|
|
return fixString(result)
|
|
}
|
|
|
|
func (s *Stat) descFn4(values ...int) string {
|
|
// for now, same as fn2
|
|
return s.descFn2(values...)
|
|
}
|
|
|
|
func (s *Stat) descFn5(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
|
|
// we know there is only one value for this stat
|
|
value := values[0]
|
|
|
|
var stringTableKey string
|
|
if value < 0 {
|
|
stringTableKey = s.Record.DescStrNeg
|
|
} else {
|
|
stringTableKey = s.Record.DescStrPos
|
|
}
|
|
|
|
stringTableString := d2common.TranslateString(stringTableKey)
|
|
|
|
var result string
|
|
|
|
switch descValPosition(s.Record.DescVal) {
|
|
case descValHide:
|
|
result = fmt.Sprintf(format, stringTableString)
|
|
case descValPrefix:
|
|
result = fmt.Sprintf(format, value, stringTableString)
|
|
case descValPostfix:
|
|
formatSplit := strings.Split(format, " ")
|
|
format = strings.Join(reverseStringSlice(formatSplit), " ")
|
|
result = fmt.Sprintf(format, stringTableString, value)
|
|
default:
|
|
result = ""
|
|
}
|
|
|
|
return fixString(result)
|
|
}
|
|
|
|
func (s *Stat) descFn6(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
|
|
// we know there is only one value for this stat
|
|
value := values[0]
|
|
|
|
var stringTableKey1 string
|
|
if value < 0 {
|
|
stringTableKey1 = s.Record.DescStrNeg
|
|
} else {
|
|
format = strings.Join([]string{"+", format}, "")
|
|
stringTableKey1 = s.Record.DescStrPos
|
|
}
|
|
|
|
stringTableStr1 := d2common.TranslateString(stringTableKey1)
|
|
|
|
// this stat has an additional string (Based on Character Level)
|
|
stringTableStr2 := d2common.TranslateString(s.Record.DescStr2)
|
|
|
|
var result string
|
|
|
|
switch descValPosition(s.Record.DescVal) {
|
|
case descValHide:
|
|
result = fmt.Sprintf(format, stringTableStr1, stringTableStr2)
|
|
case descValPrefix:
|
|
result = fmt.Sprintf(format, value, stringTableStr1, stringTableStr2)
|
|
case descValPostfix:
|
|
formatSplit := strings.Split(format, " ")
|
|
format = strings.Join(reverseStringSlice(formatSplit), " ")
|
|
result = fmt.Sprintf(format, stringTableStr1, value)
|
|
default:
|
|
result = ""
|
|
}
|
|
|
|
return fixString(result)
|
|
}
|
|
|
|
func (s *Stat) descFn7(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
|
|
// we know there is only one value for this stat
|
|
value := values[0]
|
|
|
|
var stringTableKey string
|
|
if value < 0 {
|
|
stringTableKey = s.Record.DescStrNeg
|
|
} else {
|
|
format = strings.Join([]string{"+", format}, "")
|
|
stringTableKey = s.Record.DescStrPos
|
|
}
|
|
|
|
stringTableStr1 := d2common.TranslateString(stringTableKey)
|
|
|
|
// this stat has an additional string (Based on Character Level)
|
|
stringTableStr2 := d2common.TranslateString(s.Record.DescStr2)
|
|
|
|
var result string
|
|
|
|
switch descValPosition(s.Record.DescVal) {
|
|
case descValHide:
|
|
result = fmt.Sprintf(format, stringTableStr1, stringTableStr2)
|
|
case descValPrefix:
|
|
result = fmt.Sprintf(format, value, stringTableStr1, stringTableStr2)
|
|
case descValPostfix:
|
|
formatSplit := strings.Split(format, " ")
|
|
formatSplit[0], formatSplit[1] = formatSplit[1], formatSplit[0]
|
|
format = strings.Join(formatSplit, " ")
|
|
result = fmt.Sprintf(format, stringTableStr1, value, stringTableStr2)
|
|
default:
|
|
result = ""
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *Stat) descFn8(values ...int) string {
|
|
// for now, same as fn7
|
|
return s.descFn7(values...)
|
|
}
|
|
|
|
func (s *Stat) descFn9(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
|
|
// we know there is only one value for this stat
|
|
value := values[0]
|
|
|
|
var stringTableKey1 string
|
|
if value < 0 {
|
|
stringTableKey1 = s.Record.DescStrNeg
|
|
} else {
|
|
stringTableKey1 = s.Record.DescStrPos
|
|
}
|
|
|
|
stringTableStr1 := d2common.TranslateString(stringTableKey1)
|
|
|
|
// this stat has an additional string (Based on Character Level)
|
|
stringTableStr2 := d2common.TranslateString(s.Record.DescStr2)
|
|
|
|
var result string
|
|
|
|
switch descValPosition(s.Record.DescVal) {
|
|
case descValHide:
|
|
result = fmt.Sprintf(format, stringTableStr1, stringTableStr2)
|
|
case descValPrefix:
|
|
result = fmt.Sprintf(format, value, stringTableStr1, stringTableStr2)
|
|
case descValPostfix:
|
|
formatSplit := strings.Split(format, " ")
|
|
formatSplit[0], formatSplit[1] = formatSplit[1], formatSplit[0]
|
|
format = strings.Join(formatSplit, " ")
|
|
result = fmt.Sprintf(format, stringTableStr1, value, stringTableStr2)
|
|
default:
|
|
result = ""
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *Stat) descFn11(values ...int) string {
|
|
// we know there is only one value for this stat
|
|
value := values[0]
|
|
|
|
// the only stat to use this fn is "Repairs durability in X seconds"
|
|
format := d2common.TranslateString(s.Record.DescStrPos)
|
|
|
|
return fmt.Sprintf(format, value)
|
|
}
|
|
|
|
func (s *Stat) descFn12(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
|
|
// we know there is only one value for this stat
|
|
value := values[0]
|
|
|
|
str1 := d2common.TranslateString(s.Record.DescStrPos)
|
|
|
|
return fmt.Sprintf(format, str1, value)
|
|
}
|
|
|
|
func (s *Stat) descFn13(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
numSkills, heroIndex := values[0], values[1]
|
|
|
|
heroMap := map[int]d2enum.Hero{
|
|
int(d2enum.HeroAmazon): d2enum.HeroAmazon,
|
|
int(d2enum.HeroSorceress): d2enum.HeroSorceress,
|
|
int(d2enum.HeroNecromancer): d2enum.HeroNecromancer,
|
|
int(d2enum.HeroPaladin): d2enum.HeroPaladin,
|
|
int(d2enum.HeroBarbarian): d2enum.HeroBarbarian,
|
|
int(d2enum.HeroDruid): d2enum.HeroDruid,
|
|
int(d2enum.HeroAssassin): d2enum.HeroAssassin,
|
|
}
|
|
|
|
classRecord := d2datadict.CharStats[heroMap[heroIndex]]
|
|
descStr1 := d2common.TranslateString(classRecord.SkillStrAll)
|
|
result := fmt.Sprintf(format, numSkills, descStr1)
|
|
|
|
result = strings.ReplaceAll(result, "+-", "-")
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *Stat) descFn14(values ...int) string {
|
|
numSkills, heroIndex, skillTabIndex := values[0], values[1], values[2]
|
|
|
|
if skillTabIndex > 2 || skillTabIndex < 0 {
|
|
skillTabIndex = 0
|
|
}
|
|
|
|
heroMap := map[int]d2enum.Hero{
|
|
int(d2enum.HeroAmazon): d2enum.HeroAmazon,
|
|
int(d2enum.HeroSorceress): d2enum.HeroSorceress,
|
|
int(d2enum.HeroNecromancer): d2enum.HeroNecromancer,
|
|
int(d2enum.HeroPaladin): d2enum.HeroPaladin,
|
|
int(d2enum.HeroBarbarian): d2enum.HeroBarbarian,
|
|
int(d2enum.HeroDruid): d2enum.HeroDruid,
|
|
int(d2enum.HeroAssassin): d2enum.HeroAssassin,
|
|
}
|
|
|
|
classRecord := d2datadict.CharStats[heroMap[heroIndex]]
|
|
skillTabKey := classRecord.SkillStrTab[skillTabIndex]
|
|
classOnlyKey := classRecord.SkillStrClassOnly
|
|
|
|
skillTabStr := d2common.TranslateString(skillTabKey) + " %v"
|
|
skillTabStr = strings.ReplaceAll(skillTabStr, "%d", "%v")
|
|
classOnlyStr := d2common.TranslateString(classOnlyKey)
|
|
result := fmt.Sprintf(skillTabStr, numSkills, classOnlyStr)
|
|
|
|
return fixString(result)
|
|
}
|
|
|
|
func within(n, min, max int) int {
|
|
if n < min {
|
|
n = min
|
|
} else if n > max {
|
|
n = max
|
|
}
|
|
|
|
return n
|
|
}
|
|
|
|
func (s *Stat) descFn15(values ...int) string {
|
|
format := d2common.TranslateString(s.Record.DescStrPos)
|
|
chanceToCast, skillLevel, skillIndex := values[0], values[1], values[2]
|
|
|
|
chanceToCast = within(chanceToCast, 0, 100)
|
|
skillLevel = within(skillLevel, 0, 255)
|
|
skillLevel = within(skillLevel, 0, 255)
|
|
|
|
skillRecord := d2datadict.SkillDetails[skillIndex]
|
|
result := fmt.Sprintf(format, chanceToCast, skillLevel, skillRecord.Skill)
|
|
result = strings.ReplaceAll(result, "+-", "-")
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *Stat) descFn16(values ...int) string {
|
|
skillLevel, skillIndex := values[0], values[1]
|
|
str1 := d2common.TranslateString(s.Record.DescStrPos)
|
|
skillRecord := d2datadict.SkillDetails[skillIndex]
|
|
result := fmt.Sprintf(str1, skillLevel, skillRecord.Skill)
|
|
|
|
return fixString(result)
|
|
}
|
|
|
|
/*
|
|
func (s *Stat) descFn17(values ...int) string {
|
|
// these were not implemented in original D2
|
|
// leaving them out for now as I don't know how to
|
|
// write a test for them, nor do I think vanilla content uses them
|
|
// but these are the stat keys which point to this func...
|
|
// item_armor_bytime
|
|
// item_hp_bytime
|
|
// item_mana_bytime
|
|
// item_maxdamage_bytime
|
|
// item_strength_bytime
|
|
// item_dexterity_bytime
|
|
// item_energy_bytime
|
|
// item_vitality_bytime
|
|
// item_tohit_bytime
|
|
// item_cold_damagemax_bytime
|
|
// item_fire_damagemax_bytime
|
|
// item_ltng_damagemax_bytime
|
|
// item_pois_damagemax_bytime
|
|
// item_stamina_bytime
|
|
// item_tohit_demon_bytime
|
|
// item_tohit_undead_bytime
|
|
// item_kick_damage_bytime
|
|
}
|
|
|
|
func (s *Stat) descFn18(values ...int) string {
|
|
// ... same with these ...
|
|
// item_armorpercent_bytime
|
|
// item_maxdamage_percent_bytime
|
|
// item_tohitpercent_bytime
|
|
// item_resist_cold_bytime
|
|
// item_resist_fire_bytime
|
|
// item_resist_ltng_bytime
|
|
// item_resist_pois_bytime
|
|
// item_absorb_cold_bytime
|
|
// item_absorb_fire_bytime
|
|
// item_absorb_ltng_bytime
|
|
// item_find_gold_bytime
|
|
// item_find_magic_bytime
|
|
// item_regenstamina_bytime
|
|
// item_damage_demon_bytime
|
|
// item_damage_undead_bytime
|
|
// item_crushingblow_bytime
|
|
// item_openwounds_bytime
|
|
// item_deadlystrike_bytime
|
|
}
|
|
*/
|
|
|
|
func (s *Stat) descFn20(values ...int) string {
|
|
// for now, same as fn2
|
|
return s.descFn2(values...)
|
|
}
|
|
|
|
func (s *Stat) descFn22(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
statAgainst, monsterIndex := values[0], values[1]
|
|
|
|
var monsterKey string
|
|
|
|
for key := range d2datadict.MonStats {
|
|
if d2datadict.MonStats[key].Id == monsterIndex {
|
|
monsterKey = key
|
|
break
|
|
}
|
|
}
|
|
|
|
str1 := d2common.TranslateString(s.Record.DescStrPos)
|
|
monsterName := d2datadict.MonStats[monsterKey].NameString
|
|
|
|
result := fmt.Sprintf(format, statAgainst, str1, monsterName)
|
|
|
|
result = strings.ReplaceAll(result, "+-", "-")
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *Stat) descFn23(values ...int) string {
|
|
// for now, same as fn22
|
|
return s.descFn22(values...)
|
|
}
|
|
|
|
func (s *Stat) descFn24(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
lvl, skillID, chargeMax, chargeCurrent := values[0], values[1], values[2], values[3]
|
|
|
|
charges := d2common.TranslateString(s.Record.DescStrPos)
|
|
charges = fmt.Sprintf(charges, chargeCurrent, chargeMax)
|
|
|
|
skillName := d2datadict.SkillDetails[skillID].Skill
|
|
|
|
result := fmt.Sprintf(format, lvl, skillName, charges)
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *Stat) descFn27(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
amount, skillID, heroIndex := values[0], values[1], values[2]
|
|
|
|
skillName := d2datadict.SkillDetails[skillID].Skill
|
|
|
|
heroMap := map[int]d2enum.Hero{
|
|
int(d2enum.HeroAmazon): d2enum.HeroAmazon,
|
|
int(d2enum.HeroSorceress): d2enum.HeroSorceress,
|
|
int(d2enum.HeroNecromancer): d2enum.HeroNecromancer,
|
|
int(d2enum.HeroPaladin): d2enum.HeroPaladin,
|
|
int(d2enum.HeroBarbarian): d2enum.HeroBarbarian,
|
|
int(d2enum.HeroDruid): d2enum.HeroDruid,
|
|
int(d2enum.HeroAssassin): d2enum.HeroAssassin,
|
|
}
|
|
|
|
classRecord := d2datadict.CharStats[heroMap[heroIndex]]
|
|
classOnlyStr := d2common.TranslateString(classRecord.SkillStrClassOnly)
|
|
|
|
return fmt.Sprintf(format, amount, skillName, classOnlyStr)
|
|
}
|
|
|
|
func (s *Stat) descFn28(values ...int) string {
|
|
format := baseFormatStrings[s.Record.DescFnID]
|
|
amount, skillID := values[0], values[1]
|
|
|
|
skillName := d2datadict.SkillDetails[skillID].Skill
|
|
|
|
return fmt.Sprintf(format, amount, skillName)
|
|
}
|
|
|
|
func fixString(s string) string {
|
|
s = strings.ReplaceAll(s, "+-", "-")
|
|
s = strings.ReplaceAll(s, " +%d", "")
|
|
|
|
return s
|
|
}
|
|
|
|
// DescGroupString return a string based on the DescGroupFuncID
|
|
func (s *Stat) DescGroupString(a ...interface{}) string {
|
|
if s.Record.DescGroupFuncID < 0 || s.Record.DescGroupFuncID > len(
|
|
baseFormatStrings) {
|
|
return ""
|
|
}
|
|
|
|
format := baseFormatStrings[s.Record.DescGroupFuncID]
|
|
|
|
return fmt.Sprintf(format, a...)
|
|
}
|
|
|
|
// NumStatValues returns the number of values a stat instance for this
|
|
// record should have
|
|
func (s *Stat) NumStatValues() int {
|
|
if num, found := statValueCountLookup[s.Record.DescGroupFuncID]; found {
|
|
return num
|
|
}
|
|
|
|
if statValueCountLookup == nil {
|
|
statValueCountLookup = make(map[int]int)
|
|
}
|
|
|
|
format := baseFormatStrings[s.Record.DescGroupFuncID]
|
|
pattern := regexp.MustCompile("%v")
|
|
matches := pattern.FindAllStringIndex(format, -1)
|
|
num := len(matches)
|
|
statValueCountLookup[s.Record.DescGroupFuncID] = num
|
|
|
|
return num
|
|
}
|
|
|
|
func reverseStringSlice(s []string) []string {
|
|
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
|
|
return s
|
|
}
|