mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-20 07:27:19 -05:00
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:
parent
fc87b2be7a
commit
be354f139b
48
d2app/app.go
48
d2app/app.go
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
)
|
@ -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
|
@ -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
|
||||
}
|
@ -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
@ -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
|
@ -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)
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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"
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)),
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user