Monstat2 loading and a bunch of lint issues (#491)

* MonStat2 loader

* Fix a bunch of lint issues in d2datadict
This commit is contained in:
Ziemas 2020-06-29 18:37:11 +02:00 committed by GitHub
parent b29e7c8fdd
commit aae565d528
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 805 additions and 241 deletions

View File

@ -39,10 +39,10 @@ type AutoMapRecord struct {
// whatever you like..." // whatever you like..."
// The values seem functional but naming conventions // The values seem functional but naming conventions
// vary between LevelNames. // vary between LevelNames.
//Type1 string // Type1 string
//Type2 string // Type2 string
//Type3 string // Type3 string
//Type4 string // Note: I commented these out for now because they supposedly aren't useful see the LoadAutoMaps function. // Type4 string // Note: I commented these out for now because they supposedly aren't useful see the LoadAutoMaps function.
// Frames determine the frame of the MaxiMap(s).dc6 that // Frames determine the frame of the MaxiMap(s).dc6 that
// will be applied to the specified tiles. The frames // will be applied to the specified tiles. The frames
@ -58,6 +58,7 @@ type AutoMapRecord struct {
} }
// AutoMaps contains all data in AutoMap.txt. // AutoMaps contains all data in AutoMap.txt.
//nolint:gochecknoglobals // Current design is to have these global
var AutoMaps []*AutoMapRecord var AutoMaps []*AutoMapRecord
// LoadAutoMaps populates AutoMaps with the data from AutoMap.txt. // LoadAutoMaps populates AutoMaps with the data from AutoMap.txt.
@ -71,9 +72,9 @@ func LoadAutoMaps(file []byte) {
// Construct records // Construct records
AutoMaps = make([]*AutoMapRecord, len(d.Data)) AutoMaps = make([]*AutoMapRecord, len(d.Data))
for idx := range d.Data { for idx := range d.Data {
// Row 2603 is a separator with all empty field values if d.GetString("LevelName", idx) == "Expansion" {
if idx == 2603 {
continue continue
} }
@ -88,7 +89,9 @@ func LoadAutoMaps(file []byte) {
//Type1: d.GetString("Type1", idx), //Type1: d.GetString("Type1", idx),
//Type2: d.GetString("Type2", idx), //Type2: d.GetString("Type2", idx),
//Type3: d.GetString("Type3", idx), //Type3: d.GetString("Type3", idx),
//Type4: d.GetString("Type4", idx), // Note: I commented these out for now because they supposedly aren't useful see the AutoMapRecord struct. //Type4: d.GetString("Type4", idx),
// Note: I commented these out for now because they supposedly
// aren't useful see the AutoMapRecord struct.
} }
AutoMaps[idx].Frames = make([]int, len(frameFields)) AutoMaps[idx].Frames = make([]int, len(frameFields))

View File

@ -7,6 +7,7 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
) )
// Charecter stats
type CharStatsRecord struct { type CharStatsRecord struct {
Class d2enum.Hero Class d2enum.Hero
@ -59,8 +60,8 @@ var CharStats map[d2enum.Hero]*CharStatsRecord
var charStringMap map[string]d2enum.Hero var charStringMap map[string]d2enum.Hero
var weaponTokenMap map[string]d2enum.WeaponClass var weaponTokenMap map[string]d2enum.WeaponClass
//nolint:funlen // Makes no sense to split
func LoadCharStats(file []byte) { func LoadCharStats(file []byte) {
charStringMap = map[string]d2enum.Hero{ charStringMap = map[string]d2enum.Hero{
"Amazon": d2enum.HeroAmazon, "Amazon": d2enum.HeroAmazon,
"Barbarian": d2enum.HeroBarbarian, "Barbarian": d2enum.HeroBarbarian,
@ -186,5 +187,6 @@ func LoadCharStats(file []byte) {
} }
CharStats[record.Class] = record CharStats[record.Class] = record
} }
log.Printf("Loaded %d CharStats records", len(CharStats)) log.Printf("Loaded %d CharStats records", len(CharStats))
} }

View File

@ -130,7 +130,7 @@ type CubeRecipeItemProperty struct {
// string or an integer. // string or an integer.
// //
// See: https://d2mods.info/forum/kb/viewarticle?a=345 // See: https://d2mods.info/forum/kb/viewarticle?a=345
//"the parameter passed on to the associated property, this is used to pass skill IDs, // "the parameter passed on to the associated property, this is used to pass skill IDs,
// state IDs, monster IDs, montype IDs and the like on to the properties that require // state IDs, monster IDs, montype IDs and the like on to the properties that require
// them, these fields support calculations." // them, these fields support calculations."
Param int // for properties that use parameters Param int // for properties that use parameters
@ -153,7 +153,6 @@ var inputFields = []string{"input 1", "input 2", "input 3", "input 4", "input 5"
// LoadCubeRecipes populates CubeRecipes with // LoadCubeRecipes populates CubeRecipes with
// the data from CubeMain.txt. // the data from CubeMain.txt.
func LoadCubeRecipes(file []byte) { func LoadCubeRecipes(file []byte) {
// Load data // Load data
d := d2common.LoadDataDictionary(string(file)) d := d2common.LoadDataDictionary(string(file))
@ -188,7 +187,6 @@ func LoadCubeRecipes(file []byte) {
// Create outputs - output "", b, c // Create outputs - output "", b, c
CubeRecipes[idx].Outputs = make([]CubeRecipeResult, 3) CubeRecipes[idx].Outputs = make([]CubeRecipeResult, 3)
for o, outLabel := range outputLabels { for o, outLabel := range outputLabels {
CubeRecipes[idx].Outputs[o] = CubeRecipeResult{ CubeRecipes[idx].Outputs[o] = CubeRecipeResult{
Item: newCubeRecipeItem( Item: newCubeRecipeItem(
d.GetString(outputFields[o], idx)), d.GetString(outputFields[o], idx)),
@ -201,7 +199,6 @@ func LoadCubeRecipes(file []byte) {
// Create properties - mod 1-5 // Create properties - mod 1-5
properties := make([]CubeRecipeItemProperty, 5) properties := make([]CubeRecipeItemProperty, 5)
for p, prop := range propLabels { for p, prop := range propLabels {
properties[p] = CubeRecipeItemProperty{ properties[p] = CubeRecipeItemProperty{
Code: d.GetString(outLabel+prop, idx), Code: d.GetString(outLabel+prop, idx),
Chance: d.GetNumber(outLabel+prop+" chance", idx), Chance: d.GetNumber(outLabel+prop+" chance", idx),
@ -213,7 +210,6 @@ func LoadCubeRecipes(file []byte) {
CubeRecipes[idx].Outputs[o].Properties = properties CubeRecipes[idx].Outputs[o].Properties = properties
} }
} }
log.Printf("Loaded %d CubeMainRecord records", len(CubeRecipes)) log.Printf("Loaded %d CubeMainRecord records", len(CubeRecipes))
@ -237,21 +233,26 @@ func newCubeRecipeItem(f string) CubeRecipeItem {
// Find the qty parameter if it was provided, // Find the qty parameter if it was provided,
// convert to int and assign to item.Count // convert to int and assign to item.Count
for idx, arg := range args { for idx, arg := range args {
if strings.HasPrefix(arg, "qty") { if !strings.HasPrefix(arg, "qty") {
count, err := strconv.Atoi(strings.Split(arg, "=")[1]) continue
if err != nil {
log.Fatal("Error parsing item count:", err)
}
item.Count = count
// Remove the qty parameter
if idx != len(args)-1 {
args[idx] = args[len(args)-1]
}
args = args[:len(args)-1]
break
} }
count, err := strconv.Atoi(strings.Split(arg, "=")[1])
if err != nil {
log.Fatal("Error parsing item count:", err)
}
item.Count = count
// Remove the qty parameter
if idx != len(args)-1 {
args[idx] = args[len(args)-1]
}
args = args[:len(args)-1]
break
} }
// No other arguments were provided // No other arguments were provided
@ -272,10 +273,12 @@ func newCubeRecipeItem(f string) CubeRecipeItem {
func classFieldToEnum(f string) []d2enum.Hero { func classFieldToEnum(f string) []d2enum.Hero {
split := splitFieldValue(f) split := splitFieldValue(f)
enums := make([]d2enum.Hero, len(split)) enums := make([]d2enum.Hero, len(split))
for idx, class := range split { for idx, class := range split {
if class == "" { if class == "" {
continue continue
} }
switch class { switch class {
case "bar": case "bar":
enums[idx] = d2enum.HeroBarbarian enums[idx] = d2enum.HeroBarbarian
@ -295,6 +298,7 @@ func classFieldToEnum(f string) []d2enum.Hero {
log.Fatalf("Unknown hero token: '%s'", class) log.Fatalf("Unknown hero token: '%s'", class)
} }
} }
return enums return enums
} }

View File

@ -6,8 +6,11 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// DifficultyLevels contain the difficulty records for each difficulty
//nolint:gochecknoglobals // Current design is to have these global
var DifficultyLevels map[string]*DifficultyLevelRecord var DifficultyLevels map[string]*DifficultyLevelRecord
// DifficultyLevelRecord contain the parameters that change for different difficultios
type DifficultyLevelRecord struct { type DifficultyLevelRecord struct {
// Difficulty name. it is hardcoded and you cannot add new ones unless you do // Difficulty name. it is hardcoded and you cannot add new ones unless you do
// some Code Edits // some Code Edits
@ -30,7 +33,7 @@ type DifficultyLevelRecord struct {
// txt file... // txt file...
// Not used. Pre 1.07 it was the percentage of magic, rare, set and unique // Not used. Pre 1.07 it was the percentage of magic, rare, set and unique
// exceptional items dropped on this difficuly. // exceptional items dropped on this difficulty.
DropChanceMagic int // UberCodeOddsGood DropChanceMagic int // UberCodeOddsGood
DropChanceRare int // UberCodeOddsGood DropChanceRare int // UberCodeOddsGood
DropChanceSet int // UberCodeOddsGood DropChanceSet int // UberCodeOddsGood
@ -89,6 +92,7 @@ type DifficultyLevelRecord struct {
} }
// LoadDifficultyLevels is a loader for difficultylevels.txt
func LoadDifficultyLevels(file []byte) { func LoadDifficultyLevels(file []byte) {
dict := d2common.LoadDataDictionary(string(file)) dict := d2common.LoadDataDictionary(string(file))
numRows := len(dict.Data) numRows := len(dict.Data)
@ -119,5 +123,4 @@ func LoadDifficultyLevels(file []byte) {
} }
log.Printf("Loaded %d DifficultyLevel records", len(DifficultyLevels)) log.Printf("Loaded %d DifficultyLevel records", len(DifficultyLevels))
} }

View File

@ -89,6 +89,7 @@ func LoadExperienceBreakpoints(file []byte) {
d2enum.HeroPaladin: d.GetNumber("Paladin", idx), d2enum.HeroPaladin: d.GetNumber("Paladin", idx),
d2enum.HeroSorceress: d.GetNumber("Sorceress", idx), d2enum.HeroSorceress: d.GetNumber("Sorceress", idx),
} }
continue continue
} }
@ -108,5 +109,6 @@ func LoadExperienceBreakpoints(file []byte) {
ExperienceBreakpoints[record.Level] = record ExperienceBreakpoints[record.Level] = record
} }
log.Printf("Loaded %d ExperienceBreakpoint records", len(ExperienceBreakpoints)) log.Printf("Loaded %d ExperienceBreakpoint records", len(ExperienceBreakpoints))
} }

View File

@ -52,7 +52,9 @@ type GemsRecord struct {
func LoadGems(file []byte) { func LoadGems(file []byte) {
d := d2common.LoadDataDictionary(string(file)) d := d2common.LoadDataDictionary(string(file))
var Gems []*GemsRecord var Gems []*GemsRecord
for idx := range d.Data { for idx := range d.Data {
if d.GetString("name", idx) != "Expansion" { if d.GetString("name", idx) != "Expansion" {
/* /*
@ -105,5 +107,6 @@ func LoadGems(file []byte) {
Gems = append(Gems, gem) Gems = append(Gems, gem)
} }
} }
log.Printf("Loaded %d Gems records", len(Gems)) log.Printf("Loaded %d Gems records", len(Gems))
} }

View File

@ -9,7 +9,7 @@ import (
type HirelingRecord struct { type HirelingRecord struct {
Hireling string Hireling string
SubType string SubType string
Id int ID int
Class int Class int
Act int Act int
Difficulty int Difficulty int
@ -84,11 +84,12 @@ type HirelingRecord struct {
func LoadHireling(file []byte) { func LoadHireling(file []byte) {
d := d2common.LoadDataDictionary(string(file)) d := d2common.LoadDataDictionary(string(file))
var Hirelings []*HirelingRecord var Hirelings []*HirelingRecord
for idx := range d.Data { for idx := range d.Data {
hireling := &HirelingRecord{ hireling := &HirelingRecord{
Hireling: d.GetString("Hireling", idx), Hireling: d.GetString("Hireling", idx),
SubType: d.GetString("SubType", idx), SubType: d.GetString("SubType", idx),
Id: d.GetNumber("Id", idx), ID: d.GetNumber("Id", idx),
Class: d.GetNumber("Class", idx), Class: d.GetNumber("Class", idx),
Act: d.GetNumber("Act", idx), Act: d.GetNumber("Act", idx),
Difficulty: d.GetNumber("Difficulty", idx), Difficulty: d.GetNumber("Difficulty", idx),
@ -161,5 +162,6 @@ func LoadHireling(file []byte) {
} }
Hirelings = append(Hirelings, hireling) Hirelings = append(Hirelings, hireling)
} }
log.Printf("Loaded %d Hireling records", len(Hirelings)) log.Printf("Loaded %d Hireling records", len(Hirelings))
} }

View File

@ -16,26 +16,28 @@ var MagicSuffixRecords []*ItemAffixCommonRecord
var AffixMagicGroups []*ItemAffixCommonGroup var AffixMagicGroups []*ItemAffixCommonGroup
var superType d2enum.ItemAffixSuperType // LoadMagicPrefix loads MagicPrefix.txt
var subType d2enum.ItemAffixSubType
func LoadMagicPrefix(file []byte) { func LoadMagicPrefix(file []byte) {
superType = d2enum.ItemAffixPrefix superType := d2enum.ItemAffixPrefix
subType = d2enum.ItemAffixMagic
subType := d2enum.ItemAffixMagic
MagicPrefixDictionary, MagicPrefixRecords = loadDictionary(file, superType, subType) MagicPrefixDictionary, MagicPrefixRecords = loadDictionary(file, superType, subType)
} }
// LoadMagicSuffix loads MagicSuffix.txt
func LoadMagicSuffix(file []byte) { func LoadMagicSuffix(file []byte) {
superType = d2enum.ItemAffixSuffix superType := d2enum.ItemAffixSuffix
subType = d2enum.ItemAffixMagic
subType := d2enum.ItemAffixMagic
MagicSuffixDictionary, MagicSuffixRecords = loadDictionary(file, superType, subType) MagicSuffixDictionary, MagicSuffixRecords = loadDictionary(file, superType, subType)
} }
func getAffixString(t1 d2enum.ItemAffixSuperType, t2 d2enum.ItemAffixSubType) string { func getAffixString(t1 d2enum.ItemAffixSuperType, t2 d2enum.ItemAffixSubType) string {
var name string = "" var name string = ""
switch t2 { if t2 == d2enum.ItemAffixMagic {
case d2enum.ItemAffixMagic:
name = "Magic" name = "Magic"
} }
@ -47,7 +49,6 @@ func getAffixString(t1 d2enum.ItemAffixSuperType, t2 d2enum.ItemAffixSubType) st
} }
return name return name
} }
func loadDictionary( func loadDictionary(
@ -59,6 +60,7 @@ func loadDictionary(
records := createItemAffixRecords(dict, superType, subType) records := createItemAffixRecords(dict, superType, subType)
name := getAffixString(superType, subType) name := getAffixString(superType, subType)
log.Printf("Loaded %d %s records", len(dict.Data), name) log.Printf("Loaded %d %s records", len(dict.Data), name)
return dict, records return dict, records
} }
@ -111,8 +113,8 @@ func createItemAffixRecords(
subType d2enum.ItemAffixSubType, subType d2enum.ItemAffixSubType,
) []*ItemAffixCommonRecord { ) []*ItemAffixCommonRecord {
records := make([]*ItemAffixCommonRecord, 0) records := make([]*ItemAffixCommonRecord, 0)
for index := range d.Data {
for index := range d.Data {
affix := &ItemAffixCommonRecord{ affix := &ItemAffixCommonRecord{
Name: d.GetString("Name", index), Name: d.GetString("Name", index),
Version: d.GetNumber("version", index), Version: d.GetNumber("version", index),
@ -179,6 +181,7 @@ func createItemAffixRecords(
records = append(records, affix) records = append(records, affix)
} }
return records return records
} }
@ -193,14 +196,17 @@ func (g *ItemAffixCommonGroup) AddMember(a *ItemAffixCommonRecord) {
if g.Members == nil { if g.Members == nil {
g.Members = make(map[string]*ItemAffixCommonRecord) g.Members = make(map[string]*ItemAffixCommonRecord)
} }
g.Members[a.Name] = a g.Members[a.Name] = a
} }
func (g *ItemAffixCommonGroup) GetTotalFrequency() int { func (g *ItemAffixCommonGroup) GetTotalFrequency() int {
total := 0 total := 0
for _, affix := range g.Members { for _, affix := range g.Members {
total += affix.Frequency total += affix.Frequency
} }
return total return total
} }
@ -249,6 +255,8 @@ func (a *ItemAffixCommonRecord) ProbabilityToSpawn(qlvl int) float64 {
if (qlvl > a.MaxLevel) || (qlvl < a.Level) { if (qlvl > a.MaxLevel) || (qlvl < a.Level) {
return 0.0 return 0.0
} }
p := (float64)(a.Frequency) / (float64)(a.Group.GetTotalFrequency())
p := float64(a.Frequency) / float64(a.Group.GetTotalFrequency())
return p return p
} }

View File

@ -181,30 +181,35 @@ type ItemVendorParams struct {
MagicLevel uint8 MagicLevel uint8
} }
// Loading Functions
var CommonItems map[string]*ItemCommonRecord var CommonItems map[string]*ItemCommonRecord
func LoadCommonItems(file []byte, source d2enum.InventoryItemType) *map[string]*ItemCommonRecord { func LoadCommonItems(file []byte, source d2enum.InventoryItemType) *map[string]*ItemCommonRecord {
if CommonItems == nil { if CommonItems == nil {
CommonItems = make(map[string]*ItemCommonRecord) CommonItems = make(map[string]*ItemCommonRecord)
} }
items := make(map[string]*ItemCommonRecord) items := make(map[string]*ItemCommonRecord)
data := strings.Split(string(file), "\r\n") data := strings.Split(string(file), "\r\n")
mapping := MapHeaders(data[0]) mapping := MapHeaders(data[0])
for lineno, line := range data { for lineno, line := range data {
if lineno == 0 { if lineno == 0 {
continue continue
} }
if len(line) == 0 {
if line == "" {
continue continue
} }
rec := createCommonItemRecord(line, &mapping, source) rec := createCommonItemRecord(line, &mapping, source)
items[rec.Code] = &rec items[rec.Code] = &rec
CommonItems[rec.Code] = &rec CommonItems[rec.Code] = &rec
} }
return &items return &items
} }
//nolint:funlen // Makes no sens to split
func createCommonItemRecord(line string, mapping *map[string]int, source d2enum.InventoryItemType) ItemCommonRecord { func createCommonItemRecord(line string, mapping *map[string]int, source d2enum.InventoryItemType) ItemCommonRecord {
r := strings.Split(line, "\t") r := strings.Split(line, "\t")
result := ItemCommonRecord{ result := ItemCommonRecord{
@ -363,6 +368,7 @@ func createCommonItemRecord(line string, mapping *map[string]int, source d2enum.
Multibuy: MapLoadBool(&r, mapping, "multibuy"), Multibuy: MapLoadBool(&r, mapping, "multibuy"),
} }
return result return result
} }
@ -398,6 +404,7 @@ func createItemVendorParams(r *[]string, mapping *map[string]int) map[string]*It
} }
result[name] = &wvp result[name] = &wvp
} }
return result return result
} }
@ -407,5 +414,6 @@ func createItemUsageStats(r *[]string, mapping *map[string]int) [3]ItemUsageStat
result[i].Stat = MapLoadString(r, mapping, "stat"+strconv.Itoa(i)) result[i].Stat = MapLoadString(r, mapping, "stat"+strconv.Itoa(i))
result[i].Calc = d2common.CalcString(MapLoadString(r, mapping, "calc"+strconv.Itoa(i))) result[i].Calc = d2common.CalcString(MapLoadString(r, mapping, "calc"+strconv.Itoa(i)))
} }
return result return result
} }

View File

@ -115,10 +115,10 @@ const (
// just adds the stat to the unit directly // just adds the stat to the unit directly
OpDefault = OperatorType(iota) OpDefault = OperatorType(iota)
// adds opstat.base * statvalue / 100 to the opstat. // Op1 adds opstat.base * statvalue / 100 to the opstat.
Op1 Op1
// adds (statvalue * basevalue) / (2 ^ param) to the opstat // Op2 adds (statvalue * basevalue) / (2 ^ param) to the opstat
// this does not work properly with any stat other then level because of the // this does not work properly with any stat other then level because of the
// way this is updated, it is only refreshed when you re-equip the item, // way this is updated, it is only refreshed when you re-equip the item,
// your character is saved or you level up, similar to passive skills, just // your character is saved or you level up, similar to passive skills, just
@ -127,56 +127,56 @@ const (
// description every frame, while the values remain unchanged serverside. // description every frame, while the values remain unchanged serverside.
Op2 Op2
// this is a percentage based version of op #2 // Op3 is a percentage based version of op #2
// look at op #2 for information about the formula behind it, just // look at op #2 for information about the formula behind it, just
// remember the stat is increased by a percentage rather then by adding // remember the stat is increased by a percentage rather then by adding
// an integer. // an integer.
Op3 Op3
// this works the same way op #2 works, however the stat bonus is // Op4 works the same way op #2 works, however the stat bonus is
// added to the item and not to the player (so that +defense per level // added to the item and not to the player (so that +defense per level
// properly adds the defense to the armor and not to the character // properly adds the defense to the armor and not to the character
// directly!) // directly!)
Op4 Op4
// this works like op #4 but is percentage based, it is used for percentage // Op5 works like op #4 but is percentage based, it is used for percentage
// based increase of stats that are found on the item itself, and not stats // based increase of stats that are found on the item itself, and not stats
// that are found on the character. // that are found on the character.
Op5 Op5
// like for op #7, however this adds a plain bonus to the stat, and just // Op6 works like for op #7, however this adds a plain bonus to the stat, and just
// like #7 it also doesn't work so I won't bother to explain the arithmetic // like #7 it also doesn't work so I won't bother to explain the arithmetic
// behind it either. // behind it either.
Op6 Op6
// this is used to increase a stat based on the current daytime of the game // Op7 is used to increase a stat based on the current daytime of the game
// world by a percentage, there is no need to explain the arithmetics // world by a percentage, there is no need to explain the arithmetics
// behind it because frankly enough it just doesn't work serverside, it // behind it because frankly enough it just doesn't work serverside, it
// only updates clientside so this op is essentially useless. // only updates clientside so this op is essentially useless.
Op7 Op7
// hardcoded to work only with maxmana, this will apply the proper amount // Op8 hardcoded to work only with maxmana, this will apply the proper amount
// of mana to your character based on CharStats.txt for the amount of energy // of mana to your character based on CharStats.txt for the amount of energy
// the stat added (doesn't work for non characters) // the stat added (doesn't work for non characters)
Op8 Op8
// hardcoded to work only with maxhp and maxstamina, this will apply the // Op9 hardcoded to work only with maxhp and maxstamina, this will apply the
// proper amount of maxhp and maxstamina to your character based on // proper amount of maxhp and maxstamina to your character based on
// CharStats.txt for the amount of vitality the stat added (doesn't work // CharStats.txt for the amount of vitality the stat added (doesn't work
// for non characters) // for non characters)
Op9 Op9
// doesn't do anything, this has no switch case in the op function. // Op10 doesn't do anything, this has no switch case in the op function.
Op10 Op10
// adds opstat.base * statvalue / 100 similar to 1 and 13, the code just // Op11 adds opstat.base * statvalue / 100 similar to 1 and 13, the code just
// does a few more checks // does a few more checks
Op11 Op11
// doesn't do anything, this has no switch case in the op function. // Op12 doesn't do anything, this has no switch case in the op function.
Op12 Op12
// adds opstat.base * statvalue / 100 to the value of opstat, this is // Op13 adds opstat.base * statvalue / 100 to the value of opstat, this is
// useable only on items it will not apply the bonus to other unit types // useable only on items it will not apply the bonus to other unit types
// (this is why it is used for +% durability, +% level requirement, // (this is why it is used for +% durability, +% level requirement,
// +% damage, +% defense ). // +% damage, +% defense ).
@ -241,6 +241,7 @@ stuff
var ItemStatCosts map[string]*ItemStatCostRecord var ItemStatCosts map[string]*ItemStatCostRecord
// LoadItemStatCosts loads ItemStatCostRecord's from text
func LoadItemStatCosts(file []byte) { func LoadItemStatCosts(file []byte) {
d := d2common.LoadDataDictionary(string(file)) d := d2common.LoadDataDictionary(string(file))
numRecords := len(d.Data) numRecords := len(d.Data)
@ -317,5 +318,6 @@ func LoadItemStatCosts(file []byte) {
ItemStatCosts[record.Name] = record ItemStatCosts[record.Name] = record
} }
log.Printf("Loaded %d ItemStatCost records", len(ItemStatCosts)) log.Printf("Loaded %d ItemStatCost records", len(ItemStatCosts))
} }

View File

@ -14,7 +14,7 @@ type LevelMazeDetailsRecord struct {
// ID from Levels.txt // ID from Levels.txt
// NOTE: Cave 1 is the Den of Evil, its associated treasure level is quest // NOTE: Cave 1 is the Den of Evil, its associated treasure level is quest
// only. // only.
LevelId int // Level LevelID int // Level
// the minimum number of .ds1 map sections that will make up the maze in // the minimum number of .ds1 map sections that will make up the maze in
// Normal, Nightmare and Hell difficulties. // Normal, Nightmare and Hell difficulties.
@ -36,21 +36,24 @@ type LevelMazeDetailsRecord struct {
var LevelMazeDetails map[int]*LevelMazeDetailsRecord var LevelMazeDetails map[int]*LevelMazeDetailsRecord
// LoadLevelMazeDetails loads LevelMazeDetailsRecords from text file
func LoadLevelMazeDetails(file []byte) { func LoadLevelMazeDetails(file []byte) {
dict := d2common.LoadDataDictionary(string(file)) dict := d2common.LoadDataDictionary(string(file))
numRecords := len(dict.Data) numRecords := len(dict.Data)
LevelMazeDetails = make(map[int]*LevelMazeDetailsRecord, numRecords) LevelMazeDetails = make(map[int]*LevelMazeDetailsRecord, numRecords)
for idx := range dict.Data { for idx := range dict.Data {
record := &LevelMazeDetailsRecord{ record := &LevelMazeDetailsRecord{
Name: dict.GetString("Name", idx), Name: dict.GetString("Name", idx),
LevelId: dict.GetNumber("Level", idx), LevelID: dict.GetNumber("Level", idx),
NumRoomsNormal: dict.GetNumber("Rooms", idx), NumRoomsNormal: dict.GetNumber("Rooms", idx),
NumRoomsNightmare: dict.GetNumber("Rooms(N)", idx), NumRoomsNightmare: dict.GetNumber("Rooms(N)", idx),
NumRoomsHell: dict.GetNumber("Rooms(H)", idx), NumRoomsHell: dict.GetNumber("Rooms(H)", idx),
SizeX: dict.GetNumber("SizeX", idx), SizeX: dict.GetNumber("SizeX", idx),
SizeY: dict.GetNumber("SizeY", idx), SizeY: dict.GetNumber("SizeY", idx),
} }
LevelMazeDetails[record.LevelId] = record LevelMazeDetails[record.LevelID] = record
} }
log.Printf("Loaded %d LevelMazeDetails records", len(LevelMazeDetails)) log.Printf("Loaded %d LevelMazeDetails records", len(LevelMazeDetails))
} }

View File

@ -9,8 +9,8 @@ import (
type LevelPresetRecord struct { type LevelPresetRecord struct {
Name string Name string
DefinitionId int DefinitionID int
LevelId int LevelID int
Populate bool Populate bool
Logicals bool Logicals bool
Outdoors bool Outdoors bool
@ -39,8 +39,8 @@ func createLevelPresetRecord(props []string) LevelPresetRecord {
} }
result := LevelPresetRecord{ result := LevelPresetRecord{
Name: props[inc()], Name: props[inc()],
DefinitionId: d2common.StringToInt(props[inc()]), DefinitionID: d2common.StringToInt(props[inc()]),
LevelId: d2common.StringToInt(props[inc()]), LevelID: d2common.StringToInt(props[inc()]),
Populate: d2common.StringToUint8(props[inc()]) == 1, Populate: d2common.StringToUint8(props[inc()]) == 1,
Logicals: d2common.StringToUint8(props[inc()]) == 1, Logicals: d2common.StringToUint8(props[inc()]) == 1,
Outdoors: d2common.StringToUint8(props[inc()]) == 1, Outdoors: d2common.StringToUint8(props[inc()]) == 1,
@ -66,31 +66,39 @@ func createLevelPresetRecord(props []string) LevelPresetRecord {
Beta: d2common.StringToUint8(props[inc()]) == 1, Beta: d2common.StringToUint8(props[inc()]) == 1,
Expansion: d2common.StringToUint8(props[inc()]) == 1, Expansion: d2common.StringToUint8(props[inc()]) == 1,
} }
return result return result
} }
var LevelPresets map[int]LevelPresetRecord var LevelPresets map[int]LevelPresetRecord
// LoadLevelPresets loads level presets from text file
func LoadLevelPresets(file []byte) { func LoadLevelPresets(file []byte) {
LevelPresets = make(map[int]LevelPresetRecord) LevelPresets = make(map[int]LevelPresetRecord)
data := strings.Split(string(file), "\r\n")[1:] data := strings.Split(string(file), "\r\n")[1:]
for _, line := range data { for _, line := range data {
if len(line) == 0 { if line == "" {
continue continue
} }
props := strings.Split(line, "\t") props := strings.Split(line, "\t")
if props[1] == "" { if props[1] == "" {
continue // any line without a definition id is skipped (e.g. the "Expansion" line) continue // any line without a definition id is skipped (e.g. the "Expansion" line)
} }
rec := createLevelPresetRecord(props) rec := createLevelPresetRecord(props)
LevelPresets[rec.DefinitionId] = rec LevelPresets[rec.DefinitionID] = rec
} }
log.Printf("Loaded %d level presets", len(LevelPresets)) log.Printf("Loaded %d level presets", len(LevelPresets))
} }
// LevelPreset looks up a LevelPresetRecord by ID
func LevelPreset(id int) LevelPresetRecord { func LevelPreset(id int) LevelPresetRecord {
for i := 0; i < len(LevelPresets); i++ { for i := 0; i < len(LevelPresets); i++ {
if LevelPresets[i].DefinitionId == id { if LevelPresets[i].DefinitionID == id {
return LevelPresets[i] return LevelPresets[i]
} }
} }

View File

@ -14,7 +14,7 @@ type LevelSubstitutionRecord struct {
// groups. If you count each row of a group starting from 0, then you'll // groups. If you count each row of a group starting from 0, then you'll
// obtain what is written in Levels.txt, columns 'SubTheme', 'SubWaypoint' // obtain what is written in Levels.txt, columns 'SubTheme', 'SubWaypoint'
// and 'SubShrine'. (added by Paul Siramy) // and 'SubShrine'. (added by Paul Siramy)
Id int // Type ID int // Type
// What .ds1 is being used. // What .ds1 is being used.
File string // File File string // File
@ -69,10 +69,9 @@ func LoadLevelSubstitutions(file []byte) {
LevelSubstitutions = make(map[int]*LevelSubstitutionRecord, numRecords) LevelSubstitutions = make(map[int]*LevelSubstitutionRecord, numRecords)
for idx := range dict.Data { for idx := range dict.Data {
record := &LevelSubstitutionRecord{ record := &LevelSubstitutionRecord{
Name: dict.GetString("Name", idx), Name: dict.GetString("Name", idx),
Id: dict.GetNumber("Type", idx), ID: dict.GetNumber("Type", idx),
File: dict.GetString("File", idx), File: dict.GetString("File", idx),
IsExpansion: dict.GetNumber("Expansion", idx) > 0, IsExpansion: dict.GetNumber("Expansion", idx) > 0,
BorderType: dict.GetNumber("BordType", idx), BorderType: dict.GetNumber("BordType", idx),
@ -95,7 +94,8 @@ func LoadLevelSubstitutions(file []byte) {
GridMax4: dict.GetNumber("Max4", idx), GridMax4: dict.GetNumber("Max4", idx),
} }
LevelSubstitutions[record.Id] = record LevelSubstitutions[record.ID] = record
} }
log.Printf("Loaded %d LevelSubstitution records", len(LevelSubstitutions)) log.Printf("Loaded %d LevelSubstitution records", len(LevelSubstitutions))
} }

View File

@ -9,7 +9,7 @@ import (
type LevelTypeRecord struct { type LevelTypeRecord struct {
Name string Name string
Id int ID int
Files [32]string Files [32]string
Beta bool Beta bool
Act int Act int
@ -21,29 +21,35 @@ var LevelTypes []LevelTypeRecord
func LoadLevelTypes(file []byte) { func LoadLevelTypes(file []byte) {
data := strings.Split(string(file), "\r\n")[1:] data := strings.Split(string(file), "\r\n")[1:]
LevelTypes = make([]LevelTypeRecord, len(data)) LevelTypes = make([]LevelTypeRecord, len(data))
for i, j := 0, 0; i < len(data); i, j = i+1, j+1 { for i, j := 0, 0; i < len(data); i, j = i+1, j+1 {
idx := -1 idx := -1
inc := func() int { inc := func() int {
idx++ idx++
return idx return idx
} }
if len(data[i]) == 0 {
if data[i] == "" {
continue continue
} }
parts := strings.Split(data[i], "\t") parts := strings.Split(data[i], "\t")
if parts[0] == "Expansion" { if parts[0] == "Expansion" {
j-- j--
continue continue
} }
LevelTypes[j].Name = parts[inc()] LevelTypes[j].Name = parts[inc()]
LevelTypes[j].Id = d2common.StringToInt(parts[inc()]) LevelTypes[j].ID = d2common.StringToInt(parts[inc()])
for fileIdx := range LevelTypes[i].Files { for fileIdx := range LevelTypes[i].Files {
LevelTypes[j].Files[fileIdx] = parts[inc()] LevelTypes[j].Files[fileIdx] = parts[inc()]
if LevelTypes[j].Files[fileIdx] == "0" { if LevelTypes[j].Files[fileIdx] == "0" {
LevelTypes[j].Files[fileIdx] = "" LevelTypes[j].Files[fileIdx] = ""
} }
} }
LevelTypes[j].Beta = parts[inc()] != "1" LevelTypes[j].Beta = parts[inc()] != "1"
LevelTypes[j].Act = d2common.StringToInt(parts[inc()]) LevelTypes[j].Act = d2common.StringToInt(parts[inc()])
LevelTypes[j].Expansion = parts[inc()] != "1" LevelTypes[j].Expansion = parts[inc()] != "1"

View File

@ -7,7 +7,7 @@ import (
) )
type LevelWarpRecord struct { type LevelWarpRecord struct {
Id int32 ID int32
SelectX int32 SelectX int32
SelectY int32 SelectY int32
SelectDX int32 SelectDX int32
@ -21,16 +21,20 @@ type LevelWarpRecord struct {
Direction string Direction string
} }
//nolint:gochecknoglobals // Currently global by design, only written once
// LevelWarps loaded from txt records
var LevelWarps map[int]*LevelWarpRecord var LevelWarps map[int]*LevelWarpRecord
// LoadLevelWarps loads LevelWarpRecord's from text file data
func LoadLevelWarps(levelWarpData []byte) { func LoadLevelWarps(levelWarpData []byte) {
LevelWarps = make(map[int]*LevelWarpRecord) LevelWarps = make(map[int]*LevelWarpRecord)
streamReader := d2common.CreateStreamReader(levelWarpData) streamReader := d2common.CreateStreamReader(levelWarpData)
numRecords := int(streamReader.GetInt32()) numRecords := int(streamReader.GetInt32())
for i := 0; i < numRecords; i++ { for i := 0; i < numRecords; i++ {
id := int(streamReader.GetInt32()) id := int(streamReader.GetInt32())
LevelWarps[id] = &LevelWarpRecord{} LevelWarps[id] = &LevelWarpRecord{}
LevelWarps[id].Id = int32(id) LevelWarps[id].ID = int32(id)
LevelWarps[id].SelectX = streamReader.GetInt32() LevelWarps[id].SelectX = streamReader.GetInt32()
LevelWarps[id].SelectY = streamReader.GetInt32() LevelWarps[id].SelectY = streamReader.GetInt32()
LevelWarps[id].SelectDX = streamReader.GetInt32() LevelWarps[id].SelectDX = streamReader.GetInt32()
@ -44,5 +48,6 @@ func LoadLevelWarps(levelWarpData []byte) {
LevelWarps[id].Direction = string(streamReader.GetByte()) LevelWarps[id].Direction = string(streamReader.GetByte())
streamReader.SkipBytes(3) streamReader.SkipBytes(3)
} }
log.Printf("Loaded %d level warps", len(LevelWarps)) log.Printf("Loaded %d level warps", len(LevelWarps))
} }

View File

@ -37,7 +37,7 @@ type LevelDetailsRecord struct {
// additional layers. // additional layers.
AutomapIndex int // Layer AutomapIndex int // Layer
// sizeX - SizeY in each difficuly. If this is a preset area this sets the // sizeX - SizeY in each difficulty. If this is a preset area this sets the
// X size for the area. Othervise use the same value here that are used in // X size for the area. Othervise use the same value here that are used in
// lvlprest.txt to set the size for the .ds1 file. // lvlprest.txt to set the size for the .ds1 file.
SizeXNormal int // SizeX SizeXNormal int // SizeX
@ -102,7 +102,7 @@ type LevelDetailsRecord struct {
// cycles, because they always use the light values specified in Intensity, // cycles, because they always use the light values specified in Intensity,
// Red, Green, Blue. this field also controls whenever sounds will echo if // Red, Green, Blue. this field also controls whenever sounds will echo if
// you're running the game with a sound card capable of it and have // you're running the game with a sound card capable of it and have
// enviroment sound effects set to true. // environment sound effects set to true.
IsInside bool // IsInside IsInside bool // IsInside
// Setting for Level Generation: You have 3 possibilities here: // Setting for Level Generation: You have 3 possibilities here:
@ -141,28 +141,28 @@ type LevelDetailsRecord struct {
// linked with, but the actuall number of Vis ( 0 - 7 ) is determined by // linked with, but the actuall number of Vis ( 0 - 7 ) is determined by
// your actual map (the .ds1 fle). // your actual map (the .ds1 fle).
// Example: Normally Cave levels are only using vis 0-3 and wilderness areas 4-7 . // Example: Normally Cave levels are only using vis 0-3 and wilderness areas 4-7 .
LevelLinkId0 int // Vis0 LevelLinkID0 int // Vis0
LevelLinkId1 int // Vis1 LevelLinkID1 int // Vis1
LevelLinkId2 int // Vis2 LevelLinkID2 int // Vis2
LevelLinkId3 int // Vis3 LevelLinkID3 int // Vis3
LevelLinkId4 int // Vis4 LevelLinkID4 int // Vis4
LevelLinkId5 int // Vis5 LevelLinkID5 int // Vis5
LevelLinkId6 int // Vis6 LevelLinkID6 int // Vis6
LevelLinkId7 int // Vis7 LevelLinkID7 int // Vis7
// This controls the visual graphics then you move the mouse pointer over // This controls the visual graphics then you move the mouse pointer over
// an entrance. To show the graphics you use an ID from lvlwarp.txt and the // an entrance. To show the graphics you use an ID from lvlwarp.txt and the
// behavior on the graphics is controlled by lvlwarp.txt. Your Warps must // behavior on the graphics is controlled by lvlwarp.txt. Your Warps must
// match your Vis. // match your Vis.
// Example: If your level uses Vis 3,5,7 then you must also use Warp 3,5,7 . // Example: If your level uses Vis 3,5,7 then you must also use Warp 3,5,7 .
WarpGraphicsId0 int // Warp0 WarpGraphicsID0 int // Warp0
WarpGraphicsId1 int // Warp1 WarpGraphicsID1 int // Warp1
WarpGraphicsId2 int // Warp2 WarpGraphicsID2 int // Warp2
WarpGraphicsId3 int // Warp3 WarpGraphicsID3 int // Warp3
WarpGraphicsId4 int // Warp4 WarpGraphicsID4 int // Warp4
WarpGraphicsId5 int // Warp5 WarpGraphicsID5 int // Warp5
WarpGraphicsId6 int // Warp6 WarpGraphicsID6 int // Warp6
WarpGraphicsId7 int // Warp7 WarpGraphicsID7 int // Warp7
// These settings handle the light intensity as well as its RGB components // These settings handle the light intensity as well as its RGB components
LightIntensity int // Intensity LightIntensity int // Intensity
@ -195,7 +195,7 @@ type LevelDetailsRecord struct {
// What quest is this level related to. This is the quest id (as example the // What quest is this level related to. This is the quest id (as example the
// first quest Den of Evil are set to 1, since its the first quest). // first quest Den of Evil are set to 1, since its the first quest).
QuestId int // Quest QuestID int // Quest
// This sets the minimum distance from a VisX or WarpX location that a // This sets the minimum distance from a VisX or WarpX location that a
// monster, object or tile can be spawned at. (also applies to waypoints and // monster, object or tile can be spawned at. (also applies to waypoints and
@ -247,40 +247,40 @@ type LevelDetailsRecord struct {
// Hell. They tell the game which monster ID taken from MonStats.txt. // Hell. They tell the game which monster ID taken from MonStats.txt.
// NOTE: you need to manually add from mon11 to mon25 and from nmon11 to // NOTE: you need to manually add from mon11 to mon25 and from nmon11 to
// nmon25 ! // nmon25 !
MonsterId1Normal string // mon1 MonsterID1Normal string // mon1
MonsterId2Normal string // mon2 MonsterID2Normal string // mon2
MonsterId3Normal string // mon3 MonsterID3Normal string // mon3
MonsterId4Normal string // mon4 MonsterID4Normal string // mon4
MonsterId5Normal string // mon5 MonsterID5Normal string // mon5
MonsterId6Normal string // mon6 MonsterID6Normal string // mon6
MonsterId7Normal string // mon7 MonsterID7Normal string // mon7
MonsterId8Normal string // mon8 MonsterID8Normal string // mon8
MonsterId9Normal string // mon9 MonsterID9Normal string // mon9
MonsterId10Normal string // mon10 MonsterID10Normal string // mon10
MonsterId1Nightmare string // nmon1 MonsterID1Nightmare string // nmon1
MonsterId2Nightmare string // nmon2 MonsterID2Nightmare string // nmon2
MonsterId3Nightmare string // nmon3 MonsterID3Nightmare string // nmon3
MonsterId4Nightmare string // nmon4 MonsterID4Nightmare string // nmon4
MonsterId5Nightmare string // nmon5 MonsterID5Nightmare string // nmon5
MonsterId6Nightmare string // nmon6 MonsterID6Nightmare string // nmon6
MonsterId7Nightmare string // nmon7 MonsterID7Nightmare string // nmon7
MonsterId8Nightmare string // nmon8 MonsterID8Nightmare string // nmon8
MonsterId9Nightmare string // nmon9 MonsterID9Nightmare string // nmon9
MonsterId10Nightmare string // nmon10 MonsterID10Nightmare string // nmon10
// Gravestench - adding additional fields for Hell, original txt combined // Gravestench - adding additional fields for Hell, original txt combined
// the nighmare and hell ID's stringo the same field // the nighmare and hell ID's stringo the same field
MonsterId1Hell string // nmon1 MonsterID1Hell string // nmon1
MonsterId2Hell string // nmon2 MonsterID2Hell string // nmon2
MonsterId3Hell string // nmon3 MonsterID3Hell string // nmon3
MonsterId4Hell string // nmon4 MonsterID4Hell string // nmon4
MonsterId5Hell string // nmon5 MonsterID5Hell string // nmon5
MonsterId6Hell string // nmon6 MonsterID6Hell string // nmon6
MonsterId7Hell string // nmon7 MonsterID7Hell string // nmon7
MonsterId8Hell string // nmon8 MonsterID8Hell string // nmon8
MonsterId9Hell string // nmon9 MonsterID9Hell string // nmon9
MonsterId10Hell string // nmon10 MonsterID10Hell string // nmon10
// Give preference to monsters set to ranged=1 in MonStats.txt on Nightmare // Give preference to monsters set to ranged=1 in MonStats.txt on Nightmare
// and Hell difficulties when picking something to spawn. // and Hell difficulties when picking something to spawn.
@ -293,24 +293,24 @@ type LevelDetailsRecord struct {
// NOTE: you can allow umon1-25 to also work in Nightmare and Hell by // NOTE: you can allow umon1-25 to also work in Nightmare and Hell by
// following this simple ASM edit // following this simple ASM edit
// (https://d2mods.info/forum/viewtopic.php?f=8&t=53969&p=425179&hilit=umon#p425179) // (https://d2mods.info/forum/viewtopic.php?f=8&t=53969&p=425179&hilit=umon#p425179)
MonsterUniqueId1 string // umon1 MonsterUniqueID1 string // umon1
MonsterUniqueId2 string // umon2 MonsterUniqueID2 string // umon2
MonsterUniqueId3 string // umon3 MonsterUniqueID3 string // umon3
MonsterUniqueId4 string // umon4 MonsterUniqueID4 string // umon4
MonsterUniqueId5 string // umon5 MonsterUniqueID5 string // umon5
MonsterUniqueId6 string // umon6 MonsterUniqueID6 string // umon6
MonsterUniqueId7 string // umon7 MonsterUniqueID7 string // umon7
MonsterUniqueId8 string // umon8 MonsterUniqueID8 string // umon8
MonsterUniqueId9 string // umon9 MonsterUniqueID9 string // umon9
MonsterUniqueId10 string // umon10 MonsterUniqueID10 string // umon10
// Critter Species 1-4. Uses the Id from monstats2.txt and only monsters // Critter Species 1-4. Uses the Id from monstats2.txt and only monsters
// with critter column set to 1 can spawn here. critter column is also found // with critter column set to 1 can spawn here. critter column is also found
// in monstats2.txt. Critters are in reality only present clientside. // in monstats2.txt. Critters are in reality only present clientside.
MonsterCritterId1 string // cmon1 MonsterCritterID1 string // cmon1
MonsterCritterId2 string // cmon2 MonsterCritterID2 string // cmon2
MonsterCritterId3 string // cmon3 MonsterCritterID3 string // cmon3
MonsterCritterId4 string // cmon4 MonsterCritterID4 string // cmon4
// Controls the chance for a critter to spawn. // Controls the chance for a critter to spawn.
MonsterCritter1SpawnChance int // cpct1 MonsterCritter1SpawnChance int // cpct1
@ -330,13 +330,13 @@ type LevelDetailsRecord struct {
// Themes // Themes
// Referes to a entry in SoundEnviron.txt (for the Levels Music) // Referes to a entry in SoundEnviron.txt (for the Levels Music)
SoundEnvironmentId int // SoundEnv SoundEnvironmentID int // SoundEnv
// 255 means no Waipoint for this level, while others state the Waypoint' ID // 255 means no Waipoint for this level, while others state the Waypoint' ID
// for the level // for the level
// NOTE: you can switch waypoint destinations between areas this way, not // NOTE: you can switch waypoint destinations between areas this way, not
// between acts however so don't even bother to try. // between acts however so don't even bother to try.
WaypointId int // Waypoint WaypointID int // Waypoint
// String Code for the Display name of the Level // String Code for the Display name of the Level
LevelDisplayName string // LevelName LevelDisplayName string // LevelName
@ -351,14 +351,14 @@ type LevelDetailsRecord struct {
// this field uses the ID of the ObjectGroup you want to Spawn in this Area, // this field uses the ID of the ObjectGroup you want to Spawn in this Area,
// taken from Objgroup.txt. // taken from Objgroup.txt.
ObjectGroupId0 int // ObjGrp0 ObjectGroupID0 int // ObjGrp0
ObjectGroupId1 int // ObjGrp1 ObjectGroupID1 int // ObjGrp1
ObjectGroupId2 int // ObjGrp2 ObjectGroupID2 int // ObjGrp2
ObjectGroupId3 int // ObjGrp3 ObjectGroupID3 int // ObjGrp3
ObjectGroupId4 int // ObjGrp4 ObjectGroupID4 int // ObjGrp4
ObjectGroupId5 int // ObjGrp5 ObjectGroupID5 int // ObjGrp5
ObjectGroupId6 int // ObjGrp6 ObjectGroupID6 int // ObjGrp6
ObjectGroupId7 int // ObjGrp7 ObjectGroupID7 int // ObjGrp7
// These fields indicates the chance for each object group to spawn (if you // These fields indicates the chance for each object group to spawn (if you
// use ObjGrp0 then set ObjPrb0 to a value below 100) // use ObjGrp0 then set ObjPrb0 to a value below 100)
@ -387,6 +387,7 @@ func GetLevelDetails(id int) *LevelDetailsRecord {
return nil return nil
} }
//nolint:funlen // Txt loader, makes no sense to split
func LoadLevelDetails(file []byte) { func LoadLevelDetails(file []byte) {
dict := d2common.LoadDataDictionary(string(file)) dict := d2common.LoadDataDictionary(string(file))
numRecords := len(dict.Data) numRecords := len(dict.Data)
@ -425,22 +426,22 @@ func LoadLevelDetails(file []byte) {
SubTheme: dict.GetNumber("SubTheme", idx), SubTheme: dict.GetNumber("SubTheme", idx),
SubWaypoint: dict.GetNumber("SubWaypoint", idx), SubWaypoint: dict.GetNumber("SubWaypoint", idx),
SubShrine: dict.GetNumber("SubShrine", idx), SubShrine: dict.GetNumber("SubShrine", idx),
LevelLinkId0: dict.GetNumber("Vis0", idx), LevelLinkID0: dict.GetNumber("Vis0", idx),
LevelLinkId1: dict.GetNumber("Vis1", idx), LevelLinkID1: dict.GetNumber("Vis1", idx),
LevelLinkId2: dict.GetNumber("Vis2", idx), LevelLinkID2: dict.GetNumber("Vis2", idx),
LevelLinkId3: dict.GetNumber("Vis3", idx), LevelLinkID3: dict.GetNumber("Vis3", idx),
LevelLinkId4: dict.GetNumber("Vis4", idx), LevelLinkID4: dict.GetNumber("Vis4", idx),
LevelLinkId5: dict.GetNumber("Vis5", idx), LevelLinkID5: dict.GetNumber("Vis5", idx),
LevelLinkId6: dict.GetNumber("Vis6", idx), LevelLinkID6: dict.GetNumber("Vis6", idx),
LevelLinkId7: dict.GetNumber("Vis7", idx), LevelLinkID7: dict.GetNumber("Vis7", idx),
WarpGraphicsId0: dict.GetNumber("Warp0", idx), WarpGraphicsID0: dict.GetNumber("Warp0", idx),
WarpGraphicsId1: dict.GetNumber("Warp1", idx), WarpGraphicsID1: dict.GetNumber("Warp1", idx),
WarpGraphicsId2: dict.GetNumber("Warp2", idx), WarpGraphicsID2: dict.GetNumber("Warp2", idx),
WarpGraphicsId3: dict.GetNumber("Warp3", idx), WarpGraphicsID3: dict.GetNumber("Warp3", idx),
WarpGraphicsId4: dict.GetNumber("Warp4", idx), WarpGraphicsID4: dict.GetNumber("Warp4", idx),
WarpGraphicsId5: dict.GetNumber("Warp5", idx), WarpGraphicsID5: dict.GetNumber("Warp5", idx),
WarpGraphicsId6: dict.GetNumber("Warp6", idx), WarpGraphicsID6: dict.GetNumber("Warp6", idx),
WarpGraphicsId7: dict.GetNumber("Warp7", idx), WarpGraphicsID7: dict.GetNumber("Warp7", idx),
LightIntensity: dict.GetNumber("Intensity", idx), LightIntensity: dict.GetNumber("Intensity", idx),
Red: dict.GetNumber("Red", idx), Red: dict.GetNumber("Red", idx),
Green: dict.GetNumber("Green", idx), Green: dict.GetNumber("Green", idx),
@ -449,7 +450,7 @@ func LoadLevelDetails(file []byte) {
PortalRepositionEnable: dict.GetNumber("Position", idx) > 0, PortalRepositionEnable: dict.GetNumber("Position", idx) > 0,
SaveMonsterStates: dict.GetNumber("SaveMonsters", idx) > 0, SaveMonsterStates: dict.GetNumber("SaveMonsters", idx) > 0,
SaveMerchantStates: dict.GetNumber("SaveMonsters", idx) > 0, SaveMerchantStates: dict.GetNumber("SaveMonsters", idx) > 0,
QuestId: dict.GetNumber("Quest", idx), QuestID: dict.GetNumber("Quest", idx),
WarpClearanceDistance: dict.GetNumber("WarpDist", idx), WarpClearanceDistance: dict.GetNumber("WarpDist", idx),
MonsterLevelNormal: dict.GetNumber("MonLvl1", idx), MonsterLevelNormal: dict.GetNumber("MonLvl1", idx),
MonsterLevelNightmare: dict.GetNumber("MonLvl2", idx), MonsterLevelNightmare: dict.GetNumber("MonLvl2", idx),
@ -469,68 +470,68 @@ func LoadLevelDetails(file []byte) {
MonsterWanderEnable: dict.GetNumber("MonWndr", idx) > 0, MonsterWanderEnable: dict.GetNumber("MonWndr", idx) > 0,
MonsterSpecialWalk: dict.GetNumber("MonSpcWalk", idx) > 0, MonsterSpecialWalk: dict.GetNumber("MonSpcWalk", idx) > 0,
NumMonsterTypes: dict.GetNumber("NumMon", idx), NumMonsterTypes: dict.GetNumber("NumMon", idx),
MonsterId1Normal: dict.GetString("mon1", idx), MonsterID1Normal: dict.GetString("mon1", idx),
MonsterId2Normal: dict.GetString("mon2", idx), MonsterID2Normal: dict.GetString("mon2", idx),
MonsterId3Normal: dict.GetString("mon3", idx), MonsterID3Normal: dict.GetString("mon3", idx),
MonsterId4Normal: dict.GetString("mon4", idx), MonsterID4Normal: dict.GetString("mon4", idx),
MonsterId5Normal: dict.GetString("mon5", idx), MonsterID5Normal: dict.GetString("mon5", idx),
MonsterId6Normal: dict.GetString("mon6", idx), MonsterID6Normal: dict.GetString("mon6", idx),
MonsterId7Normal: dict.GetString("mon7", idx), MonsterID7Normal: dict.GetString("mon7", idx),
MonsterId8Normal: dict.GetString("mon8", idx), MonsterID8Normal: dict.GetString("mon8", idx),
MonsterId9Normal: dict.GetString("mon9", idx), MonsterID9Normal: dict.GetString("mon9", idx),
MonsterId10Normal: dict.GetString("mon10", idx), MonsterID10Normal: dict.GetString("mon10", idx),
MonsterId1Nightmare: dict.GetString("nmon1", idx), MonsterID1Nightmare: dict.GetString("nmon1", idx),
MonsterId2Nightmare: dict.GetString("nmon2", idx), MonsterID2Nightmare: dict.GetString("nmon2", idx),
MonsterId3Nightmare: dict.GetString("nmon3", idx), MonsterID3Nightmare: dict.GetString("nmon3", idx),
MonsterId4Nightmare: dict.GetString("nmon4", idx), MonsterID4Nightmare: dict.GetString("nmon4", idx),
MonsterId5Nightmare: dict.GetString("nmon5", idx), MonsterID5Nightmare: dict.GetString("nmon5", idx),
MonsterId6Nightmare: dict.GetString("nmon6", idx), MonsterID6Nightmare: dict.GetString("nmon6", idx),
MonsterId7Nightmare: dict.GetString("nmon7", idx), MonsterID7Nightmare: dict.GetString("nmon7", idx),
MonsterId8Nightmare: dict.GetString("nmon8", idx), MonsterID8Nightmare: dict.GetString("nmon8", idx),
MonsterId9Nightmare: dict.GetString("nmon9", idx), MonsterID9Nightmare: dict.GetString("nmon9", idx),
MonsterId10Nightmare: dict.GetString("nmon10", idx), MonsterID10Nightmare: dict.GetString("nmon10", idx),
MonsterId1Hell: dict.GetString("nmon1", idx), MonsterID1Hell: dict.GetString("nmon1", idx),
MonsterId2Hell: dict.GetString("nmon2", idx), MonsterID2Hell: dict.GetString("nmon2", idx),
MonsterId3Hell: dict.GetString("nmon3", idx), MonsterID3Hell: dict.GetString("nmon3", idx),
MonsterId4Hell: dict.GetString("nmon4", idx), MonsterID4Hell: dict.GetString("nmon4", idx),
MonsterId5Hell: dict.GetString("nmon5", idx), MonsterID5Hell: dict.GetString("nmon5", idx),
MonsterId6Hell: dict.GetString("nmon6", idx), MonsterID6Hell: dict.GetString("nmon6", idx),
MonsterId7Hell: dict.GetString("nmon7", idx), MonsterID7Hell: dict.GetString("nmon7", idx),
MonsterId8Hell: dict.GetString("nmon8", idx), MonsterID8Hell: dict.GetString("nmon8", idx),
MonsterId9Hell: dict.GetString("nmon9", idx), MonsterID9Hell: dict.GetString("nmon9", idx),
MonsterId10Hell: dict.GetString("nmon10", idx), MonsterID10Hell: dict.GetString("nmon10", idx),
MonsterPreferRanged: dict.GetNumber("rangedspawn", idx) > 0, MonsterPreferRanged: dict.GetNumber("rangedspawn", idx) > 0,
MonsterUniqueId1: dict.GetString("umon1", idx), MonsterUniqueID1: dict.GetString("umon1", idx),
MonsterUniqueId2: dict.GetString("umon2", idx), MonsterUniqueID2: dict.GetString("umon2", idx),
MonsterUniqueId3: dict.GetString("umon3", idx), MonsterUniqueID3: dict.GetString("umon3", idx),
MonsterUniqueId4: dict.GetString("umon4", idx), MonsterUniqueID4: dict.GetString("umon4", idx),
MonsterUniqueId5: dict.GetString("umon5", idx), MonsterUniqueID5: dict.GetString("umon5", idx),
MonsterUniqueId6: dict.GetString("umon6", idx), MonsterUniqueID6: dict.GetString("umon6", idx),
MonsterUniqueId7: dict.GetString("umon7", idx), MonsterUniqueID7: dict.GetString("umon7", idx),
MonsterUniqueId8: dict.GetString("umon8", idx), MonsterUniqueID8: dict.GetString("umon8", idx),
MonsterUniqueId9: dict.GetString("umon9", idx), MonsterUniqueID9: dict.GetString("umon9", idx),
MonsterUniqueId10: dict.GetString("umon10", idx), MonsterUniqueID10: dict.GetString("umon10", idx),
MonsterCritterId1: dict.GetString("cmon1", idx), MonsterCritterID1: dict.GetString("cmon1", idx),
MonsterCritterId2: dict.GetString("cmon2", idx), MonsterCritterID2: dict.GetString("cmon2", idx),
MonsterCritterId3: dict.GetString("cmon3", idx), MonsterCritterID3: dict.GetString("cmon3", idx),
MonsterCritterId4: dict.GetString("cmon4", idx), MonsterCritterID4: dict.GetString("cmon4", idx),
MonsterCritter1SpawnChance: dict.GetNumber("cpct1", idx), MonsterCritter1SpawnChance: dict.GetNumber("cpct1", idx),
MonsterCritter2SpawnChance: dict.GetNumber("cpct2", idx), MonsterCritter2SpawnChance: dict.GetNumber("cpct2", idx),
MonsterCritter3SpawnChance: dict.GetNumber("cpct3", idx), MonsterCritter3SpawnChance: dict.GetNumber("cpct3", idx),
MonsterCritter4SpawnChance: dict.GetNumber("cpct4", idx), MonsterCritter4SpawnChance: dict.GetNumber("cpct4", idx),
SoundEnvironmentId: dict.GetNumber("SoundEnv", idx), SoundEnvironmentID: dict.GetNumber("SoundEnv", idx),
WaypointId: dict.GetNumber("Waypoint", idx), WaypointID: dict.GetNumber("Waypoint", idx),
LevelDisplayName: dict.GetString("LevelName", idx), LevelDisplayName: dict.GetString("LevelName", idx),
LevelWarpName: dict.GetString("LevelWarp", idx), LevelWarpName: dict.GetString("LevelWarp", idx),
TitleImageName: dict.GetString("EntryFile", idx), TitleImageName: dict.GetString("EntryFile", idx),
ObjectGroupId0: dict.GetNumber("ObjGrp0", idx), ObjectGroupID0: dict.GetNumber("ObjGrp0", idx),
ObjectGroupId1: dict.GetNumber("ObjGrp1", idx), ObjectGroupID1: dict.GetNumber("ObjGrp1", idx),
ObjectGroupId2: dict.GetNumber("ObjGrp2", idx), ObjectGroupID2: dict.GetNumber("ObjGrp2", idx),
ObjectGroupId3: dict.GetNumber("ObjGrp3", idx), ObjectGroupID3: dict.GetNumber("ObjGrp3", idx),
ObjectGroupId4: dict.GetNumber("ObjGrp4", idx), ObjectGroupID4: dict.GetNumber("ObjGrp4", idx),
ObjectGroupId5: dict.GetNumber("ObjGrp5", idx), ObjectGroupID5: dict.GetNumber("ObjGrp5", idx),
ObjectGroupId6: dict.GetNumber("ObjGrp6", idx), ObjectGroupID6: dict.GetNumber("ObjGrp6", idx),
ObjectGroupId7: dict.GetNumber("ObjGrp7", idx), ObjectGroupID7: dict.GetNumber("ObjGrp7", idx),
ObjectGroupSpawnChance0: dict.GetNumber("ObjPrb0", idx), ObjectGroupSpawnChance0: dict.GetNumber("ObjPrb0", idx),
ObjectGroupSpawnChance1: dict.GetNumber("ObjPrb1", idx), ObjectGroupSpawnChance1: dict.GetNumber("ObjPrb1", idx),
ObjectGroupSpawnChance2: dict.GetNumber("ObjPrb2", idx), ObjectGroupSpawnChance2: dict.GetNumber("ObjPrb2", idx),
@ -542,5 +543,6 @@ func LoadLevelDetails(file []byte) {
} }
LevelDetails[idx] = record LevelDetails[idx] = record
} }
log.Printf("Loaded %d LevelDetails records", len(LevelDetails)) log.Printf("Loaded %d LevelDetails records", len(LevelDetails))
} }

View File

@ -9,9 +9,11 @@ import (
func MapHeaders(line string) map[string]int { func MapHeaders(line string) map[string]int {
m := make(map[string]int) m := make(map[string]int)
r := strings.Split(line, "\t") r := strings.Split(line, "\t")
for index, header := range r { for index, header := range r {
m[header] = index m[header] = index
} }
return m return m
} }
@ -20,6 +22,7 @@ func MapLoadInt(r *[]string, mapping *map[string]int, field string) int {
if ok { if ok {
return d2common.StringToInt(d2common.EmptyToZero(d2common.AsterToEmpty((*r)[index]))) return d2common.StringToInt(d2common.EmptyToZero(d2common.AsterToEmpty((*r)[index])))
} }
return 0 return 0
} }
@ -28,6 +31,7 @@ func MapLoadString(r *[]string, mapping *map[string]int, field string) string {
if ok { if ok {
return d2common.AsterToEmpty((*r)[index]) return d2common.AsterToEmpty((*r)[index])
} }
return "" return ""
} }
@ -40,5 +44,6 @@ func MapLoadUint8(r *[]string, mapping *map[string]int, field string) uint8 {
if ok { if ok {
return d2common.StringToUint8(d2common.EmptyToZero(d2common.AsterToEmpty((*r)[index]))) return d2common.StringToUint8(d2common.EmptyToZero(d2common.AsterToEmpty((*r)[index])))
} }
return 0 return 0
} }

View File

@ -109,8 +109,10 @@ type MissileRecord struct {
UseAttackRating bool // if true, uses 'attack rating' to determine if it hits or misses UseAttackRating bool // if true, uses 'attack rating' to determine if it hits or misses
// if false, has a 95% chance to hit. // if false, has a 95% chance to hit.
AlwaysExplode bool // if true, always calls its collision function when it is destroyed, even if it doesn't hit anything AlwaysExplode bool // if true, always calls its collision function when it is destroyed,
// note that some collision functions (lightning fury) seem to ignore this and always explode regardless of setting (requires investigation) // even if it doesn't hit anything
// note that some collision functions (lightning fury)
// seem to ignore this and always explode regardless of setting (requires investigation)
ClientExplosion bool // if true, does not really exist ClientExplosion bool // if true, does not really exist
// is only aesthetic / client side // is only aesthetic / client side
@ -286,21 +288,26 @@ func createMissileRecord(line string) MissileRecord {
ClientSubMissile: [3]string{r[inc()], r[inc()], r[inc()]}, ClientSubMissile: [3]string{r[inc()], r[inc()], r[inc()]},
ClientHitSubMissile: [4]string{r[inc()], r[inc()], r[inc()], r[inc()]}, ClientHitSubMissile: [4]string{r[inc()], r[inc()], r[inc()], r[inc()]},
} }
return result return result
} }
//nolint:gochecknoglobals // Currently global by design, only written once
var Missiles map[int]*MissileRecord var Missiles map[int]*MissileRecord
func LoadMissiles(file []byte) { func LoadMissiles(file []byte) {
Missiles = make(map[int]*MissileRecord) Missiles = make(map[int]*MissileRecord)
data := strings.Split(string(file), "\r\n")[1:] data := strings.Split(string(file), "\r\n")[1:]
for _, line := range data { for _, line := range data {
if len(line) == 0 { if line == "" {
continue continue
} }
rec := createMissileRecord(line) rec := createMissileRecord(line)
Missiles[rec.Id] = &rec Missiles[rec.Id] = &rec
} }
log.Printf("Loaded %d missiles", len(Missiles)) log.Printf("Loaded %d missiles", len(Missiles))
} }
@ -309,6 +316,7 @@ func loadMissileCalcParam(r *[]string, inc func() int) MissileCalcParam {
Param: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), Param: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])),
Desc: (*r)[inc()], Desc: (*r)[inc()],
} }
return result return result
} }
@ -318,9 +326,11 @@ func loadMissileCalc(r *[]string, inc func() int, params int) MissileCalc {
Desc: (*r)[inc()], Desc: (*r)[inc()],
} }
result.Params = make([]MissileCalcParam, params) result.Params = make([]MissileCalcParam, params)
for p := 0; p < params; p++ { for p := 0; p < params; p++ {
result.Params[p] = loadMissileCalcParam(r, inc) result.Params[p] = loadMissileCalcParam(r, inc)
} }
return result return result
} }
@ -332,6 +342,7 @@ func loadMissileLight(r *[]string, inc func() int) MissileLight {
Green: d2common.StringToUint8(d2common.EmptyToZero((*r)[inc()])), Green: d2common.StringToUint8(d2common.EmptyToZero((*r)[inc()])),
Blue: d2common.StringToUint8(d2common.EmptyToZero((*r)[inc()])), Blue: d2common.StringToUint8(d2common.EmptyToZero((*r)[inc()])),
} }
return result return result
} }
@ -349,6 +360,7 @@ func loadMissileAnimation(r *[]string, inc func() int) MissileAnimation {
SubStartingFrame: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), SubStartingFrame: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])),
SubEndingFrame: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), SubEndingFrame: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])),
} }
return result return result
} }
@ -364,6 +376,7 @@ func loadMissileCollision(r *[]string, inc func() int) MissileCollision {
UseCollisionTimer: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])) == 1, UseCollisionTimer: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])) == 1,
TimerFrames: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), TimerFrames: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])),
} }
return result return result
} }
@ -387,6 +400,7 @@ func loadMissileDamage(r *[]string, inc func() int) MissileDamage {
}, },
DamageSynergyPerCalc: d2common.CalcString((*r)[inc()]), DamageSynergyPerCalc: d2common.CalcString((*r)[inc()]),
} }
return result return result
} }
@ -401,5 +415,6 @@ func loadMissileElementalDamage(r *[]string, inc func() int) MissileElementalDam
d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])),
}, },
} }
return result return result
} }

View File

@ -6,26 +6,32 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
//nolint:gochecknoglobals // Currently global by design, only written once
var MonPresets [][]string var MonPresets [][]string
func LoadMonPresets(file []byte) { func LoadMonPresets(file []byte) {
dict := d2common.LoadDataDictionary(string(file)) dict := d2common.LoadDataDictionary(string(file))
numRecords := len(dict.Data) numRecords := len(dict.Data)
MonPresets = make([][]string, numRecords) MonPresets = make([][]string, numRecords)
for idx := range MonPresets { for idx := range MonPresets {
MonPresets[idx] = make([]string, numRecords) MonPresets[idx] = make([]string, numRecords)
} }
lastAct := 0 lastAct := 0
placeIdx := 0 placeIdx := 0
for dictIdx := range dict.Data { for dictIdx := range dict.Data {
act := dict.GetNumber("Act", dictIdx) act := dict.GetNumber("Act", dictIdx)
if act != lastAct { if act != lastAct {
placeIdx = 0 placeIdx = 0
} }
MonPresets[act][placeIdx] = dict.GetString("Place", dictIdx) MonPresets[act][placeIdx] = dict.GetString("Place", dictIdx)
placeIdx++
lastAct = act lastAct = act
placeIdx++
} }
log.Printf("Loaded %d MonPreset records", len(MonPresets)) log.Printf("Loaded %d MonPreset records", len(MonPresets))
} }

View File

@ -1,3 +1,4 @@
// d2datadict contains loaders for the txt file data
package d2datadict package d2datadict
import ( import (
@ -37,7 +38,7 @@ type MonStatsRecord struct {
// this is the actual internal ID of the unit (this is what the ID pointer // this is the actual internal ID of the unit (this is what the ID pointer
// actually points at) remember that no two units can have the same ID, // actually points at) remember that no two units can have the same ID,
// this will result in lots of unpredictable behaviour and crashes so please // this will result in lots of unpredictable behavior and crashes so please
// dont do it. This 'HarcCodedInDeX' is used for several things, such as // dont do it. This 'HarcCodedInDeX' is used for several things, such as
// determining whenever the unit uses DCC or DC6 graphics (like mephisto // determining whenever the unit uses DCC or DC6 graphics (like mephisto
// and the death animations of Diablo, the Maggoc Queen etc.), the hcIdx // and the death animations of Diablo, the Maggoc Queen etc.), the hcIdx
@ -49,8 +50,8 @@ type MonStatsRecord struct {
// this column contains the ID pointer of the “base” unit for this specific // this column contains the ID pointer of the “base” unit for this specific
// monster type (ex. There are five types of “Fallen”; all of them have // monster type (ex. There are five types of “Fallen”; all of them have
// fallen1 as their “base” unit). The baseID is responsible for some // fallen1 as their “base” unit). The baseID is responsible for some
// hardcoded behaviours, for example moving thru walls (ghosts), knowing // hardcoded behaviors, for example moving thru walls (ghosts), knowing
// what units to ressurect, create etc (putrid defilers, shamans etc), the // what units to resurrect, create etc (putrid defilers, shamans etc), the
// explosion appended to suicide minions (either cold, fire or ice). Thanks // explosion appended to suicide minions (either cold, fire or ice). Thanks
// to Kingpin for additional info on this column. // to Kingpin for additional info on this column.
BaseKey string // BaseId BaseKey string // BaseId
@ -312,7 +313,7 @@ type MonStatsRecord struct {
// these columns control “non-skill-related” missiles used by the monster. // these columns control “non-skill-related” missiles used by the monster.
// For example if you enter a missile ID pointer (from Missiles.txt) in // For example if you enter a missile ID pointer (from Missiles.txt) in
// MissA1 then, whenever the monster uses its A1 mode, it will shoot a // MissA1 then, whenever the monster uses its A1 mode, it will shoot a
// missile, this however will succesfully prevent it from dealing any damage // missile, this however will successfully prevent it from dealing any damage
// with the swing of A1. // with the swing of A1.
// NOTE: for the beginners, A1=Attack1, A2=Attack2, S1=Skill1, S2=Skill2, // NOTE: for the beginners, A1=Attack1, A2=Attack2, S1=Skill1, S2=Skill2,
// S3=Skill3, S4=Skill4, C=Cast, SQ=Sequence. // S3=Skill3, S4=Skill4, C=Cast, SQ=Sequence.
@ -389,7 +390,7 @@ type MonStatsRecord struct {
// Boolean, 1=I can open doors, 0=Im too damn retarded to open doors. Ever // Boolean, 1=I can open doors, 0=Im too damn retarded to open doors. Ever
// wanted to make the game more like D1 (where closing doors could actually // wanted to make the game more like D1 (where closing doors could actually
// protect you), then this column is all you need. By setting this to 0 you // protect you), then this column is all you need. By setting this to 0 you
// will succesfully lobotomize the monster, thus he will not be able to open // will successfully lobotomize the monster, thus he will not be able to open
// doors any more. // doors any more.
CanOpenDoors bool // opendoors CanOpenDoors bool // opendoors
@ -793,10 +794,12 @@ type MonStatsRecord struct {
var MonStats map[string]*MonStatsRecord var MonStats map[string]*MonStatsRecord
//nolint:funlen // Makes no sense to split
func LoadMonStats(file []byte) { func LoadMonStats(file []byte) {
dict := d2common.LoadDataDictionary(string(file)) dict := d2common.LoadDataDictionary(string(file))
numRecords := len(dict.Data) numRecords := len(dict.Data)
MonStats = make(map[string]*MonStatsRecord, numRecords) MonStats = make(map[string]*MonStatsRecord, numRecords)
for idx := range dict.Data { for idx := range dict.Data {
record := &MonStatsRecord{ record := &MonStatsRecord{
Key: dict.GetString("Id", idx), Key: dict.GetString("Id", idx),
@ -1054,5 +1057,6 @@ func LoadMonStats(file []byte) {
} }
MonStats[record.Key] = record MonStats[record.Key] = record
} }
log.Printf("Loaded %d MonStats records", len(MonStats)) log.Printf("Loaded %d MonStats records", len(MonStats))
} }

View File

@ -0,0 +1,369 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
type MonStats2Record struct {
// Key, the object ID MonStatEx feild from MonStat
Key string
// These follow three are apparently unused
Height int
OverlayHeight int
PixelHeight int
// Diameter in subtiles
SizeX int
SizeY int
// This specifies if the size values get used for collision detection
NoGfxHitTest bool
// Bounding box
BoxTop int
BoxLeft int
BoxWidth int
BoxHeight int
// Spawn method used
SpawnMethod int
// Melee radius
MeleeRng int
// base weaponclass?
BaseWeaponClass string
HitClass int
// Available options for equipment
// randomly selected from
HDv []string
TRv []string
LGv []string
Rav []string
Lav []string
RHv []string
LHv []string
SHv []string
S1v []string
S2v []string
S3v []string
S4v []string
S5v []string
S6v []string
S7v []string
S8v []string
// Does the unit have this component
HD bool
TR bool
LG bool
RA bool
LA bool
RH bool
LH bool
SH bool
S1 bool
S2 bool
S3 bool
S4 bool
S5 bool
S6 bool
S7 bool
S8 bool
// Sum of available components
TotalPieces int
// Available animation modes
mDT bool
mNU bool
mWL bool
mGH bool
mA1 bool
mA2 bool
mBL bool
mSC bool
mS1 bool
mS2 bool
mS3 bool
mS4 bool
mDD bool
mKB bool
mSQ bool
mRN bool
// Number of directions for each mode
dDT int
dNU int
dWL int
dGH int
dA1 int
dA2 int
dBL int
dSC int
dS1 int
dS2 int
dS3 int
dS4 int
dDD int
dKB int
dSQ int
dRN int
// Available modes while moving aside from WL and RN
A1mv bool
A2mv bool
SCmv bool
S1mv bool
S2mv bool
S3mv bool
S4mv bool
// If the units is restored on map reload
Restore int
// What maximap index is used for the automap
AutomapCel int
// true of unit uses an automap entry
NoMap bool
// If the units can use overlays
NoOvly bool
// If unit is selectable
IsSelectable bool
// If unit is selectable by allies
AllySelectable bool
// If unit is not selectable
NotSelectable bool
// Kinda unk, used for bonewalls etc that are not properly selectable
shiftSel bool
// if the units corpse is selectable
IsCorpseSelectable bool
// If the unit is attackable
IsAttackable bool
// If the unit is revivable
IsRevivable bool
// If the unit is a critter
IsCritter bool
// If the unit is Small, Small units can be knocked back with 100% efficiency
IsSmall bool
// Large units can be knocked back at 25% efficincy
IsLarge bool
// Possibly to do with sound, usually set for creatures without flesh
IsSoft bool
// Aggressive or harmless, usually NPC's
IsInert bool
// Unknown
objCol bool
// Enables collision on corpse for units
IsCorpseCollidable bool
// Can the corpse be walked through
IsCorpseWalkable bool
// If the unit casts a shadow
HasShadow bool
// If unique palettes should not be used
NoUniqueShift bool
// If multiple layers should be used on death (otherwise only TR)
CompositeDeath bool
// Blood offset?
LocalBlood int
// 0 = don't bleed, 1 = small blood missile, 2 = small and large, > 3 other missiles?
Bleed int
// If the unit is lights up the area
Light int
// Light color
LightR int
LightG int
lightB int
// Palettes per difficulty
NormalPalette int
NightmarePalette int
HellPalatte int
// These two are useless as of 1.07
Heart string
BodyPart string
// Inferno animation stuff
InfernoLen int
InfernoAnim int
InfernoRollback int
// Which mode is used after resurrection
ResurrectMode d2enum.MonsterAnimationMode
// Which skill is used for resurrection
ResurrectSkill string
}
//nolint:gochecknoglobals // Current design issue
var MonStats2 map[string]MonStats2Record
//nolint:funlen //just a big data loader
func LoadMonStats2(file []byte) {
dict := d2common.LoadDataDictionary(string(file))
numRecords := len(dict.Data)
MonStats2 = make(map[string]MonStats2Record, numRecords)
for idx := range dict.Data {
record := MonStats2Record{
Key: dict.GetString("Id", idx),
Height: dict.GetNumber("Height", idx),
OverlayHeight: dict.GetNumber("OverlayHeight", idx),
PixelHeight: dict.GetNumber("pixHeight", idx),
SizeX: dict.GetNumber("SizeX", idx),
SizeY: dict.GetNumber("SizeY", idx),
SpawnMethod: dict.GetNumber("spawnCol", idx),
MeleeRng: dict.GetNumber("MeleeRng", idx),
BaseWeaponClass: dict.GetString("BaseW", idx),
HitClass: dict.GetNumber("HitClass", idx),
HDv: dict.GetDelimitedList("HDv", idx),
TRv: dict.GetDelimitedList("TRv", idx),
LGv: dict.GetDelimitedList("LGv", idx),
Rav: dict.GetDelimitedList("Rav", idx),
Lav: dict.GetDelimitedList("Lav", idx),
RHv: dict.GetDelimitedList("RDv", idx),
LHv: dict.GetDelimitedList("LHv", idx),
SHv: dict.GetDelimitedList("SHv", idx),
S1v: dict.GetDelimitedList("S1v", idx),
S2v: dict.GetDelimitedList("S2v", idx),
S3v: dict.GetDelimitedList("S3v", idx),
S4v: dict.GetDelimitedList("S4v", idx),
S5v: dict.GetDelimitedList("S5v", idx),
S6v: dict.GetDelimitedList("S6v", idx),
S7v: dict.GetDelimitedList("S7v", idx),
S8v: dict.GetDelimitedList("S8v", idx),
HD: dict.GetBool("HD", idx),
TR: dict.GetBool("TR", idx),
LG: dict.GetBool("LG", idx),
RA: dict.GetBool("RA", idx),
LA: dict.GetBool("LA", idx),
RH: dict.GetBool("RH", idx),
LH: dict.GetBool("LH", idx),
SH: dict.GetBool("SH", idx),
S1: dict.GetBool("S1", idx),
S2: dict.GetBool("S2", idx),
S3: dict.GetBool("S3", idx),
S4: dict.GetBool("S4", idx),
S5: dict.GetBool("S5", idx),
S6: dict.GetBool("S6", idx),
S7: dict.GetBool("S7", idx),
S8: dict.GetBool("S8", idx),
TotalPieces: dict.GetNumber("TotalPieces", idx),
mDT: dict.GetBool("mDT", idx),
mNU: dict.GetBool("mNU", idx),
mWL: dict.GetBool("mWL", idx),
mGH: dict.GetBool("mGH", idx),
mA1: dict.GetBool("mA1", idx),
mA2: dict.GetBool("mA2", idx),
mBL: dict.GetBool("mBL", idx),
mSC: dict.GetBool("mSC", idx),
mS1: dict.GetBool("mS1", idx),
mS2: dict.GetBool("mS2", idx),
mS3: dict.GetBool("mS3", idx),
mS4: dict.GetBool("mS4", idx),
mDD: dict.GetBool("mDD", idx),
mKB: dict.GetBool("mKB", idx),
mSQ: dict.GetBool("mSQ", idx),
mRN: dict.GetBool("mRN", idx),
dDT: dict.GetNumber("mDT", idx),
dNU: dict.GetNumber("mNU", idx),
dWL: dict.GetNumber("mWL", idx),
dGH: dict.GetNumber("mGH", idx),
dA1: dict.GetNumber("mA1", idx),
dA2: dict.GetNumber("mA2", idx),
dBL: dict.GetNumber("mBL", idx),
dSC: dict.GetNumber("mSC", idx),
dS1: dict.GetNumber("mS1", idx),
dS2: dict.GetNumber("mS2", idx),
dS3: dict.GetNumber("mS3", idx),
dS4: dict.GetNumber("mS4", idx),
dDD: dict.GetNumber("mDD", idx),
dKB: dict.GetNumber("mKB", idx),
dSQ: dict.GetNumber("mSQ", idx),
dRN: dict.GetNumber("mRN", idx),
A1mv: dict.GetBool("A1mv", idx),
A2mv: dict.GetBool("A2mv", idx),
SCmv: dict.GetBool("SCmv", idx),
S1mv: dict.GetBool("S1mv", idx),
S2mv: dict.GetBool("S2mv", idx),
S3mv: dict.GetBool("S3mv", idx),
S4mv: dict.GetBool("S4mv", idx),
NoGfxHitTest: dict.GetBool("noGfxHitTest", idx),
BoxTop: dict.GetNumber("htTop", idx),
BoxLeft: dict.GetNumber("htLeft", idx),
BoxWidth: dict.GetNumber("htWidth", idx),
BoxHeight: dict.GetNumber("htHeight", idx),
Restore: dict.GetNumber("restore", idx),
AutomapCel: dict.GetNumber("automapCel", idx),
NoMap: dict.GetBool("noMap", idx),
NoOvly: dict.GetBool("noOvly", idx),
IsSelectable: dict.GetBool("isSel", idx),
AllySelectable: dict.GetBool("alSel", idx),
shiftSel: dict.GetBool("shiftSel", idx),
NotSelectable: dict.GetBool("noSel", idx),
IsCorpseSelectable: dict.GetBool("corpseSel", idx),
IsAttackable: dict.GetBool("isAtt", idx),
IsRevivable: dict.GetBool("revive", idx),
IsCritter: dict.GetBool("critter", idx),
IsSmall: dict.GetBool("small", idx),
IsLarge: dict.GetBool("large", idx),
IsSoft: dict.GetBool("soft", idx),
IsInert: dict.GetBool("inert", idx),
objCol: dict.GetBool("objCol", idx),
IsCorpseCollidable: dict.GetBool("deadCol", idx),
IsCorpseWalkable: dict.GetBool("unflatDead", idx),
HasShadow: dict.GetBool("Shadow", idx),
NoUniqueShift: dict.GetBool("noUniqueShift", idx),
CompositeDeath: dict.GetBool("compositeDeath", idx),
LocalBlood: dict.GetNumber("localBlood", idx),
Bleed: dict.GetNumber("Bleed", idx),
Light: dict.GetNumber("Light", idx),
LightR: dict.GetNumber("light-r", idx),
LightG: dict.GetNumber("light-g", idx),
lightB: dict.GetNumber("light-b", idx),
NormalPalette: dict.GetNumber("Utrans", idx),
NightmarePalette: dict.GetNumber("Utrans(N)", idx),
HellPalatte: dict.GetNumber("Utrans(H)", idx),
Heart: dict.GetString("Heart", idx),
BodyPart: dict.GetString("BodyPart", idx),
InfernoLen: dict.GetNumber("InfernoLen", idx),
InfernoAnim: dict.GetNumber("InfernoAnim", idx),
InfernoRollback: dict.GetNumber("InfernoRollback", idx),
ResurrectMode: d2enum.MonsterAnimationModeFromString(dict.GetString("ResurrectMode", idx)),
ResurrectSkill: dict.GetString("ResurrectSkill", idx),
}
MonStats2[record.Key] = record
}
log.Printf("Loaded %d MonStats2 records", len(MonStats2))
}

View File

@ -49,6 +49,7 @@ func LookupObject(act, typ, id int) *ObjectLookupRecord {
if object == nil { if object == nil {
log.Panicf("Failed to look up object Act: %d, Type: %d, Id: %d", act, typ, id) log.Panicf("Failed to look up object Act: %d, Type: %d, Id: %d", act, typ, id)
} }
return object return object
} }
@ -56,6 +57,7 @@ func lookupObject(act, typ, id int, objects [][][]*ObjectLookupRecord) *ObjectLo
if objects[act] != nil && objects[act][typ] != nil && objects[act][typ][id] != nil { if objects[act] != nil && objects[act][typ] != nil && objects[act][typ][id] != nil {
return objects[act][typ][id] return objects[act][typ][id]
} }
return nil return nil
} }
@ -66,6 +68,7 @@ func init() {
func indexObjects(objects []ObjectLookupRecord) [][][]*ObjectLookupRecord { func indexObjects(objects []ObjectLookupRecord) [][][]*ObjectLookupRecord {
// Allocating 6 to allow Acts 1-5 without requiring a -1 at every read. // Allocating 6 to allow Acts 1-5 without requiring a -1 at every read.
indexedObjects = make([][][]*ObjectLookupRecord, 6) indexedObjects = make([][][]*ObjectLookupRecord, 6)
for i := range objects { for i := range objects {
record := &objects[i] record := &objects[i]
if indexedObjects[record.Act] == nil { if indexedObjects[record.Act] == nil {

View File

@ -12,12 +12,15 @@ type ObjectTypeRecord struct {
Token string Token string
} }
//nolint:gochecknoglobals // Currently global by design, only written once
// ObjectTypes contains the name and token for objects
var ObjectTypes []ObjectTypeRecord var ObjectTypes []ObjectTypeRecord
func LoadObjectTypes(objectTypeData []byte) { func LoadObjectTypes(objectTypeData []byte) {
streamReader := d2common.CreateStreamReader(objectTypeData) streamReader := d2common.CreateStreamReader(objectTypeData)
count := streamReader.GetInt32() count := streamReader.GetInt32()
ObjectTypes = make([]ObjectTypeRecord, count) ObjectTypes = make([]ObjectTypeRecord, count)
for i := range ObjectTypes { for i := range ObjectTypes {
nameBytes := streamReader.ReadBytes(32) nameBytes := streamReader.ReadBytes(32)
tokenBytes := streamReader.ReadBytes(20) tokenBytes := streamReader.ReadBytes(20)
@ -26,5 +29,6 @@ func LoadObjectTypes(objectTypeData []byte) {
Token: strings.TrimSpace(strings.ReplaceAll(string(tokenBytes), string(0), "")), Token: strings.TrimSpace(strings.ReplaceAll(string(tokenBytes), string(0), "")),
} }
} }
log.Printf("Loaded %d object types", len(ObjectTypes)) log.Printf("Loaded %d object types", len(ObjectTypes))
} }

View File

@ -119,6 +119,7 @@ type ObjectRecord struct {
// 0 = it doesn't, rest of modes need to be analyzed // 0 = it doesn't, rest of modes need to be analyzed
} }
//nolint:funlen // Makes no sense to split
// CreateObjectRecord parses a row from objects.txt into an object record // CreateObjectRecord parses a row from objects.txt into an object record
func createObjectRecord(props []string) ObjectRecord { func createObjectRecord(props []string) ObjectRecord {
i := -1 i := -1
@ -330,24 +331,31 @@ func createObjectRecord(props []string) ObjectRecord {
AutoMap: d2common.StringToInt(props[inc()]), AutoMap: d2common.StringToInt(props[inc()]),
} }
return result return result
} }
//nolint:gochecknoglobals // Currently global by design, only written once
var Objects map[int]*ObjectRecord var Objects map[int]*ObjectRecord
func LoadObjects(file []byte) { func LoadObjects(file []byte) {
Objects = make(map[int]*ObjectRecord) Objects = make(map[int]*ObjectRecord)
data := strings.Split(string(file), "\r\n")[1:] data := strings.Split(string(file), "\r\n")[1:]
for _, line := range data { for _, line := range data {
if len(line) == 0 { if line == "" {
continue continue
} }
props := strings.Split(line, "\t") props := strings.Split(line, "\t")
if props[2] == "" { if props[2] == "" {
continue // skip a line that doesn't have an id continue // skip a line that doesn't have an id
} }
rec := createObjectRecord(props) rec := createObjectRecord(props)
Objects[rec.Id] = &rec Objects[rec.Id] = &rec
} }
log.Printf("Loaded %d objects", len(Objects)) log.Printf("Loaded %d objects", len(Objects))
} }

View File

@ -71,21 +71,27 @@ func createSoundEntry(soundLine string) SoundEntry {
Block2: d2common.StringToInt(props[inc()]), Block2: d2common.StringToInt(props[inc()]),
Block3: d2common.StringToInt(props[inc()]), Block3: d2common.StringToInt(props[inc()]),
} }
return result return result
} }
//nolint:gochecknoglobals // Currently global by design, only written once
var Sounds map[string]SoundEntry var Sounds map[string]SoundEntry
func LoadSounds(file []byte) { func LoadSounds(file []byte) {
Sounds = make(map[string]SoundEntry) Sounds = make(map[string]SoundEntry)
soundData := strings.Split(string(file), "\r\n")[1:] soundData := strings.Split(string(file), "\r\n")[1:]
for _, line := range soundData { for _, line := range soundData {
if len(line) == 0 { if line == "" {
continue continue
} }
soundEntry := createSoundEntry(line) soundEntry := createSoundEntry(line)
soundEntry.FileName = "/data/global/sfx/" + strings.ReplaceAll(soundEntry.FileName, `\`, "/") soundEntry.FileName = "/data/global/sfx/" + strings.ReplaceAll(soundEntry.FileName, `\`, "/")
Sounds[soundEntry.Handle] = soundEntry Sounds[soundEntry.Handle] = soundEntry
//nolint:gocritic // Debug util code
/* /*
// Use the following code to write out the values // Use the following code to write out the values
f, err := os.OpenFile(`C:\Users\lunat\Desktop\D2\sounds.txt`, f, err := os.OpenFile(`C:\Users\lunat\Desktop\D2\sounds.txt`,
@ -98,6 +104,7 @@ func LoadSounds(file []byte) {
log.Println(err) log.Println(err)
} }
*/ */
} } //nolint:wsl // Debug util code
log.Printf("Loaded %d sound definitions", len(Sounds)) log.Printf("Loaded %d sound definitions", len(Sounds))
} }

View File

@ -93,8 +93,10 @@ type SuperUniqueRecord struct {
// Boolean indicates if the game is expansion or classic // Boolean indicates if the game is expansion or classic
IsExpansion bool // named as "EClass" in the SuperUniques.txt IsExpansion bool // named as "EClass" in the SuperUniques.txt
// This field states whether the SuperUnique will be placed within a radius from his original position(defined by the .ds1 map file), or not. // This field states whether the SuperUnique will be placed within a radius from his original
// false means that the boss will spawn in a random position within a large radius from its actual position in the .ds1 file, // position(defined by the .ds1 map file), or not.
// false means that the boss will spawn in a random position within a large radius from its actual
// position in the .ds1 file,
// true means it will spawn exactly where expected. // true means it will spawn exactly where expected.
AutoPosition bool AutoPosition bool
@ -104,7 +106,8 @@ type SuperUniqueRecord struct {
// Treasure Classes for the 3 Difficulties. // Treasure Classes for the 3 Difficulties.
// These columns list the treasureclass that is valid if this boss is killed and drops something. // These columns list the treasureclass that is valid if this boss is killed and drops something.
// These fields must contain the values taken from the "TreasureClass" column in TreasureClassEx.txt (Expansion) or TreasureClass (Classic). // These fields must contain the values taken from the "TreasureClass" column in TreasureClassEx.txt (Expansion)
// or TreasureClass (Classic).
TreasureClassNormal string TreasureClassNormal string
TreasureClassNightmare string TreasureClassNightmare string
TreasureClassHell string TreasureClassHell string
@ -120,6 +123,7 @@ var SuperUniques map[string]*SuperUniqueRecord
func LoadSuperUniques(file []byte) { func LoadSuperUniques(file []byte) {
dictionary := d2common.LoadDataDictionary(string(file)) dictionary := d2common.LoadDataDictionary(string(file))
SuperUniques = make(map[string]*SuperUniqueRecord, len(dictionary.Data)) SuperUniques = make(map[string]*SuperUniqueRecord, len(dictionary.Data))
for idx := range dictionary.Data { for idx := range dictionary.Data {
record := &SuperUniqueRecord{ record := &SuperUniqueRecord{
Key: dictionary.GetString("Superunique", idx), Key: dictionary.GetString("Superunique", idx),
@ -146,5 +150,6 @@ func LoadSuperUniques(file []byte) {
} }
SuperUniques[record.Key] = record SuperUniques[record.Key] = record
} }
log.Printf("Loaded %d SuperUnique records", len(SuperUniques)) log.Printf("Loaded %d SuperUnique records", len(SuperUniques))
} }

View File

@ -99,6 +99,7 @@ func createUniqueItemRecord(r []string) UniqueItemRecord {
createUniqueItemProperty(&r, inc), createUniqueItemProperty(&r, inc),
}, },
} }
return result return result
} }
@ -109,6 +110,7 @@ func createUniqueItemProperty(r *[]string, inc func() int) UniqueItemProperty {
Min: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), Min: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])),
Max: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), Max: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])),
} }
return result return result
} }
@ -117,17 +119,21 @@ var UniqueItems map[string]*UniqueItemRecord
func LoadUniqueItems(file []byte) { func LoadUniqueItems(file []byte) {
UniqueItems = make(map[string]*UniqueItemRecord) UniqueItems = make(map[string]*UniqueItemRecord)
data := strings.Split(string(file), "\r\n")[1:] data := strings.Split(string(file), "\r\n")[1:]
for _, line := range data { for _, line := range data {
if len(line) == 0 { if line == "" {
continue continue
} }
r := strings.Split(line, "\t") r := strings.Split(line, "\t")
// skip rows that are not enabled // skip rows that are not enabled
if r[2] != "1" { if r[2] != "1" {
continue continue
} }
rec := createUniqueItemRecord(r) rec := createUniqueItemRecord(r)
UniqueItems[rec.Code] = &rec UniqueItems[rec.Code] = &rec
} }
log.Printf("Loaded %d unique items", len(UniqueItems)) log.Printf("Loaded %d unique items", len(UniqueItems))
} }

