diff --git a/d2app/app.go b/d2app/app.go index ea3d0a9a..be13a338 100644 --- a/d2app/app.go +++ b/d2app/app.go @@ -239,6 +239,11 @@ func (a *App) loadDataDict() error { {d2resource.Skills, d2datadict.LoadSkills}, {d2resource.Properties, d2datadict.LoadProperties}, {d2resource.SkillDesc, d2datadict.LoadSkillDescriptions}, + {d2resource.ItemTypes, d2datadict.LoadItemTypes}, + {d2resource.BodyLocations, d2datadict.LoadBodyLocations}, + {d2resource.Sets, d2datadict.LoadSets}, + {d2resource.SetItems, d2datadict.LoadSetItems}, + {d2resource.AutoMagic, d2datadict.LoadAutoMagicRecords}, } d2datadict.InitObjectRecords() diff --git a/d2common/d2data/d2datadict/automagic.go b/d2common/d2data/d2datadict/automagic.go new file mode 100644 index 00000000..9a992be5 --- /dev/null +++ b/d2common/d2data/d2datadict/automagic.go @@ -0,0 +1,206 @@ +package d2datadict + +import ( + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" +) + +// AutoMagicRecord describes rules for automatically generating magic properties when spawning +// items +type AutoMagicRecord struct { + // 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 + + // 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 // rare + + // 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 // level + + // MaxSpawnLevel + // this field accepts numeric values and specifies the maximum level beyond which the automod + // stop spawning. + MaxSpawnLevel int // maxlevel + + // 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 // classlevelreq + + // 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 + + // 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 + + // ModParam, min, max + // Parameter, min, and max values for the property + ModParam [3]int + ModMin [3]int + ModMax [3]int + + // transform + // It is a boolean value whichallows the colorization of the items. + Transform bool + + // PaletteTransform + // If transform is set to 1 then the item will be colored with the chosen color code, + // taken from Colors.txt + PaletteTransform int // transformcolor + + // 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 + + // ExcludeItemCodes + // etype 1 to etype3 + // 'Exclude type' . This field prevents certain mods from spawning on specific item codes. + ExcludeItemCodes [3]string + + // CostDivide + // Numeric value that acts as divisor for the item price. + CostDivide int // divide + + // CostMultiply + // Numeric value that acts as multiplier for the item price. + CostMultiply int // multiply + + // CostAdd + // Numeric value that acts as a flat sum added to the item price. + CostAdd int // add +} + +// 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 := d2common.LoadDataDictionary(file) + + for d.Next() { + record := &AutoMagicRecord{ + Name: d.String("Name"), + Version: d.Number("version"), + Spawnable: d.Number("spawnable") > 0, + SpawnOnRare: d.Number("rare") > 0, + MinSpawnLevel: d.Number("level"), + MaxSpawnLevel: d.Number("maxlevel"), + LevelRequirement: d.Number("levelreq"), + Class: charCodeMap[d.String("class")], + ClassLevelRequirement: d.Number("classlevelreq"), + Frequency: d.Number("frequency"), + Group: d.Number("group"), + ModCode: [3]string{ + d.String("mod1code"), + d.String("mod2code"), + d.String("mod3code"), + }, + ModParam: [3]int{ + d.Number("mod1param"), + d.Number("mod2param"), + d.Number("mod3param"), + }, + ModMin: [3]int{ + d.Number("mod1min"), + d.Number("mod2min"), + d.Number("mod3min"), + }, + ModMax: [3]int{ + d.Number("mod1max"), + d.Number("mod2max"), + d.Number("mod3max"), + }, + Transform: d.Number("transform") > 0, + PaletteTransform: d.Number("transformcolor"), + IncludeItemCodes: [7]string{ + d.String("itype1"), + d.String("itype2"), + d.String("itype3"), + d.String("itype4"), + d.String("itype5"), + d.String("itype6"), + d.String("itype7"), + }, + ExcludeItemCodes: [3]string{ + d.String("etype1"), + d.String("etype2"), + d.String("etype3"), + }, + CostDivide: d.Number("divide"), + CostMultiply: d.Number("multiply"), + CostAdd: d.Number("add"), + } + + AutoMagic = append(AutoMagic, record) + } + + if d.Err != nil { + panic(d.Err) + } + + log.Printf("Loaded %d AutoMagic records", len(AutoMagic)) +} diff --git a/d2common/d2data/d2datadict/body_locations.go b/d2common/d2data/d2datadict/body_locations.go new file mode 100644 index 00000000..c7ec00bd --- /dev/null +++ b/d2common/d2data/d2datadict/body_locations.go @@ -0,0 +1,37 @@ +package d2datadict + +import ( + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" +) + +// 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 := d2common.LoadDataDictionary(file) + for d.Next() { + location := &BodyLocationRecord{ + Name: d.String("Name"), + Code: d.String("Code"), + } + BodyLocations[location.Code] = location + } + + if d.Err != nil { + panic(d.Err) + } + + log.Printf("Loaded %d Body Location records", len(BodyLocations)) +} diff --git a/d2common/d2data/d2datadict/item_types.go b/d2common/d2data/d2datadict/item_types.go new file mode 100644 index 00000000..701b7149 --- /dev/null +++ b/d2common/d2data/d2datadict/item_types.go @@ -0,0 +1,257 @@ +package d2datadict + +import ( + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" +) + +// 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 + + // 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 + + // 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 + + // 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 + + // 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 + + // 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 + + // 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 +} + +// 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 := d2common.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.Name] = itemType + } + + if d.Err != nil { + panic(d.Err) + } + + log.Printf("Loaded %d ItemType records", len(ItemTypes)) +} diff --git a/d2common/d2data/d2datadict/set_items.go b/d2common/d2data/d2datadict/set_items.go new file mode 100644 index 00000000..51c390dc --- /dev/null +++ b/d2common/d2data/d2datadict/set_items.go @@ -0,0 +1,275 @@ +package d2datadict + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "log" +) + +// SetItemRecord represents a set item +type SetItemRecord struct { + // StringTableKey (index) + // string key to item's name in a .tbl file + StringTableKey string + + // SetKey (set) + // string key to the index field in Sets.txt - the set the item is a part of. + SetKey string + + // ItemCode (item) + // base item code of this set item (matches code field in Weapons.txt, Armor.txt or Misc.txt files). + ItemCode string + + // Rarity + // Chance to pick this set item if more then one set item of the same base item exist, + // this uses the common rarity/total_rarity formula, so if you have two set rings, + // one with a rarity of 100 the other with a rarity of 1, + // then the first will drop 100/101 percent of the time ( + // 99%) and the other will drop 1/101 percent of the time (1%), + // rarity can be anything between 0 and 255. + Rarity int + + // QualityLevel (lvl) + // The quality level of this set item, monsters, cube recipes, vendors, + // objects and the like most be at least this level or higher to be able to drop this item, + // otherwise they would drop a magical item with twice normal durability. + QualityLevel int + + // RequiredLevel ("lvl req") + // The character level required to use this set item. + RequiredLevel int + + // CharacterPaletteTransform (chrtransform) + // Palette shift to apply to the the DCC component-file and the DC6 flippy-file ( + // whenever or not the color shift will apply is determined by Weapons.txt, + // Armor.txt or Misc.txt). This is an ID pointer from Colors.txt. + CharacterPaletteTransform int + + // InventoryPaletteTransform (invtransform) + // Palette shift to apply to the the DC6 inventory-file ( + // whenever or not the color shift will apply is determined by Weapons.txt, + // Armor.txt or Misc.txt). This is an ID pointer from Colors.txt. + InventoryPaletteTransform int + + // InvFile + // Overrides the invfile and setinvfile specified in Weapons.txt, + // Armor.txt or Misc.txt for the base item. + // This field contains the file name of the DC6 inventory graphic (without the .dc6 extension). + InvFile string + + // FlippyFile + // Overrides the flippyfile specified in Weapons.txt, Armor.txt or Misc.txt for the base item. + // This field contains the file name of the DC6 flippy animation (without the .dc6 extension). + FlippyFile string + + // DropSound + // Overrides the dropsound (the sound played when the item hits the ground) specified in Weapons. + // txt, Armor.txt or Misc.txt for the base item. This field contains an ID pointer from Sounds.txt. + DropSound string + + // DropSfxFrame + // How many frames after the flippy animation starts playing will the associated drop sound start + // to play. This overrides the values in Weapons.txt, Armor.txt or Misc.txt. + DropSfxFrame int + + // UseSound + // Overrides the usesound (the sound played when the item is consumed by the player) specified in + // Weapons.txt, Armor.txt or Misc.txt for the base item. + // This field contains an ID pointer from Sounds.txt. + UseSound string + + // CostMult ("cost mult") + // The base item's price is multiplied by this value when sold, repaired or bought from a vendor. + CostMult int + + // CostAdd ("cost add") + // After the price has been multiplied, this amount of gold is added to the price on top. + CostAdd int + + // AddFn ("add func") + // a property mode field that controls how the variable attributes will appear and be functional + // on a set item. See the appendix for further details about this field's effects. + AddFn int + + // Prop (prop1 to prop9) + // An ID pointer of a property from Properties.txt, + // these columns control each of the nine different fixed ( + // blue) modifiers a set item can grant you at most. + Prop [9]string + + // Par (par1 to par9) + // 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. + Par [9]int + + // Min, Max (min1 to min9, max1 to max9) + // Minimum value to assign to the associated (blue) property. + // Certain properties have special interpretations based on stat encoding (e.g. + // chance-to-cast and charged skills). See the File Guide for Properties.txt and ItemStatCost. + // txt for further details. + Min [9]int + Max [9]int + + // APropA, APropB (aprop1a,aprop1b to aprop5a,aprop5b) + // An ID pointer of a property from Properties.txt, + // these columns control each of the five pairs of different variable ( + // green) modifiers a set item can grant you at most. + APropA [5]string + APropB [5]string + + // AParA, AParB (apar1a,apar1b to apar5a,apar5b) + // 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. + AParA [5]int + AParB [5]int + + // AMinA, AMinB, AMaxA, AMaxB (amin1a,amin1b to amin5a,amin5b) + // Minimum value to assign to the associated property. + // Certain properties have special interpretations based on stat encoding (e.g. + // chance-to-cast and charged skills). See the File Guide for Properties.txt and ItemStatCost. + // txt for further details. + AMinA [5]int + AMinB [5]int + AMaxA [5]int + AMaxB [5]int +} + +// SetItems holds all of the SetItemRecords +var SetItems []*SetItemRecord //nolint:gochecknoglobals // Currently global by design, only written once + +// LoadSetItems loads all of the SetItemRecords from SetItems.txt +func LoadSetItems(file []byte) { + SetItems = make([]*SetItemRecord, 0) + + d := d2common.LoadDataDictionary(file) + + for d.Next() { + record := &SetItemRecord{ + StringTableKey: d.String("index"), + SetKey: d.String("set"), + ItemCode: d.String("item"), + Rarity: d.Number("rarity"), + QualityLevel: d.Number("lvl"), + RequiredLevel: d.Number("lvl req"), + CharacterPaletteTransform: d.Number("chrtransform"), + InventoryPaletteTransform: d.Number("invtransform"), + InvFile: d.String("invfile"), + FlippyFile: d.String("flippyfile"), + DropSound: d.String("dropsound"), + DropSfxFrame: d.Number("dropsfxframe"), + UseSound: d.String("usesound"), + CostMult: d.Number("cost mult"), + CostAdd: d.Number("cost add"), + AddFn: d.Number("add func"), + Prop: [9]string{ + d.String("prop1"), + d.String("prop2"), + d.String("prop3"), + d.String("prop4"), + d.String("prop5"), + d.String("prop6"), + d.String("prop7"), + d.String("prop8"), + d.String("prop9"), + }, + Par: [9]int{ + d.Number("par1"), + d.Number("par2"), + d.Number("par3"), + d.Number("par4"), + d.Number("par5"), + d.Number("par6"), + d.Number("par7"), + d.Number("par8"), + d.Number("par9"), + }, + Min: [9]int{ + d.Number("min1"), + d.Number("min2"), + d.Number("min3"), + d.Number("min4"), + d.Number("min5"), + d.Number("min6"), + d.Number("min7"), + d.Number("min8"), + d.Number("min9"), + }, + Max: [9]int{ + d.Number("max1"), + d.Number("max2"), + d.Number("max3"), + d.Number("max4"), + d.Number("max5"), + d.Number("max6"), + d.Number("max7"), + d.Number("max8"), + d.Number("max9"), + }, + APropA: [5]string{ + d.String("aprop1a"), + d.String("aprop2a"), + d.String("aprop3a"), + d.String("aprop4a"), + d.String("aprop5a"), + }, + APropB: [5]string{ + d.String("aprop1b"), + d.String("aprop2b"), + d.String("aprop3b"), + d.String("aprop4b"), + d.String("aprop5b"), + }, + AParA: [5]int{ + d.Number("apar1a"), + d.Number("apar2a"), + d.Number("apar3a"), + d.Number("apar4a"), + d.Number("apar5a"), + }, + AParB: [5]int{ + d.Number("apar1b"), + d.Number("apar2b"), + d.Number("apar3b"), + d.Number("apar4b"), + d.Number("apar5b"), + }, + AMinA: [5]int{ + d.Number("amin1a"), + d.Number("amin2a"), + d.Number("amin3a"), + d.Number("amin4a"), + d.Number("amin5a"), + }, + AMinB: [5]int{ + d.Number("amin1b"), + d.Number("amin2b"), + d.Number("amin3b"), + d.Number("amin4b"), + d.Number("amin5b"), + }, + AMaxA: [5]int{ + d.Number("amax1a"), + d.Number("amax2a"), + d.Number("amax3a"), + d.Number("amax4a"), + d.Number("amax5a"), + }, + AMaxB: [5]int{ + d.Number("amax1b"), + d.Number("amax2b"), + d.Number("amax3b"), + d.Number("amax4b"), + d.Number("amax5b"), + }, + } + + SetItems = append(SetItems, record) + } + + if d.Err != nil { + panic(d.Err) + } + + log.Printf("Loaded %d SetItem records", len(SetItems)) +} diff --git a/d2common/d2data/d2datadict/sets.go b/d2common/d2data/d2datadict/sets.go new file mode 100644 index 00000000..56c06d02 --- /dev/null +++ b/d2common/d2data/d2datadict/sets.go @@ -0,0 +1,190 @@ +package d2datadict + +import ( + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" +) + +type SetRecord struct { + // index + // String key linked to by the set field in SetItems. + // txt - used to tie all of the set's items to the same set. + Key string + + // name + // String key to item's name in a .tbl file. + StringTableKey string + + // Version 0 for vanilla, 100 for LoD expansion + Version int + + // Level + // set level, perhaps intended as a minimum level for partial or full attributes to appear + // (reference only, not loaded into game). + Level int + + // PCodeA, PCodeB -- PCode2a,PCode2b to PCode5a,PCode5b + // An ID pointer of a property from Properties.txt, + // these columns control each of the five pairs of different partial set modifiers a set item can + // grant you at most. + PCodeA [4]string + PCodeB [4]string + + // PParamA, PParamB -- PParam2[a|b] to PParam5[a|b] + // 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. + PParamA [4]int + PParamB [4]int + + // PMinA, PMaxA, PMinB, PMaxB -- P[Min|Max]2[a|b] to P[Min|Max]5[a|b] + // Minimum value to assign to the associated property. + // Certain properties have special interpretations based on stat encoding (e.g. + // chance-to-cast and charged skills). See the File Guides for Properties.txt and ItemStatCost. + // txt for further details. + PMinA [4]int + PMaxA [4]int + PMinB [4]int + PMaxB [4]int + + // FCode -- FCode1 to FCode8 + // An ID pointer of a property from Properties.txt, + // these columns control each of the eight different full set modifiers a set item can grant you + // at most. + FCode [8]string + + // FParam -- FParam1 to FParam8 + // 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. + FParam [8]int + + // FMin -- FMin1 to FMin8 + // Minimum value to assign to the associated property. + // Certain properties have special interpretations based on stat encoding (e.g. + // chance-to-cast and charged skills). See the File Guides for Properties.txt and ItemStatCost. + // txt for further details. + FMin [8]int + + // FMax -- FMax1 to FMax8 + // Maximum value to assign to the associated property. + // Certain properties have special interpretations based on stat encoding (e.g. + // chance-to-cast and charged skills). See the File Guides for Properties.txt and ItemStatCost. + // txt for further details. + FMax [8]int +} + +// SetRecords contain the set records from sets.txt +var SetRecords map[string]*SetRecord + +// LoadSetRecords loads set records from sets.txt +func LoadSets(file []byte) { + SetRecords = make(map[string]*SetRecord) + + d := d2common.LoadDataDictionary(file) + for d.Next() { + record := &SetRecord{ + Key: d.String("index"), + StringTableKey: d.String("name"), + Version: d.Number("version"), + Level: d.Number("level"), + PCodeA: [4]string{ + d.String("PCode2a"), + d.String("PCode3a"), + d.String("PCode4a"), + d.String("PCode5a"), + }, + PCodeB: [4]string{ + d.String("PCode2b"), + d.String("PCode3b"), + d.String("PCode4b"), + d.String("PCode5b"), + }, + PParamA: [4]int{ + d.Number("PParam2a"), + d.Number("PParam3a"), + d.Number("PParam4a"), + d.Number("PParam5a"), + }, + PParamB: [4]int{ + d.Number("PParam2b"), + d.Number("PParam3b"), + d.Number("PParam4b"), + d.Number("PParam5b"), + }, + PMinA: [4]int{ + d.Number("PMin2a"), + d.Number("PMin3a"), + d.Number("PMin4a"), + d.Number("PMin5a"), + }, + PMinB: [4]int{ + d.Number("PMin2b"), + d.Number("PMin3b"), + d.Number("PMin4b"), + d.Number("PMin5b"), + }, + PMaxA: [4]int{ + d.Number("PMax2a"), + d.Number("PMax3a"), + d.Number("PMax4a"), + d.Number("PMax5a"), + }, + PMaxB: [4]int{ + d.Number("PMax2b"), + d.Number("PMax3b"), + d.Number("PMax4b"), + d.Number("PMax5b"), + }, + FCode: [8]string{ + d.String("FCode1"), + d.String("FCode2"), + d.String("FCode3"), + d.String("FCode4"), + d.String("FCode5"), + d.String("FCode6"), + d.String("FCode7"), + d.String("FCode9"), + }, + FParam: [8]int{ + d.Number("FParam1"), + d.Number("FParam2"), + d.Number("FParam3"), + d.Number("FParam4"), + d.Number("FParam5"), + d.Number("FParam6"), + d.Number("FParam7"), + d.Number("FParam9"), + }, + FMin: [8]int{ + d.Number("FMin1"), + d.Number("FMin2"), + d.Number("FMin3"), + d.Number("FMin4"), + d.Number("FMin5"), + d.Number("FMin6"), + d.Number("FMin7"), + d.Number("FMin9"), + }, + FMax: [8]int{ + d.Number("FMax1"), + d.Number("FMax2"), + d.Number("FMax3"), + d.Number("FMax4"), + d.Number("FMax5"), + d.Number("FMax6"), + d.Number("FMax7"), + d.Number("FMax9"), + }, + } + + SetRecords[record.Key] = record + } + + if d.Err != nil { + panic(d.Err) + } + + log.Printf("Loaded %d Sets records", len(SetRecords)) +} diff --git a/d2common/d2resource/resource_paths.go b/d2common/d2resource/resource_paths.go index fcacc142..a2810287 100644 --- a/d2common/d2resource/resource_paths.go +++ b/d2common/d2resource/resource_paths.go @@ -180,6 +180,11 @@ const ( ObjectDetails = "/data/global/excel/Objects.txt" SoundSettings = "/data/global/excel/Sounds.txt" ItemStatCost = "/data/global/excel/ItemStatCost.txt" + ItemTypes = "/data/global/excel/ItemTypes.txt" + Sets = "/data/global/excel/Sets.txt" + SetItems = "/data/global/excel/SetItems.txt" + AutoMagic = "/data/global/excel/automagic.txt" + BodyLocations = "/data/global/excel/bodylocs.txt" Properties = "/data/global/excel/Properties.txt" Hireling = "/data/global/excel/hireling.txt" DifficultyLevels = "/data/global/excel/difficultylevels.txt"