diff --git a/d2app/app.go b/d2app/app.go index 05286dff..a5f341f0 100644 --- a/d2app/app.go +++ b/d2app/app.go @@ -31,7 +31,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2config" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" "github.com/OpenDiablo2/OpenDiablo2/d2game/d2gamescreen" @@ -195,8 +194,6 @@ func (a *App) initialize() error { return err } - d2inventory.LoadHeroObjects() - a.ui.Initialize() return nil @@ -226,73 +223,25 @@ func (a *App) loadDataDict() error { path string loader func(data []byte) }{ - {d2resource.LevelType, d2datadict.LoadLevelTypes}, - {d2resource.LevelPreset, d2datadict.LoadLevelPresets}, - {d2resource.LevelWarp, d2datadict.LoadLevelWarps}, {d2resource.ObjectType, d2datadict.LoadObjectTypes}, {d2resource.ObjectDetails, d2datadict.LoadObjects}, - {d2resource.Weapons, d2datadict.LoadWeapons}, - {d2resource.Armor, d2datadict.LoadArmors}, - {d2resource.Books, d2datadict.LoadBooks}, - {d2resource.Misc, d2datadict.LoadMiscItems}, {d2resource.UniqueItems, d2datadict.LoadUniqueItems}, - {d2resource.Missiles, d2datadict.LoadMissiles}, - {d2resource.SoundSettings, d2datadict.LoadSounds}, {d2resource.AnimationData, d2data.LoadAnimationData}, - {d2resource.MonStats, d2datadict.LoadMonStats}, - {d2resource.MonStats2, d2datadict.LoadMonStats2}, - {d2resource.MonPreset, d2datadict.LoadMonPresets}, - {d2resource.MonProp, d2datadict.LoadMonProps}, - {d2resource.MonType, d2datadict.LoadMonTypes}, - {d2resource.MonMode, d2datadict.LoadMonModes}, - {d2resource.MagicPrefix, d2datadict.LoadMagicPrefix}, - {d2resource.MagicSuffix, d2datadict.LoadMagicSuffix}, - {d2resource.ItemStatCost, d2datadict.LoadItemStatCosts}, - {d2resource.ItemRatio, d2datadict.LoadItemRatios}, {d2resource.Overlays, d2datadict.LoadOverlays}, - {d2resource.CharStats, d2datadict.LoadCharStats}, - {d2resource.Hireling, d2datadict.LoadHireling}, - {d2resource.Experience, d2datadict.LoadExperienceBreakpoints}, - {d2resource.Gems, d2datadict.LoadGems}, {d2resource.QualityItems, d2datadict.LoadQualityItems}, {d2resource.Runes, d2datadict.LoadRunewords}, - {d2resource.DifficultyLevels, d2datadict.LoadDifficultyLevels}, - {d2resource.AutoMap, d2datadict.LoadAutoMaps}, - {d2resource.LevelDetails, d2datadict.LoadLevelDetails}, - {d2resource.LevelMaze, d2datadict.LoadLevelMazeDetails}, - {d2resource.LevelSubstitutions, d2datadict.LoadLevelSubstitutions}, - {d2resource.CubeRecipes, d2datadict.LoadCubeRecipes}, {d2resource.SuperUniques, d2datadict.LoadSuperUniques}, - {d2resource.Inventory, d2datadict.LoadInventory}, - {d2resource.Skills, d2datadict.LoadSkills}, - {d2resource.SkillCalc, d2datadict.LoadSkillCalculations}, - {d2resource.MissileCalc, d2datadict.LoadMissileCalculations}, {d2resource.Properties, d2datadict.LoadProperties}, - {d2resource.SkillDesc, d2datadict.LoadSkillDescriptions}, - {d2resource.ItemTypes, d2datadict.LoadItemTypes}, - {d2resource.BodyLocations, d2datadict.LoadBodyLocations}, {d2resource.Sets, d2datadict.LoadSetRecords}, {d2resource.SetItems, d2datadict.LoadSetItems}, - {d2resource.AutoMagic, d2datadict.LoadAutoMagicRecords}, {d2resource.TreasureClass, d2datadict.LoadTreasureClassRecords}, {d2resource.States, d2datadict.LoadStates}, - {d2resource.SoundEnvirons, d2datadict.LoadSoundEnvirons}, {d2resource.Shrines, d2datadict.LoadShrines}, - {d2resource.ElemType, d2datadict.LoadElemTypes}, {d2resource.PlrMode, d2datadict.LoadPlrModes}, {d2resource.PetType, d2datadict.LoadPetTypes}, - {d2resource.NPC, d2datadict.LoadNPCs}, - {d2resource.MonsterUniqueModifier, d2datadict.LoadMonsterUniqueModifiers}, - {d2resource.MonsterEquipment, d2datadict.LoadMonsterEquipment}, {d2resource.UniqueAppellation, d2datadict.LoadUniqueAppellations}, - {d2resource.MonsterLevel, d2datadict.LoadMonsterLevels}, - {d2resource.MonsterSound, d2datadict.LoadMonsterSounds}, - {d2resource.MonsterSequence, d2datadict.LoadMonsterSequences}, {d2resource.PlayerClass, d2datadict.LoadPlayerClasses}, - {d2resource.MonsterPlacement, d2datadict.LoadMonsterPlacements}, {d2resource.ObjectGroup, d2datadict.LoadObjectGroups}, - {d2resource.CompCode, d2datadict.LoadComponentCodes}, - {d2resource.MonsterAI, d2datadict.LoadMonsterAI}, {d2resource.RarePrefix, d2datadict.LoadRareItemPrefixRecords}, {d2resource.RareSuffix, d2datadict.LoadRareItemSuffixRecords}, } @@ -308,8 +257,6 @@ func (a *App) loadDataDict() error { entry.loader(data) } - d2datadict.LoadItemEquivalencies() // depends on ItemCommon and ItemTypes - return nil } @@ -683,16 +630,23 @@ func updateInitError(target d2interface.Surface) error { func (a *App) ToMainMenu() { buildInfo := d2gamescreen.BuildInfo{Branch: a.gitBranch, Commit: a.gitCommit} - mainMenu := d2gamescreen.CreateMainMenu(a, a.asset, a.renderer, a.inputManager, a.audio, a.ui, - buildInfo) + mainMenu, err := d2gamescreen.CreateMainMenu(a, a.asset, a.renderer, a.inputManager, a.audio, a.ui, buildInfo) + if err != nil { + log.Print(err) + return + } a.screen.SetNextScreen(mainMenu) } // ToSelectHero forces the game to transition to the Select Hero (create character) screen func (a *App) ToSelectHero(connType d2clientconnectiontype.ClientConnectionType, host string) { - selectHero := d2gamescreen.CreateSelectHeroClass(a, a.asset, a.renderer, a.audio, a.ui, - connType, host) + selectHero, err := d2gamescreen.CreateSelectHeroClass(a, a.asset, a.renderer, a.audio, a.ui, connType, host) + if err != nil { + log.Print(err) + return + } + a.screen.SetNextScreen(selectHero) } @@ -719,9 +673,12 @@ func (a *App) ToCharacterSelect(connType d2clientconnectiontype.ClientConnection // ToMapEngineTest forces the game to transition to the map engine test screen func (a *App) ToMapEngineTest(region, level int) { - met := d2gamescreen.CreateMapEngineTest(region, level, a.asset, a.terminal, a.renderer, - a.inputManager, - a.audio, a.screen) + met, err := d2gamescreen.CreateMapEngineTest(region, level, a.asset, a.terminal, a.renderer, a.inputManager, a.audio, a.screen) + if err != nil { + return + log.Print(err) + } + a.screen.SetNextScreen(met) } diff --git a/d2common/d2data/d2datadict/armor.go b/d2common/d2data/d2datadict/armor.go deleted file mode 100644 index ab7e4153..00000000 --- a/d2common/d2data/d2datadict/armor.go +++ /dev/null @@ -1,17 +0,0 @@ -package d2datadict - -import ( - "log" - - "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 - -// LoadArmors loads entries from armor.txt as ItemCommonRecords -func LoadArmors(file []byte) { - Armors = LoadCommonItems(file, d2enum.InventoryItemTypeArmor) - log.Printf("Loaded %d armors", len(Armors)) -} diff --git a/d2common/d2data/d2datadict/automagic.go b/d2common/d2data/d2datadict/automagic.go deleted file mode 100644 index 662e8415..00000000 --- a/d2common/d2data/d2datadict/automagic.go +++ /dev/null @@ -1,206 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// AutoMagicRecord describes rules for automatically generating magic properties when spawning -// items -type AutoMagicRecord struct { - // IncludeItemCodes - // itype 1 to itype7 - // "Include Type" fields. You need to place item codes in any of these columns to allow that item - // to receive mods from this row. See the note below. - IncludeItemCodes [7]string - - // ModCode - // They're the Property codes from Properties.txt. - // These determine the actual properties which make up this autoprefix. - // Each autoprefix can include up to three modifiers. - ModCode [3]string - - // ExcludeItemCodes - // etype 1 to etype3 - // 'Exclude type' . This field prevents certain mods from spawning on specific item codes. - ExcludeItemCodes [3]string - - // ModParam, min, max - // Parameter, min, and max values for the property - ModParam [3]int - ModMin [3]int - ModMax [3]int - - // Name - // String Comment Blizzard lists the equivalent prefix/affix here. - // You can use what ever you wish here though. Handy for keeping track of groups. - Name string - - // Version - // it needs to be set to 0 if the prefix\affix you want to create or edit is going to be a - // classic-only item ( with "classic" we mean "non-expansion" mode, - // which you can toggle on and off when creating a character) or set to 100 if it's going to be - // available in Expansion. This field is important, - // as Items with " version" set to 100 will NOT be generated in Classic Diablo II. - Version int - - // MinSpawnLevel - // this field accepts numeric values and specifies the minimum level from which this autoprefix - // can spawn. The column in question can be combined with the following maxlevel: to effectively - // control groups of automods, - // because you can use this field to combine multiple rows so that the autoprefixes are assigned - // based on the level of the treasure drop [see below]. - MinSpawnLevel int - - // MaxSpawnLevel - // this field accepts numeric values and specifies the maximum level beyond which the automod - // stop spawning. - MaxSpawnLevel int - - // LevelRequirement - // It is the level requirement for this autoprefix. - // This value is added to the Level Requirement of the item generated with this mod. - LevelRequirement int - - // Class - // the class type - Class d2enum.Hero - - // ClassLevelRequirement - // If class is set, this should allow a separate level requirement for this class. - // This is a polite thing to do, - // as other classes gain no advantage from class specific modifiers. - // I am uncertain that this actually works. - ClassLevelRequirement int - - // Frequency - // For autoprefix groups, it states the chance to spawn this specific group member vs others. - // Higher numbers means the automod will be more common. The 1. - // 09 version file guide has some formuae relateing to this. - Frequency int - - // Group - // This field accepts numeric values and groups all the lines with the same values, - // which are treated as a group. Only one autoprefix per group can be chosen, - // and groups are influenced by levelreq, classlevelreq and frequency The 1. - // 09 version file guide has a very nice tutorial about how to set up groups. - // NOTE: The group number must also be entered in the 'auto prefix' column of each entry in - // Weapons.txt or Armor.txt in order for the property to appear. - Group int - - // PaletteTransform - // If transform is set to 1 then the item will be colored with the chosen color code, - // taken from Colors.txt - PaletteTransform int - - // CostDivide - // Numeric value that acts as divisor for the item price. - CostDivide int - - // CostMultiply - // Numeric value that acts as multiplier for the item price. - CostMultiply int - - // CostAdd - // Numeric value that acts as a flat sum added to the item price. - CostAdd int - - // Spawnable - // It is a boolean type field, and states if this autoprefix can actually spawn in the game. - // You can disable this row by setting it to 0 , or enable it by setting it to 1 - Spawnable bool - - // SpawnOnRare - // It decides whether this autoprefix spawns on rare quality items or not. - // You can prevent that from happening by setting it to 0 , or you can allow it by setting it to 1 - SpawnOnRare bool - - // transform - // It is a boolean value whichallows the colorization of the items. - Transform bool -} - -// AutoMagic has all of the AutoMagicRecords, used for generating magic properties for spawned items -var AutoMagic []*AutoMagicRecord //nolint:gochecknoglobals // Currently global by design, only written once - -// LoadAutoMagicRecords loads AutoMagic records from automagic.txt -func LoadAutoMagicRecords(file []byte) { - AutoMagic = make([]*AutoMagicRecord, 0) - - charCodeMap := map[string]d2enum.Hero{ - "ama": d2enum.HeroAmazon, - "ass": d2enum.HeroAssassin, - "bar": d2enum.HeroBarbarian, - "dru": d2enum.HeroDruid, - "nec": d2enum.HeroNecromancer, - "pal": d2enum.HeroPaladin, - "sor": d2enum.HeroSorceress, - } - - d := d2txt.LoadDataDictionary(file) - - for d.Next() { - record := &AutoMagicRecord{ - Name: d.String("Name"), - Version: d.Number("version"), - Spawnable: d.Number("spawnable") > 0, - SpawnOnRare: d.Number("rare") > 0, - MinSpawnLevel: d.Number("level"), - MaxSpawnLevel: d.Number("maxlevel"), - LevelRequirement: d.Number("levelreq"), - Class: charCodeMap[d.String("class")], - ClassLevelRequirement: d.Number("classlevelreq"), - Frequency: d.Number("frequency"), - Group: d.Number("group"), - ModCode: [3]string{ - d.String("mod1code"), - d.String("mod2code"), - d.String("mod3code"), - }, - ModParam: [3]int{ - d.Number("mod1param"), - d.Number("mod2param"), - d.Number("mod3param"), - }, - ModMin: [3]int{ - d.Number("mod1min"), - d.Number("mod2min"), - d.Number("mod3min"), - }, - ModMax: [3]int{ - d.Number("mod1max"), - d.Number("mod2max"), - d.Number("mod3max"), - }, - Transform: d.Number("transform") > 0, - PaletteTransform: d.Number("transformcolor"), - IncludeItemCodes: [7]string{ - d.String("itype1"), - d.String("itype2"), - d.String("itype3"), - d.String("itype4"), - d.String("itype5"), - d.String("itype6"), - d.String("itype7"), - }, - ExcludeItemCodes: [3]string{ - d.String("etype1"), - d.String("etype2"), - d.String("etype3"), - }, - CostDivide: d.Number("divide"), - CostMultiply: d.Number("multiply"), - CostAdd: d.Number("add"), - } - - AutoMagic = append(AutoMagic, record) - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d AutoMagic records", len(AutoMagic)) -} diff --git a/d2common/d2data/d2datadict/automap.go b/d2common/d2data/d2datadict/automap.go deleted file mode 100644 index fb0ebd98..00000000 --- a/d2common/d2data/d2datadict/automap.go +++ /dev/null @@ -1,98 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// 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 { - // LevelName is a string with an act number followed - // by a level type, separated by a space. For example: - // '1 Barracks' is the barracks level in act 1. - LevelName string - - // TileName refers to a certain tile orientation. - // See https://d2mods.info/forum/kb/viewarticle?a=468 - TileName string - - // Style is the top index in a 2D tile array. - Style int // tiles[autoMapRecord.Style][] - - // StartSequence and EndSequence are sub indices the - // same 2D array as Style. They describe a range of - // tiles for which covered by this AutoMapRecord. - // In some rows you can find a value of -1. This means - // the game will only look at Style and TileName to - // determine which tiles are addressed. - StartSequence int // tiles[][autoMapRecord.StartSequence] - EndSequence int // tiles[][autoMapRecord.EndSequence] - - // Type values are described as: - // "...just comment fields, as far as I know. Put in - // whatever you like..." - // The values seem functional but naming conventions - // vary between LevelNames. - // Type1 string - // Type2 string - // Type3 string - // Type4 string // Note: I commented these out for now because they supposedly aren't useful see the LoadAutoMaps function. - - // Frames determine the frame of the MaxiMap(s).dc6 that - // will be applied to the specified tiles. The frames - // are in rows, if each row holds 20 images (when you - // re-extract the chart with Dc6Table, you can specify - // how many graphics a line can hold), line 1 includes - // icons 0-19, line 2 from 20 to 39 etc. - // Multiple values exist for Cel (and Type) to enable - // variation. Presumably game chooses randomly between - // any of the 4 values which are not set to -1. - Frames []int -} - -// AutoMaps contains all data in AutoMap.txt. -//nolint:gochecknoglobals // Current design is to have these global -var AutoMaps []*AutoMapRecord - -// LoadAutoMaps populates AutoMaps with the data from AutoMap.txt. -// It also amends a duplicate field (column) name in that data. -func LoadAutoMaps(file []byte) { - AutoMaps = make([]*AutoMapRecord, 0) - - var frameFields = []string{"Cel1", "Cel2", "Cel3", "Cel4"} - - // Split file by newlines and tabs - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &AutoMapRecord{ - LevelName: d.String("LevelName"), - TileName: d.String("TileName"), - - Style: d.Number("Style"), - StartSequence: d.Number("StartSequence"), - EndSequence: d.Number("EndSequence"), - - //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)) - - for i := range frameFields { - 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)) -} diff --git a/d2common/d2data/d2datadict/body_locations.go b/d2common/d2data/d2datadict/body_locations.go deleted file mode 100644 index b39a0a5c..00000000 --- a/d2common/d2data/d2datadict/body_locations.go +++ /dev/null @@ -1,37 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// BodyLocationRecord describes a body location that items can be equipped to -type BodyLocationRecord struct { - Name string - Code string -} - -// BodyLocations contains the body location records -//nolint:gochecknoglobals // Currently global by design, only written once -var BodyLocations map[string]*BodyLocationRecord - -// LoadBodyLocations loads body locations from -func LoadBodyLocations(file []byte) { - BodyLocations = make(map[string]*BodyLocationRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - location := &BodyLocationRecord{ - Name: d.String("Name"), - Code: d.String("Code"), - } - BodyLocations[location.Code] = location - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d Body Location records", len(BodyLocations)) -} diff --git a/d2common/d2data/d2datadict/books.go b/d2common/d2data/d2datadict/books.go deleted file mode 100644 index b0281932..00000000 --- a/d2common/d2data/d2datadict/books.go +++ /dev/null @@ -1,54 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// BooksRecord is a representation of a row from books.txt -type BooksRecord struct { - Name string - Namco string // The displayed name, where the string prefix is "Tome" - Completed string - ScrollSpellCode string - BookSpellCode string - pSpell int - SpellIcon int - ScrollSkill string - BookSkill string - BaseCost int - CostPerCharge int -} - -// Books stores all of the BooksRecords -var Books map[string]*BooksRecord //nolint:gochecknoglobals // Currently global by design, only written once - -// LoadBooks loads Books records into a map[string]*BooksRecord -func LoadBooks(file []byte) { - Books = make(map[string]*BooksRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &BooksRecord{ - Name: d.String("Name"), - Namco: d.String("Namco"), - Completed: d.String("Completed"), - ScrollSpellCode: d.String("ScrollSpellCode"), - BookSpellCode: d.String("BooksSpellCode"), - pSpell: d.Number("pSpell"), - SpellIcon: d.Number("SpellIcon"), - ScrollSkill: d.String("ScrollSkill"), - BookSkill: d.String("BookSkill"), - BaseCost: d.Number("BaseCost"), - CostPerCharge: d.Number("CostPerCharge"), - } - Books[record.Namco] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d book items", len(Books)) -} diff --git a/d2common/d2data/d2datadict/calculations.go b/d2common/d2data/d2datadict/calculations.go deleted file mode 100644 index 50ecbe3b..00000000 --- a/d2common/d2data/d2datadict/calculations.go +++ /dev/null @@ -1,61 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// CalculationRecord The skillcalc.txt and misscalc.txt files are essentially lookup tables -// for the Skills.txt and Missiles.txt Calc functions To avoid duplication (since they have -// identical fields) they are both represented by the CalculationRecord type -type CalculationRecord struct { - Code string - Description string -} - -// SkillCalculations is where calculation records are stored -var SkillCalculations map[string]*CalculationRecord //nolint:gochecknoglobals // Currently global by design - -// MissileCalculations is where missile calculations are stored -var MissileCalculations map[string]*CalculationRecord //nolint:gochecknoglobals // Currently global by design - -// LoadSkillCalculations loads skill calculation records from skillcalc.txt -func LoadSkillCalculations(file []byte) { - SkillCalculations = make(map[string]*CalculationRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &CalculationRecord{ - Code: d.String("code"), - Description: d.String("*desc"), - } - SkillCalculations[record.Code] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d Skill Calculation records", len(SkillCalculations)) -} - -// LoadMissileCalculations loads missile calculation records from misscalc.txt -func LoadMissileCalculations(file []byte) { - MissileCalculations = make(map[string]*CalculationRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &CalculationRecord{ - Code: d.String("code"), - Description: d.String("*desc"), - } - MissileCalculations[record.Code] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d Missile Calculation records", len(MissileCalculations)) -} diff --git a/d2common/d2data/d2datadict/charstats.go b/d2common/d2data/d2datadict/charstats.go deleted file mode 100644 index 3cd9b1bc..00000000 --- a/d2common/d2data/d2datadict/charstats.go +++ /dev/null @@ -1,201 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// CharStatsRecord is a struct that represents a single row from charstats.txt -type CharStatsRecord struct { - Class d2enum.Hero - - // the initial stats at character level 1 - InitStr int // initial strength - InitDex int // initial dexterity - InitVit int // initial vitality - InitEne int // initial energy - InitStamina int // initial stamina - - ManaRegen int // number of seconds to regen mana completely - ToHitFactor int // added to basic AR of character class - - VelocityWalk int // velocity of the character while walking - VelocityRun int // velocity of the character while running - StaminaRunDrain int // rate of stamina loss, lower is longer drain time - - // NOTE: Each point of Life/Mana/Stamina is divided by 256 for precision. - // value is in fourths, lowest possible is 64/256 - LifePerLevel int // amount of life per character level - ManaPerLevel int // amount of mana per character level - StaminaPerLevel int // amount of stamina per character level - - LifePerVit int // life per point of vitality - ManaPerEne int // mana per point of energy - StaminaPerVit int // stamina per point of vitality - - StatPerLevel int // amount of stat points per level - - BlockFactor int // added to base shield block% in armor.txt (display & calc) - - // appears on starting weapon - StartSkillBonus string // a key that points to a property - - // The skills the character class starts with (always available) - BaseSkill [10]string // the base skill keys of the character, always available - - // string for bonus to class skills (ex: +1 to all Amazon skills). - SkillStrAll string // string for bonus to all skills - SkillStrTab [3]string // string for bonus per skill tabs - SkillStrClassOnly string // string for class-exclusive skills - - BaseWeaponClass d2enum.WeaponClass // controls animation when unarmed - - StartItem [10]string // tokens for the starting items - StartItemLocation [10]string // locations of the starting items - StartItemCount [10]int // amount of the starting items -} - -// CharStats holds all of the CharStatsRecords -//nolint:gochecknoglobals // Currently global by design, only written once -var CharStats map[d2enum.Hero]*CharStatsRecord -var charStringMap map[string]d2enum.Hero //nolint:gochecknoglobals // Currently global by design -var weaponTokenMap map[string]d2enum.WeaponClass //nolint:gochecknoglobals // Currently global by design - -// LoadCharStats loads charstats.txt file contents into map[d2enum.Hero]*CharStatsRecord -//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, - "Druid": d2enum.HeroDruid, - "Assassin": d2enum.HeroAssassin, - "Necromancer": d2enum.HeroNecromancer, - "Paladin": d2enum.HeroPaladin, - "Sorceress": d2enum.HeroSorceress, - } - - weaponTokenMap = map[string]d2enum.WeaponClass{ - "": d2enum.WeaponClassNone, - "hth": d2enum.WeaponClassHandToHand, - "bow": d2enum.WeaponClassBow, - "1hs": d2enum.WeaponClassOneHandSwing, - "1ht": d2enum.WeaponClassOneHandThrust, - "stf": d2enum.WeaponClassStaff, - "2hs": d2enum.WeaponClassTwoHandSwing, - "2ht": d2enum.WeaponClassTwoHandThrust, - "xbw": d2enum.WeaponClassCrossbow, - "1js": d2enum.WeaponClassLeftJabRightSwing, - "1jt": d2enum.WeaponClassLeftJabRightThrust, - "1ss": d2enum.WeaponClassLeftSwingRightSwing, - "1st": d2enum.WeaponClassLeftSwingRightThrust, - "ht1": d2enum.WeaponClassOneHandToHand, - "ht2": d2enum.WeaponClassTwoHandToHand, - } - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &CharStatsRecord{ - Class: charStringMap[d.String("class")], - - InitStr: d.Number("str"), - InitDex: d.Number("dex"), - InitVit: d.Number("vit"), - InitEne: d.Number("int"), - InitStamina: d.Number("stamina"), - - ManaRegen: d.Number("ManaRegen"), - ToHitFactor: d.Number("ToHitFactor"), - - VelocityWalk: d.Number("WalkVelocity"), - VelocityRun: d.Number("RunVelocity"), - StaminaRunDrain: d.Number("RunDrain"), - - LifePerLevel: d.Number("LifePerLevel"), - ManaPerLevel: d.Number("ManaPerLevel"), - StaminaPerLevel: d.Number("StaminaPerLevel"), - - LifePerVit: d.Number("LifePerVitality"), - ManaPerEne: d.Number("ManaPerMagic"), - StaminaPerVit: d.Number("StaminaPerVitality"), - - StatPerLevel: d.Number("StatPerLevel"), - BlockFactor: d.Number("BlockFactor"), - - StartSkillBonus: d.String("StartSkill"), - SkillStrAll: d.String("StrAllSkills"), - SkillStrClassOnly: d.String("StrClassOnly"), - - BaseSkill: [10]string{ - 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.String("StrSkillTab1"), - d.String("StrSkillTab2"), - d.String("StrSkillTab3"), - }, - - BaseWeaponClass: weaponTokenMap[d.String("baseWClass")], - - StartItem: [10]string{ - 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.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.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)) -} diff --git a/d2common/d2data/d2datadict/component_codes.go b/d2common/d2data/d2datadict/component_codes.go deleted file mode 100644 index 0763577d..00000000 --- a/d2common/d2data/d2datadict/component_codes.go +++ /dev/null @@ -1,37 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// ComponentCodeRecord represents a single row from compcode.txt -type ComponentCodeRecord struct { - Component string - Code string -} - -// ComponentCodes is a lookup table for DCC Animation Component Subtype, -// it links hardcoded data with the txt files -var ComponentCodes map[string]*ComponentCodeRecord //nolint:gochecknoglobals // Currently global by design - -// LoadComponentCodes loads components code records from compcode.txt -func LoadComponentCodes(file []byte) { - ComponentCodes = make(map[string]*ComponentCodeRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &ComponentCodeRecord{ - Component: d.String("component"), - Code: d.String("code"), - } - ComponentCodes[record.Component] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d ComponentCode records", len(ComponentCodes)) -} diff --git a/d2common/d2data/d2datadict/cubemain.go b/d2common/d2data/d2datadict/cubemain.go deleted file mode 100644 index 2f71063a..00000000 --- a/d2common/d2data/d2datadict/cubemain.go +++ /dev/null @@ -1,317 +0,0 @@ -package d2datadict - -import ( - "log" - "strconv" - "strings" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// CubeRecipeRecord represents one row from CubeMain.txt. -// It is one possible recipe for the Horadric Cube, with -// requirements and output items. -// See: https://d2mods.info/forum/kb/viewarticle?a=284 -type CubeRecipeRecord struct { - // Description has no function, it just describes the - // recipe. - Description string - - // Enabled is true if the recipe is active in game. - Enabled bool - - // Ladder is true if the recipe is only allowed in - // ladder on realms. Also works for single player - // TCP/IP. - Ladder bool - - // MinDiff sets the minimum difficulty level required - // to use this recipe. - MinDiff int // 0, 1, 2 = normal, nightmare, hell - - // Version specifies whether the recipe is old - // classic, new classic or expansion. - Version int // 0, 1, 100 = old cl, new cl, expansion - - // The following three 'Req' values form a comparison: - // if then recipe - // is allowed. - // - // ReqStatID is an ID value from the ItemStatsCost - // data set specifying the stat to compare. Whether - // this references a player or item stat depends on - // the Operator. - ReqStatID int - // ReqOperation is a number describing the - // comparison operator and the action to take if - // it evaluates to true. See Appendix A in the - // linked article and note that 1, 2, 27 and 28 - // are unusual. - ReqOperation int // 1 - 28 - // ReqValue is the number the stat is compared against. - ReqValue int - - // Class Can be used to make recipes class - // specific. Example class codes given are: - // ama bar pal nec sor dru ass - // - // Since this field isn't used in the game data, - // classFieldToEnum has been implemented based on that - // example. It understands the following syntax, - // which may be incorrect: - // "ama,bar,dru" - Class []d2enum.Hero - - // NumInputs is the total count of input items - // required, including counts in item stacks. - NumInputs int - - // Inputs is the actual recipe, a collection of - // items/stacks with parameters required to - // obtain the items defined in Outputs. - Inputs []CubeRecipeItem - - // Outputs are the items created when the recipe - // is used. - Outputs []CubeRecipeResult -} - -// CubeRecipeResult is an item generated on use of a -// cube recipe. -type CubeRecipeResult struct { - // Item is the item, with a count and parameters. - Item CubeRecipeItem - - // Level causes the item to be a specific level. - // - // Note that this value force spawns the item at - // this specific level. Its also used in the - // formula for the next two fields. - Level int // the item level of Item - - // PLevel uses a portion of the players level for - // the output level. - PLevel int - - // ILevel uses a portion of the first input's - // level for the output level. - ILevel int - - // Properties is a list of properties which may - // be attached to Item. - Properties []CubeRecipeItemProperty -} - -// CubeRecipeItem represents an item, with a stack count -// and parameters. Here it is used to describe the -// required ingredients of the recipe and the output -// result. See: -// https://d2mods.info/forum/kb/viewarticle?a=284 -type CubeRecipeItem struct { - Code string // item code e.g. 'weap' - Params []string // list of parameters e.g. 'sock' - Count int // required stack count -} - -// CubeRecipeItemProperty represents the mod #, -// mod # chance, mod # param, mod # min, mod # max -// fields in cubemain.go -type CubeRecipeItemProperty struct { - Code string // the code field from properties.txt - - // Note: I can't find any example value for this - // so I've made it an int for now - Chance int // the chance to apply the property - - // Note: The few examples in cubemain.go are integers, - // however d2datadict.UniqueItemProperty is a similar - // struct which handles a similar field that may be a - // string or an integer. - // - // See: https://d2mods.info/forum/kb/viewarticle?a=345 - // "the parameter passed on to the associated property, this is used to pass skill IDs, - // state IDs, monster IDs, montype IDs and the like on to the properties that require - // them, these fields support calculations." - Param int // for properties that use parameters - - Min int // the minimum value of the property stat - Max int // the maximum value of the property stat -} - -// CubeRecipes contains all rows in CubeMain.txt. -//nolint:gochecknoglobals // Currently global by design, only written once -var CubeRecipes []*CubeRecipeRecord - -// LoadCubeRecipes populates CubeRecipes with -// the data from CubeMain.txt. -func LoadCubeRecipes(file []byte) { - CubeRecipes = make([]*CubeRecipeRecord, 0) - - // There are repeated fields and sections in this file, some - // of which have inconsistent naming conventions. These slices - // are a simple way to handle them. - var outputFields = []string{"output", "output b", "output c"} - - var outputLabels = []string{"", "b ", "c "} - - var propLabels = []string{"mod 1", "mod 2", "mod 3", "mod 4", "mod 5"} - - var inputFields = []string{"input 1", "input 2", "input 3", "input 4", "input 5", "input 6", "input 7"} - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &CubeRecipeRecord{ - Description: d.String("description"), - - Enabled: d.Bool("enabled"), - Ladder: d.Bool("ladder"), - - MinDiff: d.Number("min diff"), - Version: d.Number("version"), - - ReqStatID: d.Number("param"), - ReqOperation: d.Number("op"), - ReqValue: d.Number("value"), - - Class: classFieldToEnum(d.String("class")), - - NumInputs: d.Number("numinputs"), - } - - // Create inputs - input 1-7 - record.Inputs = make([]CubeRecipeItem, len(inputFields)) - for i := range inputFields { - record.Inputs[i] = newCubeRecipeItem( - d.String(inputFields[i])) - } - - // Create outputs - output "", b, c - record.Outputs = make([]CubeRecipeResult, len(outputLabels)) - for o, outLabel := range outputLabels { - record.Outputs[o] = CubeRecipeResult{ - Item: newCubeRecipeItem( - d.String(outputFields[o])), - - Level: d.Number(outLabel + "lvl"), - ILevel: d.Number(outLabel + "plvl"), - PLevel: d.Number(outLabel + "ilvl"), - } - - // Create properties - mod 1-5 - properties := make([]CubeRecipeItemProperty, len(propLabels)) - for p, prop := range propLabels { - properties[p] = CubeRecipeItemProperty{ - 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"), - } - } - - record.Outputs[o].Properties = properties - } - - CubeRecipes = append(CubeRecipes, record) - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d CubeMainRecord records", len(CubeRecipes)) -} - -// newCubeRecipeItem constructs a CubeRecipeItem from a string of -// arguments. arguments include at least an item and sometimes -// parameters and/or a count (qty parameter). For example: -// "weap,sock,mag,qty=10" -func newCubeRecipeItem(f string) CubeRecipeItem { - args := splitFieldValue(f) - - item := CubeRecipeItem{ - Code: args[0], // the first argument is always the item count - Count: 1, // default to a count of 1 (no qty parameter) - } - - // Ignore the first argument - args = args[1:] - - // Find the qty parameter if it was provided, - // convert to int and assign to item.Count - for idx, arg := range args { - if !strings.HasPrefix(arg, "qty") { - continue - } - - count, err := strconv.Atoi(strings.Split(arg, "=")[1]) - - if err != nil { - log.Fatal("Error parsing item count:", err) - } - - item.Count = count - - // Remove the qty parameter - if idx != len(args)-1 { - args[idx] = args[len(args)-1] - } - - args = args[:len(args)-1] - - break - } - - // No other arguments were provided - if len(args) == 0 { - return item - } - - // Record the argument strings - item.Params = make([]string, len(args)) - for idx, arg := range args { - item.Params[idx] = arg - } - - return item -} - -// classFieldToEnum converts class tokens to s2enum.Hero. -func classFieldToEnum(f string) []d2enum.Hero { - split := splitFieldValue(f) - enums := make([]d2enum.Hero, len(split)) - - for idx, class := range split { - if class == "" { - continue - } - - switch class { - case "bar": - enums[idx] = d2enum.HeroBarbarian - case "nec": - enums[idx] = d2enum.HeroNecromancer - case "pal": - enums[idx] = d2enum.HeroPaladin - case "ass": - enums[idx] = d2enum.HeroAssassin - case "sor": - enums[idx] = d2enum.HeroSorceress - case "ama": - enums[idx] = d2enum.HeroAmazon - case "dru": - enums[idx] = d2enum.HeroDruid - default: - log.Fatalf("Unknown hero token: '%s'", class) - } - } - - return enums -} - -// splitFieldValue splits a string array from the following format: -// "one,two,three" -func splitFieldValue(s string) []string { - return strings.Split(strings.Trim(s, "\""), ",") -} diff --git a/d2common/d2data/d2datadict/difficultylevels.go b/d2common/d2data/d2datadict/difficultylevels.go deleted file mode 100644 index 7f9dd7b8..00000000 --- a/d2common/d2data/d2datadict/difficultylevels.go +++ /dev/null @@ -1,128 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// DifficultyLevels contain the difficulty records for each difficulty -//nolint:gochecknoglobals // Current design is to have these global -var DifficultyLevels map[string]*DifficultyLevelRecord - -// DifficultyLevelRecord contain the parameters that change for different difficultios -type DifficultyLevelRecord struct { - // Difficulty name. it is hardcoded and you cannot add new ones unless you do - // some Code Edits - Name string // Name - - // Resistance penalty in the current difficulty. - ResistancePenalty int // ResistPenalty - - // The percentage of experience you lose when you die on this difficulty. - DeathExperiencePenalty int // DeathExpPenalty - - // Not Used. Pre 1.07 it was the percentage of low quality, normal, superior and - // exceptional items dropped on this difficulty. - DropChanceLow int // UberCodeOddsNormal - DropChanceNormal int // UberCodeOddsNormal - DropChanceSuperior int // UberCodeOddsNormal - DropChanceExceptional int // UberCodeOddsNormal - // Gravestench - I'm splitting this field because I feel it's incoherent - // to keep all of those drop chances together, even if it is that way in the - // txt file... - - // Not used. Pre 1.07 it was the percentage of magic, rare, set and unique - // exceptional items dropped on this difficulty. - DropChanceMagic int // UberCodeOddsGood - DropChanceRare int // UberCodeOddsGood - DropChanceSet int // UberCodeOddsGood - DropChanceUnique int // UberCodeOddsGood - // Gravestench - same as my above comment - - // Not used and didn't exist pre 1.07. - // UltraCodeOddsNormal - - // Additional skill points added to monster skills specified in MonStats.txt - // for this difficulty. It has nothing to do with the missile damage bonus. - MonsterSkillBonus int // MonsterSkillBonus - - // This value is a divisor, and so never set it to 0. It applies to the monster - // freezing length and cold length duration. - MonsterColdDivisor int // MonsterColdDivisor - MonsterFreezeDivisor int // MonsterFreezeDivisor - - // These values are divisor and they're used respectively for AI altering states - AiCurseDivisor int // AiCurseDivisor - LifeStealDivisor int // LifeStealDivisor - ManaStealDivisor int // ManaStealDivisor - - // ----------------------------------------------------------------------- - // The rest of these are listed on PK page, but not present in - // my copy of the txt file (patch_d2/data/global/excel/difficultylevels.txt) - // so I am going to leave these comments - - // Effective percentage of damage and attack rating added to Extra Strong - // Unique/Minion and Champion monsters. This field is actually a coefficient, - // as the total bonus output is BonusFromMonUMod/100*ThisField - // UniqueDamageBonus - // ChampionDamageBonus - - // This is a percentage of how much damage your mercenaries do to an Act boss. - // HireableBossDamagePercent - - // Monster Corpse Explosion damage percent limit. Since the monsters HP grows - // proportionally to the number of players in the game, you can set a cap via - // this field. - // MonsterCEDamagePercent - - // Maximum cap of the monster hit points percentage that can be damaged through - // Static Field. Setting these columns to 0 will make Static Field work the same - // way it did in Classic Diablo II. - // StaticFieldMin - - // Parameters for gambling. They states the odds to find Rares, Sets, Uniques, - // Exceptionals and Elite items when gambling. See Appendix A - // GambleRare - // GambleSet - // GambleUnique - // GambleUber - // GambleUltra - // ----------------------------------------------------------------------- - -} - -// LoadDifficultyLevels is a loader for difficultylevels.txt -func LoadDifficultyLevels(file []byte) { - DifficultyLevels = make(map[string]*DifficultyLevelRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &DifficultyLevelRecord{ - 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)) -} diff --git a/d2common/d2data/d2datadict/elemtype.go b/d2common/d2data/d2datadict/elemtype.go deleted file mode 100644 index 3dc84867..00000000 --- a/d2common/d2data/d2datadict/elemtype.go +++ /dev/null @@ -1,39 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// ElemTypeRecord represents a single line in ElemType.txt -type ElemTypeRecord struct { - // ElemType Elemental damage type name - ElemType string - - // Code Elemental damage type code - Code string -} - -// ElemTypes stores the ElemTypeRecords -var ElemTypes map[string]*ElemTypeRecord //nolint:gochecknoglobals // Currently global by design - -// LoadElemTypes loads ElemTypeRecords into ElemTypes -func LoadElemTypes(file []byte) { - ElemTypes = make(map[string]*ElemTypeRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &ElemTypeRecord{ - ElemType: d.String("Elemental Type"), - Code: d.String("Code"), - } - ElemTypes[record.ElemType] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d ElemType records", len(ElemTypes)) -} diff --git a/d2common/d2data/d2datadict/events.go b/d2common/d2data/d2datadict/events.go deleted file mode 100644 index 5e56b2ca..00000000 --- a/d2common/d2data/d2datadict/events.go +++ /dev/null @@ -1,34 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// EventRecord is a representation of a single row from events.txt -type EventRecord struct { - Event string -} - -// Events holds all of the event records from events.txt -var Events map[string]*EventRecord //nolint:gochecknoglobals // Currently global by design - -// LoadEvents loads all of the event records from events.txt -func LoadEvents(file []byte) { - Events = make(map[string]*EventRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &EventRecord{ - Event: d.String("event"), - } - Events[record.Event] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d Event records", len(Events)) -} diff --git a/d2common/d2data/d2datadict/experience.go b/d2common/d2data/d2datadict/experience.go deleted file mode 100644 index ace0235c..00000000 --- a/d2common/d2data/d2datadict/experience.go +++ /dev/null @@ -1,101 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -/* first column of experience.txt - Level - Amazon - Sorceress - Necromancer - Paladin - Barbarian - Druid - Assassin - ExpRatio - - second row is special case, shows max levels - - MaxLvl - 99 - 99 - 99 - 99 - 99 - 99 - 99 - 10 -*/ - -// ExperienceBreakpointsRecord describes the experience points required to -// gain a level for all character classes -type ExperienceBreakpointsRecord struct { - Level int - HeroBreakpoints map[d2enum.Hero]int - Ratio int -} - -// ExperienceBreakpoints describes the required experience -// for each level for each character class -//nolint:gochecknoglobals // Currently global by design, only written once -var ExperienceBreakpoints map[int]*ExperienceBreakpointsRecord - -//nolint:gochecknoglobals // Currently global by design -var maxLevels map[d2enum.Hero]int - -// GetMaxLevelByHero returns the highest level attainable for a hero type -func GetMaxLevelByHero(heroType d2enum.Hero) int { - return maxLevels[heroType] -} - -// GetExperienceBreakpoint given a hero type and a level, returns the experience required for the level -func GetExperienceBreakpoint(heroType d2enum.Hero, level int) int { - return ExperienceBreakpoints[level].HeroBreakpoints[heroType] -} - -// LoadExperienceBreakpoints loads experience.txt into a map -// ExperienceBreakpoints []*ExperienceBreakpointsRecord -func LoadExperienceBreakpoints(file []byte) { - ExperienceBreakpoints = make(map[int]*ExperienceBreakpointsRecord) - - d := d2txt.LoadDataDictionary(file) - d.Next() - - // 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.Number("Level"), - HeroBreakpoints: 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"), - }, - Ratio: d.Number("ExpRatio"), - } - ExperienceBreakpoints[record.Level] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d ExperienceBreakpoint records", len(ExperienceBreakpoints)) -} diff --git a/d2common/d2data/d2datadict/gems.go b/d2common/d2data/d2datadict/gems.go deleted file mode 100644 index 519149a6..00000000 --- a/d2common/d2data/d2datadict/gems.go +++ /dev/null @@ -1,115 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// GemsRecord is a representation of a single row of gems.txt -// it describes the properties of socketable items -type GemsRecord struct { - Name string - Letter string - Transform int - Code string - Nummods int - WeaponMod1Code string - WeaponMod1Param int - WeaponMod1Min int - WeaponMod1Max int - WeaponMod2Code string - WeaponMod2Param int - WeaponMod2Min int - WeaponMod2Max int - WeaponMod3Code string - WeaponMod3Param int - WeaponMod3Min int - WeaponMod3Max int - HelmMod1Code string - HelmMod1Param int - HelmMod1Min int - HelmMod1Max int - HelmMod2Code string - HelmMod2Param int - HelmMod2Min int - HelmMod2Max int - HelmMod3Code string - HelmMod3Param int - HelmMod3Min int - HelmMod3Max int - ShieldMod1Code string - ShieldMod1Param int - ShieldMod1Min int - ShieldMod1Max int - ShieldMod2Code string - ShieldMod2Param int - ShieldMod2Min int - ShieldMod2Max int - ShieldMod3Code string - ShieldMod3Param int - ShieldMod3Min int - ShieldMod3Max int -} - -// Gems stores all of the GemsRecords -var Gems map[string]*GemsRecord //nolint:gochecknoglobals // Currently global by design, only written once - -// LoadGems loads gem records into a map[string]*GemsRecord -func LoadGems(file []byte) { - Gems = make(map[string]*GemsRecord) - - d := d2txt.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)) -} diff --git a/d2common/d2data/d2datadict/hireling.go b/d2common/d2data/d2datadict/hireling.go deleted file mode 100644 index 9570c543..00000000 --- a/d2common/d2data/d2datadict/hireling.go +++ /dev/null @@ -1,178 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// HirelingRecord is a representation of rows in hireling.txt -// these records describe mercenaries -type HirelingRecord struct { - Hireling string - SubType string - ID int - Class int - Act int - Difficulty int - Level int - Seller int - NameFirst string - NameLast string - Gold int - ExpPerLvl int - HP int - HPPerLvl int - Defense int - DefPerLvl int - Str int - StrPerLvl int - Dex int - DexPerLvl int - AR int - ARPerLvl int - Share int - DmgMin int - DmgMax int - DmgPerLvl int - Resist int - ResistPerLvl int - WType1 string - WType2 string - HireDesc string - DefaultChance int - Skill1 string - Mode1 int - Chance1 int - ChancePerLevel1 int - Level1 int - LvlPerLvl1 int - Skill2 string - Mode2 int - Chance2 int - ChancePerLevel2 int - Level2 int - LvlPerLvl2 int - Skill3 string - Mode3 int - Chance3 int - ChancePerLevel3 int - Level3 int - LvlPerLvl3 int - Skill4 string - Mode4 int - Chance4 int - ChancePerLevel4 int - Level4 int - LvlPerLvl4 int - Skill5 string - Mode5 int - Chance5 int - ChancePerLevel5 int - Level5 int - LvlPerLvl5 int - Skill6 string - Mode6 int - Chance6 int - ChancePerLevel6 int - Level6 int - LvlPerLvl6 int - Head int - Torso int - Weapon int - Shield int -} - -// Hirelings stores hireling (mercenary) records -//nolint:gochecknoglobals // Currently global by design, only written once -var Hirelings []*HirelingRecord - -// LoadHireling loads hireling data into []*HirelingRecord -func LoadHireling(file []byte) { - Hirelings = make([]*HirelingRecord, 0) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - hireling := &HirelingRecord{ - 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)) -} diff --git a/d2common/d2data/d2datadict/inventory.go b/d2common/d2data/d2datadict/inventory.go deleted file mode 100644 index e22edfe3..00000000 --- a/d2common/d2data/d2datadict/inventory.go +++ /dev/null @@ -1,166 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -type box struct { - Left int - Right int - Top int - Bottom int - Width int - Height int -} - -type grid struct { - Box *box - Rows int - Columns int - CellWidth int - CellHeight int -} - -// InventoryRecord represents a single row from inventory.txt, it describes the grid -// layout and positioning of various inventory-related ui panels. -type InventoryRecord struct { - Name string - Panel *box - Grid *grid - Slots map[d2enum.EquippedSlot]*box -} - -// Inventory holds all of the inventory records from inventory.txt -var Inventory map[string]*InventoryRecord //nolint:gochecknoglobals // Currently global by design - -// LoadInventory loads all of the inventory records from inventory.txt -func LoadInventory(file []byte) { //nolint:funlen // doesn't make sense to split - d := d2txt.LoadDataDictionary(file) - Inventory = make(map[string]*InventoryRecord) - - for d.Next() { - // we need to calc the width/height for the box as it isn't - // specified in the txt file - pBox := &box{} - pBox.Left = d.Number("invLeft") - pBox.Right = d.Number("invRight") - pBox.Top = d.Number("invTop") - pBox.Bottom = d.Number("invBottom") - pBox.Width = pBox.Right - pBox.Left - pBox.Height = pBox.Bottom - pBox.Top - - gBox := &box{ - Left: d.Number("gridLeft"), - Right: d.Number("gridRight"), - Top: d.Number("gridTop"), - Bottom: d.Number("gridBottom"), - } - gBox.Width = gBox.Right - gBox.Left - gBox.Height = gBox.Bottom - gBox.Top - - record := &InventoryRecord{ - Name: d.String("class"), - Panel: pBox, - Grid: &grid{ - Box: gBox, - Rows: d.Number("gridY"), - Columns: d.Number("gridX"), - CellWidth: d.Number("gridBoxWidth"), - CellHeight: d.Number("gridBoxHeight"), - }, - Slots: map[d2enum.EquippedSlot]*box{ - d2enum.EquippedSlotHead: { - d.Number("headLeft"), - d.Number("headRight"), - d.Number("headTop"), - d.Number("headBottom"), - d.Number("headWidth"), - d.Number("headHeight"), - }, - d2enum.EquippedSlotNeck: { - d.Number("neckLeft"), - d.Number("neckRight"), - d.Number("neckTop"), - d.Number("neckBottom"), - d.Number("neckWidth"), - d.Number("neckHeight"), - }, - d2enum.EquippedSlotTorso: { - d.Number("torsoLeft"), - d.Number("torsoRight"), - d.Number("torsoTop"), - d.Number("torsoBottom"), - d.Number("torsoWidth"), - d.Number("torsoHeight"), - }, - d2enum.EquippedSlotLeftArm: { - d.Number("lArmLeft"), - d.Number("lArmRight"), - d.Number("lArmTop"), - d.Number("lArmBottom"), - d.Number("lArmWidth"), - d.Number("lArmHeight"), - }, - d2enum.EquippedSlotRightArm: { - d.Number("rArmLeft"), - d.Number("rArmRight"), - d.Number("rArmTop"), - d.Number("rArmBottom"), - d.Number("rArmWidth"), - d.Number("rArmHeight"), - }, - d2enum.EquippedSlotLeftHand: { - d.Number("lHandLeft"), - d.Number("lHandRight"), - d.Number("lHandTop"), - d.Number("lHandBottom"), - d.Number("lHandWidth"), - d.Number("lHandHeight"), - }, - d2enum.EquippedSlotRightHand: { - d.Number("rHandLeft"), - d.Number("rHandRight"), - d.Number("rHandTop"), - d.Number("rHandBottom"), - d.Number("rHandWidth"), - d.Number("rHandHeight"), - }, - d2enum.EquippedSlotGloves: { - d.Number("glovesLeft"), - d.Number("glovesRight"), - d.Number("glovesTop"), - d.Number("glovesBottom"), - d.Number("glovesWidth"), - d.Number("glovesHeight"), - }, - d2enum.EquippedSlotBelt: { - d.Number("beltLeft"), - d.Number("beltRight"), - d.Number("beltTop"), - d.Number("beltBottom"), - d.Number("beltWidth"), - d.Number("beltHeight"), - }, - d2enum.EquippedSlotLegs: { - d.Number("feetLeft"), - d.Number("feetRight"), - d.Number("feetTop"), - d.Number("feetBottom"), - d.Number("feetWidth"), - d.Number("feetHeight"), - }, - }, - } - - Inventory[record.Name] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d Inventory Panel records", len(Inventory)) -} diff --git a/d2common/d2data/d2datadict/item_affix.go b/d2common/d2data/d2datadict/item_affix.go deleted file mode 100644 index 1d2b1c89..00000000 --- a/d2common/d2data/d2datadict/item_affix.go +++ /dev/null @@ -1,231 +0,0 @@ -package d2datadict - -import ( - "fmt" - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// MagicPrefix stores all of the magic prefix records -// nolint:gochecknoglobals // Currently global by design -var MagicPrefix map[string]*ItemAffixCommonRecord - -// MagicSuffix stores all of the magic suffix records -// nolint:gochecknoglobals // Currently global by design -var MagicSuffix map[string]*ItemAffixCommonRecord - -// LoadMagicPrefix loads MagicPrefix.txt -func LoadMagicPrefix(file []byte) { - superType := d2enum.ItemAffixPrefix - - subType := d2enum.ItemAffixMagic - - MagicPrefix = loadDictionary(file, superType, subType) -} - -// LoadMagicSuffix loads MagicSuffix.txt -func LoadMagicSuffix(file []byte) { - superType := d2enum.ItemAffixSuffix - - subType := d2enum.ItemAffixMagic - - MagicSuffix = loadDictionary(file, superType, subType) -} - -func getAffixString(t1 d2enum.ItemAffixSuperType, t2 d2enum.ItemAffixSubType) string { - var name = "" - - if t2 == d2enum.ItemAffixMagic { - name = "Magic" - } - - switch t1 { - case d2enum.ItemAffixPrefix: - name += "Prefix" - case d2enum.ItemAffixSuffix: - name += "Suffix" - } - - return name -} - -func loadDictionary( - file []byte, - superType d2enum.ItemAffixSuperType, - subType d2enum.ItemAffixSubType, -) map[string]*ItemAffixCommonRecord { - d := d2txt.LoadDataDictionary(file) - records := createItemAffixRecords(d, superType, subType) - name := getAffixString(superType, subType) - log.Printf("Loaded %d %s records", len(records), name) - - return records -} - -func createItemAffixRecords( - d *d2txt.DataDictionary, - superType d2enum.ItemAffixSuperType, - subType d2enum.ItemAffixSubType, -) map[string]*ItemAffixCommonRecord { - records := make(map[string]*ItemAffixCommonRecord) - - for d.Next() { - affix := &ItemAffixCommonRecord{ - Name: d.String("Name"), - Version: d.Number("version"), - Type: subType, - IsPrefix: superType == d2enum.ItemAffixPrefix, - IsSuffix: superType == d2enum.ItemAffixSuffix, - 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 (Code references with parameters to be eval'd) - for i := 1; i <= 3; i++ { - codeKey := fmt.Sprintf("mod%dcode", i) - paramKey := fmt.Sprintf("mod%dparam", i) - minKey := fmt.Sprintf("mod%dmin", i) - maxKey := fmt.Sprintf("mod%dmax", i) - modifier := &ItemAffixCommonModifier{ - Code: d.String(codeKey), - Parameter: d.Number(paramKey), - Min: d.Number(minKey), - Max: d.Number(maxKey), - } - affix.Modifiers = append(affix.Modifiers, modifier) - } - - // items to include for spawning - for i := 1; i <= 7; i++ { - itemKey := fmt.Sprintf("itype%d", i) - 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.String(itemKey) - affix.ItemExclude = append(affix.ItemExclude, itemToken) - } - - // affix groupis - if ItemAffixGroups == nil { - ItemAffixGroups = make(map[int]*ItemAffixCommonGroup) - } - - if _, found := ItemAffixGroups[affix.GroupID]; !found { - ItemAffixGroup := &ItemAffixCommonGroup{} - ItemAffixGroup.ID = affix.GroupID - ItemAffixGroups[affix.GroupID] = ItemAffixGroup - } - - group := ItemAffixGroups[affix.GroupID] - group.addMember(affix) - - records[affix.Name] = affix - } - - if d.Err != nil { - panic(d.Err) - } - - return records -} - -// ItemAffixGroups are groups of MagicPrefix/Suffixes -var ItemAffixGroups map[int]*ItemAffixCommonGroup //nolint:gochecknoglobals // Currently global by design - -// ItemAffixCommonGroup is a grouping that is common between prefix/suffix -type ItemAffixCommonGroup struct { - ID int - Members map[string]*ItemAffixCommonRecord -} - -func (g *ItemAffixCommonGroup) addMember(a *ItemAffixCommonRecord) { - if g.Members == nil { - g.Members = make(map[string]*ItemAffixCommonRecord) - } - - g.Members[a.Name] = a -} - -func (g *ItemAffixCommonGroup) getTotalFrequency() int { - total := 0 - - for _, affix := range g.Members { - total += affix.Frequency - } - - return total -} - -// ItemAffixCommonModifier is the generic modifier form that prefix/suffix shares -// modifiers are like dynamic properties, they have a key that points to a property -// a parameter for the property, and a min/max value -type ItemAffixCommonModifier struct { - Code string - Parameter int - Min int - Max int -} - -// ItemAffixCommonRecord is a common definition that both prefix and suffix use -type ItemAffixCommonRecord struct { - Group *ItemAffixCommonGroup - Modifiers []*ItemAffixCommonModifier - - ItemInclude []string - ItemExclude []string - - Name string - Class string - TransformColor string - - Version int - Type d2enum.ItemAffixSubType - - Level int - MaxLevel int - - LevelReq int - ClassLevelReq int - - Frequency int - GroupID int - - PriceAdd int - PriceScale int - - IsPrefix bool - IsSuffix bool - - Spawnable bool - Rare bool - Transform bool -} - -// ProbabilityToSpawn returns the chance of the affix spawning on an -// item with a given quality level -func (a *ItemAffixCommonRecord) ProbabilityToSpawn(qlvl int) float64 { - if (qlvl > a.MaxLevel) || (qlvl < a.Level) { - return 0 - } - - p := float64(a.Frequency) / float64(a.Group.getTotalFrequency()) - - return p -} diff --git a/d2common/d2data/d2datadict/item_common.go b/d2common/d2data/d2datadict/item_common.go deleted file mode 100644 index ef053502..00000000 --- a/d2common/d2data/d2datadict/item_common.go +++ /dev/null @@ -1,398 +0,0 @@ -package d2datadict - -import ( - "strconv" - "strings" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" -) - -// ItemCommonRecord is a representation of entries from armor.txt, weapons.txt, and misc.txt -type ItemCommonRecord struct { - UsageStats [3]ItemUsageStat // stat boosts applied upon usage - CureOverlayStates [2]string // name of the overlay states that are removed upon use of this item - OverlayState string // name of the overlay state to be applied upon use of this item - SpellDescriptionString string // points to a string containing the description - BetterGem string // 3 char code pointing to the gem this upgrades to (non if not applicable) - SpellDescriptionCalc d2calculation.CalcString // a calc string what value to display - WeaponClass string // what kind of attack does this weapon have (i.e. determines attack animations) - WeaponClass2Hand string // what kind of attack when wielded with two hands - HitClass string // determines sounds/graphic effects when attacking - SpecialFeature string // Just a comment - FlavorText string // unknown, probably just for reference - TransmogCode string // the 3 char code representing the item this becomes via transmog - NightmareUpgrade string // upgraded in higher difficulties - HellUpgrade string - SourceArt string // unused? - GameArt string // unused? - Vendors map[string]*ItemVendorParams // controls vendor settings - Type string // base type in ItemTypes.txt - Type2 string - DropSound string // sfx for dropping - UseSound string // sfx for using - FlippyFile string // DC6 file animation to play when item drops on the ground - InventoryFile string // DC6 file used in your inventory - UniqueInventoryFile string // DC6 file used by the unique version of this item - SetInventoryFile string // DC6 file used by the set version of this item - Code string // identifies the item - NameString string // seems to be identical to code? - AlternateGfx string // code of the DCC used when equipped - OpenBetaGfx string // unknown - NormalCode string - UberCode string - UltraCode string - Name string - Source d2enum.InventoryItemType - - Version int // 0 = classic, 100 = expansion - Rarity int // higher, the rarer - MinAC int - MaxAC int - Absorbs int // unused? - Speed int // affects movement speed of wielder, >0 = you move slower, <0 = you move faster - RequiredStrength int - Block int // chance to block, capped at 75% - Durability int // base durability 0-255 - Level int // base item level (controls monster drops, for instance a lv20 monster cannot drop a lv30 item) - RequiredLevel int // required level to wield - Cost int // base cost - GambleCost int // for reference only, not used - MagicLevel int // additional magic level (for gambling?) - AutoPrefix int // prefix automatically assigned to this item on spawn, maps to group column of Automagic.txt - SpellOffset int // unknown - Component int // corresponds to Composit.txt, player animation layer used by this - InventoryWidth int - InventoryHeight int - GemSockets int // number of gems to store - GemApplyType int // what kind of gem effect is applied - // 0 = weapon, 1= armor or helmet, 2 = shield - - // these represent how player animations and graphics change upon wearing this - // these come from ArmType.txt - AnimRightArm int - AnimLeftArm int - AnimTorso int - AnimLegs int - AnimRightShoulderPad int - AnimLeftShoulderPad int - - MinStack int // min size of stack when item is spawned, used if stackable - MaxStack int // max size of stack when item is spawned - DropSfxFrame int // what frame of drop animation the sfx triggers on - TransTable int // unknown, related to blending mode? - LightRadius int // apparently unused - Quest int // indicates that this item belongs to a given quest? - MissileType int // missile gfx for throwing - DurabilityWarning int // controls what warning icon appears when durability is low - QuantityWarning int // controls at what quantity the low quantity warning appears - MinDamage int - MaxDamage int - StrengthBonus int - DexterityBonus int - // final mindam = min * str / strbonus + min * dex / dexbonus - // same for maxdam - - GemOffset int // unknown - BitField1 int // 1 = leather item, 3 = metal - ColorTransform int // colormap to use for player's gfx - InventoryColorTransform int // colormap to use for inventory's gfx - Min2HandDamage int - Max2HandDamage int - MinMissileDamage int // ranged damage stats - MaxMissileDamage int - MissileSpeed int // unknown, affects movement speed of wielder during ranged attacks? - ExtraRange int // base range = 1, if this is non-zero add this to the range - // final mindam = min * str / strbonus + min * dex / dexbonus - // same for maxdam - RequiredDexterity int - SpawnStack int // unknown, something to do with stack size when spawned (sold maybe?) - TransmogMin int // min amount of the transmog item to create - TransmogMax int // max '' - SpellIcon int // which icon to display when used? Is this always -1? - SpellType int // determines what kind of function is used when you use this item - EffectLength int // timer for timed usage effects - SpellDescriptionType int // specifies how to format the usage description - // 0 = none, 1 = use desc string, 2 = use desc string + calc value - - AutoBelt bool // if true, item is put into your belt when picked up - HasInventory bool // if true, item can store gems or runes - CompactSave bool // if true, doesn't store any stats upon saving - Spawnable bool // if 0, cannot spawn in shops - NoDurability bool // if true, item has no durability - Useable bool // can be used via right click if true - // game knows what to do if used by item code - Throwable bool - Stackable bool // can be stacked in inventory - Unique bool // if true, only spawns as unique - Transparent bool // unused - Quivered bool // if true, requires ammo to use - Belt bool // tells what kind of belt this item is - SkipName bool // if true, don't include the base name in the item description - Nameable bool // if true, item can be personalized - BarbOneOrTwoHanded bool // if true, barb can wield this in one or two hands - UsesTwoHands bool // if true, it's a 2handed weapon - QuestDifficultyCheck bool // if true, item only works in the difficulty it was found in - PermStoreItem bool // if true, vendor will always sell this - Transmogrify bool // if true, can be turned into another item via right click - Multibuy bool // if true, when you buy via right click + shift it will fill your belt automatically -} - -// ItemUsageStat the stat that gets applied when the item is used -type ItemUsageStat struct { - Stat string // name of the stat to add to - Calc d2calculation.CalcString // calc string representing the amount to add -} - -// ItemVendorParams are parameters that vendors use -type ItemVendorParams struct { - Min int // minimum of this item they can stock - Max int // max they can stock - MagicMin int - MagicMax int - MagicLevel uint8 -} - -// CommonItems stores all ItemCommonRecords -var CommonItems map[string]*ItemCommonRecord //nolint:gochecknoglobals // Currently global by design - -// LoadCommonItems loads armor/weapons/misc.txt ItemCommonRecords -func LoadCommonItems(file []byte, source d2enum.InventoryItemType) map[string]*ItemCommonRecord { - if CommonItems == nil { - CommonItems = make(map[string]*ItemCommonRecord) - } - - items := make(map[string]*ItemCommonRecord) - data := strings.Split(string(file), "\r\n") - mapping := mapHeaders(data[0]) - - for lineno, line := range data { - if lineno == 0 { - continue - } - - if line == "" { - continue - } - - rec := createCommonItemRecord(line, mapping, source) - - if rec.Name == expansion { - continue - } - - items[rec.Code] = &rec - CommonItems[rec.Code] = &rec - } - - return items -} - -//nolint:funlen // Makes no sens to split -func createCommonItemRecord(line string, mapping map[string]int, source d2enum.InventoryItemType) ItemCommonRecord { - r := strings.Split(line, "\t") - result := ItemCommonRecord{ - Source: source, - - Name: mapLoadString(&r, mapping, "name"), - - Version: mapLoadInt(&r, mapping, "version"), - CompactSave: mapLoadBool(&r, mapping, "compactsave"), - Rarity: mapLoadInt(&r, mapping, "rarity"), - Spawnable: mapLoadBool(&r, mapping, "spawnable"), - - MinAC: mapLoadInt(&r, mapping, "minac"), - MaxAC: mapLoadInt(&r, mapping, "maxac"), - Absorbs: mapLoadInt(&r, mapping, "absorbs"), - Speed: mapLoadInt(&r, mapping, "speed"), - RequiredStrength: mapLoadInt(&r, mapping, "reqstr"), - Block: mapLoadInt(&r, mapping, "block"), - Durability: mapLoadInt(&r, mapping, "durability"), - NoDurability: mapLoadBool(&r, mapping, "nodurability"), - - Level: mapLoadInt(&r, mapping, "level"), - RequiredLevel: mapLoadInt(&r, mapping, "levelreq"), - Cost: mapLoadInt(&r, mapping, "cost"), - GambleCost: mapLoadInt(&r, mapping, "gamble cost"), - Code: mapLoadString(&r, mapping, "code"), - NameString: mapLoadString(&r, mapping, "namestr"), - MagicLevel: mapLoadInt(&r, mapping, "magic lvl"), - AutoPrefix: mapLoadInt(&r, mapping, "auto prefix"), - - AlternateGfx: mapLoadString(&r, mapping, "alternategfx"), - OpenBetaGfx: mapLoadString(&r, mapping, "OpenBetaGfx"), - NormalCode: mapLoadString(&r, mapping, "normcode"), - UberCode: mapLoadString(&r, mapping, "ubercode"), - UltraCode: mapLoadString(&r, mapping, "ultracode"), - - SpellOffset: mapLoadInt(&r, mapping, "spelloffset"), - - Component: mapLoadInt(&r, mapping, "component"), - InventoryWidth: mapLoadInt(&r, mapping, "invwidth"), - InventoryHeight: mapLoadInt(&r, mapping, "invheight"), - HasInventory: mapLoadBool(&r, mapping, "hasinv"), - GemSockets: mapLoadInt(&r, mapping, "gemsockets"), - GemApplyType: mapLoadInt(&r, mapping, "gemapplytype"), - - FlippyFile: mapLoadString(&r, mapping, "flippyfile"), - InventoryFile: mapLoadString(&r, mapping, "invfile"), - UniqueInventoryFile: mapLoadString(&r, mapping, "uniqueinvfile"), - SetInventoryFile: mapLoadString(&r, mapping, "setinvfile"), - - AnimRightArm: mapLoadInt(&r, mapping, "rArm"), - AnimLeftArm: mapLoadInt(&r, mapping, "lArm"), - AnimTorso: mapLoadInt(&r, mapping, "Torso"), - AnimLegs: mapLoadInt(&r, mapping, "Legs"), - AnimRightShoulderPad: mapLoadInt(&r, mapping, "rSPad"), - AnimLeftShoulderPad: mapLoadInt(&r, mapping, "lSPad"), - - Useable: mapLoadBool(&r, mapping, "useable"), - - Throwable: mapLoadBool(&r, mapping, "throwable"), - Stackable: mapLoadBool(&r, mapping, "stackable"), - MinStack: mapLoadInt(&r, mapping, "minstack"), - MaxStack: mapLoadInt(&r, mapping, "maxstack"), - - Type: mapLoadString(&r, mapping, "type"), - Type2: mapLoadString(&r, mapping, "type2"), - - DropSound: mapLoadString(&r, mapping, "dropsound"), - DropSfxFrame: mapLoadInt(&r, mapping, "dropsfxframe"), - UseSound: mapLoadString(&r, mapping, "usesound"), - - Unique: mapLoadBool(&r, mapping, "unique"), - Transparent: mapLoadBool(&r, mapping, "transparent"), - TransTable: mapLoadInt(&r, mapping, "transtbl"), - Quivered: mapLoadBool(&r, mapping, "quivered"), - LightRadius: mapLoadInt(&r, mapping, "lightradius"), - Belt: mapLoadBool(&r, mapping, "belt"), - - Quest: mapLoadInt(&r, mapping, "quest"), - - MissileType: mapLoadInt(&r, mapping, "missiletype"), - DurabilityWarning: mapLoadInt(&r, mapping, "durwarning"), - QuantityWarning: mapLoadInt(&r, mapping, "qntwarning"), - - MinDamage: mapLoadInt(&r, mapping, "mindam"), - MaxDamage: mapLoadInt(&r, mapping, "maxdam"), - StrengthBonus: mapLoadInt(&r, mapping, "StrBonus"), - DexterityBonus: mapLoadInt(&r, mapping, "DexBonus"), - - GemOffset: mapLoadInt(&r, mapping, "gemoffset"), - BitField1: mapLoadInt(&r, mapping, "bitfield1"), - - Vendors: createItemVendorParams(&r, mapping), - - SourceArt: mapLoadString(&r, mapping, "Source Art"), - GameArt: mapLoadString(&r, mapping, "Game Art"), - ColorTransform: mapLoadInt(&r, mapping, "Transform"), - InventoryColorTransform: mapLoadInt(&r, mapping, "InvTrans"), - - SkipName: mapLoadBool(&r, mapping, "SkipName"), - NightmareUpgrade: mapLoadString(&r, mapping, "NightmareUpgrade"), - HellUpgrade: mapLoadString(&r, mapping, "HellUpgrade"), - - Nameable: mapLoadBool(&r, mapping, "Nameable"), - - // weapon params - BarbOneOrTwoHanded: mapLoadBool(&r, mapping, "1or2handed"), - UsesTwoHands: mapLoadBool(&r, mapping, "2handed"), - Min2HandDamage: mapLoadInt(&r, mapping, "2handmindam"), - Max2HandDamage: mapLoadInt(&r, mapping, "2handmaxdam"), - MinMissileDamage: mapLoadInt(&r, mapping, "minmisdam"), - MaxMissileDamage: mapLoadInt(&r, mapping, "maxmisdam"), - MissileSpeed: mapLoadInt(&r, mapping, "misspeed"), - ExtraRange: mapLoadInt(&r, mapping, "rangeadder"), - - RequiredDexterity: mapLoadInt(&r, mapping, "reqdex"), - - WeaponClass: mapLoadString(&r, mapping, "wclass"), - WeaponClass2Hand: mapLoadString(&r, mapping, "2handedwclass"), - - HitClass: mapLoadString(&r, mapping, "hit class"), - SpawnStack: mapLoadInt(&r, mapping, "spawnstack"), - - SpecialFeature: mapLoadString(&r, mapping, "special"), - - QuestDifficultyCheck: mapLoadBool(&r, mapping, "questdiffcheck"), - - PermStoreItem: mapLoadBool(&r, mapping, "PermStoreItem"), - - // misc params - FlavorText: mapLoadString(&r, mapping, "szFlavorText"), - - Transmogrify: mapLoadBool(&r, mapping, "Transmogrify"), - TransmogCode: mapLoadString(&r, mapping, "TMogType"), - TransmogMin: mapLoadInt(&r, mapping, "TMogMin"), - TransmogMax: mapLoadInt(&r, mapping, "TMogMax"), - - AutoBelt: mapLoadBool(&r, mapping, "autobelt"), - - SpellIcon: mapLoadInt(&r, mapping, "spellicon"), - SpellType: mapLoadInt(&r, mapping, "pSpell"), - OverlayState: mapLoadString(&r, mapping, "state"), - CureOverlayStates: [2]string{ - mapLoadString(&r, mapping, "cstate1"), - mapLoadString(&r, mapping, "cstate2"), - }, - EffectLength: mapLoadInt(&r, mapping, "len"), - UsageStats: createItemUsageStats(&r, mapping), - - SpellDescriptionType: mapLoadInt(&r, mapping, "spelldesc"), - // 0 = none, 1 = use desc string, 2 = use desc string + calc value - SpellDescriptionString: mapLoadString(&r, mapping, "spelldescstr"), - SpellDescriptionCalc: d2calculation.CalcString(mapLoadString(&r, mapping, "spelldesccalc")), - - BetterGem: mapLoadString(&r, mapping, "BetterGem"), - - Multibuy: mapLoadBool(&r, mapping, "multibuy"), - } - - return result -} - -func createItemVendorParams(r *[]string, mapping map[string]int) map[string]*ItemVendorParams { - vs := make([]string, 17) - vs[0] = "Charsi" - vs[1] = "Gheed" - vs[2] = "Akara" - vs[3] = "Fara" - vs[4] = "Lysander" - vs[5] = "Drognan" - vs[6] = "Hralti" - vs[7] = "Alkor" - vs[8] = "Ormus" - vs[9] = "Elzix" - vs[10] = "Asheara" - vs[11] = "Cain" - vs[12] = "Halbu" - vs[13] = "Jamella" - vs[14] = "Larzuk" - vs[15] = "Malah" - vs[16] = "Drehya" - - result := make(map[string]*ItemVendorParams) - - for _, name := range vs { - wvp := ItemVendorParams{ - Min: mapLoadInt(r, mapping, name+"Min"), - Max: mapLoadInt(r, mapping, name+"Max"), - MagicMin: mapLoadInt(r, mapping, name+"MagicMin"), - MagicMax: mapLoadInt(r, mapping, name+"MagicMax"), - MagicLevel: mapLoadUint8(r, mapping, name+"MagicLvl"), - } - result[name] = &wvp - } - - return result -} - -func createItemUsageStats(r *[]string, mapping map[string]int) [3]ItemUsageStat { - result := [3]ItemUsageStat{} - for i := 0; i < 3; i++ { - result[i].Stat = mapLoadString(r, mapping, "stat"+strconv.Itoa(i)) - result[i].Calc = d2calculation.CalcString(mapLoadString(r, mapping, "calc"+strconv.Itoa(i))) - } - - return result -} diff --git a/d2common/d2data/d2datadict/item_ratio.go b/d2common/d2data/d2datadict/item_ratio.go deleted file mode 100644 index 0c47c2f9..00000000 --- a/d2common/d2data/d2datadict/item_ratio.go +++ /dev/null @@ -1,91 +0,0 @@ -package d2datadict - -import ( - "log" - "strconv" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// A helper type for item drop calculation -type dropRatioInfo struct { - frequency int - divisor int - divisorMin int -} - -// ItemRatioRecord encapsulates information found in ItemRatio.txt, it specifies drop ratios -// for various types of items -// The information has been gathered from [https://d2mods.info/forum/kb/viewarticle?a=387] -type ItemRatioRecord struct { - Function string - // 0 for classic, 1 for LoD - Version bool - - // 0 for normal, 1 for exceptional - Uber bool - ClassSpecific bool - - // All following fields are used in item drop calculation - UniqueDropInfo dropRatioInfo - RareDropInfo dropRatioInfo - SetDropInfo dropRatioInfo - MagicDropInfo dropRatioInfo - HiQualityDropInfo dropRatioInfo - NormalDropInfo dropRatioInfo -} - -// ItemRatios holds all of the ItemRatioRecords from ItemRatio.txt -var ItemRatios map[string]*ItemRatioRecord //nolint:gochecknoglobals // Currently global by design - -// LoadItemRatios loads all of the ItemRatioRecords from ItemRatio.txt -func LoadItemRatios(file []byte) { - ItemRatios = make(map[string]*ItemRatioRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &ItemRatioRecord{ - Function: d.String("Function"), - Version: d.Bool("Version"), - Uber: d.Bool("Uber"), - ClassSpecific: d.Bool("Class Specific"), - UniqueDropInfo: dropRatioInfo{ - frequency: d.Number("Unique"), - divisor: d.Number("UniqueDivisor"), - divisorMin: d.Number("UniqueMin"), - }, - RareDropInfo: dropRatioInfo{ - frequency: d.Number("Rare"), - divisor: d.Number("RareDivisor"), - divisorMin: d.Number("RareMin"), - }, - SetDropInfo: dropRatioInfo{ - frequency: d.Number("Set"), - divisor: d.Number("SetDivisor"), - divisorMin: d.Number("SetMin"), - }, - MagicDropInfo: dropRatioInfo{ - frequency: d.Number("Magic"), - divisor: d.Number("MagicDivisor"), - divisorMin: d.Number("MagicMin"), - }, - HiQualityDropInfo: dropRatioInfo{ - frequency: d.Number("HiQuality"), - divisor: d.Number("HiQualityDivisor"), - divisorMin: 0, - }, - NormalDropInfo: dropRatioInfo{ - frequency: d.Number("Normal"), - divisor: d.Number("NormalDivisor"), - divisorMin: 0, - }, - } - ItemRatios[record.Function+strconv.FormatBool(record.Version)] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d ItemRatio records", len(ItemRatios)) -} diff --git a/d2common/d2data/d2datadict/item_types.go b/d2common/d2data/d2datadict/item_types.go deleted file mode 100644 index f7c839e9..00000000 --- a/d2common/d2data/d2datadict/item_types.go +++ /dev/null @@ -1,357 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// ItemTypeRecord describes the types for items -type ItemTypeRecord struct { - // Name (ItemType) - // A comment field that contains the “internal name” of this iType, - // you can basically enter anything you wish here, - // but since you can add as many comment columns as you wish, - // there is no reason to use it for another purpose . - Name string - - // Code - // The ID pointer of this ItemType, this pointer is used in many txt files (armor.txt, - // cubemain.txt, misc.txt, skills.txt, treasureclassex.txt, weapons.txt), - // never use the same ID pointer twice, - // the game will only use the first instance and ignore all other occurrences. - // ID pointers are case sensitive, 3-4 chars long and can contain numbers, letters and symbols. - Code string - - // Equiv1-2 - // This is used to define the parent iType, note that an iType can have multiple parents ( - // as will be shown in the cladogram – link below), - // the only thing you must avoid at all cost is creating infinite loops. - // I haven't ever tested what happens when you create an iType loop, - // but infinite loops are something you should always avoid. - Equiv1 string - Equiv2 string - - // Shoots - // This column specifies which type of quiver (“ammo”) this iType ( - // in case it is a weapon) requires in order to shoot ( - // you use the ID pointer of the quiver iType here). - // Caution: The place it checks which missile to pick (either arrow, bolt, - // explosive arrow or magic arrow) is buried deep within D2Common.dll, - // the section can be modified, there is an extensive post discussing this in Code Editing. - // - Thanks go to Kingpin for spotting a silly little mistake in here. - Shoots string - - // Quiver - // The equivalent to the previous column, - // in here you specify which weapon this quiver is linked to. Make sure the two columns match. ( - // this also uses the ID pointer of course). - Quiver string - - // InvGfx1-6 - // This column contains the file names of the inventory graphics that are randomly picked for - // this iType, so if you use columns 1-3, you will set VarInvGfx to 3 (duh). - InvGfx1 string - InvGfx2 string - InvGfx3 string - InvGfx4 string - InvGfx5 string - InvGfx6 string - - // StorePage - // The page code for the page a vendor should place this iType in when sold, - // if you enable the magic tab in D2Client.dll, - // you need to use the proper code here to put items in that tab. - // Right now the ones used are weap = weapons1 and 2, armo = armor and misc = miscellaneous. - StorePage string - - // BodyLoc1-2 - // If you have set the previous column to 1, - // you need to specify the inventory slots in which the item has to be equipped. ( - // the codes used by this field are read from BodyLocs.txt) - BodyLoc1 int - BodyLoc2 int - - // MaxSock1, MaxSock25, MaxSock40 - // Maximum sockets for iLvl 1-25, - // 26-40 and 40+. The range is hardcoded but the location is known, - // so you can alter around the range to your liking. On normal, - // items dropped from monsters are limited to 3, on nightmare to 4 and on hell to 6 sockets, - // irregardless of this columns content. - MaxSock1 int - MaxSock25 int - MaxSock40 int - - // TreasureClass - // Can this iType ID Pointer be used as an auto TC in TreasureClassEx.txt. 1=Yes, - // 0=No. *Such as armo3-99 and weap3-99 etc. - TreasureClass int - - // Rarity - // Dunno what it does, may have to do with the chance that an armor or weapon rack will pick - // items of this iType. If it works like other rarity fields, - // the chance is rarity / total_rarity * 100. - Rarity int - - // StaffMods - // Contains the class code for the character class that should get +skills from this iType ( - // such as wands that can spawn with +Necromancer skills). Note, - // this only works if the item is not low quality, set or unique. Note, - // that this uses the vanilla min/max skill IDs for each class as the range for the skill pool, - // so if you add new class skills to the end of the file, you should use automagic.txt instead - StaffMods d2enum.Hero - - // CostFormula - // Does the game generate the sell/repair/buy prices of this iType based on its modifiers or does - // it use only the cost specific in the respective item txt files. 2=Organ ( - // probably higher price based on unit that dropped the organ), 1=Yes, 0=No. - // Note: Only applies to items that are not unique or set, for those the price is solely controlled - // by the base item file and by the bonus to price given in SetItems and UniqueItems txt files. - // The exact functionality remains unknown, as for example charms, have this disabled. - CostFormula int - - // Class - // Contains the class code for the class that should be able to use this iType ( - // for class specific items). - Class d2enum.Hero - - // VarInvGfx - // This column contains the sum of randomly picked inventory graphics this iType can have. - VarInvGfx int - - // Repair - // Boolean, 1=Merchants can repair this item type, 0=Merchants cannot repair this iType (note, - // this also refers to charges being rechargeable). - Repair bool - - // Body - // Boolean, 1=The character can wear this iType, - // 0=This iType can only be carried in the inventory, - // cube or stash (and belt if it is set as “beltable” in the other item related txt files) - Body bool - - // Throwable - // Can this iType be thrown (determines whenever it uses the quantity and throwing damage columns - // in Weapons.txt for example). - Throwable bool - - // Reload - // Can the this item be re-stacked via drag and drop. 1=Yes, 0=No. - Reload bool - - // ReEquip - // If the ammo runs out the game will automatically pick the next item of the same iType to - // be equipped in it's place. - // 1=Yes, 0=No. (more clearly, when you use up all the arrows in a quiver, the next quiver, - // if available, will be equipped in its place). - ReEquip bool - - // AutoStack - // Are identical stacks automatically combined when you pick the up? 1=Yes, 0=No. (for example, - // which you pick up throwing potions or normal javelins, - // they are automatically combined with those you already have) - AutoStack bool - - // Magic - // Is this iType always Magic? 1=Yes, 0=No. - Magic bool - - // Rare - // Can this iType spawn as a rare item? - // 1=Yes, 0=No. - // Note: If you want an item that spawns only as magic or rare, - // you need to set the previous column to 1 as well. - Rare bool - - // Normal - // Is this iType always Normal? 1=Yes, 0=No. - Normal bool - - // Charm - // Does this iType function as a charm? 1=Yes, 0=No. Note: This effect is hardcoded, - // if you need a new charm type, you must use the char iType in one of the equivs. - Charm bool - - // Gem - // Can this iType be inserted into sockets? 1=Yes, - // 0=No (Link your item to the sock iType instead to achieve this). - Gem bool - - // Beltable - // Can this iType be placed in your characters belt slots? 1=Yes, - // 0=No. (This requires further tweaking in other txt files). - Beltable bool -} - -// ItemTypes stores all of the ItemTypeRecords -var ItemTypes map[string]*ItemTypeRecord //nolint:gochecknoglobals // Currently global by design, only written once - -// LoadItemTypes loads ItemType records -func LoadItemTypes(file []byte) { - ItemTypes = make(map[string]*ItemTypeRecord) - - charCodeMap := map[string]d2enum.Hero{ - "ama": d2enum.HeroAmazon, - "ass": d2enum.HeroAssassin, - "bar": d2enum.HeroBarbarian, - "dru": d2enum.HeroDruid, - "nec": d2enum.HeroNecromancer, - "pal": d2enum.HeroPaladin, - "sor": d2enum.HeroSorceress, - } - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - if d.String("*eol") == "" { - continue - } - - itemType := &ItemTypeRecord{ - Name: d.String("ItemType"), - Code: d.String("Code"), - Equiv1: d.String("Equiv1"), - Equiv2: d.String("Equiv2"), - Repair: d.Number("Repair") > 0, - Body: d.Number("Body") > 0, - BodyLoc1: d.Number("BodyLoc1"), - BodyLoc2: d.Number("BodyLoc2"), - Shoots: d.String("Shoots"), - Quiver: d.String("Quiver"), - Throwable: d.Number("Throwable") > 0, - Reload: d.Number("Reload") > 0, - ReEquip: d.Number("ReEquip") > 0, - AutoStack: d.Number("AutoStack") > 0, - Magic: d.Number("Magic") > 0, - Rare: d.Number("Rare") > 0, - Normal: d.Number("Normal") > 0, - Charm: d.Number("Charm") > 0, - Gem: d.Number("Gem") > 0, - Beltable: d.Number("Beltable") > 0, - MaxSock1: d.Number("MaxSock1"), - MaxSock25: d.Number("MaxSock25"), - MaxSock40: d.Number("MaxSock40"), - TreasureClass: d.Number("TreasureClass"), - Rarity: d.Number("Rarity"), - StaffMods: charCodeMap[d.String("StaffMods")], - CostFormula: d.Number("CostFormula"), - Class: charCodeMap[d.String("Class")], - VarInvGfx: d.Number("VarInvGfx"), - InvGfx1: d.String("InvGfx1"), - InvGfx2: d.String("InvGfx2"), - InvGfx3: d.String("InvGfx3"), - InvGfx4: d.String("InvGfx4"), - InvGfx5: d.String("InvGfx5"), - InvGfx6: d.String("InvGfx6"), - StorePage: d.String("StorePage"), - } - - ItemTypes[itemType.Code] = itemType - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d ItemType records", len(ItemTypes)) -} - -// ItemEquivalenciesByTypeCode describes item equivalencies for ItemTypes -var ItemEquivalenciesByTypeCode map[string][]*ItemCommonRecord //nolint:gochecknoglobals // Currently global by design - -// LoadItemEquivalencies loads a map of ItemType string codes to slices of ItemCommonRecord pointers -func LoadItemEquivalencies() { - ItemEquivalenciesByTypeCode = make(map[string][]*ItemCommonRecord) - - makeEmptyEquivalencyMaps() - - for icrCode := range CommonItems { - commonItem := CommonItems[icrCode] - updateEquivalencies(commonItem, ItemTypes[commonItem.Type], nil) - - if commonItem.Type2 != "" { // some items (like gems) have a secondary type - updateEquivalencies(commonItem, ItemTypes[commonItem.Type2], nil) - } - } -} - -func makeEmptyEquivalencyMaps() { - for typeCode := range ItemTypes { - code := []string{ - typeCode, - ItemTypes[typeCode].Equiv1, - ItemTypes[typeCode].Equiv2, - } - - for _, str := range code { - if str == "" { - continue - } - - if ItemEquivalenciesByTypeCode[str] == nil { - ItemEquivalenciesByTypeCode[str] = make([]*ItemCommonRecord, 0) - } - } - } -} - -func updateEquivalencies(icr *ItemCommonRecord, itemType *ItemTypeRecord, checked []string) { - if itemType.Code == "" { - return - } - - if checked == nil { - checked = make([]string, 0) - } - - checked = append(checked, itemType.Code) - - if !itemEquivPresent(icr, ItemEquivalenciesByTypeCode[itemType.Code]) { - ItemEquivalenciesByTypeCode[itemType.Code] = append(ItemEquivalenciesByTypeCode[itemType.Code], icr) - } - - if itemType.Equiv1 != "" { - updateEquivalencies(icr, ItemTypes[itemType.Equiv1], checked) - } - - if itemType.Equiv2 != "" { - updateEquivalencies(icr, ItemTypes[itemType.Equiv2], checked) - } -} - -func itemEquivPresent(icr *ItemCommonRecord, list []*ItemCommonRecord) bool { - for idx := range list { - if list[idx] == icr { - return true - } - } - - return false -} - -var itemCommonTypeLookup map[*ItemCommonRecord][]string //nolint:gochecknoglobals // Currently global by design - -// FindEquivalentTypesByItemCommonRecord returns itemtype codes that are equivalent -// to the given item common record -func FindEquivalentTypesByItemCommonRecord(icr *ItemCommonRecord) []string { - if itemCommonTypeLookup == nil { - itemCommonTypeLookup = make(map[*ItemCommonRecord][]string) - } - - // the first lookup generates the lookup table entry, next time will just use the table - if itemCommonTypeLookup[icr] == nil { - itemCommonTypeLookup[icr] = make([]string, 0) - - for code := range ItemEquivalenciesByTypeCode { - icrList := ItemEquivalenciesByTypeCode[code] - for idx := range icrList { - if icr == icrList[idx] { - itemCommonTypeLookup[icr] = append(itemCommonTypeLookup[icr], code) - break - } - } - } - } - - return itemCommonTypeLookup[icr] -} diff --git a/d2common/d2data/d2datadict/itemstatcost.go b/d2common/d2data/d2datadict/itemstatcost.go deleted file mode 100644 index 3d47d73c..00000000 --- a/d2common/d2data/d2datadict/itemstatcost.go +++ /dev/null @@ -1,199 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// ItemStatCostRecord represents a row from itemstatcost.txt -// these records describe the stat values and costs (in shops) of items -// refer to https://d2mods.info/forum/kb/viewarticle?a=448 -type ItemStatCostRecord struct { - Name string - OpBase string - OpStat1 string - OpStat2 string - OpStat3 string - - MaxStat string // if Direct true, will not exceed val of MaxStat - DescStrPos string // string used when val is positive - DescStrNeg string - DescStr2 string // additional string used by some string funcs - DescGroupStrPos string // string used when val is positive - DescGroupStrNeg string - DescGroupStr2 string // additional string used by some string funcs - - // Stuff - // Stay far away from this column unless you really know what you're - // doing and / or work for Blizzard, this column is used during bin-file - // creation to generate a cache regulating the op-stat stuff and other - // things, changing it can be futile, it works like the constants column - // in MonUMod.txt and has no other relation to ItemStatCost.txt, the first - // stat in the file simply must have this set or else you may break the - // entire op stuff. - Stuff string - - Index int - - // path_d2.mpq version doesnt have Ranged columne, excluding for now - // Ranged bool // game attempts to keep stat in a range, like strength >-1 - MinAccr int // minimum ranged value - - SendBits int // #bits to send in stat update - SendParam int // #bits to send in stat update - - SavedBits int // #bits allocated to the value in .d2s file - - SaveBits int // #bits saved to .d2s files, max == 2^SaveBits-1 - SaveAdd int // how large the negative range is (lowers max, as well) - SaveParamBits int // #param bits are saved (safe value is 17) - - Encode d2enum.EncodingType // how the stat is encoded in .d2s files - - // these two fields control additional cost on items - // cost * (1 + value * multiply / 1024)) + add (...) - CostAdd int - CostMultiply int - // CostDivide // exists in txt, but division hardcoded to 1024 - // if divide is used, could we do (?): - // cost * (1 + value * multiply / divide)) + add (...) - - ValShift int // controls how stat is stored in .d2s - // so that you can save `+1` instead of `+256` - - OperatorType d2enum.OperatorType - OpParam int - - EventID1 d2enum.ItemEventType - EventID2 d2enum.ItemEventType - EventFuncID1 d2enum.ItemEventFuncID - EventFuncID2 d2enum.ItemEventFuncID - - DescPriority int // determines order when displayed - DescFnID int - - // Controls whenever and if so in what way the stat value is shown - // 0 = doesn't show the value of the stat - // 1 = shows the value of the stat infront of the description - // 2 = shows the value of the stat after the description. - DescVal int - - // when stats in the same group have the same value they use the - // group func for desc (they need to be in the same affix) - DescGroup int - DescGroupVal int - DescGroupFuncID int - - CallbackEnabled bool // whether callback fn is called if value changes - Signed bool // whether the stat is signed - KeepZero bool // prevent from going negative (assume only client side) - UpdateAnimRate bool // when altered, forces speed handler to adjust speed - SendOther bool // whether to send to other clients - Saved bool // whether this stat is saved in .d2s files - SavedSigned bool // whether the stat is saved as signed/unsigned - Direct bool // whether is temporary or permanent - ItemSpecific bool // prevents stacking with an existing stat on item - // like when socketing a jewel - - DamageRelated bool // prevents stacking of stats while dual wielding -} - -// ItemStatCosts stores all of the ItemStatCostRecords -//nolint:gochecknoglobals // Currently global by design -var ItemStatCosts map[string]*ItemStatCostRecord - -// LoadItemStatCosts loads ItemStatCostRecord's from text -func LoadItemStatCosts(file []byte) { - ItemStatCosts = make(map[string]*ItemStatCostRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &ItemStatCostRecord{ - Name: d.String("Stat"), - Index: d.Number("ID"), - - Signed: d.Number("Signed") > 0, - KeepZero: d.Number("keepzero") > 0, - - // Ranged: d.Number("Ranged") > 0, - MinAccr: d.Number("MinAccr"), - - UpdateAnimRate: d.Number("UpdateAnimRate") > 0, - - SendOther: d.Number("Send Other") > 0, - SendBits: d.Number("Send Bits"), - SendParam: d.Number("Send Param Bits"), - - 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.Number("Encode")), - - CallbackEnabled: d.Number("fCallback") > 0, - - CostAdd: d.Number("Add"), - CostMultiply: d.Number("Multiply"), - ValShift: d.Number("ValShift"), - - 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.Number("direct") > 0, - MaxStat: d.String("maxstat"), - - ItemSpecific: d.Number("itemspecific") > 0, - DamageRelated: d.Number("damagerelated") > 0, - - EventID1: d2enum.GetItemEventType(d.String("itemevent1")), - EventID2: d2enum.GetItemEventType(d.String("itemevent2")), - EventFuncID1: d2enum.ItemEventFuncID(d.Number("itemeventfunc1")), - EventFuncID2: d2enum.ItemEventFuncID(d.Number("itemeventfunc2")), - - DescPriority: d.Number("descpriority"), - DescFnID: d.Number("descfunc"), - // DescVal: d.Number("descval"), // needs special handling - DescStrPos: d.String("descstrpos"), - DescStrNeg: d.String("descstrneg"), - DescStr2: d.String("descstr2"), - - DescGroup: d.Number("dgrp"), - DescGroupFuncID: d.Number("dgrpfunc"), - - DescGroupVal: d.Number("dgrpval"), - DescGroupStrPos: d.String("dgrpstrpos"), - DescGroupStrNeg: d.String("dgrpstrneg"), - DescGroupStr2: d.String("dgrpstr2"), - - Stuff: d.String("stuff"), - } - - descValStr := d.String("descval") - switch descValStr { - case "2": - record.DescVal = 2 - case "0": - record.DescVal = 0 - default: - // handle empty fields, seems like they should have been 1 - record.DescVal = 1 - } - - ItemStatCosts[record.Name] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d ItemStatCost records", len(ItemStatCosts)) -} diff --git a/d2common/d2data/d2datadict/level_maze.go b/d2common/d2data/d2datadict/level_maze.go deleted file mode 100644 index e312a6d6..00000000 --- a/d2common/d2data/d2datadict/level_maze.go +++ /dev/null @@ -1,65 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// LevelMazeDetailsRecord is a representation of a row from lvlmaze.txt -// these records define the parameters passed to the maze level generator -type LevelMazeDetailsRecord struct { - // descriptive, not loaded in game. Corresponds with Name field in - // Levels.txt - Name string // Name - - // ID from Levels.txt - // NOTE: Cave 1 is the Den of Evil, its associated treasure level is quest - // only. - LevelID int // Level - - // the minimum number of .ds1 map sections that will make up the maze in - // Normal, Nightmare and Hell difficulties. - NumRoomsNormal int // Rooms - NumRoomsNightmare int // Rooms(N) - NumRoomsHell int // Rooms(H) - - // the size in the X\Y direction of any component ds1 map section. - SizeX int // SizeX - SizeY int // SizeY - - // Possibly related to how adjacent .ds1s are connected with each other, - // but what the different values are for is unknown. - // Merge int // Merge - - // Included in the original Diablo II beta tests and in the demo version. - // Beta -} - -// LevelMazeDetails stores all of the LevelMazeDetailsRecords -var LevelMazeDetails map[int]*LevelMazeDetailsRecord //nolint:gochecknoglobals // Currently global by design - -// LoadLevelMazeDetails loads LevelMazeDetailsRecords from text file -func LoadLevelMazeDetails(file []byte) { - LevelMazeDetails = make(map[int]*LevelMazeDetailsRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &LevelMazeDetailsRecord{ - 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)) -} diff --git a/d2common/d2data/d2datadict/level_presets.go b/d2common/d2data/d2datadict/level_presets.go deleted file mode 100644 index 001416ff..00000000 --- a/d2common/d2data/d2datadict/level_presets.go +++ /dev/null @@ -1,109 +0,0 @@ -package d2datadict - -import ( - "log" - "strings" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" -) - -// LevelPresetRecord is a representation of a row from lvlprest.txt -// these records define parameters for the preset level map generator -type LevelPresetRecord struct { - Files [6]string - Name string - DefinitionID int - LevelID int - SizeX int - SizeY int - Pops int - PopPad int - FileCount int - Dt1Mask uint - Populate bool - Logicals bool - Outdoors bool - Animate bool - KillEdge bool - FillBlanks bool - AutoMap bool - Scan bool - Beta bool - Expansion bool -} - -// CreateLevelPresetRecord parses a row from lvlprest.txt into a LevelPresetRecord -func createLevelPresetRecord(props []string) LevelPresetRecord { - i := -1 - inc := func() int { - i++ - return i - } - result := LevelPresetRecord{ - Name: props[inc()], - DefinitionID: d2util.StringToInt(props[inc()]), - LevelID: d2util.StringToInt(props[inc()]), - Populate: d2util.StringToUint8(props[inc()]) == 1, - Logicals: d2util.StringToUint8(props[inc()]) == 1, - Outdoors: d2util.StringToUint8(props[inc()]) == 1, - Animate: d2util.StringToUint8(props[inc()]) == 1, - KillEdge: d2util.StringToUint8(props[inc()]) == 1, - FillBlanks: d2util.StringToUint8(props[inc()]) == 1, - SizeX: d2util.StringToInt(props[inc()]), - SizeY: d2util.StringToInt(props[inc()]), - AutoMap: d2util.StringToUint8(props[inc()]) == 1, - Scan: d2util.StringToUint8(props[inc()]) == 1, - Pops: d2util.StringToInt(props[inc()]), - PopPad: d2util.StringToInt(props[inc()]), - FileCount: d2util.StringToInt(props[inc()]), - Files: [6]string{ - props[inc()], - props[inc()], - props[inc()], - props[inc()], - props[inc()], - props[inc()], - }, - Dt1Mask: d2util.StringToUint(props[inc()]), - Beta: d2util.StringToUint8(props[inc()]) == 1, - Expansion: d2util.StringToUint8(props[inc()]) == 1, - } - - return result -} - -// LevelPresets stores all of the LevelPresetRecords -var LevelPresets map[int]LevelPresetRecord //nolint:gochecknoglobals // Currently global by design - -// LoadLevelPresets loads level presets from text file -func LoadLevelPresets(file []byte) { - LevelPresets = make(map[int]LevelPresetRecord) - data := strings.Split(string(file), "\r\n")[1:] - - for _, line := range data { - if line == "" { - continue - } - - props := strings.Split(line, "\t") - - if props[1] == "" { - continue // any line without a definition id is skipped (e.g. the "Expansion" line) - } - - rec := createLevelPresetRecord(props) - LevelPresets[rec.DefinitionID] = rec - } - - log.Printf("Loaded %d level presets", len(LevelPresets)) -} - -// LevelPreset looks up a LevelPresetRecord by ID -func LevelPreset(id int) LevelPresetRecord { - for i := 0; i < len(LevelPresets); i++ { - if LevelPresets[i].DefinitionID == id { - return LevelPresets[i] - } - } - panic("Unknown level preset") -} diff --git a/d2common/d2data/d2datadict/level_sub.go b/d2common/d2data/d2datadict/level_sub.go deleted file mode 100644 index a68ad4ca..00000000 --- a/d2common/d2data/d2datadict/level_sub.go +++ /dev/null @@ -1,109 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// LevelSubstitutionRecord is a representation of a row from lvlsub.txt -// these records are parameters for levels and describe substitution rules -type LevelSubstitutionRecord struct { - // Description, reference only. - Name string // Name - - // This value is used in Levels.txt, in the column 'SubType'. You'll notice - // that in LvlSub.txt some rows use the same value, we can say they forms - // groups. If you count each row of a group starting from 0, then you'll - // obtain what is written in Levels.txt, columns 'SubTheme', 'SubWaypoint' - // and 'SubShrine'. (added by Paul Siramy) - ID int // Type - - // What .ds1 is being used. - File string // File - - // 0 for classic, 1 for Expansion. - IsExpansion bool // Expansion - - // Unknown as all have 0. - // CheckAll - - // this field can contain values ranging from -1 to 2 - // NOTE: wall types have 0, 1 or 2, while Non-wall types have -1. - BorderType int // BordType - - // Set it to 1 or 2 I'm assuming this means a block of tiles ie: 4x4. - GridSize int // GridSize - - // For some rows, this is their place in LvlTypes.txt. The Dt1 mask also - // includes the mask for the Floor.Dt1 of that level. (see Trials0 below) - Mask int // Dt1Mask - - // The probability of the Dt1 being spawned. - ChanceSpawn0 int // Prob0 - ChanceSpawn1 int // Prob1 - ChanceSpawn2 int // Prob2 - ChanceSpawn3 int // Prob3 - ChanceSpawn4 int // Prob4 - - // This appears to be a chance of either a floor tile being spawned or the - // actual Dt1.. - ChanceFloor0 int // Trials0 - ChanceFloor1 int // Trials1 - ChanceFloor2 int // Trials2 - ChanceFloor3 int // Trials3 - ChanceFloor4 int // Trials4 - - // This appears to be how much will spawn in the Grid. - GridMax0 int // Max0 - GridMax1 int // Max1 - GridMax2 int // Max2 - GridMax3 int // Max3 - GridMax4 int // Max4 - - // Beta -} - -// LevelSubstitutions stores all of the LevelSubstitutionRecords -//nolint:gochecknoglobals // Currently global by design -var LevelSubstitutions map[int]*LevelSubstitutionRecord - -// LoadLevelSubstitutions loads lvlsub.txt and parses into records -func LoadLevelSubstitutions(file []byte) { - LevelSubstitutions = make(map[int]*LevelSubstitutionRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &LevelSubstitutionRecord{ - 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)) -} diff --git a/d2common/d2data/d2datadict/level_types.go b/d2common/d2data/d2datadict/level_types.go deleted file mode 100644 index 1b3097d5..00000000 --- a/d2common/d2data/d2datadict/level_types.go +++ /dev/null @@ -1,62 +0,0 @@ -package d2datadict - -import ( - "log" - "strings" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" -) - -// LevelTypeRecord is a representation of a row from lvltype.txt -// the fields describe what ds1 files a level uses -type LevelTypeRecord struct { - Files [32]string - Name string - ID int - Act int - Beta bool - Expansion bool -} - -// LevelTypes stores all of the LevelTypeRecords -var LevelTypes []LevelTypeRecord //nolint:gochecknoglobals // Currently global by design, - -// LoadLevelTypes loads the LevelTypeRecords -func LoadLevelTypes(file []byte) { - data := strings.Split(string(file), "\r\n")[1:] - LevelTypes = make([]LevelTypeRecord, len(data)) - - for i, j := 0, 0; i < len(data); i, j = i+1, j+1 { - idx := -1 - inc := func() int { - idx++ - return idx - } - - if data[i] == "" { - continue - } - - parts := strings.Split(data[i], "\t") - - if parts[0] == "Expansion" { - j-- - continue - } - - LevelTypes[j].Name = parts[inc()] - LevelTypes[j].ID = d2util.StringToInt(parts[inc()]) - - for fileIdx := range LevelTypes[i].Files { - LevelTypes[j].Files[fileIdx] = parts[inc()] - if LevelTypes[j].Files[fileIdx] == "0" { - LevelTypes[j].Files[fileIdx] = "" - } - } - - LevelTypes[j].Beta = parts[inc()] != "1" - LevelTypes[j].Act = d2util.StringToInt(parts[inc()]) - LevelTypes[j].Expansion = parts[inc()] != "1" - } - log.Printf("Loaded %d LevelType records", len(LevelTypes)) -} diff --git a/d2common/d2data/d2datadict/level_warp.go b/d2common/d2data/d2datadict/level_warp.go deleted file mode 100644 index f5e48049..00000000 --- a/d2common/d2data/d2datadict/level_warp.go +++ /dev/null @@ -1,56 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// LevelWarpRecord is a representation of a row from lvlwarp.txt -// it describes the warp graphics offsets and dimensions for levels -type LevelWarpRecord struct { - Name string - ID int - SelectX int - SelectY int - SelectDX int - SelectDY int - ExitWalkX int - ExitWalkY int - OffsetX int - OffsetY int - LitVersion bool - Tiles int - Direction string -} - -// LevelWarps loaded from txt records -//nolint:gochecknoglobals // Currently global by design, only written once -var LevelWarps map[int]*LevelWarpRecord - -// LoadLevelWarps loads LevelWarpRecord's from text file data -func LoadLevelWarps(file []byte) { - LevelWarps = make(map[int]*LevelWarpRecord) - - d := d2txt.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)) -} diff --git a/d2common/d2data/d2datadict/levels.go b/d2common/d2data/d2datadict/levels.go deleted file mode 100644 index 9ed0c17d..00000000 --- a/d2common/d2data/d2datadict/levels.go +++ /dev/null @@ -1,543 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// LevelDetailsRecord is a representation of a row from levels.txt -// it describes lots of things about the levels, like where they are connected, -// what kinds of monsters spawn, the level generator type, and lots of other stuff. -type LevelDetailsRecord struct { - - // Name - // This column has no function, it only serves as a comment field to make it - // easier to identify the Level name - Name string // Name <-- the corresponding column name in the txt - - // mon1-mon25 work in Normal difficulty, while nmon1-nmon25 in Nightmare and - // Hell. They tell the game which monster ID taken from MonStats.txt. - // NOTE: you need to manually add from mon11 to mon25 and from nmon11 to - // nmon25 ! - MonsterID1Normal string // mon1 - MonsterID2Normal string // mon2 - MonsterID3Normal string // mon3 - MonsterID4Normal string // mon4 - MonsterID5Normal string // mon5 - MonsterID6Normal string // mon6 - MonsterID7Normal string // mon7 - MonsterID8Normal string // mon8 - MonsterID9Normal string // mon9 - MonsterID10Normal string // mon10 - - MonsterID1Nightmare string // nmon1 - MonsterID2Nightmare string // nmon2 - MonsterID3Nightmare string // nmon3 - MonsterID4Nightmare string // nmon4 - MonsterID5Nightmare string // nmon5 - MonsterID6Nightmare string // nmon6 - MonsterID7Nightmare string // nmon7 - MonsterID8Nightmare string // nmon8 - MonsterID9Nightmare string // nmon9 - MonsterID10Nightmare string // nmon10 - - // Gravestench - adding additional fields for Hell, original txt combined - // the nighmare and hell ID's stringo the same field - MonsterID1Hell string // nmon1 - MonsterID2Hell string // nmon2 - MonsterID3Hell string // nmon3 - MonsterID4Hell string // nmon4 - MonsterID5Hell string // nmon5 - MonsterID6Hell string // nmon6 - MonsterID7Hell string // nmon7 - MonsterID8Hell string // nmon8 - MonsterID9Hell string // nmon9 - MonsterID10Hell string // nmon10 - - // Works only in normal and it tells which ID will be used for Champion and - // Random Uniques. The ID is taken from MonStats.txtOnly the first ten - // columns appear in the unmodded file. In 1.10 final, beta 1.10s and - // v1.11+ you can add the missing umon11-umon25 columns. - // NOTE: you can allow umon1-25 to also work in Nightmare and Hell by - // following this simple ASM edit - // (https://d2mods.info/forum/viewtopic.php?f=8&t=53969&p=425179&hilit=umon#p425179) - MonsterUniqueID1 string // umon1 - MonsterUniqueID2 string // umon2 - MonsterUniqueID3 string // umon3 - MonsterUniqueID4 string // umon4 - MonsterUniqueID5 string // umon5 - MonsterUniqueID6 string // umon6 - MonsterUniqueID7 string // umon7 - MonsterUniqueID8 string // umon8 - MonsterUniqueID9 string // umon9 - MonsterUniqueID10 string // umon10 - - // Critter Species 1-4. Uses the ID from monstats2.txt and only monsters - // with critter column set to 1 can spawn here. critter column is also found - // in monstats2.txt. Critters are in reality only present clientside. - MonsterCritterID1 string // cmon1 - MonsterCritterID2 string // cmon2 - MonsterCritterID3 string // cmon3 - MonsterCritterID4 string // cmon4 - - // String Code for the Display name of the Level - LevelDisplayName string // LevelName - - LevelWarpName string // LevelWarp - - // Which *.DC6 Title Image is loaded when you enter this area. this file - // MUST exist, otherwise you will crash with an exception when you enter the - // level (for all levels below the expansion row, the files must be - // present in the expension folders) - TitleImageName string // EntryFile - - // ID - // Level ID (used in columns like VIS0-7) - ID int - - // Palette is the Act Palette . Reference only - Palette int // Pal - - // Act that the Level is located in (internal enumeration ranges from 0 to 4) - Act int // Act - - // QuestFlag, QuestExpansionFlag - // Used the first one in Classic games and the latter in Expansion games , - // they set a questflag. If this flag is set, a character must have - // completed the quest associated with the flag to take a town portal to - // the area in question. A character can always use a portal to get back to - // town. - QuestFlag int // QuestFlag - QuestFlagExpansion int // QuestFlagEx - - // Each layer is an unique ID. This number is used to store each automap on - // a character. This is used by the game to remember what level the automap - // are for. - // NOTE: you need to use the extended levels plugin to be able to add - // additional layers. - AutomapIndex int // Layer - - // SizeXNormal -- SizeYHell If this is a preset area this sets the - // X size for the area. Othervise use the same value here that are used in - // lvlprest.txt to set the size for the .ds1 file. - SizeXNormal int // SizeX - SizeYNormal int // SizeY - SizeXNightmare int // SizeX(N) - SizeYNightmare int // SizeY(N) - SizeXHell int // SizeX(H) - SizeYHell int // SizeY(H) - - // They set the X\Y position in the world space - WorldOffsetX int // OffsetX - WorldOffsetY int // OffsetY - - // This set what level id's are the Depended level. - // Example: Monastery uses this field to place its entrance always at same - // location. - DependantLevelID int // Depend - - // The type of the Level (Id from lvltypes.txt) - LevelType int // LevelType - - // Controls if teleport is allowed in that level. - // 0 = Teleport not allowed - // 1 = Teleport allowed - // 2 = Teleport allowed, but not able to use teleport throu walls/objects - // (maybe for objects this is controlled by IsDoor column in objects.txt) - TeleportFlag d2enum.TeleportFlag // Teleport - - // Setting for Level Generation: You have 3 possibilities here: - // 1 Random Maze - // 2 Preset Area - // 3 Wilderness level - LevelGenerationType d2enum.LevelGenerationType // DrlgType - - // NOTE - // IDs from LvlSub.txt, which is used to randomize outdoor areas, such as - // spawning ponds in the blood moor and more stones in the Stoney Field. - // This is all changeable, the other subcolumns are explained in this post. - - // Setting Regarding the level sub-type. - // Example: 6=wilderness, 9=desert etc, -1=no subtype. - SubType int // SubType - - // Tells which subtheme a wilderness area should use. - // Themes ranges from -1 (no subtheme) to 4. - SubTheme int // SubTheme - - // Setting Regarding Waypoints - // NOTE: it does NOT control waypoint placement. - SubWaypoint int // SubWaypoint - - // Setting Regarding Shrines. - // NOTE: it does NOT control which Shrine will spawn. - SubShrine int // SubShrine - - // These fields allow linking level serverside, allowing you to travel - // through areas. The Vis must be filled in with the LevelID your level is - // linked with, but the actuall number of Vis ( 0 - 7 ) is determined by - // your actual map (the .ds1 fle). - // Example: Normally Cave levels are only using vis 0-3 and wilderness areas 4-7 . - LevelLinkID0 int // Vis0 - LevelLinkID1 int // Vis1 - LevelLinkID2 int // Vis2 - LevelLinkID3 int // Vis3 - LevelLinkID4 int // Vis4 - LevelLinkID5 int // Vis5 - LevelLinkID6 int // Vis6 - LevelLinkID7 int // Vis7 - - // This controls the visual graphics then you move the mouse pointer over - // an entrance. To show the graphics you use an ID from lvlwarp.txt and the - // behavior on the graphics is controlled by lvlwarp.txt. Your Warps must - // match your Vis. - // Example: If your level uses Vis 3,5,7 then you must also use Warp 3,5,7 . - WarpGraphicsID0 int // Warp0 - WarpGraphicsID1 int // Warp1 - WarpGraphicsID2 int // Warp2 - WarpGraphicsID3 int // Warp3 - WarpGraphicsID4 int // Warp4 - WarpGraphicsID5 int // Warp5 - WarpGraphicsID6 int // Warp6 - WarpGraphicsID7 int // Warp7 - - // These settings handle the light intensity as well as its RGB components - LightIntensity int // Intensity - Red int // Red - Green int // Green - Blue int // Blue - - // What quest is this level related to. This is the quest id (as example the - // first quest Den of Evil are set to 1, since its the first quest). - QuestID int // Quest - - // This sets the minimum distance from a VisX or WarpX location that a - // monster, object or tile can be spawned at. (also applies to waypoints and - // some preset portals). - WarpClearanceDistance int // WarpDist - - // Area Level on Normal-Nightmare-Hell in Classic and Expansion. - // It controls the item level of items that drop from chests etc. - MonsterLevelNormal int // MonLvl1 - MonsterLevelNightmare int // MonLvl2 - MonsterLevelHell int // MonLvl3 - MonsterLevelNormalEx int // MonLvl1Ex - MonsterLevelNightmareEx int // MonLvl2Ex - MonsterLevelHellEx int // MonLvl3Ex - - // This is a chance in 100000ths that a monster pack will spawn on a tile. - // The maximum chance the game allows is 10% (aka 10000) in v1.10+, - MonsterDensityNormal int // MonDen - MonsterDensityNightmare int // MonDen(N) - MonsterDensityHell int // MonDen(H) - - // Minimum - Maximum Unique and Champion Monsters Spawned in this Level. - // Whenever any spawn at all however is bound to MonDen. - MonsterUniqueMinNormal int // MonUMin - MonsterUniqueMinNightmare int // MonUMin(N) - MonsterUniqueMinHell int // MonUMin(H) - - MonsterUniqueMaxNormal int // MonUMax - MonsterUniqueMaxNightmare int // MonUMax(N) - MonsterUniqueMaxHell int // MonUMax(H) - - // Number of different Monster Types that will be present in this area, the - // maximum is 13. You can have up to 13 different monster types at a time in - // Nightmare and Hell difficulties, selected randomly from nmon1-nmon25. In - // Normal difficulty you can have up to 13 normal monster types selected - // randomly from mon1-mon25, and the same number of champion and unique - // types selected randomly from umon1-umon25. - NumMonsterTypes int // NumMon - - // Controls the chance for a critter to spawn. - MonsterCritter1SpawnChance int // cpct1 - MonsterCritter2SpawnChance int // cpct2 - MonsterCritter3SpawnChance int // cpct3 - MonsterCritter4SpawnChance int // cpct4 - - // Referes to a entry in SoundEnviron.txt (for the Levels Music) - SoundEnvironmentID int // SoundEnv - - // 255 means no Waipoint for this level, while others state the Waypoint' ID - // for the level - // NOTE: you can switch waypoint destinations between areas this way, not - // between acts however so don't even bother to try. - WaypointID int // Waypoint - - // this field uses the ID of the ObjectGroup you want to Spawn in this Area, - // taken from Objgroup.txt. - ObjectGroupID0 int // ObjGrp0 - ObjectGroupID1 int // ObjGrp1 - ObjectGroupID2 int // ObjGrp2 - ObjectGroupID3 int // ObjGrp3 - ObjectGroupID4 int // ObjGrp4 - ObjectGroupID5 int // ObjGrp5 - ObjectGroupID6 int // ObjGrp6 - ObjectGroupID7 int // ObjGrp7 - - // These fields indicates the chance for each object group to spawn (if you - // use ObjGrp0 then set ObjPrb0 to a value below 100) - ObjectGroupSpawnChance0 int // ObjPrb0 - ObjectGroupSpawnChance1 int // ObjPrb1 - ObjectGroupSpawnChance2 int // ObjPrb2 - ObjectGroupSpawnChance3 int // ObjPrb3 - ObjectGroupSpawnChance4 int // ObjPrb4 - ObjectGroupSpawnChance5 int // ObjPrb5 - ObjectGroupSpawnChance6 int // ObjPrb6 - ObjectGroupSpawnChance7 int // ObjPrb7 - - // It sets whether rain or snow (in act 5 only) can fall . Set it to 1 in - // order to enable it, 0 to disable it. - EnableRain bool // Rain - - // Unused setting (In pre beta D2 Blizzard planned Rain to generate Mud - // which would have slowed your character's speed down, but this never made - // it into the final game). the field is read by the code but the return - // value is never utilized. - EnableMud bool // Mud - - // Setting for 3D Enhanced D2 that disables Perspective Mode for a specific - // level. A value of 1 enables the users to choose between normal and - // Perspective view, while 0 disables that choice. - EnablePerspective bool // NoPer - - // Allows you to look through objects and walls even if they are not in a - // wilderness level. 1 enables it, 0 disables it. - EnableLineOfSightDraw bool // LOSDraw - - // Unknown. Probably has to do with Tiles and their Placement. - // 1 enables it, 0 disables it. - EnableFloorFliter bool // FloorFilter - - // Unknown. Probably has to do with tiles and their placement. - // 1 enables it, 0 disables it. - EnableBlankScreen bool // BlankScreen - - // for levels bordered with mountains or walls, like the act 1 wildernesses. - // 1 enables it, 0 disables it. - EnableDrawEdges bool // DrawEdges - - // Setting it to 1 makes the level to be treated as an indoor area, while - // 0 makes this level an outdoor. Indoor areas are not affected by day-night - // cycles, because they always use the light values specified in Intensity, - // Red, Green, Blue. this field also controls whenever sounds will echo if - // you're running the game with a sound card capable of it and have - // environment sound effects set to true. - IsInside bool // IsInside - - // This field is required for some levels, entering those levels when portal - // field isn't set will often crash the game. This also applies to - // duplicates of those levels created with both of the extended level - // plugins. - PortalEnable bool // Portal - - // This controls if you can re-position a portal in a level or not. If it's - // set to 1 you will be able to reposition the portal by using either map - // entry#76 Tp Location #79. If both tiles are in the level it will use Tp - // Location #79. If set to 0 the map won't allow repositioning. - PortalRepositionEnable bool // Position - - // Setting this field to 1 will make the monsters status saved in the map. - // Setting it to 0 will allow some useful things like NPC refreshing their - // stores. - // WARNING: Do not set this to 1 for non-town areas, or the monsters you'll - // flee from will simply vanish and never reappear. They won't even be - // replaced by new ones - // Gravestench - this funcionality should not be in one field - SaveMonsterStates bool // SaveMonsters - SaveMerchantStates bool // SaveMonsters - - // No info on the PK page, but I'm guessing it's for monster wandering - MonsterWanderEnable bool // MonWndr - - // This setting is hardcoded to certain level Ids, like the River Of Flame, - // enabling it in other places can glitch up the game, so leave it alone. - // It is not known what exactly it does however. - MonsterSpecialWalk bool // MonSpcWalk - - // Give preference to monsters set to ranged=1 in MonStats.txt on Nightmare - // and Hell difficulties when picking something to spawn. - MonsterPreferRanged bool // rangedspawn - -} - -// LevelDetails has all of the LevelDetailsRecords -//nolint:gochecknoglobals // Currently global by design, only written once -var LevelDetails map[int]*LevelDetailsRecord - -// GetLevelDetails gets a LevelDetailsRecord by the record Id -func GetLevelDetails(id int) *LevelDetailsRecord { - for i := 0; i < len(LevelDetails); i++ { - if LevelDetails[i].ID == id { - return LevelDetails[i] - } - } - - return nil -} - -// LoadLevelDetails loads level details records from levels.txt -//nolint:funlen // Txt loader, makes no sense to split -func LoadLevelDetails(file []byte) { - LevelDetails = make(map[int]*LevelDetailsRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &LevelDetailsRecord{ - 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[record.ID] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d LevelDetails records", len(LevelDetails)) -} diff --git a/d2common/d2data/d2datadict/misc.go b/d2common/d2data/d2datadict/misc.go deleted file mode 100644 index 30339ba3..00000000 --- a/d2common/d2data/d2datadict/misc.go +++ /dev/null @@ -1,16 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" -) - -// MiscItems stores all of the ItemCommonRecords for misc.txt -var MiscItems map[string]*ItemCommonRecord //nolint:gochecknoglobals // Currently global by design - -// LoadMiscItems loads ItemCommonRecords from misc.txt -func LoadMiscItems(file []byte) { - MiscItems = LoadCommonItems(file, d2enum.InventoryItemTypeItem) - log.Printf("Loaded %d misc items", len(MiscItems)) -} diff --git a/d2common/d2data/d2datadict/missiles.go b/d2common/d2data/d2datadict/missiles.go deleted file mode 100644 index 249fb9cd..00000000 --- a/d2common/d2data/d2datadict/missiles.go +++ /dev/null @@ -1,445 +0,0 @@ -package d2datadict - -import ( - "log" - "strings" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" -) - -// MissileCalcParam is a calculation parameter for a missile -type MissileCalcParam struct { - Param int - Desc string -} - -// MissileCalc is a calculation for a missile -type MissileCalc struct { - Calc d2calculation.CalcString - Desc string - Params []MissileCalcParam -} - -// MissileLight has the parameters for missile lighting -type MissileLight struct { - Diameter int - Flicker int - Red uint8 - Green uint8 - Blue uint8 -} - -// MissileAnimation stores parameters for a missile animation -type MissileAnimation struct { - CelFileName string - StepsBeforeVisible int - StepsBeforeActive int - AnimationRate int // seems to do nothing - AnimationLength int - AnimationSpeed int - StartingFrame int // called "RandFrame" - SubStartingFrame int - SubEndingFrame int - LoopAnimation bool - HasSubLoop bool // runs after first animation ends -} - -// MissileCollision parameters for missile collision -type MissileCollision struct { - CollisionType int // controls the kind of collision - // 0 = none, 1 = units only, 3 = normal (units, walls), - // 6 = walls only, 8 = walls, units, and floors - TimerFrames int // how many frames to persist - DestroyedUponCollision bool - FriendlyFire bool - LastCollide bool // unknown - Collision bool // unknown - ClientCollision bool // unknown - ClientSend bool // unclear - UseCollisionTimer bool // after hit, use timer before dying -} - -// MissileDamage parameters for calculating missile physical damage -type MissileDamage struct { - MinDamage int - MaxDamage int - MinLevelDamage [5]int // additional damage per missile level - // [0]: lvs 2-8, [1]: lvs 9-16, [2]: lvs 17-22, [3]: lvs 23-28, [4]: lv 29+ - MaxLevelDamage [5]int // see above - DamageSynergyPerCalc d2calculation.CalcString // works like synergy in skills.txt, not clear -} - -// MissileElementalDamage parameters for calculating missile elemental damage -type MissileElementalDamage struct { - Damage MissileDamage - ElementType string - Duration int // frames, 25 = 1 second - LevelDuration [3]int // 0,1,2, unknown level intervals, bonus duration per level -} - -// MissileRecord is a representation of a row from missiles.txt -type MissileRecord struct { - ServerMovementCalc MissileCalc - ClientMovementCalc MissileCalc - ServerCollisionCalc MissileCalc - ClientCollisionCalc MissileCalc - ServerDamageCalc MissileCalc - Light MissileLight - Animation MissileAnimation - Collision MissileCollision - Damage MissileDamage - ElementalDamage MissileElementalDamage - SubMissile [3]string // 0,1,2 name of missiles spawned by movement function - HitSubMissile [4]string // 0,1,2 name of missiles spawned by collision function - ClientSubMissile [3]string // see above, but for client only - ClientHitSubMissile [4]string // see above, but for client only - Name string - - SkillName string // if not empty, the missile will refer to this skill instead of its own data for the following: - // "ResultFlags, HitFlags, HitShift, HitClass, SrcDamage (SrcDam in skills.txt!), - // MinDam, MinLevDam1-5, MaxDam, MaxLevDam1-5, DmgSymPerCalc, EType, EMin, EMinLev1-5, - // EMax, EMaxLev1-5, EDmgSymPerCalc, ELen, ELenLev1-3, ELenSymPerCalc" - - TravelSound string // name of sound to play during lifetime - // whether or not it loops depends on the specific sound's settings? - // if it doesn't loop, it's just a on-spawn sound effect - HitSound string // sound plays upon collision - ProgSound string // plays at "special events", like a mariachi band - - ProgOverlay string // name of an overlay from overlays.txt to use at special events - 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,stylecheck // ID is the correct key - - ClientMovementFunc int - ClientCollisionFunc int - ServerMovementFunc int - ServerCollisionFunc int - ServerDamageFunc int - - Velocity int - MaxVelocity int - LevelVelocityBonus int - Accel int - Range int - LevelRangeBonus int - - XOffset int - YOffset int - ZOffset int - Size int // diameter - - DestroyedByTPFrame int // see above, for client side, (this is a guess) which frame it vanishes on - - HolyFilterType int // specifies what this missile can hit - KnockbackPercent int // chance of knocking the target back, 0-100 - - TransparencyMode int // controls rendering - // 0 = normal, 1 = alpha blending (darker = more transparent) - // 2 = special (black and white?) - - ResultFlags int // unknown - // 4 = normal missiles, 5 = explosions, 8 = non-damaging missiles - HitFlags int // unknown - // 2 = explosions, 5 = freezing arrow - - HitShift int // damage is measured in 256s - // the actual damage is [damage] * 2 ^ [hitshift] - // e.g. 100 damage, 8 hitshift = 100 * 2 ^ 8 = 100 * 256 = 25600 - // (visually, the damage is this result / 256) - - SourceDamage int // 0-128, 128 is 100% - SourceMissDamage int // 0-128, 128 is 100% - // unknown, only used for poison clouds. - - HitClass int // controls clientside aesthetic effects for collisions - // particularly sound effects that are played on a hit - NumDirections int // count of dirs in the DCC loaded by CelFile - // apparently this value is no longer needed in D2 - LocalBlood int // blood effects? - // 0 = no blood, 1 = blood, 2 = blood and affected by open wounds - DamageReductionRate int // how many frames between applications of the - // magic_damage_reduced stat, so for instance on a 0 this stat applies every frame - // on a 3, only every 4th frame has damage reduced - - DestroyedByTP bool // if true, destroyed when source player teleports to town - CanDestroy bool // unknown - - UseAttackRating bool // if true, uses 'attack rating' to determine if it hits or misses - // if false, has a 95% chance to hit. - AlwaysExplode bool // if true, always calls its collision function when it is destroyed, - // even if it doesn't hit anything - // note that some collision functions (lightning fury) - // seem to ignore this and always explode regardless of setting (requires investigation) - - ClientExplosion bool // if true, does not really exist - // is only aesthetic / client side - TownSafe bool // if true, doesn't vanish when spawned in town - // if false, vanishes when spawned in town - IgnoreBossModifiers bool // if true, doesn't get bonuses from boss mods - IgnoreMultishot bool // if true, can't gain the mulitshot modifier - // 0 = all units, 1 = undead only, 2 = demons only, 3 = all units (again?) - CanBeSlowed bool // if true, is affected by skill_handofathena - TriggersHitEvents bool // if true, triggers events that happen "upon getting hit" on targets - TriggersGetHit bool // if true, can cause target to enter hit recovery mode - SoftHit bool // unknown - - UseQuantity bool // if true, uses quantity - // not clear what this means. Also apparently requires a special starting function in skills.txt - AffectedByPierce bool // if true, affected by the pierce modifier and the Pierce skill - SpecialSetup bool // unknown, only true for potions - - MissileSkill bool // if true, applies elemental damage from items to the splash radius instead of normal damage modifiers - - ApplyMastery bool // unknown - // percentage of source units attack properties to apply to the missile? - // not only affects damage but also other modifiers like lifesteal and manasteal (need a complete list) - // setting this to -1 "gets rid of SrcDmg from skills.txt", not clear what that means - HalfDamageForTwoHander bool // if true, damage is halved when a two-handed weapon is used - -} - -func createMissileRecord(line string) MissileRecord { - r := strings.Split(line, "\t") - i := -1 - inc := func() int { - i++ - return i - } - // note: in this file, empties are equivalent to zero, so all numerical conversions should - // be wrapped in an d2common.EmptyToZero transform - result := MissileRecord{ - Name: r[inc()], - Id: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - - ClientMovementFunc: d2util.StringToInt(d2util.EmptyToZero(d2util.AsterToEmpty(r[inc()]))), - ClientCollisionFunc: d2util.StringToInt(d2util.EmptyToZero(d2util.AsterToEmpty(r[inc()]))), - ServerMovementFunc: d2util.StringToInt(d2util.EmptyToZero(d2util.AsterToEmpty(r[inc()]))), - ServerCollisionFunc: d2util.StringToInt(d2util.EmptyToZero(d2util.AsterToEmpty(r[inc()]))), - ServerDamageFunc: d2util.StringToInt(d2util.EmptyToZero(d2util.AsterToEmpty(r[inc()]))), - - ServerMovementCalc: loadMissileCalc(&r, inc, 5), - ClientMovementCalc: loadMissileCalc(&r, inc, 5), - ServerCollisionCalc: loadMissileCalc(&r, inc, 3), - ClientCollisionCalc: loadMissileCalc(&r, inc, 3), - ServerDamageCalc: loadMissileCalc(&r, inc, 2), - - Velocity: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - MaxVelocity: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - LevelVelocityBonus: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - Accel: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - Range: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - LevelRangeBonus: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - - Light: loadMissileLight(&r, inc), - - Animation: loadMissileAnimation(&r, inc), - - Collision: loadMissileCollision(&r, inc), - - XOffset: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - YOffset: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - ZOffset: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - Size: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - - DestroyedByTP: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - DestroyedByTPFrame: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - CanDestroy: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - - UseAttackRating: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - AlwaysExplode: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - - ClientExplosion: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - TownSafe: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - IgnoreBossModifiers: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - IgnoreMultishot: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - HolyFilterType: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - CanBeSlowed: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - TriggersHitEvents: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - TriggersGetHit: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - SoftHit: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - KnockbackPercent: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - - TransparencyMode: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - - UseQuantity: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - AffectedByPierce: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - SpecialSetup: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - - MissileSkill: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - SkillName: r[inc()], - - ResultFlags: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - HitFlags: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - - HitShift: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - ApplyMastery: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - SourceDamage: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - HalfDamageForTwoHander: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1, - SourceMissDamage: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - - Damage: loadMissileDamage(&r, inc), - ElementalDamage: loadMissileElementalDamage(&r, inc), - - HitClass: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - NumDirections: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - LocalBlood: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - DamageReductionRate: d2util.StringToInt(d2util.EmptyToZero(r[inc()])), - - TravelSound: r[inc()], - HitSound: r[inc()], - ProgSound: r[inc()], - ProgOverlay: r[inc()], - ExplosionMissile: r[inc()], - - SubMissile: [3]string{r[inc()], r[inc()], r[inc()]}, - HitSubMissile: [4]string{r[inc()], r[inc()], r[inc()], r[inc()]}, - ClientSubMissile: [3]string{r[inc()], r[inc()], r[inc()]}, - ClientHitSubMissile: [4]string{r[inc()], r[inc()], r[inc()], r[inc()]}, - } - - return result -} - -// Missiles stores all of the MissileRecords -//nolint:gochecknoglobals // Currently global by design, only written once -var Missiles map[int]*MissileRecord -var missilesByName map[string]*MissileRecord - -// GetMissileByName allows lookup of a MissileRecord by a given name. The name will be lowercased and stripped of whitespaces. -func GetMissileByName(missileName string) *MissileRecord { - return missilesByName[sanitize(missileName)] -} - -// LoadMissiles loads MissileRecords from missiles.txt -func LoadMissiles(file []byte) { - Missiles = make(map[int]*MissileRecord) - missilesByName = make(map[string]*MissileRecord) - data := strings.Split(string(file), "\r\n")[1:] - - for _, line := range data { - if line == "" { - continue - } - - rec := createMissileRecord(line) - Missiles[rec.Id] = &rec - missilesByName[sanitize(rec.Name)] = &rec - } - - log.Printf("Loaded %d missiles", len(Missiles)) -} - -func sanitize(missileName string) string { - return strings.ToLower(strings.ReplaceAll(missileName, " ", "")) -} - -func loadMissileCalcParam(r *[]string, inc func() int) MissileCalcParam { - result := MissileCalcParam{ - Param: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - Desc: (*r)[inc()], - } - - return result -} - -func loadMissileCalc(r *[]string, inc func() int, params int) MissileCalc { - result := MissileCalc{ - Calc: d2calculation.CalcString((*r)[inc()]), - Desc: (*r)[inc()], - } - result.Params = make([]MissileCalcParam, params) - - for p := 0; p < params; p++ { - result.Params[p] = loadMissileCalcParam(r, inc) - } - - return result -} - -func loadMissileLight(r *[]string, inc func() int) MissileLight { - result := MissileLight{ - Diameter: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - Flicker: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - Red: d2util.StringToUint8(d2util.EmptyToZero((*r)[inc()])), - Green: d2util.StringToUint8(d2util.EmptyToZero((*r)[inc()])), - Blue: d2util.StringToUint8(d2util.EmptyToZero((*r)[inc()])), - } - - return result -} - -func loadMissileAnimation(r *[]string, inc func() int) MissileAnimation { - result := MissileAnimation{ - StepsBeforeVisible: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - StepsBeforeActive: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - LoopAnimation: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])) == 1, - CelFileName: (*r)[inc()], - AnimationRate: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - AnimationLength: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - AnimationSpeed: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - StartingFrame: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - HasSubLoop: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])) == 1, - SubStartingFrame: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - SubEndingFrame: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - } - - return result -} - -func loadMissileCollision(r *[]string, inc func() int) MissileCollision { - result := MissileCollision{ - CollisionType: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - DestroyedUponCollision: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])) == 1, - FriendlyFire: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])) == 1, - LastCollide: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])) == 1, - Collision: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])) == 1, - ClientCollision: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])) == 1, - ClientSend: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])) == 1, - UseCollisionTimer: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])) == 1, - TimerFrames: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - } - - return result -} - -func loadMissileDamage(r *[]string, inc func() int) MissileDamage { - result := MissileDamage{ - MinDamage: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - MinLevelDamage: [5]int{ - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - }, - MaxDamage: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - MaxLevelDamage: [5]int{ - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - }, - DamageSynergyPerCalc: d2calculation.CalcString((*r)[inc()]), - } - - return result -} - -func loadMissileElementalDamage(r *[]string, inc func() int) MissileElementalDamage { - result := MissileElementalDamage{ - ElementType: (*r)[inc()], - Damage: loadMissileDamage(r, inc), - Duration: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - LevelDuration: [3]int{ - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])), - }, - } - - return result -} diff --git a/d2common/d2data/d2datadict/monmode.go b/d2common/d2data/d2datadict/monmode.go deleted file mode 100644 index bd6163f8..00000000 --- a/d2common/d2data/d2datadict/monmode.go +++ /dev/null @@ -1,38 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// MonModeRecord is a representation of a single row of Monmode.txt -type MonModeRecord struct { - Name string - Token string - Code string -} - -// MonModes stores all of the GemsRecords -var MonModes map[string]*MonModeRecord //nolint:gochecknoglobals // Currently global by design, only written once - -// LoadMonModes loads gem records into a map[string]*MonModeRecord -func LoadMonModes(file []byte) { - MonModes = make(map[string]*MonModeRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &MonModeRecord{ - Name: d.String("name"), - Token: d.String("token"), - Code: d.String("code"), - } - MonModes[record.Name] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d MonMode records", len(MonModes)) -} diff --git a/d2common/d2data/d2datadict/monpreset.go b/d2common/d2data/d2datadict/monpreset.go deleted file mode 100644 index 35634b8a..00000000 --- a/d2common/d2data/d2datadict/monpreset.go +++ /dev/null @@ -1,32 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// MonPresets stores monster presets -//nolint:gochecknoglobals // Currently global by design, only written once -var MonPresets map[int32][]string - -// LoadMonPresets loads monster presets from monpresets.txt -func LoadMonPresets(file []byte) { - MonPresets = make(map[int32][]string) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - act := int32(d.Number("Act")) - if _, ok := MonPresets[act]; !ok { - MonPresets[act] = make([]string, 0) - } - - MonPresets[act] = append(MonPresets[act], d.String("Place")) - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d MonPreset records", len(MonPresets)) -} diff --git a/d2common/d2data/d2datadict/monprop.go b/d2common/d2data/d2datadict/monprop.go deleted file mode 100644 index c14ecbb5..00000000 --- a/d2common/d2data/d2datadict/monprop.go +++ /dev/null @@ -1,98 +0,0 @@ -package d2datadict - -import ( - "fmt" - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -const ( - numMonProps = 6 - fmtProp = "prop%d%s" - fmtChance = "chance%d%s" - fmtPar = "par%d%s" - fmtMin = "min%d%s" - fmtMax = "max%d%s" - fmtNormal = "" - fmtNightmare = " (N)" - fmtHell = " (H)" -) - -// MonPropRecord is a representation of a single row of monprop.txt -type MonPropRecord struct { - ID string - - Properties struct { - Normal [numMonProps]*monProp - Nightmare [numMonProps]*monProp - Hell [numMonProps]*monProp - } -} - -type monProp struct { - Code string - Param string - Chance int - Min int - Max int -} - -// MonProps stores all of the MonPropRecords -var MonProps map[string]*MonPropRecord //nolint:gochecknoglobals // Currently global by design, only written once - -// LoadMonProps loads monster property records into a map[string]*MonPropRecord -func LoadMonProps(file []byte) { - MonProps = make(map[string]*MonPropRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &MonPropRecord{ - ID: d.String("Id"), - - Properties: struct { - Normal [numMonProps]*monProp - Nightmare [numMonProps]*monProp - Hell [numMonProps]*monProp - }{ - [numMonProps]*monProp{}, - [numMonProps]*monProp{}, - [numMonProps]*monProp{}, - }, - } - - for idx := 1; idx <= numMonProps; idx++ { - record.Properties.Normal[idx-1] = &monProp{ - Code: d.String(fmt.Sprintf(fmtProp, idx, fmtNormal)), - Param: d.String(fmt.Sprintf(fmtPar, idx, fmtNormal)), - Chance: d.Number(fmt.Sprintf(fmtChance, idx, fmtNormal)), - Min: d.Number(fmt.Sprintf(fmtMin, idx, fmtNormal)), - Max: d.Number(fmt.Sprintf(fmtMax, idx, fmtNormal)), - } - - record.Properties.Nightmare[idx-1] = &monProp{ - Code: d.String(fmt.Sprintf(fmtProp, idx, fmtNightmare)), - Param: d.String(fmt.Sprintf(fmtPar, idx, fmtNightmare)), - Chance: d.Number(fmt.Sprintf(fmtChance, idx, fmtNightmare)), - Min: d.Number(fmt.Sprintf(fmtMin, idx, fmtNightmare)), - Max: d.Number(fmt.Sprintf(fmtMax, idx, fmtNightmare)), - } - - record.Properties.Hell[idx-1] = &monProp{ - Code: d.String(fmt.Sprintf(fmtProp, idx, fmtHell)), - Param: d.String(fmt.Sprintf(fmtPar, idx, fmtHell)), - Chance: d.Number(fmt.Sprintf(fmtChance, idx, fmtHell)), - Min: d.Number(fmt.Sprintf(fmtMin, idx, fmtHell)), - Max: d.Number(fmt.Sprintf(fmtMax, idx, fmtHell)), - } - } - - MonProps[record.ID] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d MonProp records", len(MonProps)) -} diff --git a/d2common/d2data/d2datadict/monstats.go b/d2common/d2data/d2datadict/monstats.go deleted file mode 100644 index eec309f6..00000000 --- a/d2common/d2data/d2datadict/monstats.go +++ /dev/null @@ -1,954 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// https://d2mods.info/forum/kb/viewarticle?a=360 - -type ( - // MonStatsRecord represents a single row from `data/global/excel/monstats.txt` in the MPQ files. - // These records are used for creating monsters. - MonStatsRecord struct { - - // Key contains the pointer that will be used in other txt files - // such as levels.txt and superuniques.txt. - Key string // called `Id` in monstats.txt - - // Id is the actual internal ID of the unit (this is what the ID pointer - // actually points at) remember that no two units can have the same ID, - // this will result in lots of unpredictable behavior and crashes so please - // don’t do it. This 'HarcCodedInDeX' is used for several things, such as - // determining whenever the unit uses DCC or DC6 graphics (like mephisto - // and the death animations of Diablo, the Maggoc Queen etc.), the hcIdx - // 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 int // 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 - // fallen1 as their “base” unit). - BaseKey string // called `BaseId` in monstats.txt - - // NextKey is the ID of the next unit in the chain. (fallen1 has the ID pointer of fallen2 in here). - // The game uses this for “map generated” monsters such as the fallen in the fallen camps, - // which get picked based on area level. - NextKey string // called `NextInClass` in monstats.txt - - // NameStringTableKey the string-key used in the TBL (string.tbl, - // expansionstring.tbl and patchstring.tbl) files to make this monsters - // name appear when you highlight it. - NameString string // called `NameStr` in monstats.txt - - // ExtraDataKey the ID pointer to an entry in MonStats2.txt. - ExtraDataKey string // called `MonStatsEx` in monstats.txt - - // PropertiesKey contains the ID pointer to an entry in MonProp.txt which - // controls what special modifiers are appended to the unit - PropertiesKey string // called `MonProp` in monstats.txt - - // MonsterGroup contains the group ID of the “super group” this monster - // belongs to, IE all skeletons belong to the "super group" skeleton. The - MonsterGroup string // called `MonType` in monstats.txt - - // AiKey tells the game which AI to use for this monster. Every AI - // needs a specific set of animation modes (GH, A1, A2, S1, WL, RN etc). - AiKey string // called `AI` in monstats.txt - - // DescriptionStringTableKey contains the string-key used in the TBL (string.tbl, - // expansionstring.tbl and patchstring.tbl) files for the monsters - // description (leave it blank for no description). - // NOTE: ever wondered how to make it say something below the monster - // name (such as “Drains Mana and Stamina etc), well this is how you do it. - // Just put the string-key of the string you want to display below the - // monsters name in here. - DescriptionStringTableKey string // called `DescStr` in monstats.txt - - // AnimationDirectoryToken controls which token (IE name of a folder that - // contains animations) the game uses for this monster. - AnimationDirectoryToken string // called `Code` in monstats.txt - - // SpawnKey contains the key of the unit to spawn. - SpawnKey string // called `spawn` in monstats.txt - - // SpawnAnimationKey - // which animation mode will the spawned monster be spawned in. - SpawnAnimationKey string // called `spawnmode` in monstats.txt - - // MinionId1 is an Id of a minion that spawns when this monster is created - MinionId1 string //nolint:golint,stylecheck // called `minion1` in monstats.txt - - // MinionId2 is an Id of a minion that spawns when this monster is created - MinionId2 string //nolint:golint,stylecheck // called `minion2` in monstats.txt - - // SoundKeyNormal, SoundKeySpecial - // specifies the ID pointer to this monsters “Sound Bank” in MonSound.txt - // when this monster is normal. - SoundKeyNormal string // called `MonSound` in monstats.txt - SoundKeySpecial string // called `UMonSound` in monstats.txt - - // MissileA1 -- MissileSQ - // these columns control “non-skill-related” missiles used by the monster. - // For example if you enter a missile ID pointer (from Missiles.txt) in - // MissA1 then, whenever the monster uses its A1 mode, it will shoot a - // missile, this however will successfully prevent it from dealing any damage - // with the swing of A1. - // NOTE: for the beginners, A1=Attack1, A2=Attack2, S1=Skill1, S2=Skill2, - // S3=Skill3, S4=Skill4, C=Cast, SQ=Sequence. - MissileA1 string // called `MissA1` in monstats.txt - MissileA2 string // called `MissA2` in monstats.txt - MissileS1 string // called `MissS1` in monstats.txt - MissileS2 string // called `MissS2` in monstats.txt - MissileS3 string // called `MissS3` in monstats.txt - MissileS4 string // called `MissS4` in monstats.txt - MissileC string // called `MissC` in monstats.txt - MissileSQ string // called `MissSQ` in monstats.txt - - // SkillId1 -- SkillId8 - // 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 //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. - SkillAnimation1 string // called `Sk1mode` in monstats.txt - SkillAnimation2 string // called `Sk2mode` in monstats.txt - SkillAnimation3 string // called `Sk3mode` in monstats.txt - SkillAnimation4 string // called `Sk4mode` in monstats.txt - SkillAnimation5 string // called `Sk5mode` in monstats.txt - SkillAnimation6 string // called `Sk6mode` in monstats.txt - SkillAnimation7 string // called `Sk7mode` in monstats.txt - SkillAnimation8 string // called `Sk8mode` in monstats.txt - - // DamageSkillId - // 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. - 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 - // you would usually attack elemental damage are A1, A2, S1, S2, S3, S4, SQ - // or C as these are the only ones that naturally contain trigger bytes. - ElementAttackMode1 string // called `El1Mode` in monstats.txt - ElementAttackMode2 string // called `El2Mode` in monstats.txt - ElementAttackMode3 string // called `El3Mode` in monstats.txt - - // ElementType1 -- ElementType3 - // the type of the elemental damage appended to an attack. There are several - // elements: fire=Fire Damage, ltng=Lightning Damage, cold=Cold Damage - // (uses duration), pois = Poison Damage (uses duration), mag=Magic Damage, - // life=Life Drain (the monster heals the specified amount when it hits - // you), mana=Mana Drain (the monster steals the specified amount of mana - // when it hits you), stam=Stamina Drain (the monster steals the specified - // amount of stamina when it hits you), stun=Stun Damage (uses duration, - // damage is not used, this only effects pets and mercs, players will not - // get immobilized but they will get thrown into hit recovery whenever they - // get hit by an attack, no matter what type of attack it is, thanks to - // Brother Laz clearing this one up), rand=Random Damage (uses duration, - // either does Poison, Cold, Fire or Lightning damage, randomly picked for - // every attack), burn=Burning Damage (uses duration, this damage type - // cannot be resisted or reduced in any way), frze=Freezing Damage (uses - // duration, this will effect players like normal cold damage but will - // freeze and shatter pets). If you want to give your monster knockback use - // MonProp.txt. - ElementType1 string // called `El1Type` in monstats.txt - ElementType2 string // called `El2Type` in monstats.txt - ElementType3 string // called `El3Type` in monstats.txt - - // TreasureClassNormal - // Treasure class for normal monsters, champions, uniques, and quests - // on the respective difficulties. - TreasureClassNormal string // called `TreasureClass1` in monstats.txt - TreasureClassNightmare string // called `TreasureClass1(N)` in monstats.txt - TreasureClassHell string // called `TreasureClass1(H)` in monstats.txt - TreasureClassChampionNormal string // called `TreasureClass2` in monstats.txt - TreasureClassChampionNightmare string // called `TreasureClass2(N)` in monstats.txt - TreasureClassChampionHell string // called `TreasureClass2(H)` in monstats.txt - TreasureClass3UniqueNormal string // called `TreasureClass3` in monstats.txt - TreasureClass3UniqueNightmare string // called `TreasureClass3(N)` in monstats.txt - TreasureClass3UniqueHell string // called `TreasureClass3(H)` in monstats.txt - TreasureClassQuestNormal string // called `TreasureClass4` in monstats.txt - TreasureClassQuestNightmare string // called `TreasureClass4(N)` in monstats.txt - TreasureClassQuestHell string // called `TreasureClass4(H)` in monstats.txt - - // TreasureClassQuestTriggerId - // the ID of the Quest that triggers the Quest Treasureclass drop. - 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 //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 - // contains 8 palettes, starting from index 0. These palettes are used by - // the game to make the various monster sub-types appear with color - // variations. The game with use the palette from the palettes file - // corresponding to the value in this column plus 2; eg: translvl = 0 will - // use the third palette in the file. - // NOTE: some tokens (token = IE name of a folder that contains animations) - // 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 //nolint:golint,stylecheck // called `TransLvl` in monstats.txt - - // SpawnOffsetX, SpawnOffsetY - // are the x/y offsets at which spawned monsters are placed. IE this prevents - // the spawned monsters from being created at the same x/y coordinates as - // the spawner itself. - SpawnOffsetX int // called `spawnx` in monstats.txt - SpawnOffsetY int // called `spawny` in monstats.txt - - // MinionPartyMin, MinionPartyMax controls how many minions are spawned together with this unit. - MinionPartyMin int // called `PartyMin` in monstats.txt - MinionPartyMax int // called `PartyMax` in monstats.txt - - // MinionGroupMin, MinionGroupMax - // controls how many units of the base unit to spawn. - MinionGroupMin int // called `MinGrp` in monstats.txt - MinionGroupMax int // called `MaxGrp` in monstats.txt - - // PopulationReductionPercent controls the overall chance something will spawn in - // percentages. Blank entries are the same as 100%. - PopulationReductionPercent int // called `sparsePopulate` in monstats.txt - - // SpeedBase, SpeedRun - // controls the walking and running speed of this monster respectively. - // NOTE: RUN is only used if the monster has a RN mode and its AI uses that - // mode. - SpeedBase int // called `Velocity` in monstats.txt - SpeedRun int // called `Run` in monstats.txt - - // Rarity controls the overall odds that this monster will be spawned. - // IE Lets say in Levels.txt you have two monsters set to spawn - Monster A - // has rarity of 10 whereas Monster B has rarity of 1 and the level in - // question is limited to 1 monster type. First the game sums up the - // chances (11) and then calculates the odds of the monster spawning. Which - // would be 1/11 (9% chance) for Monster B and 10/11 (91% chance) for - // Monster A, thus Monster A is a lot more common than monster B. If you set - // this column to 0 then the monster will never be selected by Levels.txt - // for obvious reasons. - Rarity int // called `Rarity` in monstats.txt - - // LevelNormal, LevelNightmare, LevelHell - // controls the monsters level on the specified difficulty. This setting is - // only used on normal. On nightmare and hell the monsters level is - // identical with the area level from Levels.txt, unless your monster has - // BOSS column set to 1, in this case its level will be always taken from - // these 3 columns. - LevelNormal int // called `Level` in monstats.txt - LevelNightmare int // called `Level(N)` in monstats.txt - LevelHell int // called `Level(H)` in monstats.txt - - // used by the game to tell AIs which unit to target first. The higher this - // is the higher the threat level. Setting this to 25 or so on Maggot Eggs - // would make your Mercenary NPC try to destroy those first. - ThreatLevel int // called `threat` in monstats.txt - - // AiDelayNormal, AiDelayNightmare, AiDelayHell - // this controls delays between AI ticks (on normal, nightmare and hell). - // The lower the number, the faster the AI's will attack thanks to reduced - // delay between swings, casting spells, throwing missiles etc. Please - // remember that some AI's got individual delays between attacks, this will - // still make them faster and seemingly more deadly though. - AiDelayNormal int // called `aidel` in monstats.txt - AiDelayNightmare int // called `aidel(N)` in monstats.txt - AiDelayHell int // called `aidel(H)` in monstats.txt - - // AiDistanceNormal, AiDistanceNightmare, AiDistanceHell - // the distance in cells from which AI is activated. Most AI"s have base - // hardcoded activation radius of 35 which stands for a distamnce of about - // 1 screen, thus leaving these fields blank sets this to 35 automatically. - AiDistanceNormal int // called `aidist` in monstats.txt - AiDistanceNightmare int // called `aidist(N)` in monstats.txt - AiDistanceHell int // called `aidist(H)` in monstats.txt - - // AiParameterNormal1, AiParameterNightmare1, AiParameterHell1 - // these cells are very important, they pass on parameters (in percentage) - // to the AI code. For descriptions about what all these AI's do, check - // The AI Compendium. https://d2mods.info/forum/viewtopic.php?t=36230 - // Warning: many people have trouble with the AI of the Imps, this AI is - // special and uses multiple rows. - AiParameterNormal1 int // called `aip1` in monstats.txt - AiParameterNormal2 int // called `aip2` in monstats.txt - AiParameterNormal3 int // called `aip3` in monstats.txt - AiParameterNormal4 int // called `aip4` in monstats.txt - AiParameterNormal5 int // called `aip5` in monstats.txt - AiParameterNormal6 int // called `aip6` in monstats.txt - AiParameterNormal7 int // called `aip7` in monstats.txt - AiParameterNormal8 int // called `aip8` in monstats.txt - AiParameterNightmare1 int // called `aip1(N)` in monstats.txt - AiParameterNightmare2 int // called `aip2(N)` in monstats.txt - AiParameterNightmare3 int // called `aip3(N)` in monstats.txt - AiParameterNightmare4 int // called `aip4(N)` in monstats.txt - AiParameterNightmare5 int // called `aip5(N)` in monstats.txt - AiParameterNightmare6 int // called `aip6(N)` in monstats.txt - AiParameterNightmare7 int // called `aip7(N)` in monstats.txt - AiParameterNightmare8 int // called `aip8(N)` in monstats.txt - AiParameterHell1 int // called `aip1(H)` in monstats.txt - AiParameterHell2 int // called `aip2(H)` in monstats.txt - AiParameterHell3 int // called `aip3(H)` in monstats.txt - AiParameterHell4 int // called `aip4(H)` in monstats.txt - AiParameterHell5 int // called `aip5(H)` in monstats.txt - AiParameterHell6 int // called `aip6(H)` in monstats.txt - AiParameterHell7 int // called `aip7(H)` in monstats.txt - AiParameterHell8 int // called `aip8(H)` in monstats.txt - - // Alignment controls whenever the monster fights on your side or - // fights against you (or if it just walks around, IE a critter). - // If you want to turn some obsolete NPCs into enemies, this is - // one of the settings you will need to modify. Setting it to 2 - // without adjusting other settings (related to AI and also some - // in MonStats2) it will simply attack everything. - Alignment d2enum.MonsterAlignmentType // called `Align` in monstats.txt - - // SkillLevel1 -- SkillLevel8 - // the skill level of the skill in question. This gets a bonus on nightmare - // and hell which you can modify in DifficultyLevels.txt. - SkillLevel1 int // called `Sk1lvl` in monstats.txt - SkillLevel2 int // called `Sk2lvl` in monstats.txt - SkillLevel3 int // called `Sk3lvl` in monstats.txt - SkillLevel4 int // called `Sk4lvl` in monstats.txt - SkillLevel5 int // called `Sk5lvl` in monstats.txt - SkillLevel6 int // called `Sk6lvl` in monstats.txt - SkillLevel7 int // called `Sk7lvl` in monstats.txt - SkillLevel8 int // called `Sk8lvl` in monstats.txt - - // LeechSensitivityNormal / Nightmare / Hell - // controls the effectiveness of Life and Mana steal from equipment on this - // unit on the respective difficulties. 0=Can’t leech at all. Remember that - // besides this, Life and Mana Steal is further limited by DifficultyLevels.txt. - LeechSensitivityNormal int // called `Drain` in monstats.txt - LeechSensitivityNightmare int // called `Drain(N)` in monstats.txt - LeechSensitivityHell int // called `Drain(H)` in monstats.txt - - // ColdSensitivityNormal / Nightmare / Hell - // controls the effectiveness of cold effect and its duration and freeze - // duration on this unit. The lower this value is, the more speed this unit - // looses when its under the effect of cold, also freezing/cold effect will - // stay for longer. Positive values will make the unit faster (thanks to - // Brother Laz for confirming my assumption), and 0 will make it - // unfreezeable. Besides this, cold length and freeze length settings are - // also set in DifficultyLevels.txt. - ColdSensitivityNormal int // called `coldeffect` in monstats.txt - ColdSensitivityNightmare int // called `coldeffect(N)` in monstats.txt - ColdSensitivityHell int // called `coldeffect(H)` in monstats.txt - - // ResistancePhysicalNormal - // Damage resistance on the respective difficulties. Negative values mean - // that the unit takes more damage from this element, values at or above 100 - // will result in immunity. - ResistancePhysicalNormal int // called `ResDm` in monstats.txt - ResistancePhysicalNightmare int // called `ResDm(N)` in monstats.txt - ResistancePhysicalHell int // called `ResDm(H)` in monstats.txt - ResistanceMagicNormal int // called `ResMa` in monstats.txt - ResistanceMagicNightmare int // called `ResMa(N)` in monstats.txt - ResistanceMagicHell int // called `ResMa(H)` in monstats.txt - ResistanceFireNormal int // called `ResFi` in monstats.txt - ResistanceFireNightmare int // called `ResFi(N)` in monstats.txt - ResistanceFireHell int // called `ResFi(H)` in monstats.txt - ResistanceLightningNormal int // called `ResLi` in monstats.txt - ResistanceLightningNightmare int // called `ResLi(N)` in monstats.txt - ResistanceLightningHell int // called `ResLi(H)` in monstats.txt - ResistanceColdNormal int // called `ResCo` in monstats.txt - ResistanceColdNightmare int // called `ResCo(N)` in monstats.txt - ResistanceColdHell int // called `ResCo(H)` in monstats.txt - ResistancePoisonNormal int // called `ResPo` in monstats.txt - ResistancePoisonNightmare int // called `ResPo(N)` in monstats.txt - ResistancePoisonHell int // called `ResPo(H)` in monstats.txt - - // HealthRegenPerFrame - // this controls how much health this unit regenerates per frame. Sometimes - // this is altered by the units AI. The formula is (REGEN * HP) / 4096. So - // a monster with 200 hp and a regen rate of 10 would regenerate ~0,5 HP - // (~12 per second) every frame (1 second = 25 frames). - HealthRegenPerFrame int // called `DamageRegen` in monstats.txt - - // ChanceToBlockNormal / Nightmare / Hell - // this units chance to block. See the above column for details when this - // applies or not. Monsters are capped at 75% block as players are AFAIK. - ChanceToBlockNormal int // called `ToBlock` in monstats.txt - ChanceToBlockNightmare int // called `ToBlock(N)` in monstats.txt - ChanceToBlockHell int // called `ToBlock(H)` in monstats.txt - - // ChanceDeadlyStrike - // this units chance of scoring a critical hit (dealing double the damage). - ChanceDeadlyStrike int // called `Crit` in monstats.txt - - // MinHPNormal -- MaxHPHell - // minHp, maxHp, minHp(N), maxHp(N), minHp(H), maxHp(H): this units minimum - // and maximum HP on the respective difficulties. - // NOTE: Monster HitPoints are calculated as the following: (minHp * Hp from - // MonLvl.txt)/100 for minimal hp and (maxHp * Hp from MonLvl.txt)/100 for - // maximum hp. - // To make this guide idiot-proof, we will calculate the hit points of a - // Hungry Dead from vanilla on Normal difficulty and Single Player mode. - // It has minHp = 101 and maxHp = 186 and level 2. Hp for level 2 in - // MonLvl.txt = 9 - // It means Hungry Dead has (101*9)/100 ~ 9 of minimum hp and - // (186*9)/100 ~ 17 maximum hit points. You have to remember monsters on - // nightmare and hell take their level (unless Boss = 1) from area level of - // Levels.txt instead of Level column of MonStats.txt. I hope this is clear. - MinHPNormal int // called `minHP` in monstats.txt - MinHPNightmare int // called `MinHP(N)` in monstats.txt - MinHPHell int // called `MinHP(H)` in monstats.txt - MaxHPNormal int // called `maxHP` in monstats.txt - MaxHPNightmare int // called `MaxHP(N)` in monstats.txt - MaxHPHell int // called `MaxHP(H)` in monstats.txt - - // ArmorClassNormal -- Hell - // this units Armor Class on the respective difficulties. The calculation is - // the same (analogical) as for hit points. - ArmorClassNormal int // called `AC` in monstats.txt - ArmorClassNightmare int // called `AC(N)` in monstats.txt - ArmorClassHell int // called `AC(H)` in monstats.txt - - // ExperienceNormal -- Hell - // the experience you get when killing this unit on the respective - // difficulty. The calculation is the same (analogical) as for hit points. - ExperienceNormal int // called `Exp` in monstats.txt - ExperienceNightmare int // called `Exp(N)` in monstats.txt - ExperienceHell int // called `Exp(H)` in monstats.txt - - // DamageMinA1Normal / Nightmare / Hell - // DamageMaxA1Normal / Nightmare /Hell - // this units minimum and maximum damage when it uses A1/A2/S1 mode. - // The calculation is the same (analogical) as for hit points. - DamageMinA1Normal int // called `A1MinD` in monstats.txt - DamageMinA1Nightmare int // called `A1MinD(N)` in monstats.txt - DamageMinA1Hell int // called `A1MinD(H)` in monstats.txt - DamageMaxA1Normal int // called `A1MaxD` in monstats.txt - DamageMaxA1Nightmare int // called `A1MaxD(N)` in monstats.txt - DamageMaxA1Hell int // called `A1MaxD(H)` in monstats.txt - DamageMinA2Normal int // called `A2MinD` in monstats.txt - DamageMinA2Nightmare int // called `A2MinD(N)` in monstats.txt - DamageMinA2Hell int // called `A2MinD(H)` in monstats.txt - DamageMaxA2Normal int // called `A2MaxD` in monstats.txt - DamageMaxA2Nightmare int // called `A2MaxD(N)` in monstats.txt - DamageMaxA2Hell int // called `A2MaxD(H)` in monstats.txt - DamageMinS1Normal int // called `S1MinD` in monstats.txt - DamageMinS1Nightmare int // called `S1MinD(N)` in monstats.txt - DamageMinS1Hell int // called `S1MinD(H)` in monstats.txt - DamageMaxS1Normal int // called `S1MaxD` in monstats.txt - DamageMaxS1Nightmare int // called `S1MaxD(N)` in monstats.txt - DamageMaxS1Hell int // called `S1MaxD(H)` in monstats.txt - - // AttackRatingA1Normal AttackRatingS1Hell - // this units attack rating for A1/A2/S1 mode on the respective difficulties - // The calculation is the same (analogical) as for hit points. - AttackRatingA1Normal int // called `A1TH` in monstats.txt - AttackRatingA1Nightmare int // called `A1TH(N)` in monstats.txt - AttackRatingA1Hell int // called `A1TH(H)` in monstats.txt - AttackRatingA2Normal int // called `A2TH` in monstats.txt - AttackRatingA2Nightmare int // called `A2TH(N)` in monstats.txt - AttackRatingA2Hell int // called `A2TH(H)` in monstats.txt - AttackRatingS1Normal int // called `S1TH` in monstats.txt - AttackRatingS1Nightmare int // called `S1TH(N)` in monstats.txt - AttackRatingS1Hell int // called `S1TH(H)` in monstats.txt - - // ElementChance1Normal -- ElementChance3Hell - // chance to append elemental damage to an attack on the respective - // difficulties. 0=Never append, 100=Always append. - ElementChance1Normal int // called `El1Pct` in monstats.txt - ElementChance1Nightmare int // called `El1Pct(N)` in monstats.txt - ElementChance1Hell int // called `El1Pct(H)` in monstats.txt - ElementChance2Normal int // called `El2Pct` in monstats.txt - ElementChance2Nightmare int // called `El2Pct(N)` in monstats.txt - ElementChance2Hell int // called `El2Pct(H)` in monstats.txt - ElementChance3Normal int // called `El3Pct` in monstats.txt - ElementChance3Nightmare int // called `El3Pct(N)` in monstats.txt - ElementChance3Hell int // called `El3Pct(H)` in monstats.txt - - // ElementDamageMin1Normal -- ElementDamageMax3Hell - // minimum and Maximum elemental damage to append to the attack on the - // respective difficulties. Note that you should only append elemental - // damage to those missiles that don’t have any set in Missiles.txt. The - // calculation is the same (analogical) as for hit points. - ElementDamageMin1Normal int // called `El1MinD` in monstats.txt - ElementDamageMin1Nightmare int // called `El1MinD(N)` in monstats.txt - ElementDamageMin1Hell int // called `El1MinD(H)` in monstats.txt - ElementDamageMin2Normal int // called `El2MinD` in monstats.txt - ElementDamageMin2Nightmare int // called `El2MinD(N)` in monstats.txt - ElementDamageMin2Hell int // called `El2MinD(H)` in monstats.txt - ElementDamageMin3Normal int // called `El3MinD` in monstats.txt - ElementDamageMin3Nightmare int // called `El3MinD(N)` in monstats.txt - ElementDamageMin3Hell int // called `El3MinD(H)` in monstats.txt - ElementDamageMax1Normal int // called `El1MaxD` in monstats.txt - ElementDamageMax1Nightmare int // called `El1MaxD(N)` in monstats.txt - ElementDamageMax1Hell int // called `El1MaxD(H)` in monstats.txt - ElementDamageMax2Normal int // called `El2MaxD` in monstats.txt - ElementDamageMax2Nightmare int // called `El2MaxD(N)` in monstats.txt - ElementDamageMax2Hell int // called `El2MaxD(H)` in monstats.txt - ElementDamageMax3Normal int // called `El3MaxD` in monstats.txt - ElementDamageMax3Nightmare int // called `El3MaxD(N)` in monstats.txt - ElementDamageMax3Hell int // called `El3MaxD(H)` in monstats.txt - - // ElementDuration1Normal -- ElementDuration3Hell - // duration of the elemental effect (for freeze, burn, cold, poison and - // stun) on the respective difficulties. - ElementDuration1Normal int // called `El1Dur` in monstats.txt - ElementDuration1Nightmare int // called `El1Dur(N)` in monstats.txt - ElementDuration1Hell int // called `El1Dur(H)` in monstats.txt - ElementDuration2Normal int // called `El2Dur` in monstats.txt - ElementDuration2Nightmare int // called `El2Dur(N)` in monstats.txt - ElementDuration2Hell int // called `El2Dur(H)` in monstats.txt - ElementDuration3Normal int // called `El3Dur` in monstats.txt - ElementDuration3Nightmare int // called `El3Dur(N)` in monstats.txt - ElementDuration3Hell int // called `El3Dur(H)` in monstats.txt - - // SpecialEndDeath - // 0 == no special death - // 1 == spawn minion1 on death - // 2 == kill mounted minion on death (ie the guard tower) - SpecialEndDeath int // called `SplEndDeath` in monstats.txt - - // Enabled controls whenever the unit can be - // used at all for any purpose whatsoever. This is not the only setting - // that controls this; there are some other things that can also disable - // the unit (Rarity and isSpawn columns see those for description). - Enabled bool // called `enabled` in monstats.txt - - // SpawnsMinions tells the game whenever this - // unit is a “nest”. IE, monsters that spawn new monsters have this set to - // 1. Note that you can make any monster spawn new monsters, irregardless of - // its AI, all you need to do is adjust spawn related columns and make sure - // one of its skills is either “Nest” or “Minion Spawner”. - SpawnsMinions bool // called `placespawn` in monstats.txt - - // IsLeader controls if a monster is the leader of minions it spawns - // a leadercan order "raid on target" it causes group members to use - // SK1 instead of A1 and A2 modes while raiding. - IsLeader bool // called `SetBoss` in monstats.txt - - // TransferLeadership is connected with the previous one, - // when "boss of the group" is killed, the "leadership" is passed to one of - // his minions. - TransferLeadership bool // called `BossXfer` in monstats.txt - - // Boolean, 1=spawnable, 0=not spawnable. This controls whenever this unit - // can be spawned via Levels.txt. - IsLevelSpawnable bool // called `isSpawn` in monstats.txt - - // IsMelee controls whenever - // this unit can spawn with boss modifiers such as multiple shot or not. - IsMelee bool // called `isMelee` in monstats.txt - - // IsNPC controls whenever the unit is a NPC or not. - IsNpc bool // called `npc` in monstats.txt - - // IsInteractable - // controls whenever you can interact with this unit. IE this controls - // whenever it opens a speech-box or menu when you click on the unit. To - // turn units like Kaeleen or Flavie into enemies you will need to set this - // to 0 (you will also need to set NPC to 0 for that). - IsInteractable bool // called `interact` in monstats.txt - - // IsRanged tells the game whenever this is a ranged attacker. It will make it possible for - // monsters to spawn with multiple shot modifier. - IsRanged bool // called `rangedtype` in monstats.txt - - // HasInventory Controls whenever this - // NPC or UNIT can carry items with it. For NPCs this means that you can - // access their Inventory and buy items (if you disable this and then try to - // access this feature it will cause a crash so don’t do it unless you know - // what you’re doing). For Monsters this means that they can access their - // equipment data in MonEquip.txt. - HasInventory bool // called `inventory` in monstats.txt - - // CanEnterTown - // controls whenever enemies can follow you into a town or not. This should be set to - // 1 for everything that spawns in a town for obvious reasons. According to - // informations from Ogodei, it also disables/enables collision in - // singleplayer and allows pets to walk/not walk in city in multiplayer. - // In multiplayer collision is always set to 0 for pets. - CanEnterTown bool // called `inTown` in monstats.txt - - // IsUndeadLow, IsUndeadHigh - // Blizzard used this to differentiate High and Low Undead (IE low - // undead like Zombies, Skeletons etc are set to 1 here), both this and - // HUNDEAD will make the unit be considered undead. Low undeads can be - // resurrected by high undeads. High undeads can't resurrect eachother. - IsUndeadLow bool // called `lUndead` in monstats.txt - IsUndeadHigh bool // called `hUndead` in monstats.txt - - // IsDemon makes the game consider this unit a demon. - IsDemon bool // called `demon` in monstats.txt - - // IsFlying If you set this to 1 the monster will be able to move fly over - // obstacles such as puddles and rivers. - IsFlying bool // called `flying` in monstats.txt - - // CanOpenDoors controls whether monsters can open doors or not - CanOpenDoors bool // called `opendoors` in monstats.txt - - // IsSpecialBoss controls whenever this unit - // is a special boss, as mentioned already, monsters set as boss IGNORE the - // level settings, IE they will always spawn with the levels specified in - // MonStats.txt. Boss will gain some special resistances, such as immunity - // to being stunned (!!!), also it will not be affected by things like - // deadly strike the way normal monsters are. - IsSpecialBoss bool // called `boss` in monstats.txt - - // IsActBoss - // Setting this to 1 will give your monsters huge (300% IIRC) damage bonus - // against hirelings and summons. Ever wondered why Diablo destroys your - // skeletons with 1 fire nova while barely doing anything to you? Here is - // your answer. - IsActBoss bool // called `primeevil` in monstats.txt - - // IsKillable will make the monster absolutely unkillable. - IsKillable bool // called `killable` in monstats.txt - - // IsAiSwitchable Gives controls if this units mind may - // be altered by “mind altering skills” like Attract, Conversion, Revive - IsAiSwitchable bool // called `switchai` in monstats.txt - - // DisableAura Monsters set to 0 here - // will not be effected by friendly auras - DisableAura bool // called `noAura` in monstats.txt - - // DisableMultiShot - // This is another layer of security to prevent this modifier from spawning, - // besides the ISMELEE layer. - DisableMultiShot bool // called `nomultishot` in monstats.txt - - // DisableCounting - // prevents your pets from being counted as population in said area, for - // example thanks to this you can finish The Den Of Evil quest while having - // pets summoned. - DisableCounting bool // called `neverCount` in monstats.txt - - // IgnorePets - // Summons and hirelings are ignored by this unit, 0=Summons and - // hirelings are noticed by this unit. If you set this to 1 you will the - // monsters going directly for the player. - IgnorePets bool // called `petIgnore` in monstats.txt - - // DealsDamageOnDeath This works similar to corpse explosion (its based on - // hitpoints) and damages the surrounding players when the unit dies. (Ever - // wanted to prevent those undead stygian dolls from doing damage when they - // die, this is all there is to it) - DealsDamageOnDeath bool // called `deathDmg` in monstats.txt - - // GenericSpawn Has to do - // something is with minions being transformed into suicide minions, the - // exact purpose of this is a mystery. - GenericSpawn bool // called `genericSpawn` in monstats.txt - - // IgnoreMonLevelTxt Does this unit use - // MonLevel.txt or does it use the stats listed in MonStats.txt as is. - // Setting this to 1 will result in an array of problems, such as the - // appended elemental damage being completely ignored, irregardless of the - // values in it. - IgnoreMonLevelTxt bool // called `noRatio` in monstats.txt - - // CanBlockWithoutShield in order for a unit to - // block it needs the BL mode, if this is set to 1 then it will block - // irregardless of what modes it has. - CanBlockWithoutShield bool // called `NoShldBlock` in monstats.txt - - // SpecialGetModeChart - // Unknown but could be telling the game to look at some internal table. - // This is used for some Act Bosses and monsters like Putrid Defilers. - SpecialGetModeChart bool // called `SplGetModeChar` in monstats.txt - - // SpecialEndGeneric Works in conjunction with SPLCLIENTEND, this - // makes the unit untargetable when it is first spawned (used for those monsters that are under water, under ground or fly above you) - SpecialEndGeneric bool // called `SplEndGeneric` in monstats.txt - - // SpecialClientEnd Works in conjunction with SPLENDGENERIC, this - // makes the unit invisible when it is first spawned (used for those - // monsters that are under water, under ground or fly above you), this is - // also used for units that have other special drawing setups. - SpecialClientEnd bool // called `SplClientEnd` in monstats.txt - - } -) - -// MonStats stores all of the MonStat Records -var MonStats map[string]*MonStatsRecord //nolint:gochecknoglobals // Currently global by design, only written once - -// LoadMonStats loads monstats -func LoadMonStats(file []byte) { // nolint:funlen // Makes no sense to split - MonStats = make(map[string]*MonStatsRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &MonStatsRecord{ - Key: d.String("Id"), - ID: d.Number("hcIdx"), - BaseKey: d.String("BaseId"), - NextKey: d.String("NextInClass"), - PaletteId: d.Number("TransLvl"), - NameString: 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)) -} diff --git a/d2common/d2data/d2datadict/monstats2.go b/d2common/d2data/d2datadict/monstats2.go deleted file mode 100644 index 0113ecb0..00000000 --- a/d2common/d2data/d2datadict/monstats2.go +++ /dev/null @@ -1,339 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// MonStats2Record is a representation of a row from monstats2.txt -type MonStats2Record struct { - // Available options for equipment - // randomly selected from - EquipmentOptions [16][]string - - 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 - - // Number of directions for each mode - DirectionsPerMode [16]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 - HasComponent [16]bool - - // Available animation modes - HasAnimationMode [16]bool - - // Available modes while moving aside from WL and RN - A1mv bool - A2mv bool - SCmv bool - S1mv bool - S2mv bool - S3mv bool - S4mv bool - - // true of unit uses an automap entry - NoMap bool - - // If the units can use overlays - NoOvly bool - - // If unit is selectable - IsSelectable bool - - // If unit is selectable by allies - AllySelectable bool - - // If unit is not selectable - NotSelectable bool - - // Kinda unk, used for bonewalls etc that are not properly selectable - shiftSel bool - - // if the units corpse is selectable - IsCorpseSelectable bool - - // If the unit is attackable - IsAttackable bool - - // If the unit is revivable - IsRevivable bool - - // If the unit is a critter - IsCritter bool - - // If the unit is Small, Small units can be knocked back with 100% efficiency - IsSmall bool - - // Large units can be knocked back at 25% efficincy - IsLarge bool - - // Possibly to do with sound, usually set for creatures without flesh - IsSoft bool - - // Aggressive or harmless, usually NPC's - IsInert bool - - // Unknown - objCol bool - - // Enables collision on corpse for units - IsCorpseCollidable bool - - // Can the corpse be walked through - IsCorpseWalkable bool - - // If the unit casts a shadow - HasShadow bool - - // If unique palettes should not be used - NoUniqueShift bool - - // If multiple layers should be used on death (otherwise only TR) - CompositeDeath bool - - // Which skill is used for resurrection - -} - -// MonStats2 stores all of the MonStats2Records -//nolint:gochecknoglobals // Current design issue -var MonStats2 map[string]*MonStats2Record - -// LoadMonStats2 loads MonStats2Records from monstats2.txt -//nolint:funlen //just a big data loader -func LoadMonStats2(file []byte) { - MonStats2 = make(map[string]*MonStats2Record) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &MonStats2Record{ - 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"), - EquipmentOptions: [16][]string{ - d.List("HDv"), - d.List("TRv"), - d.List("LGv"), - d.List("Rav"), - d.List("Lav"), - d.List("RHv"), - d.List("LHv"), - d.List("SHv"), - d.List("S1v"), - d.List("S2v"), - d.List("S3v"), - d.List("S4v"), - d.List("S5v"), - d.List("S6v"), - d.List("S7v"), - d.List("S8v"), - }, - HasComponent: [16]bool{ - d.Bool("HD"), - d.Bool("TR"), - d.Bool("LG"), - d.Bool("RA"), - d.Bool("LA"), - d.Bool("RH"), - d.Bool("LH"), - d.Bool("SH"), - d.Bool("S1"), - d.Bool("S2"), - d.Bool("S3"), - d.Bool("S4"), - d.Bool("S5"), - d.Bool("S6"), - d.Bool("S7"), - d.Bool("S8"), - }, - TotalPieces: d.Number("TotalPieces"), - HasAnimationMode: [16]bool{ - d.Bool("mDT"), - d.Bool("mNU"), - d.Bool("mWL"), - d.Bool("mGH"), - d.Bool("mA1"), - d.Bool("mA2"), - d.Bool("mBL"), - d.Bool("mSC"), - d.Bool("mS1"), - d.Bool("mS2"), - d.Bool("mS3"), - d.Bool("mS4"), - d.Bool("mDD"), - d.Bool("mKB"), - d.Bool("mSQ"), - d.Bool("mRN"), - }, - DirectionsPerMode: [16]int{ - d.Number("dDT"), - d.Number("dNU"), - d.Number("dWL"), - d.Number("dGH"), - d.Number("dA1"), - d.Number("dA2"), - d.Number("dBL"), - d.Number("dSC"), - d.Number("dS1"), - d.Number("dS2"), - d.Number("dS3"), - d.Number("dS4"), - d.Number("dDD"), - d.Number("dKB"), - d.Number("dSQ"), - d.Number("dRN"), - }, - 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: 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)) -} - -//nolint:gochecknoglobals // better for lookup -var monsterAnimationModeLookup = map[string]d2enum.MonsterAnimationMode{ - d2enum.MonsterAnimationModeNeutral.String(): d2enum.MonsterAnimationModeNeutral, - d2enum.MonsterAnimationModeSkill1.String(): d2enum.MonsterAnimationModeSkill1, - d2enum.MonsterAnimationModeSequence.String(): d2enum.MonsterAnimationModeSequence, -} - -func monsterAnimationModeFromString(s string) d2enum.MonsterAnimationMode { - v, ok := monsterAnimationModeLookup[s] - if !ok { - log.Fatalf("unhandled MonsterAnimationMode %q", s) - return d2enum.MonsterAnimationModeNeutral - } - - return v -} diff --git a/d2common/d2data/d2datadict/monster_ai.go b/d2common/d2data/d2datadict/monster_ai.go deleted file mode 100644 index 761131f9..00000000 --- a/d2common/d2data/d2datadict/monster_ai.go +++ /dev/null @@ -1,34 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// MonsterAIRecord represents a single row from monai.txt -type MonsterAIRecord struct { - AI string -} - -// MonsterAI holds the MonsterAIRecords, The monai.txt file is a lookup table for unit AI codes -var MonsterAI map[string]*MonsterAIRecord //nolint:gochecknoglobals // Currently global by design - -// LoadMonsterAI loads MonsterAIRecords from monai.txt -func LoadMonsterAI(file []byte) { - MonsterAI = make(map[string]*MonsterAIRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &MonsterAIRecord{ - AI: d.String("AI"), - } - MonsterAI[record.AI] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d MonsterAI records", len(MonsterAI)) -} diff --git a/d2common/d2data/d2datadict/monster_equipment.go b/d2common/d2data/d2datadict/monster_equipment.go deleted file mode 100644 index 3aa82994..00000000 --- a/d2common/d2data/d2datadict/monster_equipment.go +++ /dev/null @@ -1,92 +0,0 @@ -package d2datadict - -import ( - "fmt" - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -const ( - numMonEquippedItems = 3 - fmtLocation = "loc%d" - fmtQuality = "mod%d" - fmtCode = "item%d" -) - -// MonsterEquipmentRecord represents a single line in monequip.txt -// Information gathered from [https://d2mods.info/forum/kb/viewarticle?a=365] -type MonsterEquipmentRecord struct { - // Name of monster, pointer to MonStats.txt - Name string - - // If true, monster is created by level, otherwise created by skill - OnInit bool - - // Not written in description, only appear on monsters with OnInit false, - // Level of skill for which this equipment row can be used? - Level int - - Equipment []*monEquip -} - -type monEquip struct { - // Code of item, probably from ItemCommonRecords - Code string - - // Location the body location of the item - Location string - - // Quality of the item - Quality int -} - -// MonsterEquipment stores the MonsterEquipmentRecords -var MonsterEquipment map[string][]*MonsterEquipmentRecord //nolint:gochecknoglobals // Currently global by design - -// LoadMonsterEquipment loads MonsterEquipmentRecords into MonsterEquipment -func LoadMonsterEquipment(file []byte) { - MonsterEquipment = make(map[string][]*MonsterEquipmentRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &MonsterEquipmentRecord{ - Name: d.String("monster"), - OnInit: d.Bool("oninit"), - Level: d.Number("level"), - Equipment: make([]*monEquip, 0), - } - - for idx := 0; idx < numMonEquippedItems; idx++ { - num := idx + 1 - code := d.String(fmt.Sprintf(fmtCode, num)) - location := d.String(fmt.Sprintf(fmtLocation, num)) - quality := d.Number(fmt.Sprintf(fmtQuality, num)) - - if code == "" { - continue - } - - equip := &monEquip{code, location, quality} - - record.Equipment = append(record.Equipment, equip) - } - - if _, ok := MonsterEquipment[record.Name]; !ok { - MonsterEquipment[record.Name] = make([]*MonsterEquipmentRecord, 0) - } - - MonsterEquipment[record.Name] = append(MonsterEquipment[record.Name], record) - } - - if d.Err != nil { - panic(d.Err) - } - - length := 0 - for k := range MonsterEquipment { - length += len(MonsterEquipment[k]) - } - - log.Printf("Loaded %d MonsterEquipment records", length) -} diff --git a/d2common/d2data/d2datadict/monster_level.go b/d2common/d2data/d2datadict/monster_level.go deleted file mode 100644 index 84b6c6c6..00000000 --- a/d2common/d2data/d2datadict/monster_level.go +++ /dev/null @@ -1,108 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// MonsterLevelRecord represents a single row in monlvl.txt -type MonsterLevelRecord struct { - - // The level - Level int - - // Values for Battle.net - BattleNet monsterDifficultyLevels - - // Values for ladder/single player/lan - Ladder monsterDifficultyLevels -} - -type monsterDifficultyLevels struct { - Normal monsterLevelValues - Nightmare monsterLevelValues - Hell monsterLevelValues -} - -type monsterLevelValues struct { - // DefenseRating AC is calcuated as (MonLvl.txt Ac * Monstats.txt AC) / 100) - DefenseRating int // also known as armor class - - // ToHit influences ToHit values for both attacks - // (MonLvl.txt TH * Monstats.txt A1TH - // and MonLvl.txt TH * Monstats.txt A2TH) / 100 - AttackRating int - - // Hitpoints, influences both minimum and maximum HP - // (MonLvl.txt HP * Monstats.txt minHP) / 100 - // and MonLvl.txt HP * Monstats.txt maxHP) / 100 - Hitpoints int - - // Damage, influences minimum and maximum damage for both attacks - // MonLvl.txt DM * Monstats.txt A1MinD) / 100 - // and MonLvl.txt DM * Monstats.txt A1MaxD) / 100 - // and MonLvl.txt DM * Monstats.txt A2MinD) / 100 - // and MonLvl.txt DM * Monstats.txt A2MaxD) / 100 - Damage int - - // Experience points, - // the formula is (MonLvl.txt XP * Monstats.txt Exp) / 100 - Experience int -} - -// MonsterLevels stores the MonsterLevelRecords -var MonsterLevels map[int]*MonsterLevelRecord //nolint:gochecknoglobals // Currently global by design - -// LoadMonsterLevels loads LoadMonsterLevelRecords into MonsterLevels -func LoadMonsterLevels(file []byte) { - MonsterLevels = make(map[int]*MonsterLevelRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &MonsterLevelRecord{ - Level: d.Number("Level"), - BattleNet: monsterDifficultyLevels{ - Normal: monsterLevelValues{ - Hitpoints: d.Number("HP"), - Damage: d.Number("DM"), - Experience: d.Number("XP"), - }, - Nightmare: monsterLevelValues{ - Hitpoints: d.Number("HP(N)"), - Damage: d.Number("DM(N)"), - Experience: d.Number("XP(N)"), - }, - Hell: monsterLevelValues{ - Hitpoints: d.Number("HP(H)"), - Damage: d.Number("DM(H)"), - Experience: d.Number("XP(H)"), - }, - }, - Ladder: monsterDifficultyLevels{ - Normal: monsterLevelValues{ - Hitpoints: d.Number("L-HP"), - Damage: d.Number("L-DM"), - Experience: d.Number("L-XP"), - }, - Nightmare: monsterLevelValues{ - Hitpoints: d.Number("L-HP(N)"), - Damage: d.Number("L-DM(N)"), - Experience: d.Number("L-XP(N)"), - }, - Hell: monsterLevelValues{ - Hitpoints: d.Number("L-HP(H)"), - Damage: d.Number("L-DM(H)"), - Experience: d.Number("L-XP(H)"), - }, - }, - } - MonsterLevels[record.Level] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d MonsterLevel records", len(MonsterLevels)) -} diff --git a/d2common/d2data/d2datadict/monster_placement.go b/d2common/d2data/d2datadict/monster_placement.go deleted file mode 100644 index 8d62960a..00000000 --- a/d2common/d2data/d2datadict/monster_placement.go +++ /dev/null @@ -1,27 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// MonsterPlacementRecord represents a line from MonPlace.txt. -type MonsterPlacementRecord string - -// MonsterPlacements stores the MonsterPlacementRecords. -var MonsterPlacements []MonsterPlacementRecord //nolint:gochecknoglobals // Currently global by design - -// LoadMonsterPlacements loads the MonsterPlacementRecords into MonsterPlacements. -func LoadMonsterPlacements(file []byte) { - d := d2txt.LoadDataDictionary(file) - for d.Next() { - MonsterPlacements = append(MonsterPlacements, MonsterPlacementRecord(d.String("code"))) - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d MonsterPlacement records", len(MonsterPlacements)) -} diff --git a/d2common/d2data/d2datadict/monster_sequence.go b/d2common/d2data/d2datadict/monster_sequence.go deleted file mode 100644 index d6ef1931..00000000 --- a/d2common/d2data/d2datadict/monster_sequence.go +++ /dev/null @@ -1,70 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// MonsterSequenceRecord contains a record for a monster sequence -// Composed of multiple lines from monseq.txt with the same name in the first column. -// Information gathered from [https://d2mods.info/forum/kb/viewarticle?a=395] -type MonsterSequenceRecord struct { - - // Name of the sequence, referred to by monstats.txt - Name string - - // Frames of this sequence - Frames []*MonsterSequenceFrame -} - -// MonsterSequenceFrame represents a single frame of a monster sequence -type MonsterSequenceFrame struct { - // The animation mode for this frame (refers to MonMode.txt) - Mode string - - // The frame of the animation mode used for this frame of the sequence - Frame int - - // Direction of the frame, enumerated by d2enum.AnimationFrameDirection - Direction int - - // Event triggered by this frame - Event int -} - -// MonsterSequences contains the MonsterSequenceRecords -// nolint:gochecknoglobals // Currently global by design -var MonsterSequences map[string]*MonsterSequenceRecord - -// LoadMonsterSequences loads the MonsterSequenceRecords into MonsterSequences -func LoadMonsterSequences(file []byte) { - MonsterSequences = make(map[string]*MonsterSequenceRecord) - - d := d2txt.LoadDataDictionary(file) - - for d.Next() { - name := d.String("sequence") - - if _, ok := MonsterSequences[name]; !ok { - record := &MonsterSequenceRecord{ - Name: name, - Frames: make([]*MonsterSequenceFrame, 0), - } - MonsterSequences[name] = record - } - - MonsterSequences[name].Frames = append(MonsterSequences[name].Frames, &MonsterSequenceFrame{ - Mode: d.String("mode"), - Frame: d.Number("frame"), - Direction: d.Number("dir"), - Event: d.Number("event"), - }) - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d MonsterSequence records", len(MonsterSequences)) -} diff --git a/d2common/d2data/d2datadict/monster_sound.go b/d2common/d2data/d2datadict/monster_sound.go deleted file mode 100644 index f0b8874e..00000000 --- a/d2common/d2data/d2datadict/monster_sound.go +++ /dev/null @@ -1,163 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// Information gathered from [https://d2mods.info/forum/kb/viewarticle?a=418] - -// MonsterSoundRecord represents a single line in MonSounds.txt -type MonsterSoundRecord struct { - // ID is the identifier, used in MonStats.txt to refer to a particular sound record - ID string - - // Melee attack sound ID, refers to a sound from Sounds.txt - Attack1 string - - // Weapon attack sound ID, refers to a sound from Sounds.txt - Weapon1 string - - // Delay in frames of Attack1 sound - Attack1Delay int - - // Delay in frames of Weapon1 sound - Weapon1Delay int - - // Probability of playing Attack1 sound instead of Weapon1 - Attack1Probability int - - // Overrides weapon volume from Sounds.txt - Weapon1Volume int - - // Ditto, 2 sets of sounds are possible - Attack2 string - Weapon2 string - Attack2Delay int - Weapon2Delay int - Attack2Probability int - Weapon2Volume int - - // Sound when monster takes a hit, refers to a sound from Sounds.txt - HitSound string - - // Sound when monster dies, refers to a sound from Sounds.txt - DeathSound string - - // Delay in frames of HitSound - HitDelay int - - // Delay in frames of DeathSound - DeaDelay int - - // Sound when monster enters skill mode - Skill1 string - Skill2 string - Skill3 string - Skill4 string - - // Sound played each loop of the WL animation - Footstep string - - // Additional WL animation sound - FootstepLayer string - - // Number of footstep sounds played (e.g. 2 for two-legged monsters) - FootstepCount int - - // FsOff, possibly delay between footstep sounds - FootstepOffset int - - // Probability of playing footstep sound, percentage - FootstepProbability int - - // Sound when monster is neutral (also played when walking) - Neutral string - - // Delay in frames between neutral sounds - NeutralTime int - - // Sound when monster is initialized - Init string - - // Sound when monster is encountered - Taunt string - - // Sound when monster retreats - Flee string - - // The following are related to skills in some way - // Initial monster animation code (MonMode.txt) - CvtMo1 string - // ID of skill - CvtSk1 string - // End monster animation code (MonMode.txt) - CvtTgt1 string - - CvtMo2 string - CvtSk2 string - CvtTgt2 string - - CvtMo3 string - CvtSk3 string - CvtTgt3 string -} - -// MonsterSounds stores the MonsterSoundRecords -//nolint:gochecknoglobals // Currently global by design -var MonsterSounds map[string]*MonsterSoundRecord - -// LoadMonsterSounds loads MonsterSoundRecords into MonsterSounds -func LoadMonsterSounds(file []byte) { - MonsterSounds = make(map[string]*MonsterSoundRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &MonsterSoundRecord{ - ID: d.String("Id"), - Attack1: d.String("Attack1"), - Weapon1: d.String("Weapon1"), - Attack1Delay: d.Number("Att1Del"), - Weapon1Delay: d.Number("Wea1Del"), - Attack1Probability: d.Number("Att1Prb"), - Weapon1Volume: d.Number("Wea1Vol"), - Attack2: d.String("Attack2"), - Weapon2: d.String("Weapon2"), - Attack2Delay: d.Number("Att2Del"), - Weapon2Delay: d.Number("Wea2Del"), - Attack2Probability: d.Number("Att2Prb"), - Weapon2Volume: d.Number("Wea2Vol"), - Skill1: d.String("Skill1"), - Skill2: d.String("Skill2"), - Skill3: d.String("Skill3"), - Skill4: d.String("Skill4"), - Footstep: d.String("Footstep"), - FootstepLayer: d.String("FootstepLayer"), - FootstepCount: d.Number("FsCnt"), - FootstepOffset: d.Number("FsOff"), - FootstepProbability: d.Number("FsPrb"), - Neutral: d.String("Neutral"), - NeutralTime: d.Number("NeuTime"), - Init: d.String("Init"), - Taunt: d.String("Taunt"), - Flee: d.String("Flee"), - CvtMo1: d.String("CvtMo1"), - CvtMo2: d.String("CvtMo2"), - CvtMo3: d.String("CvtMo3"), - CvtSk1: d.String("CvtSk1"), - CvtSk2: d.String("CvtSk2"), - CvtSk3: d.String("CvtSk3"), - CvtTgt1: d.String("CvtTgt1"), - CvtTgt2: d.String("CvtTgt2"), - CvtTgt3: d.String("CvtTgt3"), - } - MonsterSounds[record.ID] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d MonsterUniqueModifier records", len(MonsterUniqueModifiers)) -} diff --git a/d2common/d2data/d2datadict/monster_unique_modifiers.go b/d2common/d2data/d2datadict/monster_unique_modifiers.go deleted file mode 100644 index 1d24c7f9..00000000 --- a/d2common/d2data/d2datadict/monster_unique_modifiers.go +++ /dev/null @@ -1,119 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -const ( - numModifierConstants = 34 -) - -// MonsterUniqueModifierRecord represents a single line in monumod.txt -// Information gathered from [https://d2mods.info/forum/kb/viewarticle?a=161] -type MonsterUniqueModifierRecord struct { - // Name of modifer, not used by other files - Name string - - // ID of the modifier, - // the Mod fields of SuperUniqueRecord refer to these ID's - ID int - - // Enabled boolean for whether this modifier can be applied - Enabled bool - - // ExpansionOnly boolean for whether this modifier can only be applied in an expansion game. - // In the file, the value 100 represents expansion only - ExpansionOnly bool - - // If true, "Minion" will be displayed below the life bar of minions of - // the monster with this modifier - Xfer bool - - // Champion boolean, only usable by champion monsters - Champion bool - - // FPick Unknown - FPick int - - // Exclude1 monster type code that cannot have this modifier - Exclude1 string - - // Exclude2 monster type code that cannot have this modifier - Exclude2 string - - PickFrequencies struct { - Normal *pickFreq - Nightmare *pickFreq - Hell *pickFreq - } -} - -type pickFreq struct { - // Champion pick frequency - Champion int - - // Unique pick frequency - Unique int -} - -// MonsterUniqueModifiers stores the MonsterUniqueModifierRecords -var MonsterUniqueModifiers map[string]*MonsterUniqueModifierRecord //nolint:gochecknoglobals // Currently global by design - -// MonsterUniqueModifierConstants contains constants from monumod.txt, -// can be accessed with indices from d2enum.MonUModConstIndex -var MonsterUniqueModifierConstants []int //nolint:gochecknoglobals // currently global by design - -// See [https://d2mods.info/forum/kb/viewarticle?a=161] for more info - -// LoadMonsterUniqueModifiers loads MonsterUniqueModifierRecords into MonsterUniqueModifiers -func LoadMonsterUniqueModifiers(file []byte) { - MonsterUniqueModifiers = make(map[string]*MonsterUniqueModifierRecord) - MonsterUniqueModifierConstants = make([]int, 0, numModifierConstants) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &MonsterUniqueModifierRecord{ - Name: d.String("uniquemod"), - ID: d.Number("id"), - Enabled: d.Bool("enabled"), - ExpansionOnly: d.Number("version") == expansionCode, - Xfer: d.Bool("xfer"), - Champion: d.Bool("champion"), - FPick: d.Number("fpick"), - Exclude1: d.String("exclude1"), - Exclude2: d.String("exclude2"), - PickFrequencies: struct { - Normal *pickFreq - Nightmare *pickFreq - Hell *pickFreq - }{ - Normal: &pickFreq{ - Champion: d.Number("cpick"), - Unique: d.Number("upick"), - }, - Nightmare: &pickFreq{ - Champion: d.Number("cpick (N)"), - Unique: d.Number("upick (N)"), - }, - Hell: &pickFreq{ - Champion: d.Number("cpick (H)"), - Unique: d.Number("upick (H)"), - }, - }, - } - - MonsterUniqueModifiers[record.Name] = record - - if len(MonsterUniqueModifierConstants) < numModifierConstants { - MonsterUniqueModifierConstants = append(MonsterUniqueModifierConstants, d.Number("constants")) - } - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d MonsterUniqueModifier records", len(MonsterUniqueModifiers)) -} diff --git a/d2common/d2data/d2datadict/montype.go b/d2common/d2data/d2datadict/montype.go deleted file mode 100644 index dfaffaf2..00000000 --- a/d2common/d2data/d2datadict/montype.go +++ /dev/null @@ -1,47 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// MonTypeRecord is a representation of a single row of MonType.txt. -type MonTypeRecord struct { - Type string - Equiv1 string - Equiv2 string - Equiv3 string - // StrSing is the string displayed for the singular form (Skeleton), note - // that this is unused in the original engine, since the only modifier - // display code that accesses MonType uses StrPlur. - StrSing string - StrPlural string -} - -// MonTypes stores all of the MonTypeRecords -var MonTypes map[string]*MonTypeRecord //nolint:gochecknoglobals // Currently global by design, only written once - -// LoadMonTypes loads MonType records into a map[string]*MonTypeRecord -func LoadMonTypes(file []byte) { - MonTypes = make(map[string]*MonTypeRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &MonTypeRecord{ - Type: d.String("type"), - Equiv1: d.String("equiv1"), - Equiv2: d.String("equiv2"), - Equiv3: d.String("equiv3"), - StrSing: d.String("strsing"), - StrPlural: d.String("strplur"), - } - MonTypes[record.Type] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d MonType records", len(MonTypes)) -} diff --git a/d2common/d2data/d2datadict/npc.go b/d2common/d2data/d2datadict/npc.go deleted file mode 100644 index a3f34039..00000000 --- a/d2common/d2data/d2datadict/npc.go +++ /dev/null @@ -1,107 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -const ( - costDivisor = 1024. -) - -// NPCRecord represents a single line in NPC.txt -// The information has been gathered from [https:// d2mods.info/forum/kb/viewarticle?a=387] -type NPCRecord struct { - // Name is an ID pointer to row of this npc in monstats.txt - Name string - - Multipliers *costMultiplier - - QuestMultipliers map[int]*costMultiplier - - // MaxBuy is the maximum amount of gold an NPC will pay for an item for the corresponding - // difficulty - MaxBuy struct { - Normal int - Nightmare int - Hell int - } -} - -type costMultiplier struct { - // Buy is a percentage of base item price used when an item is bought by NPC - Buy float64 - - // Sell is a percentage of base item price used when an item is sold by NPC - Sell float64 - - // Repair is a percentage of base item price used to calculate the base repair price - Repair float64 -} - -// NPCs stores the NPCRecords -var NPCs map[string]*NPCRecord // nolint:gochecknoglobals // Currently global by design - -// LoadNPCs loads NPCRecords into NPCs -func LoadNPCs(file []byte) { - NPCs = make(map[string]*NPCRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &NPCRecord{ - Name: d.String("npc"), - Multipliers: &costMultiplier{ - Buy: float64(d.Number("buy mult")) / costDivisor, - Sell: float64(d.Number("sell mult")) / costDivisor, - Repair: float64(d.Number("rep mult")) / costDivisor, - }, - MaxBuy: struct { - Normal int - Nightmare int - Hell int - }{ - Normal: d.Number("max buy"), - Nightmare: d.Number("max buy (N)"), - Hell: d.Number("max buy (H)"), - }, - } - - record.QuestMultipliers = make(map[int]*costMultiplier) - - if flagStr := d.String("questflag A"); flagStr != "" { - flag := d.Number("questflag A") - record.QuestMultipliers[flag] = &costMultiplier{ - float64(d.Number("questbuymult A")) / costDivisor, - float64(d.Number("questsellmult A")) / costDivisor, - float64(d.Number("questrepmult A")) / costDivisor, - } - } - - if flagStr := d.String("questflag B"); flagStr != "" { - flag := d.Number("questflag B") - record.QuestMultipliers[flag] = &costMultiplier{ - float64(d.Number("questbuymult B")) / costDivisor, - float64(d.Number("questsellmult B")) / costDivisor, - float64(d.Number("questrepmult B")) / costDivisor, - } - } - - if flagStr := d.String("questflag C"); flagStr != "" { - flag := d.Number("questflag C") - record.QuestMultipliers[flag] = &costMultiplier{ - float64(d.Number("questbuymult C")) / costDivisor, - float64(d.Number("questsellmult C")) / costDivisor, - float64(d.Number("questrepmult C")) / costDivisor, - } - } - - NPCs[record.Name] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d NPC records", len(NPCs)) -} diff --git a/d2common/d2data/d2datadict/skilldesc.go b/d2common/d2data/d2datadict/skilldesc.go deleted file mode 100644 index b2d156bf..00000000 --- a/d2common/d2data/d2datadict/skilldesc.go +++ /dev/null @@ -1,266 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation/d2parser" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// SkillDescriptionRecord is a single row from skilldesc.txt and is used for -// generating text strings for skills. -type SkillDescriptionRecord struct { - Name string // skilldesc - SkillPage string // SkillPage - SkillRow string // SkillRow - SkillColumn string // SkillColumn - ListRow string // ListRow - ListPool string // ListPool - IconCel int // IconCel - NameKey string // str name - ShortKey string // str short - LongKey string // str long - AltKey string // str alt - ManaKey string // str mana - Descdam string // descdam - DdamCalc1 d2calculation.Calculation // ddam calc1 - DdamCalc2 d2calculation.Calculation // ddam calc2 - P1dmelem string // p1dmelem - P1dmmin d2calculation.Calculation // p1dmmin - P1dmmax d2calculation.Calculation // p1dmmax - P2dmelem string // p2dmelem - P2dmmin d2calculation.Calculation // p2dmmin - P2dmmax d2calculation.Calculation // p2dmmax - P3dmelem string // p3dmelem - P3dmmin d2calculation.Calculation // p3dmmin - P3dmmax d2calculation.Calculation // p3dmmax - Descatt string // descatt - Descmissile1 string // descmissile1 - Descmissile2 string // descmissile2 - Descmissile3 string // descmissile3 - Descline1 string // descline1 - Desctexta1 string // desctexta1 - Desctextb1 string // desctextb1 - Desccalca1 d2calculation.Calculation // desccalca1 - Desccalcb1 d2calculation.Calculation // desccalcb1 - Descline2 string // descline2 - Desctexta2 string // desctexta2 - Desctextb2 string // desctextb2 - Desccalca2 d2calculation.Calculation // desccalca2 - Desccalcb2 d2calculation.Calculation // desccalcb2 - Descline3 string // descline3 - Desctexta3 string // desctexta3 - Desctextb3 string // desctextb3 - Desccalca3 d2calculation.Calculation // desccalca3 - Desccalcb3 d2calculation.Calculation // desccalcb3 - Descline4 string // descline4 - Desctexta4 string // desctexta4 - Desctextb4 string // desctextb4 - Desccalca4 d2calculation.Calculation // desccalca4 - Desccalcb4 d2calculation.Calculation // desccalcb4 - Descline5 string // descline5 - Desctexta5 string // desctexta5 - Desctextb5 string // desctextb5 - Desccalca5 d2calculation.Calculation // desccalca5 - Desccalcb5 d2calculation.Calculation // desccalcb5 - Descline6 string // descline6 - Desctexta6 string // desctexta6 - Desctextb6 string // desctextb6 - Desccalca6 d2calculation.Calculation // desccalca6 - Desccalcb6 d2calculation.Calculation // desccalcb6 - Dsc2line1 string // dsc2line1 - Dsc2texta1 string // dsc2texta1 - Dsc2textb1 string // dsc2textb1 - Dsc2calca1 d2calculation.Calculation // dsc2calca1 - Dsc2calcb1 d2calculation.Calculation // dsc2calcb1 - Dsc2line2 string // dsc2line2 - Dsc2texta2 string // dsc2texta2 - Dsc2textb2 string // dsc2textb2 - Dsc2calca2 d2calculation.Calculation // dsc2calca2 - Dsc2calcb2 d2calculation.Calculation // dsc2calcb2 - Dsc2line3 string // dsc2line3 - Dsc2texta3 string // dsc2texta3 - Dsc2textb3 string // dsc2textb3 - Dsc2calca3 d2calculation.Calculation // dsc2calca3 - Dsc2calcb3 d2calculation.Calculation // dsc2calcb3 - Dsc2line4 string // dsc2line4 - Dsc2texta4 string // dsc2texta4 - Dsc2textb4 string // dsc2textb4 - Dsc2calca4 d2calculation.Calculation // dsc2calca4 - Dsc2calcb4 d2calculation.Calculation // dsc2calcb4 - Dsc3line1 string // dsc3line1 - Dsc3texta1 string // dsc3texta1 - Dsc3textb1 string // dsc3textb1 - Dsc3calca1 d2calculation.Calculation // dsc3calca1 - Dsc3calcb1 d2calculation.Calculation // dsc3calcb1 - Dsc3line2 string // dsc3line2 - Dsc3texta2 string // dsc3texta2 - Dsc3textb2 string // dsc3textb2 - Dsc3calca2 d2calculation.Calculation // dsc3calca2 - Dsc3calcb2 d2calculation.Calculation // dsc3calcb2 - Dsc3line3 string // dsc3line3 - Dsc3texta3 string // dsc3texta3 - Dsc3textb3 string // dsc3textb3 - Dsc3calca3 d2calculation.Calculation // dsc3calca3 - Dsc3calcb3 d2calculation.Calculation // dsc3calcb3 - Dsc3line4 string // dsc3line4 - Dsc3texta4 string // dsc3texta4 - Dsc3textb4 string // dsc3textb4 - Dsc3calca4 d2calculation.Calculation // dsc3calca4 - Dsc3calcb4 d2calculation.Calculation // dsc3calcb4 - Dsc3line5 string // dsc3line5 - Dsc3texta5 string // dsc3texta5 - Dsc3textb5 string // dsc3textb5 - Dsc3calca5 d2calculation.Calculation // dsc3calca5 - Dsc3calcb5 d2calculation.Calculation // dsc3calcb5 - Dsc3line6 string // dsc3line6 - Dsc3texta6 string // dsc3texta6 - Dsc3textb6 string // dsc3textb6 - Dsc3calca6 d2calculation.Calculation // dsc3calca6 - Dsc3calcb6 d2calculation.Calculation // dsc3calcb6 - Dsc3line7 string // dsc3line7 - Dsc3texta7 string // dsc3texta7 - Dsc3textb7 string // dsc3textb7 - Dsc3calca7 d2calculation.Calculation // dsc3calca7 - Dsc3calcb7 d2calculation.Calculation // dsc3calcb7 -} - -// SkillDescriptions stores all of the SkillDescriptionRecords -//nolint:gochecknoglobals // Currently global by design -var SkillDescriptions map[string]*SkillDescriptionRecord - -// LoadSkillDescriptions loads skill description records from skilldesc.txt -func LoadSkillDescriptions(file []byte) { //nolint:funlen // doesn't make sense to split - SkillDescriptions = make(map[string]*SkillDescriptionRecord) - - parser := d2parser.New() - parser.SetCurrentReference("skill", "TODO: connect skill with description!") //nolint:godox // TODO: Connect skill with description. - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &SkillDescriptionRecord{ - d.String("skilldesc"), - d.String("SkillPage"), - d.String("SkillRow"), - d.String("SkillColumn"), - d.String("ListRow"), - d.String("ListPool"), - d.Number("IconCel"), - d.String("str name"), - d.String("str short"), - d.String("str long"), - d.String("str alt"), - d.String("str mana"), - d.String("descdam"), - parser.Parse(d.String("ddam calc1")), - parser.Parse(d.String("ddam calc2")), - d.String("p1dmelem"), - parser.Parse(d.String("p1dmmin")), - parser.Parse(d.String("p1dmmax")), - d.String("p2dmelem"), - parser.Parse(d.String("p2dmmin")), - parser.Parse(d.String("p2dmmax")), - d.String("p3dmelem"), - parser.Parse(d.String("p3dmmin")), - parser.Parse(d.String("p3dmmax")), - d.String("descatt"), - d.String("descmissile1"), - d.String("descmissile2"), - d.String("descmissile3"), - d.String("descline1"), - d.String("desctexta1"), - d.String("desctextb1"), - parser.Parse(d.String("desccalca1")), - parser.Parse(d.String("desccalcb1")), - d.String("descline2"), - d.String("desctexta2"), - d.String("desctextb2"), - parser.Parse(d.String("desccalca2")), - parser.Parse(d.String("desccalcb2")), - d.String("descline3"), - d.String("desctexta3"), - d.String("desctextb3"), - parser.Parse(d.String("desccalca3")), - parser.Parse(d.String("desccalcb3")), - d.String("descline4"), - d.String("desctexta4"), - d.String("desctextb4"), - parser.Parse(d.String("desccalca4")), - parser.Parse(d.String("desccalcb4")), - d.String("descline5"), - d.String("desctexta5"), - d.String("desctextb5"), - parser.Parse(d.String("desccalca5")), - parser.Parse(d.String("desccalcb5")), - d.String("descline6"), - d.String("desctexta6"), - d.String("desctextb6"), - parser.Parse(d.String("desccalca6")), - parser.Parse(d.String("desccalcb6")), - d.String("dsc2line1"), - d.String("dsc2texta1"), - d.String("dsc2textb1"), - parser.Parse(d.String("dsc2calca1")), - parser.Parse(d.String("dsc2calcb1")), - d.String("dsc2line2"), - d.String("dsc2texta2"), - d.String("dsc2textb2"), - parser.Parse(d.String("dsc2calca2")), - parser.Parse(d.String("dsc2calcb2")), - d.String("dsc2line3"), - d.String("dsc2texta3"), - d.String("dsc2textb3"), - parser.Parse(d.String("dsc2calca3")), - parser.Parse(d.String("dsc2calcb3")), - d.String("dsc2line4"), - d.String("dsc2texta4"), - d.String("dsc2textb4"), - parser.Parse(d.String("dsc2calca4")), - parser.Parse(d.String("dsc2calcb4")), - d.String("dsc3line1"), - d.String("dsc3texta1"), - d.String("dsc3textb1"), - parser.Parse(d.String("dsc3calca1")), - parser.Parse(d.String("dsc3calcb1")), - d.String("dsc3line2"), - d.String("dsc3texta2"), - d.String("dsc3textb2"), - parser.Parse(d.String("dsc3calca2")), - parser.Parse(d.String("dsc3calcb2")), - d.String("dsc3line3"), - d.String("dsc3texta3"), - d.String("dsc3textb3"), - parser.Parse(d.String("dsc3calca3")), - parser.Parse(d.String("dsc3calcb3")), - d.String("dsc3line4"), - d.String("dsc3texta4"), - d.String("dsc3textb4"), - parser.Parse(d.String("dsc3calca4")), - parser.Parse(d.String("dsc3calcb4")), - d.String("dsc3line5"), - d.String("dsc3texta5"), - d.String("dsc3textb5"), - parser.Parse(d.String("dsc3calca5")), - parser.Parse(d.String("dsc3calcb5")), - d.String("dsc3line6"), - d.String("dsc3texta6"), - d.String("dsc3textb6"), - parser.Parse(d.String("dsc3calca6")), - parser.Parse(d.String("dsc3calcb6")), - d.String("dsc3line7"), - d.String("dsc3texta7"), - d.String("dsc3textb7"), - parser.Parse(d.String("dsc3calca7")), - parser.Parse(d.String("dsc3calcb7")), - } - - SkillDescriptions[record.Name] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d Skill Description records", len(SkillDescriptions)) -} diff --git a/d2common/d2data/d2datadict/skills.go b/d2common/d2data/d2datadict/skills.go deleted file mode 100644 index ecb4d9d1..00000000 --- a/d2common/d2data/d2datadict/skills.go +++ /dev/null @@ -1,534 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation/d2parser" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// SkillDetails has all of the SkillRecords -//nolint:gochecknoglobals // Currently global by design, only written once -var SkillDetails map[int]*SkillRecord - -var skillDetailsByName map[string]*SkillRecord - -// SkillRecord is a row from the skills.txt file. Here are two resources for more info on each field -// [https://d2mods.info/forum/viewtopic.php?t=41556, https://d2mods.info/forum/kb/viewarticle?a=246] -type SkillRecord struct { - Skill string - Charclass string - Skilldesc string - Prgcalc1 d2calculation.Calculation - Prgcalc2 d2calculation.Calculation - Prgcalc3 d2calculation.Calculation - Srvmissile string - Srvmissilea string - Srvmissileb string - Srvmissilec string - Srvoverlay string - Aurastate string - Auratargetstate string - Auralencalc d2calculation.Calculation - Aurarangecalc d2calculation.Calculation - Aurastat1 string - Aurastatcalc1 d2calculation.Calculation - Aurastat2 string - Aurastatcalc2 d2calculation.Calculation - Aurastat3 string - Aurastatcalc3 d2calculation.Calculation - Aurastat4 string - Aurastatcalc4 d2calculation.Calculation - Aurastat5 string - Aurastatcalc5 d2calculation.Calculation - Aurastat6 string - Aurastatcalc6 d2calculation.Calculation - Auraevent1 string - Auraevent2 string - Auraevent3 string - Auratgtevent string - Auratgteventfunc string - Passivestate string - Passiveitype string - Passivestat1 string - Passivecalc1 d2calculation.Calculation - Passivestat2 string - Passivecalc2 d2calculation.Calculation - Passivestat3 string - Passivecalc3 d2calculation.Calculation - Passivestat4 string - Passivecalc4 d2calculation.Calculation - Passivestat5 string - Passivecalc5 d2calculation.Calculation - Passiveevent string - Passiveeventfunc string - Summon string - Pettype string - Petmax d2calculation.Calculation - Summode string - Sumskill1 string - Sumsk1calc d2calculation.Calculation - Sumskill2 string - Sumsk2calc d2calculation.Calculation - Sumskill3 string - Sumsk3calc d2calculation.Calculation - Sumskill4 string - Sumsk4calc d2calculation.Calculation - Sumskill5 string - Sumsk5calc d2calculation.Calculation - Sumoverlay string - Stsound string - Stsoundclass string - Dosound string - DosoundA string - DosoundB string - Tgtoverlay string - Tgtsound string - Prgoverlay string - Prgsound string - Castoverlay string - Cltoverlaya string - Cltoverlayb string - Cltmissile string - Cltmissilea string - Cltmissileb string - Cltmissilec string - Cltmissiled string - Cltcalc1 d2calculation.Calculation - Cltcalc2 d2calculation.Calculation - Cltcalc3 d2calculation.Calculation - Range string - Itypea1 string - Itypea2 string - Itypea3 string - Etypea1 string - Etypea2 string - Itypeb1 string - Itypeb2 string - Itypeb3 string - Etypeb1 string - Etypeb2 string - Anim string - Seqtrans string - Monanim string - ItemCastSound string - ItemCastOverlay string - Skpoints d2calculation.Calculation - Reqskill1 string - Reqskill2 string - Reqskill3 string - State1 string - State2 string - State3 string - Perdelay d2calculation.Calculation - Calc1 d2calculation.Calculation - Calc2 d2calculation.Calculation - Calc3 d2calculation.Calculation - Calc4 d2calculation.Calculation - ToHitCalc d2calculation.Calculation - DmgSymPerCalc d2calculation.Calculation - EType string - EDmgSymPerCalc d2calculation.Calculation - ELenSymPerCalc d2calculation.Calculation - ID int - Srvstfunc int - Srvdofunc int - Srvprgfunc1 int - Srvprgfunc2 int - Srvprgfunc3 int - Prgdam int - Aurafilter int - Auraeventfunc1 int - Auraeventfunc2 int - Auraeventfunc3 int - Sumumod int - Cltstfunc int - Cltdofunc int - Cltprgfunc1 int - Cltprgfunc2 int - Cltprgfunc3 int - Attackrank int - Weapsel int - Seqnum int - Seqinput int - LineOfSight int - SelectProc int - ItemEffect int - ItemCltEffect int - ItemTgtDo int - ItemTarget int - Reqlevel int - Maxlvl int - Reqstr int - Reqdex int - Reqint int - Reqvit int - Restrict int - Delay int - Checkfunc int - Startmana int - Minmana int - Manashift int - Mana int - Lvlmana int - Param1 int - Param2 int - Param3 int - Param4 int - Param5 int - Param6 int - Param7 int - Param8 int - ToHit int - LevToHit int - ResultFlags int - HitFlags int - HitClass int - HitShift int - SrcDam int - MinDam int - MinLevDam1 int - MinLevDam2 int - MinLevDam3 int - MinLevDam4 int - MinLevDam5 int - MaxDam int - MaxLevDam1 int - MaxLevDam2 int - MaxLevDam3 int - MaxLevDam4 int - MaxLevDam5 int - EMin int - EMinLev1 int - EMinLev2 int - EMinLev3 int - EMinLev4 int - EMinLev5 int - EMax int - EMaxLev1 int - EMaxLev2 int - EMaxLev3 int - EMaxLev4 int - EMaxLev5 int - ELen int - ELevLen1 int - ELevLen2 int - ELevLen3 int - Aitype int - Aibonus int - CostMult int - CostAdd int - Prgstack bool - Decquant bool - Lob bool - Stsuccessonly bool - Stsounddelay bool - Weaponsnd bool - Warp bool - Immediate bool - Enhanceable bool - Noammo bool - Durability bool - UseAttackRate bool - TargetableOnly bool - SearchEnemyXY bool - SearchEnemyNear bool - SearchOpenXY bool - TargetCorpse bool - TargetPet bool - TargetAlly bool - TargetItem bool - AttackNoMana bool - TgtPlaceCheck bool - ItemCheckStart bool - ItemCltCheckStart bool - Leftskill bool - Repeat bool - Nocostinstate bool - Usemanaondo bool - Interrupt bool - InTown bool - Aura bool - Periodic bool - Finishing bool - Passive bool - Progressive bool - General bool - Scroll bool - InGame bool - Kick bool -} - -// LoadSkills loads skills.txt file contents into a skill record map -//nolint:funlen // Makes no sense to split -// LoadCharStats loads charstats.txt file contents into map[d2enum.Hero]*CharStatsRecord -func LoadSkills(file []byte) { - SkillDetails = make(map[int]*SkillRecord) - skillDetailsByName = make(map[string]*SkillRecord) - - parser := d2parser.New() - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - name := d.String("skill") - parser.SetCurrentReference("skill", name) - - record := &SkillRecord{ - Skill: d.String("skill"), - ID: d.Number("Id"), - Charclass: d.String("charclass"), - Skilldesc: d.String("skilldesc"), - Srvstfunc: d.Number("srvstfunc"), - Srvdofunc: d.Number("srvdofunc"), - Prgstack: d.Bool("prgstack"), - Srvprgfunc1: d.Number("srvprgfunc1"), - Srvprgfunc2: d.Number("srvprgfunc2"), - Srvprgfunc3: d.Number("srvprgfunc3"), - Prgcalc1: parser.Parse(d.String("prgcalc1")), - Prgcalc2: parser.Parse(d.String("prgcalc2")), - Prgcalc3: parser.Parse(d.String("prgcalc3")), - Prgdam: d.Number("prgdam"), - Srvmissile: d.String("srvmissile"), - Decquant: d.Bool("decquant"), - Lob: d.Bool("lob"), - Srvmissilea: d.String("srvmissilea"), - Srvmissileb: d.String("srvmissileb"), - Srvmissilec: d.String("srvmissilec"), - Srvoverlay: d.String("srvoverlay"), - Aurafilter: d.Number("aurafilter"), - Aurastate: d.String("aurastate"), - Auratargetstate: d.String("auratargetstate"), - Auralencalc: parser.Parse(d.String("auralencalc")), - Aurarangecalc: parser.Parse(d.String("aurarangecalc")), - Aurastat1: d.String("aurastat1"), - Aurastatcalc1: parser.Parse(d.String("aurastatcalc1")), - Aurastat2: d.String("aurastat2"), - Aurastatcalc2: parser.Parse(d.String("aurastatcalc2")), - Aurastat3: d.String("aurastat3"), - Aurastatcalc3: parser.Parse(d.String("aurastatcalc3")), - Aurastat4: d.String("aurastat4"), - Aurastatcalc4: parser.Parse(d.String("aurastatcalc4")), - Aurastat5: d.String("aurastat5"), - Aurastatcalc5: parser.Parse(d.String("aurastatcalc5")), - Aurastat6: d.String("aurastat6"), - Aurastatcalc6: parser.Parse(d.String("aurastatcalc6")), - Auraevent1: d.String("auraevent1"), - Auraeventfunc1: d.Number("auraeventfunc1"), - Auraevent2: d.String("auraevent2"), - Auraeventfunc2: d.Number("auraeventfunc2"), - Auraevent3: d.String("auraevent3"), - Auraeventfunc3: d.Number("auraeventfunc3"), - Auratgtevent: d.String("auratgtevent"), - Auratgteventfunc: d.String("auratgteventfunc"), - Passivestate: d.String("passivestate"), - Passiveitype: d.String("passiveitype"), - Passivestat1: d.String("passivestat1"), - Passivecalc1: parser.Parse(d.String("passivecalc1")), - Passivestat2: d.String("passivestat2"), - Passivecalc2: parser.Parse(d.String("passivecalc2")), - Passivestat3: d.String("passivestat3"), - Passivecalc3: parser.Parse(d.String("passivecalc3")), - Passivestat4: d.String("passivestat4"), - Passivecalc4: parser.Parse(d.String("passivecalc4")), - Passivestat5: d.String("passivestat5"), - Passivecalc5: parser.Parse(d.String("passivecalc5")), - Passiveevent: d.String("passiveevent"), - Passiveeventfunc: d.String("passiveeventfunc"), - Summon: d.String("summon"), - Pettype: d.String("pettype"), - Petmax: parser.Parse(d.String("petmax")), - Summode: d.String("summode"), - Sumskill1: d.String("sumskill1"), - Sumsk1calc: parser.Parse(d.String("sumsk1calc")), - Sumskill2: d.String("sumskill2"), - Sumsk2calc: parser.Parse(d.String("sumsk2calc")), - Sumskill3: d.String("sumskill3"), - Sumsk3calc: parser.Parse(d.String("sumsk3calc")), - Sumskill4: d.String("sumskill4"), - Sumsk4calc: parser.Parse(d.String("sumsk4calc")), - Sumskill5: d.String("sumskill5"), - Sumsk5calc: parser.Parse(d.String("sumsk5calc")), - Sumumod: d.Number("sumumod"), - Sumoverlay: d.String("sumoverlay"), - Stsuccessonly: d.Bool("stsuccessonly"), - Stsound: d.String("stsound"), - Stsoundclass: d.String("stsoundclass"), - Stsounddelay: d.Bool("stsounddelay"), - Weaponsnd: d.Bool("weaponsnd"), - Dosound: d.String("dosound"), - DosoundA: d.String("dosound a"), - DosoundB: d.String("dosound b"), - Tgtoverlay: d.String("tgtoverlay"), - Tgtsound: d.String("tgtsound"), - Prgoverlay: d.String("prgoverlay"), - Prgsound: d.String("prgsound"), - Castoverlay: d.String("castoverlay"), - Cltoverlaya: d.String("cltoverlaya"), - Cltoverlayb: d.String("cltoverlayb"), - Cltstfunc: d.Number("cltstfunc"), - Cltdofunc: d.Number("cltdofunc"), - Cltprgfunc1: d.Number("cltprgfunc1"), - Cltprgfunc2: d.Number("cltprgfunc2"), - Cltprgfunc3: d.Number("cltprgfunc3"), - Cltmissile: d.String("cltmissile"), - Cltmissilea: d.String("cltmissilea"), - Cltmissileb: d.String("cltmissileb"), - Cltmissilec: d.String("cltmissilec"), - Cltmissiled: d.String("cltmissiled"), - Cltcalc1: parser.Parse(d.String("cltcalc1")), - Cltcalc2: parser.Parse(d.String("cltcalc2")), - Cltcalc3: parser.Parse(d.String("cltcalc3")), - Warp: d.Bool("warp"), - Immediate: d.Bool("immediate"), - Enhanceable: d.Bool("enhanceable"), - Attackrank: d.Number("attackrank"), - Noammo: d.Bool("noammo"), - Range: d.String("range"), - Weapsel: d.Number("weapsel"), - Itypea1: d.String("itypea1"), - Itypea2: d.String("itypea2"), - Itypea3: d.String("itypea3"), - Etypea1: d.String("etypea1"), - Etypea2: d.String("etypea2"), - Itypeb1: d.String("itypeb1"), - Itypeb2: d.String("itypeb2"), - Itypeb3: d.String("itypeb3"), - Etypeb1: d.String("etypeb1"), - Etypeb2: d.String("etypeb2"), - Anim: d.String("anim"), - Seqtrans: d.String("seqtrans"), - Monanim: d.String("monanim"), - Seqnum: d.Number("seqnum"), - Seqinput: d.Number("seqinput"), - Durability: d.Bool("durability"), - UseAttackRate: d.Bool("UseAttackRate"), - LineOfSight: d.Number("LineOfSight"), - TargetableOnly: d.Bool("TargetableOnly"), - SearchEnemyXY: d.Bool("SearchEnemyXY"), - SearchEnemyNear: d.Bool("SearchEnemyNear"), - SearchOpenXY: d.Bool("SearchOpenXY"), - SelectProc: d.Number("SelectProc"), - TargetCorpse: d.Bool("TargetCorpse"), - TargetPet: d.Bool("TargetPet"), - TargetAlly: d.Bool("TargetAlly"), - TargetItem: d.Bool("TargetItem"), - AttackNoMana: d.Bool("AttackNoMana"), - TgtPlaceCheck: d.Bool("TgtPlaceCheck"), - ItemEffect: d.Number("ItemEffect"), - ItemCltEffect: d.Number("ItemCltEffect"), - ItemTgtDo: d.Number("ItemTgtDo"), - ItemTarget: d.Number("ItemTarget"), - ItemCheckStart: d.Bool("ItemCheckStart"), - ItemCltCheckStart: d.Bool("ItemCltCheckStart"), - ItemCastSound: d.String("ItemCastSound"), - ItemCastOverlay: d.String("ItemCastOverlay"), - Skpoints: parser.Parse(d.String("skpoints")), - Reqlevel: d.Number("reqlevel"), - Maxlvl: d.Number("maxlvl"), - Reqstr: d.Number("reqstr"), - Reqdex: d.Number("reqdex"), - Reqint: d.Number("reqint"), - Reqvit: d.Number("reqvit"), - Reqskill1: d.String("reqskill1"), - Reqskill2: d.String("reqskill2"), - Reqskill3: d.String("reqskill3"), - Restrict: d.Number("restrict"), - State1: d.String("State1"), - State2: d.String("State2"), - State3: d.String("State3"), - Delay: d.Number("delay"), - Leftskill: d.Bool("leftskill"), - Repeat: d.Bool("repeat"), - Checkfunc: d.Number("checkfunc"), - Nocostinstate: d.Bool("nocostinstate"), - Usemanaondo: d.Bool("usemanaondo"), - Startmana: d.Number("startmana"), - Minmana: d.Number("minmana"), - Manashift: d.Number("manashift"), - Mana: d.Number("mana"), - Lvlmana: d.Number("lvlmana"), - Interrupt: d.Bool("interrupt"), - InTown: d.Bool("InTown"), - Aura: d.Bool("aura"), - Periodic: d.Bool("periodic"), - Perdelay: parser.Parse(d.String("perdelay")), - Finishing: d.Bool("finishing"), - Passive: d.Bool("passive"), - Progressive: d.Bool("progressive"), - General: d.Bool("general"), - Scroll: d.Bool("scroll"), - Calc1: parser.Parse(d.String("calc1")), - Calc2: parser.Parse(d.String("calc2")), - Calc3: parser.Parse(d.String("calc3")), - Calc4: parser.Parse(d.String("calc4")), - Param1: d.Number("Param1"), - Param2: d.Number("Param2"), - Param3: d.Number("Param3"), - Param4: d.Number("Param4"), - Param5: d.Number("Param5"), - Param6: d.Number("Param6"), - Param7: d.Number("Param7"), - Param8: d.Number("Param8"), - InGame: d.Bool("InGame"), - ToHit: d.Number("ToHit"), - LevToHit: d.Number("LevToHit"), - ToHitCalc: parser.Parse(d.String("ToHitCalc")), - ResultFlags: d.Number("ResultFlags"), - HitFlags: d.Number("HitFlags"), - HitClass: d.Number("HitClass"), - Kick: d.Bool("Kick"), - HitShift: d.Number("HitShift"), - SrcDam: d.Number("SrcDam"), - MinDam: d.Number("MinDam"), - MinLevDam1: d.Number("MinLevDam1"), - MinLevDam2: d.Number("MinLevDam2"), - MinLevDam3: d.Number("MinLevDam3"), - MinLevDam4: d.Number("MinLevDam4"), - MinLevDam5: d.Number("MinLevDam5"), - MaxDam: d.Number("MaxDam"), - MaxLevDam1: d.Number("MaxLevDam1"), - MaxLevDam2: d.Number("MaxLevDam2"), - MaxLevDam3: d.Number("MaxLevDam3"), - MaxLevDam4: d.Number("MaxLevDam4"), - MaxLevDam5: d.Number("MaxLevDam5"), - DmgSymPerCalc: parser.Parse(d.String("DmgSymPerCalc")), - EType: d.String("EType"), - EMin: d.Number("EMin"), - EMinLev1: d.Number("EMinLev1"), - EMinLev2: d.Number("EMinLev2"), - EMinLev3: d.Number("EMinLev3"), - EMinLev4: d.Number("EMinLev4"), - EMinLev5: d.Number("EMinLev5"), - EMax: d.Number("EMax"), - EMaxLev1: d.Number("EMaxLev1"), - EMaxLev2: d.Number("EMaxLev2"), - EMaxLev3: d.Number("EMaxLev3"), - EMaxLev4: d.Number("EMaxLev4"), - EMaxLev5: d.Number("EMaxLev5"), - EDmgSymPerCalc: parser.Parse(d.String("EDmgSymPerCalc")), - ELen: d.Number("ELen"), - ELevLen1: d.Number("ELevLen1"), - ELevLen2: d.Number("ELevLen2"), - ELevLen3: d.Number("ELevLen3"), - ELenSymPerCalc: parser.Parse(d.String("ELenSymPerCalc")), - Aitype: d.Number("aitype"), - Aibonus: d.Number("aibonus"), - CostMult: d.Number("cost mult"), - CostAdd: d.Number("cost add"), - } - SkillDetails[record.ID] = record - skillDetailsByName[record.Skill] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d Skill records", len(SkillDetails)) -} - -// GetSkillByName returns the skill record for the given Skill name. -func GetSkillByName(skillName string) *SkillRecord { - return skillDetailsByName[skillName] -} diff --git a/d2common/d2data/d2datadict/soundenviron.go b/d2common/d2data/d2datadict/soundenviron.go deleted file mode 100644 index 986df82c..00000000 --- a/d2common/d2data/d2datadict/soundenviron.go +++ /dev/null @@ -1,81 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// SoundEnvironRecord describes the different sound environments. Not listed on Phrozen Keep. -type SoundEnvironRecord struct { - Handle string - Index int - Song int - DayAmbience int - NightAmbience int - DayEvent int - NightEvent int - EventDelay int - Indoors int - Material1 int - Material2 int - EAXEnviron int - EAXEnvSize int - EAXEnvDiff int - EAXRoomVol int - EAXRoomHF int - EAXDecayTime int - EAXDecayHF int - EAXReflect int - EAXReflectDelay int - EAXReverb int - EAXRevDelay int - EAXRoomRoll int - EAXAirAbsorb int -} - -// SoundEnvirons contains the SoundEnviron records -//nolint:gochecknoglobals // Currently global by design, only written once -var SoundEnvirons map[int]*SoundEnvironRecord - -// LoadSoundEnvirons loads SoundEnvirons from the supplied file -func LoadSoundEnvirons(file []byte) { - SoundEnvirons = make(map[int]*SoundEnvironRecord) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - record := &SoundEnvironRecord{ - Handle: d.String("Handle"), - Index: d.Number("Index"), - Song: d.Number("Song"), - DayAmbience: d.Number("Day Ambience"), - NightAmbience: d.Number("Night Ambience"), - DayEvent: d.Number("Day Event"), - NightEvent: d.Number("Night Event"), - EventDelay: d.Number("Event Delay"), - Indoors: d.Number("Indoors"), - Material1: d.Number("Material 1"), - Material2: d.Number("Material 2"), - EAXEnviron: d.Number("EAX Environ"), - EAXEnvSize: d.Number("EAX Env Size"), - EAXEnvDiff: d.Number("EAX Env Diff"), - EAXRoomVol: d.Number("EAX Room Vol"), - EAXRoomHF: d.Number("EAX Room HF"), - EAXDecayTime: d.Number("EAX Decay Time"), - EAXDecayHF: d.Number("EAX Decay HF"), - EAXReflect: d.Number("EAX Reflect"), - EAXReflectDelay: d.Number("EAX Reflect Delay"), - EAXReverb: d.Number("EAX Reverb"), - EAXRevDelay: d.Number("EAX Rev Delay"), - EAXRoomRoll: d.Number("EAX Room Roll"), - EAXAirAbsorb: d.Number("EAX Air Absorb"), - } - SoundEnvirons[record.Index] = record - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d SoundEnviron records", len(SoundEnvirons)) -} diff --git a/d2common/d2data/d2datadict/sounds.go b/d2common/d2data/d2datadict/sounds.go deleted file mode 100644 index 9c83c2f4..00000000 --- a/d2common/d2data/d2datadict/sounds.go +++ /dev/null @@ -1,94 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" -) - -// SoundEntry represents a sound entry -type SoundEntry struct { - Handle string - FileName string - Index int - Volume int - GroupSize int - FadeIn int - FadeOut int - Duration int - Compound int - Reverb int - Falloff int - Priority int - Block1 int - Block2 int - Block3 int - Loop bool - DeferInst bool - StopInst bool - Cache bool - AsyncOnly bool - Stream bool - Stereo bool - Tracking bool - Solo bool - MusicVol bool -} - -// Sounds stores all of the SoundEntries -//nolint:gochecknoglobals // Currently global by design, only written once -var Sounds map[string]*SoundEntry - -// LoadSounds loads SoundEntries from sounds.txt -func LoadSounds(file []byte) { - Sounds = make(map[string]*SoundEntry) - - d := d2txt.LoadDataDictionary(file) - for d.Next() { - entry := &SoundEntry{ - Handle: d.String("Sound"), - Index: d.Number("Index"), - FileName: d.String("FileName"), - Volume: d.Number("Volume"), - GroupSize: d.Number("Group Size"), - Loop: d.Bool("Loop"), - FadeIn: d.Number("Fade In"), - FadeOut: d.Number("Fade Out"), - DeferInst: d.Bool("Defer Inst"), - StopInst: d.Bool("Stop Inst"), - Duration: d.Number("Duration"), - Compound: d.Number("Compound"), - Reverb: d.Number("Reverb"), - Falloff: d.Number("Falloff"), - Cache: d.Bool("Cache"), - AsyncOnly: d.Bool("Async Only"), - Priority: d.Number("Priority"), - Stream: d.Bool("Stream"), - Stereo: d.Bool("Stereo"), - Tracking: d.Bool("Tracking"), - Solo: d.Bool("Solo"), - MusicVol: d.Bool("Music Vol"), - Block1: d.Number("Block 1"), - Block2: d.Number("Block 2"), - Block3: d.Number("Block 3"), - } - Sounds[entry.Handle] = entry - } - - if d.Err != nil { - panic(d.Err) - } - - log.Printf("Loaded %d sound definitions", len(Sounds)) -} - -// SelectSoundByIndex selects a sound by its ID -func SelectSoundByIndex(index int) *SoundEntry { - for idx := range Sounds { - if Sounds[idx].Index == index { - return Sounds[idx] - } - } - - return nil -} diff --git a/d2common/d2data/d2datadict/weapons.go b/d2common/d2data/d2datadict/weapons.go deleted file mode 100644 index a4ab088c..00000000 --- a/d2common/d2data/d2datadict/weapons.go +++ /dev/null @@ -1,16 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" -) - -// 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)) -} diff --git a/d2core/d2audio/ebiten/ebiten_audio_provider.go b/d2core/d2audio/ebiten/ebiten_audio_provider.go index 3e653fa3..1f31dfc8 100644 --- a/d2core/d2audio/ebiten/ebiten_audio_provider.go +++ b/d2core/d2audio/ebiten/ebiten_audio_provider.go @@ -4,7 +4,6 @@ package ebiten import ( "log" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" @@ -19,7 +18,7 @@ var _ d2interface.AudioProvider = &AudioProvider{} // Static check to confirm st // CreateAudio creates an instance of ebiten's audio provider func CreateAudio(am *d2asset.AssetManager) (*AudioProvider, error) { result := &AudioProvider{ - assetManager: am, + asset: am, } var err error @@ -35,7 +34,7 @@ func CreateAudio(am *d2asset.AssetManager) (*AudioProvider, error) { // AudioProvider represents a provider capable of playing audio type AudioProvider struct { - assetManager *d2asset.AssetManager + asset *d2asset.AssetManager audioContext *audio.Context // The Audio context bgmAudio *audio.Player // The audio player lastBgm string @@ -65,7 +64,7 @@ func (eap *AudioProvider) PlayBGM(song string) { } } - audioStream, err := eap.assetManager.LoadFileStream(song) + audioStream, err := eap.asset.LoadFileStream(song) if err != nil { panic(err) @@ -128,17 +127,17 @@ func (eap *AudioProvider) createSoundEffect(sfx string, context *audio.Context, soundFile := "/data/global/sfx/" - if _, exists := d2datadict.Sounds[sfx]; exists { - soundEntry := d2datadict.Sounds[sfx] + if _, exists := eap.asset.Records.Sound.Details[sfx]; exists { + soundEntry := eap.asset.Records.Sound.Details[sfx] soundFile += soundEntry.FileName } else { soundFile += sfx } - audioData, err := eap.assetManager.LoadFileStream(soundFile) + audioData, err := eap.asset.LoadFileStream(soundFile) if err != nil { - audioData, err = eap.assetManager.LoadFileStream("/data/global/music/" + sfx) + audioData, err = eap.asset.LoadFileStream("/data/global/music/" + sfx) } if err != nil { diff --git a/d2core/d2audio/sound_engine.go b/d2core/d2audio/sound_engine.go index da561820..d6b79abb 100644 --- a/d2core/d2audio/sound_engine.go +++ b/d2core/d2audio/sound_engine.go @@ -4,7 +4,10 @@ import ( "log" "math/rand" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" ) @@ -23,7 +26,7 @@ const originalFPS float64 = 25 // A Sound that can be started and stopped type Sound struct { effect d2interface.SoundEffect - entry *d2datadict.SoundEntry + entry *d2records.SoundDetailsRecord volume float64 vTarget float64 vRate float64 @@ -95,6 +98,7 @@ func (s *Sound) Stop() { // SoundEngine provides functions for playing sounds type SoundEngine struct { + asset *d2asset.AssetManager provider d2interface.AudioProvider timer float64 accTime float64 @@ -102,8 +106,10 @@ type SoundEngine struct { } // NewSoundEngine creates a new sound engine -func NewSoundEngine(provider d2interface.AudioProvider, term d2interface.Terminal) *SoundEngine { +func NewSoundEngine(provider d2interface.AudioProvider, + asset *d2asset.AssetManager, term d2interface.Terminal) *SoundEngine { r := SoundEngine{ + asset: asset, provider: provider, sounds: map[*Sound]struct{}{}, timer: 1, @@ -173,10 +179,10 @@ func (s *SoundEngine) PlaySoundID(id int) *Sound { return nil } - entry := d2datadict.SelectSoundByIndex(id) + entry := s.asset.Records.SelectSoundByIndex(id) if entry.GroupSize > 0 { - entry = d2datadict.SelectSoundByIndex(entry.Index + rand.Intn(entry.GroupSize)) + entry = s.asset.Records.SelectSoundByIndex(entry.Index + rand.Intn(entry.GroupSize)) } effect, _ := s.provider.LoadSound(entry.FileName, entry.Loop, entry.MusicVol) @@ -195,6 +201,6 @@ func (s *SoundEngine) PlaySoundID(id int) *Sound { // PlaySoundHandle plays a sound by sounds.txt handle func (s *SoundEngine) PlaySoundHandle(handle string) *Sound { - sound := d2datadict.Sounds[handle].Index + sound := s.asset.Records.Sound.Details[handle].Index return s.PlaySoundID(sound) } diff --git a/d2core/d2audio/sound_environment.go b/d2core/d2audio/sound_environment.go index 081aa19e..ed67700d 100644 --- a/d2core/d2audio/sound_environment.go +++ b/d2core/d2audio/sound_environment.go @@ -3,14 +3,14 @@ package d2audio import ( "math/rand" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" ) const assumedFPS = 25 // SoundEnvironment represents the audio environment for map areas type SoundEnvironment struct { - environment *d2datadict.SoundEnvironRecord + environment *d2records.SoundEnvironRecord engine *SoundEngine bgm *Sound ambiance *Sound @@ -21,7 +21,7 @@ type SoundEnvironment struct { func NewSoundEnvironment(soundEngine *SoundEngine) SoundEnvironment { r := SoundEnvironment{ // Start with env NONE - environment: d2datadict.SoundEnvirons[0], + environment: soundEngine.asset.Records.Sound.Environment[0], engine: soundEngine, } @@ -31,7 +31,7 @@ func NewSoundEnvironment(soundEngine *SoundEngine) SoundEnvironment { // SetEnv sets the sound environment using the given record index func (s *SoundEnvironment) SetEnv(environmentIdx int) { if s.environment.Index != environmentIdx { - newEnv := d2datadict.SoundEnvirons[environmentIdx] + newEnv := s.engine.asset.Records.Sound.Environment[environmentIdx] if s.environment.Song != newEnv.Song { if s.bgm != nil { diff --git a/d2core/d2hero/hero_skill.go b/d2core/d2hero/hero_skill.go index 0d2717d1..f7c46305 100644 --- a/d2core/d2hero/hero_skill.go +++ b/d2core/d2hero/hero_skill.go @@ -4,14 +4,15 @@ import ( "encoding/json" "log" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" ) // HeroSkill stores additional payload for a skill of a hero. type HeroSkill struct { - *d2datadict.SkillRecord - *d2datadict.SkillDescriptionRecord + *d2records.SkillRecord + *d2records.SkillDescriptionRecord SkillPoints int + shallow *shallowHeroSkill } // An auxilary struct which only stores the ID of the SkillRecord, instead of the whole SkillRecord and SkillDescrptionRecord. @@ -38,14 +39,12 @@ func (hs *HeroSkill) MarshalJSON() ([]byte, error) { // UnmarshalJSON overrides the default logic used when the HeroSkill is deserialized from a byte array. func (hs *HeroSkill) UnmarshalJSON(data []byte) error { - shallow := shallowHeroSkill{} - if err := json.Unmarshal(data, &shallow); err != nil { + shallow := &shallowHeroSkill{} + if err := json.Unmarshal(data, shallow); err != nil { return err } - hs.SkillRecord = d2datadict.SkillDetails[shallow.SkillID] - hs.SkillDescriptionRecord = d2datadict.SkillDescriptions[hs.SkillRecord.Skilldesc] - hs.SkillPoints = shallow.SkillPoints + hs.shallow = shallow return nil } diff --git a/d2core/d2hero/hero_skills_state.go b/d2core/d2hero/hero_skills_state.go deleted file mode 100644 index 9d611a75..00000000 --- a/d2core/d2hero/hero_skills_state.go +++ /dev/null @@ -1,26 +0,0 @@ -package d2hero - -import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" - -// HeroSkillsState hold all spells that a hero has. -type HeroSkillsState map[int] *HeroSkill - -// CreateHeroSkillsState will assemble the hero skills from the class stats record. -func CreateHeroSkillsState(classStats *d2datadict.CharStatsRecord) *HeroSkillsState { - baseSkills := HeroSkillsState{} - - for idx := range classStats.BaseSkill { - skillName := &classStats.BaseSkill[idx] - if len(*skillName) == 0 { - continue - } - - skillRecord := d2datadict.GetSkillByName(*skillName) - baseSkills[skillRecord.ID] = &HeroSkill{SkillPoints: 1, SkillRecord: skillRecord} - } - - skillRecord := d2datadict.GetSkillByName("Attack") - baseSkills[skillRecord.ID] = &HeroSkill{SkillPoints: 1, SkillRecord: skillRecord} - - return &baseSkills -} diff --git a/d2core/d2hero/hero_state.go b/d2core/d2hero/hero_state.go new file mode 100644 index 00000000..7742fa6f --- /dev/null +++ b/d2core/d2hero/hero_state.go @@ -0,0 +1,20 @@ +package d2hero + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory" +) + +// HeroState stores the state of the player +type HeroState struct { + HeroName string `json:"heroName"` + HeroType d2enum.Hero `json:"heroType"` + HeroLevel int `json:"heroLevel"` + Act int `json:"act"` + FilePath string `json:"-"` + Equipment d2inventory.CharacterEquipment `json:"equipment"` + Stats *HeroStatsState `json:"stats"` + Skills map[int]*HeroSkill `json:"skills"` + X float64 `json:"x"` + Y float64 `json:"y"` +} diff --git a/d2core/d2hero/hero_state_factory.go b/d2core/d2hero/hero_state_factory.go new file mode 100644 index 00000000..b6e3352e --- /dev/null +++ b/d2core/d2hero/hero_state_factory.go @@ -0,0 +1,235 @@ +package d2hero + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path" + "strconv" + "strings" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" +) + +// NewHeroStateFactory creates a new HeroStateFactory and initializes it. +func NewHeroStateFactory(asset *d2asset.AssetManager) (*HeroStateFactory, error) { + inventoryItemFactory, err := d2inventory.NewInventoryItemFactory(asset) + if err != nil { + return nil, err + } + + factory := &HeroStateFactory{ + asset: asset, + InventoryItemFactory: inventoryItemFactory, + } + + return factory, nil +} + +// HeroStateFactory is responsible for creating player state objects +type HeroStateFactory struct { + asset *d2asset.AssetManager + *d2inventory.InventoryItemFactory +} + +// CreateHeroState creates a HeroState instance and returns a pointer to it +func (f *HeroStateFactory) CreateHeroState( + heroName string, + hero d2enum.Hero, + statsState *HeroStatsState, +) (*HeroState, error) { + result := &HeroState{ + HeroName: heroName, + HeroType: hero, + Act: 1, + Stats: statsState, + Equipment: f.DefaultHeroItems[hero], + FilePath: "", + } + + defaultStats := f.asset.Records.Character.Stats[hero] + skillState, err := f.CreateHeroSkillsState(defaultStats) + if err != nil { + return nil, err + } + + result.Skills = skillState + + return result, nil +} + +// GetAllHeroStates returns all player saves +func (f *HeroStateFactory) GetAllHeroStates() ([]*HeroState, error) { + basePath, _ := f.getGameBaseSavePath() + files, _ := ioutil.ReadDir(basePath) + result := make([]*HeroState, 0) + + for _, file := range files { + fileName := file.Name() + if file.IsDir() || len(fileName) < 5 || !strings.EqualFold(fileName[len(fileName)-4:], ".od2") { + continue + } + + gameState := f.LoadHeroState(path.Join(basePath, file.Name())) + if gameState == nil || gameState.HeroType == d2enum.HeroNone { + + } else if gameState.Stats == nil || gameState.Skills == nil { + // temporarily loading default class stats if the character was created before saving stats/skills was introduced + // to be removed in the future + classStats := f.asset.Records.Character.Stats[gameState.HeroType] + gameState.Stats = f.CreateHeroStatsState(gameState.HeroType, classStats) + + skillState, err := f.CreateHeroSkillsState(classStats) + if err != nil { + return nil, err + } + + gameState.Skills = skillState + + if err := f.Save(gameState); err != nil { + fmt.Printf("failed to save game state!, err: %v\n", err) + } + } + + result = append(result, gameState) + } + + return result, nil +} + +// CreateHeroSkillsState will assemble the hero skills from the class stats record. +func (f *HeroStateFactory) CreateHeroSkillsState(classStats *d2records.CharStatsRecord) (map[int]*HeroSkill, error) { + baseSkills := map[int]*HeroSkill{} + + for idx := range classStats.BaseSkill { + skillName := &classStats.BaseSkill[idx] + + if len(*skillName) == 0 { + continue + } + + skill, err := f.CreateHeroSkill(1, *skillName) + if err != nil { + continue + } + + baseSkills[skill.ID] = skill + } + + skillRecord, err := f.CreateHeroSkill(1, "Attack") + if err != nil { + return nil, err + } + + baseSkills[skillRecord.ID] = skillRecord + + return baseSkills, nil +} + +// CreateHeroSkill creates an instance of a skill +func (f *HeroStateFactory) CreateHeroSkill(points int, name string) (*HeroSkill, error) { + skillRecord := f.asset.Records.GetSkillByName(name) + if skillRecord == nil { + return nil, fmt.Errorf("Skill not found: %s", name) + } + + skillDescRecord, found := f.asset.Records.Skill.Descriptions[skillRecord.Skilldesc] + if !found { + return nil, fmt.Errorf("Skill Description not found: %s", name) + } + + result := &HeroSkill{ + SkillPoints: points, + SkillRecord: skillRecord, + SkillDescriptionRecord: skillDescRecord, + } + + return result, nil +} + +// HasGameStates returns true if the player has any previously saved game +func (f *HeroStateFactory) HasGameStates() bool { + basePath, _ := f.getGameBaseSavePath() + files, _ := ioutil.ReadDir(basePath) + + return len(files) > 0 +} + +// CreateTestGameState is used for the map engine previewer +func (f *HeroStateFactory) CreateTestGameState() *HeroState { + result := &HeroState{} + return result +} + +// LoadHeroState loads the player state from the file +func (f *HeroStateFactory) LoadHeroState(filePath string) *HeroState { + strData, err := ioutil.ReadFile(filePath) + if err != nil { + return nil + } + + result := &HeroState{ + FilePath: filePath, + } + + err = json.Unmarshal(strData, result) + if err != nil { + return nil + } + + // Here, we turn the shallow skill data back into records from the asset manager. + // This is because this factory has a reference to the asset manager with loaded records. + // We cant do this while unmarshalling because there is no reference to the asset manager. + for idx := range result.Skills { + hs := result.Skills[idx] + hs.SkillRecord = f.asset.Records.Skill.Details[hs.shallow.SkillID] + hs.SkillDescriptionRecord = f.asset.Records.Skill.Descriptions[hs.SkillRecord.Skilldesc] + hs.SkillPoints = hs.shallow.SkillPoints + } + + return result +} + +func (f *HeroStateFactory) getGameBaseSavePath() (string, error) { + configDir, err := os.UserConfigDir() + if err != nil { + return "", err + } + + return path.Join(configDir, "OpenDiablo2/Saves"), nil +} + +func (f *HeroStateFactory) getFirstFreeFileName() string { + i := 0 + basePath, _ := f.getGameBaseSavePath() + + for { + filePath := path.Join(basePath, strconv.Itoa(i)+".od2") + if _, err := os.Stat(filePath); os.IsNotExist(err) { + return filePath + } + i++ + } +} + +// Save saves the player state to a file +func (f *HeroStateFactory) Save(state *HeroState) error { + if state.FilePath == "" { + state.FilePath = f.getFirstFreeFileName() + } + if err := os.MkdirAll(path.Dir(state.FilePath), 0755); err != nil { + return err + } + + fileJSON, _ := json.MarshalIndent(state, "", " ") + if err := ioutil.WriteFile(state.FilePath, fileJSON, 0644); err != nil { + return err + } + + return nil +} diff --git a/d2core/d2hero/hero_stats_state.go b/d2core/d2hero/hero_stats_state.go index 4f8626f4..5fb66386 100644 --- a/d2core/d2hero/hero_stats_state.go +++ b/d2core/d2hero/hero_stats_state.go @@ -1,8 +1,8 @@ package d2hero import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" ) // HeroStatsState is a serializable state of hero stats. @@ -35,11 +35,11 @@ type HeroStatsState struct { } // CreateHeroStatsState generates a running state from a hero stats. -func CreateHeroStatsState(heroClass d2enum.Hero, classStats *d2datadict.CharStatsRecord) *HeroStatsState { +func (f *HeroStateFactory) CreateHeroStatsState(heroClass d2enum.Hero, classStats *d2records.CharStatsRecord) *HeroStatsState { result := HeroStatsState{ Level: 1, Experience: 0, - NextLevelExp: d2datadict.GetExperienceBreakpoint(heroClass, 1), + NextLevelExp: f.asset.Records.GetExperienceBreakpoint(heroClass, 1), Strength: classStats.InitStr, Dexterity: classStats.InitDex, Vitality: classStats.InitVit, diff --git a/d2core/d2inventory/hero_objects.go b/d2core/d2inventory/hero_objects.go index e1b979ce..0800952b 100644 --- a/d2core/d2inventory/hero_objects.go +++ b/d2core/d2inventory/hero_objects.go @@ -5,39 +5,4 @@ import ( ) // HeroObjects map contains the hero type to CharacterEquipments -var HeroObjects map[d2enum.Hero]CharacterEquipment - -// LoadHeroObjects loads the equipment objects of the hero -func LoadHeroObjects() { - //Mode: d2enum.AnimationModePlayerNeutral.String(), - //Base: "/data/global/chars", - HeroObjects = map[d2enum.Hero]CharacterEquipment{ - d2enum.HeroBarbarian: { - RightHand: GetWeaponItemByCode("hax"), - Shield: GetArmorItemByCode("buc"), - }, - d2enum.HeroNecromancer: { - RightHand: GetWeaponItemByCode("wnd"), - }, - d2enum.HeroPaladin: { - RightHand: GetWeaponItemByCode("ssd"), - Shield: GetArmorItemByCode("buc"), - }, - d2enum.HeroAssassin: { - RightHand: GetWeaponItemByCode("ktr"), - Shield: GetArmorItemByCode("buc"), - }, - d2enum.HeroSorceress: { - RightHand: GetWeaponItemByCode("sst"), - LeftHand: GetWeaponItemByCode("sst"), - }, - d2enum.HeroAmazon: { - RightHand: GetWeaponItemByCode("jav"), - Shield: GetArmorItemByCode("buc"), - }, - d2enum.HeroDruid: { - RightHand: GetWeaponItemByCode("clb"), - Shield: GetArmorItemByCode("buc"), - }, - } -} +type HeroObjects map[d2enum.Hero]CharacterEquipment diff --git a/d2core/d2inventory/inventory_item_armor.go b/d2core/d2inventory/inventory_item_armor.go index 24011f15..1f5f9ccb 100644 --- a/d2core/d2inventory/inventory_item_armor.go +++ b/d2core/d2inventory/inventory_item_armor.go @@ -1,9 +1,6 @@ package d2inventory import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" ) @@ -18,22 +15,6 @@ type InventoryItemArmor struct { ArmorClass string `json:"armorClass"` } -// GetArmorItemByCode returns the armor item for the given code -func GetArmorItemByCode(code string) *InventoryItemArmor { - result := d2datadict.Armors[code] - if result == nil { - log.Fatalf("Could not find armor entry for code '%s'", code) - } - - return &InventoryItemArmor{ - InventorySizeX: result.InventoryWidth, - InventorySizeY: result.InventoryHeight, - ItemName: result.Name, - ItemCode: result.Code, - ArmorClass: "lit", // TODO: Where does this come from? - } -} - // GetArmorClass returns the class of the armor func (v *InventoryItemArmor) GetArmorClass() string { if v == nil || v.ItemCode == "" { diff --git a/d2core/d2inventory/inventory_item_factory.go b/d2core/d2inventory/inventory_item_factory.go new file mode 100644 index 00000000..317d702b --- /dev/null +++ b/d2core/d2inventory/inventory_item_factory.go @@ -0,0 +1,107 @@ +package d2inventory + +import ( + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" +) + +// NewInventoryItemFactory creates a new InventoryItemFactory and initializes it +func NewInventoryItemFactory(asset *d2asset.AssetManager) (*InventoryItemFactory, error) { + factory := &InventoryItemFactory{asset: asset} + + factory.loadHeroObjects() + + return factory, nil +} + +// InventoryItemFactory is responsible for creating inventory items +type InventoryItemFactory struct { + asset *d2asset.AssetManager + DefaultHeroItems HeroObjects +} + +// LoadHeroObjects loads the equipment objects of the hero +func (f *InventoryItemFactory) loadHeroObjects() { + //Mode: d2enum.AnimationModePlayerNeutral.String(), + //Base: "/data/global/chars", + f.DefaultHeroItems = map[d2enum.Hero]CharacterEquipment{ + d2enum.HeroBarbarian: { + RightHand: f.GetWeaponItemByCode("hax"), + Shield: f.GetArmorItemByCode("buc"), + }, + d2enum.HeroNecromancer: { + RightHand: f.GetWeaponItemByCode("wnd"), + }, + d2enum.HeroPaladin: { + RightHand: f.GetWeaponItemByCode("ssd"), + Shield: f.GetArmorItemByCode("buc"), + }, + d2enum.HeroAssassin: { + RightHand: f.GetWeaponItemByCode("ktr"), + Shield: f.GetArmorItemByCode("buc"), + }, + d2enum.HeroSorceress: { + RightHand: f.GetWeaponItemByCode("sst"), + LeftHand: f.GetWeaponItemByCode("sst"), + }, + d2enum.HeroAmazon: { + RightHand: f.GetWeaponItemByCode("jav"), + Shield: f.GetArmorItemByCode("buc"), + }, + d2enum.HeroDruid: { + RightHand: f.GetWeaponItemByCode("clb"), + Shield: f.GetArmorItemByCode("buc"), + }, + } +} + +// GetArmorItemByCode returns the armor item for the given code +func (f *InventoryItemFactory) GetArmorItemByCode(code string) *InventoryItemArmor { + result := f.asset.Records.Item.Armors[code] + if result == nil { + log.Fatalf("Could not find armor entry for code '%s'", code) + } + + return &InventoryItemArmor{ + InventorySizeX: result.InventoryWidth, + InventorySizeY: result.InventoryHeight, + ItemName: result.Name, + ItemCode: result.Code, + ArmorClass: "lit", // TODO: Where does this come from? + } +} + +// GetMiscItemByCode returns the miscellaneous item for the given code +func (f *InventoryItemFactory) GetMiscItemByCode(code string) *InventoryItemMisc { + result := f.asset.Records.Item.Misc[code] + if result == nil { + log.Fatalf("Could not find misc item entry for code '%s'", code) + } + + return &InventoryItemMisc{ + InventorySizeX: result.InventoryWidth, + InventorySizeY: result.InventoryHeight, + ItemName: result.Name, + ItemCode: result.Code, + } +} + +// GetWeaponItemByCode returns the weapon item for the given code +func (f *InventoryItemFactory) GetWeaponItemByCode(code string) *InventoryItemWeapon { + // TODO: Non-normal codes will fail here... + result := f.asset.Records.Item.Weapons[code] + if result == nil { + log.Fatalf("Could not find weapon entry for code '%s'", code) + } + + return &InventoryItemWeapon{ + InventorySizeX: result.InventoryWidth, + InventorySizeY: result.InventoryHeight, + ItemName: result.Name, + ItemCode: result.Code, + WeaponClass: result.WeaponClass, + WeaponClassOffHand: result.WeaponClass2Hand, + } +} diff --git a/d2core/d2inventory/inventory_item_misc.go b/d2core/d2inventory/inventory_item_misc.go index c8faba52..1fb02707 100644 --- a/d2core/d2inventory/inventory_item_misc.go +++ b/d2core/d2inventory/inventory_item_misc.go @@ -1,9 +1,6 @@ package d2inventory import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" ) @@ -17,21 +14,6 @@ type InventoryItemMisc struct { ItemCode string `json:"itemCode"` } -// GetMiscItemByCode returns the miscellaneous item for the given code -func GetMiscItemByCode(code string) *InventoryItemMisc { - result := d2datadict.MiscItems[code] - if result == nil { - log.Fatalf("Could not find misc item entry for code '%s'", code) - } - - return &InventoryItemMisc{ - InventorySizeX: result.InventoryWidth, - InventorySizeY: result.InventoryHeight, - ItemName: result.Name, - ItemCode: result.Code, - } -} - // InventoryItemName returns the name of the miscellaneous item func (v *InventoryItemMisc) InventoryItemName() string { if v == nil { diff --git a/d2core/d2inventory/inventory_item_weapon.go b/d2core/d2inventory/inventory_item_weapon.go index b47ac887..1362c60c 100644 --- a/d2core/d2inventory/inventory_item_weapon.go +++ b/d2core/d2inventory/inventory_item_weapon.go @@ -1,9 +1,6 @@ package d2inventory import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" ) @@ -19,24 +16,6 @@ type InventoryItemWeapon struct { WeaponClassOffHand string `json:"weaponClassOffHand"` } -// GetWeaponItemByCode returns the weapon item for the given code -func GetWeaponItemByCode(code string) *InventoryItemWeapon { - // TODO: Non-normal codes will fail here... - result := d2datadict.Weapons[code] - if result == nil { - log.Fatalf("Could not find weapon entry for code '%s'", code) - } - - return &InventoryItemWeapon{ - InventorySizeX: result.InventoryWidth, - InventorySizeY: result.InventoryHeight, - ItemName: result.Name, - ItemCode: result.Code, - WeaponClass: result.WeaponClass, - WeaponClassOffHand: result.WeaponClass2Hand, - } -} - // GetWeaponClass returns the class of the weapon func (v *InventoryItemWeapon) GetWeaponClass() string { if v == nil || v.ItemCode == "" { diff --git a/d2core/d2item/diablo2item/diablo2item.go b/d2core/d2item/diablo2item/diablo2item.go deleted file mode 100644 index 3a0910df..00000000 --- a/d2core/d2item/diablo2item/diablo2item.go +++ /dev/null @@ -1,94 +0,0 @@ -package diablo2item - -import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" - -func NewItem(codes ...string) *Item { - var item *Item - - var common, set, unique string - - var prefixes, suffixes []string - - for _, code := range codes { - if found := d2datadict.CommonItems[code]; found != nil { - common = code - continue - } - - if found := d2datadict.SetItems[code]; found != nil { - set = code - continue - } - - if found := d2datadict.UniqueItems[code]; found != nil { - unique = code - continue - } - - if found := d2datadict.MagicPrefix[code]; found != nil { - if prefixes == nil { - prefixes = make([]string, 0) - } - - prefixes = append(prefixes, code) - - continue - } - - if found := d2datadict.MagicSuffix[code]; found != nil { - if suffixes == nil { - suffixes = make([]string, 0) - } - - suffixes = append(suffixes, code) - - continue - } - } - - if common != "" { // we will at least have a regular item - item = &Item{CommonCode: common} - - if set != "" { // it's a set item - item.SetItemCode = set - return item.init() - } - - if unique != "" { // it's a unique item - item.UniqueCode = unique - return item.init() - } - - if prefixes != nil { - if len(prefixes) > 0 { // it's a magic or rare item - item.PrefixCodes = prefixes - } - } - - if suffixes != nil { - if len(suffixes) > 0 { // it's a magic or rare item - item.SuffixCodes = suffixes - } - } - - return item.init() - } - - return nil -} - -// NewProperty creates a property -func NewProperty(code string, values ...int) *Property { - record := d2datadict.Properties[code] - - if record == nil { - return nil - } - - result := &Property{ - record: record, - inputParams: values, - } - - return result.init() -} diff --git a/d2core/d2item/diablo2item/item.go b/d2core/d2item/diablo2item/item.go index 90005e6b..b69307cf 100644 --- a/d2core/d2item/diablo2item/item.go +++ b/d2core/d2item/diablo2item/item.go @@ -6,12 +6,13 @@ import ( "sort" "strings" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2item" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats/diablo2stats" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) @@ -48,9 +49,10 @@ const ( var _ d2item.Item = &Item{} type Item struct { - name string - Seed int64 - rand *rand.Rand // non-global rand instance for re-generating the item + factory *ItemFactory + name string + Seed int64 + rand *rand.Rand // non-global rand instance for re-generating the item slotType d2enum.EquippedSlot @@ -177,18 +179,18 @@ func (i *Item) ItemLevel() int { } // TypeRecord returns the ItemTypeRecord of the item -func (i *Item) TypeRecord() *d2datadict.ItemTypeRecord { - return d2datadict.ItemTypes[i.TypeCode] +func (i *Item) TypeRecord() *d2records.ItemTypeRecord { + return i.factory.asset.Records.Item.Types[i.TypeCode] } // CommonRecord returns the ItemCommonRecord of the item -func (i *Item) CommonRecord() *d2datadict.ItemCommonRecord { - return d2datadict.CommonItems[i.CommonCode] +func (i *Item) CommonRecord() *d2records.ItemCommonRecord { + return i.factory.asset.Records.Item.All[i.CommonCode] } // UniqueRecord returns the UniqueItemRecord of the item -func (i *Item) UniqueRecord() *d2datadict.UniqueItemRecord { - return d2datadict.UniqueItems[i.UniqueCode] +func (i *Item) UniqueRecord() *d2records.UniqueItemRecord { + return i.factory.asset.Records.Item.Unique[i.UniqueCode] } // SetRecord returns the SetRecord of the item @@ -202,24 +204,24 @@ func (i *Item) SetItemRecord() *d2datadict.SetItemRecord { } // PrefixRecords returns the ItemAffixCommonRecords of the prefixes of the item -func (i *Item) PrefixRecords() []*d2datadict.ItemAffixCommonRecord { - return affixRecords(i.PrefixCodes, d2datadict.MagicPrefix) +func (i *Item) PrefixRecords() []*d2records.ItemAffixCommonRecord { + return affixRecords(i.PrefixCodes, i.factory.asset.Records.Item.Magic.Prefix) } // SuffixRecords returns the ItemAffixCommonRecords of the prefixes of the item -func (i *Item) SuffixRecords() []*d2datadict.ItemAffixCommonRecord { - return affixRecords(i.SuffixCodes, d2datadict.MagicSuffix) +func (i *Item) SuffixRecords() []*d2records.ItemAffixCommonRecord { + return affixRecords(i.SuffixCodes, i.factory.asset.Records.Item.Magic.Suffix) } func affixRecords( fromCodes []string, - affixes map[string]*d2datadict.ItemAffixCommonRecord, -) []*d2datadict.ItemAffixCommonRecord { + affixes map[string]*d2records.ItemAffixCommonRecord, +) []*d2records.ItemAffixCommonRecord { if len(fromCodes) < 1 { return nil } - result := make([]*d2datadict.ItemAffixCommonRecord, len(fromCodes)) + result := make([]*d2records.ItemAffixCommonRecord, len(fromCodes)) for idx, code := range fromCodes { rec := affixes[code] @@ -344,15 +346,19 @@ func (i *Item) pickMagicAffixes(mod DropModifier) { totalAffixes = numPrefixes + numSuffixes } - i.PrefixCodes = i.pickRandomAffixes(numPrefixes, totalAffixes, d2datadict.MagicPrefix) - i.SuffixCodes = i.pickRandomAffixes(numSuffixes, totalAffixes, d2datadict.MagicSuffix) + prefixes := i.factory.asset.Records.Item.Magic.Prefix + suffixes := i.factory.asset.Records.Item.Magic.Prefix + + i.PrefixCodes = i.pickRandomAffixes(numPrefixes, totalAffixes, prefixes) + i.SuffixCodes = i.pickRandomAffixes(numSuffixes, totalAffixes, suffixes) } -func (i *Item) pickRandomAffixes(max, totalMax int, affixMap map[string]*d2datadict.ItemAffixCommonRecord) []string { +func (i *Item) pickRandomAffixes(max, totalMax int, + affixMap map[string]*d2records.ItemAffixCommonRecord) []string { pickedCodes := make([]string, 0) for numPicks := 0; numPicks < max; numPicks++ { - matches := findMatchingAffixes(i.CommonRecord(), affixMap) + matches := i.factory.FindMatchingAffixes(i.CommonRecord(), affixMap) if rollPrefix := i.rand.Intn(2); rollPrefix > 0 { affixCount := len(i.PrefixRecords()) + len(i.SuffixRecords()) @@ -506,7 +512,7 @@ func (i *Item) updateItemAttributes() { } func (i *Item) generateAffixProperties(pool PropertyPool) []*Property { - var affixRecords []*d2datadict.ItemAffixCommonRecord + var affixRecords []*d2records.ItemAffixCommonRecord switch pool { case PropertyPoolPrefix: @@ -530,7 +536,7 @@ func (i *Item) generateAffixProperties(pool PropertyPool) []*Property { for modIdx := range affix.Modifiers { mod := affix.Modifiers[modIdx] - prop := NewProperty(mod.Code, mod.Parameter, mod.Min, mod.Max) + prop := i.factory.NewProperty(mod.Code, mod.Parameter, mod.Min, mod.Max) if prop == nil { continue } @@ -558,14 +564,14 @@ func (i *Item) generateUniqueProperties() []*Property { paramInt := getNumericComponent(propInfo.Parameter) if paramStr != "" { - for skillID := range d2datadict.SkillDetails { - if d2datadict.SkillDetails[skillID].Skill == paramStr { + for skillID := range i.factory.asset.Records.Skill.Details { + if i.factory.asset.Records.Skill.Details[skillID].Skill == paramStr { paramInt = skillID } } } - prop := NewProperty(propInfo.Code, paramInt, propInfo.Min, propInfo.Max) + prop := i.factory.NewProperty(propInfo.Code, paramInt, propInfo.Min, propInfo.Max) if prop == nil { continue } @@ -592,14 +598,14 @@ func (i *Item) generateSetItemProperties() []*Property { paramInt := getNumericComponent(setProp.Parameter) if paramStr != "" { - for skillID := range d2datadict.SkillDetails { - if d2datadict.SkillDetails[skillID].Skill == paramStr { + for skillID := range i.factory.asset.Records.Skill.Details { + if i.factory.asset.Records.Skill.Details[skillID].Skill == paramStr { paramInt = skillID } } } - prop := NewProperty(setProp.Code, paramInt, setProp.Min, setProp.Max) + prop := i.factory.NewProperty(setProp.Code, paramInt, setProp.Min, setProp.Max) if prop == nil { continue } @@ -687,7 +693,7 @@ func (i *Item) GetStatStrings() []string { } if len(stats) > 0 { - stats = diablo2stats.NewStatList(stats...).ReduceStats().Stats() + stats = i.factory.stat.NewStatList(stats...).ReduceStats().Stats() } sort.Slice(stats, func(i, j int) bool { return stats[i].Priority() > stats[j].Priority() }) @@ -702,7 +708,7 @@ func (i *Item) GetStatStrings() []string { return result } -func findMatchingUniqueRecords(icr *d2datadict.ItemCommonRecord) []*d2datadict.UniqueItemRecord { +func findMatchingUniqueRecords(icr *d2records.ItemCommonRecord) []*d2datadict.UniqueItemRecord { result := make([]*d2datadict.UniqueItemRecord, 0) c1, c2, c3, c4 := icr.Code, icr.NormalCode, icr.UberCode, icr.UltraCode @@ -720,7 +726,7 @@ func findMatchingUniqueRecords(icr *d2datadict.ItemCommonRecord) []*d2datadict.U } // find possible SetItemRecords that the given ItemCommonRecord can have -func findMatchingSetItemRecords(icr *d2datadict.ItemCommonRecord) []*d2datadict.SetItemRecord { +func findMatchingSetItemRecords(icr *d2records.ItemCommonRecord) []*d2datadict.SetItemRecord { result := make([]*d2datadict.SetItemRecord, 0) c1, c2, c3, c4 := icr.Code, icr.NormalCode, icr.UberCode, icr.UltraCode @@ -735,55 +741,6 @@ func findMatchingSetItemRecords(icr *d2datadict.ItemCommonRecord) []*d2datadict. return result } -// for a given ItemCommonRecord, find all possible affixes that can spawn -func findMatchingAffixes( - icr *d2datadict.ItemCommonRecord, - fromAffixes map[string]*d2datadict.ItemAffixCommonRecord, -) []*d2datadict.ItemAffixCommonRecord { - result := make([]*d2datadict.ItemAffixCommonRecord, 0) - - equivItemTypes := d2datadict.FindEquivalentTypesByItemCommonRecord(icr) - - for prefixIdx := range fromAffixes { - include, exclude := false, false - affix := fromAffixes[prefixIdx] - - for itemTypeIdx := range equivItemTypes { - itemType := equivItemTypes[itemTypeIdx] - - for _, excludedType := range affix.ItemExclude { - if itemType == excludedType { - exclude = true - break - } - } - - if exclude { - break - } - - for _, includedType := range affix.ItemInclude { - if itemType == includedType { - include = true - break - } - } - - if !include { - continue - } - - if icr.Level < affix.Level { - continue - } - - result = append(result, affix) - } - } - - return result -} - // these functions are to satisfy the inventory grid item interface // GetInventoryItemName returns the item name @@ -795,8 +752,8 @@ func (i *Item) GetInventoryItemName() string { func (i *Item) GetInventoryItemType() d2enum.InventoryItemType { typeCode := i.TypeRecord().Code - armorEquiv := d2datadict.ItemEquivalenciesByTypeCode["armo"] - weaponEquiv := d2datadict.ItemEquivalenciesByTypeCode["weap"] + armorEquiv := i.factory.asset.Records.Item.Equivalency["armo"] + weaponEquiv := i.factory.asset.Records.Item.Equivalency["weap"] for idx := range armorEquiv { if armorEquiv[idx].Code == typeCode { diff --git a/d2core/d2item/diablo2item/item_factory.go b/d2core/d2item/diablo2item/item_factory.go new file mode 100644 index 00000000..dbfb2bab --- /dev/null +++ b/d2core/d2item/diablo2item/item_factory.go @@ -0,0 +1,426 @@ +package diablo2item + +import ( + "errors" + "math/rand" + "regexp" + "strconv" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats/diablo2stats" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" +) + +const ( + defaultSeed = 0 +) + +const ( + DropModifierBaseProbability = 1024 // base DropModifier probability total +) + +type DropModifier int + +const ( + DropModifierNone DropModifier = iota + DropModifierUnique + DropModifierSet + DropModifierRare + DropModifierMagic +) + +const ( + // DynamicItemLevelRange for treasure codes like `armo33`, this code is used to + // select all equivalent items (matching `armo` in this case) with item levels 33,34,35 + DynamicItemLevelRange = 3 +) + +const ( + goldItemCodeWithMult = "gld,mul=" + goldItemCode = "gld" +) + +func NewItemFactory(asset *d2asset.AssetManager) (*ItemFactory, error) { + itemFactory := &ItemFactory{ + asset: asset, + Seed: 0, + } + + itemFactory.SetSeed(defaultSeed) + + statFactory, err := diablo2stats.NewStatFactory(asset) + if err != nil { + return nil, err + } + + itemFactory.stat = statFactory + + return itemFactory, nil +} + +// ItemFactory is a diablo 2 implementation of an item generator +type ItemFactory struct { + asset *d2asset.AssetManager + stat *diablo2stats.StatFactory + rand *rand.Rand + source rand.Source + Seed int64 +} + +// SetSeed sets the item generator seed +func (f *ItemFactory) SetSeed(seed int64) { + if f.rand == nil || f.source == nil { + f.source = rand.NewSource(seed) + f.rand = rand.New(f.source) + } + + f.Seed = seed +} + +func (f *ItemFactory) NewItem(codes ...string) (*Item, error) { + var item *Item + + var common, set, unique string + + var prefixes, suffixes []string + + for _, code := range codes { + if found := f.asset.Records.Item.All[code]; found != nil { + common = code + continue + } + + if found := d2datadict.SetItems[code]; found != nil { + set = code + continue + } + + if found := d2datadict.UniqueItems[code]; found != nil { + unique = code + continue + } + + if found := f.asset.Records.Item.Magic.Prefix[code]; found != nil { + if prefixes == nil { + prefixes = make([]string, 0) + } + + prefixes = append(prefixes, code) + + continue + } + + if found := f.asset.Records.Item.Magic.Suffix[code]; found != nil { + if suffixes == nil { + suffixes = make([]string, 0) + } + + suffixes = append(suffixes, code) + + continue + } + } + + if common != "" { // we will at least have a regular item + item = &Item{CommonCode: common} + + if set != "" { // it's a set item + item.SetItemCode = set + return item.init(), nil + } + + if unique != "" { // it's a unique item + item.UniqueCode = unique + return item.init(), nil + } + + if prefixes != nil { + if len(prefixes) > 0 { // it's a magic or rare item + item.PrefixCodes = prefixes + } + } + + if suffixes != nil { + if len(suffixes) > 0 { // it's a magic or rare item + item.SuffixCodes = suffixes + } + } + + item.factory = f + return item.init(), nil + } + + return nil, errors.New("cannot create item") +} + +// NewProperty creates a property +func (f *ItemFactory) NewProperty(code string, values ...int) *Property { + record := f.asset.Records.Properties[code] + + if record == nil { + return nil + } + + result := &Property{ + factory: f, + record: record, + inputParams: values, + } + + return result.init() +} + +func (f *ItemFactory) rollDropModifier(tcr *d2datadict.TreasureClassRecord) DropModifier { + modMap := map[int]DropModifier{ + 0: DropModifierNone, + 1: DropModifierUnique, + 2: DropModifierSet, + 3: DropModifierRare, + 4: DropModifierMagic, + } + + dropModifiers := []int{ + DropModifierBaseProbability, + tcr.FreqUnique, + tcr.FreqSet, + tcr.FreqRare, + tcr.FreqMagic, + } + + for idx := range dropModifiers { + if idx == 0 { + continue + } + + dropModifiers[idx] += dropModifiers[idx-1] + } + + roll := f.rand.Intn(dropModifiers[len(dropModifiers)-1]) + + for idx := range dropModifiers { + if roll < dropModifiers[idx] { + return modMap[idx] + } + } + + return DropModifierNone +} + +func (f *ItemFactory) rollTreasurePick(tcr *d2datadict.TreasureClassRecord) *d2datadict.Treasure { + // treasure probabilities + tprob := make([]int, len(tcr.Treasures)+1) + total := tcr.FreqNoDrop + tprob[0] = total + + for idx := range tcr.Treasures { + total += tcr.Treasures[idx].Probability + tprob[idx+1] = total + } + + roll := f.rand.Intn(total) + + for idx := range tprob { + if roll < tprob[idx] { + if idx == 0 { + break + } + + return tcr.Treasures[idx-1] + } + } + + return nil +} + +// ItemsFromTreasureClass rolls for and creates items using a treasure class record +func (f *ItemFactory) ItemsFromTreasureClass(tcr *d2datadict.TreasureClassRecord) []*Item { + result := make([]*Item, 0) + + treasurePicks := make([]*d2datadict.Treasure, 0) + + // if tcr.NumPicks is negative, each item probability is instead a count for how many + // of that treasure to drop + if tcr.NumPicks < 0 { + picksLeft := tcr.NumPicks + + // for each of the treasures, we pick it N times, where N is the count for the item + // we do this until we run out of picks + for idx := range tcr.Treasures { + howMany := tcr.Treasures[idx].Probability + for count := 0; count < howMany && picksLeft < 0; count++ { + treasurePicks = append(treasurePicks, tcr.Treasures[idx]) + picksLeft++ + } + } + } else { + // for N picks, we roll for a treasure and append to our treasures if it isn't a NoDrop + for picksLeft := tcr.NumPicks; picksLeft > 0; picksLeft-- { + rolledTreasure := f.rollTreasurePick(tcr) + + if rolledTreasure == nil { + continue + } + + treasurePicks = append(treasurePicks, rolledTreasure) + } + } + + // for each of our picked/rolled treasures, we will attempt to generate an item. + // The treasure may actually be a reference to another treasure class, in which + // case we will roll that treasure class, eventually getting a slice of items + for idx := range treasurePicks { + picked := treasurePicks[idx] + if record, found := d2datadict.TreasureClass[picked.Code]; found { + // the code is for a treasure class, we roll again using that TC + itemSlice := f.ItemsFromTreasureClass(record) + for itemIdx := range itemSlice { + itemSlice[itemIdx].applyDropModifier(f.rollDropModifier(tcr)) + itemSlice[itemIdx].init() + result = append(result, itemSlice[itemIdx]) + } + } else { + // the code is not for a treasure class, but for an item + item := f.ItemFromTreasure(picked) + if item != nil { + item.applyDropModifier(f.rollDropModifier(tcr)) + item.init() + result = append(result, item) + } + } + } + + return result +} + +// ItemFromTreasure rolls for a f.rand.m item using the Treasure struct (from d2datadict) +func (f *ItemFactory) ItemFromTreasure(treasure *d2datadict.Treasure) *Item { + result := &Item{ + rand: rand.New(rand.NewSource(f.Seed)), + } + + // in this case, the treasure code is a code used by an ItemCommonRecord + commonRecord := f.asset.Records.Item.All[treasure.Code] + if commonRecord != nil { + result.CommonCode = commonRecord.Code + return result + } + + // next, we check if the treasure code is a generic type like `armo` + equivList := f.asset.Records.Item.Equivalency[treasure.Code] + if equivList != nil { + result.CommonCode = equivList[f.rand.Intn(len(equivList))].Code + return result + } + + // in this case, the treasure code is something like `armo23` and needs to + // be resolved to ItemCommonRecords for armors with levels 23,24,25 + matches := f.resolveDynamicTreasureCode(treasure.Code) + if matches != nil { + numItems := len(matches) + if numItems < 1 { + return nil + } + + result.CommonCode = matches[f.rand.Intn(numItems)].Code + + return result + } + + return nil +} + +// FindMatchingAffixes for a given ItemCommonRecord, find all possible affixes that can spawn +func (f *ItemFactory) FindMatchingAffixes( + icr *d2records.ItemCommonRecord, + fromAffixes map[string]*d2records.ItemAffixCommonRecord, +) []*d2records.ItemAffixCommonRecord { + result := make([]*d2records.ItemAffixCommonRecord, 0) + + equivItemTypes := f.asset.Records.FindEquivalentTypesByItemCommonRecord(icr) + + for prefixIdx := range fromAffixes { + include, exclude := false, false + affix := fromAffixes[prefixIdx] + + for itemTypeIdx := range equivItemTypes { + itemType := equivItemTypes[itemTypeIdx] + + for _, excludedType := range affix.ItemExclude { + if itemType == excludedType { + exclude = true + break + } + } + + if exclude { + break + } + + for _, includedType := range affix.ItemInclude { + if itemType == includedType { + include = true + break + } + } + + if !include { + continue + } + + if icr.Level < affix.Level { + continue + } + + result = append(result, affix) + } + } + + return result +} + +func (f *ItemFactory) resolveDynamicTreasureCode(code string) []*d2records.ItemCommonRecord { + numericComponent := getNumericComponent(code) + stringComponent := getStringComponent(code) + + if stringComponent == goldItemCodeWithMult { + // todo need to do something with the numeric component (the gold multiplier) + stringComponent = goldItemCode + } + + result := make([]*d2records.ItemCommonRecord, 0) + equivList := f.asset.Records.Item.Equivalency[stringComponent] + + for idx := range equivList { + record := equivList[idx] + minLevel := numericComponent + maxLevel := minLevel + DynamicItemLevelRange + + if record.Level >= minLevel && record.Level < maxLevel { + result = append(result, record) + } + } + + return result +} + +func getStringComponent(code string) string { + re := regexp.MustCompile(`\d+`) + return string(re.ReplaceAll([]byte(code), []byte(""))) +} + +func getNumericComponent(code string) int { + result := 0 + + re := regexp.MustCompile(`\D`) + numStr := string(re.ReplaceAll([]byte(code), []byte(""))) + + if number, err := strconv.ParseInt(numStr, 10, 32); err == nil { + result = int(number) + } + + return result +} diff --git a/d2core/d2item/diablo2item/item_generator.go b/d2core/d2item/diablo2item/item_generator.go deleted file mode 100644 index 43b6fe84..00000000 --- a/d2core/d2item/diablo2item/item_generator.go +++ /dev/null @@ -1,253 +0,0 @@ -package diablo2item - -import ( - "math/rand" - "regexp" - "strconv" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" -) - -const ( - DropModifierBaseProbability = 1024 // base DropModifier probability total -) - -type DropModifier int - -const ( - DropModifierNone DropModifier = iota - DropModifierUnique - DropModifierSet - DropModifierRare - DropModifierMagic -) - -const ( - // DynamicItemLevelRange for treasure codes like `armo33`, this code is used to - // select all equivalent items (matching `armo` in this case) with item levels 33,34,35 - DynamicItemLevelRange = 3 -) - -const ( - goldItemCodeWithMult = "gld,mul=" - goldItemCode = "gld" -) - -// ItemGenerator is a diablo 2 implementation of an item generator -type ItemGenerator struct { - rand *rand.Rand - source rand.Source - Seed int64 -} - -// SetSeed sets the item generator seed -func (ig *ItemGenerator) SetSeed(seed int64) { - if ig.rand == nil || ig.source == nil { - ig.source = rand.NewSource(seed) - ig.rand = rand.New(ig.source) - } - - ig.Seed = seed -} - -func (ig *ItemGenerator) rollDropModifier(tcr *d2datadict.TreasureClassRecord) DropModifier { - modMap := map[int]DropModifier{ - 0: DropModifierNone, - 1: DropModifierUnique, - 2: DropModifierSet, - 3: DropModifierRare, - 4: DropModifierMagic, - } - - dropModifiers := []int{ - DropModifierBaseProbability, - tcr.FreqUnique, - tcr.FreqSet, - tcr.FreqRare, - tcr.FreqMagic, - } - - for idx := range dropModifiers { - if idx == 0 { - continue - } - - dropModifiers[idx] += dropModifiers[idx-1] - } - - roll := ig.rand.Intn(dropModifiers[len(dropModifiers)-1]) - - for idx := range dropModifiers { - if roll < dropModifiers[idx] { - return modMap[idx] - } - } - - return DropModifierNone -} - -func (ig *ItemGenerator) rollTreasurePick(tcr *d2datadict.TreasureClassRecord) *d2datadict.Treasure { - // treasure probabilities - tprob := make([]int, len(tcr.Treasures)+1) - total := tcr.FreqNoDrop - tprob[0] = total - - for idx := range tcr.Treasures { - total += tcr.Treasures[idx].Probability - tprob[idx+1] = total - } - - roll := ig.rand.Intn(total) - - for idx := range tprob { - if roll < tprob[idx] { - if idx == 0 { - break - } - - return tcr.Treasures[idx-1] - } - } - - return nil -} - -// ItemsFromTreasureClass rolls for and creates items using a treasure class record -func (ig *ItemGenerator) ItemsFromTreasureClass(tcr *d2datadict.TreasureClassRecord) []*Item { - result := make([]*Item, 0) - - treasurePicks := make([]*d2datadict.Treasure, 0) - - // if tcr.NumPicks is negative, each item probability is instead a count for how many - // of that treasure to drop - if tcr.NumPicks < 0 { - picksLeft := tcr.NumPicks - - // for each of the treasures, we pick it N times, where N is the count for the item - // we do this until we run out of picks - for idx := range tcr.Treasures { - howMany := tcr.Treasures[idx].Probability - for count := 0; count < howMany && picksLeft < 0; count++ { - treasurePicks = append(treasurePicks, tcr.Treasures[idx]) - picksLeft++ - } - } - } else { - // for N picks, we roll for a treasure and append to our treasures if it isn't a NoDrop - for picksLeft := tcr.NumPicks; picksLeft > 0; picksLeft-- { - rolledTreasure := ig.rollTreasurePick(tcr) - - if rolledTreasure == nil { - continue - } - - treasurePicks = append(treasurePicks, rolledTreasure) - } - } - - // for each of our picked/rolled treasures, we will attempt to generate an item. - // The treasure may actually be a reference to another treasure class, in which - // case we will roll that treasure class, eventually getting a slice of items - for idx := range treasurePicks { - picked := treasurePicks[idx] - if record, found := d2datadict.TreasureClass[picked.Code]; found { - // the code is for a treasure class, we roll again using that TC - itemSlice := ig.ItemsFromTreasureClass(record) - for itemIdx := range itemSlice { - itemSlice[itemIdx].applyDropModifier(ig.rollDropModifier(tcr)) - itemSlice[itemIdx].init() - result = append(result, itemSlice[itemIdx]) - } - } else { - // the code is not for a treasure class, but for an item - item := ig.ItemFromTreasure(picked) - if item != nil { - item.applyDropModifier(ig.rollDropModifier(tcr)) - item.init() - result = append(result, item) - } - } - } - - return result -} - -// ItemFromTreasure rolls for a ig.rand.m item using the Treasure struct (from d2datadict) -func (ig *ItemGenerator) ItemFromTreasure(treasure *d2datadict.Treasure) *Item { - result := &Item{ - rand: rand.New(rand.NewSource(ig.Seed)), - } - - // in this case, the treasure code is a code used by an ItemCommonRecord - commonRecord := d2datadict.CommonItems[treasure.Code] - if commonRecord != nil { - result.CommonCode = commonRecord.Code - return result - } - - // next, we check if the treasure code is a generic type like `armo` - equivList := d2datadict.ItemEquivalenciesByTypeCode[treasure.Code] - if equivList != nil { - result.CommonCode = equivList[ig.rand.Intn(len(equivList))].Code - return result - } - - // in this case, the treasure code is something like `armo23` and needs to - // be resolved to ItemCommonRecords for armors with levels 23,24,25 - matches := resolveDynamicTreasureCode(treasure.Code) - if matches != nil { - numItems := len(matches) - if numItems < 1 { - return nil - } - - result.CommonCode = matches[ig.rand.Intn(numItems)].Code - - return result - } - - return nil -} - -func resolveDynamicTreasureCode(code string) []*d2datadict.ItemCommonRecord { - numericComponent := getNumericComponent(code) - stringComponent := getStringComponent(code) - - if stringComponent == goldItemCodeWithMult { - // todo need to do something with the numeric component (the gold multiplier) - stringComponent = goldItemCode - } - - result := make([]*d2datadict.ItemCommonRecord, 0) - equivList := d2datadict.ItemEquivalenciesByTypeCode[stringComponent] - - for idx := range equivList { - record := equivList[idx] - minLevel := numericComponent - maxLevel := minLevel + DynamicItemLevelRange - - if record.Level >= minLevel && record.Level < maxLevel { - result = append(result, record) - } - } - - return result -} - -func getStringComponent(code string) string { - re := regexp.MustCompile(`\d+`) - return string(re.ReplaceAll([]byte(code), []byte(""))) -} - -func getNumericComponent(code string) int { - result := 0 - - re := regexp.MustCompile(`\D`) - numStr := string(re.ReplaceAll([]byte(code), []byte(""))) - - if number, err := strconv.ParseInt(numStr, 10, 32); err == nil { - result = int(number) - } - - return result -} diff --git a/d2core/d2item/diablo2item/item_property.go b/d2core/d2item/diablo2item/item_property.go index 8f2e5001..7ef03e44 100644 --- a/d2core/d2item/diablo2item/item_property.go +++ b/d2core/d2item/diablo2item/item_property.go @@ -3,9 +3,8 @@ package diablo2item import ( "math/rand" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats/diablo2stats" ) const ( @@ -67,15 +66,10 @@ const ( fnRandClassSkill = 36 ) -// Property is an item property. Properties act as stat initializers, as well as -// item attribute initializers. A good example of this is for the `Ethereal` property, -// which DOES have a stat, but the stat is actually non-printable as far as the record -// in itemstatcosts.txt is concerned. The behavior of displaying `Ethereal` on an item -// in diablo 2 is hardcoded into whatever handled displaying item descriptions, not -// what was generating stat descriptions (this is a guess, though). -// Another example in min/max damage properties, which do NOT have stats! +// Property is an item property. type Property struct { - record *d2datadict.PropertyRecord + factory *ItemFactory + record *d2records.PropertyRecord stats []d2stats.Stat PropertyType PropertyType @@ -119,7 +113,7 @@ func (p *Property) init() *Property { // repeat the previous fn with the same parameters, but for a different stat. func (p *Property) eval(propStatIdx, previousFnID int) (stat d2stats.Stat, funcID int) { pStatRecord := p.record.Stats[propStatIdx] - iscRecord := d2datadict.ItemStatCosts[pStatRecord.StatCode] + iscRecord := p.factory.asset.Records.Item.Stats[pStatRecord.StatCode] funcID = pStatRecord.FunctionID @@ -169,7 +163,7 @@ func (p *Property) eval(propStatIdx, previousFnID int) (stat d2stats.Stat, funcI } // fnValuesToStat Applies a value to a stat, can use SetX parameter. -func (p *Property) fnValuesToStat(iscRecord *d2datadict.ItemStatCostRecord) d2stats.Stat { +func (p *Property) fnValuesToStat(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat { // the only special case to handle for this function is for // property "color", which corresponds to ISC record "item_lightcolor" // I'm not yet sure how to handle this special case... it is likely @@ -196,7 +190,7 @@ func (p *Property) fnValuesToStat(iscRecord *d2datadict.ItemStatCostRecord) d2st statValue = float64(rand.Intn(max-min+1) + min) - return diablo2stats.NewStat(iscRecord.Name, statValue, propParam) + return p.factory.stat.NewStat(iscRecord.Name, statValue, propParam) } // fnComputeInteger Dmg-min related ??? @@ -216,7 +210,7 @@ func (p *Property) fnComputeInteger() int { } // fnClassSkillTab skilltab skill group ??? -func (p *Property) fnClassSkillTab(iscRecord *d2datadict.ItemStatCostRecord) d2stats.Stat { +func (p *Property) fnClassSkillTab(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat { // from here: https://d2mods.info/forum/kb/viewarticle?a=45 // Amazon // 0 - Bow & Crossbow @@ -251,11 +245,11 @@ func (p *Property) fnClassSkillTab(iscRecord *d2datadict.ItemStatCostRecord) d2s classIdx := float64(param / skillTabsPerClass) level := float64(rand.Intn(max-min+1) + min) - return diablo2stats.NewStat(iscRecord.Name, level, classIdx, skillTabIdx) + return p.factory.stat.NewStat(iscRecord.Name, level, classIdx, skillTabIdx) } // fnProcs event-based skills ??? -func (p *Property) fnProcs(iscRecord *d2datadict.ItemStatCostRecord) d2stats.Stat { +func (p *Property) fnProcs(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat { var skillID, chance, skillLevel float64 switch len(p.inputParams) { @@ -267,11 +261,11 @@ func (p *Property) fnProcs(iscRecord *d2datadict.ItemStatCostRecord) d2stats.Sta skillLevel = float64(p.inputParams[2]) } - return diablo2stats.NewStat(iscRecord.Name, chance, skillLevel, skillID) + return p.factory.stat.NewStat(iscRecord.Name, chance, skillLevel, skillID) } // fnRandomSkill random selection of parameters for parameter-based stat ??? -func (p *Property) fnRandomSkill(iscRecord *d2datadict.ItemStatCostRecord) d2stats.Stat { +func (p *Property) fnRandomSkill(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat { var skillLevel, skillID float64 invalidHeroIndex := -1.0 @@ -285,22 +279,22 @@ func (p *Property) fnRandomSkill(iscRecord *d2datadict.ItemStatCostRecord) d2sta skillID = float64(rand.Intn(max-min+1) + min) } - return diablo2stats.NewStat(iscRecord.Name, skillLevel, skillID, invalidHeroIndex) + return p.factory.stat.NewStat(iscRecord.Name, skillLevel, skillID, invalidHeroIndex) } // fnStatParam use param field only -func (p *Property) fnStatParam(iscRecord *d2datadict.ItemStatCostRecord) d2stats.Stat { +func (p *Property) fnStatParam(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat { switch len(p.inputParams) { case noValue: return nil default: val := float64(p.inputParams[0]) - return diablo2stats.NewStat(iscRecord.Name, val) + return p.factory.stat.NewStat(iscRecord.Name, val) } } // fnChargeRelated Related to charged item. -func (p *Property) fnChargeRelated(iscRecord *d2datadict.ItemStatCostRecord) d2stats.Stat { +func (p *Property) fnChargeRelated(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat { var lvl, skill, charges float64 switch len(p.inputParams) { @@ -311,7 +305,7 @@ func (p *Property) fnChargeRelated(iscRecord *d2datadict.ItemStatCostRecord) d2s skill = float64(p.inputParams[0]) charges = float64(p.inputParams[1]) - return diablo2stats.NewStat(iscRecord.Name, lvl, skill, charges, charges) + return p.factory.stat.NewStat(iscRecord.Name, lvl, skill, charges, charges) } } @@ -333,8 +327,8 @@ func (p *Property) fnBoolean() bool { // fnClassSkills Add to group of skills, group determined by stat ID, uses ValX parameter. func (p *Property) fnClassSkills( - propStatRecord *d2datadict.PropertyStatRecord, - iscRecord *d2datadict.ItemStatCostRecord, + propStatRecord *d2records.PropertyStatRecord, + iscRecord *d2records.ItemStatCostRecord, ) d2stats.Stat { // in order 0..6 // Amazon @@ -355,16 +349,16 @@ func (p *Property) fnClassSkills( statValue := rand.Intn(max-min+1) + min classIdx = propStatRecord.Value - return diablo2stats.NewStat(iscRecord.Name, float64(statValue), float64(classIdx)) + return p.factory.stat.NewStat(iscRecord.Name, float64(statValue), float64(classIdx)) } // fnStateApplyToTarget property applied to character or target monster ??? -func (p *Property) fnStateApplyToTarget(iscRecord *d2datadict.ItemStatCostRecord) d2stats.Stat { +func (p *Property) fnStateApplyToTarget(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat { // todo need to implement states return nil } // fnRandClassSkill property applied to character or target monster ??? -func (p *Property) fnRandClassSkill(iscRecord *d2datadict.ItemStatCostRecord) d2stats.Stat { +func (p *Property) fnRandClassSkill(iscRecord *d2records.ItemStatCostRecord) d2stats.Stat { return nil } diff --git a/d2core/d2item/diablo2item/item_property_test.go b/d2core/d2item/diablo2item/item_property_test.go index 9e234637..fe9f8bd9 100644 --- a/d2core/d2item/diablo2item/item_property_test.go +++ b/d2core/d2item/diablo2item/item_property_test.go @@ -7,413 +7,419 @@ import ( "testing" "time" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" ) -//nolint:funlen // this just gets mock data ready for the tests -func TestStat_InitMockData(t *testing.T) { - var itemStatCosts = map[string]*d2datadict.ItemStatCostRecord{ - "strength": { - Name: "strength", - DescFnID: 1, - DescVal: 1, - DescStrPos: "to Strength", - DescStrNeg: "to Strength", - }, - "dexterity": { - Name: "dexterity", - DescFnID: 1, - DescVal: 1, - DescStrPos: "to Dexterity", - DescStrNeg: "to Dexterity", - }, - "vitality": { - Name: "vitality", - DescFnID: 1, - DescVal: 1, - DescStrPos: "to Vitality", - DescStrNeg: "to Vitality", - }, - "energy": { - Name: "energy", - DescFnID: 1, - DescVal: 1, - DescStrPos: "to Energy", - DescStrNeg: "to Energy", - }, - "hpregen": { - Name: "hpregen", - DescFnID: 1, - DescVal: 2, - DescStrPos: "Replenish Life", - DescStrNeg: "Drain Life", - }, - "toblock": { - Name: "toblock", - DescFnID: 2, - DescVal: 1, - DescStrPos: "Increased Chance of Blocking", - DescStrNeg: "Increased Chance of Blocking", - }, - "item_absorblight_percent": { - Name: "item_absorblight_percent", - DescFnID: 2, - DescVal: 2, - DescStrPos: "Lightning Absorb", - DescStrNeg: "Lightning Absorb", - }, - "item_maxdurability_percent": { - Name: "item_maxdurability_percent", - DescFnID: 2, - DescVal: 2, - DescStrPos: "Increase Maximum Durability", - DescStrNeg: "Increase Maximum Durability", - }, - "item_restinpeace": { - Name: "item_restinpeace", - DescFnID: 3, - DescVal: 0, - DescStrPos: "Slain Monsters Rest in Peace", - DescStrNeg: "Slain Monsters Rest in Peace", - }, - "normal_damage_reduction": { - Name: "normal_damage_reduction", - DescFnID: 3, - DescVal: 2, - DescStrPos: "Damage Reduced by", - DescStrNeg: "Damage Reduced by", - }, - "poisonresist": { - Name: "poisonresist", - DescFnID: 4, - DescVal: 2, - DescStrPos: "Poison Resist", - DescStrNeg: "Poison Resist", - }, - "item_fastermovevelocity": { - Name: "item_fastermovevelocity", - DescFnID: 4, - DescVal: 1, - DescStrPos: "Faster Run/Walk", - DescStrNeg: "Faster Run/Walk", - }, - "item_howl": { - Name: "item_howl", - DescFnID: 5, - DescVal: 2, - DescStrPos: "Hit Causes Monster to Flee", - DescStrNeg: "Hit Causes Monster to Flee", - }, - "item_hp_perlevel": { - Name: "item_hp_perlevel", - DescFnID: 6, - DescVal: 1, - DescStrPos: "to Life", - DescStrNeg: "to Life", - DescStr2: "(Based on Character Level)", - }, - "item_resist_ltng_perlevel": { - Name: "item_resist_ltng_perlevel", - DescFnID: 7, - DescVal: 2, - DescStrPos: "Lightning Resist", - DescStrNeg: "Lightning Resist", - DescStr2: "(Based on Character Level)", - }, - "item_find_magic_perlevel": { - Name: "item_find_magic_perlevel", - DescFnID: 7, - DescVal: 1, - DescStrPos: "Better Chance of Getting Magic Items", - DescStrNeg: "Better Chance of Getting Magic Items", - DescStr2: "(Based on Character Level)", - }, - "item_armorpercent_perlevel": { - Name: "item_armorpercent_perlevel", - DescFnID: 8, - DescVal: 1, - DescStrPos: "Enhanced Defense", - DescStrNeg: "Enhanced Defense", - DescStr2: "(Based on Character Level)", - }, - "item_regenstamina_perlevel": { - Name: "item_regenstamina_perlevel", - DescFnID: 8, - DescVal: 2, - DescStrPos: "Heal Stamina Plus", - DescStrNeg: "Heal Stamina Plus", - DescStr2: "(Based on Character Level)", - }, - "item_thorns_perlevel": { - Name: "item_thorns_perlevel", - DescFnID: 9, - DescVal: 2, - DescStrPos: "Attacker Takes Damage of", - DescStrNeg: "Attacker Takes Damage of", - DescStr2: "(Based on Character Level)", - }, - "item_replenish_durability": { - Name: "item_replenish_durability", - DescFnID: 11, - DescVal: 1, - DescStrPos: "Repairs %v durability per second", - DescStrNeg: "Repairs %v durability per second", - DescStr2: "", - }, - "item_stupidity": { - Name: "item_stupidity", - DescFnID: 12, - DescVal: 2, - DescStrPos: "Hit Blinds Target", - DescStrNeg: "Hit Blinds Target", - }, - "item_addclassskills": { - Name: "item_addclassskills", - DescFnID: 13, - DescVal: 1, - }, - "item_addskill_tab": { - Name: "item_addskill_tab", - DescFnID: 14, - DescVal: 1, - }, - "item_skillonattack": { - Name: "item_skillonattack", - DescFnID: 15, - DescVal: 1, - DescStrPos: "%d%% Chance to cast level %d %s on attack", - DescStrNeg: "%d%% Chance to cast level %d %s on attack", - }, - "item_aura": { - Name: "item_aura", - DescFnID: 16, - DescVal: 1, - DescStrPos: "Level %d %s Aura When Equipped", - DescStrNeg: "Level %d %s Aura When Equipped", - }, - "item_fractionaltargetac": { - Name: "item_fractionaltargetac", - DescFnID: 20, - DescVal: 1, - DescStrPos: "Target Defense", - DescStrNeg: "Target Defense", - }, - "attack_vs_montype": { - Name: "item_fractionaltargetac", - DescFnID: 22, - DescVal: 1, - DescStrPos: "to Attack Rating versus", - DescStrNeg: "to Attack Rating versus", - }, - "item_reanimate": { - Name: "item_reanimate", - DescFnID: 23, - DescVal: 2, - DescStrPos: "Reanimate as:", - DescStrNeg: "Reanimate as:", - }, - "item_charged_skill": { - Name: "item_charged_skill", - DescFnID: 24, - DescVal: 2, - DescStrPos: "(%d/%d Charges)", - DescStrNeg: "(%d/%d Charges)", - }, - "item_singleskill": { - Name: "item_singleskill", - DescFnID: 27, - DescVal: 0, - }, - "item_nonclassskill": { - Name: "item_nonclassskill", - DescFnID: 28, - DescVal: 2, - DescStrPos: "(%d/%d Charges)", - DescStrNeg: "(%d/%d Charges)", - }, - "item_armor_percent": { - Name: "item_armor_percent", - DescFnID: 4, - DescVal: 1, - DescStrPos: "Enhanced Defense", - DescStrNeg: "Enhanced Defense", - }, - "item_fastercastrate": { - Name: "item_fastercastrate", - DescFnID: 4, - DescVal: 1, - DescStrPos: "Faster Cast Rate", - DescStrNeg: "Faster Cast Rate", - }, - "item_skillonlevelup": { - Name: "item_skillonlevelup", - DescFnID: 15, - DescVal: 0, - DescStrPos: "%d%% Chance to cast level %d %s when you Level-Up", - DescStrNeg: "%d%% Chance to cast level %d %s when you Level-Up", - }, - "item_numsockets": { - Name: "item_numsockets", - }, - "poisonmindam": { - Name: "poisonmindam", - DescFnID: 1, - DescVal: 1, - DescStrPos: "to Minimum Poison Damage", - DescStrNeg: "to Minimum Poison Damage", - }, - "poisonmaxdam": { - Name: "poisonmaxdam", - DescFnID: 1, - DescVal: 1, - DescStrPos: "to Maximum Poison Damage", - DescStrNeg: "to Maximum Poison Damage", - }, - "poisonlength": { - Name: "poisonlength", - }, - } +var itemStatCosts = map[string]*d2records.ItemStatCostRecord{ + "strength": { + Name: "strength", + DescFnID: 1, + DescVal: 1, + DescStrPos: "to Strength", + DescStrNeg: "to Strength", + }, + "dexterity": { + Name: "dexterity", + DescFnID: 1, + DescVal: 1, + DescStrPos: "to Dexterity", + DescStrNeg: "to Dexterity", + }, + "vitality": { + Name: "vitality", + DescFnID: 1, + DescVal: 1, + DescStrPos: "to Vitality", + DescStrNeg: "to Vitality", + }, + "energy": { + Name: "energy", + DescFnID: 1, + DescVal: 1, + DescStrPos: "to Energy", + DescStrNeg: "to Energy", + }, + "hpregen": { + Name: "hpregen", + DescFnID: 1, + DescVal: 2, + DescStrPos: "Replenish Life", + DescStrNeg: "Drain Life", + }, + "toblock": { + Name: "toblock", + DescFnID: 2, + DescVal: 1, + DescStrPos: "Increased Chance of Blocking", + DescStrNeg: "Increased Chance of Blocking", + }, + "item_absorblight_percent": { + Name: "item_absorblight_percent", + DescFnID: 2, + DescVal: 2, + DescStrPos: "Lightning Absorb", + DescStrNeg: "Lightning Absorb", + }, + "item_maxdurability_percent": { + Name: "item_maxdurability_percent", + DescFnID: 2, + DescVal: 2, + DescStrPos: "Increase Maximum Durability", + DescStrNeg: "Increase Maximum Durability", + }, + "item_restinpeace": { + Name: "item_restinpeace", + DescFnID: 3, + DescVal: 0, + DescStrPos: "Slain Monsters Rest in Peace", + DescStrNeg: "Slain Monsters Rest in Peace", + }, + "normal_damage_reduction": { + Name: "normal_damage_reduction", + DescFnID: 3, + DescVal: 2, + DescStrPos: "Damage Reduced by", + DescStrNeg: "Damage Reduced by", + }, + "poisonresist": { + Name: "poisonresist", + DescFnID: 4, + DescVal: 2, + DescStrPos: "Poison Resist", + DescStrNeg: "Poison Resist", + }, + "item_fastermovevelocity": { + Name: "item_fastermovevelocity", + DescFnID: 4, + DescVal: 1, + DescStrPos: "Faster Run/Walk", + DescStrNeg: "Faster Run/Walk", + }, + "item_howl": { + Name: "item_howl", + DescFnID: 5, + DescVal: 2, + DescStrPos: "Hit Causes Monster to Flee", + DescStrNeg: "Hit Causes Monster to Flee", + }, + "item_hp_perlevel": { + Name: "item_hp_perlevel", + DescFnID: 6, + DescVal: 1, + DescStrPos: "to Life", + DescStrNeg: "to Life", + DescStr2: "(Based on Character Level)", + }, + "item_resist_ltng_perlevel": { + Name: "item_resist_ltng_perlevel", + DescFnID: 7, + DescVal: 2, + DescStrPos: "Lightning Resist", + DescStrNeg: "Lightning Resist", + DescStr2: "(Based on Character Level)", + }, + "item_find_magic_perlevel": { + Name: "item_find_magic_perlevel", + DescFnID: 7, + DescVal: 1, + DescStrPos: "Better Chance of Getting Magic Items", + DescStrNeg: "Better Chance of Getting Magic Items", + DescStr2: "(Based on Character Level)", + }, + "item_armorpercent_perlevel": { + Name: "item_armorpercent_perlevel", + DescFnID: 8, + DescVal: 1, + DescStrPos: "Enhanced Defense", + DescStrNeg: "Enhanced Defense", + DescStr2: "(Based on Character Level)", + }, + "item_regenstamina_perlevel": { + Name: "item_regenstamina_perlevel", + DescFnID: 8, + DescVal: 2, + DescStrPos: "Heal Stamina Plus", + DescStrNeg: "Heal Stamina Plus", + DescStr2: "(Based on Character Level)", + }, + "item_thorns_perlevel": { + Name: "item_thorns_perlevel", + DescFnID: 9, + DescVal: 2, + DescStrPos: "Attacker Takes Damage of", + DescStrNeg: "Attacker Takes Damage of", + DescStr2: "(Based on Character Level)", + }, + "item_replenish_durability": { + Name: "item_replenish_durability", + DescFnID: 11, + DescVal: 1, + DescStrPos: "Repairs %v durability per second", + DescStrNeg: "Repairs %v durability per second", + DescStr2: "", + }, + "item_stupidity": { + Name: "item_stupidity", + DescFnID: 12, + DescVal: 2, + DescStrPos: "Hit Blinds Target", + DescStrNeg: "Hit Blinds Target", + }, + "item_addclassskills": { + Name: "item_addclassskills", + DescFnID: 13, + DescVal: 1, + }, + "item_addskill_tab": { + Name: "item_addskill_tab", + DescFnID: 14, + DescVal: 1, + }, + "item_skillonattack": { + Name: "item_skillonattack", + DescFnID: 15, + DescVal: 1, + DescStrPos: "%d%% Chance to cast level %d %s on attack", + DescStrNeg: "%d%% Chance to cast level %d %s on attack", + }, + "item_aura": { + Name: "item_aura", + DescFnID: 16, + DescVal: 1, + DescStrPos: "Level %d %s Aura When Equipped", + DescStrNeg: "Level %d %s Aura When Equipped", + }, + "item_fractionaltargetac": { + Name: "item_fractionaltargetac", + DescFnID: 20, + DescVal: 1, + DescStrPos: "Target Defense", + DescStrNeg: "Target Defense", + }, + "attack_vs_montype": { + Name: "item_fractionaltargetac", + DescFnID: 22, + DescVal: 1, + DescStrPos: "to Attack Rating versus", + DescStrNeg: "to Attack Rating versus", + }, + "item_reanimate": { + Name: "item_reanimate", + DescFnID: 23, + DescVal: 2, + DescStrPos: "Reanimate as:", + DescStrNeg: "Reanimate as:", + }, + "item_charged_skill": { + Name: "item_charged_skill", + DescFnID: 24, + DescVal: 2, + DescStrPos: "(%d/%d Charges)", + DescStrNeg: "(%d/%d Charges)", + }, + "item_singleskill": { + Name: "item_singleskill", + DescFnID: 27, + DescVal: 0, + }, + "item_nonclassskill": { + Name: "item_nonclassskill", + DescFnID: 28, + DescVal: 2, + DescStrPos: "(%d/%d Charges)", + DescStrNeg: "(%d/%d Charges)", + }, + "item_armor_percent": { + Name: "item_armor_percent", + DescFnID: 4, + DescVal: 1, + DescStrPos: "Enhanced Defense", + DescStrNeg: "Enhanced Defense", + }, + "item_fastercastrate": { + Name: "item_fastercastrate", + DescFnID: 4, + DescVal: 1, + DescStrPos: "Faster Cast Rate", + DescStrNeg: "Faster Cast Rate", + }, + "item_skillonlevelup": { + Name: "item_skillonlevelup", + DescFnID: 15, + DescVal: 0, + DescStrPos: "%d%% Chance to cast level %d %s when you Level-Up", + DescStrNeg: "%d%% Chance to cast level %d %s when you Level-Up", + }, + "item_numsockets": { + Name: "item_numsockets", + }, + "poisonmindam": { + Name: "poisonmindam", + DescFnID: 1, + DescVal: 1, + DescStrPos: "to Minimum Poison Damage", + DescStrNeg: "to Minimum Poison Damage", + }, + "poisonmaxdam": { + Name: "poisonmaxdam", + DescFnID: 1, + DescVal: 1, + DescStrPos: "to Maximum Poison Damage", + DescStrNeg: "to Maximum Poison Damage", + }, + "poisonlength": { + Name: "poisonlength", + }, +} - var charStats = map[d2enum.Hero]*d2datadict.CharStatsRecord{ - d2enum.HeroPaladin: { - Class: d2enum.HeroPaladin, - SkillStrAll: "to Paladin Skill Levels", - SkillStrClassOnly: "(Paladin Only)", - SkillStrTab: [3]string{ - "+%d to Combat Skills", - "+%d to Offensive Auras", - "+%d to Defensive Auras", - }, +var charStats = map[d2enum.Hero]*d2records.CharStatsRecord{ + d2enum.HeroPaladin: { + Class: d2enum.HeroPaladin, + SkillStrAll: "to Paladin Skill Levels", + SkillStrClassOnly: "(Paladin Only)", + SkillStrTab: [3]string{ + "+%d to Combat Skills", + "+%d to Offensive Auras", + "+%d to Defensive Auras", }, - } + }, +} - var skillDetails = map[int]*d2datadict.SkillRecord{ - 37: {Skill: "Warmth"}, - 64: {Skill: "Frozen Orb"}, - } +var skillDetails = map[int]*d2records.SkillRecord{ + 37: {Skill: "Warmth"}, + 64: {Skill: "Frozen Orb"}, +} - var monStats = map[string]*d2datadict.MonStatsRecord{ - "Specter": {NameString: "Specter", ID: 40}, - } +var monStats = map[string]*d2records.MonStatsRecord{ + "Specter": {NameString: "Specter", ID: 40}, +} - properties := map[string]*d2datadict.PropertyRecord{ - "allstats": { - Code: "allstats", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 1, StatCode: "strength"}, - {FunctionID: 3, StatCode: "dexterity"}, - {FunctionID: 3, StatCode: "vitality"}, - {FunctionID: 3, StatCode: "energy"}, - }, +var properties = map[string]*d2records.PropertyRecord{ + "allstats": { + Code: "allstats", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 1, StatCode: "strength"}, + {FunctionID: 3, StatCode: "dexterity"}, + {FunctionID: 3, StatCode: "vitality"}, + {FunctionID: 3, StatCode: "energy"}, }, - "ac%": { - Code: "ac%", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 2, StatCode: "item_armor_percent"}, - }, + }, + "ac%": { + Code: "ac%", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 2, StatCode: "item_armor_percent"}, }, - // dmg-min, dmg-max, dmg%, indestruct, and ethereal do not yield stats. - // these properties are used specifically to compute a value. - "dmg-min": { - Code: "dmg-min", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 5}, - }, + }, + "dmg-min": { + Code: "dmg-min", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 5}, }, - "dmg-max": { - Code: "dmg-max", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 6}, - }, + }, + "dmg-max": { + Code: "dmg-max", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 6}, }, - "dmg%": { - Code: "dmg%", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 7}, - }, + }, + "dmg%": { + Code: "dmg%", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 7}, }, - "cast1": { - Code: "cast1", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 8, StatCode: "item_fastercastrate"}, - }, + }, + "cast1": { + Code: "cast1", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 8, StatCode: "item_fastercastrate"}, }, - "skilltab": { - Code: "skilltab", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 10, StatCode: "item_addskill_tab"}, - }, + }, + "skilltab": { + Code: "skilltab", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 10, StatCode: "item_addskill_tab"}, }, - "levelup-skill": { - Code: "levelup-skill", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 11, StatCode: "item_skillonlevelup"}, - }, + }, + "levelup-skill": { + Code: "levelup-skill", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 11, StatCode: "item_skillonlevelup"}, }, - "skill-rand": { - Code: "skill-rand", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 12, StatCode: "item_singleskill"}, - }, + }, + "skill-rand": { + Code: "skill-rand", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 12, StatCode: "item_singleskill"}, }, - "dur%": { - Code: "dur%", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 13, StatCode: "item_maxdurability_percent"}, - }, + }, + "dur%": { + Code: "dur%", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 13, StatCode: "item_maxdurability_percent"}, }, - "sock": { - Code: "sock", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 14, StatCode: "item_numsockets"}, - }, + }, + "sock": { + Code: "sock", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 14, StatCode: "item_numsockets"}, }, - "dmg-pois": { - Code: "dmg-pois", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 15, StatCode: "poisonmindam"}, - {FunctionID: 16, StatCode: "poisonmaxdam"}, - {FunctionID: 17, StatCode: "poisonlength"}, - }, + }, + "dmg-pois": { + Code: "dmg-pois", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 15, StatCode: "poisonmindam"}, + {FunctionID: 16, StatCode: "poisonmaxdam"}, + {FunctionID: 17, StatCode: "poisonlength"}, }, - "charged": { - Code: "charged", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 19, StatCode: "item_charged_skill"}, - }, + }, + "charged": { + Code: "charged", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 19, StatCode: "item_charged_skill"}, }, - "indestruct": { - Code: "indestruct", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 20}, - }, + }, + "indestruct": { + Code: "indestruct", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 20}, }, - "pal": { - Code: "pal", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 21, StatCode: "item_addclassskills", Value: 3}, - }, + }, + "pal": { + Code: "pal", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 21, StatCode: "item_addclassskills", Value: 3}, }, - "oskill": { - Code: "oskill", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 22, StatCode: "item_nonclassskill"}, - }, + }, + "oskill": { + Code: "oskill", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 22, StatCode: "item_nonclassskill"}, }, - "ethereal": { - Code: "ethereal", - Stats: [7]*d2datadict.PropertyStatRecord{ - {FunctionID: 23}, - }, + }, + "ethereal": { + Code: "ethereal", + Stats: [7]*d2records.PropertyStatRecord{ + {FunctionID: 23}, }, - } + }, +} - d2datadict.ItemStatCosts = itemStatCosts - d2datadict.CharStats = charStats - d2datadict.SkillDetails = skillDetails - d2datadict.MonStats = monStats - d2datadict.Properties = properties +var testAssetManager *d2asset.AssetManager + +var testItemFactory *ItemFactory + +func TestSetup(t *testing.T) { + testAssetManager = &d2asset.AssetManager{} + testAssetManager.Records = &d2records.RecordManager{} + testItemFactory, _ = NewItemFactory(testAssetManager) + testAssetManager.Records.Item.Stats = itemStatCosts + testAssetManager.Records.Character.Stats = charStats + testAssetManager.Records.Skill.Details = skillDetails + testAssetManager.Records.Monster.Stats = monStats + testAssetManager.Records.Properties = properties } func TestNewProperty(t *testing.T) { //nolint:funlen it's mostly test-case definitions @@ -546,7 +552,7 @@ func TestNewProperty(t *testing.T) { //nolint:funlen it's mostly test-case defin for testIdx := range tests { test := &tests[testIdx] - prop := NewProperty(test.propKey, test.inputValues...) + prop := testItemFactory.NewProperty(test.propKey, test.inputValues...) if prop == nil { t.Error("property is nil") diff --git a/d2core/d2map/d2mapengine/engine.go b/d2core/d2map/d2mapengine/engine.go index 799334ea..fe58bc0c 100644 --- a/d2core/d2map/d2mapengine/engine.go +++ b/d2core/d2map/d2mapengine/engine.go @@ -4,9 +4,10 @@ import ( "log" "strings" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1" @@ -24,17 +25,17 @@ type MapEngine struct { seed int64 // The map seed entities map[string]d2interface.MapEntity // Entities on the map tiles []MapTile - size d2geom.Size // Size of the map, in tiles - levelType d2datadict.LevelTypeRecord // Level type of this map - dt1TileData []d2dt1.Tile // DT1 tile data - startSubTileX int // Starting X position - startSubTileY int // Starting Y position - dt1Files []string // List of DS1 strings + size d2geom.Size // Size of the map, in tiles + levelType d2records.LevelTypeRecord // Level type of this map + dt1TileData []d2dt1.Tile // DT1 tile data + startSubTileX int // Starting X position + startSubTileY int // Starting Y position + dt1Files []string // List of DS1 strings } // CreateMapEngine creates a new instance of the map engine and returns a pointer to it. func CreateMapEngine(asset *d2asset.AssetManager) *MapEngine { - entity := d2mapentity.NewMapEntityFactory(asset) + entity, _ := d2mapentity.NewMapEntityFactory(asset) stamp := d2mapstamp.NewStampFactory(asset, entity) engine := &MapEngine{ @@ -54,7 +55,7 @@ func (m *MapEngine) GetStartingPosition() (x, y int) { // ResetMap clears all map and entity data and reloads it from the cached files. func (m *MapEngine) ResetMap(levelType d2enum.RegionIdType, width, height int) { m.entities = make(map[string]d2interface.MapEntity) - m.levelType = d2datadict.LevelTypes[levelType] + m.levelType = *m.asset.Records.Level.Types[levelType] m.size = d2geom.Size{Width: width, Height: height} m.tiles = make([]MapTile, width*height) m.dt1TileData = make([]d2dt1.Tile, 0) @@ -115,7 +116,7 @@ func (m *MapEngine) AddDS1(fileName string) { } // LevelType returns the level type of this map. -func (m *MapEngine) LevelType() d2datadict.LevelTypeRecord { +func (m *MapEngine) LevelType() d2records.LevelTypeRecord { return m.levelType } diff --git a/d2core/d2map/d2mapentity/factory.go b/d2core/d2map/d2mapentity/factory.go index ec1f4a5b..d4498c43 100644 --- a/d2core/d2map/d2mapentity/factory.go +++ b/d2core/d2map/d2mapentity/factory.go @@ -1,16 +1,17 @@ package d2mapentity import ( - "errors" "fmt" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" + uuid "github.com/satori/go.uuid" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" @@ -19,13 +20,31 @@ import ( ) // NewMapEntityFactory creates a MapEntityFactory instance with the given asset manager -func NewMapEntityFactory(asset *d2asset.AssetManager) *MapEntityFactory { - return &MapEntityFactory{asset} +func NewMapEntityFactory(asset *d2asset.AssetManager) (*MapEntityFactory, error) { + itemFactory, err := diablo2item.NewItemFactory(asset) + if err != nil { + return nil, err + } + + stateFactory, err := d2hero.NewHeroStateFactory(asset) + if err != nil { + return nil, err + } + + entityFactory := &MapEntityFactory{ + stateFactory, + asset, + itemFactory, + } + + return entityFactory, nil } // MapEntityFactory creates map entities for the MapEngine type MapEntityFactory struct { + *d2hero.HeroStateFactory asset *d2asset.AssetManager + item *diablo2item.ItemFactory } // NewAnimatedEntity creates an instance of AnimatedEntity @@ -41,7 +60,7 @@ func NewAnimatedEntity(x, y int, animation d2interface.Animation) *AnimatedEntit // NewPlayer creates a new player entity and returns a pointer to it. func (f *MapEntityFactory) NewPlayer(id, name string, x, y, direction int, heroType d2enum.Hero, - stats *d2hero.HeroStatsState, skills *d2hero.HeroSkillsState, equipment *d2inventory.CharacterEquipment) *Player { + stats *d2hero.HeroStatsState, skills map[int]*d2hero.HeroSkill, equipment *d2inventory.CharacterEquipment) *Player { layerEquipment := &[d2enum.CompositeTypeMax]string{ d2enum.CompositeTypeHead: equipment.Head.GetArmorClass(), d2enum.CompositeTypeTorso: equipment.Torso.GetArmorClass(), @@ -59,21 +78,25 @@ func (f *MapEntityFactory) NewPlayer(id, name string, x, y, direction int, heroT panic(err) } - stats.NextLevelExp = d2datadict.GetExperienceBreakpoint(heroType, stats.Level) + stats.NextLevelExp = f.asset.Records.GetExperienceBreakpoint(heroType, stats.Level) stats.Stamina = stats.MaxStamina + defaultCharStats := f.asset.Records.Character.Stats[heroType] + statsState := f.HeroStateFactory.CreateHeroStatsState(heroType, defaultCharStats) + heroState, _ := f.CreateHeroState(name, heroType, statsState) + attackSkillID := 0 result := &Player{ mapEntity: newMapEntity(x, y), composite: composite, Equipment: equipment, - Stats: stats, - Skills: skills, + Stats: heroState.Stats, + Skills: heroState.Skills, //TODO: active left & right skill should be loaded from save file instead - LeftSkill: (*skills)[attackSkillID], - RightSkill: (*skills)[attackSkillID], - name: name, - Class: heroType, + LeftSkill: heroState.Skills[attackSkillID], + RightSkill: heroState.Skills[attackSkillID], + name: name, + Class: heroType, //nameLabel: d2ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic), isRunToggled: true, isInTown: true, @@ -99,7 +122,7 @@ func (f *MapEntityFactory) NewPlayer(id, name string, x, y, direction int, heroT } // NewMissile creates a new Missile and initializes it's animation. -func (f *MapEntityFactory) NewMissile(x, y int, record *d2datadict.MissileRecord) (*Missile, error) { +func (f *MapEntityFactory) NewMissile(x, y int, record *d2records.MissileRecord) (*Missile, error) { animation, err := f.asset.LoadAnimation( fmt.Sprintf("%s/%s.dcc", d2resource.MissileData, record.Animation.CelFileName), d2resource.PaletteUnits, @@ -128,10 +151,10 @@ func (f *MapEntityFactory) NewMissile(x, y int, record *d2datadict.MissileRecord // NewItem creates an item map entity func (f *MapEntityFactory) NewItem(x, y int, codes ...string) (*Item, error) { - item := diablo2item.NewItem(codes...) + item, err := f.item.NewItem(codes...) - if item == nil { - return nil, errors.New(errInvalidItemCodes) + if err != nil { + return nil, err } filename := item.CommonRecord().FlippyFile @@ -155,12 +178,12 @@ func (f *MapEntityFactory) NewItem(x, y int, codes ...string) (*Item, error) { } // NewNPC creates a new NPC and returns a pointer to it. -func (f *MapEntityFactory) NewNPC(x, y int, monstat *d2datadict.MonStatsRecord, direction int) (*NPC, error) { +func (f *MapEntityFactory) NewNPC(x, y int, monstat *d2records.MonStatsRecord, direction int) (*NPC, error) { result := &NPC{ mapEntity: newMapEntity(x, y), HasPaths: false, monstatRecord: monstat, - monstatEx: d2datadict.MonStats2[monstat.ExtraDataKey], + monstatEx: f.asset.Records.Monster.Stats2[monstat.ExtraDataKey], } var equipment [16]string @@ -214,9 +237,9 @@ func (f *MapEntityFactory) NewObject(x, y int, objectRec *d2datadict.ObjectRecor entity.composite = composite - entity.setMode(d2enum.ObjectAnimationModeNeutral, 0, false) + _ = entity.setMode(d2enum.ObjectAnimationModeNeutral, 0, false) - initObject(entity) + _, _ = initObject(entity) return entity, nil } diff --git a/d2core/d2map/d2mapentity/item.go b/d2core/d2map/d2mapentity/item.go index 03486f83..1a034d78 100644 --- a/d2core/d2map/d2mapentity/item.go +++ b/d2core/d2map/d2mapentity/item.go @@ -9,10 +9,6 @@ import ( // static check that item implements map entity interface var _ d2interface.MapEntity = &Item{} -const ( - errInvalidItemCodes = "invalid item codes supplied" -) - // Item is a map entity for an item type Item struct { *AnimatedEntity diff --git a/d2core/d2map/d2mapentity/missile.go b/d2core/d2map/d2mapentity/missile.go index b8b6429d..9f70343e 100644 --- a/d2core/d2map/d2mapentity/missile.go +++ b/d2core/d2map/d2mapentity/missile.go @@ -3,15 +3,15 @@ package d2mapentity import ( "math" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" ) // Missile is a simple animated entity representing a projectile, // such as a spell or arrow. type Missile struct { *AnimatedEntity - record *d2datadict.MissileRecord + record *d2records.MissileRecord } // ID returns the missile uuid diff --git a/d2core/d2map/d2mapentity/npc.go b/d2core/d2map/d2mapentity/npc.go index 6c57299c..a91de028 100644 --- a/d2core/d2map/d2mapentity/npc.go +++ b/d2core/d2map/d2mapentity/npc.go @@ -3,7 +3,8 @@ package d2mapentity import ( "math/rand" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" @@ -21,8 +22,8 @@ type NPC struct { action int path int repetitions int - monstatRecord *d2datadict.MonStatsRecord - monstatEx *d2datadict.MonStats2Record + monstatRecord *d2records.MonStatsRecord + monstatEx *d2records.MonStats2Record HasPaths bool isDone bool } diff --git a/d2core/d2map/d2mapentity/player.go b/d2core/d2map/d2mapentity/player.go index 85cc3eb0..254c1dae 100644 --- a/d2core/d2map/d2mapentity/player.go +++ b/d2core/d2map/d2mapentity/player.go @@ -19,7 +19,7 @@ type Player struct { composite *d2asset.Composite Equipment *d2inventory.CharacterEquipment Stats *d2hero.HeroStatsState - Skills *d2hero.HeroSkillsState + Skills map[int]*d2hero.HeroSkill LeftSkill *d2hero.HeroSkill RightSkill *d2hero.HeroSkill Class d2enum.Hero diff --git a/d2core/d2map/d2mapgen/act1_overworld.go b/d2core/d2map/d2mapgen/act1_overworld.go index 79d31970..7a2a68ac 100644 --- a/d2core/d2map/d2mapgen/act1_overworld.go +++ b/d2core/d2map/d2mapgen/act1_overworld.go @@ -5,34 +5,24 @@ import ( "math/rand" "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen/d2wilderness" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapstamp" ) -func loadPreset(mapEngine *d2mapengine.MapEngine, id, index int) *d2mapstamp.Stamp { - for _, file := range d2datadict.LevelPreset(id).Files { - mapEngine.AddDS1(file) - } - - return mapEngine.LoadStamp(d2enum.RegionAct1Wilderness, id, index) -} - // GenerateAct1Overworld generates the map and entities for the first town and surrounding area. -func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) { - rand.Seed(mapEngine.Seed()) +func (g *MapGenerator) GenerateAct1Overworld() { + rand.Seed(g.engine.Seed()) - wilderness1Details := d2datadict.GetLevelDetails(2) + wilderness1Details := g.asset.Records.GetLevelDetails(2) - mapEngine.ResetMap(d2enum.RegionAct1Town, 150, 150) - mapWidth := mapEngine.Size().Width - mapHeight := mapEngine.Size().Height + g.engine.ResetMap(d2enum.RegionAct1Town, 150, 150) + mapWidth := g.engine.Size().Width + mapHeight := g.engine.Size().Height - townStamp := mapEngine.LoadStamp(d2enum.RegionAct1Town, 1, -1) + townStamp := g.engine.LoadStamp(d2enum.RegionAct1Town, 1, -1) townStamp.RegionPath() townSize := townStamp.Size() @@ -40,57 +30,59 @@ func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) { switch { case strings.Contains(townStamp.RegionPath(), "E1"): - mapEngine.PlaceStamp(townStamp, 0, 0) - generateWilderness1TownEast(mapEngine, townSize.Width, 0) + g.engine.PlaceStamp(townStamp, 0, 0) + g.generateWilderness1TownEast(townSize.Width, 0) case strings.Contains(townStamp.RegionPath(), "S1"): - mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, 0) - rightWaterBorderStamp := loadPreset(mapEngine, d2wilderness.WaterBorderEast, 0) - rightWaterBorderStamp2 := loadPreset(mapEngine, d2wilderness.WaterBorderWest, 0) + g.engine.PlaceStamp(townStamp, mapWidth-townSize.Width, 0) + rightWaterBorderStamp := g.loadPreset(d2wilderness.WaterBorderEast, 0) + rightWaterBorderStamp2 := g.loadPreset(d2wilderness.WaterBorderWest, 0) for y := townSize.Height; y < mapHeight-9; y += 9 { - mapEngine.PlaceStamp(rightWaterBorderStamp, mapWidth-17, y) - mapEngine.PlaceStamp(rightWaterBorderStamp2, mapWidth-9, y) + g.engine.PlaceStamp(rightWaterBorderStamp, mapWidth-17, y) + g.engine.PlaceStamp(rightWaterBorderStamp2, mapWidth-9, y) } - generateWilderness1TownSouth(mapEngine, mapWidth-wilderness1Details.SizeXNormal-14, townSize.Height) + g.generateWilderness1TownSouth(mapWidth-wilderness1Details.SizeXNormal-14, townSize.Height) case strings.Contains(townStamp.RegionPath(), "W1"): - mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height) - generateWilderness1TownWest(mapEngine, mapWidth-townSize.Width-wilderness1Details.SizeXNormal, mapHeight-wilderness1Details.SizeYNormal) + g.engine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height) + startX := mapWidth - townSize.Width - wilderness1Details.SizeXNormal + startY := mapHeight - wilderness1Details.SizeYNormal + g.generateWilderness1TownWest(startX, startY) default: - mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height) + g.engine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height) } } -func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, startY int) { - levelDetails := d2datadict.GetLevelDetails(2) +func (g *MapGenerator) generateWilderness1TownEast(startX, startY int) { + levelDetails := g.asset.Records.GetLevelDetails(2) fenceNorthStamp := []*d2mapstamp.Stamp{ - loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0), - loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 1), - loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 2), + g.loadPreset(d2wilderness.TreeBorderNorth, 0), + g.loadPreset(d2wilderness.TreeBorderNorth, 1), + g.loadPreset(d2wilderness.TreeBorderNorth, 2), } fenceSouthStamp := []*d2mapstamp.Stamp{ - loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 0), - loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 1), - loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 2), + g.loadPreset(d2wilderness.TreeBorderSouth, 0), + g.loadPreset(d2wilderness.TreeBorderSouth, 1), + g.loadPreset(d2wilderness.TreeBorderSouth, 2), } fenceWestStamp := []*d2mapstamp.Stamp{ - loadPreset(mapEngine, d2wilderness.TreeBorderWest, 0), - loadPreset(mapEngine, d2wilderness.TreeBorderWest, 1), - loadPreset(mapEngine, d2wilderness.TreeBorderWest, 2), + g.loadPreset(d2wilderness.TreeBorderWest, 0), + g.loadPreset(d2wilderness.TreeBorderWest, 1), + g.loadPreset(d2wilderness.TreeBorderWest, 2), } fenceEastStamp := []*d2mapstamp.Stamp{ - loadPreset(mapEngine, d2wilderness.TreeBorderEast, 0), - loadPreset(mapEngine, d2wilderness.TreeBorderEast, 1), - loadPreset(mapEngine, d2wilderness.TreeBorderEast, 2), + g.loadPreset(d2wilderness.TreeBorderEast, 0), + g.loadPreset(d2wilderness.TreeBorderEast, 1), + g.loadPreset(d2wilderness.TreeBorderEast, 2), } - fenceSouthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthWest, 0) - fenceNorthEastStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthEast, 0) - fenceSouthEastStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthEast, 0) - fenceWestEdge := loadPreset(mapEngine, d2wilderness.TreeBoxNorthEast, 0) + fenceSouthWestStamp := g.loadPreset(d2wilderness.TreeBorderSouthWest, 0) + fenceNorthEastStamp := g.loadPreset(d2wilderness.TreeBorderNorthEast, 0) + fenceSouthEastStamp := g.loadPreset(d2wilderness.TreeBorderSouthEast, 0) + fenceWestEdge := g.loadPreset(d2wilderness.TreeBoxNorthEast, 0) areaRect := d2geom.Rectangle{ Left: startX, @@ -98,54 +90,57 @@ func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, start Width: levelDetails.SizeXNormal, Height: levelDetails.SizeYNormal - 3, } - generateWilderness1Contents(mapEngine, areaRect) + + g.generateWilderness1Contents(areaRect) // Draw the north and south fence for i := 0; i < 9; i++ { - mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9), startY) - mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), startY+(levelDetails.SizeYNormal+6)) + g.engine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9), startY) + g.engine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), + startY+(levelDetails.SizeYNormal+6)) } // West fence for i := 1; i < 6; i++ { - mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(levelDetails.SizeYNormal+6)-(i*9)) + g.engine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, + startY+(levelDetails.SizeYNormal+6)-(i*9)) } // East Fence for i := 1; i < 10; i++ { - mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX+levelDetails.SizeXNormal, startY+(i*9)) + g.engine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX+levelDetails.SizeXNormal, startY+(i*9)) } - mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal+6) - mapEngine.PlaceStamp(fenceWestEdge, startX, startY+(levelDetails.SizeYNormal-3)-45) - mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal, startY) - mapEngine.PlaceStamp(fenceSouthEastStamp, startX+levelDetails.SizeXNormal, startY+levelDetails.SizeYNormal+6) + g.engine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal+6) + g.engine.PlaceStamp(fenceWestEdge, startX, startY+(levelDetails.SizeYNormal-3)-45) + g.engine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal, startY) + g.engine.PlaceStamp(fenceSouthEastStamp, startX+levelDetails.SizeXNormal, startY+levelDetails.SizeYNormal+6) } -func generateWilderness1TownSouth(mapEngine *d2mapengine.MapEngine, startX, startY int) { - levelDetails := d2datadict.GetLevelDetails(2) +func (g *MapGenerator) generateWilderness1TownSouth(startX, startY int) { + levelDetails := g.asset.Records.GetLevelDetails(2) fenceNorthStamp := []*d2mapstamp.Stamp{ - loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0), - loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 1), - loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 2), + g.loadPreset(d2wilderness.TreeBorderNorth, 0), + g.loadPreset(d2wilderness.TreeBorderNorth, 1), + g.loadPreset(d2wilderness.TreeBorderNorth, 2), } fenceWestStamp := []*d2mapstamp.Stamp{ - loadPreset(mapEngine, d2wilderness.TreeBorderWest, 0), - loadPreset(mapEngine, d2wilderness.TreeBorderWest, 1), - loadPreset(mapEngine, d2wilderness.TreeBorderWest, 2), + g.loadPreset(d2wilderness.TreeBorderWest, 0), + g.loadPreset(d2wilderness.TreeBorderWest, 1), + g.loadPreset(d2wilderness.TreeBorderWest, 2), } fenceSouthStamp := []*d2mapstamp.Stamp{ - loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 0), - loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 1), - loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 2), + g.loadPreset(d2wilderness.TreeBorderSouth, 0), + g.loadPreset(d2wilderness.TreeBorderSouth, 1), + g.loadPreset(d2wilderness.TreeBorderSouth, 2), } - fenceNorthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthWest, 0) - fenceSouthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthWest, 0) - fenceWaterBorderSouthEast := loadPreset(mapEngine, d2wilderness.WaterBorderEast, 1) + fenceNorthWestStamp := g.loadPreset(d2wilderness.TreeBorderNorthWest, 0) + fenceSouthWestStamp := g.loadPreset(d2wilderness.TreeBorderSouthWest, 0) + fenceWaterBorderSouthEast := g.loadPreset(d2wilderness.WaterBorderEast, 1) areaRect := d2geom.Rectangle{ Left: startX + 2, @@ -153,84 +148,84 @@ func generateWilderness1TownSouth(mapEngine *d2mapengine.MapEngine, startX, star Width: levelDetails.SizeXNormal - 2, Height: levelDetails.SizeYNormal - 3, } - generateWilderness1Contents(mapEngine, areaRect) + g.generateWilderness1Contents(areaRect) // Draw the north fence for i := 0; i < 4; i++ { - mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9)+5, startY-6) + g.engine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9)+5, startY-6) } // Draw the west fence for i := 0; i < 8; i++ { - mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(i*9)+3) + g.engine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(i*9)+3) } // Draw the south fence for i := 1; i < 9; i++ { - mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), startY+(8*9)+3) + g.engine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), startY+(8*9)+3) } - mapEngine.PlaceStamp(fenceNorthWestStamp, startX, startY-6) - mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+(8*9)+3) - mapEngine.PlaceStamp(fenceWaterBorderSouthEast, startX+(9*9)-4, startY+(8*9)+1) + g.engine.PlaceStamp(fenceNorthWestStamp, startX, startY-6) + g.engine.PlaceStamp(fenceSouthWestStamp, startX, startY+(8*9)+3) + g.engine.PlaceStamp(fenceWaterBorderSouthEast, startX+(9*9)-4, startY+(8*9)+1) } -func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, startY int) { - levelDetails := d2datadict.GetLevelDetails(2) +func (g *MapGenerator) generateWilderness1TownWest(startX, startY int) { + levelDetails := g.asset.Records.GetLevelDetails(2) - fenceEastEdge := loadPreset(mapEngine, d2wilderness.TreeBoxSouthWest, 0) - fenceNorthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthWest, 0) - fenceNorthEastStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthEast, 0) - fenceSouthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthWest, 0) + fenceEastEdge := g.loadPreset(d2wilderness.TreeBoxSouthWest, 0) + fenceNorthWestStamp := g.loadPreset(d2wilderness.TreeBorderNorthWest, 0) + fenceNorthEastStamp := g.loadPreset(d2wilderness.TreeBorderNorthEast, 0) + fenceSouthWestStamp := g.loadPreset(d2wilderness.TreeBorderSouthWest, 0) fenceSouthStamp := []*d2mapstamp.Stamp{ - loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 0), - loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 1), - loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 2), + g.loadPreset(d2wilderness.TreeBorderSouth, 0), + g.loadPreset(d2wilderness.TreeBorderSouth, 1), + g.loadPreset(d2wilderness.TreeBorderSouth, 2), } fenceNorthStamp := []*d2mapstamp.Stamp{ - loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0), - loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 1), - loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 2), + g.loadPreset(d2wilderness.TreeBorderNorth, 0), + g.loadPreset(d2wilderness.TreeBorderNorth, 1), + g.loadPreset(d2wilderness.TreeBorderNorth, 2), } fenceEastStamp := []*d2mapstamp.Stamp{ - loadPreset(mapEngine, d2wilderness.TreeBorderEast, 0), - loadPreset(mapEngine, d2wilderness.TreeBorderEast, 1), - loadPreset(mapEngine, d2wilderness.TreeBorderEast, 2), + g.loadPreset(d2wilderness.TreeBorderEast, 0), + g.loadPreset(d2wilderness.TreeBorderEast, 1), + g.loadPreset(d2wilderness.TreeBorderEast, 2), } fenceWestStamp := []*d2mapstamp.Stamp{ - loadPreset(mapEngine, d2wilderness.TreeBorderWest, 0), - loadPreset(mapEngine, d2wilderness.TreeBorderWest, 1), - loadPreset(mapEngine, d2wilderness.TreeBorderWest, 2), + g.loadPreset(d2wilderness.TreeBorderWest, 0), + g.loadPreset(d2wilderness.TreeBorderWest, 1), + g.loadPreset(d2wilderness.TreeBorderWest, 2), } // Draw the north and south fences for i := 0; i < 9; i++ { if i > 0 && i < 8 { - mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9)-1, startY-15) + g.engine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9)-1, startY-15) } - mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9)-1, startY+levelDetails.SizeYNormal-12) + g.engine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9)-1, startY+levelDetails.SizeYNormal-12) } // Draw the east fence for i := 0; i < 6; i++ { - mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX+levelDetails.SizeXNormal-9, startY+(i*9)-6) + g.engine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX+levelDetails.SizeXNormal-9, startY+(i*9)-6) } // Draw the west fence for i := 0; i < 9; i++ { - mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(i*9)-6) + g.engine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(i*9)-6) } // Draw the west fence - mapEngine.PlaceStamp(fenceEastEdge, startX+levelDetails.SizeXNormal-9, startY+39) - mapEngine.PlaceStamp(fenceNorthWestStamp, startX, startY-15) - mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal-12) - mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal-9, startY-15) + g.engine.PlaceStamp(fenceEastEdge, startX+levelDetails.SizeXNormal-9, startY+39) + g.engine.PlaceStamp(fenceNorthWestStamp, startX, startY-15) + g.engine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal-12) + g.engine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal-9, startY-15) areaRect := d2geom.Rectangle{ Left: startX + 9, @@ -238,13 +233,13 @@ func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, start Width: levelDetails.SizeXNormal - 9, Height: levelDetails.SizeYNormal - 2, } - generateWilderness1Contents(mapEngine, areaRect) + g.generateWilderness1Contents(areaRect) } -func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2geom.Rectangle) { - levelDetails := d2datadict.GetLevelDetails(2) +func (g *MapGenerator) generateWilderness1Contents(rect d2geom.Rectangle) { + levelDetails := g.asset.Records.GetLevelDetails(2) - denOfEvil := loadPreset(mapEngine, d2wilderness.DenOfEvilEntrance, 0) + denOfEvil := g.loadPreset(d2wilderness.DenOfEvilEntrance, 0) denOfEvilLoc := d2geom.Point{ X: rect.Left + (rect.Width / 2) + rand.Intn(10), Y: rect.Top + (rect.Height / 2) + rand.Intn(10), @@ -253,36 +248,36 @@ func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2geom.R // Fill in the grass for y := 0; y < rect.Height; y++ { for x := 0; x < rect.Width; x++ { - tile := mapEngine.Tile(rect.Left+x, rect.Top+y) + tile := g.engine.Tile(rect.Left+x, rect.Top+y) tile.RegionType = d2enum.RegionIdType(levelDetails.LevelType) tile.Components.Floors = []d2ds1.FloorShadowRecord{{Prop1: 1, Style: 0, Sequence: 0}} // wildernessGrass - tile.PrepareTile(x, y, mapEngine) + tile.PrepareTile(x, y, g.engine) } } stuff := []*d2mapstamp.Stamp{ - loadPreset(mapEngine, d2wilderness.StoneFill1, 0), - loadPreset(mapEngine, d2wilderness.StoneFill1, 1), - loadPreset(mapEngine, d2wilderness.StoneFill1, 2), - loadPreset(mapEngine, d2wilderness.StoneFill2, 0), - loadPreset(mapEngine, d2wilderness.StoneFill2, 1), - loadPreset(mapEngine, d2wilderness.StoneFill2, 2), - loadPreset(mapEngine, d2wilderness.Cottages1, 0), - loadPreset(mapEngine, d2wilderness.Cottages1, 1), - loadPreset(mapEngine, d2wilderness.Cottages1, 2), - loadPreset(mapEngine, d2wilderness.Cottages1, 3), - loadPreset(mapEngine, d2wilderness.Cottages1, 4), - loadPreset(mapEngine, d2wilderness.Cottages1, 5), - loadPreset(mapEngine, d2wilderness.FallenCamp1, 0), - loadPreset(mapEngine, d2wilderness.FallenCamp1, 1), - loadPreset(mapEngine, d2wilderness.FallenCamp1, 2), - loadPreset(mapEngine, d2wilderness.FallenCamp1, 3), - loadPreset(mapEngine, d2wilderness.Pond, 0), - loadPreset(mapEngine, d2wilderness.SwampFill1, 0), - loadPreset(mapEngine, d2wilderness.SwampFill2, 0), + g.loadPreset(d2wilderness.StoneFill1, 0), + g.loadPreset(d2wilderness.StoneFill1, 1), + g.loadPreset(d2wilderness.StoneFill1, 2), + g.loadPreset(d2wilderness.StoneFill2, 0), + g.loadPreset(d2wilderness.StoneFill2, 1), + g.loadPreset(d2wilderness.StoneFill2, 2), + g.loadPreset(d2wilderness.Cottages1, 0), + g.loadPreset(d2wilderness.Cottages1, 1), + g.loadPreset(d2wilderness.Cottages1, 2), + g.loadPreset(d2wilderness.Cottages1, 3), + g.loadPreset(d2wilderness.Cottages1, 4), + g.loadPreset(d2wilderness.Cottages1, 5), + g.loadPreset(d2wilderness.FallenCamp1, 0), + g.loadPreset(d2wilderness.FallenCamp1, 1), + g.loadPreset(d2wilderness.FallenCamp1, 2), + g.loadPreset(d2wilderness.FallenCamp1, 3), + g.loadPreset(d2wilderness.Pond, 0), + g.loadPreset(d2wilderness.SwampFill1, 0), + g.loadPreset(d2wilderness.SwampFill2, 0), } - mapEngine.PlaceStamp(denOfEvil, denOfEvilLoc.X, denOfEvilLoc.Y) + g.engine.PlaceStamp(denOfEvil, denOfEvilLoc.X, denOfEvilLoc.Y) numPlaced := 0 for numPlaced < 25 { @@ -295,34 +290,9 @@ func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2geom.R Height: stamp.Size().Height, } - if areaEmpty(mapEngine, stampRect) { - mapEngine.PlaceStamp(stamp, stampRect.Left, stampRect.Top) + if areaEmpty(g.engine, stampRect) { + g.engine.PlaceStamp(stamp, stampRect.Left, stampRect.Top) numPlaced++ } } } - -func areaEmpty(mapEngine *d2mapengine.MapEngine, rect d2geom.Rectangle) bool { - mapHeight := mapEngine.Size().Height - mapWidth := mapEngine.Size().Width - - if rect.Top < 0 || rect.Left < 0 || rect.Bottom() >= mapHeight || rect.Right() >= mapWidth { - return false - } - - for y := rect.Top; y <= rect.Bottom(); y++ { - for x := rect.Left; x <= rect.Right(); x++ { - if len(mapEngine.Tile(x, y).Components.Floors) == 0 { - continue - } - - floor := mapEngine.Tile(x, y).Components.Floors[0] - - if floor.Style != 0 || floor.Sequence != 0 || floor.Prop1 != 1 { - return false - } - } - } - - return true -} diff --git a/d2core/d2map/d2mapgen/map_generator.go b/d2core/d2map/d2mapgen/map_generator.go new file mode 100644 index 00000000..068fb657 --- /dev/null +++ b/d2core/d2map/d2mapgen/map_generator.go @@ -0,0 +1,59 @@ +package d2mapgen + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapstamp" +) + +// NewMapGenerator creates a map generator instance +func NewMapGenerator(a *d2asset.AssetManager, e *d2mapengine.MapEngine) (*MapGenerator, error) { + generator := &MapGenerator{ + asset: a, + engine: e, + } + + return generator, nil +} + +// MapGenerator generates maps for the map engine +type MapGenerator struct { + asset *d2asset.AssetManager + engine *d2mapengine.MapEngine +} + +func (g *MapGenerator) loadPreset(id, index int) *d2mapstamp.Stamp { + for _, file := range g.asset.Records.LevelPreset(id).Files { + g.engine.AddDS1(file) + } + + return g.engine.LoadStamp(d2enum.RegionAct1Wilderness, id, index) +} + +func areaEmpty(mapEngine *d2mapengine.MapEngine, rect d2geom.Rectangle) bool { + mapHeight := mapEngine.Size().Height + mapWidth := mapEngine.Size().Width + + if rect.Top < 0 || rect.Left < 0 || rect.Bottom() >= mapHeight || rect.Right() >= mapWidth { + return false + } + + for y := rect.Top; y <= rect.Bottom(); y++ { + for x := rect.Left; x <= rect.Right(); x++ { + if len(mapEngine.Tile(x, y).Components.Floors) == 0 { + continue + } + + floor := mapEngine.Tile(x, y).Components.Floors[0] + + if floor.Style != 0 || floor.Sequence != 0 || floor.Prop1 != 1 { + return false + } + } + } + + return true +} diff --git a/d2core/d2map/d2mapstamp/factory.go b/d2core/d2map/d2mapstamp/factory.go index 2a8ae56f..96e6e132 100644 --- a/d2core/d2map/d2mapstamp/factory.go +++ b/d2core/d2map/d2mapstamp/factory.go @@ -6,7 +6,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1" @@ -25,10 +24,11 @@ type StampFactory struct { // LoadStamp loads the Stamp data from file. func (f *StampFactory) LoadStamp(levelType d2enum.RegionIdType, levelPreset, fileIndex int) *Stamp { stamp := &Stamp{ + factory: f, entity: f.entity, regionID: levelType, - levelType: d2datadict.LevelTypes[levelType], - levelPreset: d2datadict.LevelPresets[levelPreset], + levelType: *f.asset.Records.Level.Types[levelType], + levelPreset: f.asset.Records.Level.Presets[levelPreset], } for _, levelTypeDt1 := range &stamp.levelType.Files { diff --git a/d2core/d2map/d2mapstamp/stamp.go b/d2core/d2map/d2mapstamp/stamp.go index 80ca6da5..3f1d0f46 100644 --- a/d2core/d2map/d2mapstamp/stamp.go +++ b/d2core/d2map/d2mapstamp/stamp.go @@ -11,6 +11,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2path" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" ) const ( @@ -19,13 +20,14 @@ const ( // Stamp represents a pre-fabricated map stamp that can be placed on a map. type Stamp struct { + factory *StampFactory entity *d2mapentity.MapEntityFactory regionPath string // The file path of the region regionID d2enum.RegionIdType - levelType d2datadict.LevelTypeRecord // The level type id for this stamp - levelPreset d2datadict.LevelPresetRecord // The level preset id for this stamp - tiles []d2dt1.Tile // The tiles contained on this stamp - ds1 *d2ds1.DS1 // The backing DS1 file for this stamp + levelType d2records.LevelTypeRecord // The level type id for this stamp + levelPreset d2records.LevelPresetRecord // The level preset id for this stamp + tiles []d2dt1.Tile // The tiles contained on this stamp + ds1 *d2ds1.DS1 // The backing DS1 file for this stamp } // Size returns the size of the stamp in tiles. @@ -34,12 +36,12 @@ func (mr *Stamp) Size() d2geom.Size { } // LevelPreset returns the level preset ID. -func (mr *Stamp) LevelPreset() d2datadict.LevelPresetRecord { +func (mr *Stamp) LevelPreset() d2records.LevelPresetRecord { return mr.levelPreset } // LevelType returns the level type ID. -func (mr *Stamp) LevelType() d2datadict.LevelTypeRecord { +func (mr *Stamp) LevelType() d2records.LevelTypeRecord { return mr.levelType } @@ -76,7 +78,8 @@ func (mr *Stamp) Entities(tileOffsetX, tileOffsetY int) []d2interface.MapEntity for _, object := range mr.ds1.Objects { if object.Type == int(d2enum.ObjectTypeCharacter) { - monstat := d2datadict.MonStats[d2datadict.MonPresets[mr.ds1.Act][object.ID]] + monPreset := mr.factory.asset.Records.Monster.Presets[mr.ds1.Act][object.ID] + monstat := mr.factory.asset.Records.Monster.Stats[monPreset] // If monstat is nil here it is a place_ type object, idk how to handle those yet. // (See monpreset and monplace txts for reference) if monstat != nil { diff --git a/d2core/d2records/missiles_loader.go b/d2core/d2records/missiles_loader.go index 935bb440..ac898af0 100644 --- a/d2core/d2records/missiles_loader.go +++ b/d2core/d2records/missiles_loader.go @@ -2,6 +2,7 @@ package d2records import ( "log" + "strings" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" @@ -10,6 +11,7 @@ import ( func missilesLoader(r *RecordManager, d *d2txt.DataDictionary) error { records := make(Missiles) + r.missilesByName = make(missilesByName) for d.Next() { record := &MissileRecord{ @@ -295,6 +297,7 @@ func missilesLoader(r *RecordManager, d *d2txt.DataDictionary) error { } records[record.Id] = record + r.missilesByName[sanitizeMissilesKey(record.Name)] = record } if d.Err != nil { @@ -307,3 +310,7 @@ func missilesLoader(r *RecordManager, d *d2txt.DataDictionary) error { return nil } + +func sanitizeMissilesKey(missileName string) string { + return strings.ToLower(strings.ReplaceAll(missileName, " ", "")) +} diff --git a/d2core/d2records/missiles_record.go b/d2core/d2records/missiles_record.go index 6dc902d7..7f8e8762 100644 --- a/d2core/d2records/missiles_record.go +++ b/d2core/d2records/missiles_record.go @@ -5,6 +5,8 @@ import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation" // Missiles stores all of the MissileRecords type Missiles map[int]*MissileRecord +type missilesByName map[string]*MissileRecord + // MissileCalcParam is a calculation parameter for a missile type MissileCalcParam struct { Param int diff --git a/d2core/d2records/record_manager.go b/d2core/d2records/record_manager.go index c32ca0de..2e7649a6 100644 --- a/d2core/d2records/record_manager.go +++ b/d2core/d2records/record_manager.go @@ -91,6 +91,7 @@ type RecordManager struct { Warp LevelWarps } Missiles + missilesByName Monster struct { AI MonsterAI Equipment MonsterEquipment @@ -274,6 +275,17 @@ func (r *RecordManager) GetExperienceBreakpoint(heroType d2enum.Hero, level int) return r.Character.Experience[level].HeroBreakpoints[heroType] } +// GetLevelDetails gets a LevelDetailsRecord by the record Id +func (r *RecordManager) GetLevelDetails(id int) *LevelDetailsRecord { + for i := 0; i < len(r.Level.Details); i++ { + if r.Level.Details[i].ID == id { + return r.Level.Details[i] + } + } + + return nil +} + // LevelPreset looks up a LevelPresetRecord by ID func (r *RecordManager) LevelPreset(id int) LevelPresetRecord { for i := 0; i < len(r.Level.Presets); i++ { @@ -371,3 +383,19 @@ func (r *RecordManager) SelectSoundByIndex(index int) *SoundDetailsRecord { return nil } + +// GetSkillByName returns the skill record for the given Skill name. +func (r *RecordManager) GetSkillByName(skillName string) *SkillRecord { + for idx := range r.Skill.Details { + if r.Skill.Details[idx].Skill == skillName { + return r.Skill.Details[idx] + } + } + + return nil +} + +// GetMissileByName allows lookup of a MissileRecord by a given name. The name will be lowercased and stripped of whitespaces. +func (r *RecordManager) GetMissileByName(missileName string) *MissileRecord { + return r.missilesByName[sanitizeMissilesKey(missileName)] +} diff --git a/d2core/d2records/skill_description_loader.go b/d2core/d2records/skill_description_loader.go index 161f291f..fe223175 100644 --- a/d2core/d2records/skill_description_loader.go +++ b/d2core/d2records/skill_description_loader.go @@ -23,7 +23,7 @@ func skillDescriptionLoader(r *RecordManager, d *d2txt.DataDictionary) error { d.String("SkillColumn"), d.String("ListRow"), d.String("ListPool"), - d.String("IconCel"), + d.Number("IconCel"), d.String("str name"), d.String("str short"), d.String("str long"), diff --git a/d2core/d2records/skill_description_record.go b/d2core/d2records/skill_description_record.go index 62819cd0..011446fc 100644 --- a/d2core/d2records/skill_description_record.go +++ b/d2core/d2records/skill_description_record.go @@ -14,7 +14,7 @@ type SkillDescriptionRecord struct { SkillColumn string // SkillColumn ListRow string // ListRow ListPool string // ListPool - IconCel string // IconCel + IconCel int // IconCel NameKey string // str name ShortKey string // str short LongKey string // str long diff --git a/d2core/d2records/unique_items_loader.go b/d2core/d2records/unique_items_loader.go index 6fa7127f..507b9540 100644 --- a/d2core/d2records/unique_items_loader.go +++ b/d2core/d2records/unique_items_loader.go @@ -116,6 +116,10 @@ func uniqueItemsLoader(r *RecordManager, d *d2txt.DataDictionary) error { }, } + if record.Name == "" { + continue + } + records[record.Name] = record } diff --git a/d2core/d2stats/diablo2stats/diablo2stats.go b/d2core/d2stats/diablo2stats/diablo2stats.go deleted file mode 100644 index f2b83ffe..00000000 --- a/d2core/d2stats/diablo2stats/diablo2stats.go +++ /dev/null @@ -1,47 +0,0 @@ -package diablo2stats - -import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats" -) - -// NewStat creates a stat instance with the given record and values -func NewStat(key string, values ...float64) d2stats.Stat { - record := d2datadict.ItemStatCosts[key] - - if record == nil { - return nil - } - - stat := &diablo2Stat{ - record: record, - } - - stat.init(values...) // init stat values, value types, and value combination rules - - return stat -} - -// NewStatList creates a stat list -func NewStatList(stats ...d2stats.Stat) d2stats.StatList { - return &Diablo2StatList{stats} -} - -// NewValue creates a stat value of the given type -func NewValue(t d2stats.StatNumberType, c d2stats.ValueCombineType) d2stats.StatValue { - sv := &Diablo2StatValue{ - numberType: t, - combineType: c, - } - - switch t { - case d2stats.StatValueFloat: - sv.stringerFn = stringerUnsignedFloat - case d2stats.StatValueInt: - sv.stringerFn = stringerUnsignedInt - default: - sv.stringerFn = stringerEmpty - } - - return sv -} diff --git a/d2core/d2stats/diablo2stats/stat.go b/d2core/d2stats/diablo2stats/stat.go index 2f520ca7..53ce0691 100644 --- a/d2core/d2stats/diablo2stats/stat.go +++ b/d2core/d2stats/diablo2stats/stat.go @@ -4,7 +4,8 @@ import ( "fmt" "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats" ) @@ -43,8 +44,9 @@ const ( // diablo2Stat is an implementation of an OpenDiablo2 Stat, with a set of values. // It is pretty tightly coupled to the data files for d2 type diablo2Stat struct { - record *d2datadict.ItemStatCostRecord - values []d2stats.StatValue + factory *StatFactory + record *d2records.ItemStatCostRecord + values []d2stats.StatValue } // depending on the stat record, sets up the proper number of values, @@ -62,113 +64,120 @@ func (s *diablo2Stat) init(numbers ...float64) { //nolint:funlen doesn't make se // 0-value descfnID field but need to store values s.values = make([]d2stats.StatValue, len(numbers)) for idx := range s.values { - s.values[idx] = NewValue(intVal, sum).SetStringer(stringerIntSigned) + s.values[idx] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerIntSigned) } case 1: // +31 to Strength // Replenish Life +20 || Drain Life -8 s.values = make([]d2stats.StatValue, oneValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned) + s.values[0] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerIntSigned) case 2: // +16% Increased Chance of Blocking // Lightning Absorb +10% s.values = make([]d2stats.StatValue, oneValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageSigned) + s.values[0] = s.factory.NewValue(intVal, + sum).SetStringer(s.factory.stringerIntPercentageSigned) case 3: // Damage Reduced by 25 // Slain Monsters Rest in Peace s.values = make([]d2stats.StatValue, oneValue) - s.values[0] = NewValue(intVal, sum) + s.values[0] = s.factory.NewValue(intVal, sum) case 4: // Poison Resist +25% // +25% Faster Run/Walk s.values = make([]d2stats.StatValue, oneValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageSigned) + s.values[0] = s.factory.NewValue(intVal, + sum).SetStringer(s.factory.stringerIntPercentageSigned) case 5: // Hit Causes Monster to Flee 25% s.values = make([]d2stats.StatValue, oneValue) - s.values[0] = NewValue(intVal, sum) - s.values[0].SetStringer(stringerIntPercentageUnsigned) + s.values[0] = s.factory.NewValue(intVal, sum) + s.values[0].SetStringer(s.factory.stringerIntPercentageUnsigned) case 6: // +25 to Life (Based on Character Level) s.values = make([]d2stats.StatValue, oneValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned) + s.values[0] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerIntSigned) case 7: // Lightning Resist +25% (Based on Character Level) // +25% Better Chance of Getting Magic Items (Based on Character Level) s.values = make([]d2stats.StatValue, oneValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageSigned) + s.values[0] = s.factory.NewValue(intVal, + sum).SetStringer(s.factory.stringerIntPercentageSigned) case 8: // +25% Enhanced Defense (Based on Character Level) // Heal Stamina Plus +25% (Based on Character Level) s.values = make([]d2stats.StatValue, oneValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageSigned) + s.values[0] = s.factory.NewValue(intVal, + sum).SetStringer(s.factory.stringerIntPercentageSigned) case 9: // Attacker Takes Damage of 25 (Based on Character Level) s.values = make([]d2stats.StatValue, oneValue) - s.values[0] = NewValue(intVal, sum) + s.values[0] = s.factory.NewValue(intVal, sum) case 11: // Repairs 2 durability per second s.values = make([]d2stats.StatValue, oneValue) - s.values[0] = NewValue(intVal, sum) + s.values[0] = s.factory.NewValue(intVal, sum) case 12: // Hit Blinds Target +5 s.values = make([]d2stats.StatValue, oneValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned) + s.values[0] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerIntSigned) case 13: // +5 to Paladin Skill Levels s.values = make([]d2stats.StatValue, twoValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned) - s.values[1] = NewValue(intVal, sum).SetStringer(stringerClassAllSkills) + s.values[0] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerIntSigned) + s.values[1] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerClassAllSkills) case 14: // +5 to Combat Skills (Paladin Only) s.values = make([]d2stats.StatValue, threeValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned) - s.values[1] = NewValue(intVal, sum).SetStringer(stringerClassOnly) - s.values[2] = NewValue(intVal, static) + s.values[0] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerIntSigned) + s.values[1] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerClassOnly) + s.values[2] = s.factory.NewValue(intVal, static) case 15: // 5% Chance to cast level 7 Frozen Orb on attack s.values = make([]d2stats.StatValue, threeValue) - s.values[0] = NewValue(intVal, sum) - s.values[1] = NewValue(intVal, static) - s.values[2] = NewValue(intVal, static).SetStringer(stringerSkillName) + s.values[0] = s.factory.NewValue(intVal, sum) + s.values[1] = s.factory.NewValue(intVal, static) + s.values[2] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerSkillName) case 16: // Level 3 Warmth Aura When Equipped s.values = make([]d2stats.StatValue, twoValue) - s.values[0] = NewValue(intVal, sum) - s.values[1] = NewValue(intVal, static).SetStringer(stringerSkillName) + s.values[0] = s.factory.NewValue(intVal, sum) + s.values[1] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerSkillName) case 20: // -25% Target Defense s.values = make([]d2stats.StatValue, oneValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageSigned) + s.values[0] = s.factory.NewValue(intVal, + sum).SetStringer(s.factory.stringerIntPercentageSigned) case 22: // 25% to Attack Rating versus Specter s.values = make([]d2stats.StatValue, twoValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageUnsigned) - s.values[1] = NewValue(intVal, static).SetStringer(stringerMonsterName) + s.values[0] = s.factory.NewValue(intVal, + sum).SetStringer(s.factory.stringerIntPercentageUnsigned) + s.values[1] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerMonsterName) case 23: // 25% Reanimate as: Specter s.values = make([]d2stats.StatValue, twoValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageUnsigned) - s.values[1] = NewValue(intVal, static).SetStringer(stringerMonsterName) + s.values[0] = s.factory.NewValue(intVal, + sum).SetStringer(s.factory.stringerIntPercentageUnsigned) + s.values[1] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerMonsterName) case 24: // Level 25 Frozen Orb (19/20 Charges) s.values = make([]d2stats.StatValue, fourValue) - s.values[0] = NewValue(intVal, static) - s.values[1] = NewValue(intVal, static).SetStringer(stringerSkillName) - s.values[2] = NewValue(intVal, static) - s.values[3] = NewValue(intVal, static) + s.values[0] = s.factory.NewValue(intVal, static) + s.values[1] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerSkillName) + s.values[2] = s.factory.NewValue(intVal, static) + s.values[3] = s.factory.NewValue(intVal, static) case 27: // +25 to Frozen Orb (Paladin Only) s.values = make([]d2stats.StatValue, threeValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned) - s.values[1] = NewValue(intVal, static).SetStringer(stringerSkillName) - s.values[2] = NewValue(intVal, static).SetStringer(stringerClassOnly) + s.values[0] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerIntSigned) + s.values[1] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerSkillName) + s.values[2] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerClassOnly) case 28: // +25 to Frozen Orb s.values = make([]d2stats.StatValue, twoValue) - s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned) - s.values[1] = NewValue(intVal, static).SetStringer(stringerSkillName) + s.values[0] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerIntSigned) + s.values[1] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerSkillName) default: return } @@ -341,7 +350,7 @@ func (s *diablo2Stat) String() string { //nolint:gocyclo switch statement is not for idx := range s.values { if s.values[idx].Stringer() == nil { - s.values[idx].SetStringer(stringerUnsignedInt) + s.values[idx].SetStringer(s.factory.stringerUnsignedInt) } } @@ -507,9 +516,9 @@ func (s *diablo2Stat) descFn13() string { func (s *diablo2Stat) descFn14() string { // strings come out like `+5 to Combat Skills (Paladin Only)` numSkills, hero, skillTab := s.values[0], s.values[1], s.values[2] - heroMap := getHeroMap() + heroMap := s.factory.getHeroMap() heroIndex := hero.Int() - classRecord := d2datadict.CharStats[heroMap[heroIndex]] + classRecord := s.factory.asset.Records.Character.Stats[heroMap[heroIndex]] // diablo 2 is hardcoded to have only 3 skill tabs skillTabIndex := skillTab.Int() diff --git a/d2core/d2stats/diablo2stats/stat_factory.go b/d2core/d2stats/diablo2stats/stat_factory.go new file mode 100644 index 00000000..4124d91a --- /dev/null +++ b/d2core/d2stats/diablo2stats/stat_factory.go @@ -0,0 +1,136 @@ +package diablo2stats + +import ( + "fmt" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats" +) + +func NewStatFactory(asset *d2asset.AssetManager) (*StatFactory, error) { + factory := &StatFactory{asset: asset} + + return factory, nil +} + +// StatFactory is responsible for creating stats +type StatFactory struct { + asset *d2asset.AssetManager +} + +// NewStat creates a stat instance with the given record and values +func (f *StatFactory) NewStat(key string, values ...float64) d2stats.Stat { + record := f.asset.Records.Item.Stats[key] + + if record == nil { + return nil + } + + stat := &diablo2Stat{ + factory: f, + record: record, + } + + stat.init(values...) // init stat values, value types, and value combination rules + + return stat +} + +// NewStatList creates a stat list +func (f *StatFactory) NewStatList(stats ...d2stats.Stat) d2stats.StatList { + return &Diablo2StatList{stats} +} + +// NewValue creates a stat value of the given type +func (f *StatFactory) NewValue(t d2stats.StatNumberType, c d2stats.ValueCombineType) d2stats.StatValue { + sv := &Diablo2StatValue{ + numberType: t, + combineType: c, + } + + switch t { + case d2stats.StatValueFloat: + sv.stringerFn = f.stringerUnsignedFloat + case d2stats.StatValueInt: + sv.stringerFn = f.stringerUnsignedInt + default: + sv.stringerFn = f.stringerEmpty + } + + return sv +} + +const ( + monsterNotFound = "{Monster not found!}" +) + +func (f *StatFactory) getHeroMap() []d2enum.Hero { + return []d2enum.Hero{ + d2enum.HeroAmazon, + d2enum.HeroSorceress, + d2enum.HeroNecromancer, + d2enum.HeroPaladin, + d2enum.HeroBarbarian, + d2enum.HeroDruid, + d2enum.HeroAssassin, + } +} + +func (f *StatFactory) stringerUnsignedInt(sv d2stats.StatValue) string { + return fmt.Sprintf("%d", sv.Int()) +} + +func (f *StatFactory) stringerUnsignedFloat(sv d2stats.StatValue) string { + return fmt.Sprintf("%.2f", sv.Float()) +} + +func (f *StatFactory) stringerEmpty(_ d2stats.StatValue) string { + return "" +} + +func (f *StatFactory) stringerIntSigned(sv d2stats.StatValue) string { + return fmt.Sprintf("%+d", sv.Int()) +} + +func (f *StatFactory) stringerIntPercentageSigned(sv d2stats.StatValue) string { + return fmt.Sprintf("%+d%%", sv.Int()) +} + +func (f *StatFactory) stringerIntPercentageUnsigned(sv d2stats.StatValue) string { + return fmt.Sprintf("%d%%", sv.Int()) +} + +func (f *StatFactory) stringerClassAllSkills(sv d2stats.StatValue) string { + heroIndex := sv.Int() + + heroMap := f.getHeroMap() + classRecord := f.asset.Records.Character.Stats[heroMap[heroIndex]] + + return d2tbl.TranslateString(classRecord.SkillStrAll) +} + +func (f *StatFactory) stringerClassOnly(sv d2stats.StatValue) string { + heroMap := f.getHeroMap() + heroIndex := sv.Int() + classRecord := f.asset.Records.Character.Stats[heroMap[heroIndex]] + classOnlyKey := classRecord.SkillStrClassOnly + + return d2tbl.TranslateString(classOnlyKey) +} + +func (f *StatFactory) stringerSkillName(sv d2stats.StatValue) string { + skillRecord := f.asset.Records.Skill.Details[sv.Int()] + return skillRecord.Skill +} + +func (f *StatFactory) stringerMonsterName(sv d2stats.StatValue) string { + for key := range f.asset.Records.Monster.Stats { + if f.asset.Records.Monster.Stats[key].ID == sv.Int() { + return f.asset.Records.Monster.Stats[key].NameString + } + } + + return monsterNotFound +} diff --git a/d2core/d2stats/diablo2stats/stat_test.go b/d2core/d2stats/diablo2stats/stat_test.go index 8d046736..504d4100 100644 --- a/d2core/d2stats/diablo2stats/stat_test.go +++ b/d2core/d2stats/diablo2stats/stat_test.go @@ -4,7 +4,10 @@ import ( "fmt" "testing" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" ) @@ -13,254 +16,261 @@ const ( errFmt string = "%v:\n\tDescFnID: %v\n\tKey: %v\n\tVal: %+v\n\texpected: %v\n\tgot: %v\n\n" ) -//nolint:funlen // this just gets mock data ready for the tests -func TestStat_InitMockData(t *testing.T) { - var itemStatCosts = map[string]*d2datadict.ItemStatCostRecord{ - "strength": { - Name: "strength", - DescFnID: 1, - DescVal: int(descValPrefix), - DescStrPos: "to Strength", - DescStrNeg: "to Strength", - }, - "dexterity": { - Name: "dexterity", - DescFnID: 1, - DescVal: int(descValPrefix), - DescStrPos: "to Dexterity", - DescStrNeg: "to Dexterity", - }, - "vitality": { - Name: "vitality", - DescFnID: 1, - DescVal: int(descValPrefix), - DescStrPos: "to Vitality", - DescStrNeg: "to Vitality", - }, - "energy": { - Name: "energy", - DescFnID: 1, - DescVal: int(descValPrefix), - DescStrPos: "to Energy", - DescStrNeg: "to Energy", - }, - "hpregen": { - Name: "hpregen", - DescFnID: 1, - DescVal: int(descValPostfix), - DescStrPos: "Replenish Life", - DescStrNeg: "Drain Life", - }, - "toblock": { - Name: "toblock", - DescFnID: 2, - DescVal: int(descValPrefix), - DescStrPos: "Increased Chance of Blocking", - DescStrNeg: "Increased Chance of Blocking", - }, - "item_absorblight_percent": { - Name: "item_absorblight_percent", - DescFnID: 2, - DescVal: int(descValPostfix), - DescStrPos: "Lightning Absorb", - DescStrNeg: "Lightning Absorb", - }, - "item_restinpeace": { - Name: "item_restinpeace", - DescFnID: 3, - DescVal: int(descValHide), - DescStrPos: "Slain Monsters Rest in Peace", - DescStrNeg: "Slain Monsters Rest in Peace", - }, - "normal_damage_reduction": { - Name: "normal_damage_reduction", - DescFnID: 3, - DescVal: int(descValPostfix), - DescStrPos: "Damage Reduced by", - DescStrNeg: "Damage Reduced by", - }, - "poisonresist": { - Name: "poisonresist", - DescFnID: 4, - DescVal: int(descValPostfix), - DescStrPos: "Poison Resist", - DescStrNeg: "Poison Resist", - }, - "item_fastermovevelocity": { - Name: "item_fastermovevelocity", - DescFnID: 4, - DescVal: int(descValPrefix), - DescStrPos: "Faster Run/Walk", - DescStrNeg: "Faster Run/Walk", - }, - "item_howl": { - Name: "item_howl", - DescFnID: 5, - DescVal: int(descValPostfix), - DescStrPos: "Hit Causes Monster to Flee", - DescStrNeg: "Hit Causes Monster to Flee", - }, - "item_hp_perlevel": { - Name: "item_hp_perlevel", - DescFnID: 6, - DescVal: int(descValPrefix), - DescStrPos: "to Life", - DescStrNeg: "to Life", - DescStr2: "(Based on Character Level)", - }, - "item_resist_ltng_perlevel": { - Name: "item_resist_ltng_perlevel", - DescFnID: 7, - DescVal: int(descValPostfix), - DescStrPos: "Lightning Resist", - DescStrNeg: "Lightning Resist", - DescStr2: "(Based on Character Level)", - }, - "item_find_magic_perlevel": { - Name: "item_find_magic_perlevel", - DescFnID: 7, - DescVal: int(descValPrefix), - DescStrPos: "Better Chance of Getting Magic Items", - DescStrNeg: "Better Chance of Getting Magic Items", - DescStr2: "(Based on Character Level)", - }, - "item_armorpercent_perlevel": { - Name: "item_armorpercent_perlevel", - DescFnID: 8, - DescVal: int(descValPrefix), - DescStrPos: "Enhanced Defense", - DescStrNeg: "Enhanced Defense", - DescStr2: "(Based on Character Level)", - }, - "item_regenstamina_perlevel": { - Name: "item_regenstamina_perlevel", - DescFnID: 8, - DescVal: int(descValPostfix), - DescStrPos: "Heal Stamina Plus", - DescStrNeg: "Heal Stamina Plus", - DescStr2: "(Based on Character Level)", - }, - "item_thorns_perlevel": { - Name: "item_thorns_perlevel", - DescFnID: 9, - DescVal: int(descValPostfix), - DescStrPos: "Attacker Takes Damage of", - DescStrNeg: "Attacker Takes Damage of", - DescStr2: "(Based on Character Level)", - }, - "item_replenish_durability": { - Name: "item_replenish_durability", - DescFnID: 11, - DescVal: int(descValPrefix), - DescStrPos: "Repairs %v durability per second", - DescStrNeg: "Repairs %v durability per second", - DescStr2: "", - }, - "item_stupidity": { - Name: "item_stupidity", - DescFnID: 12, - DescVal: int(descValPostfix), - DescStrPos: "Hit Blinds Target", - DescStrNeg: "Hit Blinds Target", - }, - "item_addclassskills": { - Name: "item_addclassskills", - DescFnID: 13, - DescVal: int(descValPrefix), - }, - "item_addskill_tab": { - Name: "item_addskill_tab", - DescFnID: 14, - DescVal: int(descValPrefix), - }, - "item_skillonattack": { - Name: "item_skillonattack", - DescFnID: 15, - DescVal: int(descValPrefix), - DescStrPos: "%d%% Chance to cast level %d %s on attack", - DescStrNeg: "%d%% Chance to cast level %d %s on attack", - }, - "item_aura": { - Name: "item_aura", - DescFnID: 16, - DescVal: int(descValPrefix), - DescStrPos: "Level %d %s Aura When Equipped", - DescStrNeg: "Level %d %s Aura When Equipped", - }, - "item_fractionaltargetac": { - Name: "item_fractionaltargetac", - DescFnID: 20, - DescVal: int(descValPrefix), - DescStrPos: "Target Defense", - DescStrNeg: "Target Defense", - }, - "attack_vs_montype": { - Name: "item_fractionaltargetac", - DescFnID: 22, - DescVal: int(descValPrefix), - DescStrPos: "to Attack Rating versus", - DescStrNeg: "to Attack Rating versus", - }, - "item_reanimate": { - Name: "item_reanimate", - DescFnID: 23, - DescVal: int(descValPostfix), - DescStrPos: "Reanimate as:", - DescStrNeg: "Reanimate as:", - }, - "item_charged_skill": { - Name: "item_charged_skill", - DescFnID: 24, - DescVal: int(descValPostfix), - DescStrPos: "(%d/%d Charges)", - DescStrNeg: "(%d/%d Charges)", - }, - "item_singleskill": { - Name: "item_singleskill", - DescFnID: 27, - DescVal: int(descValPostfix), - DescStrPos: "(%d/%d Charges)", - DescStrNeg: "(%d/%d Charges)", - }, - "item_nonclassskill": { - Name: "item_nonclassskill", - DescFnID: 28, - DescVal: int(descValPostfix), - DescStrPos: "(%d/%d Charges)", - DescStrNeg: "(%d/%d Charges)", - }, - } +var itemStatCosts = map[string]*d2records.ItemStatCostRecord{ + "strength": { + Name: "strength", + DescFnID: 1, + DescVal: int(descValPrefix), + DescStrPos: "to Strength", + DescStrNeg: "to Strength", + }, + "dexterity": { + Name: "dexterity", + DescFnID: 1, + DescVal: int(descValPrefix), + DescStrPos: "to Dexterity", + DescStrNeg: "to Dexterity", + }, + "vitality": { + Name: "vitality", + DescFnID: 1, + DescVal: int(descValPrefix), + DescStrPos: "to Vitality", + DescStrNeg: "to Vitality", + }, + "energy": { + Name: "energy", + DescFnID: 1, + DescVal: int(descValPrefix), + DescStrPos: "to Energy", + DescStrNeg: "to Energy", + }, + "hpregen": { + Name: "hpregen", + DescFnID: 1, + DescVal: int(descValPostfix), + DescStrPos: "Replenish Life", + DescStrNeg: "Drain Life", + }, + "toblock": { + Name: "toblock", + DescFnID: 2, + DescVal: int(descValPrefix), + DescStrPos: "Increased Chance of Blocking", + DescStrNeg: "Increased Chance of Blocking", + }, + "item_absorblight_percent": { + Name: "item_absorblight_percent", + DescFnID: 2, + DescVal: int(descValPostfix), + DescStrPos: "Lightning Absorb", + DescStrNeg: "Lightning Absorb", + }, + "item_restinpeace": { + Name: "item_restinpeace", + DescFnID: 3, + DescVal: int(descValHide), + DescStrPos: "Slain Monsters Rest in Peace", + DescStrNeg: "Slain Monsters Rest in Peace", + }, + "normal_damage_reduction": { + Name: "normal_damage_reduction", + DescFnID: 3, + DescVal: int(descValPostfix), + DescStrPos: "Damage Reduced by", + DescStrNeg: "Damage Reduced by", + }, + "poisonresist": { + Name: "poisonresist", + DescFnID: 4, + DescVal: int(descValPostfix), + DescStrPos: "Poison Resist", + DescStrNeg: "Poison Resist", + }, + "item_fastermovevelocity": { + Name: "item_fastermovevelocity", + DescFnID: 4, + DescVal: int(descValPrefix), + DescStrPos: "Faster Run/Walk", + DescStrNeg: "Faster Run/Walk", + }, + "item_howl": { + Name: "item_howl", + DescFnID: 5, + DescVal: int(descValPostfix), + DescStrPos: "Hit Causes Monster to Flee", + DescStrNeg: "Hit Causes Monster to Flee", + }, + "item_hp_perlevel": { + Name: "item_hp_perlevel", + DescFnID: 6, + DescVal: int(descValPrefix), + DescStrPos: "to Life", + DescStrNeg: "to Life", + DescStr2: "(Based on Character Level)", + }, + "item_resist_ltng_perlevel": { + Name: "item_resist_ltng_perlevel", + DescFnID: 7, + DescVal: int(descValPostfix), + DescStrPos: "Lightning Resist", + DescStrNeg: "Lightning Resist", + DescStr2: "(Based on Character Level)", + }, + "item_find_magic_perlevel": { + Name: "item_find_magic_perlevel", + DescFnID: 7, + DescVal: int(descValPrefix), + DescStrPos: "Better Chance of Getting Magic Items", + DescStrNeg: "Better Chance of Getting Magic Items", + DescStr2: "(Based on Character Level)", + }, + "item_armorpercent_perlevel": { + Name: "item_armorpercent_perlevel", + DescFnID: 8, + DescVal: int(descValPrefix), + DescStrPos: "Enhanced Defense", + DescStrNeg: "Enhanced Defense", + DescStr2: "(Based on Character Level)", + }, + "item_regenstamina_perlevel": { + Name: "item_regenstamina_perlevel", + DescFnID: 8, + DescVal: int(descValPostfix), + DescStrPos: "Heal Stamina Plus", + DescStrNeg: "Heal Stamina Plus", + DescStr2: "(Based on Character Level)", + }, + "item_thorns_perlevel": { + Name: "item_thorns_perlevel", + DescFnID: 9, + DescVal: int(descValPostfix), + DescStrPos: "Attacker Takes Damage of", + DescStrNeg: "Attacker Takes Damage of", + DescStr2: "(Based on Character Level)", + }, + "item_replenish_durability": { + Name: "item_replenish_durability", + DescFnID: 11, + DescVal: int(descValPrefix), + DescStrPos: "Repairs %v durability per second", + DescStrNeg: "Repairs %v durability per second", + DescStr2: "", + }, + "item_stupidity": { + Name: "item_stupidity", + DescFnID: 12, + DescVal: int(descValPostfix), + DescStrPos: "Hit Blinds Target", + DescStrNeg: "Hit Blinds Target", + }, + "item_addclassskills": { + Name: "item_addclassskills", + DescFnID: 13, + DescVal: int(descValPrefix), + }, + "item_addskill_tab": { + Name: "item_addskill_tab", + DescFnID: 14, + DescVal: int(descValPrefix), + }, + "item_skillonattack": { + Name: "item_skillonattack", + DescFnID: 15, + DescVal: int(descValPrefix), + DescStrPos: "%d%% Chance to cast level %d %s on attack", + DescStrNeg: "%d%% Chance to cast level %d %s on attack", + }, + "item_aura": { + Name: "item_aura", + DescFnID: 16, + DescVal: int(descValPrefix), + DescStrPos: "Level %d %s Aura When Equipped", + DescStrNeg: "Level %d %s Aura When Equipped", + }, + "item_fractionaltargetac": { + Name: "item_fractionaltargetac", + DescFnID: 20, + DescVal: int(descValPrefix), + DescStrPos: "Target Defense", + DescStrNeg: "Target Defense", + }, + "attack_vs_montype": { + Name: "item_fractionaltargetac", + DescFnID: 22, + DescVal: int(descValPrefix), + DescStrPos: "to Attack Rating versus", + DescStrNeg: "to Attack Rating versus", + }, + "item_reanimate": { + Name: "item_reanimate", + DescFnID: 23, + DescVal: int(descValPostfix), + DescStrPos: "Reanimate as:", + DescStrNeg: "Reanimate as:", + }, + "item_charged_skill": { + Name: "item_charged_skill", + DescFnID: 24, + DescVal: int(descValPostfix), + DescStrPos: "(%d/%d Charges)", + DescStrNeg: "(%d/%d Charges)", + }, + "item_singleskill": { + Name: "item_singleskill", + DescFnID: 27, + DescVal: int(descValPostfix), + DescStrPos: "(%d/%d Charges)", + DescStrNeg: "(%d/%d Charges)", + }, + "item_nonclassskill": { + Name: "item_nonclassskill", + DescFnID: 28, + DescVal: int(descValPostfix), + DescStrPos: "(%d/%d Charges)", + DescStrNeg: "(%d/%d Charges)", + }, +} - var charStats = map[d2enum.Hero]*d2datadict.CharStatsRecord{ - d2enum.HeroPaladin: { - Class: d2enum.HeroPaladin, - SkillStrAll: "to Paladin Skill Levels", - SkillStrClassOnly: "(Paladin Only)", - SkillStrTab: [3]string{ - "+%d to Combat Skills", - "+%d to Offensive Auras", - "+%d to Defensive Auras", - }, +var skillDetails = map[int]*d2records.SkillRecord{ + 37: {Skill: "Warmth"}, + 64: {Skill: "Frozen Orb"}, +} + +var monStats = map[string]*d2records.MonStatsRecord{ + "Specter": {NameString: "Specter", ID: 40}, +} + +var charStats = map[d2enum.Hero]*d2records.CharStatsRecord{ + d2enum.HeroPaladin: { + Class: d2enum.HeroPaladin, + SkillStrAll: "to Paladin Skill Levels", + SkillStrClassOnly: "(Paladin Only)", + SkillStrTab: [3]string{ + "+%d to Combat Skills", + "+%d to Offensive Auras", + "+%d to Defensive Auras", }, - } + }, +} +var testAssetManager2 *d2asset.AssetManager - var skillDetails = map[int]*d2datadict.SkillRecord{ - 37: {Skill: "Warmth"}, - 64: {Skill: "Frozen Orb"}, - } +var testStatFactory2 *StatFactory - var monStats = map[string]*d2datadict.MonStatsRecord{ - "Specter": {NameString: "Specter", ID: 40}, - } +func TestSetup_Stat(t *testing.T) { + testAssetManager2 = &d2asset.AssetManager{} + testAssetManager2.Records = &d2records.RecordManager{} - d2datadict.ItemStatCosts = itemStatCosts - d2datadict.CharStats = charStats - d2datadict.SkillDetails = skillDetails - d2datadict.MonStats = monStats + testStatFactory2, _ = NewStatFactory(testAssetManager2) + + testAssetManager2.Records.Item.Stats = itemStatCosts + testAssetManager2.Records.Character.Stats = charStats + testAssetManager2.Records.Skill.Details = skillDetails + testAssetManager2.Records.Monster.Stats = monStats } func TestStat_Clone(t *testing.T) { - s1 := NewStat("strength", 5) + s1 := testStatFactory2.NewStat("strength", 5) s2 := s1.Clone() // make sure the stats are distinct @@ -371,9 +381,9 @@ func TestStat_Descriptions(t *testing.T) { for idx := range tests { test := tests[idx] key := test.recordKey - record := d2datadict.ItemStatCosts[key] + record := itemStatCosts[key] expect := test.expect - stat := NewStat(key, test.vals...) + stat := testStatFactory2.NewStat(key, test.vals...) if got := stat.String(); got != expect { t.Errorf(errFmt, errStr, record.DescFnID, test.recordKey, test.vals, expect, got) @@ -386,8 +396,8 @@ func TestStat_Descriptions(t *testing.T) { } func TestDiablo2Stat_Combine(t *testing.T) { - a := NewStat("item_nonclassskill", 25, 64) // "+25 to Frozen Orb" - b := NewStat("item_nonclassskill", 5, 64) // "+5 to Frozen Orb" + a := testStatFactory2.NewStat("item_nonclassskill", 25, 64) // "+25 to Frozen Orb" + b := testStatFactory2.NewStat("item_nonclassskill", 5, 64) // "+5 to Frozen Orb" c, err := a.Combine(b) @@ -395,7 +405,7 @@ func TestDiablo2Stat_Combine(t *testing.T) { t.Errorf("stats combination failed\r%s", err) } - d := NewStat("item_nonclassskill", 5, 37) // "+5 to Warmth" + d := testStatFactory2.NewStat("item_nonclassskill", 5, 37) // "+5 to Warmth" _, err = c.Combine(d) if err == nil { diff --git a/d2core/d2stats/diablo2stats/stat_value_stringers.go b/d2core/d2stats/diablo2stats/stat_value_stringers.go index bce1c1ab..ef00c8cf 100644 --- a/d2core/d2stats/diablo2stats/stat_value_stringers.go +++ b/d2core/d2stats/diablo2stats/stat_value_stringers.go @@ -1,83 +1 @@ package diablo2stats - -import ( - "fmt" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats" -) - -const ( - monsterNotFound = "{Monster not found!}" -) - -func getHeroMap() []d2enum.Hero { - return []d2enum.Hero{ - d2enum.HeroAmazon, - d2enum.HeroSorceress, - d2enum.HeroNecromancer, - d2enum.HeroPaladin, - d2enum.HeroBarbarian, - d2enum.HeroDruid, - d2enum.HeroAssassin, - } -} - -func stringerUnsignedInt(sv d2stats.StatValue) string { - return fmt.Sprintf("%d", sv.Int()) -} - -func stringerUnsignedFloat(sv d2stats.StatValue) string { - return fmt.Sprintf("%.2f", sv.Float()) -} - -func stringerEmpty(_ d2stats.StatValue) string { - return "" -} - -func stringerIntSigned(sv d2stats.StatValue) string { - return fmt.Sprintf("%+d", sv.Int()) -} - -func stringerIntPercentageSigned(sv d2stats.StatValue) string { - return fmt.Sprintf("%+d%%", sv.Int()) -} - -func stringerIntPercentageUnsigned(sv d2stats.StatValue) string { - return fmt.Sprintf("%d%%", sv.Int()) -} - -func stringerClassAllSkills(sv d2stats.StatValue) string { - heroIndex := sv.Int() - - heroMap := getHeroMap() - classRecord := d2datadict.CharStats[heroMap[heroIndex]] - - return d2tbl.TranslateString(classRecord.SkillStrAll) -} - -func stringerClassOnly(sv d2stats.StatValue) string { - heroMap := getHeroMap() - heroIndex := sv.Int() - classRecord := d2datadict.CharStats[heroMap[heroIndex]] - classOnlyKey := classRecord.SkillStrClassOnly - - return d2tbl.TranslateString(classOnlyKey) -} - -func stringerSkillName(sv d2stats.StatValue) string { - skillRecord := d2datadict.SkillDetails[sv.Int()] - return skillRecord.Skill -} - -func stringerMonsterName(sv d2stats.StatValue) string { - for key := range d2datadict.MonStats { - if d2datadict.MonStats[key].ID == sv.Int() { - return d2datadict.MonStats[key].NameString - } - } - - return monsterNotFound -} diff --git a/d2core/d2stats/diablo2stats/statlist_test.go b/d2core/d2stats/diablo2stats/statlist_test.go index 0a9a2628..aa56ea6b 100644 --- a/d2core/d2stats/diablo2stats/statlist_test.go +++ b/d2core/d2stats/diablo2stats/statlist_test.go @@ -3,11 +3,30 @@ package diablo2stats import ( "testing" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats" ) +var testAssetManager *d2asset.AssetManager + +var testStatFactory *StatFactory + +func TestSetup_StatList(t *testing.T) { + testAssetManager = &d2asset.AssetManager{} + testAssetManager.Records = &d2records.RecordManager{} + + testStatFactory, _ = NewStatFactory(testAssetManager) + + testAssetManager.Records.Item.Stats = itemStatCosts + testAssetManager.Records.Character.Stats = charStats + testAssetManager.Records.Skill.Details = skillDetails + testAssetManager.Records.Monster.Stats = monStats +} + func TestDiablo2StatList_Index(t *testing.T) { - strength := NewStat("strength", 10) + strength := testStatFactory.NewStat("strength", 10) list1 := &Diablo2StatList{stats: []d2stats.Stat{strength}} if list1.Index(0) != strength { @@ -16,7 +35,7 @@ func TestDiablo2StatList_Index(t *testing.T) { } func TestStatList_Clone(t *testing.T) { - strength := NewStat("strength", 10) + strength := testStatFactory.NewStat("strength", 10) list1 := &Diablo2StatList{} list1.Push(strength) @@ -38,13 +57,13 @@ func TestStatList_Clone(t *testing.T) { func TestStatList_Reduce(t *testing.T) { stats := []d2stats.Stat{ - NewStat("strength", 1), - NewStat("strength", 1), - NewStat("strength", 1), - NewStat("strength", 1), + testStatFactory.NewStat("strength", 1), + testStatFactory.NewStat("strength", 1), + testStatFactory.NewStat("strength", 1), + testStatFactory.NewStat("strength", 1), } - list := NewStatList(stats...) + list := testStatFactory.NewStatList(stats...) reduction := list.ReduceStats() if len(reduction.Stats()) != 1 || reduction.Index(0).String() != "+4 to Strength" { @@ -52,13 +71,13 @@ func TestStatList_Reduce(t *testing.T) { } stats = []d2stats.Stat{ - NewStat("strength", 1), - NewStat("energy", 1), - NewStat("dexterity", 1), - NewStat("vitality", 1), + testStatFactory.NewStat("strength", 1), + testStatFactory.NewStat("energy", 1), + testStatFactory.NewStat("dexterity", 1), + testStatFactory.NewStat("vitality", 1), } - list = NewStatList(stats...) + list = testStatFactory.NewStatList(stats...) reduction = list.ReduceStats() if len(reduction.Stats()) != 4 { @@ -69,10 +88,10 @@ func TestStatList_Reduce(t *testing.T) { func TestStatList_Append(t *testing.T) { list1 := &Diablo2StatList{ []d2stats.Stat{ - NewStat("strength", 1), - NewStat("energy", 1), - NewStat("dexterity", 1), - NewStat("vitality", 1), + testStatFactory.NewStat("strength", 1), + testStatFactory.NewStat("energy", 1), + testStatFactory.NewStat("dexterity", 1), + testStatFactory.NewStat("vitality", 1), }, } list2 := list1.Clone() diff --git a/d2game/d2gamescreen/character_select.go b/d2game/d2gamescreen/character_select.go index c92a6777..ea657dac 100644 --- a/d2game/d2gamescreen/character_select.go +++ b/d2game/d2gamescreen/character_select.go @@ -6,16 +6,16 @@ import ( "math" "os" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" ) @@ -23,6 +23,7 @@ import ( type CharacterSelect struct { asset *d2asset.AssetManager *d2mapentity.MapEntityFactory + *d2hero.HeroStateFactory background *d2ui.Sprite newCharButton *d2ui.Button convertCharButton *d2ui.Button @@ -40,7 +41,7 @@ type CharacterSelect struct { characterStatsLabel [8]*d2ui.Label characterExpLabel [8]*d2ui.Label characterImage [8]*d2mapentity.Player - gameStates []*d2player.PlayerState + gameStates []*d2hero.HeroState selectedCharacter int showDeleteConfirmation bool connectionType d2clientconnectiontype.ClientConnectionType @@ -64,10 +65,13 @@ func CreateCharacterSelect( connectionType d2clientconnectiontype.ClientConnectionType, connectionHost string, ) *CharacterSelect { + playerStateFactory, _ := d2hero.NewHeroStateFactory(asset) // TODO: handle errors + entityFactory, _ := d2mapentity.NewMapEntityFactory(asset) + return &CharacterSelect{ selectedCharacter: -1, asset: asset, - MapEntityFactory: d2mapentity.NewMapEntityFactory(asset), + MapEntityFactory: entityFactory, renderer: renderer, connectionType: connectionType, connectionHost: connectionHost, @@ -75,6 +79,7 @@ func CreateCharacterSelect( audioProvider: audioProvider, navigator: navigator, uiManager: ui, + HeroStateFactory: playerStateFactory, } } @@ -282,7 +287,7 @@ func (v *CharacterSelect) updateCharacterBoxes() { v.characterExpLabel[i].SetText(d2ui.ColorTokenize(expText, d2ui.ColorTokenGreen)) heroType := v.gameStates[idx].HeroType - equipment := d2inventory.HeroObjects[heroType] + equipment := v.DefaultHeroItems[heroType] // TODO: Generate or load the object from the actual player data... v.characterImage[i] = v.NewPlayer("", "", 0, 0, 0, @@ -434,7 +439,11 @@ func (v *CharacterSelect) toggleDeleteCharacterDialog(showDialog bool) { } func (v *CharacterSelect) refreshGameStates() { - v.gameStates = d2player.GetAllPlayerStates() + gameStates, err := v.HeroStateFactory.GetAllHeroStates() + if err == nil { + v.gameStates = gameStates + } + v.updateCharacterBoxes() if len(v.gameStates) > 0 { diff --git a/d2game/d2gamescreen/game.go b/d2game/d2gamescreen/game.go index 5c46a353..fb8764b3 100644 --- a/d2game/d2gamescreen/game.go +++ b/d2game/d2gamescreen/game.go @@ -10,7 +10,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio" @@ -93,7 +92,7 @@ func CreateGame( audioProvider: audioProvider, renderer: renderer, terminal: term, - soundEngine: d2audio.NewSoundEngine(audioProvider, term), + soundEngine: d2audio.NewSoundEngine(audioProvider, asset, term), uiManager: ui, guiManager: guiManager, } @@ -142,7 +141,7 @@ func (v *Game) OnLoad(_ d2screen.LoadingState) { func(name string) { x := int(v.localPlayer.Position.X()) y := int(v.localPlayer.Position.Y()) - monstat := d2datadict.MonStats[name] + monstat := v.asset.Records.Monster.Stats[name] if monstat == nil { v.terminal.OutputErrorf("no monstat entry for \"%s\"", name) return @@ -233,12 +232,13 @@ func (v *Game) Advance(elapsed float64) error { tile := v.gameClient.MapEngine.TileAt(int(tilePosition.X()), int(tilePosition.Y())) if tile != nil { - v.soundEnv.SetEnv(d2datadict.LevelDetails[int(tile.RegionType)].SoundEnvironmentID) + levelDetails := v.asset.Records.Level.Details[int(tile.RegionType)] + v.soundEnv.SetEnv(levelDetails.SoundEnvironmentID) // skip showing zone change text the first time we enter the world if v.lastRegionType != d2enum.RegionNone && v.lastRegionType != tile.RegionType { //TODO: Should not be using RegionType as an index - this will return incorrect LevelDetails record for most of the zones. - areaName := d2datadict.LevelDetails[int(tile.RegionType)].LevelDisplayName + areaName := levelDetails.LevelDisplayName areaChgStr := fmt.Sprintf("Entering The %s", areaName) v.gameControls.SetZoneChangeText(areaChgStr) v.gameControls.ShowZoneChangeText() diff --git a/d2game/d2gamescreen/main_menu.go b/d2game/d2gamescreen/main_menu.go index 6605a251..2ad2ca3a 100644 --- a/d2game/d2gamescreen/main_menu.go +++ b/d2game/d2gamescreen/main_menu.go @@ -8,6 +8,8 @@ import ( "os/exec" "runtime" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -16,7 +18,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" "github.com/OpenDiablo2/OpenDiablo2/d2script" ) @@ -118,6 +119,7 @@ type MainMenu struct { scriptEngine *d2script.ScriptEngine navigator Navigator uiManager *d2ui.UIManager + heroState *d2hero.HeroStateFactory buildInfo BuildInfo } @@ -131,8 +133,13 @@ func CreateMainMenu( audioProvider d2interface.AudioProvider, ui *d2ui.UIManager, buildInfo BuildInfo, -) *MainMenu { - return &MainMenu{ +) (*MainMenu, error) { + heroStateFactory, err := d2hero.NewHeroStateFactory(asset) + if err != nil { + return nil, err + } + + mainMenu := &MainMenu{ asset: asset, screenMode: ScreenModeUnknown, leftButtonHeld: true, @@ -142,7 +149,10 @@ func CreateMainMenu( navigator: navigator, buildInfo: buildInfo, uiManager: ui, + heroState: heroStateFactory, } + + return mainMenu, nil } // OnLoad is called to load the resources for the main menu @@ -320,7 +330,7 @@ func (v *MainMenu) onMapTestClicked() { } func (v *MainMenu) onSinglePlayerClicked() { - if d2player.HasGameStates() { + if v.heroState.HasGameStates() { // Go here only if existing characters are available to select v.navigator.ToCharacterSelect(d2clientconnectiontype.Local, v.tcpJoinGameEntry.GetText()) } else { diff --git a/d2game/d2gamescreen/map_engine_testing.go b/d2game/d2gamescreen/map_engine_testing.go index c61a9f1a..2c5f4568 100644 --- a/d2game/d2gamescreen/map_engine_testing.go +++ b/d2game/d2gamescreen/map_engine_testing.go @@ -7,6 +7,8 @@ import ( "strings" "time" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" @@ -16,7 +18,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen" - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" ) type regionSpec struct { @@ -84,15 +85,17 @@ func getRegions() []regionSpec { // MapEngineTest represents the MapEngineTest screen type MapEngineTest struct { - asset *d2asset.AssetManager - gameState *d2player.PlayerState - mapEngine *d2mapengine.MapEngine - mapRenderer *d2maprenderer.MapRenderer - terminal d2interface.Terminal - renderer d2interface.Renderer - inputManager d2interface.InputManager - audioProvider d2interface.AudioProvider - screen *d2screen.ScreenManager + asset *d2asset.AssetManager + playerStateFactory *d2hero.HeroStateFactory + playerState *d2hero.HeroState + mapEngine *d2mapengine.MapEngine + mapGen *d2mapgen.MapGenerator + mapRenderer *d2maprenderer.MapRenderer + terminal d2interface.Terminal + renderer d2interface.Renderer + inputManager d2interface.InputManager + audioProvider d2interface.AudioProvider + screen *d2screen.ScreenManager lastMouseX, lastMouseY int selX, selY int @@ -116,23 +119,30 @@ func CreateMapEngineTest(currentRegion, inputManager d2interface.InputManager, audioProvider d2interface.AudioProvider, screen *d2screen.ScreenManager, -) *MapEngineTest { - result := &MapEngineTest{ - currentRegion: currentRegion, - levelPreset: levelPreset, - fileIndex: 0, - regionSpec: regionSpec{}, - filesCount: 0, - asset: asset, - terminal: term, - renderer: renderer, - inputManager: inputManager, - audioProvider: audioProvider, - screen: screen, +) (*MapEngineTest, error) { + heroStateFactory, err := d2hero.NewHeroStateFactory(asset) + if err != nil { + return nil, err } - result.gameState = d2player.CreateTestGameState() - return result + result := &MapEngineTest{ + currentRegion: currentRegion, + levelPreset: levelPreset, + fileIndex: 0, + regionSpec: regionSpec{}, + filesCount: 0, + asset: asset, + terminal: term, + renderer: renderer, + inputManager: inputManager, + audioProvider: audioProvider, + screen: screen, + playerStateFactory: heroStateFactory, + } + + result.playerState = heroStateFactory.CreateTestGameState() + + return result, nil } func (met *MapEngineTest) loadRegionByIndex(n, levelPreset, fileIndex int) { @@ -167,9 +177,12 @@ func (met *MapEngineTest) loadRegionByIndex(n, levelPreset, fileIndex int) { met.levelPreset = levelPreset } + mapGen, _ := d2mapgen.NewMapGenerator(met.asset, met.mapEngine) + met.mapGen = mapGen + if n == 0 { met.mapEngine.SetSeed(time.Now().UnixNano()) - d2mapgen.GenerateAct1Overworld(met.mapEngine) + met.mapGen.GenerateAct1Overworld() } else { met.mapEngine = d2mapengine.CreateMapEngine(met.asset) // necessary for map name update met.mapEngine.SetSeed(time.Now().UnixNano()) diff --git a/d2game/d2gamescreen/select_hero_class.go b/d2game/d2gamescreen/select_hero_class.go index cf9db311..5d8186a2 100644 --- a/d2game/d2gamescreen/select_hero_class.go +++ b/d2game/d2gamescreen/select_hero_class.go @@ -4,7 +4,10 @@ import ( "fmt" "image" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -14,7 +17,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" ) @@ -268,18 +270,20 @@ func (hri *HeroRenderInfo) advance(elapsed float64) { // SelectHeroClass represents the Select Hero Class screen type SelectHeroClass struct { - asset *d2asset.AssetManager - uiManager *d2ui.UIManager - bgImage *d2ui.Sprite - campfire *d2ui.Sprite - headingLabel *d2ui.Label - heroClassLabel *d2ui.Label - heroDesc1Label *d2ui.Label - heroDesc2Label *d2ui.Label - heroDesc3Label *d2ui.Label - heroNameTextbox *d2ui.TextBox - heroNameLabel *d2ui.Label - heroRenderInfo map[d2enum.Hero]*HeroRenderInfo + asset *d2asset.AssetManager + uiManager *d2ui.UIManager + bgImage *d2ui.Sprite + campfire *d2ui.Sprite + headingLabel *d2ui.Label + heroClassLabel *d2ui.Label + heroDesc1Label *d2ui.Label + heroDesc2Label *d2ui.Label + heroDesc3Label *d2ui.Label + heroNameTextbox *d2ui.TextBox + heroNameLabel *d2ui.Label + heroRenderInfo map[d2enum.Hero]*HeroRenderInfo + *d2inventory.InventoryItemFactory + *d2hero.HeroStateFactory selectedHero d2enum.Hero exitButton *d2ui.Button okButton *d2ui.Button @@ -304,20 +308,32 @@ func CreateSelectHeroClass( ui *d2ui.UIManager, connectionType d2clientconnectiontype.ClientConnectionType, connectionHost string, -) *SelectHeroClass { - result := &SelectHeroClass{ - asset: asset, - heroRenderInfo: make(map[d2enum.Hero]*HeroRenderInfo), - selectedHero: d2enum.HeroNone, - connectionType: connectionType, - connectionHost: connectionHost, - audioProvider: audioProvider, - renderer: renderer, - navigator: navigator, - uiManager: ui, +) (*SelectHeroClass, error) { + playerStateFactory, err := d2hero.NewHeroStateFactory(asset) + if err != nil { + return nil, err } - return result + inventoryItemFactory, err := d2inventory.NewInventoryItemFactory(asset) + if err != nil { + return nil, err + } + + result := &SelectHeroClass{ + asset: asset, + heroRenderInfo: make(map[d2enum.Hero]*HeroRenderInfo), + selectedHero: d2enum.HeroNone, + connectionType: connectionType, + connectionHost: connectionHost, + audioProvider: audioProvider, + renderer: renderer, + navigator: navigator, + uiManager: ui, + HeroStateFactory: playerStateFactory, + InventoryItemFactory: inventoryItemFactory, + } + + return result, nil } // OnLoad loads the resources for the Select Hero Class screen @@ -469,12 +485,23 @@ func (v *SelectHeroClass) onExitButtonClicked() { } func (v *SelectHeroClass) onOkButtonClicked() { - gameState := d2player.CreatePlayerState( - v.heroNameTextbox.GetText(), - v.selectedHero, - d2datadict.CharStats[v.selectedHero], - ) - v.navigator.ToCreateGame(gameState.FilePath, d2clientconnectiontype.Local, v.connectionHost) + + heroName := v.heroNameTextbox.GetText() + defaultStats := v.asset.Records.Character.Stats[v.selectedHero] + statsState := v.CreateHeroStatsState(v.selectedHero, defaultStats) + + playerState, err := v.CreateHeroState(heroName, v.selectedHero, statsState) + + if err := v.Save(playerState); err != nil { + fmt.Printf("failed to save game state!, err: %v\n", err) + } + + if err != nil { + return + } + + playerState.Equipment = v.InventoryItemFactory.DefaultHeroItems[v.selectedHero] + v.navigator.ToCreateGame(playerState.FilePath, d2clientconnectiontype.Local, v.connectionHost) } // Render renders the Select Hero Class screen diff --git a/d2game/d2player/equipment_slot.go b/d2game/d2player/equipment_slot.go index cc6bfe4b..462958fe 100644 --- a/d2game/d2player/equipment_slot.go +++ b/d2game/d2player/equipment_slot.go @@ -1,8 +1,8 @@ package d2player import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" ) // EquipmentSlot represents an equipment slot for a player @@ -14,7 +14,7 @@ type EquipmentSlot struct { height int } -func genEquipmentSlotsMap(record *d2datadict.InventoryRecord) map[d2enum.EquippedSlot]EquipmentSlot { +func genEquipmentSlotsMap(record *d2records.InventoryRecord) map[d2enum.EquippedSlot]EquipmentSlot { slotMap := map[d2enum.EquippedSlot]EquipmentSlot{} slots := []d2enum.EquippedSlot{ diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index f74cd8f4..eefbaa82 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -12,7 +12,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player/help" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" @@ -51,6 +50,7 @@ type GameControls struct { renderer d2interface.Renderer // TODO: This shouldn't be a dependency inputListener InputCallbackListener hero *d2mapentity.Player + heroState *d2hero.HeroStateFactory mapEngine *d2mapengine.MapEngine mapRenderer *d2maprenderer.MapRenderer uiManager *d2ui.UIManager @@ -155,18 +155,24 @@ func NewGameControls( return nil, fmt.Errorf("unknown hero class: %d", hero.Class) } - inventoryRecord := d2datadict.Inventory[inventoryRecordKey] + inventoryRecord := asset.Records.Layout.Inventory[inventoryRecordKey] hoverLabel := nameLabel hoverLabel.SetBackgroundColor(color.RGBA{0, 0, 0, uint8(128)}) globeStatsLabel := hpManaStatsLabel + heroState, err := d2hero.NewHeroStateFactory(asset) + if err != nil { + return nil, err + } + gc := &GameControls{ asset: asset, uiManager: ui, renderer: renderer, hero: hero, + heroState: heroState, mapEngine: mapEngine, inputListener: inputListener, mapRenderer: mapRenderer, @@ -196,7 +202,7 @@ func NewGameControls( isSinglePlayer: isSinglePlayer, } - err := term.BindAction("freecam", "toggle free camera movement", func() { + err = term.BindAction("freecam", "toggle free camera movement", func() { gc.FreeCam = !gc.FreeCam }) @@ -205,13 +211,23 @@ func NewGameControls( } err = term.BindAction("setleftskill", "set skill to fire on left click", func(id int) { - skillRecord := d2datadict.SkillDetails[id] - gc.hero.LeftSkill = &d2hero.HeroSkill{SkillPoints: 0, SkillRecord: skillRecord, SkillDescriptionRecord: d2datadict.SkillDescriptions[skillRecord.Skilldesc]} + skillRecord := gc.asset.Records.Skill.Details[id] + skill, err := heroState.CreateHeroSkill(0, skillRecord.Skill) + if err != nil { + term.OutputErrorf("cannot create skill with ID of %d", id) + } + + gc.hero.LeftSkill = skill }) err = term.BindAction("setrightskill", "set skill to fire on right click", func(id int) { - skillRecord := d2datadict.SkillDetails[id] - gc.hero.RightSkill = &d2hero.HeroSkill{SkillPoints: 0, SkillRecord: skillRecord, SkillDescriptionRecord: d2datadict.SkillDescriptions[skillRecord.Skilldesc]} + skillRecord := gc.asset.Records.Skill.Details[id] + skill, err := heroState.CreateHeroSkill(0, skillRecord.Skill) + if err != nil { + term.OutputErrorf("cannot create skill with ID of %d", id) + } + + gc.hero.RightSkill = skill }) if err != nil { @@ -416,7 +432,7 @@ func (g *GameControls) Load() { attackIconID := 2 g.leftSkill = &SkillResource{SkillIcon: genericSkillsSprite, IconNumber: attackIconID, SkillResourcePath: d2resource.GenericSkills} - g.rightSkill = &SkillResource{SkillIcon: genericSkillsSprite, IconNumber: attackIconID, SkillResourcePath: d2resource.GenericSkills} + g.rightSkill = &SkillResource{SkillIcon: genericSkillsSprite, IconNumber: attackIconID, SkillResourcePath: d2resource.GenericSkills} g.loadUIButtons() diff --git a/d2game/d2player/inventory.go b/d2game/d2player/inventory.go index a30e3ebf..8763d0b2 100644 --- a/d2game/d2player/inventory.go +++ b/d2game/d2player/inventory.go @@ -4,7 +4,8 @@ import ( "fmt" "image/color" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" @@ -17,6 +18,7 @@ import ( // Inventory represents the inventory type Inventory struct { asset *d2asset.AssetManager + item *diablo2item.ItemFactory uiManager *d2ui.UIManager frame *d2ui.Sprite panel *d2ui.Sprite @@ -34,13 +36,16 @@ type Inventory struct { // NewInventory creates an inventory instance and returns a pointer to it func NewInventory(asset *d2asset.AssetManager, ui *d2ui.UIManager, - record *d2datadict.InventoryRecord) *Inventory { + record *d2records.InventoryRecord) *Inventory { hoverLabel := ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic) hoverLabel.Alignment = d2gui.HorizontalAlignCenter + itemFactory, _ := diablo2item.NewItemFactory(asset) // TODO handle errors + return &Inventory{ asset: asset, uiManager: ui, + item: itemFactory, grid: NewItemGrid(asset, ui, record), originX: record.Panel.Left, hoverLabel: hoverLabel, @@ -74,28 +79,52 @@ func (g *Inventory) Load() { g.frame, _ = g.uiManager.NewSprite(d2resource.Frame, d2resource.PaletteSky) g.panel, _ = g.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky) - items := []InventoryItem{ - diablo2item.NewItem("kit", "Crimson", "of the Bat", "of Frost").Identify(), - diablo2item.NewItem("rin", "Steel", "of Shock").Identify(), - diablo2item.NewItem("jav").Identify(), - diablo2item.NewItem("buc").Identify(), - // diablo2item.NewItem("Arctic Furs", "qui"), - // TODO: Load the player's actual items + + // TODO: remove this item test code + testInventoryCodes := [][]string{ + {"kit", "Crimson", "of the Bat", "of Frost"}, + {"rin", "Steel", "of Shock"}, + {"jav"}, + {"buc"}, + } + + inventoryItems := make([]InventoryItem, 0) + + for idx := range testInventoryCodes { + item, err := g.item.NewItem(testInventoryCodes[idx]...) + if err != nil { + continue + } + + item.Identify() + inventoryItems = append(inventoryItems, item) + } + + testEquippedItemCodes := map[d2enum.EquippedSlot][]string{ + d2enum.EquippedSlotLeftArm: {"wnd"}, + d2enum.EquippedSlotRightArm: {"buc"}, + d2enum.EquippedSlotHead: {"crn"}, + d2enum.EquippedSlotTorso: {"plt"}, + d2enum.EquippedSlotLegs: {"vbt"}, + d2enum.EquippedSlotBelt: {"vbl"}, + d2enum.EquippedSlotGloves: {"lgl"}, + d2enum.EquippedSlotLeftHand: {"rin"}, + d2enum.EquippedSlotRightHand: {"rin"}, + d2enum.EquippedSlotNeck: {"amu"}, + } + + for slot := range testEquippedItemCodes { + item, err := g.item.NewItem(testEquippedItemCodes[slot]...) + if err != nil { + continue + } + + g.grid.ChangeEquippedSlot(slot, item) } - g.grid.ChangeEquippedSlot(d2enum.EquippedSlotLeftArm, diablo2item.NewItem("wnd")) - g.grid.ChangeEquippedSlot(d2enum.EquippedSlotRightArm, diablo2item.NewItem("buc")) - g.grid.ChangeEquippedSlot(d2enum.EquippedSlotHead, diablo2item.NewItem("crn")) - g.grid.ChangeEquippedSlot(d2enum.EquippedSlotTorso, diablo2item.NewItem("plt")) - g.grid.ChangeEquippedSlot(d2enum.EquippedSlotLegs, diablo2item.NewItem("vbt")) - g.grid.ChangeEquippedSlot(d2enum.EquippedSlotBelt, diablo2item.NewItem("vbl")) - g.grid.ChangeEquippedSlot(d2enum.EquippedSlotGloves, diablo2item.NewItem("lgl")) - g.grid.ChangeEquippedSlot(d2enum.EquippedSlotLeftHand, diablo2item.NewItem("rin")) - g.grid.ChangeEquippedSlot(d2enum.EquippedSlotRightHand, diablo2item.NewItem("rin")) - g.grid.ChangeEquippedSlot(d2enum.EquippedSlotNeck, diablo2item.NewItem("amu")) // TODO: Load the player's actual items - _, err := g.grid.Add(items...) + _, err := g.grid.Add(inventoryItems...) if err != nil { fmt.Printf("could not add items to the inventory, err: %v\n", err) } diff --git a/d2game/d2player/inventory_grid.go b/d2game/d2player/inventory_grid.go index 2f564a03..2ccae2a9 100644 --- a/d2game/d2player/inventory_grid.go +++ b/d2game/d2player/inventory_grid.go @@ -5,7 +5,8 @@ import ( "fmt" "log" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -46,7 +47,7 @@ type ItemGrid struct { } func NewItemGrid(asset *d2asset.AssetManager, ui *d2ui.UIManager, - record *d2datadict.InventoryRecord) *ItemGrid { + record *d2records.InventoryRecord) *ItemGrid { grid := record.Grid return &ItemGrid{ diff --git a/d2game/d2player/player_state.go b/d2game/d2player/player_state.go deleted file mode 100644 index 37bb5dae..00000000 --- a/d2game/d2player/player_state.go +++ /dev/null @@ -1,155 +0,0 @@ -package d2player - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path" - "strconv" - "strings" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory" -) - -// PlayerState stores the state of the player -type PlayerState struct { - HeroName string `json:"heroName"` - HeroType d2enum.Hero `json:"heroType"` - HeroLevel int `json:"heroLevel"` - Act int `json:"act"` - FilePath string `json:"-"` - Equipment d2inventory.CharacterEquipment `json:"equipment"` - Stats *d2hero.HeroStatsState `json:"stats"` - Skills *d2hero.HeroSkillsState `json:"skills"` - X float64 `json:"x"` - Y float64 `json:"y"` -} - -// HasGameStates returns true if the player has any previously saved game -func HasGameStates() bool { - basePath, _ := getGameBaseSavePath() - files, _ := ioutil.ReadDir(basePath) - - return len(files) > 0 -} - -// GetAllPlayerStates returns all player saves -func GetAllPlayerStates() []*PlayerState { - basePath, _ := getGameBaseSavePath() - files, _ := ioutil.ReadDir(basePath) - result := make([]*PlayerState, 0) - - for _, file := range files { - fileName := file.Name() - if file.IsDir() || len(fileName) < 5 || !strings.EqualFold(fileName[len(fileName)-4:], ".od2") { - continue - } - - gameState := LoadPlayerState(path.Join(basePath, file.Name())) - if gameState == nil || gameState.HeroType == d2enum.HeroNone { - continue - } else if gameState.Stats == nil || gameState.Skills == nil { - // temporarily loading default class stats if the character was created before saving stats/skills was introduced - // to be removed in the future - classStats := d2datadict.CharStats[gameState.HeroType] - gameState.Stats = d2hero.CreateHeroStatsState(gameState.HeroType, classStats) - gameState.Skills = d2hero.CreateHeroSkillsState(classStats) - - if err := gameState.Save(); err != nil { - fmt.Printf("failed to save game state!, err: %v\n", err) - } - } - result = append(result, gameState) - - } - - return result -} - -// CreateTestGameState is used for the map engine previewer -func CreateTestGameState() *PlayerState { - result := &PlayerState{} - return result -} - -// LoadPlayerState loads the player state from the file -func LoadPlayerState(filePath string) *PlayerState { - strData, err := ioutil.ReadFile(filePath) - if err != nil { - return nil - } - - result := &PlayerState{ - FilePath: filePath, - } - - err = json.Unmarshal(strData, result) - if err != nil { - return nil - } - - return result -} - -// CreatePlayerState creates a PlayerState instance and returns a pointer to it -func CreatePlayerState(heroName string, hero d2enum.Hero, classStats *d2datadict.CharStatsRecord) *PlayerState { - result := &PlayerState{ - HeroName: heroName, - HeroType: hero, - Act: 1, - Stats: d2hero.CreateHeroStatsState(hero, classStats), - Skills: d2hero.CreateHeroSkillsState(classStats), - Equipment: d2inventory.HeroObjects[hero], - FilePath: "", - } - - if err := result.Save(); err != nil { - fmt.Printf("failed to save game state!, err: %v\n", err) - return nil - } - - return result -} - -func getGameBaseSavePath() (string, error) { - configDir, err := os.UserConfigDir() - if err != nil { - return "", err - } - - return path.Join(configDir, "OpenDiablo2/Saves"), nil -} - -func getFirstFreeFileName() string { - i := 0 - basePath, _ := getGameBaseSavePath() - - for { - filePath := path.Join(basePath, strconv.Itoa(i)+".od2") - if _, err := os.Stat(filePath); os.IsNotExist(err) { - return filePath - } - i++ - } -} - -// Save saves the player state to a file -func (v *PlayerState) Save() error { - if v.FilePath == "" { - v.FilePath = getFirstFreeFileName() - } - if err := os.MkdirAll(path.Dir(v.FilePath), 0755); err != nil { - return err - } - - fileJSON, _ := json.MarshalIndent(v, "", " ") - if err := ioutil.WriteFile(v.FilePath, fileJSON, 0644); err != nil { - return err - } - - return nil -} diff --git a/d2networking/d2client/d2localclient/local_client_connection.go b/d2networking/d2client/d2localclient/local_client_connection.go index 2f875d5d..1f8ba9b8 100644 --- a/d2networking/d2client/d2localclient/local_client_connection.go +++ b/d2networking/d2client/d2localclient/local_client_connection.go @@ -2,9 +2,9 @@ package d2localclient import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" uuid "github.com/satori/go.uuid" - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" "github.com/OpenDiablo2/OpenDiablo2/d2networking" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" @@ -15,10 +15,11 @@ import ( // for a local client. type LocalClientConnection struct { asset *d2asset.AssetManager + heroState *d2hero.HeroStateFactory clientListener d2networking.ClientListener // The game client uniqueID string // Unique ID generated on construction openNetworkServer bool // True if this is a server - playerState *d2player.PlayerState // Local player state + playerState *d2hero.HeroState // Local player state gameServer *d2server.GameServer // Game Server } @@ -40,21 +41,27 @@ func (l *LocalClientConnection) SendPacketToClient(packet d2netpacket.NetPacket) // Create constructs a new LocalClientConnection and returns // a pointer to it. -func Create(asset *d2asset.AssetManager, openNetworkServer bool) *LocalClientConnection { +func Create(asset *d2asset.AssetManager, openNetworkServer bool) (*LocalClientConnection, error) { + heroStateFactory, err := d2hero.NewHeroStateFactory(asset) + if err != nil { + return nil, err + } + result := &LocalClientConnection{ + heroState: heroStateFactory, asset: asset, uniqueID: uuid.NewV4().String(), openNetworkServer: openNetworkServer, } - return result + return result, nil } // Open creates a new GameServer, runs the server and connects this client to it. func (l *LocalClientConnection) Open(_, saveFilePath string) error { var err error - l.SetPlayerState(d2player.LoadPlayerState(saveFilePath)) + l.SetPlayerState(l.heroState.LoadHeroState(saveFilePath)) l.gameServer, err = d2server.NewGameServer(l.asset, l.openNetworkServer, 30) if err != nil { @@ -95,11 +102,11 @@ func (l *LocalClientConnection) SetClientListener(listener d2networking.ClientLi } // GetPlayerState returns LocalClientConnection.playerState. -func (l *LocalClientConnection) GetPlayerState() *d2player.PlayerState { +func (l *LocalClientConnection) GetPlayerState() *d2hero.HeroState { return l.playerState } // SetPlayerState sets LocalClientConnection.playerState to the given value. -func (l *LocalClientConnection) SetPlayerState(playerState *d2player.PlayerState) { +func (l *LocalClientConnection) SetPlayerState(playerState *d2hero.HeroState) { l.playerState = playerState } diff --git a/d2networking/d2client/d2remoteclient/remote_client_connection.go b/d2networking/d2client/d2remoteclient/remote_client_connection.go index a9230646..2fc6fefc 100644 --- a/d2networking/d2client/d2remoteclient/remote_client_connection.go +++ b/d2networking/d2client/d2remoteclient/remote_client_connection.go @@ -7,9 +7,12 @@ import ( "net" "strings" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + uuid "github.com/satori/go.uuid" - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" "github.com/OpenDiablo2/OpenDiablo2/d2networking" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" @@ -19,6 +22,8 @@ import ( // RemoteClientConnection is the implementation of ClientConnection // for a remote client. type RemoteClientConnection struct { + asset *d2asset.AssetManager + heroState *d2hero.HeroStateFactory clientListener d2networking.ClientListener // The GameClient uniqueID string // Unique ID generated on construction tcpConnection *net.TCPConn // UDP connection to the server @@ -27,12 +32,18 @@ type RemoteClientConnection struct { // Create constructs a new RemoteClientConnection // and returns a pointer to it. -func Create() *RemoteClientConnection { - result := &RemoteClientConnection{ - uniqueID: uuid.NewV4().String(), +func Create(asset *d2asset.AssetManager) (*RemoteClientConnection, error) { + heroStateFactory, err := d2hero.NewHeroStateFactory(asset) + if err != nil { + return nil, err } - return result + result := &RemoteClientConnection{ + heroState: heroStateFactory, + uniqueID: uuid.NewV4().String(), + } + + return result, nil } // Open runs serverListener() in a goroutine to continuously read UDP packets. @@ -61,7 +72,7 @@ func (r *RemoteClientConnection) Open(connectionString, saveFilePath string) err log.Printf("Connected to server at %s", r.tcpConnection.RemoteAddr().String()) - gameState := d2player.LoadPlayerState(saveFilePath) + gameState := r.heroState.LoadHeroState(saveFilePath) packet := d2netpacket.CreatePlayerConnectionRequestPacket(r.GetUniqueID(), gameState) err = r.SendPacketToServer(packet) diff --git a/d2networking/d2client/game_client.go b/d2networking/d2client/game_client.go index 916d2586..1015d54d 100644 --- a/d2networking/d2client/game_client.go +++ b/d2networking/d2client/game_client.go @@ -5,17 +5,18 @@ import ( "log" "os" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen" - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2localclient" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2remoteclient" @@ -35,8 +36,9 @@ type GameClient struct { connectionType d2clientconnectiontype.ClientConnectionType // Type of connection (local or remote) asset *d2asset.AssetManager scriptEngine *d2script.ScriptEngine - GameState *d2player.PlayerState // local player state + GameState *d2hero.HeroState // local player state MapEngine *d2mapengine.MapEngine // Map and entities + mapGen *d2mapgen.MapGenerator // map generator PlayerID string // ID of the local player Players map[string]*d2mapentity.Player // IDs of the other players Seed int64 // Map seed @@ -54,15 +56,26 @@ func Create(connectionType d2clientconnectiontype.ClientConnectionType, scriptEngine: scriptEngine, } + mapGen, err := d2mapgen.NewMapGenerator(asset, result.MapEngine) + if err != nil { + return nil, err + } + + result.mapGen = mapGen + switch connectionType { case d2clientconnectiontype.LANClient: - result.clientConnection = d2remoteclient.Create() + result.clientConnection, err = d2remoteclient.Create(asset) case d2clientconnectiontype.LANServer: - result.clientConnection = d2localclient.Create(asset, true) + result.clientConnection, err = d2localclient.Create(asset, true) case d2clientconnectiontype.Local: - result.clientConnection = d2localclient.Create(asset, false) + result.clientConnection, err = d2localclient.Create(asset, false) default: - return nil, fmt.Errorf("unknown client connection type specified: %d", connectionType) + err = fmt.Errorf("unknown client connection type specified: %d", connectionType) + } + + if err != nil { + return nil, err } result.clientConnection.SetClientListener(result) @@ -157,7 +170,7 @@ func (g *GameClient) handleGenerateMapPacket(packet d2netpacket.NetPacket) error } if mapData.RegionType == d2enum.RegionAct1Town { - d2mapgen.GenerateAct1Overworld(g.MapEngine) + g.mapGen.GenerateAct1Overworld() } g.RegenMap = true @@ -264,8 +277,8 @@ func (g *GameClient) handleCastSkillPacket(packet d2netpacket.NetPacket) error { direction := player.Position.DirectionTo(*d2vector.NewVector(castX, castY)) player.SetDirection(direction) - skill := d2datadict.SkillDetails[playerCast.SkillID] - missileRecord := d2datadict.GetMissileByName(skill.Cltmissile) + skill := g.asset.Records.Skill.Details[playerCast.SkillID] + missileRecord := g.asset.Records.GetMissileByName(skill.Cltmissile) if missileRecord == nil { //TODO: handle casts that have no missiles(or have multiple missiles and require additional logic) @@ -276,7 +289,7 @@ func (g *GameClient) handleCastSkillPacket(packet d2netpacket.NetPacket) error { missile, err := g.MapEngine.NewMissile( int(player.Position.X()), int(player.Position.Y()), - d2datadict.Missiles[missileRecord.Id], + missileRecord, ) if err != nil { @@ -288,7 +301,7 @@ func (g *GameClient) handleCastSkillPacket(packet d2netpacket.NetPacket) error { }) player.StartCasting(func() { - // shoot the missile after the player finished casting + // shoot the missile after the player finished casting g.MapEngine.AddEntity(missile) }) diff --git a/d2networking/d2netpacket/packet_add_player.go b/d2networking/d2netpacket/packet_add_player.go index b1095451..d87fbca1 100644 --- a/d2networking/d2netpacket/packet_add_player.go +++ b/d2networking/d2netpacket/packet_add_player.go @@ -20,13 +20,13 @@ type AddPlayerPacket struct { HeroType d2enum.Hero `json:"hero"` Equipment d2inventory.CharacterEquipment `json:"equipment"` Stats *d2hero.HeroStatsState `json:"heroStats"` - Skills *d2hero.HeroSkillsState `json:"heroSkills"` + Skills map[int]*d2hero.HeroSkill `json:"heroSkills"` } // CreateAddPlayerPacket returns a NetPacket which declares an // AddPlayerPacket with the data in given parameters. func CreateAddPlayerPacket(id, name string, x, y int, heroType d2enum.Hero, - stats *d2hero.HeroStatsState, skills *d2hero.HeroSkillsState, equipment d2inventory.CharacterEquipment) NetPacket { + stats *d2hero.HeroStatsState, skills map[int]*d2hero.HeroSkill, equipment d2inventory.CharacterEquipment) NetPacket { addPlayerPacket := AddPlayerPacket{ ID: id, Name: name, diff --git a/d2networking/d2netpacket/packet_player_connection_request.go b/d2networking/d2netpacket/packet_player_connection_request.go index 95148e88..d9b646cf 100644 --- a/d2networking/d2netpacket/packet_player_connection_request.go +++ b/d2networking/d2netpacket/packet_player_connection_request.go @@ -3,20 +3,21 @@ package d2netpacket import ( "encoding/json" - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" ) // PlayerConnectionRequestPacket contains a player ID and game state. // It is sent by a remote client to initiate a connection (join a game). type PlayerConnectionRequestPacket struct { - ID string `json:"id"` - PlayerState *d2player.PlayerState `json:"gameState"` + ID string `json:"id"` + PlayerState *d2hero.HeroState `json:"gameState"` } // CreatePlayerConnectionRequestPacket returns a NetPacket which defines a // PlayerConnectionRequestPacket with the given ID and game state. -func CreatePlayerConnectionRequestPacket(id string, playerState *d2player.PlayerState) NetPacket { +func CreatePlayerConnectionRequestPacket(id string, playerState *d2hero.HeroState) NetPacket { playerConnectionRequest := PlayerConnectionRequestPacket{ ID: id, PlayerState: playerState, diff --git a/d2networking/d2netpacket/packet_player_disconnect_request.go b/d2networking/d2netpacket/packet_player_disconnect_request.go index f8fe9fd5..c354e9b9 100644 --- a/d2networking/d2netpacket/packet_player_disconnect_request.go +++ b/d2networking/d2netpacket/packet_player_disconnect_request.go @@ -3,15 +3,16 @@ package d2netpacket import ( "encoding/json" - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" ) // PlayerDisconnectRequestPacket contains a player ID and game state. // It is sent by a remote client to close the connection (leave a game). type PlayerDisconnectRequestPacket struct { - ID string `json:"id"` - PlayerState *d2player.PlayerState `json:"gameState"` // TODO: remove this? It isn't used. + ID string `json:"id"` + PlayerState *d2hero.HeroState `json:"gameState"` // TODO: remove this? It isn't used. } // CreatePlayerDisconnectRequestPacket returns a NetPacket which defines a diff --git a/d2networking/d2server/client_connection.go b/d2networking/d2server/client_connection.go index c7b238cb..d02ef066 100644 --- a/d2networking/d2server/client_connection.go +++ b/d2networking/d2server/client_connection.go @@ -1,7 +1,7 @@ package d2server import ( - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" ) @@ -12,6 +12,6 @@ type ClientConnection interface { GetUniqueID() string GetConnectionType() d2clientconnectiontype.ClientConnectionType SendPacketToClient(packet d2netpacket.NetPacket) error - GetPlayerState() *d2player.PlayerState - SetPlayerState(playerState *d2player.PlayerState) + GetPlayerState() *d2hero.HeroState + SetPlayerState(playerState *d2hero.HeroState) } diff --git a/d2networking/d2server/d2tcpclientconnection/tcp_client_connection.go b/d2networking/d2server/d2tcpclientconnection/tcp_client_connection.go index 906f7c66..1c9debd8 100644 --- a/d2networking/d2server/d2tcpclientconnection/tcp_client_connection.go +++ b/d2networking/d2server/d2tcpclientconnection/tcp_client_connection.go @@ -4,16 +4,16 @@ import ( "encoding/json" "net" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" - - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" ) type TCPClientConnection struct { id string tcpConnection net.Conn - playerState *d2player.PlayerState + playerState *d2hero.HeroState } func CreateTCPClientConnection(tcpConnection net.Conn, id string) *TCPClientConnection { @@ -41,11 +41,11 @@ func (t *TCPClientConnection) SendPacketToClient(p d2netpacket.NetPacket) error return nil } -func (t *TCPClientConnection) SetPlayerState(playerState *d2player.PlayerState) { +func (t *TCPClientConnection) SetPlayerState(playerState *d2hero.HeroState) { t.playerState = playerState } -func (t *TCPClientConnection) GetPlayerState() *d2player.PlayerState { +func (t *TCPClientConnection) GetPlayerState() *d2hero.HeroState { return t.playerState } diff --git a/d2networking/d2server/d2udpclientconnection/udp_client_connection.go b/d2networking/d2server/d2udpclientconnection/udp_client_connection.go index ab8f80da..6c8bc3e5 100644 --- a/d2networking/d2server/d2udpclientconnection/udp_client_connection.go +++ b/d2networking/d2server/d2udpclientconnection/udp_client_connection.go @@ -8,9 +8,9 @@ import ( "fmt" "net" - "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" ) @@ -19,10 +19,10 @@ import ( // d2server.ClientConnection interface to represent remote client from the // server perspective. type UDPClientConnection struct { - id string // ID of the associated RemoteClientConnection - address *net.UDPAddr // IP address of the associated RemoteClientConnection - udpConnection *net.UDPConn // Server's UDP Connection - playerState *d2player.PlayerState // Client's game state + id string // ID of the associated RemoteClientConnection + address *net.UDPAddr // IP address of the associated RemoteClientConnection + udpConnection *net.UDPConn // Server's UDP Connection + playerState *d2hero.HeroState // Client's game state } // CreateUDPClientConnection constructs a new UDPClientConnection and @@ -80,12 +80,12 @@ func (u *UDPClientConnection) SendPacketToClient(packet d2netpacket.NetPacket) e return nil } -// SetPlayerState sets UDP.playerState to the given value. -func (u *UDPClientConnection) SetPlayerState(playerState *d2player.PlayerState) { +// SetHeroState sets UDP.playerState to the given value. +func (u *UDPClientConnection) SetHeroState(playerState *d2hero.HeroState) { u.playerState = playerState } -// GetPlayerState returns UDPClientConnection.playerState. -func (u *UDPClientConnection) GetPlayerState() *d2player.PlayerState { +// GetHeroState returns UDPClientConnection.playerState. +func (u *UDPClientConnection) GetHeroState() *d2hero.HeroState { return u.playerState } diff --git a/d2networking/d2server/game_server.go b/d2networking/d2server/game_server.go index 4d4f4008..2b9532a3 100644 --- a/d2networking/d2server/game_server.go +++ b/d2networking/d2server/game_server.go @@ -86,7 +86,13 @@ func NewGameServer(asset *d2asset.AssetManager, networkServer bool, mapEngine := d2mapengine.CreateMapEngine(asset) mapEngine.SetSeed(gameServer.seed) mapEngine.ResetMap(d2enum.RegionAct1Town, 100, 100) // TODO: Mapgen - Needs levels.txt stuff - d2mapgen.GenerateAct1Overworld(mapEngine) + + mapGen, err := d2mapgen.NewMapGenerator(asset, mapEngine) + if err != nil { + return nil, err + } + + mapGen.GenerateAct1Overworld() gameServer.mapEngines = append(gameServer.mapEngines, mapEngine)