View File

@ -0,0 +1,57 @@
// Manually edited to remove duplicate
// If you generate it again you need fix it up
// Code generated by "string2enum -samepkg -linecomment -type MonsterAnimationMode ."; DO NOT EDIT.
package d2enum
import "fmt"
// MonsterAnimationModeFromString returns the MonsterAnimationMode enum corresponding to s.
func MonsterAnimationModeFromString(s string) MonsterAnimationMode {
if len(s) == 0 {
return 0
}
for i := range _MonsterAnimationMode_index[:len(_MonsterAnimationMode_index)-1] {
if s == _MonsterAnimationMode_name[_MonsterAnimationMode_index[i]:_MonsterAnimationMode_index[i+1]] {
return MonsterAnimationMode(i)
}
}
panic(fmt.Errorf("unable to locate MonsterAnimationMode enum corresponding to %q", s))
}
func _(s string) {
// Check for duplicate string values in type "MonsterAnimationMode".
switch s {
// 0
case "DT":
// 1
case "NU":
// 2
case "WL":
// 3
case "GH":
// 4
case "A1":
// 5
case "A2":
// 6
case "BL":
// 7
case "SC":
// 8
case "S1":
// 9
case "S2":
// 10
case "S3":
// 11
case "S4":
// 12
case "DD":
// 14
case "xx":
// 15
case "RN":
}
}

