mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-06-17 04:45:23 +00: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/d2asset"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2gamescreen"
|
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2gamescreen"
|
||||||
|
@ -195,8 +194,6 @@ func (a *App) initialize() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
d2inventory.LoadHeroObjects()
|
|
||||||
|
|
||||||
a.ui.Initialize()
|
a.ui.Initialize()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -226,73 +223,25 @@ func (a *App) loadDataDict() error {
|
||||||
path string
|
path string
|
||||||
loader func(data []byte)
|
loader func(data []byte)
|
||||||
}{
|
}{
|
||||||
{d2resource.LevelType, d2datadict.LoadLevelTypes},
|
|
||||||
{d2resource.LevelPreset, d2datadict.LoadLevelPresets},
|
|
||||||
{d2resource.LevelWarp, d2datadict.LoadLevelWarps},
|
|
||||||
{d2resource.ObjectType, d2datadict.LoadObjectTypes},
|
{d2resource.ObjectType, d2datadict.LoadObjectTypes},
|
||||||
{d2resource.ObjectDetails, d2datadict.LoadObjects},
|
{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.UniqueItems, d2datadict.LoadUniqueItems},
|
||||||
{d2resource.Missiles, d2datadict.LoadMissiles},
|
|
||||||
{d2resource.SoundSettings, d2datadict.LoadSounds},
|
|
||||||
{d2resource.AnimationData, d2data.LoadAnimationData},
|
{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.Overlays, d2datadict.LoadOverlays},
|
||||||
{d2resource.CharStats, d2datadict.LoadCharStats},
|
|
||||||
{d2resource.Hireling, d2datadict.LoadHireling},
|
|
||||||
{d2resource.Experience, d2datadict.LoadExperienceBreakpoints},
|
|
||||||
{d2resource.Gems, d2datadict.LoadGems},
|
|
||||||
{d2resource.QualityItems, d2datadict.LoadQualityItems},
|
{d2resource.QualityItems, d2datadict.LoadQualityItems},
|
||||||
{d2resource.Runes, d2datadict.LoadRunewords},
|
{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.SuperUniques, d2datadict.LoadSuperUniques},
|
||||||
{d2resource.Inventory, d2datadict.LoadInventory},
|
|
||||||
{d2resource.Skills, d2datadict.LoadSkills},
|
|
||||||
{d2resource.SkillCalc, d2datadict.LoadSkillCalculations},
|
|
||||||
{d2resource.MissileCalc, d2datadict.LoadMissileCalculations},
|
|
||||||
{d2resource.Properties, d2datadict.LoadProperties},
|
{d2resource.Properties, d2datadict.LoadProperties},
|
||||||
{d2resource.SkillDesc, d2datadict.LoadSkillDescriptions},
|
|
||||||
{d2resource.ItemTypes, d2datadict.LoadItemTypes},
|
|
||||||
{d2resource.BodyLocations, d2datadict.LoadBodyLocations},
|
|
||||||
{d2resource.Sets, d2datadict.LoadSetRecords},
|
{d2resource.Sets, d2datadict.LoadSetRecords},
|
||||||
{d2resource.SetItems, d2datadict.LoadSetItems},
|
{d2resource.SetItems, d2datadict.LoadSetItems},
|
||||||
{d2resource.AutoMagic, d2datadict.LoadAutoMagicRecords},
|
|
||||||
{d2resource.TreasureClass, d2datadict.LoadTreasureClassRecords},
|
{d2resource.TreasureClass, d2datadict.LoadTreasureClassRecords},
|
||||||
{d2resource.States, d2datadict.LoadStates},
|
{d2resource.States, d2datadict.LoadStates},
|
||||||
{d2resource.SoundEnvirons, d2datadict.LoadSoundEnvirons},
|
|
||||||
{d2resource.Shrines, d2datadict.LoadShrines},
|
{d2resource.Shrines, d2datadict.LoadShrines},
|
||||||
{d2resource.ElemType, d2datadict.LoadElemTypes},
|
|
||||||
{d2resource.PlrMode, d2datadict.LoadPlrModes},
|
{d2resource.PlrMode, d2datadict.LoadPlrModes},
|
||||||
{d2resource.PetType, d2datadict.LoadPetTypes},
|
{d2resource.PetType, d2datadict.LoadPetTypes},
|
||||||
{d2resource.NPC, d2datadict.LoadNPCs},
|
|
||||||
{d2resource.MonsterUniqueModifier, d2datadict.LoadMonsterUniqueModifiers},
|
|
||||||
{d2resource.MonsterEquipment, d2datadict.LoadMonsterEquipment},
|
|
||||||
{d2resource.UniqueAppellation, d2datadict.LoadUniqueAppellations},
|
{d2resource.UniqueAppellation, d2datadict.LoadUniqueAppellations},
|
||||||
{d2resource.MonsterLevel, d2datadict.LoadMonsterLevels},
|
|
||||||
{d2resource.MonsterSound, d2datadict.LoadMonsterSounds},
|
|
||||||
{d2resource.MonsterSequence, d2datadict.LoadMonsterSequences},
|
|
||||||
{d2resource.PlayerClass, d2datadict.LoadPlayerClasses},
|
{d2resource.PlayerClass, d2datadict.LoadPlayerClasses},
|
||||||
{d2resource.MonsterPlacement, d2datadict.LoadMonsterPlacements},
|
|
||||||
{d2resource.ObjectGroup, d2datadict.LoadObjectGroups},
|
{d2resource.ObjectGroup, d2datadict.LoadObjectGroups},
|
||||||
{d2resource.CompCode, d2datadict.LoadComponentCodes},
|
|
||||||
{d2resource.MonsterAI, d2datadict.LoadMonsterAI},
|
|
||||||
{d2resource.RarePrefix, d2datadict.LoadRareItemPrefixRecords},
|
{d2resource.RarePrefix, d2datadict.LoadRareItemPrefixRecords},
|
||||||
{d2resource.RareSuffix, d2datadict.LoadRareItemSuffixRecords},
|
{d2resource.RareSuffix, d2datadict.LoadRareItemSuffixRecords},
|
||||||
}
|
}
|
||||||
|
@ -308,8 +257,6 @@ func (a *App) loadDataDict() error {
|
||||||
entry.loader(data)
|
entry.loader(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
d2datadict.LoadItemEquivalencies() // depends on ItemCommon and ItemTypes
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -683,16 +630,23 @@ func updateInitError(target d2interface.Surface) error {
|
||||||
func (a *App) ToMainMenu() {
|
func (a *App) ToMainMenu() {
|
||||||
buildInfo := d2gamescreen.BuildInfo{Branch: a.gitBranch, Commit: a.gitCommit}
|
buildInfo := d2gamescreen.BuildInfo{Branch: a.gitBranch, Commit: a.gitCommit}
|
||||||
|
|
||||||
mainMenu := d2gamescreen.CreateMainMenu(a, a.asset, a.renderer, a.inputManager, a.audio, a.ui,
|
mainMenu, err := d2gamescreen.CreateMainMenu(a, a.asset, a.renderer, a.inputManager, a.audio, a.ui, buildInfo)
|
||||||
buildInfo)
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
a.screen.SetNextScreen(mainMenu)
|
a.screen.SetNextScreen(mainMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToSelectHero forces the game to transition to the Select Hero (create character) screen
|
// ToSelectHero forces the game to transition to the Select Hero (create character) screen
|
||||||
func (a *App) ToSelectHero(connType d2clientconnectiontype.ClientConnectionType, host string) {
|
func (a *App) ToSelectHero(connType d2clientconnectiontype.ClientConnectionType, host string) {
|
||||||
selectHero := d2gamescreen.CreateSelectHeroClass(a, a.asset, a.renderer, a.audio, a.ui,
|
selectHero, err := d2gamescreen.CreateSelectHeroClass(a, a.asset, a.renderer, a.audio, a.ui, connType, host)
|
||||||
connType, host)
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
a.screen.SetNextScreen(selectHero)
|
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
|
// ToMapEngineTest forces the game to transition to the map engine test screen
|
||||||
func (a *App) ToMapEngineTest(region, level int) {
|
func (a *App) ToMapEngineTest(region, level int) {
|
||||||
met := d2gamescreen.CreateMapEngineTest(region, level, a.asset, a.terminal, a.renderer,
|
met, err := d2gamescreen.CreateMapEngineTest(region, level, a.asset, a.terminal, a.renderer, a.inputManager, a.audio, a.screen)
|
||||||
a.inputManager,
|
if err != nil {
|
||||||
a.audio, a.screen)
|
return
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
a.screen.SetNextScreen(met)
|
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 (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
"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
|
// CreateAudio creates an instance of ebiten's audio provider
|
||||||
func CreateAudio(am *d2asset.AssetManager) (*AudioProvider, error) {
|
func CreateAudio(am *d2asset.AssetManager) (*AudioProvider, error) {
|
||||||
result := &AudioProvider{
|
result := &AudioProvider{
|
||||||
assetManager: am,
|
asset: am,
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -35,7 +34,7 @@ func CreateAudio(am *d2asset.AssetManager) (*AudioProvider, error) {
|
||||||
|
|
||||||
// AudioProvider represents a provider capable of playing audio
|
// AudioProvider represents a provider capable of playing audio
|
||||||
type AudioProvider struct {
|
type AudioProvider struct {
|
||||||
assetManager *d2asset.AssetManager
|
asset *d2asset.AssetManager
|
||||||
audioContext *audio.Context // The Audio context
|
audioContext *audio.Context // The Audio context
|
||||||
bgmAudio *audio.Player // The audio player
|
bgmAudio *audio.Player // The audio player
|
||||||
lastBgm string
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -128,17 +127,17 @@ func (eap *AudioProvider) createSoundEffect(sfx string, context *audio.Context,
|
||||||
|
|
||||||
soundFile := "/data/global/sfx/"
|
soundFile := "/data/global/sfx/"
|
||||||
|
|
||||||
if _, exists := d2datadict.Sounds[sfx]; exists {
|
if _, exists := eap.asset.Records.Sound.Details[sfx]; exists {
|
||||||
soundEntry := d2datadict.Sounds[sfx]
|
soundEntry := eap.asset.Records.Sound.Details[sfx]
|
||||||
soundFile += soundEntry.FileName
|
soundFile += soundEntry.FileName
|
||||||
} else {
|
} else {
|
||||||
soundFile += sfx
|
soundFile += sfx
|
||||||
}
|
}
|
||||||
|
|
||||||
audioData, err := eap.assetManager.LoadFileStream(soundFile)
|
audioData, err := eap.asset.LoadFileStream(soundFile)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
audioData, err = eap.assetManager.LoadFileStream("/data/global/music/" + sfx)
|
audioData, err = eap.asset.LoadFileStream("/data/global/music/" + sfx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -4,7 +4,10 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"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"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,7 +26,7 @@ const originalFPS float64 = 25
|
||||||
// A Sound that can be started and stopped
|
// A Sound that can be started and stopped
|
||||||
type Sound struct {
|
type Sound struct {
|
||||||
effect d2interface.SoundEffect
|
effect d2interface.SoundEffect
|
||||||
entry *d2datadict.SoundEntry
|
entry *d2records.SoundDetailsRecord
|
||||||
volume float64
|
volume float64
|
||||||
vTarget float64
|
vTarget float64
|
||||||
vRate float64
|
vRate float64
|
||||||
|
@ -95,6 +98,7 @@ func (s *Sound) Stop() {
|
||||||
|
|
||||||
// SoundEngine provides functions for playing sounds
|
// SoundEngine provides functions for playing sounds
|
||||||
type SoundEngine struct {
|
type SoundEngine struct {
|
||||||
|
asset *d2asset.AssetManager
|
||||||
provider d2interface.AudioProvider
|
provider d2interface.AudioProvider
|
||||||
timer float64
|
timer float64
|
||||||
accTime float64
|
accTime float64
|
||||||
|
@ -102,8 +106,10 @@ type SoundEngine struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSoundEngine creates a new sound engine
|
// 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{
|
r := SoundEngine{
|
||||||
|
asset: asset,
|
||||||
provider: provider,
|
provider: provider,
|
||||||
sounds: map[*Sound]struct{}{},
|
sounds: map[*Sound]struct{}{},
|
||||||
timer: 1,
|
timer: 1,
|
||||||
|
@ -173,10 +179,10 @@ func (s *SoundEngine) PlaySoundID(id int) *Sound {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
entry := d2datadict.SelectSoundByIndex(id)
|
entry := s.asset.Records.SelectSoundByIndex(id)
|
||||||
|
|
||||||
if entry.GroupSize > 0 {
|
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)
|
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
|
// PlaySoundHandle plays a sound by sounds.txt handle
|
||||||
func (s *SoundEngine) PlaySoundHandle(handle string) *Sound {
|
func (s *SoundEngine) PlaySoundHandle(handle string) *Sound {
|
||||||
sound := d2datadict.Sounds[handle].Index
|
sound := s.asset.Records.Sound.Details[handle].Index
|
||||||
return s.PlaySoundID(sound)
|
return s.PlaySoundID(sound)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,14 @@ package d2audio
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
||||||
)
|
)
|
||||||
|
|
||||||
const assumedFPS = 25
|
const assumedFPS = 25
|
||||||
|
|
||||||
// SoundEnvironment represents the audio environment for map areas
|
// SoundEnvironment represents the audio environment for map areas
|
||||||
type SoundEnvironment struct {
|
type SoundEnvironment struct {
|
||||||
environment *d2datadict.SoundEnvironRecord
|
environment *d2records.SoundEnvironRecord
|
||||||
engine *SoundEngine
|
engine *SoundEngine
|
||||||
bgm *Sound
|
bgm *Sound
|
||||||
ambiance *Sound
|
ambiance *Sound
|
||||||
|
@ -21,7 +21,7 @@ type SoundEnvironment struct {
|
||||||
func NewSoundEnvironment(soundEngine *SoundEngine) SoundEnvironment {
|
func NewSoundEnvironment(soundEngine *SoundEngine) SoundEnvironment {
|
||||||
r := SoundEnvironment{
|
r := SoundEnvironment{
|
||||||
// Start with env NONE
|
// Start with env NONE
|
||||||
environment: d2datadict.SoundEnvirons[0],
|
environment: soundEngine.asset.Records.Sound.Environment[0],
|
||||||
engine: soundEngine,
|
engine: soundEngine,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ func NewSoundEnvironment(soundEngine *SoundEngine) SoundEnvironment {
|
||||||
// SetEnv sets the sound environment using the given record index
|
// SetEnv sets the sound environment using the given record index
|
||||||
func (s *SoundEnvironment) SetEnv(environmentIdx int) {
|
func (s *SoundEnvironment) SetEnv(environmentIdx int) {
|
||||||
if s.environment.Index != environmentIdx {
|
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.environment.Song != newEnv.Song {
|
||||||
if s.bgm != nil {
|
if s.bgm != nil {
|
||||||
|
|
|
@ -4,14 +4,15 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HeroSkill stores additional payload for a skill of a hero.
|
// HeroSkill stores additional payload for a skill of a hero.
|
||||||
type HeroSkill struct {
|
type HeroSkill struct {
|
||||||
*d2datadict.SkillRecord
|
*d2records.SkillRecord
|
||||||
*d2datadict.SkillDescriptionRecord
|
*d2records.SkillDescriptionRecord
|
||||||
SkillPoints int
|
SkillPoints int
|
||||||
|
shallow *shallowHeroSkill
|
||||||
}
|
}
|
||||||
|
|
||||||
// An auxilary struct which only stores the ID of the SkillRecord, instead of the whole SkillRecord and SkillDescrptionRecord.
|
// 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.
|
// UnmarshalJSON overrides the default logic used when the HeroSkill is deserialized from a byte array.
|
||||||
func (hs *HeroSkill) UnmarshalJSON(data []byte) error {
|
func (hs *HeroSkill) UnmarshalJSON(data []byte) error {
|
||||||
shallow := shallowHeroSkill{}
|
shallow := &shallowHeroSkill{}
|
||||||
if err := json.Unmarshal(data, &shallow); err != nil {
|
if err := json.Unmarshal(data, shallow); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hs.SkillRecord = d2datadict.SkillDetails[shallow.SkillID]
|
hs.shallow = shallow
|
||||||
hs.SkillDescriptionRecord = d2datadict.SkillDescriptions[hs.SkillRecord.Skilldesc]
|
|
||||||
hs.SkillPoints = shallow.SkillPoints
|
|
||||||
|
|
||||||
return nil
|
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
|
package d2hero
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HeroStatsState is a serializable state of hero stats.
|
// HeroStatsState is a serializable state of hero stats.
|
||||||
|
@ -35,11 +35,11 @@ type HeroStatsState struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateHeroStatsState generates a running state from a hero stats.
|
// 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{
|
result := HeroStatsState{
|
||||||
Level: 1,
|
Level: 1,
|
||||||
Experience: 0,
|
Experience: 0,
|
||||||
NextLevelExp: d2datadict.GetExperienceBreakpoint(heroClass, 1),
|
NextLevelExp: f.asset.Records.GetExperienceBreakpoint(heroClass, 1),
|
||||||
Strength: classStats.InitStr,
|
Strength: classStats.InitStr,
|
||||||
Dexterity: classStats.InitDex,
|
Dexterity: classStats.InitDex,
|
||||||
Vitality: classStats.InitVit,
|
Vitality: classStats.InitVit,
|
||||||
|
|
|
@ -5,39 +5,4 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// HeroObjects map contains the hero type to CharacterEquipments
|
// HeroObjects map contains the hero type to CharacterEquipments
|
||||||
var HeroObjects map[d2enum.Hero]CharacterEquipment
|
type 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"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package d2inventory
|
package d2inventory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,22 +15,6 @@ type InventoryItemArmor struct {
|
||||||
ArmorClass string `json:"armorClass"`
|
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
|
// GetArmorClass returns the class of the armor
|
||||||
func (v *InventoryItemArmor) GetArmorClass() string {
|
func (v *InventoryItemArmor) GetArmorClass() string {
|
||||||
if v == nil || v.ItemCode == "" {
|
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
|
package d2inventory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,21 +14,6 @@ type InventoryItemMisc struct {
|
||||||
ItemCode string `json:"itemCode"`
|
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
|
// InventoryItemName returns the name of the miscellaneous item
|
||||||
func (v *InventoryItemMisc) InventoryItemName() string {
|
func (v *InventoryItemMisc) InventoryItemName() string {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package d2inventory
|
package d2inventory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,24 +16,6 @@ type InventoryItemWeapon struct {
|
||||||
WeaponClassOffHand string `json:"weaponClassOffHand"`
|
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
|
// GetWeaponClass returns the class of the weapon
|
||||||
func (v *InventoryItemWeapon) GetWeaponClass() string {
|
func (v *InventoryItemWeapon) GetWeaponClass() string {
|
||||||
if v == nil || v.ItemCode == "" {
|
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"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2item"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2item"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats/diablo2stats"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,9 +49,10 @@ const (
|
||||||
var _ d2item.Item = &Item{}
|
var _ d2item.Item = &Item{}
|
||||||
|
|
||||||
type Item struct {
|
type Item struct {
|
||||||
name string
|
factory *ItemFactory
|
||||||
Seed int64
|
name string
|
||||||
rand *rand.Rand // non-global rand instance for re-generating the item
|
Seed int64
|
||||||
|
rand *rand.Rand // non-global rand instance for re-generating the item
|
||||||
|
|
||||||
slotType d2enum.EquippedSlot
|
slotType d2enum.EquippedSlot
|
||||||
|
|
||||||
|
@ -177,18 +179,18 @@ func (i *Item) ItemLevel() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TypeRecord returns the ItemTypeRecord of the item
|
// TypeRecord returns the ItemTypeRecord of the item
|
||||||
func (i *Item) TypeRecord() *d2datadict.ItemTypeRecord {
|
func (i *Item) TypeRecord() *d2records.ItemTypeRecord {
|
||||||
return d2datadict.ItemTypes[i.TypeCode]
|
return i.factory.asset.Records.Item.Types[i.TypeCode]
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommonRecord returns the ItemCommonRecord of the item
|
// CommonRecord returns the ItemCommonRecord of the item
|
||||||
func (i *Item) CommonRecord() *d2datadict.ItemCommonRecord {
|
func (i *Item) CommonRecord() *d2records.ItemCommonRecord {
|
||||||
return d2datadict.CommonItems[i.CommonCode]
|
return i.factory.asset.Records.Item.All[i.CommonCode]
|
||||||
}
|
}
|
||||||
|
|
||||||
// UniqueRecord returns the UniqueItemRecord of the item
|
// UniqueRecord returns the UniqueItemRecord of the item
|
||||||
func (i *Item) UniqueRecord() *d2datadict.UniqueItemRecord {
|
func (i *Item) UniqueRecord() *d2records.UniqueItemRecord {
|
||||||
return d2datadict.UniqueItems[i.UniqueCode]
|
return i.factory.asset.Records.Item.Unique[i.UniqueCode]
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRecord returns the SetRecord of the item
|
// 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
|
// PrefixRecords returns the ItemAffixCommonRecords of the prefixes of the item
|
||||||
func (i *Item) PrefixRecords() []*d2datadict.ItemAffixCommonRecord {
|
func (i *Item) PrefixRecords() []*d2records.ItemAffixCommonRecord {
|
||||||
return affixRecords(i.PrefixCodes, d2datadict.MagicPrefix)
|
return affixRecords(i.PrefixCodes, i.factory.asset.Records.Item.Magic.Prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SuffixRecords returns the ItemAffixCommonRecords of the prefixes of the item
|
// SuffixRecords returns the ItemAffixCommonRecords of the prefixes of the item
|
||||||
func (i *Item) SuffixRecords() []*d2datadict.ItemAffixCommonRecord {
|
func (i *Item) SuffixRecords() []*d2records.ItemAffixCommonRecord {
|
||||||
return affixRecords(i.SuffixCodes, d2datadict.MagicSuffix)
|
return affixRecords(i.SuffixCodes, i.factory.asset.Records.Item.Magic.Suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
func affixRecords(
|
func affixRecords(
|
||||||
fromCodes []string,
|
fromCodes []string,
|
||||||
affixes map[string]*d2datadict.ItemAffixCommonRecord,
|
affixes map[string]*d2records.ItemAffixCommonRecord,
|
||||||
) []*d2datadict.ItemAffixCommonRecord {
|
) []*d2records.ItemAffixCommonRecord {
|
||||||
if len(fromCodes) < 1 {
|
if len(fromCodes) < 1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]*d2datadict.ItemAffixCommonRecord, len(fromCodes))
|
result := make([]*d2records.ItemAffixCommonRecord, len(fromCodes))
|
||||||
|
|
||||||
for idx, code := range fromCodes {
|
for idx, code := range fromCodes {
|
||||||
rec := affixes[code]
|
rec := affixes[code]
|
||||||
|
@ -344,15 +346,19 @@ func (i *Item) pickMagicAffixes(mod DropModifier) {
|
||||||
totalAffixes = numPrefixes + numSuffixes
|
totalAffixes = numPrefixes + numSuffixes
|
||||||
}
|
}
|
||||||
|
|
||||||
i.PrefixCodes = i.pickRandomAffixes(numPrefixes, totalAffixes, d2datadict.MagicPrefix)
|
prefixes := i.factory.asset.Records.Item.Magic.Prefix
|
||||||
i.SuffixCodes = i.pickRandomAffixes(numSuffixes, totalAffixes, d2datadict.MagicSuffix)
|
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)
|
pickedCodes := make([]string, 0)
|
||||||
|
|
||||||
for numPicks := 0; numPicks < max; numPicks++ {
|
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 {
|
if rollPrefix := i.rand.Intn(2); rollPrefix > 0 {
|
||||||
affixCount := len(i.PrefixRecords()) + len(i.SuffixRecords())
|
affixCount := len(i.PrefixRecords()) + len(i.SuffixRecords())
|
||||||
|
@ -506,7 +512,7 @@ func (i *Item) updateItemAttributes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Item) generateAffixProperties(pool PropertyPool) []*Property {
|
func (i *Item) generateAffixProperties(pool PropertyPool) []*Property {
|
||||||
var affixRecords []*d2datadict.ItemAffixCommonRecord
|
var affixRecords []*d2records.ItemAffixCommonRecord
|
||||||
|
|
||||||
switch pool {
|
switch pool {
|
||||||
case PropertyPoolPrefix:
|
case PropertyPoolPrefix:
|
||||||
|
@ -530,7 +536,7 @@ func (i *Item) generateAffixProperties(pool PropertyPool) []*Property {
|
||||||
for modIdx := range affix.Modifiers {
|
for modIdx := range affix.Modifiers {
|
||||||
mod := affix.Modifiers[modIdx]
|
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 {
|
if prop == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -558,14 +564,14 @@ func (i *Item) generateUniqueProperties() []*Property {
|
||||||
paramInt := getNumericComponent(propInfo.Parameter)
|
paramInt := getNumericComponent(propInfo.Parameter)
|
||||||
|
|
||||||
if paramStr != "" {
|
if paramStr != "" {
|
||||||
for skillID := range d2datadict.SkillDetails {
|
for skillID := range i.factory.asset.Records.Skill.Details {
|
||||||
if d2datadict.SkillDetails[skillID].Skill == paramStr {
|
if i.factory.asset.Records.Skill.Details[skillID].Skill == paramStr {
|
||||||
paramInt = skillID
|
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 {
|
if prop == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -592,14 +598,14 @@ func (i *Item) generateSetItemProperties() []*Property {
|
||||||
paramInt := getNumericComponent(setProp.Parameter)
|
paramInt := getNumericComponent(setProp.Parameter)
|
||||||
|
|
||||||
if paramStr != "" {
|
if paramStr != "" {
|
||||||
for skillID := range d2datadict.SkillDetails {
|
for skillID := range i.factory.asset.Records.Skill.Details {
|
||||||
if d2datadict.SkillDetails[skillID].Skill == paramStr {
|
if i.factory.asset.Records.Skill.Details[skillID].Skill == paramStr {
|
||||||
paramInt = skillID
|
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 {
|
if prop == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -687,7 +693,7 @@ func (i *Item) GetStatStrings() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(stats) > 0 {
|
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() })
|
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
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func findMatchingUniqueRecords(icr *d2datadict.ItemCommonRecord) []*d2datadict.UniqueItemRecord {
|
func findMatchingUniqueRecords(icr *d2records.ItemCommonRecord) []*d2datadict.UniqueItemRecord {
|
||||||
result := make([]*d2datadict.UniqueItemRecord, 0)
|
result := make([]*d2datadict.UniqueItemRecord, 0)
|
||||||
|
|
||||||
c1, c2, c3, c4 := icr.Code, icr.NormalCode, icr.UberCode, icr.UltraCode
|
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
|
// 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)
|
result := make([]*d2datadict.SetItemRecord, 0)
|
||||||
|
|
||||||
c1, c2, c3, c4 := icr.Code, icr.NormalCode, icr.UberCode, icr.UltraCode
|
c1, c2, c3, c4 := icr.Code, icr.NormalCode, icr.UberCode, icr.UltraCode
|
||||||
|
@ -735,55 +741,6 @@ func findMatchingSetItemRecords(icr *d2datadict.ItemCommonRecord) []*d2datadict.
|
||||||
return result
|
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
|
// these functions are to satisfy the inventory grid item interface
|
||||||
|
|
||||||
// GetInventoryItemName returns the item name
|
// GetInventoryItemName returns the item name
|
||||||
|
@ -795,8 +752,8 @@ func (i *Item) GetInventoryItemName() string {
|
||||||
func (i *Item) GetInventoryItemType() d2enum.InventoryItemType {
|
func (i *Item) GetInventoryItemType() d2enum.InventoryItemType {
|
||||||
typeCode := i.TypeRecord().Code
|
typeCode := i.TypeRecord().Code
|
||||||
|
|
||||||
armorEquiv := d2datadict.ItemEquivalenciesByTypeCode["armo"]
|
armorEquiv := i.factory.asset.Records.Item.Equivalency["armo"]
|
||||||
weaponEquiv := d2datadict.ItemEquivalenciesByTypeCode["weap"]
|
weaponEquiv := i.factory.asset.Records.Item.Equivalency["weap"]
|
||||||
|
|
||||||
for idx := range armorEquiv {
|
for idx := range armorEquiv {
|
||||||
if armorEquiv[idx].Code == typeCode {
|
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 (
|
import (
|
||||||
"math/rand"
|
"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"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats/diablo2stats"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -67,15 +66,10 @@ const (
|
||||||
fnRandClassSkill = 36
|
fnRandClassSkill = 36
|
||||||
)
|
)
|
||||||
|
|
||||||
// Property is an item property. Properties act as stat initializers, as well as
|
// Property is an item property.
|
||||||
// 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!
|
|
||||||
type Property struct {
|
type Property struct {
|
||||||
record *d2datadict.PropertyRecord
|
factory *ItemFactory
|
||||||
|
record *d2records.PropertyRecord
|
||||||
stats []d2stats.Stat
|
stats []d2stats.Stat
|
||||||
PropertyType PropertyType
|
PropertyType PropertyType
|
||||||
|
|
||||||
|
@ -119,7 +113,7 @@ func (p *Property) init() *Property {
|
||||||
// repeat the previous fn with the same parameters, but for a different stat.
|
// 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) {
|
func (p *Property) eval(propStatIdx, previousFnID int) (stat d2stats.Stat, funcID int) {
|
||||||
pStatRecord := p.record.Stats[propStatIdx]
|
pStatRecord := p.record.Stats[propStatIdx]
|
||||||
iscRecord := d2datadict.ItemStatCosts[pStatRecord.StatCode]
|
iscRecord := p.factory.asset.Records.Item.Stats[pStatRecord.StatCode]
|
||||||
|
|
||||||
funcID = pStatRecord.FunctionID
|
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.
|
// 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
|
// the only special case to handle for this function is for
|
||||||
// property "color", which corresponds to ISC record "item_lightcolor"
|
// property "color", which corresponds to ISC record "item_lightcolor"
|
||||||
// I'm not yet sure how to handle this special case... it is likely
|
// 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)
|
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 ???
|
// fnComputeInteger Dmg-min related ???
|
||||||
|
@ -216,7 +210,7 @@ func (p *Property) fnComputeInteger() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// fnClassSkillTab skilltab skill group ???
|
// 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
|
// from here: https://d2mods.info/forum/kb/viewarticle?a=45
|
||||||
// Amazon
|
// Amazon
|
||||||
// 0 - Bow & Crossbow
|
// 0 - Bow & Crossbow
|
||||||
|
@ -251,11 +245,11 @@ func (p *Property) fnClassSkillTab(iscRecord *d2datadict.ItemStatCostRecord) d2s
|
||||||
classIdx := float64(param / skillTabsPerClass)
|
classIdx := float64(param / skillTabsPerClass)
|
||||||
level := float64(rand.Intn(max-min+1) + min)
|
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 ???
|
// 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
|
var skillID, chance, skillLevel float64
|
||||||
|
|
||||||
switch len(p.inputParams) {
|
switch len(p.inputParams) {
|
||||||
|
@ -267,11 +261,11 @@ func (p *Property) fnProcs(iscRecord *d2datadict.ItemStatCostRecord) d2stats.Sta
|
||||||
skillLevel = float64(p.inputParams[2])
|
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 ???
|
// 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
|
var skillLevel, skillID float64
|
||||||
|
|
||||||
invalidHeroIndex := -1.0
|
invalidHeroIndex := -1.0
|
||||||
|
@ -285,22 +279,22 @@ func (p *Property) fnRandomSkill(iscRecord *d2datadict.ItemStatCostRecord) d2sta
|
||||||
skillID = float64(rand.Intn(max-min+1) + min)
|
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
|
// 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) {
|
switch len(p.inputParams) {
|
||||||
case noValue:
|
case noValue:
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
val := float64(p.inputParams[0])
|
val := float64(p.inputParams[0])
|
||||||
return diablo2stats.NewStat(iscRecord.Name, val)
|
return p.factory.stat.NewStat(iscRecord.Name, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fnChargeRelated Related to charged item.
|
// 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
|
var lvl, skill, charges float64
|
||||||
|
|
||||||
switch len(p.inputParams) {
|
switch len(p.inputParams) {
|
||||||
|
@ -311,7 +305,7 @@ func (p *Property) fnChargeRelated(iscRecord *d2datadict.ItemStatCostRecord) d2s
|
||||||
skill = float64(p.inputParams[0])
|
skill = float64(p.inputParams[0])
|
||||||
charges = float64(p.inputParams[1])
|
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.
|
// fnClassSkills Add to group of skills, group determined by stat ID, uses ValX parameter.
|
||||||
func (p *Property) fnClassSkills(
|
func (p *Property) fnClassSkills(
|
||||||
propStatRecord *d2datadict.PropertyStatRecord,
|
propStatRecord *d2records.PropertyStatRecord,
|
||||||
iscRecord *d2datadict.ItemStatCostRecord,
|
iscRecord *d2records.ItemStatCostRecord,
|
||||||
) d2stats.Stat {
|
) d2stats.Stat {
|
||||||
// in order 0..6
|
// in order 0..6
|
||||||
// Amazon
|
// Amazon
|
||||||
|
@ -355,16 +349,16 @@ func (p *Property) fnClassSkills(
|
||||||
statValue := rand.Intn(max-min+1) + min
|
statValue := rand.Intn(max-min+1) + min
|
||||||
classIdx = propStatRecord.Value
|
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 ???
|
// 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
|
// todo need to implement states
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fnRandClassSkill property applied to character or target monster ???
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,413 +7,419 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:funlen // this just gets mock data ready for the tests
|
var itemStatCosts = map[string]*d2records.ItemStatCostRecord{
|
||||||
func TestStat_InitMockData(t *testing.T) {
|
"strength": {
|
||||||
var itemStatCosts = map[string]*d2datadict.ItemStatCostRecord{
|
Name: "strength",
|
||||||
"strength": {
|
DescFnID: 1,
|
||||||
Name: "strength",
|
DescVal: 1,
|
||||||
DescFnID: 1,
|
DescStrPos: "to Strength",
|
||||||
DescVal: 1,
|
DescStrNeg: "to Strength",
|
||||||
DescStrPos: "to Strength",
|
},
|
||||||
DescStrNeg: "to Strength",
|
"dexterity": {
|
||||||
},
|
Name: "dexterity",
|
||||||
"dexterity": {
|
DescFnID: 1,
|
||||||
Name: "dexterity",
|
DescVal: 1,
|
||||||
DescFnID: 1,
|
DescStrPos: "to Dexterity",
|
||||||
DescVal: 1,
|
DescStrNeg: "to Dexterity",
|
||||||
DescStrPos: "to Dexterity",
|
},
|
||||||
DescStrNeg: "to Dexterity",
|
"vitality": {
|
||||||
},
|
Name: "vitality",
|
||||||
"vitality": {
|
DescFnID: 1,
|
||||||
Name: "vitality",
|
DescVal: 1,
|
||||||
DescFnID: 1,
|
DescStrPos: "to Vitality",
|
||||||
DescVal: 1,
|
DescStrNeg: "to Vitality",
|
||||||
DescStrPos: "to Vitality",
|
},
|
||||||
DescStrNeg: "to Vitality",
|
"energy": {
|
||||||
},
|
Name: "energy",
|
||||||
"energy": {
|
DescFnID: 1,
|
||||||
Name: "energy",
|
DescVal: 1,
|
||||||
DescFnID: 1,
|
DescStrPos: "to Energy",
|
||||||
DescVal: 1,
|
DescStrNeg: "to Energy",
|
||||||
DescStrPos: "to Energy",
|
},
|
||||||
DescStrNeg: "to Energy",
|
"hpregen": {
|
||||||
},
|
Name: "hpregen",
|
||||||
"hpregen": {
|
DescFnID: 1,
|
||||||
Name: "hpregen",
|
DescVal: 2,
|
||||||
DescFnID: 1,
|
DescStrPos: "Replenish Life",
|
||||||
DescVal: 2,
|
DescStrNeg: "Drain Life",
|
||||||
DescStrPos: "Replenish Life",
|
},
|
||||||
DescStrNeg: "Drain Life",
|
"toblock": {
|
||||||
},
|
Name: "toblock",
|
||||||
"toblock": {
|
DescFnID: 2,
|
||||||
Name: "toblock",
|
DescVal: 1,
|
||||||
DescFnID: 2,
|
DescStrPos: "Increased Chance of Blocking",
|
||||||
DescVal: 1,
|
DescStrNeg: "Increased Chance of Blocking",
|
||||||
DescStrPos: "Increased Chance of Blocking",
|
},
|
||||||
DescStrNeg: "Increased Chance of Blocking",
|
"item_absorblight_percent": {
|
||||||
},
|
Name: "item_absorblight_percent",
|
||||||
"item_absorblight_percent": {
|
DescFnID: 2,
|
||||||
Name: "item_absorblight_percent",
|
DescVal: 2,
|
||||||
DescFnID: 2,
|
DescStrPos: "Lightning Absorb",
|
||||||
DescVal: 2,
|
DescStrNeg: "Lightning Absorb",
|
||||||
DescStrPos: "Lightning Absorb",
|
},
|
||||||
DescStrNeg: "Lightning Absorb",
|
"item_maxdurability_percent": {
|
||||||
},
|
Name: "item_maxdurability_percent",
|
||||||
"item_maxdurability_percent": {
|
DescFnID: 2,
|
||||||
Name: "item_maxdurability_percent",
|
DescVal: 2,
|
||||||
DescFnID: 2,
|
DescStrPos: "Increase Maximum Durability",
|
||||||
DescVal: 2,
|
DescStrNeg: "Increase Maximum Durability",
|
||||||
DescStrPos: "Increase Maximum Durability",
|
},
|
||||||
DescStrNeg: "Increase Maximum Durability",
|
"item_restinpeace": {
|
||||||
},
|
Name: "item_restinpeace",
|
||||||
"item_restinpeace": {
|
DescFnID: 3,
|
||||||
Name: "item_restinpeace",
|
DescVal: 0,
|
||||||
DescFnID: 3,
|
DescStrPos: "Slain Monsters Rest in Peace",
|
||||||
DescVal: 0,
|
DescStrNeg: "Slain Monsters Rest in Peace",
|
||||||
DescStrPos: "Slain Monsters Rest in Peace",
|
},
|
||||||
DescStrNeg: "Slain Monsters Rest in Peace",
|
"normal_damage_reduction": {
|
||||||
},
|
Name: "normal_damage_reduction",
|
||||||
"normal_damage_reduction": {
|
DescFnID: 3,
|
||||||
Name: "normal_damage_reduction",
|
DescVal: 2,
|
||||||
DescFnID: 3,
|
DescStrPos: "Damage Reduced by",
|
||||||
DescVal: 2,
|
DescStrNeg: "Damage Reduced by",
|
||||||
DescStrPos: "Damage Reduced by",
|
},
|
||||||
DescStrNeg: "Damage Reduced by",
|
"poisonresist": {
|
||||||
},
|
Name: "poisonresist",
|
||||||
"poisonresist": {
|
DescFnID: 4,
|
||||||
Name: "poisonresist",
|
DescVal: 2,
|
||||||
DescFnID: 4,
|
DescStrPos: "Poison Resist",
|
||||||
DescVal: 2,
|
DescStrNeg: "Poison Resist",
|
||||||
DescStrPos: "Poison Resist",
|
},
|
||||||
DescStrNeg: "Poison Resist",
|
"item_fastermovevelocity": {
|
||||||
},
|
Name: "item_fastermovevelocity",
|
||||||
"item_fastermovevelocity": {
|
DescFnID: 4,
|
||||||
Name: "item_fastermovevelocity",
|
DescVal: 1,
|
||||||
DescFnID: 4,
|
DescStrPos: "Faster Run/Walk",
|
||||||
DescVal: 1,
|
DescStrNeg: "Faster Run/Walk",
|
||||||
DescStrPos: "Faster Run/Walk",
|
},
|
||||||
DescStrNeg: "Faster Run/Walk",
|
"item_howl": {
|
||||||
},
|
Name: "item_howl",
|
||||||
"item_howl": {
|
DescFnID: 5,
|
||||||
Name: "item_howl",
|
DescVal: 2,
|
||||||
DescFnID: 5,
|
DescStrPos: "Hit Causes Monster to Flee",
|
||||||
DescVal: 2,
|
DescStrNeg: "Hit Causes Monster to Flee",
|
||||||
DescStrPos: "Hit Causes Monster to Flee",
|
},
|
||||||
DescStrNeg: "Hit Causes Monster to Flee",
|
"item_hp_perlevel": {
|
||||||
},
|
Name: "item_hp_perlevel",
|
||||||
"item_hp_perlevel": {
|
DescFnID: 6,
|
||||||
Name: "item_hp_perlevel",
|
DescVal: 1,
|
||||||
DescFnID: 6,
|
DescStrPos: "to Life",
|
||||||
DescVal: 1,
|
DescStrNeg: "to Life",
|
||||||
DescStrPos: "to Life",
|
DescStr2: "(Based on Character Level)",
|
||||||
DescStrNeg: "to Life",
|
},
|
||||||
DescStr2: "(Based on Character Level)",
|
"item_resist_ltng_perlevel": {
|
||||||
},
|
Name: "item_resist_ltng_perlevel",
|
||||||
"item_resist_ltng_perlevel": {
|
DescFnID: 7,
|
||||||
Name: "item_resist_ltng_perlevel",
|
DescVal: 2,
|
||||||
DescFnID: 7,
|
DescStrPos: "Lightning Resist",
|
||||||
DescVal: 2,
|
DescStrNeg: "Lightning Resist",
|
||||||
DescStrPos: "Lightning Resist",
|
DescStr2: "(Based on Character Level)",
|
||||||
DescStrNeg: "Lightning Resist",
|
},
|
||||||
DescStr2: "(Based on Character Level)",
|
"item_find_magic_perlevel": {
|
||||||
},
|
Name: "item_find_magic_perlevel",
|
||||||
"item_find_magic_perlevel": {
|
DescFnID: 7,
|
||||||
Name: "item_find_magic_perlevel",
|
DescVal: 1,
|
||||||
DescFnID: 7,
|
DescStrPos: "Better Chance of Getting Magic Items",
|
||||||
DescVal: 1,
|
DescStrNeg: "Better Chance of Getting Magic Items",
|
||||||
DescStrPos: "Better Chance of Getting Magic Items",
|
DescStr2: "(Based on Character Level)",
|
||||||
DescStrNeg: "Better Chance of Getting Magic Items",
|
},
|
||||||
DescStr2: "(Based on Character Level)",
|
"item_armorpercent_perlevel": {
|
||||||
},
|
Name: "item_armorpercent_perlevel",
|
||||||
"item_armorpercent_perlevel": {
|
DescFnID: 8,
|
||||||
Name: "item_armorpercent_perlevel",
|
DescVal: 1,
|
||||||
DescFnID: 8,
|
DescStrPos: "Enhanced Defense",
|
||||||
DescVal: 1,
|
DescStrNeg: "Enhanced Defense",
|
||||||
DescStrPos: "Enhanced Defense",
|
DescStr2: "(Based on Character Level)",
|
||||||
DescStrNeg: "Enhanced Defense",
|
},
|
||||||
DescStr2: "(Based on Character Level)",
|
"item_regenstamina_perlevel": {
|
||||||
},
|
Name: "item_regenstamina_perlevel",
|
||||||
"item_regenstamina_perlevel": {
|
DescFnID: 8,
|
||||||
Name: "item_regenstamina_perlevel",
|
DescVal: 2,
|
||||||
DescFnID: 8,
|
DescStrPos: "Heal Stamina Plus",
|
||||||
DescVal: 2,
|
DescStrNeg: "Heal Stamina Plus",
|
||||||
DescStrPos: "Heal Stamina Plus",
|
DescStr2: "(Based on Character Level)",
|
||||||
DescStrNeg: "Heal Stamina Plus",
|
},
|
||||||
DescStr2: "(Based on Character Level)",
|
"item_thorns_perlevel": {
|
||||||
},
|
Name: "item_thorns_perlevel",
|
||||||
"item_thorns_perlevel": {
|
DescFnID: 9,
|
||||||
Name: "item_thorns_perlevel",
|
DescVal: 2,
|
||||||
DescFnID: 9,
|
DescStrPos: "Attacker Takes Damage of",
|
||||||
DescVal: 2,
|
DescStrNeg: "Attacker Takes Damage of",
|
||||||
DescStrPos: "Attacker Takes Damage of",
|
DescStr2: "(Based on Character Level)",
|
||||||
DescStrNeg: "Attacker Takes Damage of",
|
},
|
||||||
DescStr2: "(Based on Character Level)",
|
"item_replenish_durability": {
|
||||||
},
|
Name: "item_replenish_durability",
|
||||||
"item_replenish_durability": {
|
DescFnID: 11,
|
||||||
Name: "item_replenish_durability",
|
DescVal: 1,
|
||||||
DescFnID: 11,
|
DescStrPos: "Repairs %v durability per second",
|
||||||
DescVal: 1,
|
DescStrNeg: "Repairs %v durability per second",
|
||||||
DescStrPos: "Repairs %v durability per second",
|
DescStr2: "",
|
||||||
DescStrNeg: "Repairs %v durability per second",
|
},
|
||||||
DescStr2: "",
|
"item_stupidity": {
|
||||||
},
|
Name: "item_stupidity",
|
||||||
"item_stupidity": {
|
DescFnID: 12,
|
||||||
Name: "item_stupidity",
|
DescVal: 2,
|
||||||
DescFnID: 12,
|
DescStrPos: "Hit Blinds Target",
|
||||||
DescVal: 2,
|
DescStrNeg: "Hit Blinds Target",
|
||||||
DescStrPos: "Hit Blinds Target",
|
},
|
||||||
DescStrNeg: "Hit Blinds Target",
|
"item_addclassskills": {
|
||||||
},
|
Name: "item_addclassskills",
|
||||||
"item_addclassskills": {
|
DescFnID: 13,
|
||||||
Name: "item_addclassskills",
|
DescVal: 1,
|
||||||
DescFnID: 13,
|
},
|
||||||
DescVal: 1,
|
"item_addskill_tab": {
|
||||||
},
|
Name: "item_addskill_tab",
|
||||||
"item_addskill_tab": {
|
DescFnID: 14,
|
||||||
Name: "item_addskill_tab",
|
DescVal: 1,
|
||||||
DescFnID: 14,
|
},
|
||||||
DescVal: 1,
|
"item_skillonattack": {
|
||||||
},
|
Name: "item_skillonattack",
|
||||||
"item_skillonattack": {
|
DescFnID: 15,
|
||||||
Name: "item_skillonattack",
|
DescVal: 1,
|
||||||
DescFnID: 15,
|
DescStrPos: "%d%% Chance to cast level %d %s on attack",
|
||||||
DescVal: 1,
|
DescStrNeg: "%d%% Chance to cast level %d %s on attack",
|
||||||
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",
|
||||||
"item_aura": {
|
DescFnID: 16,
|
||||||
Name: "item_aura",
|
DescVal: 1,
|
||||||
DescFnID: 16,
|
DescStrPos: "Level %d %s Aura When Equipped",
|
||||||
DescVal: 1,
|
DescStrNeg: "Level %d %s Aura When Equipped",
|
||||||
DescStrPos: "Level %d %s Aura When Equipped",
|
},
|
||||||
DescStrNeg: "Level %d %s Aura When Equipped",
|
"item_fractionaltargetac": {
|
||||||
},
|
Name: "item_fractionaltargetac",
|
||||||
"item_fractionaltargetac": {
|
DescFnID: 20,
|
||||||
Name: "item_fractionaltargetac",
|
DescVal: 1,
|
||||||
DescFnID: 20,
|
DescStrPos: "Target Defense",
|
||||||
DescVal: 1,
|
DescStrNeg: "Target Defense",
|
||||||
DescStrPos: "Target Defense",
|
},
|
||||||
DescStrNeg: "Target Defense",
|
"attack_vs_montype": {
|
||||||
},
|
Name: "item_fractionaltargetac",
|
||||||
"attack_vs_montype": {
|
DescFnID: 22,
|
||||||
Name: "item_fractionaltargetac",
|
DescVal: 1,
|
||||||
DescFnID: 22,
|
DescStrPos: "to Attack Rating versus",
|
||||||
DescVal: 1,
|
DescStrNeg: "to Attack Rating versus",
|
||||||
DescStrPos: "to Attack Rating versus",
|
},
|
||||||
DescStrNeg: "to Attack Rating versus",
|
"item_reanimate": {
|
||||||
},
|
Name: "item_reanimate",
|
||||||
"item_reanimate": {
|
DescFnID: 23,
|
||||||
Name: "item_reanimate",
|
DescVal: 2,
|
||||||
DescFnID: 23,
|
DescStrPos: "Reanimate as:",
|
||||||
DescVal: 2,
|
DescStrNeg: "Reanimate as:",
|
||||||
DescStrPos: "Reanimate as:",
|
},
|
||||||
DescStrNeg: "Reanimate as:",
|
"item_charged_skill": {
|
||||||
},
|
Name: "item_charged_skill",
|
||||||
"item_charged_skill": {
|
DescFnID: 24,
|
||||||
Name: "item_charged_skill",
|
DescVal: 2,
|
||||||
DescFnID: 24,
|
DescStrPos: "(%d/%d Charges)",
|
||||||
DescVal: 2,
|
DescStrNeg: "(%d/%d Charges)",
|
||||||
DescStrPos: "(%d/%d Charges)",
|
},
|
||||||
DescStrNeg: "(%d/%d Charges)",
|
"item_singleskill": {
|
||||||
},
|
Name: "item_singleskill",
|
||||||
"item_singleskill": {
|
DescFnID: 27,
|
||||||
Name: "item_singleskill",
|
DescVal: 0,
|
||||||
DescFnID: 27,
|
},
|
||||||
DescVal: 0,
|
"item_nonclassskill": {
|
||||||
},
|
Name: "item_nonclassskill",
|
||||||
"item_nonclassskill": {
|
DescFnID: 28,
|
||||||
Name: "item_nonclassskill",
|
DescVal: 2,
|
||||||
DescFnID: 28,
|
DescStrPos: "(%d/%d Charges)",
|
||||||
DescVal: 2,
|
DescStrNeg: "(%d/%d Charges)",
|
||||||
DescStrPos: "(%d/%d Charges)",
|
},
|
||||||
DescStrNeg: "(%d/%d Charges)",
|
"item_armor_percent": {
|
||||||
},
|
Name: "item_armor_percent",
|
||||||
"item_armor_percent": {
|
DescFnID: 4,
|
||||||
Name: "item_armor_percent",
|
DescVal: 1,
|
||||||
DescFnID: 4,
|
DescStrPos: "Enhanced Defense",
|
||||||
DescVal: 1,
|
DescStrNeg: "Enhanced Defense",
|
||||||
DescStrPos: "Enhanced Defense",
|
},
|
||||||
DescStrNeg: "Enhanced Defense",
|
"item_fastercastrate": {
|
||||||
},
|
Name: "item_fastercastrate",
|
||||||
"item_fastercastrate": {
|
DescFnID: 4,
|
||||||
Name: "item_fastercastrate",
|
DescVal: 1,
|
||||||
DescFnID: 4,
|
DescStrPos: "Faster Cast Rate",
|
||||||
DescVal: 1,
|
DescStrNeg: "Faster Cast Rate",
|
||||||
DescStrPos: "Faster Cast Rate",
|
},
|
||||||
DescStrNeg: "Faster Cast Rate",
|
"item_skillonlevelup": {
|
||||||
},
|
Name: "item_skillonlevelup",
|
||||||
"item_skillonlevelup": {
|
DescFnID: 15,
|
||||||
Name: "item_skillonlevelup",
|
DescVal: 0,
|
||||||
DescFnID: 15,
|
DescStrPos: "%d%% Chance to cast level %d %s when you Level-Up",
|
||||||
DescVal: 0,
|
DescStrNeg: "%d%% Chance to cast level %d %s when you Level-Up",
|
||||||
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",
|
||||||
"item_numsockets": {
|
},
|
||||||
Name: "item_numsockets",
|
"poisonmindam": {
|
||||||
},
|
Name: "poisonmindam",
|
||||||
"poisonmindam": {
|
DescFnID: 1,
|
||||||
Name: "poisonmindam",
|
DescVal: 1,
|
||||||
DescFnID: 1,
|
DescStrPos: "to Minimum Poison Damage",
|
||||||
DescVal: 1,
|
DescStrNeg: "to Minimum Poison Damage",
|
||||||
DescStrPos: "to Minimum Poison Damage",
|
},
|
||||||
DescStrNeg: "to Minimum Poison Damage",
|
"poisonmaxdam": {
|
||||||
},
|
Name: "poisonmaxdam",
|
||||||
"poisonmaxdam": {
|
DescFnID: 1,
|
||||||
Name: "poisonmaxdam",
|
DescVal: 1,
|
||||||
DescFnID: 1,
|
DescStrPos: "to Maximum Poison Damage",
|
||||||
DescVal: 1,
|
DescStrNeg: "to Maximum Poison Damage",
|
||||||
DescStrPos: "to Maximum Poison Damage",
|
},
|
||||||
DescStrNeg: "to Maximum Poison Damage",
|
"poisonlength": {
|
||||||
},
|
Name: "poisonlength",
|
||||||
"poisonlength": {
|
},
|
||||||
Name: "poisonlength",
|
}
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var charStats = map[d2enum.Hero]*d2datadict.CharStatsRecord{
|
var charStats = map[d2enum.Hero]*d2records.CharStatsRecord{
|
||||||
d2enum.HeroPaladin: {
|
d2enum.HeroPaladin: {
|
||||||
Class: d2enum.HeroPaladin,
|
Class: d2enum.HeroPaladin,
|
||||||
SkillStrAll: "to Paladin Skill Levels",
|
SkillStrAll: "to Paladin Skill Levels",
|
||||||
SkillStrClassOnly: "(Paladin Only)",
|
SkillStrClassOnly: "(Paladin Only)",
|
||||||
SkillStrTab: [3]string{
|
SkillStrTab: [3]string{
|
||||||
"+%d to Combat Skills",
|
"+%d to Combat Skills",
|
||||||
"+%d to Offensive Auras",
|
"+%d to Offensive Auras",
|
||||||
"+%d to Defensive Auras",
|
"+%d to Defensive Auras",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var skillDetails = map[int]*d2datadict.SkillRecord{
|
var skillDetails = map[int]*d2records.SkillRecord{
|
||||||
37: {Skill: "Warmth"},
|
37: {Skill: "Warmth"},
|
||||||
64: {Skill: "Frozen Orb"},
|
64: {Skill: "Frozen Orb"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var monStats = map[string]*d2datadict.MonStatsRecord{
|
var monStats = map[string]*d2records.MonStatsRecord{
|
||||||
"Specter": {NameString: "Specter", ID: 40},
|
"Specter": {NameString: "Specter", ID: 40},
|
||||||
}
|
}
|
||||||
|
|
||||||
properties := map[string]*d2datadict.PropertyRecord{
|
var properties = map[string]*d2records.PropertyRecord{
|
||||||
"allstats": {
|
"allstats": {
|
||||||
Code: "allstats",
|
Code: "allstats",
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
{FunctionID: 1, StatCode: "strength"},
|
{FunctionID: 1, StatCode: "strength"},
|
||||||
{FunctionID: 3, StatCode: "dexterity"},
|
{FunctionID: 3, StatCode: "dexterity"},
|
||||||
{FunctionID: 3, StatCode: "vitality"},
|
{FunctionID: 3, StatCode: "vitality"},
|
||||||
{FunctionID: 3, StatCode: "energy"},
|
{FunctionID: 3, StatCode: "energy"},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"ac%": {
|
},
|
||||||
Code: "ac%",
|
"ac%": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "ac%",
|
||||||
{FunctionID: 2, StatCode: "item_armor_percent"},
|
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": {
|
||||||
"dmg-min": {
|
Code: "dmg-min",
|
||||||
Code: "dmg-min",
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
{FunctionID: 5},
|
||||||
{FunctionID: 5},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"dmg-max": {
|
},
|
||||||
Code: "dmg-max",
|
"dmg-max": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "dmg-max",
|
||||||
{FunctionID: 6},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 6},
|
||||||
},
|
},
|
||||||
"dmg%": {
|
},
|
||||||
Code: "dmg%",
|
"dmg%": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "dmg%",
|
||||||
{FunctionID: 7},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 7},
|
||||||
},
|
},
|
||||||
"cast1": {
|
},
|
||||||
Code: "cast1",
|
"cast1": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "cast1",
|
||||||
{FunctionID: 8, StatCode: "item_fastercastrate"},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 8, StatCode: "item_fastercastrate"},
|
||||||
},
|
},
|
||||||
"skilltab": {
|
},
|
||||||
Code: "skilltab",
|
"skilltab": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "skilltab",
|
||||||
{FunctionID: 10, StatCode: "item_addskill_tab"},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 10, StatCode: "item_addskill_tab"},
|
||||||
},
|
},
|
||||||
"levelup-skill": {
|
},
|
||||||
Code: "levelup-skill",
|
"levelup-skill": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "levelup-skill",
|
||||||
{FunctionID: 11, StatCode: "item_skillonlevelup"},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 11, StatCode: "item_skillonlevelup"},
|
||||||
},
|
},
|
||||||
"skill-rand": {
|
},
|
||||||
Code: "skill-rand",
|
"skill-rand": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "skill-rand",
|
||||||
{FunctionID: 12, StatCode: "item_singleskill"},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 12, StatCode: "item_singleskill"},
|
||||||
},
|
},
|
||||||
"dur%": {
|
},
|
||||||
Code: "dur%",
|
"dur%": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "dur%",
|
||||||
{FunctionID: 13, StatCode: "item_maxdurability_percent"},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 13, StatCode: "item_maxdurability_percent"},
|
||||||
},
|
},
|
||||||
"sock": {
|
},
|
||||||
Code: "sock",
|
"sock": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "sock",
|
||||||
{FunctionID: 14, StatCode: "item_numsockets"},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 14, StatCode: "item_numsockets"},
|
||||||
},
|
},
|
||||||
"dmg-pois": {
|
},
|
||||||
Code: "dmg-pois",
|
"dmg-pois": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "dmg-pois",
|
||||||
{FunctionID: 15, StatCode: "poisonmindam"},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
{FunctionID: 16, StatCode: "poisonmaxdam"},
|
{FunctionID: 15, StatCode: "poisonmindam"},
|
||||||
{FunctionID: 17, StatCode: "poisonlength"},
|
{FunctionID: 16, StatCode: "poisonmaxdam"},
|
||||||
},
|
{FunctionID: 17, StatCode: "poisonlength"},
|
||||||
},
|
},
|
||||||
"charged": {
|
},
|
||||||
Code: "charged",
|
"charged": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "charged",
|
||||||
{FunctionID: 19, StatCode: "item_charged_skill"},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 19, StatCode: "item_charged_skill"},
|
||||||
},
|
},
|
||||||
"indestruct": {
|
},
|
||||||
Code: "indestruct",
|
"indestruct": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "indestruct",
|
||||||
{FunctionID: 20},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 20},
|
||||||
},
|
},
|
||||||
"pal": {
|
},
|
||||||
Code: "pal",
|
"pal": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "pal",
|
||||||
{FunctionID: 21, StatCode: "item_addclassskills", Value: 3},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 21, StatCode: "item_addclassskills", Value: 3},
|
||||||
},
|
},
|
||||||
"oskill": {
|
},
|
||||||
Code: "oskill",
|
"oskill": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "oskill",
|
||||||
{FunctionID: 22, StatCode: "item_nonclassskill"},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 22, StatCode: "item_nonclassskill"},
|
||||||
},
|
},
|
||||||
"ethereal": {
|
},
|
||||||
Code: "ethereal",
|
"ethereal": {
|
||||||
Stats: [7]*d2datadict.PropertyStatRecord{
|
Code: "ethereal",
|
||||||
{FunctionID: 23},
|
Stats: [7]*d2records.PropertyStatRecord{
|
||||||
},
|
{FunctionID: 23},
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
d2datadict.ItemStatCosts = itemStatCosts
|
var testAssetManager *d2asset.AssetManager
|
||||||
d2datadict.CharStats = charStats
|
|
||||||
d2datadict.SkillDetails = skillDetails
|
var testItemFactory *ItemFactory
|
||||||
d2datadict.MonStats = monStats
|
|
||||||
d2datadict.Properties = properties
|
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
|
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 {
|
for testIdx := range tests {
|
||||||
test := &tests[testIdx]
|
test := &tests[testIdx]
|
||||||
prop := NewProperty(test.propKey, test.inputValues...)
|
prop := testItemFactory.NewProperty(test.propKey, test.inputValues...)
|
||||||
|
|
||||||
if prop == nil {
|
if prop == nil {
|
||||||
t.Error("property is nil")
|
t.Error("property is nil")
|
||||||
|
|
|
@ -4,9 +4,10 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
|
"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/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
|
||||||
|
@ -24,17 +25,17 @@ type MapEngine struct {
|
||||||
seed int64 // The map seed
|
seed int64 // The map seed
|
||||||
entities map[string]d2interface.MapEntity // Entities on the map
|
entities map[string]d2interface.MapEntity // Entities on the map
|
||||||
tiles []MapTile
|
tiles []MapTile
|
||||||
size d2geom.Size // Size of the map, in tiles
|
size d2geom.Size // Size of the map, in tiles
|
||||||
levelType d2datadict.LevelTypeRecord // Level type of this map
|
levelType d2records.LevelTypeRecord // Level type of this map
|
||||||
dt1TileData []d2dt1.Tile // DT1 tile data
|
dt1TileData []d2dt1.Tile // DT1 tile data
|
||||||
startSubTileX int // Starting X position
|
startSubTileX int // Starting X position
|
||||||
startSubTileY int // Starting Y position
|
startSubTileY int // Starting Y position
|
||||||
dt1Files []string // List of DS1 strings
|
dt1Files []string // List of DS1 strings
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateMapEngine creates a new instance of the map engine and returns a pointer to it.
|
// CreateMapEngine creates a new instance of the map engine and returns a pointer to it.
|
||||||
func CreateMapEngine(asset *d2asset.AssetManager) *MapEngine {
|
func CreateMapEngine(asset *d2asset.AssetManager) *MapEngine {
|
||||||
entity := d2mapentity.NewMapEntityFactory(asset)
|
entity, _ := d2mapentity.NewMapEntityFactory(asset)
|
||||||
stamp := d2mapstamp.NewStampFactory(asset, entity)
|
stamp := d2mapstamp.NewStampFactory(asset, entity)
|
||||||
|
|
||||||
engine := &MapEngine{
|
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.
|
// ResetMap clears all map and entity data and reloads it from the cached files.
|
||||||
func (m *MapEngine) ResetMap(levelType d2enum.RegionIdType, width, height int) {
|
func (m *MapEngine) ResetMap(levelType d2enum.RegionIdType, width, height int) {
|
||||||
m.entities = make(map[string]d2interface.MapEntity)
|
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.size = d2geom.Size{Width: width, Height: height}
|
||||||
m.tiles = make([]MapTile, width*height)
|
m.tiles = make([]MapTile, width*height)
|
||||||
m.dt1TileData = make([]d2dt1.Tile, 0)
|
m.dt1TileData = make([]d2dt1.Tile, 0)
|
||||||
|
@ -115,7 +116,7 @@ func (m *MapEngine) AddDS1(fileName string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LevelType returns the level type of this map.
|
// LevelType returns the level type of this map.
|
||||||
func (m *MapEngine) LevelType() d2datadict.LevelTypeRecord {
|
func (m *MapEngine) LevelType() d2records.LevelTypeRecord {
|
||||||
return m.levelType
|
return m.levelType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
package d2mapentity
|
package d2mapentity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
||||||
|
|
||||||
uuid "github.com/satori/go.uuid"
|
uuid "github.com/satori/go.uuid"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
|
||||||
|
@ -19,13 +20,31 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewMapEntityFactory creates a MapEntityFactory instance with the given asset manager
|
// NewMapEntityFactory creates a MapEntityFactory instance with the given asset manager
|
||||||
func NewMapEntityFactory(asset *d2asset.AssetManager) *MapEntityFactory {
|
func NewMapEntityFactory(asset *d2asset.AssetManager) (*MapEntityFactory, error) {
|
||||||
return &MapEntityFactory{asset}
|
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
|
// MapEntityFactory creates map entities for the MapEngine
|
||||||
type MapEntityFactory struct {
|
type MapEntityFactory struct {
|
||||||
|
*d2hero.HeroStateFactory
|
||||||
asset *d2asset.AssetManager
|
asset *d2asset.AssetManager
|
||||||
|
item *diablo2item.ItemFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAnimatedEntity creates an instance of AnimatedEntity
|
// 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.
|
// 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,
|
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{
|
layerEquipment := &[d2enum.CompositeTypeMax]string{
|
||||||
d2enum.CompositeTypeHead: equipment.Head.GetArmorClass(),
|
d2enum.CompositeTypeHead: equipment.Head.GetArmorClass(),
|
||||||
d2enum.CompositeTypeTorso: equipment.Torso.GetArmorClass(),
|
d2enum.CompositeTypeTorso: equipment.Torso.GetArmorClass(),
|
||||||
|
@ -59,21 +78,25 @@ func (f *MapEntityFactory) NewPlayer(id, name string, x, y, direction int, heroT
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stats.NextLevelExp = d2datadict.GetExperienceBreakpoint(heroType, stats.Level)
|
stats.NextLevelExp = f.asset.Records.GetExperienceBreakpoint(heroType, stats.Level)
|
||||||
stats.Stamina = stats.MaxStamina
|
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
|
attackSkillID := 0
|
||||||
result := &Player{
|
result := &Player{
|
||||||
mapEntity: newMapEntity(x, y),
|
mapEntity: newMapEntity(x, y),
|
||||||
composite: composite,
|
composite: composite,
|
||||||
Equipment: equipment,
|
Equipment: equipment,
|
||||||
Stats: stats,
|
Stats: heroState.Stats,
|
||||||
Skills: skills,
|
Skills: heroState.Skills,
|
||||||
//TODO: active left & right skill should be loaded from save file instead
|
//TODO: active left & right skill should be loaded from save file instead
|
||||||
LeftSkill: (*skills)[attackSkillID],
|
LeftSkill: heroState.Skills[attackSkillID],
|
||||||
RightSkill: (*skills)[attackSkillID],
|
RightSkill: heroState.Skills[attackSkillID],
|
||||||
name: name,
|
name: name,
|
||||||
Class: heroType,
|
Class: heroType,
|
||||||
//nameLabel: d2ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic),
|
//nameLabel: d2ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic),
|
||||||
isRunToggled: true,
|
isRunToggled: true,
|
||||||
isInTown: 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.
|
// 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(
|
animation, err := f.asset.LoadAnimation(
|
||||||
fmt.Sprintf("%s/%s.dcc", d2resource.MissileData, record.Animation.CelFileName),
|
fmt.Sprintf("%s/%s.dcc", d2resource.MissileData, record.Animation.CelFileName),
|
||||||
d2resource.PaletteUnits,
|
d2resource.PaletteUnits,
|
||||||
|
@ -128,10 +151,10 @@ func (f *MapEntityFactory) NewMissile(x, y int, record *d2datadict.MissileRecord
|
||||||
|
|
||||||
// NewItem creates an item map entity
|
// NewItem creates an item map entity
|
||||||
func (f *MapEntityFactory) NewItem(x, y int, codes ...string) (*Item, error) {
|
func (f *MapEntityFactory) NewItem(x, y int, codes ...string) (*Item, error) {
|
||||||
item := diablo2item.NewItem(codes...)
|
item, err := f.item.NewItem(codes...)
|
||||||
|
|
||||||
if item == nil {
|
if err != nil {
|
||||||
return nil, errors.New(errInvalidItemCodes)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filename := item.CommonRecord().FlippyFile
|
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.
|
// 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{
|
result := &NPC{
|
||||||
mapEntity: newMapEntity(x, y),
|
mapEntity: newMapEntity(x, y),
|
||||||
HasPaths: false,
|
HasPaths: false,
|
||||||
monstatRecord: monstat,
|
monstatRecord: monstat,
|
||||||
monstatEx: d2datadict.MonStats2[monstat.ExtraDataKey],
|
monstatEx: f.asset.Records.Monster.Stats2[monstat.ExtraDataKey],
|
||||||
}
|
}
|
||||||
|
|
||||||
var equipment [16]string
|
var equipment [16]string
|
||||||
|
@ -214,9 +237,9 @@ func (f *MapEntityFactory) NewObject(x, y int, objectRec *d2datadict.ObjectRecor
|
||||||
|
|
||||||
entity.composite = composite
|
entity.composite = composite
|
||||||
|
|
||||||
entity.setMode(d2enum.ObjectAnimationModeNeutral, 0, false)
|
_ = entity.setMode(d2enum.ObjectAnimationModeNeutral, 0, false)
|
||||||
|
|
||||||
initObject(entity)
|
_, _ = initObject(entity)
|
||||||
|
|
||||||
return entity, nil
|
return entity, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,6 @@ import (
|
||||||
// static check that item implements map entity interface
|
// static check that item implements map entity interface
|
||||||
var _ d2interface.MapEntity = &Item{}
|
var _ d2interface.MapEntity = &Item{}
|
||||||
|
|
||||||
const (
|
|
||||||
errInvalidItemCodes = "invalid item codes supplied"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Item is a map entity for an item
|
// Item is a map entity for an item
|
||||||
type Item struct {
|
type Item struct {
|
||||||
*AnimatedEntity
|
*AnimatedEntity
|
||||||
|
|
|
@ -3,15 +3,15 @@ package d2mapentity
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Missile is a simple animated entity representing a projectile,
|
// Missile is a simple animated entity representing a projectile,
|
||||||
// such as a spell or arrow.
|
// such as a spell or arrow.
|
||||||
type Missile struct {
|
type Missile struct {
|
||||||
*AnimatedEntity
|
*AnimatedEntity
|
||||||
record *d2datadict.MissileRecord
|
record *d2records.MissileRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID returns the missile uuid
|
// ID returns the missile uuid
|
||||||
|
|
|
@ -3,7 +3,8 @@ package d2mapentity
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"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/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
||||||
|
@ -21,8 +22,8 @@ type NPC struct {
|
||||||
action int
|
action int
|
||||||
path int
|
path int
|
||||||
repetitions int
|
repetitions int
|
||||||
monstatRecord *d2datadict.MonStatsRecord
|
monstatRecord *d2records.MonStatsRecord
|
||||||
monstatEx *d2datadict.MonStats2Record
|
monstatEx *d2records.MonStats2Record
|
||||||
HasPaths bool
|
HasPaths bool
|
||||||
isDone bool
|
isDone bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ type Player struct {
|
||||||
composite *d2asset.Composite
|
composite *d2asset.Composite
|
||||||
Equipment *d2inventory.CharacterEquipment
|
Equipment *d2inventory.CharacterEquipment
|
||||||
Stats *d2hero.HeroStatsState
|
Stats *d2hero.HeroStatsState
|
||||||
Skills *d2hero.HeroSkillsState
|
Skills map[int]*d2hero.HeroSkill
|
||||||
LeftSkill *d2hero.HeroSkill
|
LeftSkill *d2hero.HeroSkill
|
||||||
RightSkill *d2hero.HeroSkill
|
RightSkill *d2hero.HeroSkill
|
||||||
Class d2enum.Hero
|
Class d2enum.Hero
|
||||||
|
|
|
@ -5,34 +5,24 @@ import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom"
|
"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/d2mapgen/d2wilderness"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapstamp"
|
"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.
|
// GenerateAct1Overworld generates the map and entities for the first town and surrounding area.
|
||||||
func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
|
func (g *MapGenerator) GenerateAct1Overworld() {
|
||||||
rand.Seed(mapEngine.Seed())
|
rand.Seed(g.engine.Seed())
|
||||||
|
|
||||||
wilderness1Details := d2datadict.GetLevelDetails(2)
|
wilderness1Details := g.asset.Records.GetLevelDetails(2)
|
||||||
|
|
||||||
mapEngine.ResetMap(d2enum.RegionAct1Town, 150, 150)
|
g.engine.ResetMap(d2enum.RegionAct1Town, 150, 150)
|
||||||
mapWidth := mapEngine.Size().Width
|
mapWidth := g.engine.Size().Width
|
||||||
mapHeight := mapEngine.Size().Height
|
mapHeight := g.engine.Size().Height
|
||||||
|
|
||||||
townStamp := mapEngine.LoadStamp(d2enum.RegionAct1Town, 1, -1)
|
townStamp := g.engine.LoadStamp(d2enum.RegionAct1Town, 1, -1)
|
||||||
townStamp.RegionPath()
|
townStamp.RegionPath()
|
||||||
townSize := townStamp.Size()
|
townSize := townStamp.Size()
|
||||||
|
|
||||||
|
@ -40,57 +30,59 @@ func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(townStamp.RegionPath(), "E1"):
|
case strings.Contains(townStamp.RegionPath(), "E1"):
|
||||||
mapEngine.PlaceStamp(townStamp, 0, 0)
|
g.engine.PlaceStamp(townStamp, 0, 0)
|
||||||
generateWilderness1TownEast(mapEngine, townSize.Width, 0)
|
g.generateWilderness1TownEast(townSize.Width, 0)
|
||||||
case strings.Contains(townStamp.RegionPath(), "S1"):
|
case strings.Contains(townStamp.RegionPath(), "S1"):
|
||||||
mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, 0)
|
g.engine.PlaceStamp(townStamp, mapWidth-townSize.Width, 0)
|
||||||
rightWaterBorderStamp := loadPreset(mapEngine, d2wilderness.WaterBorderEast, 0)
|
rightWaterBorderStamp := g.loadPreset(d2wilderness.WaterBorderEast, 0)
|
||||||
rightWaterBorderStamp2 := loadPreset(mapEngine, d2wilderness.WaterBorderWest, 0)
|
rightWaterBorderStamp2 := g.loadPreset(d2wilderness.WaterBorderWest, 0)
|
||||||
|
|
||||||
for y := townSize.Height; y < mapHeight-9; y += 9 {
|
for y := townSize.Height; y < mapHeight-9; y += 9 {
|
||||||
mapEngine.PlaceStamp(rightWaterBorderStamp, mapWidth-17, y)
|
g.engine.PlaceStamp(rightWaterBorderStamp, mapWidth-17, y)
|
||||||
mapEngine.PlaceStamp(rightWaterBorderStamp2, mapWidth-9, 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"):
|
case strings.Contains(townStamp.RegionPath(), "W1"):
|
||||||
mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height)
|
g.engine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height)
|
||||||
generateWilderness1TownWest(mapEngine, mapWidth-townSize.Width-wilderness1Details.SizeXNormal, mapHeight-wilderness1Details.SizeYNormal)
|
startX := mapWidth - townSize.Width - wilderness1Details.SizeXNormal
|
||||||
|
startY := mapHeight - wilderness1Details.SizeYNormal
|
||||||
|
g.generateWilderness1TownWest(startX, startY)
|
||||||
default:
|
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) {
|
func (g *MapGenerator) generateWilderness1TownEast(startX, startY int) {
|
||||||
levelDetails := d2datadict.GetLevelDetails(2)
|
levelDetails := g.asset.Records.GetLevelDetails(2)
|
||||||
|
|
||||||
fenceNorthStamp := []*d2mapstamp.Stamp{
|
fenceNorthStamp := []*d2mapstamp.Stamp{
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0),
|
g.loadPreset(d2wilderness.TreeBorderNorth, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 1),
|
g.loadPreset(d2wilderness.TreeBorderNorth, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 2),
|
g.loadPreset(d2wilderness.TreeBorderNorth, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
fenceSouthStamp := []*d2mapstamp.Stamp{
|
fenceSouthStamp := []*d2mapstamp.Stamp{
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 0),
|
g.loadPreset(d2wilderness.TreeBorderSouth, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 1),
|
g.loadPreset(d2wilderness.TreeBorderSouth, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 2),
|
g.loadPreset(d2wilderness.TreeBorderSouth, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
fenceWestStamp := []*d2mapstamp.Stamp{
|
fenceWestStamp := []*d2mapstamp.Stamp{
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderWest, 0),
|
g.loadPreset(d2wilderness.TreeBorderWest, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderWest, 1),
|
g.loadPreset(d2wilderness.TreeBorderWest, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderWest, 2),
|
g.loadPreset(d2wilderness.TreeBorderWest, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
fenceEastStamp := []*d2mapstamp.Stamp{
|
fenceEastStamp := []*d2mapstamp.Stamp{
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderEast, 0),
|
g.loadPreset(d2wilderness.TreeBorderEast, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderEast, 1),
|
g.loadPreset(d2wilderness.TreeBorderEast, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderEast, 2),
|
g.loadPreset(d2wilderness.TreeBorderEast, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
fenceSouthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthWest, 0)
|
fenceSouthWestStamp := g.loadPreset(d2wilderness.TreeBorderSouthWest, 0)
|
||||||
fenceNorthEastStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthEast, 0)
|
fenceNorthEastStamp := g.loadPreset(d2wilderness.TreeBorderNorthEast, 0)
|
||||||
fenceSouthEastStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthEast, 0)
|
fenceSouthEastStamp := g.loadPreset(d2wilderness.TreeBorderSouthEast, 0)
|
||||||
fenceWestEdge := loadPreset(mapEngine, d2wilderness.TreeBoxNorthEast, 0)
|
fenceWestEdge := g.loadPreset(d2wilderness.TreeBoxNorthEast, 0)
|
||||||
|
|
||||||
areaRect := d2geom.Rectangle{
|
areaRect := d2geom.Rectangle{
|
||||||
Left: startX,
|
Left: startX,
|
||||||
|
@ -98,54 +90,57 @@ func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, start
|
||||||
Width: levelDetails.SizeXNormal,
|
Width: levelDetails.SizeXNormal,
|
||||||
Height: levelDetails.SizeYNormal - 3,
|
Height: levelDetails.SizeYNormal - 3,
|
||||||
}
|
}
|
||||||
generateWilderness1Contents(mapEngine, areaRect)
|
|
||||||
|
g.generateWilderness1Contents(areaRect)
|
||||||
|
|
||||||
// Draw the north and south fence
|
// Draw the north and south fence
|
||||||
for i := 0; i < 9; i++ {
|
for i := 0; i < 9; i++ {
|
||||||
mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9), startY)
|
g.engine.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(fenceSouthStamp[rand.Intn(3)], startX+(i*9),
|
||||||
|
startY+(levelDetails.SizeYNormal+6))
|
||||||
}
|
}
|
||||||
|
|
||||||
// West fence
|
// West fence
|
||||||
for i := 1; i < 6; i++ {
|
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
|
// East Fence
|
||||||
for i := 1; i < 10; i++ {
|
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)
|
g.engine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal+6)
|
||||||
mapEngine.PlaceStamp(fenceWestEdge, startX, startY+(levelDetails.SizeYNormal-3)-45)
|
g.engine.PlaceStamp(fenceWestEdge, startX, startY+(levelDetails.SizeYNormal-3)-45)
|
||||||
mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal, startY)
|
g.engine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal, startY)
|
||||||
mapEngine.PlaceStamp(fenceSouthEastStamp, startX+levelDetails.SizeXNormal, startY+levelDetails.SizeYNormal+6)
|
g.engine.PlaceStamp(fenceSouthEastStamp, startX+levelDetails.SizeXNormal, startY+levelDetails.SizeYNormal+6)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateWilderness1TownSouth(mapEngine *d2mapengine.MapEngine, startX, startY int) {
|
func (g *MapGenerator) generateWilderness1TownSouth(startX, startY int) {
|
||||||
levelDetails := d2datadict.GetLevelDetails(2)
|
levelDetails := g.asset.Records.GetLevelDetails(2)
|
||||||
|
|
||||||
fenceNorthStamp := []*d2mapstamp.Stamp{
|
fenceNorthStamp := []*d2mapstamp.Stamp{
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0),
|
g.loadPreset(d2wilderness.TreeBorderNorth, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 1),
|
g.loadPreset(d2wilderness.TreeBorderNorth, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 2),
|
g.loadPreset(d2wilderness.TreeBorderNorth, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
fenceWestStamp := []*d2mapstamp.Stamp{
|
fenceWestStamp := []*d2mapstamp.Stamp{
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderWest, 0),
|
g.loadPreset(d2wilderness.TreeBorderWest, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderWest, 1),
|
g.loadPreset(d2wilderness.TreeBorderWest, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderWest, 2),
|
g.loadPreset(d2wilderness.TreeBorderWest, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
fenceSouthStamp := []*d2mapstamp.Stamp{
|
fenceSouthStamp := []*d2mapstamp.Stamp{
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 0),
|
g.loadPreset(d2wilderness.TreeBorderSouth, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 1),
|
g.loadPreset(d2wilderness.TreeBorderSouth, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 2),
|
g.loadPreset(d2wilderness.TreeBorderSouth, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
fenceNorthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthWest, 0)
|
fenceNorthWestStamp := g.loadPreset(d2wilderness.TreeBorderNorthWest, 0)
|
||||||
fenceSouthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthWest, 0)
|
fenceSouthWestStamp := g.loadPreset(d2wilderness.TreeBorderSouthWest, 0)
|
||||||
fenceWaterBorderSouthEast := loadPreset(mapEngine, d2wilderness.WaterBorderEast, 1)
|
fenceWaterBorderSouthEast := g.loadPreset(d2wilderness.WaterBorderEast, 1)
|
||||||
|
|
||||||
areaRect := d2geom.Rectangle{
|
areaRect := d2geom.Rectangle{
|
||||||
Left: startX + 2,
|
Left: startX + 2,
|
||||||
|
@ -153,84 +148,84 @@ func generateWilderness1TownSouth(mapEngine *d2mapengine.MapEngine, startX, star
|
||||||
Width: levelDetails.SizeXNormal - 2,
|
Width: levelDetails.SizeXNormal - 2,
|
||||||
Height: levelDetails.SizeYNormal - 3,
|
Height: levelDetails.SizeYNormal - 3,
|
||||||
}
|
}
|
||||||
generateWilderness1Contents(mapEngine, areaRect)
|
g.generateWilderness1Contents(areaRect)
|
||||||
|
|
||||||
// Draw the north fence
|
// Draw the north fence
|
||||||
for i := 0; i < 4; i++ {
|
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
|
// Draw the west fence
|
||||||
for i := 0; i < 8; i++ {
|
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
|
// Draw the south fence
|
||||||
for i := 1; i < 9; i++ {
|
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)
|
g.engine.PlaceStamp(fenceNorthWestStamp, startX, startY-6)
|
||||||
mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+(8*9)+3)
|
g.engine.PlaceStamp(fenceSouthWestStamp, startX, startY+(8*9)+3)
|
||||||
mapEngine.PlaceStamp(fenceWaterBorderSouthEast, startX+(9*9)-4, startY+(8*9)+1)
|
g.engine.PlaceStamp(fenceWaterBorderSouthEast, startX+(9*9)-4, startY+(8*9)+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, startY int) {
|
func (g *MapGenerator) generateWilderness1TownWest(startX, startY int) {
|
||||||
levelDetails := d2datadict.GetLevelDetails(2)
|
levelDetails := g.asset.Records.GetLevelDetails(2)
|
||||||
|
|
||||||
fenceEastEdge := loadPreset(mapEngine, d2wilderness.TreeBoxSouthWest, 0)
|
fenceEastEdge := g.loadPreset(d2wilderness.TreeBoxSouthWest, 0)
|
||||||
fenceNorthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthWest, 0)
|
fenceNorthWestStamp := g.loadPreset(d2wilderness.TreeBorderNorthWest, 0)
|
||||||
fenceNorthEastStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthEast, 0)
|
fenceNorthEastStamp := g.loadPreset(d2wilderness.TreeBorderNorthEast, 0)
|
||||||
fenceSouthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderSouthWest, 0)
|
fenceSouthWestStamp := g.loadPreset(d2wilderness.TreeBorderSouthWest, 0)
|
||||||
|
|
||||||
fenceSouthStamp := []*d2mapstamp.Stamp{
|
fenceSouthStamp := []*d2mapstamp.Stamp{
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 0),
|
g.loadPreset(d2wilderness.TreeBorderSouth, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 1),
|
g.loadPreset(d2wilderness.TreeBorderSouth, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderSouth, 2),
|
g.loadPreset(d2wilderness.TreeBorderSouth, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
fenceNorthStamp := []*d2mapstamp.Stamp{
|
fenceNorthStamp := []*d2mapstamp.Stamp{
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0),
|
g.loadPreset(d2wilderness.TreeBorderNorth, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 1),
|
g.loadPreset(d2wilderness.TreeBorderNorth, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 2),
|
g.loadPreset(d2wilderness.TreeBorderNorth, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
fenceEastStamp := []*d2mapstamp.Stamp{
|
fenceEastStamp := []*d2mapstamp.Stamp{
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderEast, 0),
|
g.loadPreset(d2wilderness.TreeBorderEast, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderEast, 1),
|
g.loadPreset(d2wilderness.TreeBorderEast, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderEast, 2),
|
g.loadPreset(d2wilderness.TreeBorderEast, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
fenceWestStamp := []*d2mapstamp.Stamp{
|
fenceWestStamp := []*d2mapstamp.Stamp{
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderWest, 0),
|
g.loadPreset(d2wilderness.TreeBorderWest, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderWest, 1),
|
g.loadPreset(d2wilderness.TreeBorderWest, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.TreeBorderWest, 2),
|
g.loadPreset(d2wilderness.TreeBorderWest, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the north and south fences
|
// Draw the north and south fences
|
||||||
for i := 0; i < 9; i++ {
|
for i := 0; i < 9; i++ {
|
||||||
if i > 0 && i < 8 {
|
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
|
// Draw the east fence
|
||||||
for i := 0; i < 6; i++ {
|
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
|
// Draw the west fence
|
||||||
for i := 0; i < 9; i++ {
|
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
|
// Draw the west fence
|
||||||
mapEngine.PlaceStamp(fenceEastEdge, startX+levelDetails.SizeXNormal-9, startY+39)
|
g.engine.PlaceStamp(fenceEastEdge, startX+levelDetails.SizeXNormal-9, startY+39)
|
||||||
mapEngine.PlaceStamp(fenceNorthWestStamp, startX, startY-15)
|
g.engine.PlaceStamp(fenceNorthWestStamp, startX, startY-15)
|
||||||
mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal-12)
|
g.engine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal-12)
|
||||||
mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal-9, startY-15)
|
g.engine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal-9, startY-15)
|
||||||
|
|
||||||
areaRect := d2geom.Rectangle{
|
areaRect := d2geom.Rectangle{
|
||||||
Left: startX + 9,
|
Left: startX + 9,
|
||||||
|
@ -238,13 +233,13 @@ func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, start
|
||||||
Width: levelDetails.SizeXNormal - 9,
|
Width: levelDetails.SizeXNormal - 9,
|
||||||
Height: levelDetails.SizeYNormal - 2,
|
Height: levelDetails.SizeYNormal - 2,
|
||||||
}
|
}
|
||||||
generateWilderness1Contents(mapEngine, areaRect)
|
g.generateWilderness1Contents(areaRect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2geom.Rectangle) {
|
func (g *MapGenerator) generateWilderness1Contents(rect d2geom.Rectangle) {
|
||||||
levelDetails := d2datadict.GetLevelDetails(2)
|
levelDetails := g.asset.Records.GetLevelDetails(2)
|
||||||
|
|
||||||
denOfEvil := loadPreset(mapEngine, d2wilderness.DenOfEvilEntrance, 0)
|
denOfEvil := g.loadPreset(d2wilderness.DenOfEvilEntrance, 0)
|
||||||
denOfEvilLoc := d2geom.Point{
|
denOfEvilLoc := d2geom.Point{
|
||||||
X: rect.Left + (rect.Width / 2) + rand.Intn(10),
|
X: rect.Left + (rect.Width / 2) + rand.Intn(10),
|
||||||
Y: rect.Top + (rect.Height / 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
|
// Fill in the grass
|
||||||
for y := 0; y < rect.Height; y++ {
|
for y := 0; y < rect.Height; y++ {
|
||||||
for x := 0; x < rect.Width; x++ {
|
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.RegionType = d2enum.RegionIdType(levelDetails.LevelType)
|
||||||
tile.Components.Floors = []d2ds1.FloorShadowRecord{{Prop1: 1, Style: 0, Sequence: 0}} // wildernessGrass
|
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{
|
stuff := []*d2mapstamp.Stamp{
|
||||||
loadPreset(mapEngine, d2wilderness.StoneFill1, 0),
|
g.loadPreset(d2wilderness.StoneFill1, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.StoneFill1, 1),
|
g.loadPreset(d2wilderness.StoneFill1, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.StoneFill1, 2),
|
g.loadPreset(d2wilderness.StoneFill1, 2),
|
||||||
loadPreset(mapEngine, d2wilderness.StoneFill2, 0),
|
g.loadPreset(d2wilderness.StoneFill2, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.StoneFill2, 1),
|
g.loadPreset(d2wilderness.StoneFill2, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.StoneFill2, 2),
|
g.loadPreset(d2wilderness.StoneFill2, 2),
|
||||||
loadPreset(mapEngine, d2wilderness.Cottages1, 0),
|
g.loadPreset(d2wilderness.Cottages1, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.Cottages1, 1),
|
g.loadPreset(d2wilderness.Cottages1, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.Cottages1, 2),
|
g.loadPreset(d2wilderness.Cottages1, 2),
|
||||||
loadPreset(mapEngine, d2wilderness.Cottages1, 3),
|
g.loadPreset(d2wilderness.Cottages1, 3),
|
||||||
loadPreset(mapEngine, d2wilderness.Cottages1, 4),
|
g.loadPreset(d2wilderness.Cottages1, 4),
|
||||||
loadPreset(mapEngine, d2wilderness.Cottages1, 5),
|
g.loadPreset(d2wilderness.Cottages1, 5),
|
||||||
loadPreset(mapEngine, d2wilderness.FallenCamp1, 0),
|
g.loadPreset(d2wilderness.FallenCamp1, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.FallenCamp1, 1),
|
g.loadPreset(d2wilderness.FallenCamp1, 1),
|
||||||
loadPreset(mapEngine, d2wilderness.FallenCamp1, 2),
|
g.loadPreset(d2wilderness.FallenCamp1, 2),
|
||||||
loadPreset(mapEngine, d2wilderness.FallenCamp1, 3),
|
g.loadPreset(d2wilderness.FallenCamp1, 3),
|
||||||
loadPreset(mapEngine, d2wilderness.Pond, 0),
|
g.loadPreset(d2wilderness.Pond, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.SwampFill1, 0),
|
g.loadPreset(d2wilderness.SwampFill1, 0),
|
||||||
loadPreset(mapEngine, d2wilderness.SwampFill2, 0),
|
g.loadPreset(d2wilderness.SwampFill2, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
mapEngine.PlaceStamp(denOfEvil, denOfEvilLoc.X, denOfEvilLoc.Y)
|
g.engine.PlaceStamp(denOfEvil, denOfEvilLoc.X, denOfEvilLoc.Y)
|
||||||
|
|
||||||
numPlaced := 0
|
numPlaced := 0
|
||||||
for numPlaced < 25 {
|
for numPlaced < 25 {
|
||||||
|
@ -295,34 +290,9 @@ func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2geom.R
|
||||||
Height: stamp.Size().Height,
|
Height: stamp.Size().Height,
|
||||||
}
|
}
|
||||||
|
|
||||||
if areaEmpty(mapEngine, stampRect) {
|
if areaEmpty(g.engine, stampRect) {
|
||||||
mapEngine.PlaceStamp(stamp, stampRect.Left, stampRect.Top)
|
g.engine.PlaceStamp(stamp, stampRect.Left, stampRect.Top)
|
||||||
numPlaced++
|
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/d2core/d2map/d2mapentity"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
|
||||||
|
@ -25,10 +24,11 @@ type StampFactory struct {
|
||||||
// LoadStamp loads the Stamp data from file.
|
// LoadStamp loads the Stamp data from file.
|
||||||
func (f *StampFactory) LoadStamp(levelType d2enum.RegionIdType, levelPreset, fileIndex int) *Stamp {
|
func (f *StampFactory) LoadStamp(levelType d2enum.RegionIdType, levelPreset, fileIndex int) *Stamp {
|
||||||
stamp := &Stamp{
|
stamp := &Stamp{
|
||||||
|
factory: f,
|
||||||
entity: f.entity,
|
entity: f.entity,
|
||||||
regionID: levelType,
|
regionID: levelType,
|
||||||
levelType: d2datadict.LevelTypes[levelType],
|
levelType: *f.asset.Records.Level.Types[levelType],
|
||||||
levelPreset: d2datadict.LevelPresets[levelPreset],
|
levelPreset: f.asset.Records.Level.Presets[levelPreset],
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, levelTypeDt1 := range &stamp.levelType.Files {
|
for _, levelTypeDt1 := range &stamp.levelType.Files {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2path"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2path"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -19,13 +20,14 @@ const (
|
||||||
|
|
||||||
// Stamp represents a pre-fabricated map stamp that can be placed on a map.
|
// Stamp represents a pre-fabricated map stamp that can be placed on a map.
|
||||||
type Stamp struct {
|
type Stamp struct {
|
||||||
|
factory *StampFactory
|
||||||
entity *d2mapentity.MapEntityFactory
|
entity *d2mapentity.MapEntityFactory
|
||||||
regionPath string // The file path of the region
|
regionPath string // The file path of the region
|
||||||
regionID d2enum.RegionIdType
|
regionID d2enum.RegionIdType
|
||||||
levelType d2datadict.LevelTypeRecord // The level type id for this stamp
|
levelType d2records.LevelTypeRecord // The level type id for this stamp
|
||||||
levelPreset d2datadict.LevelPresetRecord // The level preset id for this stamp
|
levelPreset d2records.LevelPresetRecord // The level preset id for this stamp
|
||||||
tiles []d2dt1.Tile // The tiles contained on this stamp
|
tiles []d2dt1.Tile // The tiles contained on this stamp
|
||||||
ds1 *d2ds1.DS1 // The backing DS1 file for this stamp
|
ds1 *d2ds1.DS1 // The backing DS1 file for this stamp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns the size of the stamp in tiles.
|
// 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.
|
// LevelPreset returns the level preset ID.
|
||||||
func (mr *Stamp) LevelPreset() d2datadict.LevelPresetRecord {
|
func (mr *Stamp) LevelPreset() d2records.LevelPresetRecord {
|
||||||
return mr.levelPreset
|
return mr.levelPreset
|
||||||
}
|
}
|
||||||
|
|
||||||
// LevelType returns the level type ID.
|
// LevelType returns the level type ID.
|
||||||
func (mr *Stamp) LevelType() d2datadict.LevelTypeRecord {
|
func (mr *Stamp) LevelType() d2records.LevelTypeRecord {
|
||||||
return mr.levelType
|
return mr.levelType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +78,8 @@ func (mr *Stamp) Entities(tileOffsetX, tileOffsetY int) []d2interface.MapEntity
|
||||||
|
|
||||||
for _, object := range mr.ds1.Objects {
|
for _, object := range mr.ds1.Objects {
|
||||||
if object.Type == int(d2enum.ObjectTypeCharacter) {
|
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.
|
// If monstat is nil here it is a place_ type object, idk how to handle those yet.
|
||||||
// (See monpreset and monplace txts for reference)
|
// (See monpreset and monplace txts for reference)
|
||||||
if monstat != nil {
|
if monstat != nil {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package d2records
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ import (
|
||||||
|
|
||||||
func missilesLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
func missilesLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
||||||
records := make(Missiles)
|
records := make(Missiles)
|
||||||
|
r.missilesByName = make(missilesByName)
|
||||||
|
|
||||||
for d.Next() {
|
for d.Next() {
|
||||||
record := &MissileRecord{
|
record := &MissileRecord{
|
||||||
|
@ -295,6 +297,7 @@ func missilesLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
records[record.Id] = record
|
records[record.Id] = record
|
||||||
|
r.missilesByName[sanitizeMissilesKey(record.Name)] = record
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.Err != nil {
|
if d.Err != nil {
|
||||||
|
@ -307,3 +310,7 @@ func missilesLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
||||||
|
|
||||||
return nil
|
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
|
// Missiles stores all of the MissileRecords
|
||||||
type Missiles map[int]*MissileRecord
|
type Missiles map[int]*MissileRecord
|
||||||
|
|
||||||
|
type missilesByName map[string]*MissileRecord
|
||||||
|
|
||||||
// MissileCalcParam is a calculation parameter for a missile
|
// MissileCalcParam is a calculation parameter for a missile
|
||||||
type MissileCalcParam struct {
|
type MissileCalcParam struct {
|
||||||
Param int
|
Param int
|
||||||
|
|
|
@ -91,6 +91,7 @@ type RecordManager struct {
|
||||||
Warp LevelWarps
|
Warp LevelWarps
|
||||||
}
|
}
|
||||||
Missiles
|
Missiles
|
||||||
|
missilesByName
|
||||||
Monster struct {
|
Monster struct {
|
||||||
AI MonsterAI
|
AI MonsterAI
|
||||||
Equipment MonsterEquipment
|
Equipment MonsterEquipment
|
||||||
|
@ -274,6 +275,17 @@ func (r *RecordManager) GetExperienceBreakpoint(heroType d2enum.Hero, level int)
|
||||||
return r.Character.Experience[level].HeroBreakpoints[heroType]
|
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
|
// LevelPreset looks up a LevelPresetRecord by ID
|
||||||
func (r *RecordManager) LevelPreset(id int) LevelPresetRecord {
|
func (r *RecordManager) LevelPreset(id int) LevelPresetRecord {
|
||||||
for i := 0; i < len(r.Level.Presets); i++ {
|
for i := 0; i < len(r.Level.Presets); i++ {
|
||||||
|
@ -371,3 +383,19 @@ func (r *RecordManager) SelectSoundByIndex(index int) *SoundDetailsRecord {
|
||||||
|
|
||||||
return nil
|
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("SkillColumn"),
|
||||||
d.String("ListRow"),
|
d.String("ListRow"),
|
||||||
d.String("ListPool"),
|
d.String("ListPool"),
|
||||||
d.String("IconCel"),
|
d.Number("IconCel"),
|
||||||
d.String("str name"),
|
d.String("str name"),
|
||||||
d.String("str short"),
|
d.String("str short"),
|
||||||
d.String("str long"),
|
d.String("str long"),
|
||||||
|
|
|
@ -14,7 +14,7 @@ type SkillDescriptionRecord struct {
|
||||||
SkillColumn string // SkillColumn
|
SkillColumn string // SkillColumn
|
||||||
ListRow string // ListRow
|
ListRow string // ListRow
|
||||||
ListPool string // ListPool
|
ListPool string // ListPool
|
||||||
IconCel string // IconCel
|
IconCel int // IconCel
|
||||||
NameKey string // str name
|
NameKey string // str name
|
||||||
ShortKey string // str short
|
ShortKey string // str short
|
||||||
LongKey string // str long
|
LongKey string // str long
|
||||||
|
|
|
@ -116,6 +116,10 @@ func uniqueItemsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if record.Name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
records[record.Name] = record
|
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"
|
"fmt"
|
||||||
"strings"
|
"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/d2common/d2fileformats/d2tbl"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
|
||||||
)
|
)
|
||||||
|
@ -43,8 +44,9 @@ const (
|
||||||
// diablo2Stat is an implementation of an OpenDiablo2 Stat, with a set of values.
|
// diablo2Stat is an implementation of an OpenDiablo2 Stat, with a set of values.
|
||||||
// It is pretty tightly coupled to the data files for d2
|
// It is pretty tightly coupled to the data files for d2
|
||||||
type diablo2Stat struct {
|
type diablo2Stat struct {
|
||||||
record *d2datadict.ItemStatCostRecord
|
factory *StatFactory
|
||||||
values []d2stats.StatValue
|
record *d2records.ItemStatCostRecord
|
||||||
|
values []d2stats.StatValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// depending on the stat record, sets up the proper number of values,
|
// 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
|
// 0-value descfnID field but need to store values
|
||||||
s.values = make([]d2stats.StatValue, len(numbers))
|
s.values = make([]d2stats.StatValue, len(numbers))
|
||||||
for idx := range s.values {
|
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:
|
case 1:
|
||||||
// +31 to Strength
|
// +31 to Strength
|
||||||
// Replenish Life +20 || Drain Life -8
|
// Replenish Life +20 || Drain Life -8
|
||||||
s.values = make([]d2stats.StatValue, oneValue)
|
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:
|
case 2:
|
||||||
// +16% Increased Chance of Blocking
|
// +16% Increased Chance of Blocking
|
||||||
// Lightning Absorb +10%
|
// Lightning Absorb +10%
|
||||||
s.values = make([]d2stats.StatValue, oneValue)
|
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:
|
case 3:
|
||||||
// Damage Reduced by 25
|
// Damage Reduced by 25
|
||||||
// Slain Monsters Rest in Peace
|
// Slain Monsters Rest in Peace
|
||||||
s.values = make([]d2stats.StatValue, oneValue)
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
s.values[0] = NewValue(intVal, sum)
|
s.values[0] = s.factory.NewValue(intVal, sum)
|
||||||
case 4:
|
case 4:
|
||||||
// Poison Resist +25%
|
// Poison Resist +25%
|
||||||
// +25% Faster Run/Walk
|
// +25% Faster Run/Walk
|
||||||
s.values = make([]d2stats.StatValue, oneValue)
|
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:
|
case 5:
|
||||||
// Hit Causes Monster to Flee 25%
|
// Hit Causes Monster to Flee 25%
|
||||||
s.values = make([]d2stats.StatValue, oneValue)
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
s.values[0] = NewValue(intVal, sum)
|
s.values[0] = s.factory.NewValue(intVal, sum)
|
||||||
s.values[0].SetStringer(stringerIntPercentageUnsigned)
|
s.values[0].SetStringer(s.factory.stringerIntPercentageUnsigned)
|
||||||
case 6:
|
case 6:
|
||||||
// +25 to Life (Based on Character Level)
|
// +25 to Life (Based on Character Level)
|
||||||
s.values = make([]d2stats.StatValue, oneValue)
|
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:
|
case 7:
|
||||||
// Lightning Resist +25% (Based on Character Level)
|
// Lightning Resist +25% (Based on Character Level)
|
||||||
// +25% Better Chance of Getting Magic Items (Based on Character Level)
|
// +25% Better Chance of Getting Magic Items (Based on Character Level)
|
||||||
s.values = make([]d2stats.StatValue, oneValue)
|
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:
|
case 8:
|
||||||
// +25% Enhanced Defense (Based on Character Level)
|
// +25% Enhanced Defense (Based on Character Level)
|
||||||
// Heal Stamina Plus +25% (Based on Character Level)
|
// Heal Stamina Plus +25% (Based on Character Level)
|
||||||
s.values = make([]d2stats.StatValue, oneValue)
|
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:
|
case 9:
|
||||||
// Attacker Takes Damage of 25 (Based on Character Level)
|
// Attacker Takes Damage of 25 (Based on Character Level)
|
||||||
s.values = make([]d2stats.StatValue, oneValue)
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
s.values[0] = NewValue(intVal, sum)
|
s.values[0] = s.factory.NewValue(intVal, sum)
|
||||||
case 11:
|
case 11:
|
||||||
// Repairs 2 durability per second
|
// Repairs 2 durability per second
|
||||||
s.values = make([]d2stats.StatValue, oneValue)
|
s.values = make([]d2stats.StatValue, oneValue)
|
||||||
s.values[0] = NewValue(intVal, sum)
|
s.values[0] = s.factory.NewValue(intVal, sum)
|
||||||
case 12:
|
case 12:
|
||||||
// Hit Blinds Target +5
|
// Hit Blinds Target +5
|
||||||
s.values = make([]d2stats.StatValue, oneValue)
|
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:
|
case 13:
|
||||||
// +5 to Paladin Skill Levels
|
// +5 to Paladin Skill Levels
|
||||||
s.values = make([]d2stats.StatValue, twoValue)
|
s.values = make([]d2stats.StatValue, twoValue)
|
||||||
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned)
|
s.values[0] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerIntSigned)
|
||||||
s.values[1] = NewValue(intVal, sum).SetStringer(stringerClassAllSkills)
|
s.values[1] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerClassAllSkills)
|
||||||
case 14:
|
case 14:
|
||||||
// +5 to Combat Skills (Paladin Only)
|
// +5 to Combat Skills (Paladin Only)
|
||||||
s.values = make([]d2stats.StatValue, threeValue)
|
s.values = make([]d2stats.StatValue, threeValue)
|
||||||
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned)
|
s.values[0] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerIntSigned)
|
||||||
s.values[1] = NewValue(intVal, sum).SetStringer(stringerClassOnly)
|
s.values[1] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerClassOnly)
|
||||||
s.values[2] = NewValue(intVal, static)
|
s.values[2] = s.factory.NewValue(intVal, static)
|
||||||
case 15:
|
case 15:
|
||||||
// 5% Chance to cast level 7 Frozen Orb on attack
|
// 5% Chance to cast level 7 Frozen Orb on attack
|
||||||
s.values = make([]d2stats.StatValue, threeValue)
|
s.values = make([]d2stats.StatValue, threeValue)
|
||||||
s.values[0] = NewValue(intVal, sum)
|
s.values[0] = s.factory.NewValue(intVal, sum)
|
||||||
s.values[1] = NewValue(intVal, static)
|
s.values[1] = s.factory.NewValue(intVal, static)
|
||||||
s.values[2] = NewValue(intVal, static).SetStringer(stringerSkillName)
|
s.values[2] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerSkillName)
|
||||||
case 16:
|
case 16:
|
||||||
// Level 3 Warmth Aura When Equipped
|
// Level 3 Warmth Aura When Equipped
|
||||||
s.values = make([]d2stats.StatValue, twoValue)
|
s.values = make([]d2stats.StatValue, twoValue)
|
||||||
s.values[0] = NewValue(intVal, sum)
|
s.values[0] = s.factory.NewValue(intVal, sum)
|
||||||
s.values[1] = NewValue(intVal, static).SetStringer(stringerSkillName)
|
s.values[1] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerSkillName)
|
||||||
case 20:
|
case 20:
|
||||||
// -25% Target Defense
|
// -25% Target Defense
|
||||||
s.values = make([]d2stats.StatValue, oneValue)
|
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:
|
case 22:
|
||||||
// 25% to Attack Rating versus Specter
|
// 25% to Attack Rating versus Specter
|
||||||
s.values = make([]d2stats.StatValue, twoValue)
|
s.values = make([]d2stats.StatValue, twoValue)
|
||||||
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageUnsigned)
|
s.values[0] = s.factory.NewValue(intVal,
|
||||||
s.values[1] = NewValue(intVal, static).SetStringer(stringerMonsterName)
|
sum).SetStringer(s.factory.stringerIntPercentageUnsigned)
|
||||||
|
s.values[1] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerMonsterName)
|
||||||
case 23:
|
case 23:
|
||||||
// 25% Reanimate as: Specter
|
// 25% Reanimate as: Specter
|
||||||
s.values = make([]d2stats.StatValue, twoValue)
|
s.values = make([]d2stats.StatValue, twoValue)
|
||||||
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntPercentageUnsigned)
|
s.values[0] = s.factory.NewValue(intVal,
|
||||||
s.values[1] = NewValue(intVal, static).SetStringer(stringerMonsterName)
|
sum).SetStringer(s.factory.stringerIntPercentageUnsigned)
|
||||||
|
s.values[1] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerMonsterName)
|
||||||
case 24:
|
case 24:
|
||||||
// Level 25 Frozen Orb (19/20 Charges)
|
// Level 25 Frozen Orb (19/20 Charges)
|
||||||
s.values = make([]d2stats.StatValue, fourValue)
|
s.values = make([]d2stats.StatValue, fourValue)
|
||||||
s.values[0] = NewValue(intVal, static)
|
s.values[0] = s.factory.NewValue(intVal, static)
|
||||||
s.values[1] = NewValue(intVal, static).SetStringer(stringerSkillName)
|
s.values[1] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerSkillName)
|
||||||
s.values[2] = NewValue(intVal, static)
|
s.values[2] = s.factory.NewValue(intVal, static)
|
||||||
s.values[3] = NewValue(intVal, static)
|
s.values[3] = s.factory.NewValue(intVal, static)
|
||||||
case 27:
|
case 27:
|
||||||
// +25 to Frozen Orb (Paladin Only)
|
// +25 to Frozen Orb (Paladin Only)
|
||||||
s.values = make([]d2stats.StatValue, threeValue)
|
s.values = make([]d2stats.StatValue, threeValue)
|
||||||
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned)
|
s.values[0] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerIntSigned)
|
||||||
s.values[1] = NewValue(intVal, static).SetStringer(stringerSkillName)
|
s.values[1] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerSkillName)
|
||||||
s.values[2] = NewValue(intVal, static).SetStringer(stringerClassOnly)
|
s.values[2] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerClassOnly)
|
||||||
case 28:
|
case 28:
|
||||||
// +25 to Frozen Orb
|
// +25 to Frozen Orb
|
||||||
s.values = make([]d2stats.StatValue, twoValue)
|
s.values = make([]d2stats.StatValue, twoValue)
|
||||||
s.values[0] = NewValue(intVal, sum).SetStringer(stringerIntSigned)
|
s.values[0] = s.factory.NewValue(intVal, sum).SetStringer(s.factory.stringerIntSigned)
|
||||||
s.values[1] = NewValue(intVal, static).SetStringer(stringerSkillName)
|
s.values[1] = s.factory.NewValue(intVal, static).SetStringer(s.factory.stringerSkillName)
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -341,7 +350,7 @@ func (s *diablo2Stat) String() string { //nolint:gocyclo switch statement is not
|
||||||
|
|
||||||
for idx := range s.values {
|
for idx := range s.values {
|
||||||
if s.values[idx].Stringer() == nil {
|
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 {
|
func (s *diablo2Stat) descFn14() string {
|
||||||
// strings come out like `+5 to Combat Skills (Paladin Only)`
|
// strings come out like `+5 to Combat Skills (Paladin Only)`
|
||||||
numSkills, hero, skillTab := s.values[0], s.values[1], s.values[2]
|
numSkills, hero, skillTab := s.values[0], s.values[1], s.values[2]
|
||||||
heroMap := getHeroMap()
|
heroMap := s.factory.getHeroMap()
|
||||||
heroIndex := hero.Int()
|
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
|
// diablo 2 is hardcoded to have only 3 skill tabs
|
||||||
skillTabIndex := skillTab.Int()
|
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"
|
"fmt"
|
||||||
"testing"
|
"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"
|
"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"
|
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
|
var itemStatCosts = map[string]*d2records.ItemStatCostRecord{
|
||||||
func TestStat_InitMockData(t *testing.T) {
|
"strength": {
|
||||||
var itemStatCosts = map[string]*d2datadict.ItemStatCostRecord{
|
Name: "strength",
|
||||||
"strength": {
|
DescFnID: 1,
|
||||||
Name: "strength",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 1,
|
DescStrPos: "to Strength",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "to Strength",
|
||||||
DescStrPos: "to Strength",
|
},
|
||||||
DescStrNeg: "to Strength",
|
"dexterity": {
|
||||||
},
|
Name: "dexterity",
|
||||||
"dexterity": {
|
DescFnID: 1,
|
||||||
Name: "dexterity",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 1,
|
DescStrPos: "to Dexterity",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "to Dexterity",
|
||||||
DescStrPos: "to Dexterity",
|
},
|
||||||
DescStrNeg: "to Dexterity",
|
"vitality": {
|
||||||
},
|
Name: "vitality",
|
||||||
"vitality": {
|
DescFnID: 1,
|
||||||
Name: "vitality",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 1,
|
DescStrPos: "to Vitality",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "to Vitality",
|
||||||
DescStrPos: "to Vitality",
|
},
|
||||||
DescStrNeg: "to Vitality",
|
"energy": {
|
||||||
},
|
Name: "energy",
|
||||||
"energy": {
|
DescFnID: 1,
|
||||||
Name: "energy",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 1,
|
DescStrPos: "to Energy",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "to Energy",
|
||||||
DescStrPos: "to Energy",
|
},
|
||||||
DescStrNeg: "to Energy",
|
"hpregen": {
|
||||||
},
|
Name: "hpregen",
|
||||||
"hpregen": {
|
DescFnID: 1,
|
||||||
Name: "hpregen",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 1,
|
DescStrPos: "Replenish Life",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "Drain Life",
|
||||||
DescStrPos: "Replenish Life",
|
},
|
||||||
DescStrNeg: "Drain Life",
|
"toblock": {
|
||||||
},
|
Name: "toblock",
|
||||||
"toblock": {
|
DescFnID: 2,
|
||||||
Name: "toblock",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 2,
|
DescStrPos: "Increased Chance of Blocking",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "Increased Chance of Blocking",
|
||||||
DescStrPos: "Increased Chance of Blocking",
|
},
|
||||||
DescStrNeg: "Increased Chance of Blocking",
|
"item_absorblight_percent": {
|
||||||
},
|
Name: "item_absorblight_percent",
|
||||||
"item_absorblight_percent": {
|
DescFnID: 2,
|
||||||
Name: "item_absorblight_percent",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 2,
|
DescStrPos: "Lightning Absorb",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "Lightning Absorb",
|
||||||
DescStrPos: "Lightning Absorb",
|
},
|
||||||
DescStrNeg: "Lightning Absorb",
|
"item_restinpeace": {
|
||||||
},
|
Name: "item_restinpeace",
|
||||||
"item_restinpeace": {
|
DescFnID: 3,
|
||||||
Name: "item_restinpeace",
|
DescVal: int(descValHide),
|
||||||
DescFnID: 3,
|
DescStrPos: "Slain Monsters Rest in Peace",
|
||||||
DescVal: int(descValHide),
|
DescStrNeg: "Slain Monsters Rest in Peace",
|
||||||
DescStrPos: "Slain Monsters Rest in Peace",
|
},
|
||||||
DescStrNeg: "Slain Monsters Rest in Peace",
|
"normal_damage_reduction": {
|
||||||
},
|
Name: "normal_damage_reduction",
|
||||||
"normal_damage_reduction": {
|
DescFnID: 3,
|
||||||
Name: "normal_damage_reduction",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 3,
|
DescStrPos: "Damage Reduced by",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "Damage Reduced by",
|
||||||
DescStrPos: "Damage Reduced by",
|
},
|
||||||
DescStrNeg: "Damage Reduced by",
|
"poisonresist": {
|
||||||
},
|
Name: "poisonresist",
|
||||||
"poisonresist": {
|
DescFnID: 4,
|
||||||
Name: "poisonresist",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 4,
|
DescStrPos: "Poison Resist",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "Poison Resist",
|
||||||
DescStrPos: "Poison Resist",
|
},
|
||||||
DescStrNeg: "Poison Resist",
|
"item_fastermovevelocity": {
|
||||||
},
|
Name: "item_fastermovevelocity",
|
||||||
"item_fastermovevelocity": {
|
DescFnID: 4,
|
||||||
Name: "item_fastermovevelocity",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 4,
|
DescStrPos: "Faster Run/Walk",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "Faster Run/Walk",
|
||||||
DescStrPos: "Faster Run/Walk",
|
},
|
||||||
DescStrNeg: "Faster Run/Walk",
|
"item_howl": {
|
||||||
},
|
Name: "item_howl",
|
||||||
"item_howl": {
|
DescFnID: 5,
|
||||||
Name: "item_howl",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 5,
|
DescStrPos: "Hit Causes Monster to Flee",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "Hit Causes Monster to Flee",
|
||||||
DescStrPos: "Hit Causes Monster to Flee",
|
},
|
||||||
DescStrNeg: "Hit Causes Monster to Flee",
|
"item_hp_perlevel": {
|
||||||
},
|
Name: "item_hp_perlevel",
|
||||||
"item_hp_perlevel": {
|
DescFnID: 6,
|
||||||
Name: "item_hp_perlevel",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 6,
|
DescStrPos: "to Life",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "to Life",
|
||||||
DescStrPos: "to Life",
|
DescStr2: "(Based on Character Level)",
|
||||||
DescStrNeg: "to Life",
|
},
|
||||||
DescStr2: "(Based on Character Level)",
|
"item_resist_ltng_perlevel": {
|
||||||
},
|
Name: "item_resist_ltng_perlevel",
|
||||||
"item_resist_ltng_perlevel": {
|
DescFnID: 7,
|
||||||
Name: "item_resist_ltng_perlevel",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 7,
|
DescStrPos: "Lightning Resist",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "Lightning Resist",
|
||||||
DescStrPos: "Lightning Resist",
|
DescStr2: "(Based on Character Level)",
|
||||||
DescStrNeg: "Lightning Resist",
|
},
|
||||||
DescStr2: "(Based on Character Level)",
|
"item_find_magic_perlevel": {
|
||||||
},
|
Name: "item_find_magic_perlevel",
|
||||||
"item_find_magic_perlevel": {
|
DescFnID: 7,
|
||||||
Name: "item_find_magic_perlevel",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 7,
|
DescStrPos: "Better Chance of Getting Magic Items",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "Better Chance of Getting Magic Items",
|
||||||
DescStrPos: "Better Chance of Getting Magic Items",
|
DescStr2: "(Based on Character Level)",
|
||||||
DescStrNeg: "Better Chance of Getting Magic Items",
|
},
|
||||||
DescStr2: "(Based on Character Level)",
|
"item_armorpercent_perlevel": {
|
||||||
},
|
Name: "item_armorpercent_perlevel",
|
||||||
"item_armorpercent_perlevel": {
|
DescFnID: 8,
|
||||||
Name: "item_armorpercent_perlevel",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 8,
|
DescStrPos: "Enhanced Defense",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "Enhanced Defense",
|
||||||
DescStrPos: "Enhanced Defense",
|
DescStr2: "(Based on Character Level)",
|
||||||
DescStrNeg: "Enhanced Defense",
|
},
|
||||||
DescStr2: "(Based on Character Level)",
|
"item_regenstamina_perlevel": {
|
||||||
},
|
Name: "item_regenstamina_perlevel",
|
||||||
"item_regenstamina_perlevel": {
|
DescFnID: 8,
|
||||||
Name: "item_regenstamina_perlevel",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 8,
|
DescStrPos: "Heal Stamina Plus",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "Heal Stamina Plus",
|
||||||
DescStrPos: "Heal Stamina Plus",
|
DescStr2: "(Based on Character Level)",
|
||||||
DescStrNeg: "Heal Stamina Plus",
|
},
|
||||||
DescStr2: "(Based on Character Level)",
|
"item_thorns_perlevel": {
|
||||||
},
|
Name: "item_thorns_perlevel",
|
||||||
"item_thorns_perlevel": {
|
DescFnID: 9,
|
||||||
Name: "item_thorns_perlevel",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 9,
|
DescStrPos: "Attacker Takes Damage of",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "Attacker Takes Damage of",
|
||||||
DescStrPos: "Attacker Takes Damage of",
|
DescStr2: "(Based on Character Level)",
|
||||||
DescStrNeg: "Attacker Takes Damage of",
|
},
|
||||||
DescStr2: "(Based on Character Level)",
|
"item_replenish_durability": {
|
||||||
},
|
Name: "item_replenish_durability",
|
||||||
"item_replenish_durability": {
|
DescFnID: 11,
|
||||||
Name: "item_replenish_durability",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 11,
|
DescStrPos: "Repairs %v durability per second",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "Repairs %v durability per second",
|
||||||
DescStrPos: "Repairs %v durability per second",
|
DescStr2: "",
|
||||||
DescStrNeg: "Repairs %v durability per second",
|
},
|
||||||
DescStr2: "",
|
"item_stupidity": {
|
||||||
},
|
Name: "item_stupidity",
|
||||||
"item_stupidity": {
|
DescFnID: 12,
|
||||||
Name: "item_stupidity",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 12,
|
DescStrPos: "Hit Blinds Target",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "Hit Blinds Target",
|
||||||
DescStrPos: "Hit Blinds Target",
|
},
|
||||||
DescStrNeg: "Hit Blinds Target",
|
"item_addclassskills": {
|
||||||
},
|
Name: "item_addclassskills",
|
||||||
"item_addclassskills": {
|
DescFnID: 13,
|
||||||
Name: "item_addclassskills",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 13,
|
},
|
||||||
DescVal: int(descValPrefix),
|
"item_addskill_tab": {
|
||||||
},
|
Name: "item_addskill_tab",
|
||||||
"item_addskill_tab": {
|
DescFnID: 14,
|
||||||
Name: "item_addskill_tab",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 14,
|
},
|
||||||
DescVal: int(descValPrefix),
|
"item_skillonattack": {
|
||||||
},
|
Name: "item_skillonattack",
|
||||||
"item_skillonattack": {
|
DescFnID: 15,
|
||||||
Name: "item_skillonattack",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 15,
|
DescStrPos: "%d%% Chance to cast level %d %s on attack",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "%d%% Chance to cast level %d %s on attack",
|
||||||
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",
|
||||||
"item_aura": {
|
DescFnID: 16,
|
||||||
Name: "item_aura",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 16,
|
DescStrPos: "Level %d %s Aura When Equipped",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "Level %d %s Aura When Equipped",
|
||||||
DescStrPos: "Level %d %s Aura When Equipped",
|
},
|
||||||
DescStrNeg: "Level %d %s Aura When Equipped",
|
"item_fractionaltargetac": {
|
||||||
},
|
Name: "item_fractionaltargetac",
|
||||||
"item_fractionaltargetac": {
|
DescFnID: 20,
|
||||||
Name: "item_fractionaltargetac",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 20,
|
DescStrPos: "Target Defense",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "Target Defense",
|
||||||
DescStrPos: "Target Defense",
|
},
|
||||||
DescStrNeg: "Target Defense",
|
"attack_vs_montype": {
|
||||||
},
|
Name: "item_fractionaltargetac",
|
||||||
"attack_vs_montype": {
|
DescFnID: 22,
|
||||||
Name: "item_fractionaltargetac",
|
DescVal: int(descValPrefix),
|
||||||
DescFnID: 22,
|
DescStrPos: "to Attack Rating versus",
|
||||||
DescVal: int(descValPrefix),
|
DescStrNeg: "to Attack Rating versus",
|
||||||
DescStrPos: "to Attack Rating versus",
|
},
|
||||||
DescStrNeg: "to Attack Rating versus",
|
"item_reanimate": {
|
||||||
},
|
Name: "item_reanimate",
|
||||||
"item_reanimate": {
|
DescFnID: 23,
|
||||||
Name: "item_reanimate",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 23,
|
DescStrPos: "Reanimate as:",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "Reanimate as:",
|
||||||
DescStrPos: "Reanimate as:",
|
},
|
||||||
DescStrNeg: "Reanimate as:",
|
"item_charged_skill": {
|
||||||
},
|
Name: "item_charged_skill",
|
||||||
"item_charged_skill": {
|
DescFnID: 24,
|
||||||
Name: "item_charged_skill",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 24,
|
DescStrPos: "(%d/%d Charges)",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "(%d/%d Charges)",
|
||||||
DescStrPos: "(%d/%d Charges)",
|
},
|
||||||
DescStrNeg: "(%d/%d Charges)",
|
"item_singleskill": {
|
||||||
},
|
Name: "item_singleskill",
|
||||||
"item_singleskill": {
|
DescFnID: 27,
|
||||||
Name: "item_singleskill",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 27,
|
DescStrPos: "(%d/%d Charges)",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "(%d/%d Charges)",
|
||||||
DescStrPos: "(%d/%d Charges)",
|
},
|
||||||
DescStrNeg: "(%d/%d Charges)",
|
"item_nonclassskill": {
|
||||||
},
|
Name: "item_nonclassskill",
|
||||||
"item_nonclassskill": {
|
DescFnID: 28,
|
||||||
Name: "item_nonclassskill",
|
DescVal: int(descValPostfix),
|
||||||
DescFnID: 28,
|
DescStrPos: "(%d/%d Charges)",
|
||||||
DescVal: int(descValPostfix),
|
DescStrNeg: "(%d/%d Charges)",
|
||||||
DescStrPos: "(%d/%d Charges)",
|
},
|
||||||
DescStrNeg: "(%d/%d Charges)",
|
}
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var charStats = map[d2enum.Hero]*d2datadict.CharStatsRecord{
|
var skillDetails = map[int]*d2records.SkillRecord{
|
||||||
d2enum.HeroPaladin: {
|
37: {Skill: "Warmth"},
|
||||||
Class: d2enum.HeroPaladin,
|
64: {Skill: "Frozen Orb"},
|
||||||
SkillStrAll: "to Paladin Skill Levels",
|
}
|
||||||
SkillStrClassOnly: "(Paladin Only)",
|
|
||||||
SkillStrTab: [3]string{
|
var monStats = map[string]*d2records.MonStatsRecord{
|
||||||
"+%d to Combat Skills",
|
"Specter": {NameString: "Specter", ID: 40},
|
||||||
"+%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 testAssetManager2 *d2asset.AssetManager
|
||||||
|
|
||||||
var skillDetails = map[int]*d2datadict.SkillRecord{
|
var testStatFactory2 *StatFactory
|
||||||
37: {Skill: "Warmth"},
|
|
||||||
64: {Skill: "Frozen Orb"},
|
|
||||||
}
|
|
||||||
|
|
||||||
var monStats = map[string]*d2datadict.MonStatsRecord{
|
func TestSetup_Stat(t *testing.T) {
|
||||||
"Specter": {NameString: "Specter", ID: 40},
|
testAssetManager2 = &d2asset.AssetManager{}
|
||||||
}
|
testAssetManager2.Records = &d2records.RecordManager{}
|
||||||
|
|
||||||
d2datadict.ItemStatCosts = itemStatCosts
|
testStatFactory2, _ = NewStatFactory(testAssetManager2)
|
||||||
d2datadict.CharStats = charStats
|
|
||||||
d2datadict.SkillDetails = skillDetails
|
testAssetManager2.Records.Item.Stats = itemStatCosts
|
||||||
d2datadict.MonStats = monStats
|
testAssetManager2.Records.Character.Stats = charStats
|
||||||
|
testAssetManager2.Records.Skill.Details = skillDetails
|
||||||
|
testAssetManager2.Records.Monster.Stats = monStats
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStat_Clone(t *testing.T) {
|
func TestStat_Clone(t *testing.T) {
|
||||||
s1 := NewStat("strength", 5)
|
s1 := testStatFactory2.NewStat("strength", 5)
|
||||||
s2 := s1.Clone()
|
s2 := s1.Clone()
|
||||||
|
|
||||||
// make sure the stats are distinct
|
// make sure the stats are distinct
|
||||||
|
@ -371,9 +381,9 @@ func TestStat_Descriptions(t *testing.T) {
|
||||||
for idx := range tests {
|
for idx := range tests {
|
||||||
test := tests[idx]
|
test := tests[idx]
|
||||||
key := test.recordKey
|
key := test.recordKey
|
||||||
record := d2datadict.ItemStatCosts[key]
|
record := itemStatCosts[key]
|
||||||
expect := test.expect
|
expect := test.expect
|
||||||
stat := NewStat(key, test.vals...)
|
stat := testStatFactory2.NewStat(key, test.vals...)
|
||||||
|
|
||||||
if got := stat.String(); got != expect {
|
if got := stat.String(); got != expect {
|
||||||
t.Errorf(errFmt, errStr, record.DescFnID, test.recordKey, test.vals, expect, got)
|
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) {
|
func TestDiablo2Stat_Combine(t *testing.T) {
|
||||||
a := NewStat("item_nonclassskill", 25, 64) // "+25 to Frozen Orb"
|
a := testStatFactory2.NewStat("item_nonclassskill", 25, 64) // "+25 to Frozen Orb"
|
||||||
b := NewStat("item_nonclassskill", 5, 64) // "+5 to Frozen Orb"
|
b := testStatFactory2.NewStat("item_nonclassskill", 5, 64) // "+5 to Frozen Orb"
|
||||||
|
|
||||||
c, err := a.Combine(b)
|
c, err := a.Combine(b)
|
||||||
|
|
||||||
|
@ -395,7 +405,7 @@ func TestDiablo2Stat_Combine(t *testing.T) {
|
||||||
t.Errorf("stats combination failed\r%s", err)
|
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)
|
_, err = c.Combine(d)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -1,83 +1 @@
|
||||||
package diablo2stats
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
|
"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) {
|
func TestDiablo2StatList_Index(t *testing.T) {
|
||||||
strength := NewStat("strength", 10)
|
strength := testStatFactory.NewStat("strength", 10)
|
||||||
|
|
||||||
list1 := &Diablo2StatList{stats: []d2stats.Stat{strength}}
|
list1 := &Diablo2StatList{stats: []d2stats.Stat{strength}}
|
||||||
if list1.Index(0) != strength {
|
if list1.Index(0) != strength {
|
||||||
|
@ -16,7 +35,7 @@ func TestDiablo2StatList_Index(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStatList_Clone(t *testing.T) {
|
func TestStatList_Clone(t *testing.T) {
|
||||||
strength := NewStat("strength", 10)
|
strength := testStatFactory.NewStat("strength", 10)
|
||||||
|
|
||||||
list1 := &Diablo2StatList{}
|
list1 := &Diablo2StatList{}
|
||||||
list1.Push(strength)
|
list1.Push(strength)
|
||||||
|
@ -38,13 +57,13 @@ func TestStatList_Clone(t *testing.T) {
|
||||||
|
|
||||||
func TestStatList_Reduce(t *testing.T) {
|
func TestStatList_Reduce(t *testing.T) {
|
||||||
stats := []d2stats.Stat{
|
stats := []d2stats.Stat{
|
||||||
NewStat("strength", 1),
|
testStatFactory.NewStat("strength", 1),
|
||||||
NewStat("strength", 1),
|
testStatFactory.NewStat("strength", 1),
|
||||||
NewStat("strength", 1),
|
testStatFactory.NewStat("strength", 1),
|
||||||
NewStat("strength", 1),
|
testStatFactory.NewStat("strength", 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
list := NewStatList(stats...)
|
list := testStatFactory.NewStatList(stats...)
|
||||||
reduction := list.ReduceStats()
|
reduction := list.ReduceStats()
|
||||||
|
|
||||||
if len(reduction.Stats()) != 1 || reduction.Index(0).String() != "+4 to Strength" {
|
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{
|
stats = []d2stats.Stat{
|
||||||
NewStat("strength", 1),
|
testStatFactory.NewStat("strength", 1),
|
||||||
NewStat("energy", 1),
|
testStatFactory.NewStat("energy", 1),
|
||||||
NewStat("dexterity", 1),
|
testStatFactory.NewStat("dexterity", 1),
|
||||||
NewStat("vitality", 1),
|
testStatFactory.NewStat("vitality", 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
list = NewStatList(stats...)
|
list = testStatFactory.NewStatList(stats...)
|
||||||
reduction = list.ReduceStats()
|
reduction = list.ReduceStats()
|
||||||
|
|
||||||
if len(reduction.Stats()) != 4 {
|
if len(reduction.Stats()) != 4 {
|
||||||
|
@ -69,10 +88,10 @@ func TestStatList_Reduce(t *testing.T) {
|
||||||
func TestStatList_Append(t *testing.T) {
|
func TestStatList_Append(t *testing.T) {
|
||||||
list1 := &Diablo2StatList{
|
list1 := &Diablo2StatList{
|
||||||
[]d2stats.Stat{
|
[]d2stats.Stat{
|
||||||
NewStat("strength", 1),
|
testStatFactory.NewStat("strength", 1),
|
||||||
NewStat("energy", 1),
|
testStatFactory.NewStat("energy", 1),
|
||||||
NewStat("dexterity", 1),
|
testStatFactory.NewStat("dexterity", 1),
|
||||||
NewStat("vitality", 1),
|
testStatFactory.NewStat("vitality", 1),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
list2 := list1.Clone()
|
list2 := list1.Clone()
|
||||||
|
|
|
@ -6,16 +6,16 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
|
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import (
|
||||||
type CharacterSelect struct {
|
type CharacterSelect struct {
|
||||||
asset *d2asset.AssetManager
|
asset *d2asset.AssetManager
|
||||||
*d2mapentity.MapEntityFactory
|
*d2mapentity.MapEntityFactory
|
||||||
|
*d2hero.HeroStateFactory
|
||||||
background *d2ui.Sprite
|
background *d2ui.Sprite
|
||||||
newCharButton *d2ui.Button
|
newCharButton *d2ui.Button
|
||||||
convertCharButton *d2ui.Button
|
convertCharButton *d2ui.Button
|
||||||
|
@ -40,7 +41,7 @@ type CharacterSelect struct {
|
||||||
characterStatsLabel [8]*d2ui.Label
|
characterStatsLabel [8]*d2ui.Label
|
||||||
characterExpLabel [8]*d2ui.Label
|
characterExpLabel [8]*d2ui.Label
|
||||||
characterImage [8]*d2mapentity.Player
|
characterImage [8]*d2mapentity.Player
|
||||||
gameStates []*d2player.PlayerState
|
gameStates []*d2hero.HeroState
|
||||||
selectedCharacter int
|
selectedCharacter int
|
||||||
showDeleteConfirmation bool
|
showDeleteConfirmation bool
|
||||||
connectionType d2clientconnectiontype.ClientConnectionType
|
connectionType d2clientconnectiontype.ClientConnectionType
|
||||||
|
@ -64,10 +65,13 @@ func CreateCharacterSelect(
|
||||||
connectionType d2clientconnectiontype.ClientConnectionType,
|
connectionType d2clientconnectiontype.ClientConnectionType,
|
||||||
connectionHost string,
|
connectionHost string,
|
||||||
) *CharacterSelect {
|
) *CharacterSelect {
|
||||||
|
playerStateFactory, _ := d2hero.NewHeroStateFactory(asset) // TODO: handle errors
|
||||||
|
entityFactory, _ := d2mapentity.NewMapEntityFactory(asset)
|
||||||
|
|
||||||
return &CharacterSelect{
|
return &CharacterSelect{
|
||||||
selectedCharacter: -1,
|
selectedCharacter: -1,
|
||||||
asset: asset,
|
asset: asset,
|
||||||
MapEntityFactory: d2mapentity.NewMapEntityFactory(asset),
|
MapEntityFactory: entityFactory,
|
||||||
renderer: renderer,
|
renderer: renderer,
|
||||||
connectionType: connectionType,
|
connectionType: connectionType,
|
||||||
connectionHost: connectionHost,
|
connectionHost: connectionHost,
|
||||||
|
@ -75,6 +79,7 @@ func CreateCharacterSelect(
|
||||||
audioProvider: audioProvider,
|
audioProvider: audioProvider,
|
||||||
navigator: navigator,
|
navigator: navigator,
|
||||||
uiManager: ui,
|
uiManager: ui,
|
||||||
|
HeroStateFactory: playerStateFactory,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,7 +287,7 @@ func (v *CharacterSelect) updateCharacterBoxes() {
|
||||||
v.characterExpLabel[i].SetText(d2ui.ColorTokenize(expText, d2ui.ColorTokenGreen))
|
v.characterExpLabel[i].SetText(d2ui.ColorTokenize(expText, d2ui.ColorTokenGreen))
|
||||||
|
|
||||||
heroType := v.gameStates[idx].HeroType
|
heroType := v.gameStates[idx].HeroType
|
||||||
equipment := d2inventory.HeroObjects[heroType]
|
equipment := v.DefaultHeroItems[heroType]
|
||||||
|
|
||||||
// TODO: Generate or load the object from the actual player data...
|
// TODO: Generate or load the object from the actual player data...
|
||||||
v.characterImage[i] = v.NewPlayer("", "", 0, 0, 0,
|
v.characterImage[i] = v.NewPlayer("", "", 0, 0, 0,
|
||||||
|
@ -434,7 +439,11 @@ func (v *CharacterSelect) toggleDeleteCharacterDialog(showDialog bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *CharacterSelect) refreshGameStates() {
|
func (v *CharacterSelect) refreshGameStates() {
|
||||||
v.gameStates = d2player.GetAllPlayerStates()
|
gameStates, err := v.HeroStateFactory.GetAllHeroStates()
|
||||||
|
if err == nil {
|
||||||
|
v.gameStates = gameStates
|
||||||
|
}
|
||||||
|
|
||||||
v.updateCharacterBoxes()
|
v.updateCharacterBoxes()
|
||||||
|
|
||||||
if len(v.gameStates) > 0 {
|
if len(v.gameStates) > 0 {
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio"
|
||||||
|
@ -93,7 +92,7 @@ func CreateGame(
|
||||||
audioProvider: audioProvider,
|
audioProvider: audioProvider,
|
||||||
renderer: renderer,
|
renderer: renderer,
|
||||||
terminal: term,
|
terminal: term,
|
||||||
soundEngine: d2audio.NewSoundEngine(audioProvider, term),
|
soundEngine: d2audio.NewSoundEngine(audioProvider, asset, term),
|
||||||
uiManager: ui,
|
uiManager: ui,
|
||||||
guiManager: guiManager,
|
guiManager: guiManager,
|
||||||
}
|
}
|
||||||
|
@ -142,7 +141,7 @@ func (v *Game) OnLoad(_ d2screen.LoadingState) {
|
||||||
func(name string) {
|
func(name string) {
|
||||||
x := int(v.localPlayer.Position.X())
|
x := int(v.localPlayer.Position.X())
|
||||||
y := int(v.localPlayer.Position.Y())
|
y := int(v.localPlayer.Position.Y())
|
||||||
monstat := d2datadict.MonStats[name]
|
monstat := v.asset.Records.Monster.Stats[name]
|
||||||
if monstat == nil {
|
if monstat == nil {
|
||||||
v.terminal.OutputErrorf("no monstat entry for \"%s\"", name)
|
v.terminal.OutputErrorf("no monstat entry for \"%s\"", name)
|
||||||
return
|
return
|
||||||
|
@ -233,12 +232,13 @@ func (v *Game) Advance(elapsed float64) error {
|
||||||
tile := v.gameClient.MapEngine.TileAt(int(tilePosition.X()), int(tilePosition.Y()))
|
tile := v.gameClient.MapEngine.TileAt(int(tilePosition.X()), int(tilePosition.Y()))
|
||||||
|
|
||||||
if tile != nil {
|
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
|
// skip showing zone change text the first time we enter the world
|
||||||
if v.lastRegionType != d2enum.RegionNone && v.lastRegionType != tile.RegionType {
|
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.
|
//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)
|
areaChgStr := fmt.Sprintf("Entering The %s", areaName)
|
||||||
v.gameControls.SetZoneChangeText(areaChgStr)
|
v.gameControls.SetZoneChangeText(areaChgStr)
|
||||||
v.gameControls.ShowZoneChangeText()
|
v.gameControls.ShowZoneChangeText()
|
||||||
|
|
|
@ -8,6 +8,8 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
@ -16,7 +18,6 @@ import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
|
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2script"
|
"github.com/OpenDiablo2/OpenDiablo2/d2script"
|
||||||
)
|
)
|
||||||
|
@ -118,6 +119,7 @@ type MainMenu struct {
|
||||||
scriptEngine *d2script.ScriptEngine
|
scriptEngine *d2script.ScriptEngine
|
||||||
navigator Navigator
|
navigator Navigator
|
||||||
uiManager *d2ui.UIManager
|
uiManager *d2ui.UIManager
|
||||||
|
heroState *d2hero.HeroStateFactory
|
||||||
|
|
||||||
buildInfo BuildInfo
|
buildInfo BuildInfo
|
||||||
}
|
}
|
||||||
|
@ -131,8 +133,13 @@ func CreateMainMenu(
|
||||||
audioProvider d2interface.AudioProvider,
|
audioProvider d2interface.AudioProvider,
|
||||||
ui *d2ui.UIManager,
|
ui *d2ui.UIManager,
|
||||||
buildInfo BuildInfo,
|
buildInfo BuildInfo,
|
||||||
) *MainMenu {
|
) (*MainMenu, error) {
|
||||||
return &MainMenu{
|
heroStateFactory, err := d2hero.NewHeroStateFactory(asset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mainMenu := &MainMenu{
|
||||||
asset: asset,
|
asset: asset,
|
||||||
screenMode: ScreenModeUnknown,
|
screenMode: ScreenModeUnknown,
|
||||||
leftButtonHeld: true,
|
leftButtonHeld: true,
|
||||||
|
@ -142,7 +149,10 @@ func CreateMainMenu(
|
||||||
navigator: navigator,
|
navigator: navigator,
|
||||||
buildInfo: buildInfo,
|
buildInfo: buildInfo,
|
||||||
uiManager: ui,
|
uiManager: ui,
|
||||||
|
heroState: heroStateFactory,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return mainMenu, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnLoad is called to load the resources for the main menu
|
// OnLoad is called to load the resources for the main menu
|
||||||
|
@ -320,7 +330,7 @@ func (v *MainMenu) onMapTestClicked() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *MainMenu) onSinglePlayerClicked() {
|
func (v *MainMenu) onSinglePlayerClicked() {
|
||||||
if d2player.HasGameStates() {
|
if v.heroState.HasGameStates() {
|
||||||
// Go here only if existing characters are available to select
|
// Go here only if existing characters are available to select
|
||||||
v.navigator.ToCharacterSelect(d2clientconnectiontype.Local, v.tcpJoinGameEntry.GetText())
|
v.navigator.ToCharacterSelect(d2clientconnectiontype.Local, v.tcpJoinGameEntry.GetText())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
|
||||||
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
"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/d2mapgen"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type regionSpec struct {
|
type regionSpec struct {
|
||||||
|
@ -84,15 +85,17 @@ func getRegions() []regionSpec {
|
||||||
|
|
||||||
// MapEngineTest represents the MapEngineTest screen
|
// MapEngineTest represents the MapEngineTest screen
|
||||||
type MapEngineTest struct {
|
type MapEngineTest struct {
|
||||||
asset *d2asset.AssetManager
|
asset *d2asset.AssetManager
|
||||||
gameState *d2player.PlayerState
|
playerStateFactory *d2hero.HeroStateFactory
|
||||||
mapEngine *d2mapengine.MapEngine
|
playerState *d2hero.HeroState
|
||||||
mapRenderer *d2maprenderer.MapRenderer
|
mapEngine *d2mapengine.MapEngine
|
||||||
terminal d2interface.Terminal
|
mapGen *d2mapgen.MapGenerator
|
||||||
renderer d2interface.Renderer
|
mapRenderer *d2maprenderer.MapRenderer
|
||||||
inputManager d2interface.InputManager
|
terminal d2interface.Terminal
|
||||||
audioProvider d2interface.AudioProvider
|
renderer d2interface.Renderer
|
||||||
screen *d2screen.ScreenManager
|
inputManager d2interface.InputManager
|
||||||
|
audioProvider d2interface.AudioProvider
|
||||||
|
screen *d2screen.ScreenManager
|
||||||
|
|
||||||
lastMouseX, lastMouseY int
|
lastMouseX, lastMouseY int
|
||||||
selX, selY int
|
selX, selY int
|
||||||
|
@ -116,23 +119,30 @@ func CreateMapEngineTest(currentRegion,
|
||||||
inputManager d2interface.InputManager,
|
inputManager d2interface.InputManager,
|
||||||
audioProvider d2interface.AudioProvider,
|
audioProvider d2interface.AudioProvider,
|
||||||
screen *d2screen.ScreenManager,
|
screen *d2screen.ScreenManager,
|
||||||
) *MapEngineTest {
|
) (*MapEngineTest, error) {
|
||||||
result := &MapEngineTest{
|
heroStateFactory, err := d2hero.NewHeroStateFactory(asset)
|
||||||
currentRegion: currentRegion,
|
if err != nil {
|
||||||
levelPreset: levelPreset,
|
return nil, err
|
||||||
fileIndex: 0,
|
|
||||||
regionSpec: regionSpec{},
|
|
||||||
filesCount: 0,
|
|
||||||
asset: asset,
|
|
||||||
terminal: term,
|
|
||||||
renderer: renderer,
|
|
||||||
inputManager: inputManager,
|
|
||||||
audioProvider: audioProvider,
|
|
||||||
screen: screen,
|
|
||||||
}
|
}
|
||||||
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) {
|
func (met *MapEngineTest) loadRegionByIndex(n, levelPreset, fileIndex int) {
|
||||||
|
@ -167,9 +177,12 @@ func (met *MapEngineTest) loadRegionByIndex(n, levelPreset, fileIndex int) {
|
||||||
met.levelPreset = levelPreset
|
met.levelPreset = levelPreset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mapGen, _ := d2mapgen.NewMapGenerator(met.asset, met.mapEngine)
|
||||||
|
met.mapGen = mapGen
|
||||||
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
met.mapEngine.SetSeed(time.Now().UnixNano())
|
met.mapEngine.SetSeed(time.Now().UnixNano())
|
||||||
d2mapgen.GenerateAct1Overworld(met.mapEngine)
|
met.mapGen.GenerateAct1Overworld()
|
||||||
} else {
|
} else {
|
||||||
met.mapEngine = d2mapengine.CreateMapEngine(met.asset) // necessary for map name update
|
met.mapEngine = d2mapengine.CreateMapEngine(met.asset) // necessary for map name update
|
||||||
met.mapEngine.SetSeed(time.Now().UnixNano())
|
met.mapEngine.SetSeed(time.Now().UnixNano())
|
||||||
|
|
|
@ -4,7 +4,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"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/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
@ -14,7 +17,6 @@ import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
|
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -268,18 +270,20 @@ func (hri *HeroRenderInfo) advance(elapsed float64) {
|
||||||
|
|
||||||
// SelectHeroClass represents the Select Hero Class screen
|
// SelectHeroClass represents the Select Hero Class screen
|
||||||
type SelectHeroClass struct {
|
type SelectHeroClass struct {
|
||||||
asset *d2asset.AssetManager
|
asset *d2asset.AssetManager
|
||||||
uiManager *d2ui.UIManager
|
uiManager *d2ui.UIManager
|
||||||
bgImage *d2ui.Sprite
|
bgImage *d2ui.Sprite
|
||||||
campfire *d2ui.Sprite
|
campfire *d2ui.Sprite
|
||||||
headingLabel *d2ui.Label
|
headingLabel *d2ui.Label
|
||||||
heroClassLabel *d2ui.Label
|
heroClassLabel *d2ui.Label
|
||||||
heroDesc1Label *d2ui.Label
|
heroDesc1Label *d2ui.Label
|
||||||
heroDesc2Label *d2ui.Label
|
heroDesc2Label *d2ui.Label
|
||||||
heroDesc3Label *d2ui.Label
|
heroDesc3Label *d2ui.Label
|
||||||
heroNameTextbox *d2ui.TextBox
|
heroNameTextbox *d2ui.TextBox
|
||||||
heroNameLabel *d2ui.Label
|
heroNameLabel *d2ui.Label
|
||||||
heroRenderInfo map[d2enum.Hero]*HeroRenderInfo
|
heroRenderInfo map[d2enum.Hero]*HeroRenderInfo
|
||||||
|
*d2inventory.InventoryItemFactory
|
||||||
|
*d2hero.HeroStateFactory
|
||||||
selectedHero d2enum.Hero
|
selectedHero d2enum.Hero
|
||||||
exitButton *d2ui.Button
|
exitButton *d2ui.Button
|
||||||
okButton *d2ui.Button
|
okButton *d2ui.Button
|
||||||
|
@ -304,20 +308,32 @@ func CreateSelectHeroClass(
|
||||||
ui *d2ui.UIManager,
|
ui *d2ui.UIManager,
|
||||||
connectionType d2clientconnectiontype.ClientConnectionType,
|
connectionType d2clientconnectiontype.ClientConnectionType,
|
||||||
connectionHost string,
|
connectionHost string,
|
||||||
) *SelectHeroClass {
|
) (*SelectHeroClass, error) {
|
||||||
result := &SelectHeroClass{
|
playerStateFactory, err := d2hero.NewHeroStateFactory(asset)
|
||||||
asset: asset,
|
if err != nil {
|
||||||
heroRenderInfo: make(map[d2enum.Hero]*HeroRenderInfo),
|
return nil, err
|
||||||
selectedHero: d2enum.HeroNone,
|
|
||||||
connectionType: connectionType,
|
|
||||||
connectionHost: connectionHost,
|
|
||||||
audioProvider: audioProvider,
|
|
||||||
renderer: renderer,
|
|
||||||
navigator: navigator,
|
|
||||||
uiManager: ui,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// OnLoad loads the resources for the Select Hero Class screen
|
||||||
|
@ -469,12 +485,23 @@ func (v *SelectHeroClass) onExitButtonClicked() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *SelectHeroClass) onOkButtonClicked() {
|
func (v *SelectHeroClass) onOkButtonClicked() {
|
||||||
gameState := d2player.CreatePlayerState(
|
|
||||||
v.heroNameTextbox.GetText(),
|
heroName := v.heroNameTextbox.GetText()
|
||||||
v.selectedHero,
|
defaultStats := v.asset.Records.Character.Stats[v.selectedHero]
|
||||||
d2datadict.CharStats[v.selectedHero],
|
statsState := v.CreateHeroStatsState(v.selectedHero, defaultStats)
|
||||||
)
|
|
||||||
v.navigator.ToCreateGame(gameState.FilePath, d2clientconnectiontype.Local, v.connectionHost)
|
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
|
// Render renders the Select Hero Class screen
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package d2player
|
package d2player
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||||
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EquipmentSlot represents an equipment slot for a player
|
// EquipmentSlot represents an equipment slot for a player
|
||||||
|
@ -14,7 +14,7 @@ type EquipmentSlot struct {
|
||||||
height int
|
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{}
|
slotMap := map[d2enum.EquippedSlot]EquipmentSlot{}
|
||||||
|
|
||||||
slots := []d2enum.EquippedSlot{
|
slots := []d2enum.EquippedSlot{
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player/help"
|
"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/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
||||||
|
@ -51,6 +50,7 @@ type GameControls struct {
|
||||||
renderer d2interface.Renderer // TODO: This shouldn't be a dependency
|
renderer d2interface.Renderer // TODO: This shouldn't be a dependency
|
||||||
inputListener InputCallbackListener
|
inputListener InputCallbackListener
|
||||||
hero *d2mapentity.Player
|
hero *d2mapentity.Player
|
||||||
|
heroState *d2hero.HeroStateFactory
|
||||||
mapEngine *d2mapengine.MapEngine
|
mapEngine *d2mapengine.MapEngine
|
||||||
mapRenderer *d2maprenderer.MapRenderer
|
mapRenderer *d2maprenderer.MapRenderer
|
||||||
uiManager *d2ui.UIManager
|
uiManager *d2ui.UIManager
|
||||||
|
@ -155,18 +155,24 @@ func NewGameControls(
|
||||||
return nil, fmt.Errorf("unknown hero class: %d", hero.Class)
|
return nil, fmt.Errorf("unknown hero class: %d", hero.Class)
|
||||||
}
|
}
|
||||||
|
|
||||||
inventoryRecord := d2datadict.Inventory[inventoryRecordKey]
|
inventoryRecord := asset.Records.Layout.Inventory[inventoryRecordKey]
|
||||||
|
|
||||||
hoverLabel := nameLabel
|
hoverLabel := nameLabel
|
||||||
hoverLabel.SetBackgroundColor(color.RGBA{0, 0, 0, uint8(128)})
|
hoverLabel.SetBackgroundColor(color.RGBA{0, 0, 0, uint8(128)})
|
||||||
|
|
||||||
globeStatsLabel := hpManaStatsLabel
|
globeStatsLabel := hpManaStatsLabel
|
||||||
|
|
||||||
|
heroState, err := d2hero.NewHeroStateFactory(asset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
gc := &GameControls{
|
gc := &GameControls{
|
||||||
asset: asset,
|
asset: asset,
|
||||||
uiManager: ui,
|
uiManager: ui,
|
||||||
renderer: renderer,
|
renderer: renderer,
|
||||||
hero: hero,
|
hero: hero,
|
||||||
|
heroState: heroState,
|
||||||
mapEngine: mapEngine,
|
mapEngine: mapEngine,
|
||||||
inputListener: inputListener,
|
inputListener: inputListener,
|
||||||
mapRenderer: mapRenderer,
|
mapRenderer: mapRenderer,
|
||||||
|
@ -196,7 +202,7 @@ func NewGameControls(
|
||||||
isSinglePlayer: isSinglePlayer,
|
isSinglePlayer: isSinglePlayer,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := term.BindAction("freecam", "toggle free camera movement", func() {
|
err = term.BindAction("freecam", "toggle free camera movement", func() {
|
||||||
gc.FreeCam = !gc.FreeCam
|
gc.FreeCam = !gc.FreeCam
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -205,13 +211,23 @@ func NewGameControls(
|
||||||
}
|
}
|
||||||
|
|
||||||
err = term.BindAction("setleftskill", "set skill to fire on left click", func(id int) {
|
err = term.BindAction("setleftskill", "set skill to fire on left click", func(id int) {
|
||||||
skillRecord := d2datadict.SkillDetails[id]
|
skillRecord := gc.asset.Records.Skill.Details[id]
|
||||||
gc.hero.LeftSkill = &d2hero.HeroSkill{SkillPoints: 0, SkillRecord: skillRecord, SkillDescriptionRecord: d2datadict.SkillDescriptions[skillRecord.Skilldesc]}
|
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) {
|
err = term.BindAction("setrightskill", "set skill to fire on right click", func(id int) {
|
||||||
skillRecord := d2datadict.SkillDetails[id]
|
skillRecord := gc.asset.Records.Skill.Details[id]
|
||||||
gc.hero.RightSkill = &d2hero.HeroSkill{SkillPoints: 0, SkillRecord: skillRecord, SkillDescriptionRecord: d2datadict.SkillDescriptions[skillRecord.Skilldesc]}
|
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 {
|
if err != nil {
|
||||||
|
@ -416,7 +432,7 @@ func (g *GameControls) Load() {
|
||||||
attackIconID := 2
|
attackIconID := 2
|
||||||
|
|
||||||
g.leftSkill = &SkillResource{SkillIcon: genericSkillsSprite, IconNumber: attackIconID, SkillResourcePath: d2resource.GenericSkills}
|
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()
|
g.loadUIButtons()
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"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/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||||
|
@ -17,6 +18,7 @@ import (
|
||||||
// Inventory represents the inventory
|
// Inventory represents the inventory
|
||||||
type Inventory struct {
|
type Inventory struct {
|
||||||
asset *d2asset.AssetManager
|
asset *d2asset.AssetManager
|
||||||
|
item *diablo2item.ItemFactory
|
||||||
uiManager *d2ui.UIManager
|
uiManager *d2ui.UIManager
|
||||||
frame *d2ui.Sprite
|
frame *d2ui.Sprite
|
||||||
panel *d2ui.Sprite
|
panel *d2ui.Sprite
|
||||||
|
@ -34,13 +36,16 @@ type Inventory struct {
|
||||||
|
|
||||||
// NewInventory creates an inventory instance and returns a pointer to it
|
// NewInventory creates an inventory instance and returns a pointer to it
|
||||||
func NewInventory(asset *d2asset.AssetManager, ui *d2ui.UIManager,
|
func NewInventory(asset *d2asset.AssetManager, ui *d2ui.UIManager,
|
||||||
record *d2datadict.InventoryRecord) *Inventory {
|
record *d2records.InventoryRecord) *Inventory {
|
||||||
hoverLabel := ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic)
|
hoverLabel := ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic)
|
||||||
hoverLabel.Alignment = d2gui.HorizontalAlignCenter
|
hoverLabel.Alignment = d2gui.HorizontalAlignCenter
|
||||||
|
|
||||||
|
itemFactory, _ := diablo2item.NewItemFactory(asset) // TODO handle errors
|
||||||
|
|
||||||
return &Inventory{
|
return &Inventory{
|
||||||
asset: asset,
|
asset: asset,
|
||||||
uiManager: ui,
|
uiManager: ui,
|
||||||
|
item: itemFactory,
|
||||||
grid: NewItemGrid(asset, ui, record),
|
grid: NewItemGrid(asset, ui, record),
|
||||||
originX: record.Panel.Left,
|
originX: record.Panel.Left,
|
||||||
hoverLabel: hoverLabel,
|
hoverLabel: hoverLabel,
|
||||||
|
@ -74,28 +79,52 @@ func (g *Inventory) Load() {
|
||||||
g.frame, _ = g.uiManager.NewSprite(d2resource.Frame, d2resource.PaletteSky)
|
g.frame, _ = g.uiManager.NewSprite(d2resource.Frame, d2resource.PaletteSky)
|
||||||
|
|
||||||
g.panel, _ = g.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
|
g.panel, _ = g.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
|
||||||
items := []InventoryItem{
|
|
||||||
diablo2item.NewItem("kit", "Crimson", "of the Bat", "of Frost").Identify(),
|
// TODO: remove this item test code
|
||||||
diablo2item.NewItem("rin", "Steel", "of Shock").Identify(),
|
testInventoryCodes := [][]string{
|
||||||
diablo2item.NewItem("jav").Identify(),
|
{"kit", "Crimson", "of the Bat", "of Frost"},
|
||||||
diablo2item.NewItem("buc").Identify(),
|
{"rin", "Steel", "of Shock"},
|
||||||
// diablo2item.NewItem("Arctic Furs", "qui"),
|
{"jav"},
|
||||||
// TODO: Load the player's actual items
|
{"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
|
// TODO: Load the player's actual items
|
||||||
|
|
||||||
_, err := g.grid.Add(items...)
|
_, err := g.grid.Add(inventoryItems...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("could not add items to the inventory, err: %v\n", err)
|
fmt.Printf("could not add items to the inventory, err: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"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/d2enum"
|
||||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ type ItemGrid struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewItemGrid(asset *d2asset.AssetManager, ui *d2ui.UIManager,
|
func NewItemGrid(asset *d2asset.AssetManager, ui *d2ui.UIManager,
|
||||||
record *d2datadict.InventoryRecord) *ItemGrid {
|
record *d2records.InventoryRecord) *ItemGrid {
|
||||||
grid := record.Grid
|
grid := record.Grid
|
||||||
|
|
||||||
return &ItemGrid{
|
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…
Reference in New Issue
Block a user