1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-03 22:40:43 +00:00
OpenDiablo2/d2common/d2data/d2datadict/item_types.go

358 lines
12 KiB
Go
Raw Normal View History

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]
}