View File

@ -279,6 +279,7 @@ const (
// --- Enemy Data --- // --- Enemy Data ---
MonStats = "/data/global/excel/monstats.txt" MonStats = "/data/global/excel/monstats.txt"
MonStats2 = "/data/global/excel/monstats2.txt"
MonPreset = "/data/global/excel/monpreset.txt" MonPreset = "/data/global/excel/monpreset.txt"
SuperUniques = "/data/global/excel/SuperUniques.txt" SuperUniques = "/data/global/excel/SuperUniques.txt"

View File

@ -47,3 +47,15 @@ func (v *DataDictionary) GetNumber(fieldName string, index int) int {
} }
return result return result
} }
func (v *DataDictionary) GetDelimitedList(fieldName string, index int) []string {
return strings.Split(v.GetString(fieldName, index), ",")
}
func (v *DataDictionary) GetBool(fieldName string, index int) bool {
n := v.GetNumber(fieldName, index)
if n > 1 {
log.Panic("GetBool on non-bool field")
}
return n == 1
}

View File

@ -41,7 +41,7 @@ func CreateMapRenderer(mapEngine *d2mapengine.MapEngine, term d2interface.Termin
result.debugVisLevel = level result.debugVisLevel = level
}) })
if mapEngine.LevelType().Id != 0 { if mapEngine.LevelType().ID != 0 {
result.generateTileCache() result.generateTileCache()
} }

