Removing the rest of the d2data singletons (#742)

* removing objects records from d2datadict

* removing Overlay singleton from d2datadict

* remove PetTypes singleton from d2datadict

* remove PlayerClass singleton from d2datadict

* removed PlrModes singleton from d2datadict

* removed Properties singleton from d2datadict

* removed ItemQuality singleton from d2datadict

* removed RarePrefix and RareSuffix singletons from d2datadict

* removed States singleton from d2datadict

* removed Runewords singleton from d2datadict

* removed Sets and SetItems singletons from d2datadict

* remoed Shrines singleton from d2datadict

* removed UniqueItems singleton from d2datadict

* removed SuperUniques singleton from d2datadict

* removed TreasureClass singleton from d2datadict

* removed UniqueAppellation singleton from d2datadict

* removed d2datadict

* removed data dict init from d2app, this has moved to asset manager init
This commit is contained in:
gravestench 2020-09-20 17:30:27 -07:00 committed by GitHub
parent fc87b2be7a
commit be354f139b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 90 additions and 10627 deletions

View File

@ -21,8 +21,6 @@ import (
"golang.org/x/image/colornames"
"gopkg.in/alecthomas/kingpin.v2"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
@ -186,10 +184,6 @@ func (a *App) initialize() error {
config := d2config.Config
a.audio.SetVolumes(config.BgmVolume, config.SfxVolume)
if err := a.loadDataDict(); err != nil {
return err
}
if err := a.loadStrings(); err != nil {
return err
}
@ -218,48 +212,6 @@ func (a *App) loadStrings() error {
return nil
}
func (a *App) loadDataDict() error {
entries := []struct {
path string
loader func(data []byte)
}{
{d2resource.ObjectType, d2datadict.LoadObjectTypes},
{d2resource.ObjectDetails, d2datadict.LoadObjects},
{d2resource.UniqueItems, d2datadict.LoadUniqueItems},
{d2resource.AnimationData, d2data.LoadAnimationData},
{d2resource.Overlays, d2datadict.LoadOverlays},
{d2resource.QualityItems, d2datadict.LoadQualityItems},
{d2resource.Runes, d2datadict.LoadRunewords},
{d2resource.SuperUniques, d2datadict.LoadSuperUniques},
{d2resource.Properties, d2datadict.LoadProperties},
{d2resource.Sets, d2datadict.LoadSetRecords},
{d2resource.SetItems, d2datadict.LoadSetItems},
{d2resource.TreasureClass, d2datadict.LoadTreasureClassRecords},
{d2resource.States, d2datadict.LoadStates},
{d2resource.Shrines, d2datadict.LoadShrines},
{d2resource.PlrMode, d2datadict.LoadPlrModes},
{d2resource.PetType, d2datadict.LoadPetTypes},
{d2resource.UniqueAppellation, d2datadict.LoadUniqueAppellations},
{d2resource.PlayerClass, d2datadict.LoadPlayerClasses},
{d2resource.ObjectGroup, d2datadict.LoadObjectGroups},
{d2resource.RarePrefix, d2datadict.LoadRareItemPrefixRecords},
{d2resource.RareSuffix, d2datadict.LoadRareItemSuffixRecords},
}
d2datadict.InitObjectRecords()
for _, entry := range entries {
data, err := a.asset.LoadFile(entry.path)
if err != nil {
return err
}
entry.loader(data)
}
return nil
}
func (a *App) renderDebug(target d2interface.Surface) error {
if !a.showFPS {
return nil

View File

@ -1,9 +1,10 @@
package d2data
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
)
const (
@ -24,11 +25,11 @@ type AnimationDataRecord struct {
}
// AnimationData represents all of the animation data records, mapped by the COF index
var AnimationData map[string][]*AnimationDataRecord //nolint:gochecknoglobals // Currently global by design
type AnimationData map[string][]*AnimationDataRecord
// LoadAnimationData loads the animation data table into the global AnimationData dictionary
func LoadAnimationData(rawData []byte) {
AnimationData = make(map[string][]*AnimationDataRecord)
func LoadAnimationData(rawData []byte) AnimationData {
animdata := make(AnimationData)
streamReader := d2datautils.CreateStreamReader(rawData)
for !streamReader.EOF() {
@ -43,13 +44,15 @@ func LoadAnimationData(rawData []byte) {
data.Flags = streamReader.ReadBytes(numFlagBytes)
cofIndex := strings.ToLower(data.COFName)
if _, found := AnimationData[cofIndex]; !found {
AnimationData[cofIndex] = make([]*AnimationDataRecord, 0)
if _, found := animdata[cofIndex]; !found {
animdata[cofIndex] = make([]*AnimationDataRecord, 0)
}
AnimationData[cofIndex] = append(AnimationData[cofIndex], data)
animdata[cofIndex] = append(animdata[cofIndex], data)
}
}
log.Printf("Loaded %d animation data records", len(AnimationData))
log.Printf("Loaded %d animation data records", len(animdata))
return animdata
}

View File

@ -1,7 +0,0 @@
package d2datadict
// these show up in a lot of txt files where blizzard added LoD expansion stuff
const (
expansion = "Expansion"
expansionCode = 100
)

View File

@ -1,4 +0,0 @@
// Package d2datadict parses txt files as data dictionaries and exports records arrays
// For the Diablo II MPQ's, data dictionaries are the tab-separated value txt files
// found in `data/global/excel`
package d2datadict

View File

@ -1,49 +0,0 @@
package d2datadict
import (
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
)
func mapHeaders(line string) map[string]int {
m := make(map[string]int)
r := strings.Split(line, "\t")
for index, header := range r {
m[header] = index
}
return m
}
func mapLoadInt(r *[]string, mapping map[string]int, field string) int {
index, ok := (mapping)[field]
if ok {
return d2util.StringToInt(d2util.EmptyToZero(d2util.AsterToEmpty((*r)[index])))
}
return 0
}
func mapLoadString(r *[]string, mapping map[string]int, field string) string {
index, ok := (mapping)[field]
if ok {
return d2util.AsterToEmpty((*r)[index])
}
return ""
}
func mapLoadBool(r *[]string, mapping map[string]int, field string) bool {
return mapLoadInt(r, mapping, field) == 1
}
func mapLoadUint8(r *[]string, mapping map[string]int, field string) uint8 {
index, ok := (mapping)[field]
if ok {
return d2util.StringToUint8(d2util.EmptyToZero(d2util.AsterToEmpty((*r)[index])))
}
return 0
}

View File

@ -1,112 +0,0 @@
package d2datadict
import (
"fmt"
"log"
"strconv"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
const (
objectsGroupSize = 7
memberDensityMin = 0
memberDensityMax = 125
memberProbabilityMin = 0
memberProbabilityMax = 100
expansionDataMarker = "EXPANSION"
)
// ObjectGroupRecord represents a single line in objgroup.txt.
// Information has been gathered from [https://d2mods.info/forum/kb/viewarticle?a=394].
type ObjectGroupRecord struct {
// GroupName is the name of the group.
GroupName string
// Offset is the ID of the group, referred to by Levels.txt.
Offset int
// Members are the objects in the group.
Members *[objectsGroupSize]ObjectGroupMember
// Shrines determines whether this is a group of shrines.
// Note: for shrine groups, densities must be set to 0.
Shrines bool
// Wells determines whether this is a group of wells.
// Note: for wells groups, densities must be set to 0.
Wells bool
}
// ObjectGroupMember represents a member of an object group.
type ObjectGroupMember struct {
// ID is the ID of the object.
ID int
// Density is how densely the level is filled with the object.
// Must be below 125.
Density int
// Probability is the probability of this particular object being spawned.
// The value is a percentage.
Probability int
}
// ObjectGroups stores the ObjectGroupRecords.
var ObjectGroups map[int]*ObjectGroupRecord //nolint:gochecknoglobals // Currently global by design.
// LoadObjectGroups loads the ObjectGroupRecords into ObjectGroups.
func LoadObjectGroups(file []byte) {
ObjectGroups = make(map[int]*ObjectGroupRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
groupName := d.String("GroupName")
if groupName == expansionDataMarker {
continue
}
shrines, wells := d.Bool("Shrines"), d.Bool("Wells")
record := &ObjectGroupRecord{
GroupName: groupName,
Offset: d.Number("Offset"),
Members: createMembers(d, shrines || wells),
Shrines: shrines,
Wells: wells,
}
ObjectGroups[record.Offset] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d ObjectGroup records", len(ObjectGroups))
}
func createMembers(d *d2txt.DataDictionary, shrinesOrWells bool) *[objectsGroupSize]ObjectGroupMember {
var members [objectsGroupSize]ObjectGroupMember
for i := 0; i < objectsGroupSize; i++ {
suffix := strconv.Itoa(i)
members[i].ID = d.Number("ID" + suffix)
members[i].Density = d.Number("DENSITY" + suffix)
if members[i].Density < memberDensityMin || members[i].Density > memberDensityMax {
panic(fmt.Sprintf("Invalid object group member density: %v, in group: %v",
members[i].Density, d.String("GroupName"))) // Vanilla crashes when density is over 125.
}
if shrinesOrWells && members[i].Density != 0 {
panic(fmt.Sprintf("Shrine and well object groups must have densities set to 0, in group: %v", d.String("GroupName")))
}
members[i].Probability = d.Number("PROB" + suffix)
if members[i].Probability < memberProbabilityMin || members[i].Probability > memberProbabilityMax {
panic(fmt.Sprintf("Invalid object group member probability: %v, in group: %v",
members[i].Probability, d.String("GroupName")))
}
}
return &members
}

File diff suppressed because it is too large Load Diff

View File

@ -1,101 +0,0 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
// ObjectLookupRecord is a representation of a row from objects.txt
type ObjectLookupRecord struct {
Act int
Type d2enum.ObjectType
Id int //nolint:golint,stylecheck // ID is the right key
Name string
Description string
ObjectsTxtId int //nolint:golint,stylecheck // ID is the right key
MonstatsTxtId int //nolint:golint,stylecheck // ID is the right key
Direction int
Base string
Token string
Mode string
Class string
HD string
TR string
LG string
RA string
LA string
RH string
LH string
SH string
S1 string
S2 string
S3 string
S4 string
S5 string
S6 string
S7 string
S8 string
ColorMap string
Index int
}
// LookupObject looks up an object record
func LookupObject(act, typ, id int) *ObjectLookupRecord {
object := lookupObject(act, typ, id, indexedObjects)
if object == nil {
log.Panicf("Failed to look up object Act: %d, Type: %d, ID: %d", act, typ, id)
}
return object
}
func lookupObject(act, typ, id int, objects [][][]*ObjectLookupRecord) *ObjectLookupRecord {
if len(objects) < act {
return nil
}
if len(objects[act]) < typ {
return nil
}
if len(objects[act][typ]) < id {
return nil
}
return objects[act][typ][id]
}
// InitObjectRecords loads ObjectRecords
func InitObjectRecords() {
indexedObjects = indexObjects(objectLookups)
}
func indexObjects(objects []ObjectLookupRecord) [][][]*ObjectLookupRecord {
// Allocating 6 to allow Acts 1-5 without requiring a -1 at every read.
indexedObjects = make([][][]*ObjectLookupRecord, 6)
for i := range objects {
record := &objects[i]
if indexedObjects[record.Act] == nil {
// Likewise allocating 3 so a -1 isn't necessary.
indexedObjects[record.Act] = make([][]*ObjectLookupRecord, 3)
}
if indexedObjects[record.Act][record.Type] == nil {
// For simplicity, allocating with length 1000 then filling the values in by index.
// If ids in the dictionary ever surpass 1000, raise this number.
indexedObjects[record.Act][record.Type] = make([]*ObjectLookupRecord, 1000)
}
indexedObjects[record.Act][record.Type][record.Id] = record
}
return indexedObjects
}
// IndexedObjects is a slice of object records for quick lookups.
// nil checks should be done for uninitialized values at each level.
// [Act 1-5][Type 1-2][ID 0-855]
//nolint:gochecknoglobals // Currently global by design
var indexedObjects [][][]*ObjectLookupRecord

View File

@ -1,43 +0,0 @@
package d2datadict
import (
"testing"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
testify "github.com/stretchr/testify/assert"
)
// Verify the lookup returns the right object after indexing.
func TestIndexObjects(t *testing.T) {
assert := testify.New(t)
testObjects := []ObjectLookupRecord{
{Act: 1, Type: d2enum.ObjectTypeCharacter, Id: 0, Description: "Act1CharId0"},
{Act: 1, Type: d2enum.ObjectTypeCharacter, Id: 1, Description: "Act1CharId1"},
{Act: 1, Type: d2enum.ObjectTypeCharacter, Id: 2, Description: "Act1CharId2"},
{Act: 1, Type: d2enum.ObjectTypeCharacter, Id: 3, Description: "Act1CharId3"},
{Act: 1, Type: d2enum.ObjectTypeItem, Id: 0, Description: "Act1ItemId0"},
{Act: 1, Type: d2enum.ObjectTypeItem, Id: 1, Description: "Act1ItemId1"},
{Act: 1, Type: d2enum.ObjectTypeItem, Id: 2, Description: "Act1ItemId2"},
{Act: 1, Type: d2enum.ObjectTypeItem, Id: 3, Description: "Act1ItemId3"},
{Act: 2, Type: d2enum.ObjectTypeCharacter, Id: 0, Description: "Act2CharId0"},
{Act: 2, Type: d2enum.ObjectTypeCharacter, Id: 1, Description: "Act2CharId1"},
{Act: 2, Type: d2enum.ObjectTypeCharacter, Id: 2, Description: "Act2CharId2"},
{Act: 2, Type: d2enum.ObjectTypeCharacter, Id: 3, Description: "Act2CharId3"},
{Act: 2, Type: d2enum.ObjectTypeItem, Id: 0, Description: "Act2ItemId0"},
{Act: 2, Type: d2enum.ObjectTypeItem, Id: 1, Description: "Act2ItemId1"},
{Act: 2, Type: d2enum.ObjectTypeItem, Id: 2, Description: "Act2ItemId2"},
{Act: 2, Type: d2enum.ObjectTypeItem, Id: 3, Description: "Act2ItemId3"},
}
indexedTestObjects := indexObjects(testObjects)
typeCharacter := int(d2enum.ObjectTypeCharacter)
typeItem := int(d2enum.ObjectTypeItem)
assert.Equal("Act1CharId2", lookupObject(1, typeCharacter, 2, indexedTestObjects).Description)
assert.Equal("Act1ItemId0", lookupObject(1, typeItem, 0, indexedTestObjects).Description)
assert.Equal("Act2CharId3", lookupObject(2, typeCharacter, 3, indexedTestObjects).Description)
assert.Equal("Act2ItemId1", lookupObject(2, typeItem, 1, indexedTestObjects).Description)
}

View File

@ -1,41 +0,0 @@
package d2datadict
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
)
// ObjectTypeRecord is a representation of a row from objtype.txt
type ObjectTypeRecord struct {
Name string
Token string
}
// ObjectTypes contains the name and token for objects
//nolint:gochecknoglobals // Currently global by design, only written once
var ObjectTypes []ObjectTypeRecord
// LoadObjectTypes loads ObjectTypeRecords from objtype.txt
func LoadObjectTypes(objectTypeData []byte) {
streamReader := d2datautils.CreateStreamReader(objectTypeData)
count := streamReader.GetInt32()
ObjectTypes = make([]ObjectTypeRecord, count)
const (
nameSize = 32
tokenSize = 20
)
for i := range ObjectTypes {
nameBytes := streamReader.ReadBytes(nameSize)
tokenBytes := streamReader.ReadBytes(tokenSize)
ObjectTypes[i] = ObjectTypeRecord{
Name: strings.TrimSpace(strings.ReplaceAll(string(nameBytes), string(byte(0)), "")),
Token: strings.TrimSpace(strings.ReplaceAll(string(tokenBytes), string(byte(0)), "")),
}
}
log.Printf("Loaded %d object types", len(ObjectTypes))
}

View File

@ -1,367 +0,0 @@
package d2datadict
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
)
// An ObjectRecord represents the settings for one type of object from objects.txt
type ObjectRecord struct {
Index int // Line number in file, this is the actual index used for objects
FrameCount [8]int // how many frames does this mode have, 0 = skip
FrameDelta [8]int // what rate is the animation played at (256 = 100% speed)
LightDiameter [8]int
StartFrame [8]int
OrderFlag [8]int // 0 = object, 1 = floor, 2 = wall
Parm [8]int // unknown
Name string
Description string
// Don't use, get token from objtypes
token string // refers to what graphics this object uses
// Don't use, index by line number
id int //nolint:golint,stylecheck // unused, indexed by line number instead
SpawnMax int // unused?
TrapProbability int // unused
SizeX int
SizeY int
NTgtFX int // unknown
NTgtFY int // unknown
NTgtBX int // unknown
NTgtBY int // unknown
Orientation int // unknown (1=sw, 2=nw, 3=se, 4=ne)
Trans int // controls palette mapping
XOffset int // in pixels offset
YOffset int
TotalPieces int // selectable DCC components count
SubClass int // subclass of object:
// 1 = shrine
// 2 = obelisk
// 4 = portal
// 8 = container
// 16 = arcane sanctuary gateway
// 32 = well
// 64 = waypoint
// 128 = secret jails door
XSpace int // unknown
YSpace int
NameOffset int // pixels to offset the name from the animation pivot
OperateRange int // distance object can be used from, might be unused
ShrineFunction int // unused
Act int // what acts this object can appear in (15 = all three)
Damage int // amount of damage done by this (used depending on operatefn)
Left int // unknown, clickable bounding box?
Top int
Width int
Height int
OperateFn int // what function is called when the player clicks on the object
PopulateFn int // what function is used to spawn this object?
InitFn int // what function is run when the object is initialized?
ClientFn int // controls special audio-visual functions
// 'To ...' or 'trap door' when highlighting, not sure which is T/F
AutoMap int // controls how this object appears on the map
// 0 = it doesn't, rest of modes need to be analyzed
CycleAnimation [8]bool // probably whether animation loops
Selectable [8]bool // is this mode selectable
BlocksLight [8]bool
HasCollision [8]bool
HasAnimationMode [8]bool // 'Mode' in source, true if this mode is used
SelS [8]bool
IsAttackable bool // do we kick it when interacting
EnvEffect bool // unknown
IsDoor bool
BlockVisibility bool // only works with IsDoor
PreOperate bool // unknown
Draw bool // if false, object isn't drawn (shadow is still drawn and player can still select though)
SelHD bool // whether these DCC components are selectable
SelTR bool
SelLG bool
SelRA bool
SelLA bool
SelRH bool
SelLH bool
SelSH bool
MonsterOk bool // unknown
Restore bool // if true, object is stored in memory and will be retained if you leave and re-enter the area
Lockable bool
Gore bool // unknown, something with corpses
Sync bool // unknown
Flicker bool // light flickers if true
Beta bool // if true, appeared in the beta?
Overlay bool // unknown
CollisionSubst bool // unknown, controls some kind of special collision checking?
RestoreVirgins bool // if true, only restores unused objects (see Restore)
BlockMissile bool // if true, missiles collide with this
DrawUnder bool // if true, drawn as a floor tile is
OpenWarp bool // needs clarification, controls whether highlighting shows
LightRed byte // if lightdiameter is set, rgb of the light
LightGreen byte
LightBlue byte
}
//nolint:funlen // Makes no sense to split
// CreateObjectRecord parses a row from objects.txt into an object record
func createObjectRecord(props []string) ObjectRecord {
i := -1
inc := func() int {
i++
return i
}
result := ObjectRecord{
Name: props[inc()],
Description: props[inc()],
id: d2util.StringToInt(props[inc()]),
token: props[inc()],
SpawnMax: d2util.StringToInt(props[inc()]),
Selectable: [8]bool{
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
},
TrapProbability: d2util.StringToInt(props[inc()]),
SizeX: d2util.StringToInt(props[inc()]),
SizeY: d2util.StringToInt(props[inc()]),
NTgtFX: d2util.StringToInt(props[inc()]),
NTgtFY: d2util.StringToInt(props[inc()]),
NTgtBX: d2util.StringToInt(props[inc()]),
NTgtBY: d2util.StringToInt(props[inc()]),
FrameCount: [8]int{
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
},
FrameDelta: [8]int{
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
},
CycleAnimation: [8]bool{
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
},
LightDiameter: [8]int{
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
},
BlocksLight: [8]bool{
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
},
HasCollision: [8]bool{
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
},
IsAttackable: d2util.StringToUint8(props[inc()]) == 1,
StartFrame: [8]int{
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
},
EnvEffect: d2util.StringToUint8(props[inc()]) == 1,
IsDoor: d2util.StringToUint8(props[inc()]) == 1,
BlockVisibility: d2util.StringToUint8(props[inc()]) == 1,
Orientation: d2util.StringToInt(props[inc()]),
Trans: d2util.StringToInt(props[inc()]),
OrderFlag: [8]int{
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
},
PreOperate: d2util.StringToUint8(props[inc()]) == 1,
HasAnimationMode: [8]bool{
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
},
XOffset: d2util.StringToInt(props[inc()]),
YOffset: d2util.StringToInt(props[inc()]),
Draw: d2util.StringToUint8(props[inc()]) == 1,
LightRed: d2util.StringToUint8(props[inc()]),
LightGreen: d2util.StringToUint8(props[inc()]),
LightBlue: d2util.StringToUint8(props[inc()]),
SelHD: d2util.StringToUint8(props[inc()]) == 1,
SelTR: d2util.StringToUint8(props[inc()]) == 1,
SelLG: d2util.StringToUint8(props[inc()]) == 1,
SelRA: d2util.StringToUint8(props[inc()]) == 1,
SelLA: d2util.StringToUint8(props[inc()]) == 1,
SelRH: d2util.StringToUint8(props[inc()]) == 1,
SelLH: d2util.StringToUint8(props[inc()]) == 1,
SelSH: d2util.StringToUint8(props[inc()]) == 1,
SelS: [8]bool{
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
d2util.StringToUint8(props[inc()]) == 1,
},
TotalPieces: d2util.StringToInt(props[inc()]),
SubClass: d2util.StringToInt(props[inc()]),
XSpace: d2util.StringToInt(props[inc()]),
YSpace: d2util.StringToInt(props[inc()]),
NameOffset: d2util.StringToInt(props[inc()]),
MonsterOk: d2util.StringToUint8(props[inc()]) == 1,
OperateRange: d2util.StringToInt(props[inc()]),
ShrineFunction: d2util.StringToInt(props[inc()]),
Restore: d2util.StringToUint8(props[inc()]) == 1,
Parm: [8]int{
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
d2util.StringToInt(props[inc()]),
},
Act: d2util.StringToInt(props[inc()]),
Lockable: d2util.StringToUint8(props[inc()]) == 1,
Gore: d2util.StringToUint8(props[inc()]) == 1,
Sync: d2util.StringToUint8(props[inc()]) == 1,
Flicker: d2util.StringToUint8(props[inc()]) == 1,
Damage: d2util.StringToInt(props[inc()]),
Beta: d2util.StringToUint8(props[inc()]) == 1,
Overlay: d2util.StringToUint8(props[inc()]) == 1,
CollisionSubst: d2util.StringToUint8(props[inc()]) == 1,
Left: d2util.StringToInt(props[inc()]),
Top: d2util.StringToInt(props[inc()]),
Width: d2util.StringToInt(props[inc()]),
Height: d2util.StringToInt(props[inc()]),
OperateFn: d2util.StringToInt(props[inc()]),
PopulateFn: d2util.StringToInt(props[inc()]),
InitFn: d2util.StringToInt(props[inc()]),
ClientFn: d2util.StringToInt(props[inc()]),
RestoreVirgins: d2util.StringToUint8(props[inc()]) == 1,
BlockMissile: d2util.StringToUint8(props[inc()]) == 1,
DrawUnder: d2util.StringToUint8(props[inc()]) == 1,
OpenWarp: d2util.StringToUint8(props[inc()]) == 1,
AutoMap: d2util.StringToInt(props[inc()]),
}
return result
}
// Objects stores all of the ObjectRecords
//nolint:gochecknoglobals // Currently global by design, only written once
var Objects map[int]*ObjectRecord
// LoadObjects loads all objects from objects.txt
func LoadObjects(file []byte) {
Objects = make(map[int]*ObjectRecord)
data := strings.Split(string(file), "\r\n")[1:]
lineNumber := 0
for _, line := range data {
if line == "" {
continue
}
props := strings.Split(line, "\t")
if props[2] == "" {
continue // skip a line that doesn't have an id
}
rec := createObjectRecord(props)
rec.Index = lineNumber
Objects[lineNumber] = &rec
lineNumber++
}
log.Printf("Loaded %d objects", len(Objects))
}

View File

@ -1,100 +0,0 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
// The information has been gathered from [https://d2mods.info/forum/kb/viewarticle?a=465]
// OverlayRecord encapsulates information found in Overlay.txt
type OverlayRecord struct {
// Overlay name
Name string
// .dcc file found in Data/Globals/Overlays
Filename string
// XOffset, YOffset the x,y offset of the overlay
XOffset, YOffset int
// These values modify Y-axis placement
Height1 int
Height2 int
Height3 int
Height4 int
// AnimRate animation speed control
AnimRate int
// Trans controls overlay blending mode, check out the link for more info
// This should probably become an "enum" later on
Trans int
// Radius maximum for light
Radius int
// InitRadius Light radius increase per frame
InitRadius int
// Red, Green, Blue color for light
Red, Green, Blue uint8
// Version is 100 for expansion, 0 for vanilla
Version bool
// PreDraw controls overlay drawing precedence
PreDraw bool
// Unknown fields, commenting out for now
// NumDirections int
// LocalBlood int
// OneOfN int
// Dir bool
// Open bool
// Beta bool
// Apparently unused
// Character string
// LoopWaitTime int
// Frames int
}
// Overlays contains all of the OverlayRecords from Overlay.txt
var Overlays map[string]*OverlayRecord // nolint:gochecknoglobals // Currently global by design
// LoadOverlays loads overlay records from Overlay.txt
func LoadOverlays(file []byte) {
Overlays = make(map[string]*OverlayRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
record := &OverlayRecord{
Name: d.String("Overlay"),
Filename: d.String("Filename"),
Version: d.Bool("Version"),
PreDraw: d.Bool("PreDraw"),
XOffset: d.Number("Xoffset"),
YOffset: d.Number("Yoffset"),
Height1: d.Number("Height1"),
Height2: d.Number("Height1"),
Height3: d.Number("Height1"),
Height4: d.Number("Height1"),
AnimRate: d.Number("AnimRate"),
Trans: d.Number("Trans"),
InitRadius: d.Number("InitRadius"),
Radius: d.Number("Radius"),
Red: uint8(d.Number("Red")),
Green: uint8(d.Number("Green")),
Blue: uint8(d.Number("Blue")),
}
Overlays[record.Name] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d Overlay records", len(Overlays))
}

View File

@ -1,104 +0,0 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
// PetTypeRecord represents a single line in PetType.txt
// The information has been gathered from [https:// d2mods.info/forum/kb/viewarticle?a=355]
type PetTypeRecord struct {
// Name of the pet type, refferred by "pettype" in skills.txt
Name string
// Name text under the pet icon
IconName string
// .dc6 file for the pet's icon, located in /data/global/ui/hireables
BaseIcon string
// Alternative pet icon .dc6 file
MIcon1 string
MIcon2 string
MIcon3 string
MIcon4 string
// ID number of the pet type
ID int
// GroupID number of the group this pet belongs to
GroupID int
// BaseMax unknown what this does...
BaseMax int
// Pet icon type
IconType int
// Alternative pet index from monstats.txt
MClass1 int
MClass2 int
MClass3 int
MClass4 int
// Warp warps with the player, otherwise it dies
Warp bool
// Range the pet only die if the distance between the player and the pet exceeds 41 sub-tiles.
Range bool
// Unknown
PartySend bool
// Unsummon whether the pet can be unsummoned
Unsummon bool
// Automap whether the pet is displayed on the automap
Automap bool
// If true, the pet's HP will be displayed under the icon
DrawHP bool
}
// PetTypes stores the PetTypeRecords
var PetTypes map[string]*PetTypeRecord // nolint:gochecknoglobals // Currently global by design
// LoadPetTypes loads PetTypeRecords into PetTypes
func LoadPetTypes(file []byte) {
PetTypes = make(map[string]*PetTypeRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
record := &PetTypeRecord{
Name: d.String("pet type"),
ID: d.Number("idx"),
GroupID: d.Number("group"),
BaseMax: d.Number("basemax"),
Warp: d.Bool("warp"),
Range: d.Bool("range"),
PartySend: d.Bool("partysend"),
Unsummon: d.Bool("unsummon"),
Automap: d.Bool("automap"),
IconName: d.String("name"),
DrawHP: d.Bool("drawhp"),
IconType: d.Number("icontype"),
BaseIcon: d.String("baseicon"),
MClass1: d.Number("mclass1"),
MIcon1: d.String("micon1"),
MClass2: d.Number("mclass2"),
MIcon2: d.String("micon2"),
MClass3: d.Number("mclass3"),
MIcon3: d.String("micon3"),
MClass4: d.Number("mclass4"),
MIcon4: d.String("micon4"),
}
PetTypes[record.Name] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d PetType records", len(PetTypes))
}

View File

@ -1,45 +0,0 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
// PlayerClassRecord represents a single line from PlayerClass.txt
// Lookup table for class codes
type PlayerClassRecord struct {
// Name of the player class
Name string
// Code for the player class
Code string
}
// PlayerClasses stores the PlayerClassRecords
var PlayerClasses map[string]*PlayerClassRecord // nolint:gochecknoglobals // Currently global by design
// LoadPlayerClasses loads the PlayerClassRecords into PlayerClasses
func LoadPlayerClasses(file []byte) {
PlayerClasses = make(map[string]*PlayerClassRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
record := &PlayerClassRecord{
Name: d.String("Player Class"),
Code: d.String("Code"),
}
if record.Name == expansion {
continue
}
PlayerClasses[record.Name] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d PlayerClass records", len(PlayerClasses))
}

View File

@ -1,39 +0,0 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
// PlrModeRecord represents a single line in PlrMode.txt
type PlrModeRecord struct {
// Player animation mode name
Name string
// Player animation mode token
Token string
}
// PlrModes stores the PlrModeRecords
var PlrModes map[string]*PlrModeRecord //nolint:gochecknoglobals // Currently global by design
// LoadPlrModes loads PlrModeRecords into PlrModes
func LoadPlrModes(file []byte) {
PlrModes = make(map[string]*PlrModeRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
record := &PlrModeRecord{
Name: d.String("Name"),
Token: d.String("Token"),
}
PlrModes[record.Name] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d PlrMode records", len(PlrModes))
}

View File

@ -1,89 +0,0 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
// PropertyStatRecord contains stat information for a property
type PropertyStatRecord struct {
SetID int
Value int
FunctionID int
StatCode string
}
// PropertyRecord is a representation of a single row of properties.txt
type PropertyRecord struct {
Code string
Active string
Stats [7]*PropertyStatRecord
}
// Properties stores all of the PropertyRecords
var Properties map[string]*PropertyRecord //nolint:gochecknoglobals // Currently global by design, only written once
// LoadProperties loads gem records into a map[string]*PropertiesRecord
func LoadProperties(file []byte) {
Properties = make(map[string]*PropertyRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
prop := &PropertyRecord{
Code: d.String("code"),
Active: d.String("*done"),
Stats: [7]*PropertyStatRecord{
{
SetID: d.Number("set1"),
Value: d.Number("val1"),
FunctionID: d.Number("func1"),
StatCode: d.String("stat1"),
},
{
SetID: d.Number("set2"),
Value: d.Number("val2"),
FunctionID: d.Number("func2"),
StatCode: d.String("stat2"),
},
{
SetID: d.Number("set3"),
Value: d.Number("val3"),
FunctionID: d.Number("func3"),
StatCode: d.String("stat3"),
},
{
SetID: d.Number("set4"),
Value: d.Number("val4"),
FunctionID: d.Number("func4"),
StatCode: d.String("stat4"),
},
{
SetID: d.Number("set5"),
Value: d.Number("val5"),
FunctionID: d.Number("func5"),
StatCode: d.String("stat5"),
},
{
SetID: d.Number("set6"),
Value: d.Number("val6"),
FunctionID: d.Number("func6"),
StatCode: d.String("stat6"),
},
{
SetID: d.Number("set7"),
Value: d.Number("val7"),
FunctionID: d.Number("func7"),
StatCode: d.String("stat7"),
},
},
}
Properties[prop.Code] = prop
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d Property records", len(Properties))
}

View File

@ -1,85 +0,0 @@
package d2datadict
import (
"log"
"strconv"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
// QualityRecord represents a single row of QualityItems.txt, which controls
// properties for superior quality items
type QualityRecord struct {
NumMods int
Mod1Code string
Mod1Param int
Mod1Min int
Mod1Max int
Mod2Code string
Mod2Param int
Mod2Min int
Mod2Max int
// The following fields determine this row's applicability to
// categories of item.
Armor bool
Weapon bool
Shield bool
Thrown bool
Scepter bool
Wand bool
Staff bool
Bow bool
Boots bool
Gloves bool
Belt bool
Level int
Multiply int
Add int
}
// QualityItems stores all of the QualityRecords
var QualityItems map[string]*QualityRecord //nolint:gochecknoglobals // Currently global by design, only written once
// LoadQualityItems loads QualityItem records into a map[string]*QualityRecord
func LoadQualityItems(file []byte) {
QualityItems = make(map[string]*QualityRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
qual := &QualityRecord{
NumMods: d.Number("nummods"),
Mod1Code: d.String("mod1code"),
Mod1Param: d.Number("mod1param"),
Mod1Min: d.Number("mod1min"),
Mod1Max: d.Number("mod1max"),
Mod2Code: d.String("mod2code"),
Mod2Param: d.Number("mod2param"),
Mod2Min: d.Number("mod2min"),
Mod2Max: d.Number("mod2max"),
Armor: d.Bool("armor"),
Weapon: d.Bool("weapon"),
Shield: d.Bool("shield"),
Thrown: d.Bool("thrown"),
Scepter: d.Bool("scepter"),
Wand: d.Bool("wand"),
Staff: d.Bool("staff"),
Bow: d.Bool("bow"),
Boots: d.Bool("boots"),
Gloves: d.Bool("gloves"),
Belt: d.Bool("belt"),
Level: d.Number("level"),
Multiply: d.Number("multiply"),
Add: d.Number("add"),
}
QualityItems[strconv.Itoa(len(QualityItems))] = qual
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d QualityItems records", len(QualityItems))
}

View File

@ -1,59 +0,0 @@
package d2datadict
import (
"fmt"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
const (
numRarePrefixInclude = 7
fmtRarePrefixInclude = "itype%d"
numRarePrefixExclude = 4
fmtRarePrefixExclude = "etype%d"
)
// RareItemPrefixRecord is a name prefix for rare items (items with more than 2 affixes)
type RareItemPrefixRecord struct {
Name string
IncludedTypes []string
ExcludedTypes []string
}
// RarePrefixes is where all RareItemPrefixRecords are stored
var RarePrefixes []*RareItemPrefixRecord // nolint:gochecknoglobals // global by design
// LoadRareItemPrefixRecords loads the rare item prefix records from rareprefix.txt
func LoadRareItemPrefixRecords(file []byte) {
d := d2txt.LoadDataDictionary(file)
RarePrefixes = make([]*RareItemPrefixRecord, 0)
for d.Next() {
record := &RareItemPrefixRecord{
Name: d.String("name"),
IncludedTypes: make([]string, 0),
ExcludedTypes: make([]string, 0),
}
for idx := 1; idx <= numRarePrefixInclude; idx++ {
column := fmt.Sprintf(fmtRarePrefixInclude, idx)
if typeCode := d.String(column); typeCode != "" {
record.IncludedTypes = append(record.IncludedTypes, typeCode)
}
}
for idx := 1; idx <= numRarePrefixExclude; idx++ {
column := fmt.Sprintf(fmtRarePrefixExclude, idx)
if typeCode := d.String(column); typeCode != "" {
record.ExcludedTypes = append(record.ExcludedTypes, typeCode)
}
}
RarePrefixes = append(RarePrefixes, record)
}
log.Printf("Loaded %d RarePrefix records", len(RarePrefixes))
}

View File

@ -1,59 +0,0 @@
package d2datadict
import (
"fmt"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
const (
numRareSuffixInclude = 7
fmtRareSuffixInclude = "itype%d"
numRareSuffixExclude = 4
fmtRareSuffixExclude = "etype%d"
)
// RareItemSuffixRecord is a name suffix for rare items (items with more than 2 affixes)
type RareItemSuffixRecord struct {
Name string
IncludedTypes []string
ExcludedTypes []string
}
// RareSuffixes is where all RareItemSuffixRecords are stored
var RareSuffixes []*RareItemSuffixRecord // nolint:gochecknoglobals // global by design
// LoadRareItemSuffixRecords loads the rare item suffix records from raresuffix.txt
func LoadRareItemSuffixRecords(file []byte) {
d := d2txt.LoadDataDictionary(file)
RareSuffixes = make([]*RareItemSuffixRecord, 0)
for d.Next() {
record := &RareItemSuffixRecord{
Name: d.String("name"),
IncludedTypes: make([]string, 0),
ExcludedTypes: make([]string, 0),
}
for idx := 1; idx <= numRareSuffixInclude; idx++ {
column := fmt.Sprintf(fmtRareSuffixInclude, idx)
if typeCode := d.String(column); typeCode != "" {
record.IncludedTypes = append(record.IncludedTypes, typeCode)
}
}
for idx := 1; idx <= numRareSuffixExclude; idx++ {
column := fmt.Sprintf(fmtRareSuffixExclude, idx)
if typeCode := d.String(column); typeCode != "" {
record.ExcludedTypes = append(record.ExcludedTypes, typeCode)
}
}
RareSuffixes = append(RareSuffixes, record)
}
log.Printf("Loaded %d RareSuffix records", len(RareSuffixes))
}

View File

@ -1,131 +0,0 @@
package d2datadict
import (
"fmt"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
const (
numRunewordTypeInclude = 6
numRunewordTypeExclude = 3
numRunewordMaxSockets = 6
numRunewordProperties = 7
)
// format strings
const (
fmtTypeInclude = "itype%d"
fmtTypeExclude = "etype%d"
fmtRuneStr = "Rune%d"
fmtRunewordPropCode = "T1Code%d"
fmtRunewordPropParam = "T1Param%d"
fmtRunewordPropMin = "T1Min%d"
fmtRunewordPropMax = "T1Max%d"
)
// RunesRecord is a representation of a single row of runes.txt. It defines
// runewords available in the game.
type RunesRecord struct {
Name string
RuneName string // More of a note - the actual name should be read from the TBL files.
Complete bool // An enabled/disabled flag. Only "Complete" runewords work in game.
Server bool // Marks a runeword as only available on ladder, not single player or tcp/ip.
// The item types for includsion/exclusion for this runeword record
ItemTypes struct {
Include []string
Exclude []string
}
// Runes slice of ID pointers from Misc.txt, controls what runes are
// required to make the rune word and in what order they are to be socketed.
Runes []string
Properties []*runewordProperty
}
type runewordProperty struct {
// Code is the property code
Code string
// Param is either string or int, parameter for the property
Param string
// Min is the minimum value for the property
Min int
// Max is the maximum value for the property
Max int
}
// Runewords stores all of the RunesRecords
var Runewords map[string]*RunesRecord //nolint:gochecknoglobals // Currently global by design, only written once
// LoadRunewords loads runes records into a map[string]*RunesRecord
func LoadRunewords(file []byte) {
Runewords = make(map[string]*RunesRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
record := &RunesRecord{
Name: d.String("name"),
RuneName: d.String("Rune Name"),
Complete: d.Bool("complete"),
Server: d.Bool("server"),
ItemTypes: struct {
Include []string
Exclude []string
}{
Include: make([]string, 0),
Exclude: make([]string, 0),
},
Runes: make([]string, 0),
Properties: make([]*runewordProperty, 0),
}
for idx := 0; idx < numRunewordTypeInclude; idx++ {
column := fmt.Sprintf(fmtTypeInclude, idx+1)
if code := d.String(column); code != "" {
record.ItemTypes.Include = append(record.ItemTypes.Include, code)
}
}
for idx := 0; idx < numRunewordTypeExclude; idx++ {
column := fmt.Sprintf(fmtTypeExclude, idx+1)
if code := d.String(column); code != "" {
record.ItemTypes.Exclude = append(record.ItemTypes.Exclude, code)
}
}
for idx := 0; idx < numRunewordMaxSockets; idx++ {
column := fmt.Sprintf(fmtRuneStr, idx+1)
if code := d.String(column); code != "" {
record.Runes = append(record.Runes, code)
}
}
for idx := 0; idx < numRunewordProperties; idx++ {
codeColumn := fmt.Sprintf(fmtRunewordPropCode, idx+1)
if code := codeColumn; code != "" {
prop := &runewordProperty{
code,
d.String(fmt.Sprintf(fmtRunewordPropParam, idx+1)),
d.Number(fmt.Sprintf(fmtRunewordPropMin, idx+1)),
d.Number(fmt.Sprintf(fmtRunewordPropMax, idx+1)),
}
record.Properties = append(record.Properties, prop)
}
}
Runewords[record.Name] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d Runewords records", len(Runewords))
}

View File

@ -1,203 +0,0 @@
package d2datadict
import (
"fmt"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
const (
numPropertiesOnSetItem = 9
numBonusPropertiesOnSetItem = 5
bonusToken1 = "a"
bonusToken2 = "b"
propCodeFmt = "prop%d"
propParamFmt = "par%d"
propMinFmt = "min%d"
propMaxFmt = "max%d"
bonusCodeFmt = "aprop%d%s"
bonusParamFmt = "apar%d%s"
bonusMinFmt = "amin%d%s"
bonusMaxFmt = "amax%d%s"
)
// SetItemRecord represents a set item
type SetItemRecord struct {
// SetItemKey (index)
// string key to item's name in a .tbl file
SetItemKey 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
// Properties are a propert code, parameter, min, max for generating an item propert
Properties [numPropertiesOnSetItem]*SetItemProperty
// SetPropertiesLevel1 is the first version of bonus properties for the set
SetPropertiesLevel1 [numBonusPropertiesOnSetItem]*SetItemProperty
// SetPropertiesLevel2 is the second version of bonus properties for the set
SetPropertiesLevel2 [numBonusPropertiesOnSetItem]*SetItemProperty
}
// SetItemProperty is describes a property of a set item
type SetItemProperty struct {
Code string
Parameter string // depending on the property, this may be an int (usually), or a string
Min int
Max int
}
// SetItems holds all of the SetItemRecords
var SetItems map[string]*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(map[string]*SetItemRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
record := &SetItemRecord{
SetItemKey: 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"),
}
// normal properties
props := [numPropertiesOnSetItem]*SetItemProperty{}
for idx := 0; idx < numPropertiesOnSetItem; idx++ {
num := idx + 1
props[idx] = &SetItemProperty{
d.String(fmt.Sprintf(propCodeFmt, num)),
d.String(fmt.Sprintf(propParamFmt, num)),
d.Number(fmt.Sprintf(propMinFmt, num)),
d.Number(fmt.Sprintf(propMaxFmt, num)),
}
}
// set bonus properties
bonus1 := [numBonusPropertiesOnSetItem]*SetItemProperty{}
bonus2 := [numBonusPropertiesOnSetItem]*SetItemProperty{}
for idx := 0; idx < numBonusPropertiesOnSetItem; idx++ {
num := idx + 1
bonus1[idx] = &SetItemProperty{
d.String(fmt.Sprintf(bonusCodeFmt, num, bonusToken1)),
d.String(fmt.Sprintf(bonusParamFmt, num, bonusToken1)),
d.Number(fmt.Sprintf(bonusMinFmt, num, bonusToken1)),
d.Number(fmt.Sprintf(bonusMaxFmt, num, bonusToken1)),
}
bonus2[idx] = &SetItemProperty{
d.String(fmt.Sprintf(bonusCodeFmt, num, bonusToken2)),
d.String(fmt.Sprintf(bonusParamFmt, num, bonusToken2)),
d.Number(fmt.Sprintf(bonusMinFmt, num, bonusToken2)),
d.Number(fmt.Sprintf(bonusMaxFmt, num, bonusToken2)),
}
}
record.Properties = props
SetItems[record.SetItemKey] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d SetItem records", len(SetItems))
}

View File

@ -1,164 +0,0 @@
package d2datadict
import (
"fmt"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
const (
numPartialSetProperties = 4
numFullSetProperties = 8
fmtPropCode = "%sCode%d%s"
fmtPropParam = "%sParam%d%s"
fmtPropMin = "%sMin%d%s"
fmtPropMax = "%sMax%d%s"
partialIdxOffset = 2
setPartialToken = "P"
setPartialTokenA = "a"
setPartialTokenB = "b"
setFullToken = "F"
)
// SetRecord describes the set bonus for a group of set items
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
// Properties contains the partial and full set bonus properties.
Properties struct {
PartialA []*setProperty
PartialB []*setProperty
Full []*setProperty
}
}
type setProperty struct {
// Code is 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.
Code string
// Param is the 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 string
// Min 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.
Min int
// Max 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.
Max int
}
// SetRecords contain the set records from sets.txt
var SetRecords map[string]*SetRecord //nolint:gochecknoglobals // Currently global by design
// LoadSetRecords loads set records from sets.txt
func LoadSetRecords(file []byte) { //nolint:funlen // doesn't make sense to split
SetRecords = make(map[string]*SetRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
record := &SetRecord{
Key: d.String("index"),
StringTableKey: d.String("name"),
Version: d.Number("version"),
Level: d.Number("level"),
Properties: struct {
PartialA []*setProperty
PartialB []*setProperty
Full []*setProperty
}{
PartialA: make([]*setProperty, 0),
PartialB: make([]*setProperty, 0),
Full: make([]*setProperty, 0),
},
}
// for partial properties 2a thru 5b
for idx := 0; idx < numPartialSetProperties; idx++ {
num := idx + partialIdxOffset // needs to be 2,3,4,5
columnA := fmt.Sprintf(fmtPropCode, setPartialToken, num, setPartialTokenA)
columnB := fmt.Sprintf(fmtPropCode, setPartialToken, num, setPartialTokenB)
if codeA := d.String(columnA); codeA != "" {
paramColumn := fmt.Sprintf(fmtPropParam, setPartialToken, num, setPartialTokenA)
minColumn := fmt.Sprintf(fmtPropMin, setPartialToken, num, setPartialTokenA)
maxColumn := fmt.Sprintf(fmtPropMax, setPartialToken, num, setPartialTokenA)
propA := &setProperty{
Code: codeA,
Param: d.String(paramColumn),
Min: d.Number(minColumn),
Max: d.Number(maxColumn),
}
record.Properties.PartialA = append(record.Properties.PartialA, propA)
}
if codeB := d.String(columnB); codeB != "" {
paramColumn := fmt.Sprintf(fmtPropParam, setPartialToken, num, setPartialTokenB)
minColumn := fmt.Sprintf(fmtPropMin, setPartialToken, num, setPartialTokenB)
maxColumn := fmt.Sprintf(fmtPropMax, setPartialToken, num, setPartialTokenB)
propB := &setProperty{
Code: codeB,
Param: d.String(paramColumn),
Min: d.Number(minColumn),
Max: d.Number(maxColumn),
}
record.Properties.PartialB = append(record.Properties.PartialB, propB)
}
}
for idx := 0; idx < numFullSetProperties; idx++ {
num := idx + 1
codeColumn := fmt.Sprintf(fmtPropCode, setFullToken, num, "")
paramColumn := fmt.Sprintf(fmtPropParam, setFullToken, num, "")
minColumn := fmt.Sprintf(fmtPropMin, setFullToken, num, "")
maxColumn := fmt.Sprintf(fmtPropMax, setFullToken, num, "")
if code := d.String(codeColumn); code != "" {
prop := &setProperty{
Code: code,
Param: d.String(paramColumn),
Min: d.Number(minColumn),
Max: d.Number(maxColumn),
}
record.Properties.Full = append(record.Properties.Full, prop)
}
}
SetRecords[record.Key] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d Sets records", len(SetRecords))
}

View File

@ -1,51 +0,0 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
// ShrineRecord is a representation of a row from shrines.txt
type ShrineRecord struct {
ShrineType string // None, Recharge, Booster, or Magic
ShrineName string // Name of the Shrine
Effect string // Effect on the player
Code int // Unique identifier
Arg0 int // ? (0-400)
Arg1 int // ? (0-2000)
DurationFrames int // How long the shrine lasts in frames
ResetTimeMinutes int // How many minutes until the shrine resets?
Rarity int // 1-3
EffectClass int // 0-4
LevelMin int // 0-32
}
// Shrines contains the Unique Appellations
//nolint:gochecknoglobals // Currently global by design, only written once
var Shrines map[string]*ShrineRecord
// LoadShrines loads Shrines from the supplied file
func LoadShrines(file []byte) {
Shrines = make(map[string]*ShrineRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
record := &ShrineRecord{
ShrineType: d.String("Shrine Type"),
ShrineName: d.String("Shrine name"),
Effect: d.String("Effect"),
Code: d.Number("Code"),
Arg0: d.Number("Arg0"),
Arg1: d.Number("Arg1"),
DurationFrames: d.Number("Duration in frames"),
ResetTimeMinutes: d.Number("reset time in minutes"),
Rarity: d.Number("rarity"),
EffectClass: d.Number("effectclass"),
LevelMin: d.Number("LevelMin"),
}
Shrines[record.ShrineName] = record
}
log.Printf("Loaded %d shrines", len(Shrines))
}

View File

@ -1,333 +0,0 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
// StateRecord describes a body location that items can be equipped to
type StateRecord struct {
// Name of status effect (Line # is used for ID purposes)
State string
// Exact usage depends on the state and how the code accesses it,
// overlay1 however is the one you should generally be using.
Overlay1 string
Overlay2 string
Overlay3 string
Overlay4 string
// Overlay shown on target of progressive skill when chargeup triggers.
PgOverlay string
// Overlay displayed when the state is applied initially
// (later replaced by overlay1 or whatever applicable by code).
CastOverlay string
// Like castoverlay, just this one is displayed when the state expires.
RemOverlay string
// Primary stat associated with the state, mostly used for display purposes
// (you should generally use skills.txt for assigning stats to states).
Stat string
// The missile that this state will utilize for certain events,
// how this is used depends entirely on the functions associated with the state.
Missile string
// The skill that will be queried for this state in some sections of code,
// strangely enough this contradicts the fact states store their assigner skill anyway
// (via STAT_MODIFIERLIST_SKILL)
Skill string
// What item type is effected by this states color change.
ItemType string
// The color being applied to this item
// (only going to have an effect on alternate gfx, inventory gfx isn't effected).
ItemTrans string
// Sound played respectively when the state is initially applied
OnSound string
// and when it expires
OffSound string
// States can be grouped together, so they cannot stack
Group int
// Clientside callback function invoked when the state is applied initially.
SetFunc int
// Clientside callback function invoked when the state expires or is otherwise removed.
RemFunc int
// The color priority for this states color change, the, this can range from 0 to 255,
// the state with the highest color priority will always be used should more then
// one co-exist on the unit. If two states exist with the same priority the one with the
// lowest id is used (IIRC).
ColorPri int
// Index for the color shift palette picked from the *.PL2 files.
ColorShift int
// Change the color of the light radius to what is indicated here,
// (only has an effect in D3D and glide of course).
LightR int
// Change the color of the light radius to what is indicated here,
// (only has an effect in D3D and glide of course).
LightG int
// Change the color of the light radius to what is indicated here,
// (only has an effect in D3D and glide of course).
LightB int
// What unit type is used for the disguise gfx
// (1 being monsters, 2 being players, contrary to internal game logic).
GfxType int
// The unit class used for disguise gfx, this corresponds with the index
// from monstats.txt and charstats.txt
GfxClass int
// When 'gfxtype' is set to 1, the "class" represents an hcIdx from MonStats.txt.
// If it's set to 2 then it will indicate a character class the unit with this state will be
// morphed into.
// Clientside event callback for this state
// (likely very inconsistent with the server side events, beware).
CltEvent string
// Callback function invoked when the client event triggers.
CltEventFunc int
// CltDoFunc called every frame the state is active
CltActiveFunc int
// Srvdofunc called every frame the state is active
SrvActiveFunc int
// If a monster gets hit, the state will be dispelled
RemHit bool
// Not yet analyzed in detail
NoSend bool
// Whenever a state transforms the appearance of a unit
Transform bool
// Aura states will stack on-screen. Aura states are dispelled when a monster is
// affected by conversion
Aura bool
// Can a heal enabled npc remove this state when you talk to them?
Cureable bool
// Curse states can't stack. Controls duration reduction from cleansing, and curse resistance.
// When a new curse is applied, the old one is removed.
Curse bool
// State has a StateActiveFunc associated with it. The active func is called every frame while
// the state is active.
Active bool
// State restricts skill usage (druid shapeshift)
Restrict bool
// State makes the game load another sprite (use with Transform)
Disguise bool
// State applies a color change that overrides all others
Blue bool
// Control when attack rating is displayed in blue
AttBlue bool
// Control when damage is displayed in blue
DmgBlue bool
// Control when armor class is displayed in blue
ArmBlue bool
// Control when fire resistance is displayed in blue
RfBlue bool
// Control when lightning resistance is displayed in blue
RlBlue bool
// Control when cold resistance is displayed in blue
RcBlue bool
// Control when poison resistance is displayed in blue
RpBlue bool
// Control when attack rating is displayed in red
AttRed bool
// Control when damage is displayed in red
DmgRed bool
// Control when armor class is displayed in red
ArmRed bool
// Control when fire resistance is displayed in red
RfRed bool
// Control when lightning resistance is displayed in red
RlRed bool
// Control when cold resistance is displayed in red
RcRed bool
// Control when poison resistance is displayed in red
RpRed bool
// Control when stamina bar color is changed to blue
StamBarBlue bool
// When a unit effected by this state kills another unit,
// the summon owner will receive experience
Exp bool
// Whenever the state is removed when the player dies
PlrStayDeath bool
// Whenever the state is removed when the monster dies
MonStayDeath bool
// Whenever the state is removed when the boss dies. Prevents bosses from shattering?
BossStayDeath bool
// When the unit dies, the corpse and death animation will not be drawn
Hide bool
// Whenever the unit shatters or explodes when it dies. This is heavily hardcoded,
// it will always use the ice shatter for all states other than STATE_UBERMINION
Shatter bool
// Whenever this state prevents the corpse from being selected by spells and the ai
UDead bool
// When a state with this is active, it cancels out the native life regen of monsters.
// (using only the mod part instead of accr).
Life bool
// Whenever this state applies a color change that overrides all others (such as from items).
// (see blue column, which seams to do the same).
Green bool
// Whenever this state is associated with progressive spells and will be
// looked up when the charges are triggered.
Pgsv bool
// Related to assigning overlays to the unit, not extensively researched yet.
NoOverlays bool
// Like the previous column, also only used on states with the previous column enabled.
NoClear bool
// whenever this state will use the minion owners inventory clientside (this is what makes
// the decoy always show up with your own equipment,
// even when you change what you wear after summoning one).
BossInv bool
// Prevents druids that wield a bow or crossbow while shape shifted from
// firing missiles, and will rather attack in melee.
MeleeOnly bool
// Not researched yet
NotOnDead bool
}
// States contains the state records
//nolint:gochecknoglobals // Currently global by design, only written once
var States map[string]*StateRecord
// LoadStates loads states from the supplied file
func LoadStates(file []byte) {
States = make(map[string]*StateRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
record := &StateRecord{
State: d.String("state"),
Group: d.Number("group"),
RemHit: d.Number("remhit") > 0,
NoSend: d.Number("nosend") > 0,
Transform: d.Number("transform") > 0,
Aura: d.Number("aura") > 0,
Cureable: d.Number("cureable") > 0,
Curse: d.Number("curse") > 0,
Active: d.Number("active") > 0,
Restrict: d.Number("restrict") > 0,
Disguise: d.Number("disguise") > 0,
Blue: d.Number("blue") > 0,
AttBlue: d.Number("attblue") > 0,
DmgBlue: d.Number("dmgblue") > 0,
ArmBlue: d.Number("armblue") > 0,
RfBlue: d.Number("rfblue") > 0,
RlBlue: d.Number("rlblue") > 0,
RcBlue: d.Number("rcblue") > 0,
RpBlue: d.Number("rpblue") > 0,
AttRed: d.Number("attred") > 0,
DmgRed: d.Number("dmgred") > 0,
ArmRed: d.Number("armred") > 0,
RfRed: d.Number("rfred") > 0,
RlRed: d.Number("rlred") > 0,
RcRed: d.Number("rcred") > 0,
RpRed: d.Number("rpred") > 0,
StamBarBlue: d.Number("stambarblue") > 0,
Exp: d.Number("exp") > 0,
PlrStayDeath: d.Number("plrstaydeath") > 0,
MonStayDeath: d.Number("monstaydeath") > 0,
BossStayDeath: d.Number("bossstaydeath") > 0,
Hide: d.Number("hide") > 0,
Shatter: d.Number("shatter") > 0,
UDead: d.Number("udead") > 0,
Life: d.Number("life") > 0,
Green: d.Number("green") > 0,
Pgsv: d.Number("pgsv") > 0,
NoOverlays: d.Number("nooverlays") > 0,
NoClear: d.Number("noclear") > 0,
BossInv: d.Number("bossinv") > 0,
MeleeOnly: d.Number("meleeonly") > 0,
NotOnDead: d.Number("notondead") > 0,
Overlay1: d.String("overlay1"),
Overlay2: d.String("overlay2"),
Overlay3: d.String("overlay3"),
Overlay4: d.String("overlay4"),
PgOverlay: d.String("pgoverlay"),
CastOverlay: d.String("castoverlay"),
RemOverlay: d.String("removerlay"),
Stat: d.String("stat"),
SetFunc: d.Number("setfunc"),
RemFunc: d.Number("remfun"),
Missile: d.String("missile"),
Skill: d.String("skill"),
ItemType: d.String("itemtype"),
ItemTrans: d.String("itemtrans"),
ColorPri: d.Number("colorpri"),
ColorShift: d.Number("colorshift"),
LightR: d.Number("light-r"),
LightG: d.Number("light-g"),
LightB: d.Number("light-b"),
OnSound: d.String("onsound"),
OffSound: d.String("offsound"),
GfxType: d.Number("gfxtype"),
GfxClass: d.Number("gfxclass"),
CltEvent: d.String("cltevent"),
CltEventFunc: d.Number("clteventfunc"),
CltActiveFunc: d.Number("cltactivefun"),
SrvActiveFunc: d.Number("srvactivefunc"),
}
States[record.State] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d State records", len(States))
}

View File

@ -1,163 +0,0 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
// https://d2mods.info/forum/kb/viewarticle?a=162
// SuperUniqueRecord Defines the unique monsters and their properties.
// SuperUnique monsters are boss monsters which always appear at the same places
// and always have the same base special abilities
// with the addition of one or two extra ones per difficulty (Nightmare provides one extra ability, Hell provides two).
// Notable examples are enemies such as Corpsefire, Pindleskin or Nihlathak.
type SuperUniqueRecord struct {
// id of the SuperUnique Monster. Each SuperUnique Monster must use a different id.
// It also serves as the string to use in the 'Place' field of MonPreset.txt
Key string // Superunique
// Name for this SuperUnique which must be retrieved from a .TBL file
Name string
// the base monster type of the SuperUnique, refers to the "Key" field in monstats.go ("ID" column in the MonStats.txt)
Class string
// This is the "hardcoded index".
// Vanilla SuperUniques in the game ranges from 0 to 65. Some of them have some hardcoded stuffs attached.
// NOTE: It is also possible to create new SuperUniques with hardcoded stuff attached. To do this, you can use a hcIx from 0 to 65.
// Example A: If you create a new SuperUnique with a hcIdx of 42 (Shenk the Overseer) then whatever its Class,
// this SuperUnique will have 20 Enslaved as minions (exactly like the vanilla Shenk, and in spite of NOT being Shenk).
// Example B: If you want a simple new SuperUnique, you must use a hcIdx greater than 65,
// because greater indexes don't exist in the code and therefore your new boss won't have anything special attached
HcIdx string
// This field forces the SuperUnique to use a special set of sounds for attacks, taunts, death etc.
// The Countess is a clear and noticeable example of this. The MonSound set is taken from MonSounds.txt.
MonSound string
// These three fields assign special abilities so SuperUnique monsters such as "Fire Enchanted" or "Stone Skin".
// These fields refers to the ID's corresponding to the properties in MonUMod.txt.
// Here is the list of available properties.
// 0. None
// 1. Inits the random name seed, automatically added to monster, you don't need to add this UMod.
// 2. Hit Point bonus which is automatically added to the monster. You don't really need to manually add this UMod
// 3. Increases the light radius and picks a random color for it (bugged in v1.10+).
// 4. Increases the monster level, resulting in higher damage.
// 5. Extra Strong: increases physical damage done by boss.
// 6. Extra Fast: faster walk / run and attack speed (Although the increased attack speed isn't added in newer versions . . .)
// 7. Cursed: randomly cast Amplify Damage when hitting
// 8. Magic Resist: +50% resistance against Elemental attacks (Fire, Cold, Lightning and Poison)
// 9. Fire Enchanted: additional fire damage and +50% fire resistance.
// 10. When killed, release a poisonous cloud, like the Mummies in Act 2.
// 11. Corpse will spawn little white maggots (like Duriel).
// 12. Works for Bloodraven only, and seems to have something to do with her Death sequence.
// 13. Ignore your Armor Class and nearly always hit you.
// 14. It should add damage to its minions
// 15. When killed, all his minions die immediately as well.
// 16. Adds base champion modifiers [color=#0040FF][b](champions only)[/b][/color]
// 17. Lightning Enchanted: additional lightning damage, +50% lightning resistance and release Charged Bolts when hit.
// 18. Cold Enchanted: additional cold damage, +50% cold resistance, and releases a Frost Nova upon death
// 19. Assigns extra damage to hireling attacks, relic from pre-lod, causes bugged damage.
// 20. Releases Charged Bolts when hit, like the Scarabs in act 2.
// 21. Present in the code, but it seems to have no effect.
// 22. Has to do with quests, but is non-functional for Superuniques which aren´t in relation to a quest.
// 23. Has a poison aura that poisons you when you're approaching him, adds poison damage to attack.
// 24. Code present, but untested in v1.10+, does something else now.
// 25. Mana Burn: steals mana from you and heals itself when hitting. Adds magic resistance.
// 26. TeleHeal: randomly warps around when attacked and heals itself.
// 27. Spectral Hit: deals random elemental damage when hitting
// 28. Stone Skin: +80% physical damage resistance, increases defense
// 29. Multiple Shots: Ranged attackers shoots several missiles at once.
// 30. Aura Enchanted: Assigns a random offensive aura (aside from Thorns, Sanctuary and Concentration) to the SuperUnique
// 31. Explodes in a Corpse Explosion when killed.
// 32. Explodeswith a fiery flash when killed (Visual effect only).
// 33. Explode and chills you when killed (like suicide minions). It heavily reduces the Boss' Hit Points
// 34. Self-resurrect effect for Reanimate Horde, bugged on other units.
// 35. Shatter into Ice pieces when killed, no corpse remains.
// 36. Adds physical resistance and reduces movement speed(used for Champions only)
// 37. Alters champion stats (used for Champions only)
// 38. Champion cannot be cursed (used for Champions only)
// 39. Alters champion stats (used for Champions only)
// 40. Releases a painworm when killed, but display is very buggy.
// 41. Code present, but has no effect in-game, probably due to bugs
// 42. Releases a Nova when killed, but display is bugged.
Mod [3]int
// These two fields control the Minimum and Maximum amount of minions which will be spawned along with the SuperUnique.
// If those values differ, the game will roll a random number within the MinGrp and the MaxGrp.
MinGrp int
MaxGrp int
// Boolean indicates if the game is expansion or classic
IsExpansion bool // named as "EClass" in the SuperUniques.txt
// This field states whether the SuperUnique will be placed within a radius from his original
// position(defined by the .ds1 map file), or not.
// false means that the boss will spawn in a random position within a large radius from its actual
// position in the .ds1 file,
// true means it will spawn exactly where expected.
AutoPosition bool
// Specifies if this SuperUnique can spawn more than once in the same game.
// true means it can spawn more than once in the same game, false means it can not.
Stacks bool
// Treasure Classes for the 3 Difficulties.
// These columns list the treasureclass that is valid if this boss is killed and drops something.
// These fields must contain the values taken from the "TreasureClass" column in TreasureClassEx.txt (Expansion)
// or TreasureClass (Classic).
TreasureClassNormal string
TreasureClassNightmare string
TreasureClassHell string
// These fields dictate which RandTransform.dat color index the SuperUnique will use respectively in Normal, Nightmare and Hell mode.
UTransNormal string
UTransNightmare string
UTransHell string
}
// SuperUniques stores all of the SuperUniqueRecords
//nolint:gochecknoglobals // Currently global by design
var SuperUniques map[string]*SuperUniqueRecord
// LoadSuperUniques loads SuperUniqueRecords from superuniques.txt
func LoadSuperUniques(file []byte) {
SuperUniques = make(map[string]*SuperUniqueRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
record := &SuperUniqueRecord{
Key: d.String("Superunique"),
Name: d.String("Name"),
Class: d.String("Class"),
HcIdx: d.String("hcIdx"),
MonSound: d.String("MonSound"),
Mod: [3]int{
d.Number("Mod1"),
d.Number("Mod2"),
d.Number("Mod3"),
},
MinGrp: d.Number("MinGrp"),
MaxGrp: d.Number("MaxGrp"),
IsExpansion: d.Bool("EClass"),
AutoPosition: d.Bool("AutoPos"),
Stacks: d.Bool("Stacks"),
TreasureClassNormal: d.String("TC"),
TreasureClassNightmare: d.String("TC(N)"),
TreasureClassHell: d.String("TC(H)"),
UTransNormal: d.String("Utrans"),
UTransNightmare: d.String("Utrans(N)"),
UTransHell: d.String("Utrans(H)"),
}
SuperUniques[record.Key] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d SuperUnique records", len(SuperUniques))
}

View File

@ -1,95 +0,0 @@
package d2datadict
import (
"fmt"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
const (
maxTreasuresPerRecord = 10
treasureItemFmt = "Item%d"
treasureProbFmt = "Prob%d"
)
// TreasureClassRecord represents a rule for item drops in diablo 2
type TreasureClassRecord struct {
Name string
Group int
Level int
NumPicks int
FreqUnique int
FreqSet int
FreqRare int
FreqMagic int
FreqNoDrop int
Treasures []*Treasure
}
// Treasure describes a treasure to drop
// the Name is either a reference to an item, or to another treasure class
type Treasure struct {
Code string
Probability int
}
// TreasureClass contains all of the TreasureClassRecords
var TreasureClass map[string]*TreasureClassRecord //nolint:gochecknoglobals // Currently global by design
// LoadTreasureClassRecords loads treasure class records from TreasureClassEx.txt
//nolint:funlen // Makes no sense to split
func LoadTreasureClassRecords(file []byte) {
TreasureClass = make(map[string]*TreasureClassRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
record := &TreasureClassRecord{
Name: d.String("Treasure Class"),
Group: d.Number("group"),
Level: d.Number("level"),
NumPicks: d.Number("Picks"),
FreqUnique: d.Number("Unique"),
FreqSet: d.Number("Set"),
FreqRare: d.Number("Rare"),
FreqMagic: d.Number("Magic"),
FreqNoDrop: d.Number("NoDrop"),
}
if record.Name == "" {
continue
}
for treasureIdx := 0; treasureIdx < maxTreasuresPerRecord; treasureIdx++ {
treasureColumnKey := fmt.Sprintf(treasureItemFmt, treasureIdx+1)
probColumnKey := fmt.Sprintf(treasureProbFmt, treasureIdx+1)
treasureName := d.String(treasureColumnKey)
if treasureName == "" {
continue
}
prob := d.Number(probColumnKey)
treasure := &Treasure{
Code: treasureName,
Probability: prob,
}
if record.Treasures == nil {
record.Treasures = []*Treasure{treasure}
} else {
record.Treasures = append(record.Treasures, treasure)
}
}
TreasureClass[record.Name] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d TreasureClass records", len(TreasureClass))
}

View File

@ -1,36 +0,0 @@
package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
// UniqueAppellationRecord described the extra suffix of a unique monster name
type UniqueAppellationRecord struct {
// The title
Name string
}
// UniqueAppellations contains all of the UniqueAppellationRecords
//nolint:gochecknoglobals // Currently global by design
var UniqueAppellations map[string]*UniqueAppellationRecord
// LoadUniqueAppellations loads UniqueAppellationRecords from UniqueAppelation.txt
func LoadUniqueAppellations(file []byte) {
UniqueAppellations = make(map[string]*UniqueAppellationRecord)
d := d2txt.LoadDataDictionary(file)
for d.Next() {
record := &UniqueAppellationRecord{
Name: d.String("Name"),
}
UniqueAppellations[record.Name] = record
}
if d.Err != nil {
panic(d.Err)
}
log.Printf("Loaded %d UniqueAppellation records", len(UniqueAppellations))
}

View File

@ -1,142 +0,0 @@
package d2datadict
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
)
// UniqueItemRecord is a representation of a row from uniqueitems.txt
type UniqueItemRecord struct {
Properties [12]UniqueItemProperty
Name string
Code string // three letter code, points to a record in Weapons, Armor, or Misc
TypeDescription string
UberDescription string
CharacterGfxTransform string // palette shift applied to this items gfx when held and when
// on the ground (flippy). Points to a record in Colors.txt
InventoryGfxTransform string // palette shift applied to the inventory gfx
FlippyFile string // if non-empty, overrides the base item's dropped gfx
InventoryFile string // if non-empty, overrides the base item's inventory gfx
DropSound string // if non-empty, overrides the base item's drop sound
UseSound string // if non-empty, overrides the sound played when item is used
Version int // 0 = classic pre 1.07, 1 = 1.07-1.11, 100 = expansion
Rarity int // 1-255, higher is more common (ironically...)
Level int // item's level, can only be dropped by monsters / recipes / vendors / objects of this level or higher
// otherwise they would drop a rare item with enhanced durability
RequiredLevel int // character must have this level to use this item
CostMultiplier int // base price is multiplied by this when sold, repaired or bought
CostAdd int // after multiplied by above, this much is added to the price
DropSfxFrame int // if non-empty, overrides the base item's frame at which the drop sound plays
Enabled bool // if false, this record won't be loaded (should always be true...)
Ladder bool // if true, can only be found on ladder and not in single player / tcp/ip
NoLimit bool // if true, can drop more than once per game
// (if false, can only drop once per game; if it would drop,
// instead a rare item with enhanced durability drops)
SingleCopy bool // if true, player can only hold one of these. can't trade it or pick it up
}
// UniqueItemProperty is describes a property of a unique item
type UniqueItemProperty struct {
Code string
Parameter string // depending on the property, this may be an int (usually), or a string
Min int
Max int
}
func createUniqueItemRecord(r []string) UniqueItemRecord {
i := -1
inc := func() int {
i++
return i
}
result := UniqueItemRecord{
Name: r[inc()],
Version: d2util.StringToInt(d2util.EmptyToZero(r[inc()])),
Enabled: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1,
Ladder: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1,
Rarity: d2util.StringToInt(d2util.EmptyToZero(r[inc()])),
NoLimit: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1,
Level: d2util.StringToInt(d2util.EmptyToZero(r[inc()])),
RequiredLevel: d2util.StringToInt(d2util.EmptyToZero(r[inc()])),
Code: r[inc()],
TypeDescription: r[inc()],
UberDescription: r[inc()],
SingleCopy: d2util.StringToInt(d2util.EmptyToZero(r[inc()])) == 1,
CostMultiplier: d2util.StringToInt(d2util.EmptyToZero(r[inc()])),
CostAdd: d2util.StringToInt(d2util.EmptyToZero(r[inc()])),
CharacterGfxTransform: r[inc()],
InventoryGfxTransform: r[inc()],
FlippyFile: r[inc()],
InventoryFile: r[inc()],
DropSound: r[inc()],
DropSfxFrame: d2util.StringToInt(d2util.EmptyToZero(r[inc()])),
UseSound: r[inc()],
Properties: [12]UniqueItemProperty{
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
createUniqueItemProperty(&r, inc),
},
}
return result
}
func createUniqueItemProperty(r *[]string, inc func() int) UniqueItemProperty {
result := UniqueItemProperty{
Code: (*r)[inc()],
Parameter: (*r)[inc()],
Min: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])),
Max: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])),
}
return result
}
// UniqueItems stores all of the UniqueItemRecords
//nolint:gochecknoglobals // Currently global by design
var UniqueItems map[string]*UniqueItemRecord
// LoadUniqueItems loadsUniqueItemRecords fro uniqueitems.txt
func LoadUniqueItems(file []byte) {
UniqueItems = make(map[string]*UniqueItemRecord)
data := strings.Split(string(file), "\r\n")[1:]
for _, line := range data {
if line == "" {
continue
}
r := strings.Split(line, "\t")
// skip rows that are not enabled
if r[2] != "1" {
continue
}
rec := createUniqueItemRecord(r)
UniqueItems[rec.Name] = &rec
}
log.Printf("Loaded %d unique items", len(UniqueItems))
}

View File

@ -181,7 +181,7 @@ const (
LevelPreset = "/data/global/excel/LvlPrest.txt"
LevelType = "/data/global/excel/LvlTypes.txt"
ObjectType = "/data/global/excel/objtype.bin"
ObjectType = "/data/global/excel/objtype.txt"
LevelWarp = "/data/global/excel/LvlWarp.txt"
LevelDetails = "/data/global/excel/Levels.txt"
LevelMaze = "/data/global/excel/LvlMaze.txt"

View File

@ -5,6 +5,8 @@ import (
"image/color"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
@ -142,6 +144,11 @@ func (am *AssetManager) initDataDictionaries() error {
}
}
err := am.initAnimationData(d2resource.AnimationData)
if err != nil {
return err
}
return nil
}
@ -240,6 +247,8 @@ func (am *AssetManager) LoadComposite(baseType d2enum.ObjectType, token, palette
palettePath: palettePath,
}
c.SetDirection(0)
return c, nil
}
@ -417,6 +426,19 @@ func (am *AssetManager) loadDCC(path string,
return animation, nil
}
func (am *AssetManager) initAnimationData(path string) error {
animDataBytes, err := am.LoadFile(path)
if err != nil {
return err
}
animData := d2data.LoadAnimationData(animDataBytes)
am.Records.Animations = animData
return nil
}
// BindTerminalCommands binds the in-game terminal comands for the asset manager.
func (am *AssetManager) BindTerminalCommands(term d2interface.Terminal) error {
if err := term.BindAction("assetspam", "display verbose asset manager logs", func(verbose bool) {

View File

@ -5,7 +5,6 @@ import (
"fmt"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2cof"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
@ -244,7 +243,7 @@ func (c *Composite) createMode(animationMode animationMode, weaponClass string)
animationKey := strings.ToLower(c.token + animationMode.String() + weaponClass)
animationData := d2data.AnimationData[animationKey]
animationData := c.Records.Animations[animationKey]
if len(animationData) == 0 {
return nil, errors.New("could not find Animation data")
}

View File

@ -8,7 +8,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2item"
@ -194,13 +193,13 @@ func (i *Item) UniqueRecord() *d2records.UniqueItemRecord {
}
// SetRecord returns the SetRecord of the item
func (i *Item) SetRecord() *d2datadict.SetRecord {
return d2datadict.SetRecords[i.SetCode]
func (i *Item) SetRecord() *d2records.SetRecord {
return i.factory.asset.Records.Item.Sets[i.SetCode]
}
// SetItemRecord returns the SetRecord of the item
func (i *Item) SetItemRecord() *d2datadict.SetItemRecord {
return d2datadict.SetItems[i.SetItemCode]
func (i *Item) SetItemRecord() *d2records.SetItemRecord {
return i.factory.asset.Records.Item.SetItems[i.SetItemCode]
}
// PrefixRecords returns the ItemAffixCommonRecords of the prefixes of the item
@ -303,7 +302,7 @@ func (i *Item) sanitizeDropModifier(modifier DropModifier) DropModifier {
}
func (i *Item) pickUniqueRecord() {
matches := findMatchingUniqueRecords(i.CommonRecord())
matches := i.findMatchingUniqueRecords(i.CommonRecord())
if len(matches) > 0 {
match := matches[i.rand.Intn(len(matches))]
i.UniqueCode = match.Code
@ -311,7 +310,7 @@ func (i *Item) pickUniqueRecord() {
}
func (i *Item) pickSetRecords() {
if matches := findMatchingSetItemRecords(i.CommonRecord()); len(matches) > 0 {
if matches := i.findMatchingSetItemRecords(i.CommonRecord()); len(matches) > 0 {
picked := matches[i.rand.Intn(len(matches))]
i.SetItemCode = picked.SetItemKey
@ -658,10 +657,16 @@ func (i *Item) generateName() {
if numAffixes >= 3 {
i.rand.Seed(i.Seed)
numPrefix, numSuffix := len(d2datadict.RarePrefixes), len(d2datadict.RareSuffixes)
prefixes := i.factory.asset.Records.Item.Rare.Prefix
suffixes := i.factory.asset.Records.Item.Rare.Suffix
numPrefix := len(prefixes)
numSuffix := len(suffixes)
preIdx, sufIdx := i.rand.Intn(numPrefix), i.rand.Intn(numSuffix)
prefix := d2datadict.RarePrefixes[preIdx].Name
suffix := d2datadict.RareSuffixes[sufIdx].Name
prefix := prefixes[preIdx].Name
suffix := suffixes[sufIdx].Name
name = fmt.Sprintf("%s %s\n%s", strings.Title(prefix), strings.Title(suffix), name)
}
@ -708,13 +713,13 @@ func (i *Item) GetStatStrings() []string {
return result
}
func findMatchingUniqueRecords(icr *d2records.ItemCommonRecord) []*d2datadict.UniqueItemRecord {
result := make([]*d2datadict.UniqueItemRecord, 0)
func (i *Item) findMatchingUniqueRecords(icr *d2records.ItemCommonRecord) []*d2records.UniqueItemRecord {
result := make([]*d2records.UniqueItemRecord, 0)
c1, c2, c3, c4 := icr.Code, icr.NormalCode, icr.UberCode, icr.UltraCode
for uCode := range d2datadict.UniqueItems {
uRec := d2datadict.UniqueItems[uCode]
for uCode := range i.factory.asset.Records.Item.Unique {
uRec := i.factory.asset.Records.Item.Unique[uCode]
switch uCode {
case c1, c2, c3, c4:
@ -726,15 +731,15 @@ func findMatchingUniqueRecords(icr *d2records.ItemCommonRecord) []*d2datadict.Un
}
// find possible SetItemRecords that the given ItemCommonRecord can have
func findMatchingSetItemRecords(icr *d2records.ItemCommonRecord) []*d2datadict.SetItemRecord {
result := make([]*d2datadict.SetItemRecord, 0)
func (i *Item) findMatchingSetItemRecords(icr *d2records.ItemCommonRecord) []*d2records.SetItemRecord {
result := make([]*d2records.SetItemRecord, 0)
c1, c2, c3, c4 := icr.Code, icr.NormalCode, icr.UberCode, icr.UltraCode
for setItemIdx := range d2datadict.SetItems {
switch d2datadict.SetItems[setItemIdx].ItemCode {
for setItemIdx := range i.factory.asset.Records.Item.SetItems {
switch i.factory.asset.Records.Item.SetItems[setItemIdx].ItemCode {
case c1, c2, c3, c4:
result = append(result, d2datadict.SetItems[setItemIdx])
result = append(result, i.factory.asset.Records.Item.SetItems[setItemIdx])
}
}

View File

@ -11,8 +11,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
)
const (
@ -94,12 +92,12 @@ func (f *ItemFactory) NewItem(codes ...string) (*Item, error) {
continue
}
if found := d2datadict.SetItems[code]; found != nil {
if found := f.asset.Records.Item.SetItems[code]; found != nil {
set = code
continue
}
if found := d2datadict.UniqueItems[code]; found != nil {
if found := f.asset.Records.Item.Unique[code]; found != nil {
unique = code
continue
}
@ -174,7 +172,7 @@ func (f *ItemFactory) NewProperty(code string, values ...int) *Property {
return result.init()
}
func (f *ItemFactory) rollDropModifier(tcr *d2datadict.TreasureClassRecord) DropModifier {
func (f *ItemFactory) rollDropModifier(tcr *d2records.TreasureClassRecord) DropModifier {
modMap := map[int]DropModifier{
0: DropModifierNone,
1: DropModifierUnique,
@ -210,7 +208,7 @@ func (f *ItemFactory) rollDropModifier(tcr *d2datadict.TreasureClassRecord) Drop
return DropModifierNone
}
func (f *ItemFactory) rollTreasurePick(tcr *d2datadict.TreasureClassRecord) *d2datadict.Treasure {
func (f *ItemFactory) rollTreasurePick(tcr *d2records.TreasureClassRecord) *d2records.Treasure {
// treasure probabilities
tprob := make([]int, len(tcr.Treasures)+1)
total := tcr.FreqNoDrop
@ -237,10 +235,10 @@ func (f *ItemFactory) rollTreasurePick(tcr *d2datadict.TreasureClassRecord) *d2d
}
// ItemsFromTreasureClass rolls for and creates items using a treasure class record
func (f *ItemFactory) ItemsFromTreasureClass(tcr *d2datadict.TreasureClassRecord) []*Item {
func (f *ItemFactory) ItemsFromTreasureClass(tcr *d2records.TreasureClassRecord) []*Item {
result := make([]*Item, 0)
treasurePicks := make([]*d2datadict.Treasure, 0)
treasurePicks := make([]*d2records.Treasure, 0)
// if tcr.NumPicks is negative, each item probability is instead a count for how many
// of that treasure to drop
@ -274,7 +272,7 @@ func (f *ItemFactory) ItemsFromTreasureClass(tcr *d2datadict.TreasureClassRecord
// 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 {
if record, found := f.asset.Records.Item.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 {
@ -297,7 +295,7 @@ func (f *ItemFactory) ItemsFromTreasureClass(tcr *d2datadict.TreasureClassRecord
}
// ItemFromTreasure rolls for a f.rand.m item using the Treasure struct (from d2datadict)
func (f *ItemFactory) ItemFromTreasure(treasure *d2datadict.Treasure) *Item {
func (f *ItemFactory) ItemFromTreasure(treasure *d2records.Treasure) *Item {
result := &Item{
rand: rand.New(rand.NewSource(f.Seed)),
}

View File

@ -7,7 +7,6 @@ import (
uuid "github.com/satori/go.uuid"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
@ -218,7 +217,7 @@ func (f *MapEntityFactory) NewNPC(x, y int, monstat *d2records.MonStatsRecord, d
}
// NewObject creates an instance of AnimatedComposite
func (f *MapEntityFactory) NewObject(x, y int, objectRec *d2datadict.ObjectRecord,
func (f *MapEntityFactory) NewObject(x, y int, objectRec *d2records.ObjectDetailsRecord,
palettePath string) (*Object, error) {
locX, locY := float64(x), float64(y)
entity := &Object{
@ -227,7 +226,7 @@ func (f *MapEntityFactory) NewObject(x, y int, objectRec *d2datadict.ObjectRecor
Position: d2vector.NewPosition(locX, locY),
name: d2tbl.TranslateString(objectRec.Name),
}
objectType := &d2datadict.ObjectTypes[objectRec.Index]
objectType := f.asset.Records.Object.Types[objectRec.Index]
composite, err := f.asset.LoadComposite(d2enum.ObjectTypeItem, objectType.Token,
palettePath)

View File

@ -5,7 +5,8 @@ import (
"fmt"
"math/rand"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
@ -19,7 +20,7 @@ type Object struct {
composite *d2asset.Composite
highlight bool
// nameLabel d2ui.Label
objectRecord *d2datadict.ObjectRecord
objectRecord *d2records.ObjectDetailsRecord
drawLayer int
name string
}

View File

@ -1,7 +1,6 @@
package d2mapstamp
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1"
@ -97,13 +96,13 @@ func (mr *Stamp) Entities(tileOffsetX, tileOffsetY int) []d2interface.MapEntity
if object.Type == int(d2enum.ObjectTypeItem) {
// For objects the DS1 ID to objectID is hardcoded in the game
// use the lookup table
lookup := d2datadict.LookupObject(int(mr.ds1.Act), object.Type, object.ID)
lookup := mr.factory.asset.Records.LookupObject(int(mr.ds1.Act), object.Type, object.ID)
if lookup == nil {
continue
}
objectRecord := d2datadict.Objects[lookup.ObjectsTxtId]
objectRecord := mr.factory.asset.Records.Object.Details[lookup.ObjectsTxtId]
if objectRecord != nil {
entity, err := mr.entity.NewObject((tileOffsetX*5)+object.X,

View File

@ -10,14 +10,11 @@ import (
func objectDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
records := make(ObjectDetails)
i := -1
inc := func() int {
i++
return i
}
i := 0
for d.Next() {
record := &ObjectDetailsRecord{
Index: i,
Name: d.String("Name"),
Description: d.String("description - not loaded"),
id: d.Number("Id"),
@ -222,9 +219,8 @@ func objectDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error {
AutoMap: d.Number("AutoMap"),
}
inc()
records[i] = record
i++
}
if d.Err != nil {

View File

@ -2,6 +2,7 @@ package d2records
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
)
@ -17,8 +18,8 @@ func objectTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error {
for d.Next() {
record := ObjectTypeRecord{
Name: d.String("Name"),
Token: d.String("Token"),
Name: sanitizeObjectString(d.String("Name")),
Token: sanitizeObjectString(d.String("Token")),
}
records = append(records, record)
@ -34,3 +35,10 @@ func objectTypesLoader(r *RecordManager, d *d2txt.DataDictionary) error {
return nil
}
func sanitizeObjectString(str string) string {
result := strings.TrimSpace(strings.ReplaceAll(str, string(byte(0)), ""))
result = strings.ToLower(result)
return result
}

View File

@ -4,6 +4,8 @@ import (
"fmt"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
@ -26,6 +28,7 @@ func NewRecordManager() (*RecordManager, error) {
// RecordManager stores all of the records loaded from txt files
type RecordManager struct {
boundLoaders map[string][]recordLoader // there can be more than one loader bound for a file
Animations d2data.AnimationData
BodyLocations
Calculation struct {
Skills Calculations