mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-20 23:47:16 -05:00
Removing d2datadict singletons (#738)
* Remove weapons, armor, misc, itemCommon, itemTyps datadict singletons - removed loader calls from d2app - removed the HeroObjects singleton from `d2core/d2inventory` - added an InventoryItemFactory in d2inventory - package-level functions that use data records are now methods of the InventoryItemFactory - renamed ItemGenerator in d2item to ItemFactory - package-level functions that use records are now methods of ItemFactory - d2map.MapEntityFactory now has an item factory instance for creating items - fixed a bug in unique item record loader where it loaded an empty record - added a PlayerStateFactory for creating a player state (uses the asset manager) - updated the test inventory/equipment code in d2player to handle errors from the ItemFactory - character select and character creation screens have a player state and inventory item factory - updated item tests to use the item factory * minor edit * Removed d2datadict.Experience singleton added a HeroStatsFactory, much like the other factories. The factory gets an asset manager reference in order to use data records. * removed d2datadict.AutoMagic singleton * removed d2datadict.AutoMap singleton * removed d2datadict.BodyLocations singleton * removed d2datadict.Books singleton * Removed singletons for level records - removed loader calls in d2app - changed type references from d2datadict to d2records - added a `MapGenerator` in d2mapgen which uses thew asset manager and map engine - package-level map generation functions are now MapGenerator methods - `d2datadict.GetLevelDetails(id int)` is now a method of the RecordManager * remove SkillCalc and MissileCalc singletons * Removed CharStats and ItemStatCost singletons - added an ItemStatFactory which uses the asset manager to create stats - package-level functions for stats in d2item are now StatFactory methods - changed type references from d2datadict to d2records - `d2player.GetAllPlayerStates` is now a method of the `PlayerStateFactory` * Removed DkillDesc and Skills singletons from d2datadict - removed loader calls from d2app - diablo2stats.Stat instances are given a reference to the factory for doing record lookups * update the stats test to use mock a asset manager and stat factory * fixed diablo2stats tests and diablo2item tests * removed CompCodes singleton from d2datadict * remove cubemain singleton from d2datadict * removed DifficultyLevels singleton from d2datadict * removed ElemTypes singleton from d2datadict * removed events.go loader from d2datadict (was unused) * removed Gems singleton from d2datadict * removed Hireling and Inventory singletons from d2datadict * removed MagicPrefix and MagicSuffix singletons from d2datadict * removed ItemRatios singleton from d2datadict * removed Missiles singleton from d2datadict * removed MonModes singleton * Removed all monster and npc singletons from d2datadict - MapStamp instances now get a reference to their factory for doing record lookups * removed SoundEntry and SoundEnviron singletons from d2datadict
This commit is contained in:
parent
a4e9797431
commit
fc87b2be7a
77
d2app/app.go
77
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)
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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 <ReqStatID> <ReqOperation> <ReqValue> 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, "\""), ",")
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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))
|
||||
}
|
@ -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]
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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")
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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
|
||||
}
|
@ -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))
|
||||
}
|
@ -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)
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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]
|
||||
}
|
@ -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))
|
||||
}
|
@ -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
|
||||
}
|
@ -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))
|
||||
}
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
20
d2core/d2hero/hero_state.go
Normal file
20
d2core/d2hero/hero_state.go
Normal file
@ -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"`
|
||||
}
|
235
d2core/d2hero/hero_state_factory.go
Normal file
235
d2core/d2hero/hero_state_factory.go
Normal file
@ -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
|
||||
}
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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 == "" {
|
||||
|
107
d2core/d2inventory/inventory_item_factory.go
Normal file
107
d2core/d2inventory/inventory_item_factory.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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 == "" {
|
||||
|
@ -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()
|
||||
}
|
@ -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 {
|
||||
|
426
d2core/d2item/diablo2item/item_factory.go
Normal file
426
d2core/d2item/diablo2item/item_factory.go
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
59
d2core/d2map/d2mapgen/map_generator.go
Normal file
59
d2core/d2map/d2mapgen/map_generator.go
Normal file
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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, " ", ""))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)]
|
||||
}
|
||||
|
@ -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"),
|
||||
|
@ -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
|
||||
|
@ -116,6 +116,10 @@ func uniqueItemsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
||||
},
|
||||
}
|
||||
|
||||
if record.Name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
records[record.Name] = record
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -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()
|
||||
|
136
d2core/d2stats/diablo2stats/stat_factory.go
Normal file
136
d2core/d2stats/diablo2stats/stat_factory.go
Normal file
@ -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
|
||||
}
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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{
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user