From aae565d5288a2fa904d9f7ba4733df74ec92a79f Mon Sep 17 00:00:00 2001 From: Ziemas Date: Mon, 29 Jun 2020 18:37:11 +0200 Subject: [PATCH] Monstat2 loading and a bunch of lint issues (#491) * MonStat2 loader * Fix a bunch of lint issues in d2datadict --- d2common/d2data/d2datadict/automap.go | 17 +- d2common/d2data/d2datadict/charstats.go | 4 +- d2common/d2data/d2datadict/cubemain.go | 42 +- .../d2data/d2datadict/difficultylevels.go | 7 +- d2common/d2data/d2datadict/experience.go | 2 + d2common/d2data/d2datadict/gems.go | 3 + d2common/d2data/d2datadict/hireling.go | 6 +- d2common/d2data/d2datadict/item_affix.go | 32 +- d2common/d2data/d2datadict/item_common.go | 12 +- d2common/d2data/d2datadict/itemstatcost.go | 28 +- d2common/d2data/d2datadict/level_maze.go | 9 +- d2common/d2data/d2datadict/level_presets.go | 22 +- d2common/d2data/d2datadict/level_sub.go | 8 +- d2common/d2data/d2datadict/level_types.go | 14 +- d2common/d2data/d2datadict/level_warp.go | 9 +- d2common/d2data/d2datadict/levels.go | 290 +++++++------- d2common/d2data/d2datadict/map_helper.go | 5 + d2common/d2data/d2datadict/missiles.go | 21 +- d2common/d2data/d2datadict/monpreset.go | 8 +- d2common/d2data/d2datadict/monstats.go | 14 +- d2common/d2data/d2datadict/monstats2.go | 369 ++++++++++++++++++ d2common/d2data/d2datadict/object_query.go | 3 + d2common/d2data/d2datadict/object_types.go | 4 + d2common/d2data/d2datadict/objects.go | 10 +- d2common/d2data/d2datadict/sounds.go | 11 +- d2common/d2data/d2datadict/super_uniques.go | 11 +- d2common/d2data/d2datadict/unique_items.go | 8 +- .../monsteranimationmode_string2enum.go | 57 +++ d2common/d2resource/resource_paths.go | 1 + d2common/data_dictionary.go | 12 + d2core/d2map/d2maprenderer/renderer.go | 2 +- d2core/d2map/d2maprenderer/tile_cache.go | 4 +- main.go | 1 + 33 files changed, 805 insertions(+), 241 deletions(-) create mode 100644 d2common/d2data/d2datadict/monstats2.go create mode 100644 d2common/d2enum/monsteranimationmode_string2enum.go diff --git a/d2common/d2data/d2datadict/automap.go b/d2common/d2data/d2datadict/automap.go index 30a6b0b9..681b38a6 100644 --- a/d2common/d2data/d2datadict/automap.go +++ b/d2common/d2data/d2datadict/automap.go @@ -39,10 +39,10 @@ type AutoMapRecord struct { // whatever you like..." // The values seem functional but naming conventions // vary between LevelNames. - //Type1 string - //Type2 string - //Type3 string - //Type4 string // Note: I commented these out for now because they supposedly aren't useful see the LoadAutoMaps function. + // Type1 string + // Type2 string + // Type3 string + // 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 // will be applied to the specified tiles. The frames @@ -58,6 +58,7 @@ type AutoMapRecord struct { } // AutoMaps contains all data in AutoMap.txt. +//nolint:gochecknoglobals // Current design is to have these global var AutoMaps []*AutoMapRecord // LoadAutoMaps populates AutoMaps with the data from AutoMap.txt. @@ -71,9 +72,9 @@ func LoadAutoMaps(file []byte) { // Construct records AutoMaps = make([]*AutoMapRecord, len(d.Data)) + for idx := range d.Data { - // Row 2603 is a separator with all empty field values - if idx == 2603 { + if d.GetString("LevelName", idx) == "Expansion" { continue } @@ -88,7 +89,9 @@ func LoadAutoMaps(file []byte) { //Type1: d.GetString("Type1", idx), //Type2: d.GetString("Type2", 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)) diff --git a/d2common/d2data/d2datadict/charstats.go b/d2common/d2data/d2datadict/charstats.go index ce86aa38..7bad5e7d 100644 --- a/d2common/d2data/d2datadict/charstats.go +++ b/d2common/d2data/d2datadict/charstats.go @@ -7,6 +7,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" ) +// Charecter stats type CharStatsRecord struct { Class d2enum.Hero @@ -59,8 +60,8 @@ var CharStats map[d2enum.Hero]*CharStatsRecord var charStringMap map[string]d2enum.Hero var weaponTokenMap map[string]d2enum.WeaponClass +//nolint:funlen // Makes no sense to split func LoadCharStats(file []byte) { - charStringMap = map[string]d2enum.Hero{ "Amazon": d2enum.HeroAmazon, "Barbarian": d2enum.HeroBarbarian, @@ -186,5 +187,6 @@ func LoadCharStats(file []byte) { } CharStats[record.Class] = record } + log.Printf("Loaded %d CharStats records", len(CharStats)) } diff --git a/d2common/d2data/d2datadict/cubemain.go b/d2common/d2data/d2datadict/cubemain.go index 5c8a4953..8aaf74c2 100644 --- a/d2common/d2data/d2datadict/cubemain.go +++ b/d2common/d2data/d2datadict/cubemain.go @@ -130,7 +130,7 @@ type CubeRecipeItemProperty struct { // string or an integer. // // 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 // them, these fields support calculations." 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 // the data from CubeMain.txt. func LoadCubeRecipes(file []byte) { - // Load data d := d2common.LoadDataDictionary(string(file)) @@ -188,7 +187,6 @@ func LoadCubeRecipes(file []byte) { // Create outputs - output "", b, c CubeRecipes[idx].Outputs = make([]CubeRecipeResult, 3) for o, outLabel := range outputLabels { - CubeRecipes[idx].Outputs[o] = CubeRecipeResult{ Item: newCubeRecipeItem( d.GetString(outputFields[o], idx)), @@ -201,7 +199,6 @@ func LoadCubeRecipes(file []byte) { // Create properties - mod 1-5 properties := make([]CubeRecipeItemProperty, 5) for p, prop := range propLabels { - properties[p] = CubeRecipeItemProperty{ Code: d.GetString(outLabel+prop, idx), Chance: d.GetNumber(outLabel+prop+" chance", idx), @@ -213,7 +210,6 @@ func LoadCubeRecipes(file []byte) { CubeRecipes[idx].Outputs[o].Properties = properties } - } 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, // convert to int and assign to item.Count for idx, arg := range args { - if strings.HasPrefix(arg, "qty") { - 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 + if !strings.HasPrefix(arg, "qty") { + continue } + + 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 @@ -272,10 +273,12 @@ func newCubeRecipeItem(f string) CubeRecipeItem { func classFieldToEnum(f string) []d2enum.Hero { split := splitFieldValue(f) enums := make([]d2enum.Hero, len(split)) + for idx, class := range split { if class == "" { continue } + switch class { case "bar": enums[idx] = d2enum.HeroBarbarian @@ -295,6 +298,7 @@ func classFieldToEnum(f string) []d2enum.Hero { log.Fatalf("Unknown hero token: '%s'", class) } } + return enums } diff --git a/d2common/d2data/d2datadict/difficultylevels.go b/d2common/d2data/d2datadict/difficultylevels.go index 3b3cec1d..aa343d49 100644 --- a/d2common/d2data/d2datadict/difficultylevels.go +++ b/d2common/d2data/d2datadict/difficultylevels.go @@ -6,8 +6,11 @@ import ( "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 +// DifficultyLevelRecord contain the parameters that change for different difficultios type DifficultyLevelRecord struct { // Difficulty name. it is hardcoded and you cannot add new ones unless you do // some Code Edits @@ -30,7 +33,7 @@ type DifficultyLevelRecord struct { // txt file... // 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 DropChanceRare int // UberCodeOddsGood DropChanceSet int // UberCodeOddsGood @@ -89,6 +92,7 @@ type DifficultyLevelRecord struct { } +// LoadDifficultyLevels is a loader for difficultylevels.txt func LoadDifficultyLevels(file []byte) { dict := d2common.LoadDataDictionary(string(file)) numRows := len(dict.Data) @@ -119,5 +123,4 @@ func LoadDifficultyLevels(file []byte) { } log.Printf("Loaded %d DifficultyLevel records", len(DifficultyLevels)) - } diff --git a/d2common/d2data/d2datadict/experience.go b/d2common/d2data/d2datadict/experience.go index e34e5ff7..55bf9bf1 100644 --- a/d2common/d2data/d2datadict/experience.go +++ b/d2common/d2data/d2datadict/experience.go @@ -89,6 +89,7 @@ func LoadExperienceBreakpoints(file []byte) { d2enum.HeroPaladin: d.GetNumber("Paladin", idx), d2enum.HeroSorceress: d.GetNumber("Sorceress", idx), } + continue } @@ -108,5 +109,6 @@ func LoadExperienceBreakpoints(file []byte) { ExperienceBreakpoints[record.Level] = record } + log.Printf("Loaded %d ExperienceBreakpoint records", len(ExperienceBreakpoints)) } diff --git a/d2common/d2data/d2datadict/gems.go b/d2common/d2data/d2datadict/gems.go index b887b14d..6dae81d8 100644 --- a/d2common/d2data/d2datadict/gems.go +++ b/d2common/d2data/d2datadict/gems.go @@ -52,7 +52,9 @@ type GemsRecord struct { func LoadGems(file []byte) { d := d2common.LoadDataDictionary(string(file)) + var Gems []*GemsRecord + for idx := range d.Data { if d.GetString("name", idx) != "Expansion" { /* @@ -105,5 +107,6 @@ func LoadGems(file []byte) { Gems = append(Gems, gem) } } + log.Printf("Loaded %d Gems records", len(Gems)) } diff --git a/d2common/d2data/d2datadict/hireling.go b/d2common/d2data/d2datadict/hireling.go index 34a6ee89..a393cdcd 100644 --- a/d2common/d2data/d2datadict/hireling.go +++ b/d2common/d2data/d2datadict/hireling.go @@ -9,7 +9,7 @@ import ( type HirelingRecord struct { Hireling string SubType string - Id int + ID int Class int Act int Difficulty int @@ -84,11 +84,12 @@ type HirelingRecord struct { func LoadHireling(file []byte) { d := d2common.LoadDataDictionary(string(file)) var Hirelings []*HirelingRecord + for idx := range d.Data { hireling := &HirelingRecord{ Hireling: d.GetString("Hireling", idx), SubType: d.GetString("SubType", idx), - Id: d.GetNumber("Id", idx), + ID: d.GetNumber("Id", idx), Class: d.GetNumber("Class", idx), Act: d.GetNumber("Act", idx), Difficulty: d.GetNumber("Difficulty", idx), @@ -161,5 +162,6 @@ func LoadHireling(file []byte) { } Hirelings = append(Hirelings, hireling) } + log.Printf("Loaded %d Hireling records", len(Hirelings)) } diff --git a/d2common/d2data/d2datadict/item_affix.go b/d2common/d2data/d2datadict/item_affix.go index 567396fb..f6b48c7a 100644 --- a/d2common/d2data/d2datadict/item_affix.go +++ b/d2common/d2data/d2datadict/item_affix.go @@ -16,26 +16,28 @@ var MagicSuffixRecords []*ItemAffixCommonRecord var AffixMagicGroups []*ItemAffixCommonGroup -var superType d2enum.ItemAffixSuperType -var subType d2enum.ItemAffixSubType - +// LoadMagicPrefix loads MagicPrefix.txt func LoadMagicPrefix(file []byte) { - superType = d2enum.ItemAffixPrefix - subType = d2enum.ItemAffixMagic + superType := d2enum.ItemAffixPrefix + + subType := d2enum.ItemAffixMagic + MagicPrefixDictionary, MagicPrefixRecords = loadDictionary(file, superType, subType) } +// LoadMagicSuffix loads MagicSuffix.txt func LoadMagicSuffix(file []byte) { - superType = d2enum.ItemAffixSuffix - subType = d2enum.ItemAffixMagic + superType := d2enum.ItemAffixSuffix + + subType := d2enum.ItemAffixMagic + MagicSuffixDictionary, MagicSuffixRecords = loadDictionary(file, superType, subType) } func getAffixString(t1 d2enum.ItemAffixSuperType, t2 d2enum.ItemAffixSubType) string { var name string = "" - switch t2 { - case d2enum.ItemAffixMagic: + if t2 == d2enum.ItemAffixMagic { name = "Magic" } @@ -47,7 +49,6 @@ func getAffixString(t1 d2enum.ItemAffixSuperType, t2 d2enum.ItemAffixSubType) st } return name - } func loadDictionary( @@ -59,6 +60,7 @@ func loadDictionary( records := createItemAffixRecords(dict, superType, subType) name := getAffixString(superType, subType) log.Printf("Loaded %d %s records", len(dict.Data), name) + return dict, records } @@ -111,8 +113,8 @@ func createItemAffixRecords( subType d2enum.ItemAffixSubType, ) []*ItemAffixCommonRecord { records := make([]*ItemAffixCommonRecord, 0) - for index := range d.Data { + for index := range d.Data { affix := &ItemAffixCommonRecord{ Name: d.GetString("Name", index), Version: d.GetNumber("version", index), @@ -179,6 +181,7 @@ func createItemAffixRecords( records = append(records, affix) } + return records } @@ -193,14 +196,17 @@ func (g *ItemAffixCommonGroup) AddMember(a *ItemAffixCommonRecord) { if g.Members == nil { g.Members = make(map[string]*ItemAffixCommonRecord) } + g.Members[a.Name] = a } func (g *ItemAffixCommonGroup) GetTotalFrequency() int { total := 0 + for _, affix := range g.Members { total += affix.Frequency } + return total } @@ -249,6 +255,8 @@ func (a *ItemAffixCommonRecord) ProbabilityToSpawn(qlvl int) float64 { if (qlvl > a.MaxLevel) || (qlvl < a.Level) { return 0.0 } - p := (float64)(a.Frequency) / (float64)(a.Group.GetTotalFrequency()) + + p := float64(a.Frequency) / float64(a.Group.GetTotalFrequency()) + return p } diff --git a/d2common/d2data/d2datadict/item_common.go b/d2common/d2data/d2datadict/item_common.go index 1ee88199..ce3e3ac9 100644 --- a/d2common/d2data/d2datadict/item_common.go +++ b/d2common/d2data/d2datadict/item_common.go @@ -181,30 +181,35 @@ type ItemVendorParams struct { MagicLevel uint8 } -// Loading Functions var CommonItems map[string]*ItemCommonRecord func LoadCommonItems(file []byte, source d2enum.InventoryItemType) *map[string]*ItemCommonRecord { if CommonItems == nil { CommonItems = make(map[string]*ItemCommonRecord) } + items := make(map[string]*ItemCommonRecord) data := strings.Split(string(file), "\r\n") mapping := MapHeaders(data[0]) + for lineno, line := range data { if lineno == 0 { continue } - if len(line) == 0 { + + if line == "" { continue } + rec := createCommonItemRecord(line, &mapping, source) items[rec.Code] = &rec CommonItems[rec.Code] = &rec } + return &items } +//nolint:funlen // Makes no sens to split func createCommonItemRecord(line string, mapping *map[string]int, source d2enum.InventoryItemType) ItemCommonRecord { r := strings.Split(line, "\t") result := ItemCommonRecord{ @@ -363,6 +368,7 @@ func createCommonItemRecord(line string, mapping *map[string]int, source d2enum. Multibuy: MapLoadBool(&r, mapping, "multibuy"), } + return result } @@ -398,6 +404,7 @@ func createItemVendorParams(r *[]string, mapping *map[string]int) map[string]*It } result[name] = &wvp } + 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].Calc = d2common.CalcString(MapLoadString(r, mapping, "calc"+strconv.Itoa(i))) } + return result } diff --git a/d2common/d2data/d2datadict/itemstatcost.go b/d2common/d2data/d2datadict/itemstatcost.go index 2315ec7a..4724619b 100644 --- a/d2common/d2data/d2datadict/itemstatcost.go +++ b/d2common/d2data/d2datadict/itemstatcost.go @@ -115,10 +115,10 @@ const ( // just adds the stat to the unit directly OpDefault = OperatorType(iota) - // adds opstat.base * statvalue / 100 to the opstat. + // Op1 adds opstat.base * statvalue / 100 to the opstat. 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 // 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 @@ -127,56 +127,56 @@ const ( // description every frame, while the values remain unchanged serverside. 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 // remember the stat is increased by a percentage rather then by adding // an integer. 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 // properly adds the defense to the armor and not to the character // directly!) 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 // that are found on the character. 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 // behind it either. 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 // behind it because frankly enough it just doesn't work serverside, it // only updates clientside so this op is essentially useless. 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 // the stat added (doesn't work for non characters) 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 // CharStats.txt for the amount of vitality the stat added (doesn't work // for non characters) 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 - // 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 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 - // 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 // (this is why it is used for +% durability, +% level requirement, // +% damage, +% defense ). @@ -241,6 +241,7 @@ stuff var ItemStatCosts map[string]*ItemStatCostRecord +// LoadItemStatCosts loads ItemStatCostRecord's from text func LoadItemStatCosts(file []byte) { d := d2common.LoadDataDictionary(string(file)) numRecords := len(d.Data) @@ -317,5 +318,6 @@ func LoadItemStatCosts(file []byte) { ItemStatCosts[record.Name] = record } + log.Printf("Loaded %d ItemStatCost records", len(ItemStatCosts)) } diff --git a/d2common/d2data/d2datadict/level_maze.go b/d2common/d2data/d2datadict/level_maze.go index 189af255..5d6d7677 100644 --- a/d2common/d2data/d2datadict/level_maze.go +++ b/d2common/d2data/d2datadict/level_maze.go @@ -14,7 +14,7 @@ type LevelMazeDetailsRecord struct { // ID from Levels.txt // NOTE: Cave 1 is the Den of Evil, its associated treasure level is quest // only. - LevelId int // Level + LevelID int // Level // the minimum number of .ds1 map sections that will make up the maze in // Normal, Nightmare and Hell difficulties. @@ -36,21 +36,24 @@ type LevelMazeDetailsRecord struct { var LevelMazeDetails map[int]*LevelMazeDetailsRecord +// LoadLevelMazeDetails loads LevelMazeDetailsRecords from text file func LoadLevelMazeDetails(file []byte) { dict := d2common.LoadDataDictionary(string(file)) numRecords := len(dict.Data) LevelMazeDetails = make(map[int]*LevelMazeDetailsRecord, numRecords) + for idx := range dict.Data { record := &LevelMazeDetailsRecord{ Name: dict.GetString("Name", idx), - LevelId: dict.GetNumber("Level", idx), + LevelID: dict.GetNumber("Level", idx), NumRoomsNormal: dict.GetNumber("Rooms", idx), NumRoomsNightmare: dict.GetNumber("Rooms(N)", idx), NumRoomsHell: dict.GetNumber("Rooms(H)", idx), SizeX: dict.GetNumber("SizeX", idx), SizeY: dict.GetNumber("SizeY", idx), } - LevelMazeDetails[record.LevelId] = record + LevelMazeDetails[record.LevelID] = record } + log.Printf("Loaded %d LevelMazeDetails records", len(LevelMazeDetails)) } diff --git a/d2common/d2data/d2datadict/level_presets.go b/d2common/d2data/d2datadict/level_presets.go index ca33891f..776c1260 100644 --- a/d2common/d2data/d2datadict/level_presets.go +++ b/d2common/d2data/d2datadict/level_presets.go @@ -9,8 +9,8 @@ import ( type LevelPresetRecord struct { Name string - DefinitionId int - LevelId int + DefinitionID int + LevelID int Populate bool Logicals bool Outdoors bool @@ -39,8 +39,8 @@ func createLevelPresetRecord(props []string) LevelPresetRecord { } result := LevelPresetRecord{ Name: props[inc()], - DefinitionId: d2common.StringToInt(props[inc()]), - LevelId: d2common.StringToInt(props[inc()]), + DefinitionID: d2common.StringToInt(props[inc()]), + LevelID: d2common.StringToInt(props[inc()]), Populate: d2common.StringToUint8(props[inc()]) == 1, Logicals: 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, Expansion: d2common.StringToUint8(props[inc()]) == 1, } + return result } var LevelPresets map[int]LevelPresetRecord +// LoadLevelPresets loads level presets from text file func LoadLevelPresets(file []byte) { LevelPresets = make(map[int]LevelPresetRecord) data := strings.Split(string(file), "\r\n")[1:] + for _, line := range data { - if len(line) == 0 { + if line == "" { continue } + props := strings.Split(line, "\t") + if props[1] == "" { continue // any line without a definition id is skipped (e.g. the "Expansion" line) } + rec := createLevelPresetRecord(props) - LevelPresets[rec.DefinitionId] = rec + LevelPresets[rec.DefinitionID] = rec } + log.Printf("Loaded %d level presets", len(LevelPresets)) } +// LevelPreset looks up a LevelPresetRecord by ID func LevelPreset(id int) LevelPresetRecord { for i := 0; i < len(LevelPresets); i++ { - if LevelPresets[i].DefinitionId == id { + if LevelPresets[i].DefinitionID == id { return LevelPresets[i] } } diff --git a/d2common/d2data/d2datadict/level_sub.go b/d2common/d2data/d2datadict/level_sub.go index e8e524b4..5b9ecb28 100644 --- a/d2common/d2data/d2datadict/level_sub.go +++ b/d2common/d2data/d2datadict/level_sub.go @@ -14,7 +14,7 @@ type LevelSubstitutionRecord struct { // 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' // and 'SubShrine'. (added by Paul Siramy) - Id int // Type + ID int // Type // What .ds1 is being used. File string // File @@ -69,10 +69,9 @@ func LoadLevelSubstitutions(file []byte) { LevelSubstitutions = make(map[int]*LevelSubstitutionRecord, numRecords) for idx := range dict.Data { - record := &LevelSubstitutionRecord{ Name: dict.GetString("Name", idx), - Id: dict.GetNumber("Type", idx), + ID: dict.GetNumber("Type", idx), File: dict.GetString("File", idx), IsExpansion: dict.GetNumber("Expansion", idx) > 0, BorderType: dict.GetNumber("BordType", idx), @@ -95,7 +94,8 @@ func LoadLevelSubstitutions(file []byte) { GridMax4: dict.GetNumber("Max4", idx), } - LevelSubstitutions[record.Id] = record + LevelSubstitutions[record.ID] = record } + log.Printf("Loaded %d LevelSubstitution records", len(LevelSubstitutions)) } diff --git a/d2common/d2data/d2datadict/level_types.go b/d2common/d2data/d2datadict/level_types.go index b7a576b8..96175b75 100644 --- a/d2common/d2data/d2datadict/level_types.go +++ b/d2common/d2data/d2datadict/level_types.go @@ -9,7 +9,7 @@ import ( type LevelTypeRecord struct { Name string - Id int + ID int Files [32]string Beta bool Act int @@ -21,29 +21,35 @@ var LevelTypes []LevelTypeRecord func LoadLevelTypes(file []byte) { data := strings.Split(string(file), "\r\n")[1:] LevelTypes = make([]LevelTypeRecord, len(data)) + for i, j := 0, 0; i < len(data); i, j = i+1, j+1 { idx := -1 inc := func() int { idx++ return idx } - if len(data[i]) == 0 { + + if data[i] == "" { continue } + parts := strings.Split(data[i], "\t") + if parts[0] == "Expansion" { j-- continue } + 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 { LevelTypes[j].Files[fileIdx] = parts[inc()] if LevelTypes[j].Files[fileIdx] == "0" { LevelTypes[j].Files[fileIdx] = "" } - } + LevelTypes[j].Beta = parts[inc()] != "1" LevelTypes[j].Act = d2common.StringToInt(parts[inc()]) LevelTypes[j].Expansion = parts[inc()] != "1" diff --git a/d2common/d2data/d2datadict/level_warp.go b/d2common/d2data/d2datadict/level_warp.go index 11f5a208..3732ef50 100644 --- a/d2common/d2data/d2datadict/level_warp.go +++ b/d2common/d2data/d2datadict/level_warp.go @@ -7,7 +7,7 @@ import ( ) type LevelWarpRecord struct { - Id int32 + ID int32 SelectX int32 SelectY int32 SelectDX int32 @@ -21,16 +21,20 @@ type LevelWarpRecord struct { Direction string } +//nolint:gochecknoglobals // Currently global by design, only written once +// LevelWarps loaded from txt records var LevelWarps map[int]*LevelWarpRecord +// LoadLevelWarps loads LevelWarpRecord's from text file data func LoadLevelWarps(levelWarpData []byte) { LevelWarps = make(map[int]*LevelWarpRecord) streamReader := d2common.CreateStreamReader(levelWarpData) numRecords := int(streamReader.GetInt32()) + for i := 0; i < numRecords; i++ { id := int(streamReader.GetInt32()) LevelWarps[id] = &LevelWarpRecord{} - LevelWarps[id].Id = int32(id) + LevelWarps[id].ID = int32(id) LevelWarps[id].SelectX = streamReader.GetInt32() LevelWarps[id].SelectY = streamReader.GetInt32() LevelWarps[id].SelectDX = streamReader.GetInt32() @@ -44,5 +48,6 @@ func LoadLevelWarps(levelWarpData []byte) { LevelWarps[id].Direction = string(streamReader.GetByte()) streamReader.SkipBytes(3) } + log.Printf("Loaded %d level warps", len(LevelWarps)) } diff --git a/d2common/d2data/d2datadict/levels.go b/d2common/d2data/d2datadict/levels.go index 280d45e6..6141ad85 100644 --- a/d2common/d2data/d2datadict/levels.go +++ b/d2common/d2data/d2datadict/levels.go @@ -37,7 +37,7 @@ type LevelDetailsRecord struct { // additional layers. 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 // lvlprest.txt to set the size for the .ds1 file. SizeXNormal int // SizeX @@ -102,7 +102,7 @@ type LevelDetailsRecord struct { // cycles, because they always use the light values specified in Intensity, // 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 - // enviroment sound effects set to true. + // environment sound effects set to true. IsInside bool // IsInside // 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 // your actual map (the .ds1 fle). // Example: Normally Cave levels are only using vis 0-3 and wilderness areas 4-7 . - LevelLinkId0 int // Vis0 - LevelLinkId1 int // Vis1 - LevelLinkId2 int // Vis2 - LevelLinkId3 int // Vis3 - LevelLinkId4 int // Vis4 - LevelLinkId5 int // Vis5 - LevelLinkId6 int // Vis6 - LevelLinkId7 int // Vis7 + LevelLinkID0 int // Vis0 + LevelLinkID1 int // Vis1 + LevelLinkID2 int // Vis2 + LevelLinkID3 int // Vis3 + LevelLinkID4 int // Vis4 + LevelLinkID5 int // Vis5 + LevelLinkID6 int // Vis6 + LevelLinkID7 int // Vis7 // 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 // behavior on the graphics is controlled by lvlwarp.txt. Your Warps must // match your Vis. // Example: If your level uses Vis 3,5,7 then you must also use Warp 3,5,7 . - WarpGraphicsId0 int // Warp0 - WarpGraphicsId1 int // Warp1 - WarpGraphicsId2 int // Warp2 - WarpGraphicsId3 int // Warp3 - WarpGraphicsId4 int // Warp4 - WarpGraphicsId5 int // Warp5 - WarpGraphicsId6 int // Warp6 - WarpGraphicsId7 int // Warp7 + WarpGraphicsID0 int // Warp0 + WarpGraphicsID1 int // Warp1 + WarpGraphicsID2 int // Warp2 + WarpGraphicsID3 int // Warp3 + WarpGraphicsID4 int // Warp4 + WarpGraphicsID5 int // Warp5 + WarpGraphicsID6 int // Warp6 + WarpGraphicsID7 int // Warp7 // These settings handle the light intensity as well as its RGB components 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 // 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 // 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. // NOTE: you need to manually add from mon11 to mon25 and from nmon11 to // nmon25 ! - MonsterId1Normal string // mon1 - MonsterId2Normal string // mon2 - MonsterId3Normal string // mon3 - MonsterId4Normal string // mon4 - MonsterId5Normal string // mon5 - MonsterId6Normal string // mon6 - MonsterId7Normal string // mon7 - MonsterId8Normal string // mon8 - MonsterId9Normal string // mon9 - MonsterId10Normal string // mon10 + MonsterID1Normal string // mon1 + MonsterID2Normal string // mon2 + MonsterID3Normal string // mon3 + MonsterID4Normal string // mon4 + MonsterID5Normal string // mon5 + MonsterID6Normal string // mon6 + MonsterID7Normal string // mon7 + MonsterID8Normal string // mon8 + MonsterID9Normal string // mon9 + MonsterID10Normal string // mon10 - MonsterId1Nightmare string // nmon1 - MonsterId2Nightmare string // nmon2 - MonsterId3Nightmare string // nmon3 - MonsterId4Nightmare string // nmon4 - MonsterId5Nightmare string // nmon5 - MonsterId6Nightmare string // nmon6 - MonsterId7Nightmare string // nmon7 - MonsterId8Nightmare string // nmon8 - MonsterId9Nightmare string // nmon9 - MonsterId10Nightmare string // nmon10 + MonsterID1Nightmare string // nmon1 + MonsterID2Nightmare string // nmon2 + MonsterID3Nightmare string // nmon3 + MonsterID4Nightmare string // nmon4 + MonsterID5Nightmare string // nmon5 + MonsterID6Nightmare string // nmon6 + MonsterID7Nightmare string // nmon7 + MonsterID8Nightmare string // nmon8 + MonsterID9Nightmare string // nmon9 + MonsterID10Nightmare string // nmon10 // Gravestench - adding additional fields for Hell, original txt combined // the nighmare and hell ID's stringo the same field - MonsterId1Hell string // nmon1 - MonsterId2Hell string // nmon2 - MonsterId3Hell string // nmon3 - MonsterId4Hell string // nmon4 - MonsterId5Hell string // nmon5 - MonsterId6Hell string // nmon6 - MonsterId7Hell string // nmon7 - MonsterId8Hell string // nmon8 - MonsterId9Hell string // nmon9 - MonsterId10Hell string // nmon10 + MonsterID1Hell string // nmon1 + MonsterID2Hell string // nmon2 + MonsterID3Hell string // nmon3 + MonsterID4Hell string // nmon4 + MonsterID5Hell string // nmon5 + MonsterID6Hell string // nmon6 + MonsterID7Hell string // nmon7 + MonsterID8Hell string // nmon8 + MonsterID9Hell string // nmon9 + MonsterID10Hell string // nmon10 // Give preference to monsters set to ranged=1 in MonStats.txt on Nightmare // 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 // following this simple ASM edit // (https://d2mods.info/forum/viewtopic.php?f=8&t=53969&p=425179&hilit=umon#p425179) - MonsterUniqueId1 string // umon1 - MonsterUniqueId2 string // umon2 - MonsterUniqueId3 string // umon3 - MonsterUniqueId4 string // umon4 - MonsterUniqueId5 string // umon5 - MonsterUniqueId6 string // umon6 - MonsterUniqueId7 string // umon7 - MonsterUniqueId8 string // umon8 - MonsterUniqueId9 string // umon9 - MonsterUniqueId10 string // umon10 + MonsterUniqueID1 string // umon1 + MonsterUniqueID2 string // umon2 + MonsterUniqueID3 string // umon3 + MonsterUniqueID4 string // umon4 + MonsterUniqueID5 string // umon5 + MonsterUniqueID6 string // umon6 + MonsterUniqueID7 string // umon7 + MonsterUniqueID8 string // umon8 + MonsterUniqueID9 string // umon9 + MonsterUniqueID10 string // umon10 // 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 // in monstats2.txt. Critters are in reality only present clientside. - MonsterCritterId1 string // cmon1 - MonsterCritterId2 string // cmon2 - MonsterCritterId3 string // cmon3 - MonsterCritterId4 string // cmon4 + MonsterCritterID1 string // cmon1 + MonsterCritterID2 string // cmon2 + MonsterCritterID3 string // cmon3 + MonsterCritterID4 string // cmon4 // Controls the chance for a critter to spawn. MonsterCritter1SpawnChance int // cpct1 @@ -330,13 +330,13 @@ type LevelDetailsRecord struct { // Themes // 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 // for the level // NOTE: you can switch waypoint destinations between areas this way, not // 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 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, // taken from Objgroup.txt. - ObjectGroupId0 int // ObjGrp0 - ObjectGroupId1 int // ObjGrp1 - ObjectGroupId2 int // ObjGrp2 - ObjectGroupId3 int // ObjGrp3 - ObjectGroupId4 int // ObjGrp4 - ObjectGroupId5 int // ObjGrp5 - ObjectGroupId6 int // ObjGrp6 - ObjectGroupId7 int // ObjGrp7 + ObjectGroupID0 int // ObjGrp0 + ObjectGroupID1 int // ObjGrp1 + ObjectGroupID2 int // ObjGrp2 + ObjectGroupID3 int // ObjGrp3 + ObjectGroupID4 int // ObjGrp4 + ObjectGroupID5 int // ObjGrp5 + ObjectGroupID6 int // ObjGrp6 + ObjectGroupID7 int // ObjGrp7 // These fields indicates the chance for each object group to spawn (if you // use ObjGrp0 then set ObjPrb0 to a value below 100) @@ -387,6 +387,7 @@ func GetLevelDetails(id int) *LevelDetailsRecord { return nil } +//nolint:funlen // Txt loader, makes no sense to split func LoadLevelDetails(file []byte) { dict := d2common.LoadDataDictionary(string(file)) numRecords := len(dict.Data) @@ -425,22 +426,22 @@ func LoadLevelDetails(file []byte) { SubTheme: dict.GetNumber("SubTheme", idx), SubWaypoint: dict.GetNumber("SubWaypoint", idx), SubShrine: dict.GetNumber("SubShrine", idx), - LevelLinkId0: dict.GetNumber("Vis0", idx), - LevelLinkId1: dict.GetNumber("Vis1", idx), - LevelLinkId2: dict.GetNumber("Vis2", idx), - LevelLinkId3: dict.GetNumber("Vis3", idx), - LevelLinkId4: dict.GetNumber("Vis4", idx), - LevelLinkId5: dict.GetNumber("Vis5", idx), - LevelLinkId6: dict.GetNumber("Vis6", idx), - LevelLinkId7: dict.GetNumber("Vis7", idx), - WarpGraphicsId0: dict.GetNumber("Warp0", idx), - WarpGraphicsId1: dict.GetNumber("Warp1", idx), - WarpGraphicsId2: dict.GetNumber("Warp2", idx), - WarpGraphicsId3: dict.GetNumber("Warp3", idx), - WarpGraphicsId4: dict.GetNumber("Warp4", idx), - WarpGraphicsId5: dict.GetNumber("Warp5", idx), - WarpGraphicsId6: dict.GetNumber("Warp6", idx), - WarpGraphicsId7: dict.GetNumber("Warp7", idx), + LevelLinkID0: dict.GetNumber("Vis0", idx), + LevelLinkID1: dict.GetNumber("Vis1", idx), + LevelLinkID2: dict.GetNumber("Vis2", idx), + LevelLinkID3: dict.GetNumber("Vis3", idx), + LevelLinkID4: dict.GetNumber("Vis4", idx), + LevelLinkID5: dict.GetNumber("Vis5", idx), + LevelLinkID6: dict.GetNumber("Vis6", idx), + LevelLinkID7: dict.GetNumber("Vis7", idx), + WarpGraphicsID0: dict.GetNumber("Warp0", idx), + WarpGraphicsID1: dict.GetNumber("Warp1", idx), + WarpGraphicsID2: dict.GetNumber("Warp2", idx), + WarpGraphicsID3: dict.GetNumber("Warp3", idx), + WarpGraphicsID4: dict.GetNumber("Warp4", idx), + WarpGraphicsID5: dict.GetNumber("Warp5", idx), + WarpGraphicsID6: dict.GetNumber("Warp6", idx), + WarpGraphicsID7: dict.GetNumber("Warp7", idx), LightIntensity: dict.GetNumber("Intensity", idx), Red: dict.GetNumber("Red", idx), Green: dict.GetNumber("Green", idx), @@ -449,7 +450,7 @@ func LoadLevelDetails(file []byte) { PortalRepositionEnable: dict.GetNumber("Position", idx) > 0, SaveMonsterStates: 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), MonsterLevelNormal: dict.GetNumber("MonLvl1", idx), MonsterLevelNightmare: dict.GetNumber("MonLvl2", idx), @@ -469,68 +470,68 @@ func LoadLevelDetails(file []byte) { MonsterWanderEnable: dict.GetNumber("MonWndr", idx) > 0, MonsterSpecialWalk: dict.GetNumber("MonSpcWalk", idx) > 0, NumMonsterTypes: dict.GetNumber("NumMon", idx), - MonsterId1Normal: dict.GetString("mon1", idx), - MonsterId2Normal: dict.GetString("mon2", idx), - MonsterId3Normal: dict.GetString("mon3", idx), - MonsterId4Normal: dict.GetString("mon4", idx), - MonsterId5Normal: dict.GetString("mon5", idx), - MonsterId6Normal: dict.GetString("mon6", idx), - MonsterId7Normal: dict.GetString("mon7", idx), - MonsterId8Normal: dict.GetString("mon8", idx), - MonsterId9Normal: dict.GetString("mon9", idx), - MonsterId10Normal: dict.GetString("mon10", idx), - MonsterId1Nightmare: dict.GetString("nmon1", idx), - MonsterId2Nightmare: dict.GetString("nmon2", idx), - MonsterId3Nightmare: dict.GetString("nmon3", idx), - MonsterId4Nightmare: dict.GetString("nmon4", idx), - MonsterId5Nightmare: dict.GetString("nmon5", idx), - MonsterId6Nightmare: dict.GetString("nmon6", idx), - MonsterId7Nightmare: dict.GetString("nmon7", idx), - MonsterId8Nightmare: dict.GetString("nmon8", idx), - MonsterId9Nightmare: dict.GetString("nmon9", idx), - MonsterId10Nightmare: dict.GetString("nmon10", idx), - MonsterId1Hell: dict.GetString("nmon1", idx), - MonsterId2Hell: dict.GetString("nmon2", idx), - MonsterId3Hell: dict.GetString("nmon3", idx), - MonsterId4Hell: dict.GetString("nmon4", idx), - MonsterId5Hell: dict.GetString("nmon5", idx), - MonsterId6Hell: dict.GetString("nmon6", idx), - MonsterId7Hell: dict.GetString("nmon7", idx), - MonsterId8Hell: dict.GetString("nmon8", idx), - MonsterId9Hell: dict.GetString("nmon9", idx), - MonsterId10Hell: dict.GetString("nmon10", idx), + MonsterID1Normal: dict.GetString("mon1", idx), + MonsterID2Normal: dict.GetString("mon2", idx), + MonsterID3Normal: dict.GetString("mon3", idx), + MonsterID4Normal: dict.GetString("mon4", idx), + MonsterID5Normal: dict.GetString("mon5", idx), + MonsterID6Normal: dict.GetString("mon6", idx), + MonsterID7Normal: dict.GetString("mon7", idx), + MonsterID8Normal: dict.GetString("mon8", idx), + MonsterID9Normal: dict.GetString("mon9", idx), + MonsterID10Normal: dict.GetString("mon10", idx), + MonsterID1Nightmare: dict.GetString("nmon1", idx), + MonsterID2Nightmare: dict.GetString("nmon2", idx), + MonsterID3Nightmare: dict.GetString("nmon3", idx), + MonsterID4Nightmare: dict.GetString("nmon4", idx), + MonsterID5Nightmare: dict.GetString("nmon5", idx), + MonsterID6Nightmare: dict.GetString("nmon6", idx), + MonsterID7Nightmare: dict.GetString("nmon7", idx), + MonsterID8Nightmare: dict.GetString("nmon8", idx), + MonsterID9Nightmare: dict.GetString("nmon9", idx), + MonsterID10Nightmare: dict.GetString("nmon10", idx), + MonsterID1Hell: dict.GetString("nmon1", idx), + MonsterID2Hell: dict.GetString("nmon2", idx), + MonsterID3Hell: dict.GetString("nmon3", idx), + MonsterID4Hell: dict.GetString("nmon4", idx), + MonsterID5Hell: dict.GetString("nmon5", idx), + MonsterID6Hell: dict.GetString("nmon6", idx), + MonsterID7Hell: dict.GetString("nmon7", idx), + MonsterID8Hell: dict.GetString("nmon8", idx), + MonsterID9Hell: dict.GetString("nmon9", idx), + MonsterID10Hell: dict.GetString("nmon10", idx), MonsterPreferRanged: dict.GetNumber("rangedspawn", idx) > 0, - MonsterUniqueId1: dict.GetString("umon1", idx), - MonsterUniqueId2: dict.GetString("umon2", idx), - MonsterUniqueId3: dict.GetString("umon3", idx), - MonsterUniqueId4: dict.GetString("umon4", idx), - MonsterUniqueId5: dict.GetString("umon5", idx), - MonsterUniqueId6: dict.GetString("umon6", idx), - MonsterUniqueId7: dict.GetString("umon7", idx), - MonsterUniqueId8: dict.GetString("umon8", idx), - MonsterUniqueId9: dict.GetString("umon9", idx), - MonsterUniqueId10: dict.GetString("umon10", idx), - MonsterCritterId1: dict.GetString("cmon1", idx), - MonsterCritterId2: dict.GetString("cmon2", idx), - MonsterCritterId3: dict.GetString("cmon3", idx), - MonsterCritterId4: dict.GetString("cmon4", idx), + MonsterUniqueID1: dict.GetString("umon1", idx), + MonsterUniqueID2: dict.GetString("umon2", idx), + MonsterUniqueID3: dict.GetString("umon3", idx), + MonsterUniqueID4: dict.GetString("umon4", idx), + MonsterUniqueID5: dict.GetString("umon5", idx), + MonsterUniqueID6: dict.GetString("umon6", idx), + MonsterUniqueID7: dict.GetString("umon7", idx), + MonsterUniqueID8: dict.GetString("umon8", idx), + MonsterUniqueID9: dict.GetString("umon9", idx), + MonsterUniqueID10: dict.GetString("umon10", idx), + MonsterCritterID1: dict.GetString("cmon1", idx), + MonsterCritterID2: dict.GetString("cmon2", idx), + MonsterCritterID3: dict.GetString("cmon3", idx), + MonsterCritterID4: dict.GetString("cmon4", idx), MonsterCritter1SpawnChance: dict.GetNumber("cpct1", idx), MonsterCritter2SpawnChance: dict.GetNumber("cpct2", idx), MonsterCritter3SpawnChance: dict.GetNumber("cpct3", idx), MonsterCritter4SpawnChance: dict.GetNumber("cpct4", idx), - SoundEnvironmentId: dict.GetNumber("SoundEnv", idx), - WaypointId: dict.GetNumber("Waypoint", idx), + SoundEnvironmentID: dict.GetNumber("SoundEnv", idx), + WaypointID: dict.GetNumber("Waypoint", idx), LevelDisplayName: dict.GetString("LevelName", idx), LevelWarpName: dict.GetString("LevelWarp", idx), TitleImageName: dict.GetString("EntryFile", idx), - ObjectGroupId0: dict.GetNumber("ObjGrp0", idx), - ObjectGroupId1: dict.GetNumber("ObjGrp1", idx), - ObjectGroupId2: dict.GetNumber("ObjGrp2", idx), - ObjectGroupId3: dict.GetNumber("ObjGrp3", idx), - ObjectGroupId4: dict.GetNumber("ObjGrp4", idx), - ObjectGroupId5: dict.GetNumber("ObjGrp5", idx), - ObjectGroupId6: dict.GetNumber("ObjGrp6", idx), - ObjectGroupId7: dict.GetNumber("ObjGrp7", idx), + ObjectGroupID0: dict.GetNumber("ObjGrp0", idx), + ObjectGroupID1: dict.GetNumber("ObjGrp1", idx), + ObjectGroupID2: dict.GetNumber("ObjGrp2", idx), + ObjectGroupID3: dict.GetNumber("ObjGrp3", idx), + ObjectGroupID4: dict.GetNumber("ObjGrp4", idx), + ObjectGroupID5: dict.GetNumber("ObjGrp5", idx), + ObjectGroupID6: dict.GetNumber("ObjGrp6", idx), + ObjectGroupID7: dict.GetNumber("ObjGrp7", idx), ObjectGroupSpawnChance0: dict.GetNumber("ObjPrb0", idx), ObjectGroupSpawnChance1: dict.GetNumber("ObjPrb1", idx), ObjectGroupSpawnChance2: dict.GetNumber("ObjPrb2", idx), @@ -542,5 +543,6 @@ func LoadLevelDetails(file []byte) { } LevelDetails[idx] = record } + log.Printf("Loaded %d LevelDetails records", len(LevelDetails)) } diff --git a/d2common/d2data/d2datadict/map_helper.go b/d2common/d2data/d2datadict/map_helper.go index 86040e99..3278dda4 100644 --- a/d2common/d2data/d2datadict/map_helper.go +++ b/d2common/d2data/d2datadict/map_helper.go @@ -9,9 +9,11 @@ import ( func MapHeaders(line string) map[string]int { m := make(map[string]int) r := strings.Split(line, "\t") + for index, header := range r { m[header] = index } + return m } @@ -20,6 +22,7 @@ func MapLoadInt(r *[]string, mapping *map[string]int, field string) int { if ok { return d2common.StringToInt(d2common.EmptyToZero(d2common.AsterToEmpty((*r)[index]))) } + return 0 } @@ -28,6 +31,7 @@ func MapLoadString(r *[]string, mapping *map[string]int, field string) string { if ok { return d2common.AsterToEmpty((*r)[index]) } + return "" } @@ -40,5 +44,6 @@ func MapLoadUint8(r *[]string, mapping *map[string]int, field string) uint8 { if ok { return d2common.StringToUint8(d2common.EmptyToZero(d2common.AsterToEmpty((*r)[index]))) } + return 0 } diff --git a/d2common/d2data/d2datadict/missiles.go b/d2common/d2data/d2datadict/missiles.go index 9e48651b..94bc76f4 100644 --- a/d2common/d2data/d2datadict/missiles.go +++ b/d2common/d2data/d2datadict/missiles.go @@ -109,8 +109,10 @@ type MissileRecord struct { UseAttackRating bool // if true, uses 'attack rating' to determine if it hits or misses // 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 - // note that some collision functions (lightning fury) seem to ignore this and always explode regardless of setting (requires investigation) + AlwaysExplode bool // if true, always calls its collision function when it is destroyed, + // 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 // is only aesthetic / client side @@ -286,21 +288,26 @@ func createMissileRecord(line string) MissileRecord { ClientSubMissile: [3]string{r[inc()], r[inc()], r[inc()]}, ClientHitSubMissile: [4]string{r[inc()], r[inc()], r[inc()], r[inc()]}, } + return result } +//nolint:gochecknoglobals // Currently global by design, only written once var Missiles map[int]*MissileRecord func LoadMissiles(file []byte) { Missiles = make(map[int]*MissileRecord) data := strings.Split(string(file), "\r\n")[1:] + for _, line := range data { - if len(line) == 0 { + if line == "" { continue } + rec := createMissileRecord(line) Missiles[rec.Id] = &rec } + 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()])), Desc: (*r)[inc()], } + return result } @@ -318,9 +326,11 @@ func loadMissileCalc(r *[]string, inc func() int, params int) MissileCalc { Desc: (*r)[inc()], } result.Params = make([]MissileCalcParam, params) + for p := 0; p < params; p++ { result.Params[p] = loadMissileCalcParam(r, inc) } + return result } @@ -332,6 +342,7 @@ func loadMissileLight(r *[]string, inc func() int) MissileLight { Green: d2common.StringToUint8(d2common.EmptyToZero((*r)[inc()])), Blue: d2common.StringToUint8(d2common.EmptyToZero((*r)[inc()])), } + return result } @@ -349,6 +360,7 @@ func loadMissileAnimation(r *[]string, inc func() int) MissileAnimation { SubStartingFrame: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), SubEndingFrame: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), } + return result } @@ -364,6 +376,7 @@ func loadMissileCollision(r *[]string, inc func() int) MissileCollision { UseCollisionTimer: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])) == 1, TimerFrames: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), } + return result } @@ -387,6 +400,7 @@ func loadMissileDamage(r *[]string, inc func() int) MissileDamage { }, DamageSynergyPerCalc: d2common.CalcString((*r)[inc()]), } + return result } @@ -401,5 +415,6 @@ func loadMissileElementalDamage(r *[]string, inc func() int) MissileElementalDam d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), }, } + return result } diff --git a/d2common/d2data/d2datadict/monpreset.go b/d2common/d2data/d2datadict/monpreset.go index 8680437e..6ba04089 100644 --- a/d2common/d2data/d2datadict/monpreset.go +++ b/d2common/d2data/d2datadict/monpreset.go @@ -6,26 +6,32 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common" ) +//nolint:gochecknoglobals // Currently global by design, only written once var MonPresets [][]string func LoadMonPresets(file []byte) { dict := d2common.LoadDataDictionary(string(file)) numRecords := len(dict.Data) MonPresets = make([][]string, numRecords) + for idx := range MonPresets { MonPresets[idx] = make([]string, numRecords) } lastAct := 0 placeIdx := 0 + for dictIdx := range dict.Data { act := dict.GetNumber("Act", dictIdx) if act != lastAct { placeIdx = 0 } + MonPresets[act][placeIdx] = dict.GetString("Place", dictIdx) - placeIdx++ lastAct = act + + placeIdx++ } + log.Printf("Loaded %d MonPreset records", len(MonPresets)) } diff --git a/d2common/d2data/d2datadict/monstats.go b/d2common/d2data/d2datadict/monstats.go index 6de5604e..a04c03e5 100644 --- a/d2common/d2data/d2datadict/monstats.go +++ b/d2common/d2data/d2datadict/monstats.go @@ -1,3 +1,4 @@ +// d2datadict contains loaders for the txt file data package d2datadict import ( @@ -37,7 +38,7 @@ type MonStatsRecord struct { // 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, - // this will result in lots of unpredictable behaviour and crashes so please + // this will result in lots of unpredictable behavior and crashes so please // don’t do it. This 'HarcCodedInDeX' is used for several things, such as // determining whenever the unit uses DCC or DC6 graphics (like mephisto // 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 // monster type (ex. There are five types of “Fallen”; all of them have // fallen1 as their “base” unit). The baseID is responsible for some - // hardcoded behaviours, for example moving thru walls (ghosts), knowing - // what units to ressurect, create etc (putrid defilers, shamans etc), the + // hardcoded behaviors, for example moving thru walls (ghosts), knowing + // what units to resurrect, create etc (putrid defilers, shamans etc), the // explosion appended to suicide minions (either cold, fire or ice). Thanks // to Kingpin for additional info on this column. BaseKey string // BaseId @@ -312,7 +313,7 @@ type MonStatsRecord struct { // these columns control “non-skill-related” missiles used by the monster. // 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 - // 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. // NOTE: for the beginners, A1=Attack1, A2=Attack2, S1=Skill1, S2=Skill2, // S3=Skill3, S4=Skill4, C=Cast, SQ=Sequence. @@ -389,7 +390,7 @@ type MonStatsRecord struct { // Boolean, 1=I can open doors, 0=I’m too damn retarded to open doors. Ever // 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 - // 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. CanOpenDoors bool // opendoors @@ -793,10 +794,12 @@ type MonStatsRecord struct { var MonStats map[string]*MonStatsRecord +//nolint:funlen // Makes no sense to split func LoadMonStats(file []byte) { dict := d2common.LoadDataDictionary(string(file)) numRecords := len(dict.Data) MonStats = make(map[string]*MonStatsRecord, numRecords) + for idx := range dict.Data { record := &MonStatsRecord{ Key: dict.GetString("Id", idx), @@ -1054,5 +1057,6 @@ func LoadMonStats(file []byte) { } MonStats[record.Key] = record } + log.Printf("Loaded %d MonStats records", len(MonStats)) } diff --git a/d2common/d2data/d2datadict/monstats2.go b/d2common/d2data/d2datadict/monstats2.go new file mode 100644 index 00000000..ff95880c --- /dev/null +++ b/d2common/d2data/d2datadict/monstats2.go @@ -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)) +} diff --git a/d2common/d2data/d2datadict/object_query.go b/d2common/d2data/d2datadict/object_query.go index eba7e303..7cdc677a 100644 --- a/d2common/d2data/d2datadict/object_query.go +++ b/d2common/d2data/d2datadict/object_query.go @@ -49,6 +49,7 @@ func LookupObject(act, typ, id int) *ObjectLookupRecord { if object == nil { log.Panicf("Failed to look up object Act: %d, Type: %d, Id: %d", act, typ, id) } + 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 { return objects[act][typ][id] } + return nil } @@ -66,6 +68,7 @@ func init() { func indexObjects(objects []ObjectLookupRecord) [][][]*ObjectLookupRecord { // Allocating 6 to allow Acts 1-5 without requiring a -1 at every read. indexedObjects = make([][][]*ObjectLookupRecord, 6) + for i := range objects { record := &objects[i] if indexedObjects[record.Act] == nil { diff --git a/d2common/d2data/d2datadict/object_types.go b/d2common/d2data/d2datadict/object_types.go index 6ab891e0..249e71d4 100644 --- a/d2common/d2data/d2datadict/object_types.go +++ b/d2common/d2data/d2datadict/object_types.go @@ -12,12 +12,15 @@ type ObjectTypeRecord struct { Token string } +//nolint:gochecknoglobals // Currently global by design, only written once +// ObjectTypes contains the name and token for objects var ObjectTypes []ObjectTypeRecord func LoadObjectTypes(objectTypeData []byte) { streamReader := d2common.CreateStreamReader(objectTypeData) count := streamReader.GetInt32() ObjectTypes = make([]ObjectTypeRecord, count) + for i := range ObjectTypes { nameBytes := streamReader.ReadBytes(32) tokenBytes := streamReader.ReadBytes(20) @@ -26,5 +29,6 @@ func LoadObjectTypes(objectTypeData []byte) { Token: strings.TrimSpace(strings.ReplaceAll(string(tokenBytes), string(0), "")), } } + log.Printf("Loaded %d object types", len(ObjectTypes)) } diff --git a/d2common/d2data/d2datadict/objects.go b/d2common/d2data/d2datadict/objects.go index 122ecc0a..7ac785a2 100644 --- a/d2common/d2data/d2datadict/objects.go +++ b/d2common/d2data/d2datadict/objects.go @@ -119,6 +119,7 @@ type ObjectRecord struct { // 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 func createObjectRecord(props []string) ObjectRecord { i := -1 @@ -330,24 +331,31 @@ func createObjectRecord(props []string) ObjectRecord { AutoMap: d2common.StringToInt(props[inc()]), } + return result } +//nolint:gochecknoglobals // Currently global by design, only written once var Objects map[int]*ObjectRecord func LoadObjects(file []byte) { Objects = make(map[int]*ObjectRecord) data := strings.Split(string(file), "\r\n")[1:] + for _, line := range data { - if len(line) == 0 { + if line == "" { continue } + props := strings.Split(line, "\t") + if props[2] == "" { continue // skip a line that doesn't have an id } + rec := createObjectRecord(props) Objects[rec.Id] = &rec } + log.Printf("Loaded %d objects", len(Objects)) } diff --git a/d2common/d2data/d2datadict/sounds.go b/d2common/d2data/d2datadict/sounds.go index 3c565c6c..152210a2 100644 --- a/d2common/d2data/d2datadict/sounds.go +++ b/d2common/d2data/d2datadict/sounds.go @@ -71,21 +71,27 @@ func createSoundEntry(soundLine string) SoundEntry { Block2: d2common.StringToInt(props[inc()]), Block3: d2common.StringToInt(props[inc()]), } + return result } +//nolint:gochecknoglobals // Currently global by design, only written once var Sounds map[string]SoundEntry func LoadSounds(file []byte) { Sounds = make(map[string]SoundEntry) soundData := strings.Split(string(file), "\r\n")[1:] + for _, line := range soundData { - if len(line) == 0 { + if line == "" { continue } + soundEntry := createSoundEntry(line) soundEntry.FileName = "/data/global/sfx/" + strings.ReplaceAll(soundEntry.FileName, `\`, "/") Sounds[soundEntry.Handle] = soundEntry + + //nolint:gocritic // Debug util code /* // Use the following code to write out the values f, err := os.OpenFile(`C:\Users\lunat\Desktop\D2\sounds.txt`, @@ -98,6 +104,7 @@ func LoadSounds(file []byte) { log.Println(err) } */ - } + } //nolint:wsl // Debug util code + log.Printf("Loaded %d sound definitions", len(Sounds)) } diff --git a/d2common/d2data/d2datadict/super_uniques.go b/d2common/d2data/d2datadict/super_uniques.go index 6685bbb5..09257854 100644 --- a/d2common/d2data/d2datadict/super_uniques.go +++ b/d2common/d2data/d2datadict/super_uniques.go @@ -93,8 +93,10 @@ type SuperUniqueRecord struct { // Boolean indicates if the game is expansion or classic 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. - // false means that the boss will spawn in a random position within a large radius from its actual position in the .ds1 file, + // This field states whether the SuperUnique will be placed within a radius from his original + // 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. AutoPosition bool @@ -104,7 +106,8 @@ type SuperUniqueRecord struct { // Treasure Classes for the 3 Difficulties. // 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 TreasureClassNightmare string TreasureClassHell string @@ -120,6 +123,7 @@ var SuperUniques map[string]*SuperUniqueRecord func LoadSuperUniques(file []byte) { dictionary := d2common.LoadDataDictionary(string(file)) SuperUniques = make(map[string]*SuperUniqueRecord, len(dictionary.Data)) + for idx := range dictionary.Data { record := &SuperUniqueRecord{ Key: dictionary.GetString("Superunique", idx), @@ -146,5 +150,6 @@ func LoadSuperUniques(file []byte) { } SuperUniques[record.Key] = record } + log.Printf("Loaded %d SuperUnique records", len(SuperUniques)) } diff --git a/d2common/d2data/d2datadict/unique_items.go b/d2common/d2data/d2datadict/unique_items.go index 89583f44..5a2aa4cb 100644 --- a/d2common/d2data/d2datadict/unique_items.go +++ b/d2common/d2data/d2datadict/unique_items.go @@ -99,6 +99,7 @@ func createUniqueItemRecord(r []string) UniqueItemRecord { createUniqueItemProperty(&r, inc), }, } + return result } @@ -109,6 +110,7 @@ func createUniqueItemProperty(r *[]string, inc func() int) UniqueItemProperty { Min: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), Max: d2common.StringToInt(d2common.EmptyToZero((*r)[inc()])), } + return result } @@ -117,17 +119,21 @@ var UniqueItems map[string]*UniqueItemRecord func LoadUniqueItems(file []byte) { UniqueItems = make(map[string]*UniqueItemRecord) data := strings.Split(string(file), "\r\n")[1:] + for _, line := range data { - if len(line) == 0 { + if line == "" { continue } + r := strings.Split(line, "\t") // skip rows that are not enabled if r[2] != "1" { continue } + rec := createUniqueItemRecord(r) UniqueItems[rec.Code] = &rec } + log.Printf("Loaded %d unique items", len(UniqueItems)) } diff --git a/d2common/d2enum/monsteranimationmode_string2enum.go b/d2common/d2enum/monsteranimationmode_string2enum.go new file mode 100644 index 00000000..b837a36f --- /dev/null +++ b/d2common/d2enum/monsteranimationmode_string2enum.go @@ -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": + } +} diff --git a/d2common/d2resource/resource_paths.go b/d2common/d2resource/resource_paths.go index 0311e792..c78e784f 100644 --- a/d2common/d2resource/resource_paths.go +++ b/d2common/d2resource/resource_paths.go @@ -279,6 +279,7 @@ const ( // --- Enemy Data --- MonStats = "/data/global/excel/monstats.txt" + MonStats2 = "/data/global/excel/monstats2.txt" MonPreset = "/data/global/excel/monpreset.txt" SuperUniques = "/data/global/excel/SuperUniques.txt" diff --git a/d2common/data_dictionary.go b/d2common/data_dictionary.go index 2c5d2b51..d123337b 100644 --- a/d2common/data_dictionary.go +++ b/d2common/data_dictionary.go @@ -47,3 +47,15 @@ func (v *DataDictionary) GetNumber(fieldName string, index int) int { } 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 +} diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index cd66790a..f3991a12 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -41,7 +41,7 @@ func CreateMapRenderer(mapEngine *d2mapengine.MapEngine, term d2interface.Termin result.debugVisLevel = level }) - if mapEngine.LevelType().Id != 0 { + if mapEngine.LevelType().ID != 0 { result.generateTileCache() } diff --git a/d2core/d2map/d2maprenderer/tile_cache.go b/d2core/d2map/d2maprenderer/tile_cache.go index 396d4fd3..5407c5e5 100644 --- a/d2core/d2map/d2maprenderer/tile_cache.go +++ b/d2core/d2map/d2maprenderer/tile_cache.go @@ -14,7 +14,7 @@ import ( ) 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() 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 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 >> 17 diff --git a/main.go b/main.go index 1d6b38cf..5cf6aa02 100644 --- a/main.go +++ b/main.go @@ -431,6 +431,7 @@ func loadDataDict() error { {d2resource.SoundSettings, d2datadict.LoadSounds}, {d2resource.AnimationData, d2data.LoadAnimationData}, {d2resource.MonStats, d2datadict.LoadMonStats}, + {d2resource.MonStats2, d2datadict.LoadMonStats2}, {d2resource.MonPreset, d2datadict.LoadMonPresets}, {d2resource.MagicPrefix, d2datadict.LoadMagicPrefix}, {d2resource.MagicSuffix, d2datadict.LoadMagicSuffix},