View File

@ -14,7 +14,7 @@ import (
) )
func (mr *MapRenderer) generateTileCache() { func (mr *MapRenderer) generateTileCache() {
mr.palette, _ = loadPaletteForAct(d2enum.RegionIdType(mr.mapEngine.LevelType().Id)) mr.palette, _ = loadPaletteForAct(d2enum.RegionIdType(mr.mapEngine.LevelType().ID))
mapEngineSize := mr.mapEngine.Size() mapEngineSize := mr.mapEngine.Size()
for idx, tile := range *mr.mapEngine.Tiles() { for idx, tile := range *mr.mapEngine.Tiles() {
@ -197,7 +197,7 @@ func (mr *MapRenderer) getRandomTile(tiles []d2dt1.Tile, x, y int, seed int64) b
var tileSeed uint64 var tileSeed uint64
tileSeed = uint64(seed) + uint64(x) tileSeed = uint64(seed) + uint64(x)
tileSeed *= uint64(y) + uint64(mr.mapEngine.LevelType().Id) tileSeed *= uint64(y) + uint64(mr.mapEngine.LevelType().ID)
tileSeed ^= tileSeed << 13 tileSeed ^= tileSeed << 13
tileSeed ^= tileSeed >> 17 tileSeed ^= tileSeed >> 17

View File

@ -431,6 +431,7 @@ func loadDataDict() error {
{d2resource.SoundSettings, d2datadict.LoadSounds}, {d2resource.SoundSettings, d2datadict.LoadSounds},
{d2resource.AnimationData, d2data.LoadAnimationData}, {d2resource.AnimationData, d2data.LoadAnimationData},
{d2resource.MonStats, d2datadict.LoadMonStats}, {d2resource.MonStats, d2datadict.LoadMonStats},
{d2resource.MonStats2, d2datadict.LoadMonStats2},
{d2resource.MonPreset, d2datadict.LoadMonPresets}, {d2resource.MonPreset, d2datadict.LoadMonPresets},
{d2resource.MagicPrefix, d2datadict.LoadMagicPrefix}, {d2resource.MagicPrefix, d2datadict.LoadMagicPrefix},
{d2resource.MagicSuffix, d2datadict.LoadMagicSuffix}, {d2resource.MagicSuffix, d2datadict.LoadMagicSuffix},