1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-04 15:00:42 +00:00

d2datadict reading changes (#553)

* Read excel files with csv.Reader

* Read LvlWarp from txt file

* Fix lint issues in d2datadict

* changed ID back to Id
This commit is contained in:
Intyre 2020-07-07 14:56:31 +02:00 committed by GitHub
parent af0fb11a54
commit 04c7ff543a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1281 additions and 1285 deletions

View File

@ -6,6 +6,7 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
// Armors stores all of the ArmorRecords
//nolint:gochecknoglobals // Currently global by design, only written once
var Armors map[string]*ItemCommonRecord

View File

@ -2,18 +2,10 @@ package d2datadict
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
const (
expansion = "Expansion" // blizzard put this in the txt where expansion data starts
)
//nolint:gochecknoglobals // Currently global by design, only written once
var frameFields = []string{"Cel1", "Cel2", "Cel3", "Cel4"}
// AutoMapRecord represents one row from d2data.mpq/AutoMap.txt.
// Based on the information here https://d2mods.info/forum/kb/viewarticle?a=419
type AutoMapRecord struct {
@ -67,66 +59,40 @@ var AutoMaps []*AutoMapRecord
// LoadAutoMaps populates AutoMaps with the data from AutoMap.txt.
// It also amends a duplicate field (column) name in that data.
func LoadAutoMaps(file []byte) {
// Fix the error in the original file
fileString := fixDuplicateFieldName(string(file))
AutoMaps = make([]*AutoMapRecord, 0)
var frameFields = []string{"Cel1", "Cel2", "Cel3", "Cel4"}
// Split file by newlines and tabs
d := d2common.LoadDataDictionary(fileString)
d := d2common.LoadDataDictionary(file)
for d.Next() {
record := &AutoMapRecord{
LevelName: d.String("LevelName"),
TileName: d.String("TileName"),
// Construct records
AutoMaps = make([]*AutoMapRecord, len(d.Data))
Style: d.Number("Style"),
StartSequence: d.Number("StartSequence"),
EndSequence: d.Number("EndSequence"),
for idx := range d.Data {
if d.GetString("LevelName", idx) == expansion {
continue
}
AutoMaps[idx] = &AutoMapRecord{
LevelName: d.GetString("LevelName", idx),
TileName: d.GetString("TileName", idx),
Style: d.GetNumber("Style", idx),
StartSequence: d.GetNumber("StartSequence", idx),
EndSequence: d.GetNumber("EndSequence", idx),
//Type1: d.GetString("Type1", idx),
//Type2: d.GetString("Type2", idx),
//Type3: d.GetString("Type3", idx),
//Type4: d.GetString("Type4", idx),
//Type1: d.String("Type1"),
//Type2: d.String("Type2"),
//Type3: d.String("Type3"),
//Type4: d.String("Type4"),
// Note: I commented these out for now because they supposedly
// aren't useful see the AutoMapRecord struct.
}
record.Frames = make([]int, len(frameFields))
AutoMaps[idx].Frames = make([]int, len(frameFields))
for i := range frameFields {
AutoMaps[idx].Frames[i] = d.GetNumber(frameFields[i], idx)
record.Frames[i] = d.Number(frameFields[i])
}
AutoMaps = append(AutoMaps, record)
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d AutoMapRecord records", len(AutoMaps))
}
// fixDuplicateFieldName changes one of the two 'Type2' fields
// in AutoMap.txt to 'Type3'. An error in the file can be seen
// by looking at the lists of 'Type' and 'Cel' fields:
//
// Type1 Type2 Type2* Type4
// Cel1 Cel2 Cel3 Cel4
//
// LoadDataDictionary uses a set of field names. The duplicate
// is omitted resulting in all rows being skipped because their
// counts are different from the field names count.
func fixDuplicateFieldName(fileString string) string {
// Split rows
rows := strings.Split(fileString, "\r\n")
// Split the field names row and correct the duplicate
fieldNames := strings.Split(rows[0], "\t")
fieldNames[9] = "Type3"
// Join the field names back up and assign to the first row
rows[0] = strings.Join(fieldNames, "\t")
// Return the rows, joined back into one string
return strings.Join(rows, "\r\n")
}

View File

@ -67,6 +67,8 @@ var weaponTokenMap map[string]d2enum.WeaponClass //nolint:gochecknoglobals // Cu
//nolint:funlen // Makes no sense to split
// LoadCharStats loads charstats.txt file contents into map[d2enum.Hero]*CharStatsRecord
func LoadCharStats(file []byte) {
CharStats = make(map[d2enum.Hero]*CharStatsRecord)
charStringMap = map[string]d2enum.Hero{
"Amazon": d2enum.HeroAmazon,
"Barbarian": d2enum.HeroBarbarian,
@ -95,103 +97,105 @@ func LoadCharStats(file []byte) {
"ht2": d2enum.WeaponClassTwoHandToHand,
}
d := d2common.LoadDataDictionary(string(file))
CharStats = make(map[d2enum.Hero]*CharStatsRecord, len(d.Data))
for idx := range d.Data {
d := d2common.LoadDataDictionary(file)
for d.Next() {
record := &CharStatsRecord{
Class: charStringMap[d.GetString("class", idx)],
Class: charStringMap[d.String("class")],
InitStr: d.GetNumber("str", idx),
InitDex: d.GetNumber("dex", idx),
InitVit: d.GetNumber("vit", idx),
InitEne: d.GetNumber("int", idx),
InitStamina: d.GetNumber("stamina", idx),
InitStr: d.Number("str"),
InitDex: d.Number("dex"),
InitVit: d.Number("vit"),
InitEne: d.Number("int"),
InitStamina: d.Number("stamina"),
ManaRegen: d.GetNumber("ManaRegen", idx),
ToHitFactor: d.GetNumber("ToHitFactor", idx),
ManaRegen: d.Number("ManaRegen"),
ToHitFactor: d.Number("ToHitFactor"),
VelocityWalk: d.GetNumber("WalkVelocity", idx),
VelocityRun: d.GetNumber("RunVelocity", idx),
StaminaRunDrain: d.GetNumber("RunDrain", idx),
VelocityWalk: d.Number("WalkVelocity"),
VelocityRun: d.Number("RunVelocity"),
StaminaRunDrain: d.Number("RunDrain"),
LifePerLevel: d.GetNumber("LifePerLevel", idx),
ManaPerLevel: d.GetNumber("ManaPerLevel", idx),
StaminaPerLevel: d.GetNumber("StaminaPerLevel", idx),
LifePerLevel: d.Number("LifePerLevel"),
ManaPerLevel: d.Number("ManaPerLevel"),
StaminaPerLevel: d.Number("StaminaPerLevel"),
LifePerVit: d.GetNumber("LifePerVitality", idx),
ManaPerEne: d.GetNumber("ManaPerMagic", idx),
StaminaPerVit: d.GetNumber("StaminaPerVitality", idx),
LifePerVit: d.Number("LifePerVitality"),
ManaPerEne: d.Number("ManaPerMagic"),
StaminaPerVit: d.Number("StaminaPerVitality"),
StatPerLevel: d.GetNumber("StatPerLevel", idx),
BlockFactor: d.GetNumber("BlockFactor", idx),
StatPerLevel: d.Number("StatPerLevel"),
BlockFactor: d.Number("BlockFactor"),
StartSkillBonus: d.GetString("StartSkill", idx),
SkillStrAll: d.GetString("StrAllSkills", idx),
SkillStrClassOnly: d.GetString("StrClassOnly", idx),
StartSkillBonus: d.String("StartSkill"),
SkillStrAll: d.String("StrAllSkills"),
SkillStrClassOnly: d.String("StrClassOnly"),
BaseSkill: [10]string{
d.GetString("Skill 1", idx),
d.GetString("Skill 2", idx),
d.GetString("Skill 3", idx),
d.GetString("Skill 4", idx),
d.GetString("Skill 5", idx),
d.GetString("Skill 6", idx),
d.GetString("Skill 7", idx),
d.GetString("Skill 8", idx),
d.GetString("Skill 9", idx),
d.GetString("Skill 10", idx),
d.String("Skill 1"),
d.String("Skill 2"),
d.String("Skill 3"),
d.String("Skill 4"),
d.String("Skill 5"),
d.String("Skill 6"),
d.String("Skill 7"),
d.String("Skill 8"),
d.String("Skill 9"),
d.String("Skill 10"),
},
SkillStrTab: [3]string{
d.GetString("StrSkillTab1", idx),
d.GetString("StrSkillTab2", idx),
d.GetString("StrSkillTab3", idx),
d.String("StrSkillTab1"),
d.String("StrSkillTab2"),
d.String("StrSkillTab3"),
},
BaseWeaponClass: weaponTokenMap[d.GetString("baseWClass", idx)],
BaseWeaponClass: weaponTokenMap[d.String("baseWClass")],
StartItem: [10]string{
d.GetString("item1", idx),
d.GetString("item2", idx),
d.GetString("item3", idx),
d.GetString("item4", idx),
d.GetString("item5", idx),
d.GetString("item6", idx),
d.GetString("item7", idx),
d.GetString("item8", idx),
d.GetString("item9", idx),
d.GetString("item10", idx),
d.String("item1"),
d.String("item2"),
d.String("item3"),
d.String("item4"),
d.String("item5"),
d.String("item6"),
d.String("item7"),
d.String("item8"),
d.String("item9"),
d.String("item10"),
},
StartItemLocation: [10]string{
d.GetString("item1loc", idx),
d.GetString("item2loc", idx),
d.GetString("item3loc", idx),
d.GetString("item4loc", idx),
d.GetString("item5loc", idx),
d.GetString("item6loc", idx),
d.GetString("item7loc", idx),
d.GetString("item8loc", idx),
d.GetString("item9loc", idx),
d.GetString("item10loc", idx),
d.String("item1loc"),
d.String("item2loc"),
d.String("item3loc"),
d.String("item4loc"),
d.String("item5loc"),
d.String("item6loc"),
d.String("item7loc"),
d.String("item8loc"),
d.String("item9loc"),
d.String("item10loc"),
},
StartItemCount: [10]int{
d.GetNumber("item1count", idx),
d.GetNumber("item2count", idx),
d.GetNumber("item3count", idx),
d.GetNumber("item4count", idx),
d.GetNumber("item5count", idx),
d.GetNumber("item6count", idx),
d.GetNumber("item7count", idx),
d.GetNumber("item8count", idx),
d.GetNumber("item9count", idx),
d.GetNumber("item10count", idx),
d.Number("item1count"),
d.Number("item2count"),
d.Number("item3count"),
d.Number("item4count"),
d.Number("item5count"),
d.Number("item6count"),
d.Number("item7count"),
d.Number("item8count"),
d.Number("item9count"),
d.Number("item10count"),
},
}
CharStats[record.Class] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d CharStats records", len(CharStats))
}

View File

@ -146,8 +146,7 @@ var CubeRecipes []*CubeRecipeRecord
// LoadCubeRecipes populates CubeRecipes with
// the data from CubeMain.txt.
func LoadCubeRecipes(file []byte) {
// Load data
d := d2common.LoadDataDictionary(string(file))
CubeRecipes = make([]*CubeRecipeRecord, 0)
// There are repeated fields and sections in this file, some
// of which have inconsistent naming conventions. These slices
@ -160,60 +159,65 @@ func LoadCubeRecipes(file []byte) {
var inputFields = []string{"input 1", "input 2", "input 3", "input 4", "input 5", "input 6", "input 7"}
// Create records
CubeRecipes = make([]*CubeRecipeRecord, len(d.Data))
for idx := range d.Data {
CubeRecipes[idx] = &CubeRecipeRecord{
Description: d.GetString("description", idx),
d := d2common.LoadDataDictionary(file)
for d.Next() {
record := &CubeRecipeRecord{
Description: d.String("description"),
Enabled: d.GetNumber("enabled", idx) == 1,
Ladder: d.GetNumber("ladder", idx) == 1,
Enabled: d.Bool("enabled"),
Ladder: d.Bool("ladder"),
MinDiff: d.GetNumber("min diff", idx),
Version: d.GetNumber("version", idx),
MinDiff: d.Number("min diff"),
Version: d.Number("version"),
ReqStatID: d.GetNumber("param", idx),
ReqOperation: d.GetNumber("op", idx),
ReqValue: d.GetNumber("value", idx),
ReqStatID: d.Number("param"),
ReqOperation: d.Number("op"),
ReqValue: d.Number("value"),
Class: classFieldToEnum(d.GetString("class", idx)),
Class: classFieldToEnum(d.String("class")),
NumInputs: d.GetNumber("numinputs", idx),
NumInputs: d.Number("numinputs"),
}
// Create inputs - input 1-7
CubeRecipes[idx].Inputs = make([]CubeRecipeItem, 7)
record.Inputs = make([]CubeRecipeItem, len(inputFields))
for i := range inputFields {
CubeRecipes[idx].Inputs[i] = newCubeRecipeItem(
d.GetString(inputFields[i], idx))
record.Inputs[i] = newCubeRecipeItem(
d.String(inputFields[i]))
}
// Create outputs - output "", b, c
CubeRecipes[idx].Outputs = make([]CubeRecipeResult, 3)
record.Outputs = make([]CubeRecipeResult, len(outputLabels))
for o, outLabel := range outputLabels {
CubeRecipes[idx].Outputs[o] = CubeRecipeResult{
record.Outputs[o] = CubeRecipeResult{
Item: newCubeRecipeItem(
d.GetString(outputFields[o], idx)),
d.String(outputFields[o])),
Level: d.GetNumber(outLabel+"lvl", idx),
ILevel: d.GetNumber(outLabel+"plvl", idx),
PLevel: d.GetNumber(outLabel+"ilvl", idx),
Level: d.Number(outLabel + "lvl"),
ILevel: d.Number(outLabel + "plvl"),
PLevel: d.Number(outLabel + "ilvl"),
}
// Create properties - mod 1-5
properties := make([]CubeRecipeItemProperty, 5)
properties := make([]CubeRecipeItemProperty, len(propLabels))
for p, prop := range propLabels {
properties[p] = CubeRecipeItemProperty{
Code: d.GetString(outLabel+prop, idx),
Chance: d.GetNumber(outLabel+prop+" chance", idx),
Param: d.GetNumber(outLabel+prop+" param", idx),
Min: d.GetNumber(outLabel+prop+" min", idx),
Max: d.GetNumber(outLabel+prop+" max", idx),
Code: d.String(outLabel + prop),
Chance: d.Number(outLabel + prop + " chance"),
Param: d.Number(outLabel + prop + " param"),
Min: d.Number(outLabel + prop + " min"),
Max: d.Number(outLabel + prop + " max"),
}
}
CubeRecipes[idx].Outputs[o].Properties = properties
record.Outputs[o].Properties = properties
}
CubeRecipes = append(CubeRecipes, record)
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d CubeMainRecord records", len(CubeRecipes))

View File

@ -94,33 +94,35 @@ type DifficultyLevelRecord struct {
// LoadDifficultyLevels is a loader for difficultylevels.txt
func LoadDifficultyLevels(file []byte) {
dict := d2common.LoadDataDictionary(string(file))
numRows := len(dict.Data)
DifficultyLevels = make(map[string]*DifficultyLevelRecord)
DifficultyLevels = make(map[string]*DifficultyLevelRecord, numRows)
for idx := range dict.Data {
d := d2common.LoadDataDictionary(file)
for d.Next() {
record := &DifficultyLevelRecord{
Name: dict.GetString("Name", idx),
ResistancePenalty: dict.GetNumber("ResistPenalty", idx),
DeathExperiencePenalty: dict.GetNumber("DeathExpPenalty", idx),
DropChanceLow: dict.GetNumber("UberCodeOddsNormal", idx),
DropChanceNormal: dict.GetNumber("UberCodeOddsNormal", idx),
DropChanceSuperior: dict.GetNumber("UberCodeOddsNormal", idx),
DropChanceExceptional: dict.GetNumber("UberCodeOddsNormal", idx),
DropChanceMagic: dict.GetNumber("UberCodeOddsGood", idx),
DropChanceRare: dict.GetNumber("UberCodeOddsGood", idx),
DropChanceSet: dict.GetNumber("UberCodeOddsGood", idx),
DropChanceUnique: dict.GetNumber("UberCodeOddsGood", idx),
MonsterSkillBonus: dict.GetNumber("MonsterSkillBonus", idx),
MonsterColdDivisor: dict.GetNumber("MonsterColdDivisor", idx),
MonsterFreezeDivisor: dict.GetNumber("MonsterFreezeDivisor", idx),
AiCurseDivisor: dict.GetNumber("AiCurseDivisor", idx),
LifeStealDivisor: dict.GetNumber("LifeStealDivisor", idx),
ManaStealDivisor: dict.GetNumber("ManaStealDivisor", idx),
Name: d.String("Name"),
ResistancePenalty: d.Number("ResistPenalty"),
DeathExperiencePenalty: d.Number("DeathExpPenalty"),
DropChanceLow: d.Number("UberCodeOddsNormal"),
DropChanceNormal: d.Number("UberCodeOddsNormal"),
DropChanceSuperior: d.Number("UberCodeOddsNormal"),
DropChanceExceptional: d.Number("UberCodeOddsNormal"),
DropChanceMagic: d.Number("UberCodeOddsGood"),
DropChanceRare: d.Number("UberCodeOddsGood"),
DropChanceSet: d.Number("UberCodeOddsGood"),
DropChanceUnique: d.Number("UberCodeOddsGood"),
MonsterSkillBonus: d.Number("MonsterSkillBonus"),
MonsterColdDivisor: d.Number("MonsterColdDivisor"),
MonsterFreezeDivisor: d.Number("MonsterFreezeDivisor"),
AiCurseDivisor: d.Number("AiCurseDivisor"),
LifeStealDivisor: d.Number("LifeStealDivisor"),
ManaStealDivisor: d.Number("ManaStealDivisor"),
}
DifficultyLevels[record.Name] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d DifficultyLevel records", len(DifficultyLevels))
}

View File

@ -42,7 +42,7 @@ type ExperienceBreakpointsRecord struct {
// ExperienceBreakpoints describes the required experience
// for each level for each character class
//nolint:gochecknoglobals // Currently global by design, only written once
var ExperienceBreakpoints []*ExperienceBreakpointsRecord
var ExperienceBreakpoints map[int]*ExperienceBreakpointsRecord
//nolint:gochecknoglobals // Currently global by design
var maxLevels map[d2enum.Hero]int
@ -60,43 +60,42 @@ func GetExperienceBreakpoint(heroType d2enum.Hero, level int) int {
// LoadExperienceBreakpoints loads experience.txt into a map
// ExperienceBreakpoints []*ExperienceBreakpointsRecord
func LoadExperienceBreakpoints(file []byte) {
d := d2common.LoadDataDictionary(string(file))
ExperienceBreakpoints = make(map[int]*ExperienceBreakpointsRecord)
// we skip the second row because that describes max level of char classes
ExperienceBreakpoints = make([]*ExperienceBreakpointsRecord, len(d.Data)-1)
d := d2common.LoadDataDictionary(file)
d.Next()
for idx := range d.Data {
if idx == 0 {
// max levels are a special case
maxLevels = map[d2enum.Hero]int{
d2enum.HeroAmazon: d.GetNumber("Amazon", idx),
d2enum.HeroBarbarian: d.GetNumber("Barbarian", idx),
d2enum.HeroDruid: d.GetNumber("Druid", idx),
d2enum.HeroAssassin: d.GetNumber("Assassin", idx),
d2enum.HeroNecromancer: d.GetNumber("Necromancer", idx),
d2enum.HeroPaladin: d.GetNumber("Paladin", idx),
d2enum.HeroSorceress: d.GetNumber("Sorceress", idx),
}
continue
}
// the first row describes the max level of char classes
maxLevels = map[d2enum.Hero]int{
d2enum.HeroAmazon: d.Number("Amazon"),
d2enum.HeroBarbarian: d.Number("Barbarian"),
d2enum.HeroDruid: d.Number("Druid"),
d2enum.HeroAssassin: d.Number("Assassin"),
d2enum.HeroNecromancer: d.Number("Necromancer"),
d2enum.HeroPaladin: d.Number("Paladin"),
d2enum.HeroSorceress: d.Number("Sorceress"),
}
for d.Next() {
record := &ExperienceBreakpointsRecord{
Level: d.GetNumber("Level", idx),
Level: d.Number("Level"),
HeroBreakpoints: map[d2enum.Hero]int{
d2enum.HeroAmazon: d.GetNumber("Amazon", idx),
d2enum.HeroBarbarian: d.GetNumber("Barbarian", idx),
d2enum.HeroDruid: d.GetNumber("Druid", idx),
d2enum.HeroAssassin: d.GetNumber("Assassin", idx),
d2enum.HeroNecromancer: d.GetNumber("Necromancer", idx),
d2enum.HeroPaladin: d.GetNumber("Paladin", idx),
d2enum.HeroSorceress: d.GetNumber("Sorceress", idx),
d2enum.HeroAmazon: d.Number("Amazon"),
d2enum.HeroBarbarian: d.Number("Barbarian"),
d2enum.HeroDruid: d.Number("Druid"),
d2enum.HeroAssassin: d.Number("Assassin"),
d2enum.HeroNecromancer: d.Number("Necromancer"),
d2enum.HeroPaladin: d.Number("Paladin"),
d2enum.HeroSorceress: d.Number("Sorceress"),
},
Ratio: d.GetNumber("ExpRatio", idx),
Ratio: d.Number("ExpRatio"),
}
ExperienceBreakpoints[record.Level] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d ExperienceBreakpoint records", len(ExperienceBreakpoints))
}

View File

@ -57,61 +57,58 @@ var Gems map[string]*GemsRecord //nolint:gochecknoglobals // Currently global by
// LoadGems loads gem records into a map[string]*GemsRecord
func LoadGems(file []byte) {
d := d2common.LoadDataDictionary(string(file))
Gems = make(map[string]*GemsRecord)
Gems = make(map[string]*GemsRecord, len(d.Data))
for idx := range d.Data {
if d.GetString("name", idx) != expansion {
/*
"Expansion" is the only field in line 36 of /data/global/excel/gems.txt and is only used to visually
separate base-game gems and expansion runes.
*/
gem := &GemsRecord{
Name: d.GetString("name", idx),
Letter: d.GetString("letter", idx),
Transform: d.GetNumber("transform", idx),
Code: d.GetString("code", idx),
Nummods: d.GetNumber("nummods", idx),
WeaponMod1Code: d.GetString("weaponMod1Code", idx),
WeaponMod1Param: d.GetNumber("weaponMod1Param", idx),
WeaponMod1Min: d.GetNumber("weaponMod1Min", idx),
WeaponMod1Max: d.GetNumber("weaponMod1Max", idx),
WeaponMod2Code: d.GetString("weaponMod2Code", idx),
WeaponMod2Param: d.GetNumber("weaponMod2Param", idx),
WeaponMod2Min: d.GetNumber("weaponMod2Min", idx),
WeaponMod2Max: d.GetNumber("weaponMod2Max", idx),
WeaponMod3Code: d.GetString("weaponMod3Code", idx),
WeaponMod3Param: d.GetNumber("weaponMod3Param", idx),
WeaponMod3Min: d.GetNumber("weaponMod3Min", idx),
WeaponMod3Max: d.GetNumber("weaponMod3Max", idx),
HelmMod1Code: d.GetString("helmMod1Code", idx),
HelmMod1Param: d.GetNumber("helmMod1Param", idx),
HelmMod1Min: d.GetNumber("helmMod1Min", idx),
HelmMod1Max: d.GetNumber("helmMod1Max", idx),
HelmMod2Code: d.GetString("helmMod2Code", idx),
HelmMod2Param: d.GetNumber("helmMod2Param", idx),
HelmMod2Min: d.GetNumber("helmMod2Min", idx),
HelmMod2Max: d.GetNumber("helmMod2Max", idx),
HelmMod3Code: d.GetString("helmMod3Code", idx),
HelmMod3Param: d.GetNumber("helmMod3Param", idx),
HelmMod3Min: d.GetNumber("helmMod3Min", idx),
HelmMod3Max: d.GetNumber("helmMod3Max", idx),
ShieldMod1Code: d.GetString("shieldMod1Code", idx),
ShieldMod1Param: d.GetNumber("shieldMod1Param", idx),
ShieldMod1Min: d.GetNumber("shieldMod1Min", idx),
ShieldMod1Max: d.GetNumber("shieldMod1Max", idx),
ShieldMod2Code: d.GetString("shieldMod2Code", idx),
ShieldMod2Param: d.GetNumber("shieldMod2Param", idx),
ShieldMod2Min: d.GetNumber("shieldMod2Min", idx),
ShieldMod2Max: d.GetNumber("shieldMod2Max", idx),
ShieldMod3Code: d.GetString("shieldMod3Code", idx),
ShieldMod3Param: d.GetNumber("shieldMod3Param", idx),
ShieldMod3Min: d.GetNumber("shieldMod3Min", idx),
ShieldMod3Max: d.GetNumber("shieldMod3Max", idx),
}
Gems[gem.Name] = gem
d := d2common.LoadDataDictionary(file)
for d.Next() {
gem := &GemsRecord{
Name: d.String("name"),
Letter: d.String("letter"),
Transform: d.Number("transform"),
Code: d.String("code"),
Nummods: d.Number("nummods"),
WeaponMod1Code: d.String("weaponMod1Code"),
WeaponMod1Param: d.Number("weaponMod1Param"),
WeaponMod1Min: d.Number("weaponMod1Min"),
WeaponMod1Max: d.Number("weaponMod1Max"),
WeaponMod2Code: d.String("weaponMod2Code"),
WeaponMod2Param: d.Number("weaponMod2Param"),
WeaponMod2Min: d.Number("weaponMod2Min"),
WeaponMod2Max: d.Number("weaponMod2Max"),
WeaponMod3Code: d.String("weaponMod3Code"),
WeaponMod3Param: d.Number("weaponMod3Param"),
WeaponMod3Min: d.Number("weaponMod3Min"),
WeaponMod3Max: d.Number("weaponMod3Max"),
HelmMod1Code: d.String("helmMod1Code"),
HelmMod1Param: d.Number("helmMod1Param"),
HelmMod1Min: d.Number("helmMod1Min"),
HelmMod1Max: d.Number("helmMod1Max"),
HelmMod2Code: d.String("helmMod2Code"),
HelmMod2Param: d.Number("helmMod2Param"),
HelmMod2Min: d.Number("helmMod2Min"),
HelmMod2Max: d.Number("helmMod2Max"),
HelmMod3Code: d.String("helmMod3Code"),
HelmMod3Param: d.Number("helmMod3Param"),
HelmMod3Min: d.Number("helmMod3Min"),
HelmMod3Max: d.Number("helmMod3Max"),
ShieldMod1Code: d.String("shieldMod1Code"),
ShieldMod1Param: d.Number("shieldMod1Param"),
ShieldMod1Min: d.Number("shieldMod1Min"),
ShieldMod1Max: d.Number("shieldMod1Max"),
ShieldMod2Code: d.String("shieldMod2Code"),
ShieldMod2Param: d.Number("shieldMod2Param"),
ShieldMod2Min: d.Number("shieldMod2Min"),
ShieldMod2Max: d.Number("shieldMod2Max"),
ShieldMod3Code: d.String("shieldMod3Code"),
ShieldMod3Param: d.Number("shieldMod3Param"),
ShieldMod3Min: d.Number("shieldMod3Min"),
ShieldMod3Max: d.Number("shieldMod3Max"),
}
Gems[gem.Name] = gem
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d Gems records", len(Gems))

View File

@ -89,87 +89,90 @@ var Hirelings []*HirelingRecord
// LoadHireling loads hireling data into []*HirelingRecord
func LoadHireling(file []byte) {
d := d2common.LoadDataDictionary(string(file))
Hirelings = make([]*HirelingRecord, 0)
Hirelings = make([]*HirelingRecord, len(d.Data))
for idx := range d.Data {
d := d2common.LoadDataDictionary(file)
for d.Next() {
hireling := &HirelingRecord{
Hireling: d.GetString("Hireling", idx),
SubType: d.GetString("SubType", idx),
ID: d.GetNumber("Id", idx),
Class: d.GetNumber("Class", idx),
Act: d.GetNumber("Act", idx),
Difficulty: d.GetNumber("Difficulty", idx),
Level: d.GetNumber("Level", idx),
Seller: d.GetNumber("Seller", idx),
NameFirst: d.GetString("NameFirst", idx),
NameLast: d.GetString("NameLast", idx),
Gold: d.GetNumber("Gold", idx),
ExpPerLvl: d.GetNumber("Exp/Lvl", idx),
HP: d.GetNumber("HP", idx),
HPPerLvl: d.GetNumber("HP/Lvl", idx),
Defense: d.GetNumber("Defense", idx),
DefPerLvl: d.GetNumber("Id", idx),
Str: d.GetNumber("Str", idx),
StrPerLvl: d.GetNumber("Str/Lvl", idx),
Dex: d.GetNumber("Dex", idx),
DexPerLvl: d.GetNumber("Dex/Lvl", idx),
AR: d.GetNumber("AR", idx),
ARPerLvl: d.GetNumber("AR/Lvl", idx),
Share: d.GetNumber("Share", idx),
DmgMin: d.GetNumber("Dmg-Min", idx),
DmgMax: d.GetNumber("Dmg-Max", idx),
DmgPerLvl: d.GetNumber("Dmg/Lvl", idx),
Resist: d.GetNumber("Resist", idx),
ResistPerLvl: d.GetNumber("Resist/Lvl", idx),
WType1: d.GetString("WType1", idx),
WType2: d.GetString("WType2", idx),
HireDesc: d.GetString("HireDesc", idx),
DefaultChance: d.GetNumber("DefaultChance", idx),
Skill1: d.GetString("Skill1", idx),
Mode1: d.GetNumber("Mode1", idx),
Chance1: d.GetNumber("Chance1", idx),
ChancePerLevel1: d.GetNumber("ChancePerLvl1", idx),
Level1: d.GetNumber("Level1", idx),
LvlPerLvl1: d.GetNumber("LvlPerLvl1", idx),
Skill2: d.GetString("Skill2", idx),
Mode2: d.GetNumber("Mode2", idx),
Chance2: d.GetNumber("Chance2", idx),
ChancePerLevel2: d.GetNumber("ChancePerLvl2", idx),
Level2: d.GetNumber("Level2", idx),
LvlPerLvl2: d.GetNumber("LvlPerLvl2", idx),
Skill3: d.GetString("Skill3", idx),
Mode3: d.GetNumber("Mode3", idx),
Chance3: d.GetNumber("Chance3", idx),
ChancePerLevel3: d.GetNumber("ChancePerLvl3", idx),
Level3: d.GetNumber("Level3", idx),
LvlPerLvl3: d.GetNumber("LvlPerLvl3", idx),
Skill4: d.GetString("Skill4", idx),
Mode4: d.GetNumber("Mode4", idx),
Chance4: d.GetNumber("Chance4", idx),
ChancePerLevel4: d.GetNumber("ChancePerLvl4", idx),
Level4: d.GetNumber("Level4", idx),
LvlPerLvl4: d.GetNumber("LvlPerLvl4", idx),
Skill5: d.GetString("Skill5", idx),
Mode5: d.GetNumber("Mode5", idx),
Chance5: d.GetNumber("Chance5", idx),
ChancePerLevel5: d.GetNumber("ChancePerLvl5", idx),
Level5: d.GetNumber("Level5", idx),
LvlPerLvl5: d.GetNumber("LvlPerLvl5", idx),
Skill6: d.GetString("Skill6", idx),
Mode6: d.GetNumber("Mode6", idx),
Chance6: d.GetNumber("Chance6", idx),
ChancePerLevel6: d.GetNumber("ChancePerLvl6", idx),
Level6: d.GetNumber("Level6", idx),
LvlPerLvl6: d.GetNumber("LvlPerLvl6", idx),
Head: d.GetNumber("Head", idx),
Torso: d.GetNumber("Torso", idx),
Weapon: d.GetNumber("Weapon", idx),
Shield: d.GetNumber("Shield", idx),
Hireling: d.String("Hireling"),
SubType: d.String("SubType"),
ID: d.Number("Id"),
Class: d.Number("Class"),
Act: d.Number("Act"),
Difficulty: d.Number("Difficulty"),
Level: d.Number("Level"),
Seller: d.Number("Seller"),
NameFirst: d.String("NameFirst"),
NameLast: d.String("NameLast"),
Gold: d.Number("Gold"),
ExpPerLvl: d.Number("Exp/Lvl"),
HP: d.Number("HP"),
HPPerLvl: d.Number("HP/Lvl"),
Defense: d.Number("Defense"),
DefPerLvl: d.Number("Id"),
Str: d.Number("Str"),
StrPerLvl: d.Number("Str/Lvl"),
Dex: d.Number("Dex"),
DexPerLvl: d.Number("Dex/Lvl"),
AR: d.Number("AR"),
ARPerLvl: d.Number("AR/Lvl"),
Share: d.Number("Share"),
DmgMin: d.Number("Dmg-Min"),
DmgMax: d.Number("Dmg-Max"),
DmgPerLvl: d.Number("Dmg/Lvl"),
Resist: d.Number("Resist"),
ResistPerLvl: d.Number("Resist/Lvl"),
WType1: d.String("WType1"),
WType2: d.String("WType2"),
HireDesc: d.String("HireDesc"),
DefaultChance: d.Number("DefaultChance"),
Skill1: d.String("Skill1"),
Mode1: d.Number("Mode1"),
Chance1: d.Number("Chance1"),
ChancePerLevel1: d.Number("ChancePerLvl1"),
Level1: d.Number("Level1"),
LvlPerLvl1: d.Number("LvlPerLvl1"),
Skill2: d.String("Skill2"),
Mode2: d.Number("Mode2"),
Chance2: d.Number("Chance2"),
ChancePerLevel2: d.Number("ChancePerLvl2"),
Level2: d.Number("Level2"),
LvlPerLvl2: d.Number("LvlPerLvl2"),
Skill3: d.String("Skill3"),
Mode3: d.Number("Mode3"),
Chance3: d.Number("Chance3"),
ChancePerLevel3: d.Number("ChancePerLvl3"),
Level3: d.Number("Level3"),
LvlPerLvl3: d.Number("LvlPerLvl3"),
Skill4: d.String("Skill4"),
Mode4: d.Number("Mode4"),
Chance4: d.Number("Chance4"),
ChancePerLevel4: d.Number("ChancePerLvl4"),
Level4: d.Number("Level4"),
LvlPerLvl4: d.Number("LvlPerLvl4"),
Skill5: d.String("Skill5"),
Mode5: d.Number("Mode5"),
Chance5: d.Number("Chance5"),
ChancePerLevel5: d.Number("ChancePerLvl5"),
Level5: d.Number("Level5"),
LvlPerLvl5: d.Number("LvlPerLvl5"),
Skill6: d.String("Skill6"),
Mode6: d.Number("Mode6"),
Chance6: d.Number("Chance6"),
ChancePerLevel6: d.Number("ChancePerLvl6"),
Level6: d.Number("Level6"),
LvlPerLvl6: d.Number("LvlPerLvl6"),
Head: d.Number("Head"),
Torso: d.Number("Torso"),
Weapon: d.Number("Weapon"),
Shield: d.Number("Shield"),
}
Hirelings = append(Hirelings, hireling)
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d Hireling records", len(Hirelings))
}

View File

@ -8,8 +8,9 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
// MagicPrefix + MagicSuffix store item affix records
// MagicPrefix stores all of the magic prefix records
var MagicPrefix []*ItemAffixCommonRecord //nolint:gochecknoglobals // Currently global by design
// MagicSuffix stores all of the magic suffix records
var MagicSuffix []*ItemAffixCommonRecord //nolint:gochecknoglobals // Currently global by design
// LoadMagicPrefix loads MagicPrefix.txt
@ -47,46 +48,38 @@ func getAffixString(t1 d2enum.ItemAffixSuperType, t2 d2enum.ItemAffixSubType) st
return name
}
func loadDictionary(
file []byte,
superType d2enum.ItemAffixSuperType,
subType d2enum.ItemAffixSubType,
) []*ItemAffixCommonRecord {
dict := d2common.LoadDataDictionary(string(file))
records := createItemAffixRecords(dict, superType, subType)
func loadDictionary(file []byte, superType d2enum.ItemAffixSuperType, subType d2enum.ItemAffixSubType) []*ItemAffixCommonRecord {
d := d2common.LoadDataDictionary(file)
records := createItemAffixRecords(d, superType, subType)
name := getAffixString(superType, subType)
log.Printf("Loaded %d %s records", len(dict.Data), name)
log.Printf("Loaded %d %s records", len(records), name)
return records
}
func createItemAffixRecords(
d *d2common.DataDictionary,
superType d2enum.ItemAffixSuperType,
subType d2enum.ItemAffixSubType,
) []*ItemAffixCommonRecord {
func createItemAffixRecords(d *d2common.DataDictionary, superType d2enum.ItemAffixSuperType, subType d2enum.ItemAffixSubType) []*ItemAffixCommonRecord {
records := make([]*ItemAffixCommonRecord, 0)
for index := range d.Data {
for d.Next() {
affix := &ItemAffixCommonRecord{
Name: d.GetString("Name", index),
Version: d.GetNumber("version", index),
Name: d.String("Name"),
Version: d.Number("version"),
Type: subType,
IsPrefix: superType == d2enum.ItemAffixPrefix,
IsSuffix: superType == d2enum.ItemAffixSuffix,
Spawnable: d.GetNumber("spawnable", index) == 1,
Rare: d.GetNumber("rare", index) == 1,
Level: d.GetNumber("level", index),
MaxLevel: d.GetNumber("maxlevel", index),
LevelReq: d.GetNumber("levelreq", index),
Class: d.GetString("classspecific", index),
ClassLevelReq: d.GetNumber("classlevelreq", index),
Frequency: d.GetNumber("frequency", index),
GroupID: d.GetNumber("group", index),
Transform: d.GetNumber("transform", index) == 1,
TransformColor: d.GetString("transformcolor", index),
PriceAdd: d.GetNumber("add", index),
PriceScale: d.GetNumber("multiply", index),
Spawnable: d.Bool("spawnable"),
Rare: d.Bool("rare"),
Level: d.Number("level"),
MaxLevel: d.Number("maxlevel"),
LevelReq: d.Number("levelreq"),
Class: d.String("classspecific"),
ClassLevelReq: d.Number("classlevelreq"),
Frequency: d.Number("frequency"),
GroupID: d.Number("group"),
Transform: d.Bool("transform"),
TransformColor: d.String("transformcolor"),
PriceAdd: d.Number("add"),
PriceScale: d.Number("multiply"),
}
// modifiers (Property references with parameters to be eval'd)
@ -96,10 +89,10 @@ func createItemAffixRecords(
minKey := fmt.Sprintf("mod%dmin", i)
maxKey := fmt.Sprintf("mod%dmax", i)
modifier := &ItemAffixCommonModifier{
Code: d.GetString(codeKey, index),
Parameter: d.GetNumber(paramKey, index),
Min: d.GetNumber(minKey, index),
Max: d.GetNumber(maxKey, index),
Code: d.String(codeKey),
Parameter: d.Number(paramKey),
Min: d.Number(minKey),
Max: d.Number(maxKey),
}
affix.Modifiers = append(affix.Modifiers, modifier)
}
@ -107,14 +100,14 @@ func createItemAffixRecords(
// items to include for spawning
for i := 1; i <= 7; i++ {
itemKey := fmt.Sprintf("itype%d", i)
itemToken := d.GetString(itemKey, index)
itemToken := d.String(itemKey)
affix.ItemInclude = append(affix.ItemInclude, itemToken)
}
// items to exclude for spawning
for i := 1; i <= 7; i++ {
itemKey := fmt.Sprintf("etype%d", i)
itemToken := d.GetString(itemKey, index)
itemToken := d.String(itemKey)
affix.ItemExclude = append(affix.ItemExclude, itemToken)
}
@ -134,6 +127,9 @@ func createItemAffixRecords(
records = append(records, affix)
}
if d.Err != nil {
panic(d.Err)
}
return records
}

View File

@ -109,81 +109,83 @@ var ItemStatCosts map[string]*ItemStatCostRecord
// LoadItemStatCosts loads ItemStatCostRecord's from text
func LoadItemStatCosts(file []byte) {
d := d2common.LoadDataDictionary(string(file))
numRecords := len(d.Data)
ItemStatCosts = make(map[string]*ItemStatCostRecord, numRecords)
ItemStatCosts = make(map[string]*ItemStatCostRecord)
for idx := range d.Data {
d := d2common.LoadDataDictionary(file)
for d.Next() {
record := &ItemStatCostRecord{
Name: d.GetString("Stat", idx),
Index: d.GetNumber("ID", idx),
Name: d.String("Stat"),
Index: d.Number("ID"),
Signed: d.GetNumber("Signed", idx) > 0,
KeepZero: d.GetNumber("keepzero", idx) > 0,
Signed: d.Number("Signed") > 0,
KeepZero: d.Number("keepzero") > 0,
// Ranged: d.GetNumber("Ranged", idx) > 0,
MinAccr: d.GetNumber("MinAccr", idx),
// Ranged: d.Number("Ranged") > 0,
MinAccr: d.Number("MinAccr"),
UpdateAnimRate: d.GetNumber("UpdateAnimRate", idx) > 0,
UpdateAnimRate: d.Number("UpdateAnimRate") > 0,
SendOther: d.GetNumber("Send Other", idx) > 0,
SendBits: d.GetNumber("Send Bits", idx),
SendParam: d.GetNumber("Send Param Bits", idx),
SendOther: d.Number("Send Other") > 0,
SendBits: d.Number("Send Bits"),
SendParam: d.Number("Send Param Bits"),
Saved: d.GetNumber("CSvBits", idx) > 0,
SavedSigned: d.GetNumber("CSvSigned", idx) > 0,
SavedBits: d.GetNumber("CSvBits", idx),
SaveBits: d.GetNumber("Save Bits", idx),
SaveAdd: d.GetNumber("Save Add", idx),
SaveParamBits: d.GetNumber("Save Param Bits", idx),
Saved: d.Number("CSvBits") > 0,
SavedSigned: d.Number("CSvSigned") > 0,
SavedBits: d.Number("CSvBits"),
SaveBits: d.Number("Save Bits"),
SaveAdd: d.Number("Save Add"),
SaveParamBits: d.Number("Save Param Bits"),
Encode: d2enum.EncodingType(d.GetNumber("Encode", idx)),
Encode: d2enum.EncodingType(d.Number("Encode")),
CallbackEnabled: d.GetNumber("fCallback", idx) > 0,
CallbackEnabled: d.Number("fCallback") > 0,
CostAdd: d.GetNumber("Add", idx),
CostMultiply: d.GetNumber("Multiply", idx),
ValShift: d.GetNumber("ValShift", idx),
CostAdd: d.Number("Add"),
CostMultiply: d.Number("Multiply"),
ValShift: d.Number("ValShift"),
OperatorType: d2enum.OperatorType(d.GetNumber("op", idx)),
OpParam: d.GetNumber("op param", idx),
OpBase: d.GetString("op base", idx),
OpStat1: d.GetString("op stat1", idx),
OpStat2: d.GetString("op stat2", idx),
OpStat3: d.GetString("op stat3", idx),
OperatorType: d2enum.OperatorType(d.Number("op")),
OpParam: d.Number("op param"),
OpBase: d.String("op base"),
OpStat1: d.String("op stat1"),
OpStat2: d.String("op stat2"),
OpStat3: d.String("op stat3"),
Direct: d.GetNumber("direct", idx) > 0,
MaxStat: d.GetString("maxstat", idx),
Direct: d.Number("direct") > 0,
MaxStat: d.String("maxstat"),
ItemSpecific: d.GetNumber("itemspecific", idx) > 0,
DamageRelated: d.GetNumber("damagerelated", idx) > 0,
ItemSpecific: d.Number("itemspecific") > 0,
DamageRelated: d.Number("damagerelated") > 0,
EventID1: d2enum.GetItemEventType(d.GetString("itemevent1", idx)),
EventID2: d2enum.GetItemEventType(d.GetString("itemevent2", idx)),
EventFuncID1: d2enum.GetItemEventFuncID(d.GetNumber("itemeventfunc1", idx)),
EventFuncID2: d2enum.GetItemEventFuncID(d.GetNumber("itemeventfunc2", idx)),
EventID1: d2enum.GetItemEventType(d.String("itemevent1")),
EventID2: d2enum.GetItemEventType(d.String("itemevent2")),
EventFuncID1: d2enum.GetItemEventFuncID(d.Number("itemeventfunc1")),
EventFuncID2: d2enum.GetItemEventFuncID(d.Number("itemeventfunc2")),
DescPriority: d.GetNumber("descpriority", idx),
DescFnID: d2enum.DescFuncID(d.GetNumber("descfunc", idx)),
DescFn: d2enum.GetDescFunction(d2enum.DescFuncID(d.GetNumber("descfunc", idx))),
DescVal: d.GetNumber("descval", idx),
DescStrPos: d.GetString("descstrpos", idx),
DescStrNeg: d.GetString("descstrneg", idx),
DescStr2: d.GetString("descstr2", idx),
DescPriority: d.Number("descpriority"),
DescFnID: d2enum.DescFuncID(d.Number("descfunc")),
DescFn: d2enum.GetDescFunction(d2enum.DescFuncID(d.Number("descfunc"))),
DescVal: d.Number("descval"),
DescStrPos: d.String("descstrpos"),
DescStrNeg: d.String("descstrneg"),
DescStr2: d.String("descstr2"),
DescGroup: d.GetNumber("dgrp", idx),
DescGroupFuncID: d2enum.DescFuncID(d.GetNumber("dgrpfunc", idx)),
DescGroupFn: d2enum.GetDescFunction(d2enum.DescFuncID(d.GetNumber("dgrpfunc", idx))),
DescGroupVal: d.GetNumber("dgrpval", idx),
DescGroupStrPos: d.GetString("dgrpstrpos", idx),
DescGroupStrNeg: d.GetString("dgrpstrneg", idx),
DescGroupStr2: d.GetString("dgrpstr2", idx),
DescGroup: d.Number("dgrp"),
DescGroupFuncID: d2enum.DescFuncID(d.Number("dgrpfunc")),
DescGroupFn: d2enum.GetDescFunction(d2enum.DescFuncID(d.Number("dgrpfunc"))),
DescGroupVal: d.Number("dgrpval"),
DescGroupStrPos: d.String("dgrpstrpos"),
DescGroupStrNeg: d.String("dgrpstrneg"),
DescGroupStr2: d.String("dgrpstr2"),
Stuff: d.GetString("stuff", idx),
Stuff: d.String("stuff"),
}
ItemStatCosts[record.Name] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d ItemStatCost records", len(ItemStatCosts))
}

View File

@ -41,22 +41,25 @@ var LevelMazeDetails map[int]*LevelMazeDetailsRecord //nolint:gochecknoglobals /
// 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)
LevelMazeDetails = make(map[int]*LevelMazeDetailsRecord)
for idx := range dict.Data {
d := d2common.LoadDataDictionary(file)
for d.Next() {
record := &LevelMazeDetailsRecord{
Name: dict.GetString("Name", 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),
Name: d.String("Name"),
LevelID: d.Number("Level"),
NumRoomsNormal: d.Number("Rooms"),
NumRoomsNightmare: d.Number("Rooms(N)"),
NumRoomsHell: d.Number("Rooms(H)"),
SizeX: d.Number("SizeX"),
SizeY: d.Number("SizeY"),
}
LevelMazeDetails[record.LevelID] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d LevelMazeDetails records", len(LevelMazeDetails))
}

View File

@ -70,38 +70,40 @@ var LevelSubstitutions map[int]*LevelSubstitutionRecord
// LoadLevelSubstitutions loads lvlsub.txt and parses into records
func LoadLevelSubstitutions(file []byte) {
dict := d2common.LoadDataDictionary(string(file))
numRecords := len(dict.Data)
LevelSubstitutions = make(map[int]*LevelSubstitutionRecord, numRecords)
LevelSubstitutions = make(map[int]*LevelSubstitutionRecord)
for idx := range dict.Data {
d := d2common.LoadDataDictionary(file)
for d.Next() {
record := &LevelSubstitutionRecord{
Name: dict.GetString("Name", idx),
ID: dict.GetNumber("Type", idx),
File: dict.GetString("File", idx),
IsExpansion: dict.GetNumber("Expansion", idx) > 0,
BorderType: dict.GetNumber("BordType", idx),
GridSize: dict.GetNumber("GridSize", idx),
Mask: dict.GetNumber("Dt1Mask", idx),
ChanceSpawn0: dict.GetNumber("Prob0", idx),
ChanceSpawn1: dict.GetNumber("Prob1", idx),
ChanceSpawn2: dict.GetNumber("Prob2", idx),
ChanceSpawn3: dict.GetNumber("Prob3", idx),
ChanceSpawn4: dict.GetNumber("Prob4", idx),
ChanceFloor0: dict.GetNumber("Trials0", idx),
ChanceFloor1: dict.GetNumber("Trials1", idx),
ChanceFloor2: dict.GetNumber("Trials2", idx),
ChanceFloor3: dict.GetNumber("Trials3", idx),
ChanceFloor4: dict.GetNumber("Trials4", idx),
GridMax0: dict.GetNumber("Max0", idx),
GridMax1: dict.GetNumber("Max1", idx),
GridMax2: dict.GetNumber("Max2", idx),
GridMax3: dict.GetNumber("Max3", idx),
GridMax4: dict.GetNumber("Max4", idx),
Name: d.String("Name"),
ID: d.Number("Type"),
File: d.String("File"),
IsExpansion: d.Number("Expansion") > 0,
BorderType: d.Number("BordType"),
GridSize: d.Number("GridSize"),
Mask: d.Number("Dt1Mask"),
ChanceSpawn0: d.Number("Prob0"),
ChanceSpawn1: d.Number("Prob1"),
ChanceSpawn2: d.Number("Prob2"),
ChanceSpawn3: d.Number("Prob3"),
ChanceSpawn4: d.Number("Prob4"),
ChanceFloor0: d.Number("Trials0"),
ChanceFloor1: d.Number("Trials1"),
ChanceFloor2: d.Number("Trials2"),
ChanceFloor3: d.Number("Trials3"),
ChanceFloor4: d.Number("Trials4"),
GridMax0: d.Number("Max0"),
GridMax1: d.Number("Max1"),
GridMax2: d.Number("Max2"),
GridMax3: d.Number("Max3"),
GridMax4: d.Number("Max4"),
}
LevelSubstitutions[record.ID] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d LevelSubstitution records", len(LevelSubstitutions))
}

View File

@ -9,17 +9,18 @@ import (
// LevelWarpRecord is a representation of a row from lvlwarp.txt
// it describes the warp graphics offsets and dimensions for levels
type LevelWarpRecord struct {
ID int32
SelectX int32
SelectY int32
SelectDX int32
SelectDY int32
ExitWalkX int32
ExitWalkY int32
OffsetX int32
OffsetY int32
Name string
ID int
SelectX int
SelectY int
SelectDX int
SelectDY int
ExitWalkX int
ExitWalkY int
OffsetX int
OffsetY int
LitVersion bool
Tiles int32
Tiles int
Direction string
}
@ -28,27 +29,27 @@ type LevelWarpRecord struct {
var LevelWarps map[int]*LevelWarpRecord
// LoadLevelWarps loads LevelWarpRecord's from text file data
func LoadLevelWarps(levelWarpData []byte) {
func LoadLevelWarps(file []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].SelectX = streamReader.GetInt32()
LevelWarps[id].SelectY = streamReader.GetInt32()
LevelWarps[id].SelectDX = streamReader.GetInt32()
LevelWarps[id].SelectDY = streamReader.GetInt32()
LevelWarps[id].ExitWalkX = streamReader.GetInt32()
LevelWarps[id].ExitWalkY = streamReader.GetInt32()
LevelWarps[id].OffsetX = streamReader.GetInt32()
LevelWarps[id].OffsetY = streamReader.GetInt32()
LevelWarps[id].LitVersion = streamReader.GetInt32() == 1
LevelWarps[id].Tiles = streamReader.GetInt32()
LevelWarps[id].Direction = string(streamReader.GetByte())
streamReader.SkipBytes(3)
d := d2common.LoadDataDictionary(file)
for d.Next() {
record := &LevelWarpRecord{
Name: d.String("Name"),
ID: d.Number("Id"),
SelectX: d.Number("SelectX"),
SelectY: d.Number("SelectY"),
SelectDX: d.Number("SelectDX"),
SelectDY: d.Number("SelectDY"),
ExitWalkX: d.Number("ExitWalkX"),
ExitWalkY: d.Number("ExitWalkY"),
OffsetX: d.Number("OffsetX"),
OffsetY: d.Number("OffsetY"),
LitVersion: d.Bool("LitVersion"),
Tiles: d.Number("Tiles"),
Direction: d.String("Direction"),
}
LevelWarps[record.ID] = record
}
log.Printf("Loaded %d level warps", len(LevelWarps))

View File

@ -95,7 +95,7 @@ type LevelDetailsRecord struct {
// Id
// Level ID (used in columns like VIS0-7)
Id int //nolint:golint Id is the right key
Id int //nolint:golint,stylecheck // Id is the right key
// Palette is the Act Palette . Reference only
Palette int // Pal
@ -381,159 +381,162 @@ func GetLevelDetails(id int) *LevelDetailsRecord {
// LoadLevelDetails loads level details records from levels.txt
//nolint:funlen // Txt loader, makes no sense to split
func LoadLevelDetails(file []byte) {
dict := d2common.LoadDataDictionary(string(file))
numRecords := len(dict.Data)
LevelDetails = make(map[int]*LevelDetailsRecord, numRecords)
LevelDetails = make(map[int]*LevelDetailsRecord)
for idx := range dict.Data {
d := d2common.LoadDataDictionary(file)
for d.Next() {
record := &LevelDetailsRecord{
Name: dict.GetString("Name ", idx),
Id: dict.GetNumber("Id", idx),
Palette: dict.GetNumber("Pal", idx),
Act: dict.GetNumber("Act", idx),
QuestFlag: dict.GetNumber("QuestFlag", idx),
QuestFlagExpansion: dict.GetNumber("QuestFlagEx", idx),
AutomapIndex: dict.GetNumber("Layer", idx),
SizeXNormal: dict.GetNumber("SizeX", idx),
SizeYNormal: dict.GetNumber("SizeY", idx),
SizeXNightmare: dict.GetNumber("SizeX(N)", idx),
SizeYNightmare: dict.GetNumber("SizeY(N)", idx),
SizeXHell: dict.GetNumber("SizeX(H)", idx),
SizeYHell: dict.GetNumber("SizeY(H)", idx),
WorldOffsetX: dict.GetNumber("OffsetX", idx),
WorldOffsetY: dict.GetNumber("OffsetY", idx),
DependantLevelID: dict.GetNumber("Depend", idx),
TeleportFlag: d2enum.TeleportFlag(dict.GetNumber("Teleport", idx)),
EnableRain: dict.GetNumber("Rain", idx) > 0,
EnableMud: dict.GetNumber("Mud", idx) > 0,
EnablePerspective: dict.GetNumber("NoPer", idx) > 0,
EnableLineOfSightDraw: dict.GetNumber("LOSDraw", idx) > 0,
EnableFloorFliter: dict.GetNumber("FloorFilter", idx) > 0,
EnableBlankScreen: dict.GetNumber("BlankScreen", idx) > 0,
EnableDrawEdges: dict.GetNumber("DrawEdges", idx) > 0,
IsInside: dict.GetNumber("IsInside", idx) > 0,
LevelGenerationType: d2enum.LevelGenerationType(dict.GetNumber("DrlgType", idx)),
LevelType: dict.GetNumber("LevelType", idx),
SubType: dict.GetNumber("SubType", idx),
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),
LightIntensity: dict.GetNumber("Intensity", idx),
Red: dict.GetNumber("Red", idx),
Green: dict.GetNumber("Green", idx),
Blue: dict.GetNumber("Blue", idx),
PortalEnable: dict.GetNumber("Portal", idx) > 0,
PortalRepositionEnable: dict.GetNumber("Position", idx) > 0,
SaveMonsterStates: dict.GetNumber("SaveMonsters", idx) > 0,
SaveMerchantStates: dict.GetNumber("SaveMonsters", idx) > 0,
QuestID: dict.GetNumber("Quest", idx),
WarpClearanceDistance: dict.GetNumber("WarpDist", idx),
MonsterLevelNormal: dict.GetNumber("MonLvl1", idx),
MonsterLevelNightmare: dict.GetNumber("MonLvl2", idx),
MonsterLevelHell: dict.GetNumber("MonLvl3", idx),
MonsterLevelNormalEx: dict.GetNumber("MonLvl1Ex", idx),
MonsterLevelNightmareEx: dict.GetNumber("MonLvl2Ex", idx),
MonsterLevelHellEx: dict.GetNumber("MonLvl3Ex", idx),
MonsterDensityNormal: dict.GetNumber("MonDen", idx),
MonsterDensityNightmare: dict.GetNumber("MonDen(N)", idx),
MonsterDensityHell: dict.GetNumber("MonDen(H)", idx),
MonsterUniqueMinNormal: dict.GetNumber("MonUMin", idx),
MonsterUniqueMinNightmare: dict.GetNumber("MonUMin(N)", idx),
MonsterUniqueMinHell: dict.GetNumber("MonUMin(H)", idx),
MonsterUniqueMaxNormal: dict.GetNumber("MonUMax", idx),
MonsterUniqueMaxNightmare: dict.GetNumber("MonUMax(N)", idx),
MonsterUniqueMaxHell: dict.GetNumber("MonUMax(H)", idx),
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),
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),
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),
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),
ObjectGroupSpawnChance0: dict.GetNumber("ObjPrb0", idx),
ObjectGroupSpawnChance1: dict.GetNumber("ObjPrb1", idx),
ObjectGroupSpawnChance2: dict.GetNumber("ObjPrb2", idx),
ObjectGroupSpawnChance3: dict.GetNumber("ObjPrb3", idx),
ObjectGroupSpawnChance4: dict.GetNumber("ObjPrb4", idx),
ObjectGroupSpawnChance5: dict.GetNumber("ObjPrb5", idx),
ObjectGroupSpawnChance6: dict.GetNumber("ObjPrb6", idx),
ObjectGroupSpawnChance7: dict.GetNumber("ObjPrb7", idx),
Name: d.String("Name "),
Id: d.Number("Id"),
Palette: d.Number("Pal"),
Act: d.Number("Act"),
QuestFlag: d.Number("QuestFlag"),
QuestFlagExpansion: d.Number("QuestFlagEx"),
AutomapIndex: d.Number("Layer"),
SizeXNormal: d.Number("SizeX"),
SizeYNormal: d.Number("SizeY"),
SizeXNightmare: d.Number("SizeX(N)"),
SizeYNightmare: d.Number("SizeY(N)"),
SizeXHell: d.Number("SizeX(H)"),
SizeYHell: d.Number("SizeY(H)"),
WorldOffsetX: d.Number("OffsetX"),
WorldOffsetY: d.Number("OffsetY"),
DependantLevelID: d.Number("Depend"),
TeleportFlag: d2enum.TeleportFlag(d.Number("Teleport")),
EnableRain: d.Number("Rain") > 0,
EnableMud: d.Number("Mud") > 0,
EnablePerspective: d.Number("NoPer") > 0,
EnableLineOfSightDraw: d.Number("LOSDraw") > 0,
EnableFloorFliter: d.Number("FloorFilter") > 0,
EnableBlankScreen: d.Number("BlankScreen") > 0,
EnableDrawEdges: d.Number("DrawEdges") > 0,
IsInside: d.Number("IsInside") > 0,
LevelGenerationType: d2enum.LevelGenerationType(d.Number("DrlgType")),
LevelType: d.Number("LevelType"),
SubType: d.Number("SubType"),
SubTheme: d.Number("SubTheme"),
SubWaypoint: d.Number("SubWaypoint"),
SubShrine: d.Number("SubShrine"),
LevelLinkID0: d.Number("Vis0"),
LevelLinkID1: d.Number("Vis1"),
LevelLinkID2: d.Number("Vis2"),
LevelLinkID3: d.Number("Vis3"),
LevelLinkID4: d.Number("Vis4"),
LevelLinkID5: d.Number("Vis5"),
LevelLinkID6: d.Number("Vis6"),
LevelLinkID7: d.Number("Vis7"),
WarpGraphicsID0: d.Number("Warp0"),
WarpGraphicsID1: d.Number("Warp1"),
WarpGraphicsID2: d.Number("Warp2"),
WarpGraphicsID3: d.Number("Warp3"),
WarpGraphicsID4: d.Number("Warp4"),
WarpGraphicsID5: d.Number("Warp5"),
WarpGraphicsID6: d.Number("Warp6"),
WarpGraphicsID7: d.Number("Warp7"),
LightIntensity: d.Number("Intensity"),
Red: d.Number("Red"),
Green: d.Number("Green"),
Blue: d.Number("Blue"),
PortalEnable: d.Number("Portal") > 0,
PortalRepositionEnable: d.Number("Position") > 0,
SaveMonsterStates: d.Number("SaveMonsters") > 0,
SaveMerchantStates: d.Number("SaveMonsters") > 0,
QuestID: d.Number("Quest"),
WarpClearanceDistance: d.Number("WarpDist"),
MonsterLevelNormal: d.Number("MonLvl1"),
MonsterLevelNightmare: d.Number("MonLvl2"),
MonsterLevelHell: d.Number("MonLvl3"),
MonsterLevelNormalEx: d.Number("MonLvl1Ex"),
MonsterLevelNightmareEx: d.Number("MonLvl2Ex"),
MonsterLevelHellEx: d.Number("MonLvl3Ex"),
MonsterDensityNormal: d.Number("MonDen"),
MonsterDensityNightmare: d.Number("MonDen(N)"),
MonsterDensityHell: d.Number("MonDen(H)"),
MonsterUniqueMinNormal: d.Number("MonUMin"),
MonsterUniqueMinNightmare: d.Number("MonUMin(N)"),
MonsterUniqueMinHell: d.Number("MonUMin(H)"),
MonsterUniqueMaxNormal: d.Number("MonUMax"),
MonsterUniqueMaxNightmare: d.Number("MonUMax(N)"),
MonsterUniqueMaxHell: d.Number("MonUMax(H)"),
MonsterWanderEnable: d.Number("MonWndr") > 0,
MonsterSpecialWalk: d.Number("MonSpcWalk") > 0,
NumMonsterTypes: d.Number("NumMon"),
MonsterID1Normal: d.String("mon1"),
MonsterID2Normal: d.String("mon2"),
MonsterID3Normal: d.String("mon3"),
MonsterID4Normal: d.String("mon4"),
MonsterID5Normal: d.String("mon5"),
MonsterID6Normal: d.String("mon6"),
MonsterID7Normal: d.String("mon7"),
MonsterID8Normal: d.String("mon8"),
MonsterID9Normal: d.String("mon9"),
MonsterID10Normal: d.String("mon10"),
MonsterID1Nightmare: d.String("nmon1"),
MonsterID2Nightmare: d.String("nmon2"),
MonsterID3Nightmare: d.String("nmon3"),
MonsterID4Nightmare: d.String("nmon4"),
MonsterID5Nightmare: d.String("nmon5"),
MonsterID6Nightmare: d.String("nmon6"),
MonsterID7Nightmare: d.String("nmon7"),
MonsterID8Nightmare: d.String("nmon8"),
MonsterID9Nightmare: d.String("nmon9"),
MonsterID10Nightmare: d.String("nmon10"),
MonsterID1Hell: d.String("nmon1"),
MonsterID2Hell: d.String("nmon2"),
MonsterID3Hell: d.String("nmon3"),
MonsterID4Hell: d.String("nmon4"),
MonsterID5Hell: d.String("nmon5"),
MonsterID6Hell: d.String("nmon6"),
MonsterID7Hell: d.String("nmon7"),
MonsterID8Hell: d.String("nmon8"),
MonsterID9Hell: d.String("nmon9"),
MonsterID10Hell: d.String("nmon10"),
MonsterPreferRanged: d.Number("rangedspawn") > 0,
MonsterUniqueID1: d.String("umon1"),
MonsterUniqueID2: d.String("umon2"),
MonsterUniqueID3: d.String("umon3"),
MonsterUniqueID4: d.String("umon4"),
MonsterUniqueID5: d.String("umon5"),
MonsterUniqueID6: d.String("umon6"),
MonsterUniqueID7: d.String("umon7"),
MonsterUniqueID8: d.String("umon8"),
MonsterUniqueID9: d.String("umon9"),
MonsterUniqueID10: d.String("umon10"),
MonsterCritterID1: d.String("cmon1"),
MonsterCritterID2: d.String("cmon2"),
MonsterCritterID3: d.String("cmon3"),
MonsterCritterID4: d.String("cmon4"),
MonsterCritter1SpawnChance: d.Number("cpct1"),
MonsterCritter2SpawnChance: d.Number("cpct2"),
MonsterCritter3SpawnChance: d.Number("cpct3"),
MonsterCritter4SpawnChance: d.Number("cpct4"),
SoundEnvironmentID: d.Number("SoundEnv"),
WaypointID: d.Number("Waypoint"),
LevelDisplayName: d.String("LevelName"),
LevelWarpName: d.String("LevelWarp"),
TitleImageName: d.String("EntryFile"),
ObjectGroupID0: d.Number("ObjGrp0"),
ObjectGroupID1: d.Number("ObjGrp1"),
ObjectGroupID2: d.Number("ObjGrp2"),
ObjectGroupID3: d.Number("ObjGrp3"),
ObjectGroupID4: d.Number("ObjGrp4"),
ObjectGroupID5: d.Number("ObjGrp5"),
ObjectGroupID6: d.Number("ObjGrp6"),
ObjectGroupID7: d.Number("ObjGrp7"),
ObjectGroupSpawnChance0: d.Number("ObjPrb0"),
ObjectGroupSpawnChance1: d.Number("ObjPrb1"),
ObjectGroupSpawnChance2: d.Number("ObjPrb2"),
ObjectGroupSpawnChance3: d.Number("ObjPrb3"),
ObjectGroupSpawnChance4: d.Number("ObjPrb4"),
ObjectGroupSpawnChance5: d.Number("ObjPrb5"),
ObjectGroupSpawnChance6: d.Number("ObjPrb6"),
ObjectGroupSpawnChance7: d.Number("ObjPrb7"),
}
LevelDetails[idx] = record
LevelDetails[record.Id] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d LevelDetails records", len(LevelDetails))

View File

@ -110,7 +110,7 @@ type MissileRecord struct {
ExplosionMissile string // name of a missile from missiles.txt that is created upon collision
// or anytime it is destroyed if AlwaysExplode is true
Id int //nolint:golint Id is the correct key
Id int //nolint:golint,stylecheck // Id is the correct key
ClientMovementFunc int
ClientCollisionFunc int

View File

@ -8,31 +8,24 @@ import (
// MonPresets stores monster presets
//nolint:gochecknoglobals // Currently global by design, only written once
var MonPresets [][]string
var MonPresets map[int32][]string
// LoadMonPresets loads monster presets from monpresets.txt
func LoadMonPresets(file []byte) {
dict := d2common.LoadDataDictionary(string(file))
numRecords := len(dict.Data)
MonPresets = make([][]string, numRecords)
MonPresets = make(map[int32][]string)
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
d := d2common.LoadDataDictionary(file)
for d.Next() {
act := int32(d.Number("Act"))
if _, ok := MonPresets[act]; !ok {
MonPresets[act] = make([]string, 0)
}
MonPresets[act][placeIdx] = dict.GetString("Place", dictIdx)
lastAct = act
MonPresets[act] = append(MonPresets[act], d.String("Place"))
}
placeIdx++
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d MonPreset records", len(MonPresets))

View File

@ -28,7 +28,7 @@ type (
// column also links other hardcoded effects to the units, such as the
// transparency on necro summons and the name-color change on unique boss
// units (thanks to Kingpin for the info)
Id string // called `hcIdx` in monstats.txt //nolint:golint Id is the right key
Id string //nolint:golint,stylecheck // called `hcIdx` in monstats.txt
// BaseKey is an ID pointer of the “base” unit for this specific
// monster type (ex. There are five types of “Fallen”; all of them have
@ -81,12 +81,10 @@ type (
SpawnAnimationKey string // called `spawnmode` in monstats.txt
// MinionId1 is an Id of a minion that spawns when this monster is created
//nolint:golint Id is the right key
MinionId1 string // called `minion1` in monstats.txt
MinionId1 string //nolint:golint,stylecheck // called `minion1` in monstats.txt
// MinionId2 is an Id of a minion that spawns when this monster is created
//nolint:golint Id is the right key
MinionId2 string // called `minion2` in monstats.txt
MinionId2 string //nolint:golint,stylecheck // called `minion2` in monstats.txt
// SoundKeyNormal, SoundKeySpecial
// specifies the ID pointer to this monsters “Sound Bank” in MonSound.txt
@ -115,14 +113,14 @@ type (
// the ID Pointer to the skill (from Skills.txt) the monster will cast when
// this specific slot is accessed by the AI. Which slots are used is
// determined by the units AI.
SkillId1 string // called `Skill1` in monstats.txt //nolint:golint Id is the right key
SkillId2 string // called `Skill2` in monstats.txt //nolint:golint Id is the right key
SkillId3 string // called `Skill3` in monstats.txt //nolint:golint Id is the right key
SkillId4 string // called `Skill4` in monstats.txt //nolint:golint Id is the right key
SkillId5 string // called `Skill5` in monstats.txt //nolint:golint Id is the right key
SkillId6 string // called `Skill6` in monstats.txt //nolint:golint Id is the right key
SkillId7 string // called `Skill7` in monstats.txt //nolint:golint Id is the right key
SkillId8 string // called `Skill8` in monstats.txt //nolint:golint Id is the right key
SkillId1 string //nolint:golint,stylecheck // called `Skill1` in monstats.txt
SkillId2 string //nolint:golint,stylecheck // called `Skill2` in monstats.txt
SkillId3 string //nolint:golint,stylecheck // called `Skill3` in monstats.txt
SkillId4 string //nolint:golint,stylecheck // called `Skill4` in monstats.txt
SkillId5 string //nolint:golint,stylecheck // called `Skill5` in monstats.txt
SkillId6 string //nolint:golint,stylecheck // called `Skill6` in monstats.txt
SkillId7 string //nolint:golint,stylecheck // called `Skill7` in monstats.txt
SkillId8 string //nolint:golint,stylecheck // called `Skill8` in monstats.txt
// SkillAnimation1 -- SkillAnimation8
// the graphical MODE (or SEQUENCE) this unit uses when it uses this skill.
@ -139,8 +137,7 @@ type (
// ID Pointer to the skill that controls this units damage. This is used for
// the druids summons. IE their damage is specified solely by Skills.txt and
// not by MonStats.txt.
//nolint:golint Id is the right key
DamageSkillId string // called `SkillDamage` in monstats.txt
DamageSkillId string //nolint:golint,stylecheck // called `SkillDamage` in monstats.txt
// ElementAttackMode1 -- ElementAttackMode3
// the mode to which the elemental damage is appended. The modes to which
@ -190,12 +187,12 @@ type (
// TreasureClassQuestTriggerId
// the ID of the Quest that triggers the Quest Treasureclass drop.
TreasureClassQuestTriggerId string // called `TCQuestId` in monstats.txt
TreasureClassQuestTriggerId string //nolint:golint,stylecheck // called `TCQuestId` in monstats.txt
// TreasureClassQuestCompleteId
// the ID of the Quest State that you need to complete to trigger the Quest
// Treasureclass trop.
TreasureClassQuestCompleteId string // called `TCQuestCP` in monstats.txt
TreasureClassQuestCompleteId string //nolint:golint,stylecheck // called `TCQuestCP` in monstats.txt
// PaletteId indicates which palette (color) entry the unit will use, most
// monsters have a palshift.dat file in their COF folder, this file
@ -208,7 +205,7 @@ type (
// such as FC do not accept their palettes.
// NOTE no 2: some monsters got unused palettes, ZM (zombie) for example
// will turn light-rotten-green with palette nr 5 and pink-creamy with 6.
PaletteId int // called `TransLvl` in monstats.txt
PaletteId int //nolint:golint,stylecheck // called `TransLvl` in monstats.txt
// SpawnOffsetX, SpawnOffsetY
// are the x/y offsets at which spawned monsters are placed. IE this prevents
@ -683,271 +680,275 @@ type (
}
)
var MonStats map[string]*MonStatsRecord
// MonStats stores all of the MonStat Records
var MonStats map[string]*MonStatsRecord //nolint:gochecknoglobals // Currently global by design, only written once
//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)
// LoadMonStats loads monstats
func LoadMonStats(file []byte) { // nolint:funlen // Makes no sense to split
MonStats = make(map[string]*MonStatsRecord)
for idx := range dict.Data {
d := d2common.LoadDataDictionary(file)
for d.Next() {
record := &MonStatsRecord{
Key: dict.GetString("Id", idx),
Id: dict.GetString("hcIdx", idx),
BaseKey: dict.GetString("BaseId", idx),
NextKey: dict.GetString("NextInClass", idx),
PaletteId: dict.GetNumber("TransLvl", idx),
NameStringTableKey: dict.GetString("NameStr", idx),
ExtraDataKey: dict.GetString("MonStatsEx", idx),
PropertiesKey: dict.GetString("MonProp", idx),
MonsterGroup: dict.GetString("MonType", idx),
AiKey: dict.GetString("AI", idx),
DescriptionStringTableKey: dict.GetString("DescStr", idx),
AnimationDirectoryToken: dict.GetString("Code", idx),
Enabled: dict.GetNumber("enabled", idx) > 0,
IsRanged: dict.GetNumber("rangedtype", idx) > 0,
SpawnsMinions: dict.GetNumber("placespawn", idx) > 0,
SpawnKey: dict.GetString("spawn", idx),
SpawnOffsetX: dict.GetNumber("spawnx", idx),
SpawnOffsetY: dict.GetNumber("spawny", idx),
SpawnAnimationKey: dict.GetString("spawnmode", idx),
MinionId1: dict.GetString("minion1", idx),
MinionId2: dict.GetString("minion2", idx),
IsLeader: dict.GetNumber("SetBoss", idx) > 0,
TransferLeadership: dict.GetNumber("BossXfer", idx) > 0,
MinionPartyMin: dict.GetNumber("PartyMin", idx),
MinionPartyMax: dict.GetNumber("PartyMax", idx),
MinionGroupMin: dict.GetNumber("MinGrp", idx),
MinionGroupMax: dict.GetNumber("MaxGrp", idx),
PopulationReductionPercent: dict.GetNumber("sparsePopulate", idx),
SpeedBase: dict.GetNumber("Velocity", idx),
SpeedRun: dict.GetNumber("Run", idx),
Rarity: dict.GetNumber("Rarity", idx),
LevelNormal: dict.GetNumber("Level", idx),
LevelNightmare: dict.GetNumber("Level(N)", idx),
LevelHell: dict.GetNumber("Level(H)", idx),
SoundKeyNormal: dict.GetString("MonSound", idx),
SoundKeySpecial: dict.GetString("UMonSound", idx),
ThreatLevel: dict.GetNumber("threat", idx),
AiDelayNormal: dict.GetNumber("aidel", idx),
AiDelayNightmare: dict.GetNumber("aidel(N)", idx),
AiDelayHell: dict.GetNumber("aidel(H)", idx),
AiDistanceNormal: dict.GetNumber("aidist", idx),
AiDistanceNightmare: dict.GetNumber("aidist(N)", idx),
AiDistanceHell: dict.GetNumber("aidist(H)", idx),
AiParameterNormal1: dict.GetNumber("aip1", idx),
AiParameterNormal2: dict.GetNumber("aip2", idx),
AiParameterNormal3: dict.GetNumber("aip3", idx),
AiParameterNormal4: dict.GetNumber("aip4", idx),
AiParameterNormal5: dict.GetNumber("aip5", idx),
AiParameterNormal6: dict.GetNumber("aip6", idx),
AiParameterNormal7: dict.GetNumber("aip7", idx),
AiParameterNormal8: dict.GetNumber("aip8", idx),
AiParameterNightmare1: dict.GetNumber("aip1(N)", idx),
AiParameterNightmare2: dict.GetNumber("aip2(N)", idx),
AiParameterNightmare3: dict.GetNumber("aip3(N)", idx),
AiParameterNightmare4: dict.GetNumber("aip4(N)", idx),
AiParameterNightmare5: dict.GetNumber("aip5(N)", idx),
AiParameterNightmare6: dict.GetNumber("aip6(N)", idx),
AiParameterNightmare7: dict.GetNumber("aip7(N)", idx),
AiParameterNightmare8: dict.GetNumber("aip8(N)", idx),
AiParameterHell1: dict.GetNumber("aip1(H)", idx),
AiParameterHell2: dict.GetNumber("aip2(H)", idx),
AiParameterHell3: dict.GetNumber("aip3(H)", idx),
AiParameterHell4: dict.GetNumber("aip4(H)", idx),
AiParameterHell5: dict.GetNumber("aip5(H)", idx),
AiParameterHell6: dict.GetNumber("aip6(H)", idx),
AiParameterHell7: dict.GetNumber("aip7(H)", idx),
AiParameterHell8: dict.GetNumber("aip8(H)", idx),
MissileA1: dict.GetString("MissA1", idx),
MissileA2: dict.GetString("MissA2", idx),
MissileS1: dict.GetString("MissS1", idx),
MissileS2: dict.GetString("MissS2", idx),
MissileS3: dict.GetString("MissS3", idx),
MissileS4: dict.GetString("MissS4", idx),
MissileC: dict.GetString("MissC", idx),
MissileSQ: dict.GetString("MissSQ", idx),
Alignment: d2enum.MonsterAlignmentType(dict.GetNumber("Align", idx)),
IsLevelSpawnable: dict.GetNumber("isSpawn", idx) > 0,
IsMelee: dict.GetNumber("isMelee", idx) > 0,
IsNpc: dict.GetNumber("npc", idx) > 0,
IsInteractable: dict.GetNumber("interact", idx) > 0,
HasInventory: dict.GetNumber("inventory", idx) > 0,
CanEnterTown: dict.GetNumber("inTown", idx) > 0,
IsUndeadLow: dict.GetNumber("lUndead", idx) > 0,
IsUndeadHigh: dict.GetNumber("hUndead", idx) > 0,
IsDemon: dict.GetNumber("demon", idx) > 0,
IsFlying: dict.GetNumber("flying", idx) > 0,
CanOpenDoors: dict.GetNumber("opendoors", idx) > 0,
IsSpecialBoss: dict.GetNumber("boss", idx) > 0,
IsActBoss: dict.GetNumber("primeevil", idx) > 0,
IsKillable: dict.GetNumber("killable", idx) > 0,
IsAiSwitchable: dict.GetNumber("switchai", idx) > 0,
DisableAura: dict.GetNumber("noAura", idx) > 0,
DisableMultiShot: dict.GetNumber("nomultishot", idx) > 0,
DisableCounting: dict.GetNumber("neverCount", idx) > 0,
IgnorePets: dict.GetNumber("petIgnore", idx) > 0,
DealsDamageOnDeath: dict.GetNumber("deathDmg", idx) > 0,
GenericSpawn: dict.GetNumber("genericSpawn", idx) > 0,
SkillId1: dict.GetString("Skill1", idx),
SkillId2: dict.GetString("Skill2", idx),
SkillId3: dict.GetString("Skill3", idx),
SkillId4: dict.GetString("Skill4", idx),
SkillId5: dict.GetString("Skill5", idx),
SkillId6: dict.GetString("Skill6", idx),
SkillId7: dict.GetString("Skill7", idx),
SkillId8: dict.GetString("Skill8", idx),
SkillAnimation1: dict.GetString("Sk1mode", idx),
SkillAnimation2: dict.GetString("Sk2mode", idx),
SkillAnimation3: dict.GetString("Sk3mode", idx),
SkillAnimation4: dict.GetString("Sk4mode", idx),
SkillAnimation5: dict.GetString("Sk5mode", idx),
SkillAnimation6: dict.GetString("Sk6mode", idx),
SkillAnimation7: dict.GetString("Sk7mode", idx),
SkillAnimation8: dict.GetString("Sk8mode", idx),
SkillLevel1: dict.GetNumber("Sk1lvl", idx),
SkillLevel2: dict.GetNumber("Sk2lvl", idx),
SkillLevel3: dict.GetNumber("Sk3lvl", idx),
SkillLevel4: dict.GetNumber("Sk4lvl", idx),
SkillLevel5: dict.GetNumber("Sk5lvl", idx),
SkillLevel6: dict.GetNumber("Sk6lvl", idx),
SkillLevel7: dict.GetNumber("Sk7lvl", idx),
SkillLevel8: dict.GetNumber("Sk8lvl", idx),
LeechSensitivityNormal: dict.GetNumber("Drain", idx),
LeechSensitivityNightmare: dict.GetNumber("Drain(N)", idx),
LeechSensitivityHell: dict.GetNumber("Drain(H)", idx),
ColdSensitivityNormal: dict.GetNumber("coldeffect", idx),
ColdSensitivityNightmare: dict.GetNumber("coldeffect(N)", idx),
ColdSensitivityHell: dict.GetNumber("coldeffect(H)", idx),
ResistancePhysicalNormal: dict.GetNumber("ResDm", idx),
ResistancePhysicalNightmare: dict.GetNumber("ResDm(N)", idx),
ResistancePhysicalHell: dict.GetNumber("ResDm(H)", idx),
ResistanceMagicNormal: dict.GetNumber("ResMa", idx),
ResistanceMagicNightmare: dict.GetNumber("ResMa(N)", idx),
ResistanceMagicHell: dict.GetNumber("ResMa(H)", idx),
ResistanceFireNormal: dict.GetNumber("ResFi", idx),
ResistanceFireNightmare: dict.GetNumber("ResFi(N)", idx),
ResistanceFireHell: dict.GetNumber("ResFi(H)", idx),
ResistanceLightningNormal: dict.GetNumber("ResLi", idx),
ResistanceLightningNightmare: dict.GetNumber("ResLi(N)", idx),
ResistanceLightningHell: dict.GetNumber("ResLi(H)", idx),
ResistanceColdNormal: dict.GetNumber("ResCo", idx),
ResistanceColdNightmare: dict.GetNumber("ResCo(N)", idx),
ResistanceColdHell: dict.GetNumber("ResCo(H)", idx),
ResistancePoisonNormal: dict.GetNumber("ResPo", idx),
ResistancePoisonNightmare: dict.GetNumber("ResPo(N)", idx),
ResistancePoisonHell: dict.GetNumber("ResPo(H)", idx),
HealthRegenPerFrame: dict.GetNumber("DamageRegen", idx),
DamageSkillId: dict.GetString("SkillDamage", idx),
IgnoreMonLevelTxt: dict.GetNumber("noRatio", idx) > 0,
CanBlockWithoutShield: dict.GetNumber("NoShldBlock", idx) > 0,
ChanceToBlockNormal: dict.GetNumber("ToBlock", idx),
ChanceToBlockNightmare: dict.GetNumber("ToBlock(N)", idx),
ChanceToBlockHell: dict.GetNumber("ToBlock(H)", idx),
ChanceDeadlyStrike: dict.GetNumber("Crit", idx),
MinHPNormal: dict.GetNumber("minHP", idx),
MinHPNightmare: dict.GetNumber("MinHP(N)", idx),
MinHPHell: dict.GetNumber("MinHP(H)", idx),
MaxHPNormal: dict.GetNumber("maxHP", idx),
MaxHPNightmare: dict.GetNumber("MaxHP(N)", idx),
MaxHPHell: dict.GetNumber("MaxHP(H)", idx),
ArmorClassNormal: dict.GetNumber("AC", idx),
ArmorClassNightmare: dict.GetNumber("AC(N)", idx),
ArmorClassHell: dict.GetNumber("AC(H)", idx),
ExperienceNormal: dict.GetNumber("Exp", idx),
ExperienceNightmare: dict.GetNumber("Exp(N)", idx),
ExperienceHell: dict.GetNumber("Exp(H)", idx),
DamageMinA1Normal: dict.GetNumber("A1MinD", idx),
DamageMinA1Nightmare: dict.GetNumber("A1MinD(N)", idx),
DamageMinA1Hell: dict.GetNumber("A1MinD(H)", idx),
DamageMaxA1Normal: dict.GetNumber("A1MaxD", idx),
DamageMaxA1Nightmare: dict.GetNumber("A1MaxD(N)", idx),
DamageMaxA1Hell: dict.GetNumber("A1MaxD(H)", idx),
DamageMinA2Normal: dict.GetNumber("A2MinD", idx),
DamageMinA2Nightmare: dict.GetNumber("A2MinD(N)", idx),
DamageMinA2Hell: dict.GetNumber("A2MinD(H)", idx),
DamageMaxA2Normal: dict.GetNumber("A2MaxD", idx),
DamageMaxA2Nightmare: dict.GetNumber("A2MaxD(N)", idx),
DamageMaxA2Hell: dict.GetNumber("A2MaxD(H)", idx),
DamageMinS1Normal: dict.GetNumber("S1MinD", idx),
DamageMinS1Nightmare: dict.GetNumber("S1MinD(N)", idx),
DamageMinS1Hell: dict.GetNumber("S1MinD(H)", idx),
DamageMaxS1Normal: dict.GetNumber("S1MaxD", idx),
DamageMaxS1Nightmare: dict.GetNumber("S1MaxD(N)", idx),
DamageMaxS1Hell: dict.GetNumber("S1MaxD(H)", idx),
AttackRatingA1Normal: dict.GetNumber("A1TH", idx),
AttackRatingA1Nightmare: dict.GetNumber("A1TH(N)", idx),
AttackRatingA1Hell: dict.GetNumber("A1TH(H)", idx),
AttackRatingA2Normal: dict.GetNumber("A2TH", idx),
AttackRatingA2Nightmare: dict.GetNumber("A2TH(N)", idx),
AttackRatingA2Hell: dict.GetNumber("A2TH(H)", idx),
AttackRatingS1Normal: dict.GetNumber("S1TH", idx),
AttackRatingS1Nightmare: dict.GetNumber("S1TH(N)", idx),
AttackRatingS1Hell: dict.GetNumber("S1TH(H)", idx),
ElementAttackMode1: dict.GetString("El1Mode", idx),
ElementAttackMode2: dict.GetString("El2Mode", idx),
ElementAttackMode3: dict.GetString("El3Mode", idx),
ElementType1: dict.GetString("El1Type", idx),
ElementType2: dict.GetString("El2Type", idx),
ElementType3: dict.GetString("El3Type", idx),
ElementChance1Normal: dict.GetNumber("El1Pct", idx),
ElementChance1Nightmare: dict.GetNumber("El1Pct(N)", idx),
ElementChance1Hell: dict.GetNumber("El1Pct(H)", idx),
ElementChance2Normal: dict.GetNumber("El2Pct", idx),
ElementChance2Nightmare: dict.GetNumber("El2Pct(N)", idx),
ElementChance2Hell: dict.GetNumber("El2Pct(H)", idx),
ElementChance3Normal: dict.GetNumber("El3Pct", idx),
ElementChance3Nightmare: dict.GetNumber("El3Pct(N)", idx),
ElementChance3Hell: dict.GetNumber("El3Pct(H)", idx),
ElementDamageMin1Normal: dict.GetNumber("El1MinD", idx),
ElementDamageMin1Nightmare: dict.GetNumber("El1MinD(N)", idx),
ElementDamageMin1Hell: dict.GetNumber("El1MinD(H)", idx),
ElementDamageMin2Normal: dict.GetNumber("El2MinD", idx),
ElementDamageMin2Nightmare: dict.GetNumber("El2MinD(N)", idx),
ElementDamageMin2Hell: dict.GetNumber("El2MinD(H)", idx),
ElementDamageMin3Normal: dict.GetNumber("El3MinD", idx),
ElementDamageMin3Nightmare: dict.GetNumber("El3MinD(N)", idx),
ElementDamageMin3Hell: dict.GetNumber("El3MinD(H)", idx),
ElementDamageMax1Normal: dict.GetNumber("El1MaxD", idx),
ElementDamageMax1Nightmare: dict.GetNumber("El1MaxD(N)", idx),
ElementDamageMax1Hell: dict.GetNumber("El1MaxD(H)", idx),
ElementDamageMax2Normal: dict.GetNumber("El2MaxD", idx),
ElementDamageMax2Nightmare: dict.GetNumber("El2MaxD(N)", idx),
ElementDamageMax2Hell: dict.GetNumber("El2MaxD(H)", idx),
ElementDamageMax3Normal: dict.GetNumber("El3MaxD", idx),
ElementDamageMax3Nightmare: dict.GetNumber("El3MaxD(N)", idx),
ElementDamageMax3Hell: dict.GetNumber("El3MaxD(H)", idx),
ElementDuration1Normal: dict.GetNumber("El1Dur", idx),
ElementDuration1Nightmare: dict.GetNumber("El1Dur(N)", idx),
ElementDuration1Hell: dict.GetNumber("El1Dur(H)", idx),
ElementDuration2Normal: dict.GetNumber("El2Dur", idx),
ElementDuration2Nightmare: dict.GetNumber("El2Dur(N)", idx),
ElementDuration2Hell: dict.GetNumber("El2Dur(H)", idx),
ElementDuration3Normal: dict.GetNumber("El3Dur", idx),
ElementDuration3Nightmare: dict.GetNumber("El3Dur(N)", idx),
ElementDuration3Hell: dict.GetNumber("El3Dur(H)", idx),
TreasureClassNormal: dict.GetString("TreasureClass1", idx),
TreasureClassNightmare: dict.GetString("TreasureClass1(N)", idx),
TreasureClassHell: dict.GetString("TreasureClass1(H)", idx),
TreasureClassChampionNormal: dict.GetString("TreasureClass2", idx),
TreasureClassChampionNightmare: dict.GetString("TreasureClass2(N)", idx),
TreasureClassChampionHell: dict.GetString("TreasureClass2(H)", idx),
TreasureClass3UniqueNormal: dict.GetString("TreasureClass3", idx),
TreasureClass3UniqueNightmare: dict.GetString("TreasureClass3(N)", idx),
TreasureClass3UniqueHell: dict.GetString("TreasureClass3(H)", idx),
TreasureClassQuestNormal: dict.GetString("TreasureClass4", idx),
TreasureClassQuestNightmare: dict.GetString("TreasureClass4(N)", idx),
TreasureClassQuestHell: dict.GetString("TreasureClass4(H)", idx),
TreasureClassQuestTriggerId: dict.GetString("TCQuestId", idx),
TreasureClassQuestCompleteId: dict.GetString("TCQuestCP", idx),
SpecialEndDeath: dict.GetNumber("SplEndDeath", idx),
SpecialGetModeChart: dict.GetNumber("SplGetModeChart", idx) > 0,
SpecialEndGeneric: dict.GetNumber("SplEndGeneric", idx) > 0,
SpecialClientEnd: dict.GetNumber("SplClientEnd", idx) > 0,
Key: d.String("Id"),
Id: d.String("hcIdx"),
BaseKey: d.String("BaseId"),
NextKey: d.String("NextInClass"),
PaletteId: d.Number("TransLvl"),
NameStringTableKey: d.String("NameStr"),
ExtraDataKey: d.String("MonStatsEx"),
PropertiesKey: d.String("MonProp"),
MonsterGroup: d.String("MonType"),
AiKey: d.String("AI"),
DescriptionStringTableKey: d.String("DescStr"),
AnimationDirectoryToken: d.String("Code"),
Enabled: d.Number("enabled") > 0,
IsRanged: d.Number("rangedtype") > 0,
SpawnsMinions: d.Number("placespawn") > 0,
SpawnKey: d.String("spawn"),
SpawnOffsetX: d.Number("spawnx"),
SpawnOffsetY: d.Number("spawny"),
SpawnAnimationKey: d.String("spawnmode"),
MinionId1: d.String("minion1"),
MinionId2: d.String("minion2"),
IsLeader: d.Number("SetBoss") > 0,
TransferLeadership: d.Number("BossXfer") > 0,
MinionPartyMin: d.Number("PartyMin"),
MinionPartyMax: d.Number("PartyMax"),
MinionGroupMin: d.Number("MinGrp"),
MinionGroupMax: d.Number("MaxGrp"),
PopulationReductionPercent: d.Number("sparsePopulate"),
SpeedBase: d.Number("Velocity"),
SpeedRun: d.Number("Run"),
Rarity: d.Number("Rarity"),
LevelNormal: d.Number("Level"),
LevelNightmare: d.Number("Level(N)"),
LevelHell: d.Number("Level(H)"),
SoundKeyNormal: d.String("MonSound"),
SoundKeySpecial: d.String("UMonSound"),
ThreatLevel: d.Number("threat"),
AiDelayNormal: d.Number("aidel"),
AiDelayNightmare: d.Number("aidel(N)"),
AiDelayHell: d.Number("aidel(H)"),
AiDistanceNormal: d.Number("aidist"),
AiDistanceNightmare: d.Number("aidist(N)"),
AiDistanceHell: d.Number("aidist(H)"),
AiParameterNormal1: d.Number("aip1"),
AiParameterNormal2: d.Number("aip2"),
AiParameterNormal3: d.Number("aip3"),
AiParameterNormal4: d.Number("aip4"),
AiParameterNormal5: d.Number("aip5"),
AiParameterNormal6: d.Number("aip6"),
AiParameterNormal7: d.Number("aip7"),
AiParameterNormal8: d.Number("aip8"),
AiParameterNightmare1: d.Number("aip1(N)"),
AiParameterNightmare2: d.Number("aip2(N)"),
AiParameterNightmare3: d.Number("aip3(N)"),
AiParameterNightmare4: d.Number("aip4(N)"),
AiParameterNightmare5: d.Number("aip5(N)"),
AiParameterNightmare6: d.Number("aip6(N)"),
AiParameterNightmare7: d.Number("aip7(N)"),
AiParameterNightmare8: d.Number("aip8(N)"),
AiParameterHell1: d.Number("aip1(H)"),
AiParameterHell2: d.Number("aip2(H)"),
AiParameterHell3: d.Number("aip3(H)"),
AiParameterHell4: d.Number("aip4(H)"),
AiParameterHell5: d.Number("aip5(H)"),
AiParameterHell6: d.Number("aip6(H)"),
AiParameterHell7: d.Number("aip7(H)"),
AiParameterHell8: d.Number("aip8(H)"),
MissileA1: d.String("MissA1"),
MissileA2: d.String("MissA2"),
MissileS1: d.String("MissS1"),
MissileS2: d.String("MissS2"),
MissileS3: d.String("MissS3"),
MissileS4: d.String("MissS4"),
MissileC: d.String("MissC"),
MissileSQ: d.String("MissSQ"),
Alignment: d2enum.MonsterAlignmentType(d.Number("Align")),
IsLevelSpawnable: d.Number("isSpawn") > 0,
IsMelee: d.Number("isMelee") > 0,
IsNpc: d.Number("npc") > 0,
IsInteractable: d.Number("interact") > 0,
HasInventory: d.Number("inventory") > 0,
CanEnterTown: d.Number("inTown") > 0,
IsUndeadLow: d.Number("lUndead") > 0,
IsUndeadHigh: d.Number("hUndead") > 0,
IsDemon: d.Number("demon") > 0,
IsFlying: d.Number("flying") > 0,
CanOpenDoors: d.Number("opendoors") > 0,
IsSpecialBoss: d.Number("boss") > 0,
IsActBoss: d.Number("primeevil") > 0,
IsKillable: d.Number("killable") > 0,
IsAiSwitchable: d.Number("switchai") > 0,
DisableAura: d.Number("noAura") > 0,
DisableMultiShot: d.Number("nomultishot") > 0,
DisableCounting: d.Number("neverCount") > 0,
IgnorePets: d.Number("petIgnore") > 0,
DealsDamageOnDeath: d.Number("deathDmg") > 0,
GenericSpawn: d.Number("genericSpawn") > 0,
SkillId1: d.String("Skill1"),
SkillId2: d.String("Skill2"),
SkillId3: d.String("Skill3"),
SkillId4: d.String("Skill4"),
SkillId5: d.String("Skill5"),
SkillId6: d.String("Skill6"),
SkillId7: d.String("Skill7"),
SkillId8: d.String("Skill8"),
SkillAnimation1: d.String("Sk1mode"),
SkillAnimation2: d.String("Sk2mode"),
SkillAnimation3: d.String("Sk3mode"),
SkillAnimation4: d.String("Sk4mode"),
SkillAnimation5: d.String("Sk5mode"),
SkillAnimation6: d.String("Sk6mode"),
SkillAnimation7: d.String("Sk7mode"),
SkillAnimation8: d.String("Sk8mode"),
SkillLevel1: d.Number("Sk1lvl"),
SkillLevel2: d.Number("Sk2lvl"),
SkillLevel3: d.Number("Sk3lvl"),
SkillLevel4: d.Number("Sk4lvl"),
SkillLevel5: d.Number("Sk5lvl"),
SkillLevel6: d.Number("Sk6lvl"),
SkillLevel7: d.Number("Sk7lvl"),
SkillLevel8: d.Number("Sk8lvl"),
LeechSensitivityNormal: d.Number("Drain"),
LeechSensitivityNightmare: d.Number("Drain(N)"),
LeechSensitivityHell: d.Number("Drain(H)"),
ColdSensitivityNormal: d.Number("coldeffect"),
ColdSensitivityNightmare: d.Number("coldeffect(N)"),
ColdSensitivityHell: d.Number("coldeffect(H)"),
ResistancePhysicalNormal: d.Number("ResDm"),
ResistancePhysicalNightmare: d.Number("ResDm(N)"),
ResistancePhysicalHell: d.Number("ResDm(H)"),
ResistanceMagicNormal: d.Number("ResMa"),
ResistanceMagicNightmare: d.Number("ResMa(N)"),
ResistanceMagicHell: d.Number("ResMa(H)"),
ResistanceFireNormal: d.Number("ResFi"),
ResistanceFireNightmare: d.Number("ResFi(N)"),
ResistanceFireHell: d.Number("ResFi(H)"),
ResistanceLightningNormal: d.Number("ResLi"),
ResistanceLightningNightmare: d.Number("ResLi(N)"),
ResistanceLightningHell: d.Number("ResLi(H)"),
ResistanceColdNormal: d.Number("ResCo"),
ResistanceColdNightmare: d.Number("ResCo(N)"),
ResistanceColdHell: d.Number("ResCo(H)"),
ResistancePoisonNormal: d.Number("ResPo"),
ResistancePoisonNightmare: d.Number("ResPo(N)"),
ResistancePoisonHell: d.Number("ResPo(H)"),
HealthRegenPerFrame: d.Number("DamageRegen"),
DamageSkillId: d.String("SkillDamage"),
IgnoreMonLevelTxt: d.Number("noRatio") > 0,
CanBlockWithoutShield: d.Number("NoShldBlock") > 0,
ChanceToBlockNormal: d.Number("ToBlock"),
ChanceToBlockNightmare: d.Number("ToBlock(N)"),
ChanceToBlockHell: d.Number("ToBlock(H)"),
ChanceDeadlyStrike: d.Number("Crit"),
MinHPNormal: d.Number("minHP"),
MinHPNightmare: d.Number("MinHP(N)"),
MinHPHell: d.Number("MinHP(H)"),
MaxHPNormal: d.Number("maxHP"),
MaxHPNightmare: d.Number("MaxHP(N)"),
MaxHPHell: d.Number("MaxHP(H)"),
ArmorClassNormal: d.Number("AC"),
ArmorClassNightmare: d.Number("AC(N)"),
ArmorClassHell: d.Number("AC(H)"),
ExperienceNormal: d.Number("Exp"),
ExperienceNightmare: d.Number("Exp(N)"),
ExperienceHell: d.Number("Exp(H)"),
DamageMinA1Normal: d.Number("A1MinD"),
DamageMinA1Nightmare: d.Number("A1MinD(N)"),
DamageMinA1Hell: d.Number("A1MinD(H)"),
DamageMaxA1Normal: d.Number("A1MaxD"),
DamageMaxA1Nightmare: d.Number("A1MaxD(N)"),
DamageMaxA1Hell: d.Number("A1MaxD(H)"),
DamageMinA2Normal: d.Number("A2MinD"),
DamageMinA2Nightmare: d.Number("A2MinD(N)"),
DamageMinA2Hell: d.Number("A2MinD(H)"),
DamageMaxA2Normal: d.Number("A2MaxD"),
DamageMaxA2Nightmare: d.Number("A2MaxD(N)"),
DamageMaxA2Hell: d.Number("A2MaxD(H)"),
DamageMinS1Normal: d.Number("S1MinD"),
DamageMinS1Nightmare: d.Number("S1MinD(N)"),
DamageMinS1Hell: d.Number("S1MinD(H)"),
DamageMaxS1Normal: d.Number("S1MaxD"),
DamageMaxS1Nightmare: d.Number("S1MaxD(N)"),
DamageMaxS1Hell: d.Number("S1MaxD(H)"),
AttackRatingA1Normal: d.Number("A1TH"),
AttackRatingA1Nightmare: d.Number("A1TH(N)"),
AttackRatingA1Hell: d.Number("A1TH(H)"),
AttackRatingA2Normal: d.Number("A2TH"),
AttackRatingA2Nightmare: d.Number("A2TH(N)"),
AttackRatingA2Hell: d.Number("A2TH(H)"),
AttackRatingS1Normal: d.Number("S1TH"),
AttackRatingS1Nightmare: d.Number("S1TH(N)"),
AttackRatingS1Hell: d.Number("S1TH(H)"),
ElementAttackMode1: d.String("El1Mode"),
ElementAttackMode2: d.String("El2Mode"),
ElementAttackMode3: d.String("El3Mode"),
ElementType1: d.String("El1Type"),
ElementType2: d.String("El2Type"),
ElementType3: d.String("El3Type"),
ElementChance1Normal: d.Number("El1Pct"),
ElementChance1Nightmare: d.Number("El1Pct(N)"),
ElementChance1Hell: d.Number("El1Pct(H)"),
ElementChance2Normal: d.Number("El2Pct"),
ElementChance2Nightmare: d.Number("El2Pct(N)"),
ElementChance2Hell: d.Number("El2Pct(H)"),
ElementChance3Normal: d.Number("El3Pct"),
ElementChance3Nightmare: d.Number("El3Pct(N)"),
ElementChance3Hell: d.Number("El3Pct(H)"),
ElementDamageMin1Normal: d.Number("El1MinD"),
ElementDamageMin1Nightmare: d.Number("El1MinD(N)"),
ElementDamageMin1Hell: d.Number("El1MinD(H)"),
ElementDamageMin2Normal: d.Number("El2MinD"),
ElementDamageMin2Nightmare: d.Number("El2MinD(N)"),
ElementDamageMin2Hell: d.Number("El2MinD(H)"),
ElementDamageMin3Normal: d.Number("El3MinD"),
ElementDamageMin3Nightmare: d.Number("El3MinD(N)"),
ElementDamageMin3Hell: d.Number("El3MinD(H)"),
ElementDamageMax1Normal: d.Number("El1MaxD"),
ElementDamageMax1Nightmare: d.Number("El1MaxD(N)"),
ElementDamageMax1Hell: d.Number("El1MaxD(H)"),
ElementDamageMax2Normal: d.Number("El2MaxD"),
ElementDamageMax2Nightmare: d.Number("El2MaxD(N)"),
ElementDamageMax2Hell: d.Number("El2MaxD(H)"),
ElementDamageMax3Normal: d.Number("El3MaxD"),
ElementDamageMax3Nightmare: d.Number("El3MaxD(N)"),
ElementDamageMax3Hell: d.Number("El3MaxD(H)"),
ElementDuration1Normal: d.Number("El1Dur"),
ElementDuration1Nightmare: d.Number("El1Dur(N)"),
ElementDuration1Hell: d.Number("El1Dur(H)"),
ElementDuration2Normal: d.Number("El2Dur"),
ElementDuration2Nightmare: d.Number("El2Dur(N)"),
ElementDuration2Hell: d.Number("El2Dur(H)"),
ElementDuration3Normal: d.Number("El3Dur"),
ElementDuration3Nightmare: d.Number("El3Dur(N)"),
ElementDuration3Hell: d.Number("El3Dur(H)"),
TreasureClassNormal: d.String("TreasureClass1"),
TreasureClassNightmare: d.String("TreasureClass1(N)"),
TreasureClassHell: d.String("TreasureClass1(H)"),
TreasureClassChampionNormal: d.String("TreasureClass2"),
TreasureClassChampionNightmare: d.String("TreasureClass2(N)"),
TreasureClassChampionHell: d.String("TreasureClass2(H)"),
TreasureClass3UniqueNormal: d.String("TreasureClass3"),
TreasureClass3UniqueNightmare: d.String("TreasureClass3(N)"),
TreasureClass3UniqueHell: d.String("TreasureClass3(H)"),
TreasureClassQuestNormal: d.String("TreasureClass4"),
TreasureClassQuestNightmare: d.String("TreasureClass4(N)"),
TreasureClassQuestHell: d.String("TreasureClass4(H)"),
TreasureClassQuestTriggerId: d.String("TCQuestId"),
TreasureClassQuestCompleteId: d.String("TCQuestCP"),
SpecialEndDeath: d.Number("SplEndDeath"),
SpecialGetModeChart: d.Number("SplGetModeChart") > 0,
SpecialEndGeneric: d.Number("SplEndGeneric") > 0,
SpecialClientEnd: d.Number("SplClientEnd") > 0,
}
MonStats[record.Key] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d MonStats records", len(MonStats))
}

View File

@ -9,37 +9,6 @@ import (
// MonStats2Record is a representation of a row from monstats2.txt
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
@ -59,45 +28,40 @@ type MonStats2Record struct {
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
Key string // Key, the object ID MonStatEx feild from MonStat
BaseWeaponClass string
ResurrectSkill string
Heart string
BodyPart string
// These follow three are apparently unused
Height int
OverlayHeight int
PixelHeight int
// Diameter in subtiles
SizeX int
SizeY int
// Bounding box
BoxTop int
BoxLeft int
BoxWidth int
BoxHeight int
// Spawn method used
SpawnMethod int
// Melee radius
MeleeRng int
// base weaponclass?
HitClass int
// 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
@ -116,6 +80,79 @@ type MonStats2Record struct {
dSQ int
dRN int
// If the units is restored on map reload
Restore int
// What maximap index is used for the automap
AutomapCel int
// 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
// Inferno animation stuff
InfernoLen int
InfernoAnim int
InfernoRollback int
// Which mode is used after resurrection
ResurrectMode d2enum.MonsterAnimationMode
// This specifies if the size values get used for collision detection
NoGfxHitTest bool
// 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
// 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
// Available modes while moving aside from WL and RN
A1mv bool
A2mv bool
@ -125,12 +162,6 @@ type MonStats2Record struct {
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
@ -191,39 +222,8 @@ type MonStats2Record struct {
// 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
}
// MonStats2 stores all of the MonStats2Records
@ -233,140 +233,143 @@ var MonStats2 map[string]*MonStats2Record
// LoadMonStats2 loads MonStats2Records from monstats2.txt
//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)
MonStats2 = make(map[string]*MonStats2Record)
for idx := range dict.Data {
d := d2common.LoadDataDictionary(file)
for d.Next() {
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("RHv", 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),
Key: d.String("Id"),
Height: d.Number("Height"),
OverlayHeight: d.Number("OverlayHeight"),
PixelHeight: d.Number("pixHeight"),
SizeX: d.Number("SizeX"),
SizeY: d.Number("SizeY"),
SpawnMethod: d.Number("spawnCol"),
MeleeRng: d.Number("MeleeRng"),
BaseWeaponClass: d.String("BaseW"),
HitClass: d.Number("HitClass"),
HDv: d.List("HDv"),
TRv: d.List("TRv"),
LGv: d.List("LGv"),
Rav: d.List("Rav"),
Lav: d.List("Lav"),
RHv: d.List("RHv"),
LHv: d.List("LHv"),
SHv: d.List("SHv"),
S1v: d.List("S1v"),
S2v: d.List("S2v"),
S3v: d.List("S3v"),
S4v: d.List("S4v"),
S5v: d.List("S5v"),
S6v: d.List("S6v"),
S7v: d.List("S7v"),
S8v: d.List("S8v"),
HD: d.Bool("HD"),
TR: d.Bool("TR"),
LG: d.Bool("LG"),
RA: d.Bool("RA"),
LA: d.Bool("LA"),
RH: d.Bool("RH"),
LH: d.Bool("LH"),
SH: d.Bool("SH"),
S1: d.Bool("S1"),
S2: d.Bool("S2"),
S3: d.Bool("S3"),
S4: d.Bool("S4"),
S5: d.Bool("S5"),
S6: d.Bool("S6"),
S7: d.Bool("S7"),
S8: d.Bool("S8"),
TotalPieces: d.Number("TotalPieces"),
mDT: d.Bool("mDT"),
mNU: d.Bool("mNU"),
mWL: d.Bool("mWL"),
mGH: d.Bool("mGH"),
mA1: d.Bool("mA1"),
mA2: d.Bool("mA2"),
mBL: d.Bool("mBL"),
mSC: d.Bool("mSC"),
mS1: d.Bool("mS1"),
mS2: d.Bool("mS2"),
mS3: d.Bool("mS3"),
mS4: d.Bool("mS4"),
mDD: d.Bool("mDD"),
mKB: d.Bool("mKB"),
mSQ: d.Bool("mSQ"),
mRN: d.Bool("mRN"),
dDT: d.Number("mDT"),
dNU: d.Number("mNU"),
dWL: d.Number("mWL"),
dGH: d.Number("mGH"),
dA1: d.Number("mA1"),
dA2: d.Number("mA2"),
dBL: d.Number("mBL"),
dSC: d.Number("mSC"),
dS1: d.Number("mS1"),
dS2: d.Number("mS2"),
dS3: d.Number("mS3"),
dS4: d.Number("mS4"),
dDD: d.Number("mDD"),
dKB: d.Number("mKB"),
dSQ: d.Number("mSQ"),
dRN: d.Number("mRN"),
A1mv: d.Bool("A1mv"),
A2mv: d.Bool("A2mv"),
SCmv: d.Bool("SCmv"),
S1mv: d.Bool("S1mv"),
S2mv: d.Bool("S2mv"),
S3mv: d.Bool("S3mv"),
S4mv: d.Bool("S4mv"),
NoGfxHitTest: d.Bool("noGfxHitTest"),
BoxTop: d.Number("htTop"),
BoxLeft: d.Number("htLeft"),
BoxWidth: d.Number("htWidth"),
BoxHeight: d.Number("htHeight"),
Restore: d.Number("restore"),
AutomapCel: d.Number("automapCel"),
NoMap: d.Bool("noMap"),
NoOvly: d.Bool("noOvly"),
IsSelectable: d.Bool("isSel"),
AllySelectable: d.Bool("alSel"),
shiftSel: d.Bool("shiftSel"),
NotSelectable: d.Bool("noSel"),
IsCorpseSelectable: d.Bool("corpseSel"),
IsAttackable: d.Bool("isAtt"),
IsRevivable: d.Bool("revive"),
IsCritter: d.Bool("critter"),
IsSmall: d.Bool("small"),
IsLarge: d.Bool("large"),
IsSoft: d.Bool("soft"),
IsInert: d.Bool("inert"),
objCol: d.Bool("objCol"),
IsCorpseCollidable: d.Bool("deadCol"),
IsCorpseWalkable: d.Bool("unflatDead"),
HasShadow: d.Bool("Shadow"),
NoUniqueShift: d.Bool("noUniqueShift"),
CompositeDeath: d.Bool("compositeDeath"),
LocalBlood: d.Number("localBlood"),
Bleed: d.Number("Bleed"),
Light: d.Number("Light"),
LightR: d.Number("light-r"),
LightG: d.Number("light-g"),
lightB: d.Number("light-b"),
NormalPalette: d.Number("Utrans"),
NightmarePalette: d.Number("Utrans(N)"),
HellPalatte: d.Number("Utrans(H)"),
Heart: d.String("Heart"),
BodyPart: d.String("BodyPart"),
InfernoLen: d.Number("InfernoLen"),
InfernoAnim: d.Number("InfernoAnim"),
InfernoRollback: d.Number("InfernoRollback"),
ResurrectMode: d2enum.MonsterAnimationModeFromString(d.String("ResurrectMode")),
ResurrectSkill: d.String("ResurrectSkill"),
}
MonStats2[record.Key] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d MonStats2 records", len(MonStats2))
}

View File

@ -10,11 +10,11 @@ import (
type ObjectLookupRecord struct {
Act int
Type d2enum.ObjectType
Id int //nolint:golint Id is the right key
Id int //nolint:golint,stylecheck // Id is the right key
Name string
Description string
ObjectsTxtId int //nolint:golint Id is the right key
MonstatsTxtId int //nolint:golint Id is the right key
ObjectsTxtId int //nolint:golint,stylecheck // Id is the right key
MonstatsTxtId int //nolint:golint,stylecheck // Id is the right key
Direction int
Base string
Token string

View File

@ -23,9 +23,14 @@ func LoadObjectTypes(objectTypeData []byte) {
count := streamReader.GetInt32()
ObjectTypes = make([]ObjectTypeRecord, count)
const (
nameSize = 32
tokenSize = 20
)
for i := range ObjectTypes {
nameBytes := streamReader.ReadBytes(32)
tokenBytes := streamReader.ReadBytes(20)
nameBytes := streamReader.ReadBytes(nameSize)
tokenBytes := streamReader.ReadBytes(tokenSize)
ObjectTypes[i] = ObjectTypeRecord{
Name: strings.TrimSpace(strings.ReplaceAll(string(nameBytes), string(0), "")),
Token: strings.TrimSpace(strings.ReplaceAll(string(tokenBytes), string(0), "")),

View File

@ -25,7 +25,7 @@ type ObjectRecord struct {
token string // refers to what graphics this object uses
// Don't use, index by line number
id int //nolint:golint it's ok that it's called Id, unused indexed by line number instead
id int //nolint:golint,stylecheck // unused, indexed by line number instead
SpawnMax int // unused?
TrapProbability int // unused

View File

@ -125,35 +125,39 @@ var SuperUniques map[string]*SuperUniqueRecord
// LoadSuperUniques loads SuperUniqueRecords from superuniques.txt
func LoadSuperUniques(file []byte) {
dictionary := d2common.LoadDataDictionary(string(file))
SuperUniques = make(map[string]*SuperUniqueRecord, len(dictionary.Data))
SuperUniques = make(map[string]*SuperUniqueRecord)
for idx := range dictionary.Data {
d := d2common.LoadDataDictionary(file)
for d.Next() {
record := &SuperUniqueRecord{
Key: dictionary.GetString("Superunique", idx),
Name: dictionary.GetString("Name", idx),
Class: dictionary.GetString("Class", idx),
HcIdx: dictionary.GetString("hcIdx", idx),
MonSound: dictionary.GetString("MonSound", idx),
Key: d.String("Superunique"),
Name: d.String("Name"),
Class: d.String("Class"),
HcIdx: d.String("hcIdx"),
MonSound: d.String("MonSound"),
Mod: [3]int{
dictionary.GetNumber("Mod1", idx),
dictionary.GetNumber("Mod2", idx),
dictionary.GetNumber("Mod3", idx),
d.Number("Mod1"),
d.Number("Mod2"),
d.Number("Mod3"),
},
MinGrp: dictionary.GetNumber("MinGrp", idx),
MaxGrp: dictionary.GetNumber("MaxGrp", idx),
IsExpansion: dictionary.GetNumber("EClass", idx) == 1,
AutoPosition: dictionary.GetNumber("AutoPos", idx) == 1,
Stacks: dictionary.GetNumber("Stacks", idx) == 1,
TreasureClassNormal: dictionary.GetString("TC", idx),
TreasureClassNightmare: dictionary.GetString("TC(N)", idx),
TreasureClassHell: dictionary.GetString("TC(H)", idx),
UTransNormal: dictionary.GetString("Utrans", idx),
UTransNightmare: dictionary.GetString("Utrans(N)", idx),
UTransHell: dictionary.GetString("Utrans(H)", idx),
MinGrp: d.Number("MinGrp"),
MaxGrp: d.Number("MaxGrp"),
IsExpansion: d.Bool("EClass"),
AutoPosition: d.Bool("AutoPos"),
Stacks: d.Bool("Stacks"),
TreasureClassNormal: d.String("TC"),
TreasureClassNightmare: d.String("TC(N)"),
TreasureClassHell: d.String("TC(H)"),
UTransNormal: d.String("Utrans"),
UTransNightmare: d.String("Utrans(N)"),
UTransHell: d.String("Utrans(H)"),
}
SuperUniques[record.Key] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d SuperUnique records", len(SuperUniques))
}

View File

@ -9,37 +9,35 @@ import (
// UniqueItemRecord is a representation of a row from uniqueitems.txt
type UniqueItemRecord struct {
Name string
Version int // 0 = classic pre 1.07, 1 = 1.07-1.11, 100 = expansion
Enabled bool // if false, this record won't be loaded (should always be true...)
Ladder bool // if true, can only be found on ladder and not in single player / tcp/ip
Rarity int // 1-255, higher is more common (ironically...)
NoLimit bool // if true, can drop more than once per game
// (if false, can only drop once per game; if it would drop,
// instead a rare item with enhanced durability drops)
Level int // item's level, can only be dropped by monsters / recipes / vendors / objects of this level or higher
// otherwise they would drop a rare item with enhanced durability
RequiredLevel int // character must have this level to use this item
Code string // three letter code, points to a record in Weapons, Armor, or Misc
TypeDescription string
UberDescription string
SingleCopy bool // if true, player can only hold one of these. can't trade it or pick it up
CostMultiplier int // base price is multiplied by this when sold, repaired or bought
CostAdd int // after multiplied by above, this much is added to the price
Properties [12]UniqueItemProperty
Name string
Code string // three letter code, points to a record in Weapons, Armor, or Misc
TypeDescription string
UberDescription string
CharacterGfxTransform string // palette shift applied to this items gfx when held and when
// on the ground (flippy). Points to a record in Colors.txt
InventoryGfxTransform string // palette shift applied to the inventory gfx
FlippyFile string // if non-empty, overrides the base item's dropped gfx
InventoryFile string // if non-empty, overrides the base item's inventory gfx
DropSound string // if non-empty, overrides the base item's drop sound
UseSound string // if non-empty, overrides the sound played when item is used
DropSound string // if non-empty, overrides the base item's drop sound
DropSfxFrame int // if non-empty, overrides the base item's frame at which the drop sound plays
UseSound string // if non-empty, overrides the sound played when item is used
Version int // 0 = classic pre 1.07, 1 = 1.07-1.11, 100 = expansion
Rarity int // 1-255, higher is more common (ironically...)
Level int // item's level, can only be dropped by monsters / recipes / vendors / objects of this level or higher
// otherwise they would drop a rare item with enhanced durability
RequiredLevel int // character must have this level to use this item
CostMultiplier int // base price is multiplied by this when sold, repaired or bought
CostAdd int // after multiplied by above, this much is added to the price
DropSfxFrame int // if non-empty, overrides the base item's frame at which the drop sound plays
Properties [12]UniqueItemProperty
Enabled bool // if false, this record won't be loaded (should always be true...)
Ladder bool // if true, can only be found on ladder and not in single player / tcp/ip
NoLimit bool // if true, can drop more than once per game
// (if false, can only drop once per game; if it would drop,
// instead a rare item with enhanced durability drops)
SingleCopy bool // if true, player can only hold one of these. can't trade it or pick it up
}
// UniqueItemProperty is describes a property of a unique item

View File

@ -6,8 +6,10 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
var Weapons map[string]*ItemCommonRecord
// Weapons stores all of the WeaponRecords
var Weapons map[string]*ItemCommonRecord //nolint:gochecknoglobals // Currently global by design, only written once
// LoadWeapons loads weapon records
func LoadWeapons(file []byte) {
Weapons = LoadCommonItems(file, d2enum.InventoryItemTypeWeapon)
log.Printf("Loaded %d weapons", len(Weapons))

View File

@ -174,7 +174,7 @@ const (
LevelPreset = "/data/global/excel/LvlPrest.txt"
LevelType = "/data/global/excel/LvlTypes.txt"
ObjectType = "/data/global/excel/objtype.bin"
LevelWarp = "/data/global/excel/LvlWarp.bin"
LevelWarp = "/data/global/excel/LvlWarp.txt"
LevelDetails = "/data/global/excel/Levels.txt"
LevelMaze = "/data/global/excel/LvlMaze.txt"
LevelSubstitutions = "/data/global/excel/LvlSub.txt"

View File

@ -1,6 +1,9 @@
package d2common
import (
"bytes"
"encoding/csv"
"io"
"log"
"strconv"
"strings"
@ -8,74 +11,78 @@ import (
// DataDictionary represents a data file (Excel)
type DataDictionary struct {
FieldNameLookup map[string]int
Data [][]string
lookup map[string]int
r *csv.Reader
record []string
Err error
}
// LoadDataDictionary loads the contents of a spreadsheet style txt file
func LoadDataDictionary(text string) *DataDictionary {
result := &DataDictionary{}
lines := strings.Split(text, "\r\n")
fileNames := strings.Split(lines[0], "\t")
result.FieldNameLookup = make(map[string]int)
func LoadDataDictionary(buf []byte) *DataDictionary {
cr := csv.NewReader(bytes.NewReader(buf))
cr.Comma = '\t'
cr.ReuseRecord = true
for i, fieldName := range fileNames {
result.FieldNameLookup[fieldName] = i
}
result.Data = make([][]string, len(lines)-2)
for i, line := range lines[1:] {
if strings.TrimSpace(line) == "" {
continue
}
values := strings.Split(line, "\t")
if len(values) != len(result.FieldNameLookup) {
continue
}
result.Data[i] = values
}
return result
}
// GetString gets a string from the given column and row
func (v *DataDictionary) GetString(fieldName string, index int) string {
return v.Data[index][v.FieldNameLookup[fieldName]]
}
// GetNumber gets a number for the given column and row
func (v *DataDictionary) GetNumber(fieldName string, index int) int {
str := v.GetString(fieldName, index)
str = EmptyToZero(AsterToEmpty(str))
result, err := strconv.Atoi(str)
fieldNames, err := cr.Read()
if err != nil {
log.Panic(err)
panic(err)
}
return result
data := &DataDictionary{
lookup: make(map[string]int, len(fieldNames)),
r: cr,
}
for i, name := range fieldNames {
data.lookup[name] = i
}
return data
}
// GetDelimitedList splits a delimited list from the given column and row
func (v *DataDictionary) GetDelimitedList(fieldName string, index int) []string {
unsplit := v.GetString(fieldName, index)
// Next reads the next row, skips Expansion lines or
// returns false when the end of a file is reached or an error occured
func (d *DataDictionary) Next() bool {
var err error
d.record, err = d.r.Read()
if err == io.EOF {
return false
} else if err != nil {
d.Err = err
return false
}
// Comma delimited fields are quoted, not terribly pretty to fix that here but...
unsplit = strings.TrimRight(unsplit, "\"")
unsplit = strings.TrimLeft(unsplit, "\"")
return strings.Split(unsplit, ",")
if d.record[0] == "Expansion" {
return d.Next()
}
return true
}
// GetBool gets a bool value for the given column and row
func (v *DataDictionary) GetBool(fieldName string, index int) bool {
n := v.GetNumber(fieldName, index)
// String gets a string from the given column
func (d *DataDictionary) String(field string) string {
return d.record[d.lookup[field]]
}
// Number gets a number for the given column
func (d *DataDictionary) Number(field string) int {
n, err := strconv.Atoi(d.String(field))
if err != nil {
return 0
}
return n
}
// List splits a delimited list from the given column
func (d *DataDictionary) List(field string) []string {
str := d.String(field)
return strings.Split(str, ",")
}
// Bool gets a bool value for the given column
func (d *DataDictionary) Bool(field string) bool {
n := d.Number(field)
if n > 1 {
log.Panic("GetBool on non-bool field")
log.Panic("Bool on non-bool field")
}
return n == 1
}