Resolved most lint errors in d2data and d2datadict (#499)

* adding comments to d2interface for linter

* moved d2render renderer interfaces and types into d2interface

* fixed most lint errors for monstats loader

* de-lint d2data wip

* d2data: resolve linting errors
This commit is contained in:
dk 2020-06-30 06:17:07 -07:00 committed by GitHub
parent 691368cba7
commit 4938ec1f44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 8967 additions and 8972 deletions

View File

@ -20,12 +20,13 @@ type AnimationDataRecord struct {
} }
// AnimationData represents all of the animation data records, mapped by the COF index // AnimationData represents all of the animation data records, mapped by the COF index
var AnimationData map[string][]*AnimationDataRecord var AnimationData map[string][]*AnimationDataRecord //nolint:gochecknoglobals // Currently global by design
// LoadAnimationData loads the animation data table into the global AnimationData dictionary // LoadAnimationData loads the animation data table into the global AnimationData dictionary
func LoadAnimationData(rawData []byte) { func LoadAnimationData(rawData []byte) {
AnimationData = make(map[string][]*AnimationDataRecord) AnimationData = make(map[string][]*AnimationDataRecord)
streamReader := d2common.CreateStreamReader(rawData) streamReader := d2common.CreateStreamReader(rawData)
for !streamReader.Eof() { for !streamReader.Eof() {
dataCount := int(streamReader.GetInt32()) dataCount := int(streamReader.GetInt32())
for i := 0; i < dataCount; i++ { for i := 0; i < dataCount; i++ {
@ -37,11 +38,14 @@ func LoadAnimationData(rawData []byte) {
} }
data.Flags = streamReader.ReadBytes(144) data.Flags = streamReader.ReadBytes(144)
cofIndex := strings.ToLower(data.COFName) cofIndex := strings.ToLower(data.COFName)
if _, found := AnimationData[cofIndex]; !found { if _, found := AnimationData[cofIndex]; !found {
AnimationData[cofIndex] = make([]*AnimationDataRecord, 0) AnimationData[cofIndex] = make([]*AnimationDataRecord, 0)
} }
AnimationData[cofIndex] = append(AnimationData[cofIndex], data) AnimationData[cofIndex] = append(AnimationData[cofIndex], data)
} }
} }
log.Printf("Loaded %d animation data records", len(AnimationData)) log.Printf("Loaded %d animation data records", len(AnimationData))
} }

View File

@ -1,5 +1,7 @@
// // Package d2compression is used for decompressing WAV files.
// MpqHuffman.go based on the origina CS file package d2compression
// MpqHuffman.go based on the original CS file
// //
// Authors: // Authors:
// Foole (fooleau@gmail.com) // Foole (fooleau@gmail.com)
@ -27,7 +29,6 @@
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// //
package d2compression
import ( import (
"log" "log"
@ -37,57 +38,60 @@ import (
// linkedNode is a node which is both hierachcical (parent/child) and doubly linked (next/prev) // linkedNode is a node which is both hierachcical (parent/child) and doubly linked (next/prev)
type linkedNode struct { type linkedNode struct {
DecompressedValue int decompressedValue int
Weight int weight int
Parent *linkedNode parent *linkedNode
Child0 *linkedNode child0 *linkedNode
Prev *linkedNode prev *linkedNode
Next *linkedNode next *linkedNode
} }
func CreateLinkedNode(decompVal, weight int) *linkedNode { // createLinkedNode creates a linked node
func createLinkedNode(decompVal, weight int) *linkedNode {
result := &linkedNode{ result := &linkedNode{
DecompressedValue: decompVal, decompressedValue: decompVal,
Weight: weight, weight: weight,
} }
return result return result
} }
func (v *linkedNode) GetChild1() *linkedNode { func (v *linkedNode) getChild1() *linkedNode {
return v.Child0.Prev return v.child0.prev
} }
func (v *linkedNode) Insert(other *linkedNode) *linkedNode { func (v *linkedNode) insert(other *linkedNode) *linkedNode {
// 'Next' should have a lower weight we should return the lower weight // 'next' should have a lower weight we should return the lower weight
if other.Weight <= v.Weight { if other.weight <= v.weight {
// insert before // insert before
if v.Next != nil { if v.next != nil {
v.Next.Prev = other v.next.prev = other
other.Next = v.Next other.next = v.next
} }
v.Next = other v.next = other
other.Prev = v other.prev = v
return other return other
} }
if v.Prev == nil { if v.prev == nil {
// Insert after // insert after
other.Prev = nil other.prev = nil
v.Prev = other v.prev = other
other.Next = v other.next = v
} else { } else {
v.Prev.Insert(other) v.prev.insert(other)
} }
return v return v
} }
//nolint:funlen // Makes no sense to split
func getPrimes() [][]byte { func getPrimes() [][]byte {
return [][]byte{ return [][]byte{
{ { //nolint:dupl we're not interested in duplicates here
// Compression type 0 // Compression type 0
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -106,7 +110,7 @@ func getPrimes() [][]byte {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
}, },
{ { //nolint:dupl we're not interested in duplicates here
// Compression type 1 // Compression type 1
0x54, 0x16, 0x16, 0x0D, 0x0C, 0x08, 0x06, 0x05, 0x06, 0x05, 0x06, 0x03, 0x04, 0x04, 0x03, 0x05, 0x54, 0x16, 0x16, 0x0D, 0x0C, 0x08, 0x06, 0x05, 0x06, 0x05, 0x06, 0x03, 0x04, 0x04, 0x03, 0x05,
0x0E, 0x0B, 0x14, 0x13, 0x13, 0x09, 0x0B, 0x06, 0x05, 0x04, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02, 0x0E, 0x0B, 0x14, 0x13, 0x13, 0x09, 0x0B, 0x06, 0x05, 0x04, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02,
@ -125,7 +129,7 @@ func getPrimes() [][]byte {
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x06, 0x4B, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x06, 0x4B,
}, { }, {
// Compression type 2 // Compression type 2 //nolint:dupl
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x27, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x27, 0x00, 0x00, 0x23, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x06, 0x0E, 0x10, 0x04, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x06, 0x0E, 0x10, 0x04,
@ -134,8 +138,8 @@ func getPrimes() [][]byte {
0x03, 0x01, 0x03, 0x06, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x01, 0x01, 0x03, 0x01, 0x03, 0x06, 0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x01, 0x01,
0x01, 0x29, 0x07, 0x16, 0x12, 0x40, 0x0A, 0x0A, 0x11, 0x25, 0x01, 0x03, 0x17, 0x10, 0x26, 0x2A, 0x01, 0x29, 0x07, 0x16, 0x12, 0x40, 0x0A, 0x0A, 0x11, 0x25, 0x01, 0x03, 0x17, 0x10, 0x26, 0x2A,
0x10, 0x01, 0x23, 0x23, 0x2F, 0x10, 0x06, 0x07, 0x02, 0x09, 0x01, 0x01, 0x01, 0x01, 0x01, 0x10, 0x01, 0x23, 0x23, 0x2F, 0x10, 0x06, 0x07, 0x02, 0x09, 0x01, 0x01, 0x01, 0x01, 0x01,
}, { }, { //nolint:dupl we're not interested in duplicates here
// Compression type 3 // Compression type 3 //nolint:dupl
0xFF, 0x0B, 0x07, 0x05, 0x0B, 0x02, 0x02, 0x02, 0x06, 0x02, 0x02, 0x01, 0x04, 0x02, 0x01, 0x03, 0xFF, 0x0B, 0x07, 0x05, 0x0B, 0x02, 0x02, 0x02, 0x06, 0x02, 0x02, 0x01, 0x04, 0x02, 0x01, 0x03,
0x09, 0x01, 0x01, 0x01, 0x03, 0x04, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x09, 0x01, 0x01, 0x01, 0x03, 0x04, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
0x05, 0x01, 0x01, 0x01, 0x0D, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x05, 0x01, 0x01, 0x01, 0x0D, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
@ -152,14 +156,15 @@ func getPrimes() [][]byte {
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x07, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x07, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01,
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x11, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x11,
}, { // Compression type 4 }, { // Compression type 4 //nolint:dupl
0xFF, 0xFB, 0x98, 0x9A, 0x84, 0x85, 0x63, 0x64, 0x3E, 0x3E, 0x22, 0x22, 0x13, 0x13, 0x18, 0x17, 0xFF, 0xFB, 0x98, 0x9A, 0x84, 0x85, 0x63, 0x64, 0x3E, 0x3E, 0x22, 0x22, 0x13, 0x13, 0x18, 0x17,
}, { // Compression type 5 }, { // Compression type 5 //nolint:dupl
0xFF, 0xF1, 0x9D, 0x9E, 0x9A, 0x9B, 0x9A, 0x97, 0x93, 0x93, 0x8C, 0x8E, 0x86, 0x88, 0x80, 0x82, 0xFF, 0xF1, 0x9D, 0x9E, 0x9A, 0x9B, 0x9A, 0x97, 0x93, 0x93, 0x8C, 0x8E, 0x86, 0x88, 0x80, 0x82,
0x7C, 0x7C, 0x72, 0x73, 0x69, 0x6B, 0x5F, 0x60, 0x55, 0x56, 0x4A, 0x4B, 0x40, 0x41, 0x37, 0x37, 0x7C, 0x7C, 0x72, 0x73, 0x69, 0x6B, 0x5F, 0x60, 0x55, 0x56, 0x4A, 0x4B, 0x40, 0x41, 0x37, 0x37,
0x2F, 0x2F, 0x27, 0x27, 0x21, 0x21, 0x1B, 0x1C, 0x17, 0x17, 0x13, 0x13, 0x10, 0x10, 0x0D, 0x0D, 0x2F, 0x2F, 0x27, 0x27, 0x21, 0x21, 0x1B, 0x1C, 0x17, 0x17, 0x13, 0x13, 0x10, 0x10, 0x0D, 0x0D,
0x0B, 0x0B, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07, 0x06, 0x05, 0x05, 0x04, 0x04, 0x04, 0x19, 0x18, 0x0B, 0x0B, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07, 0x06, 0x05, 0x05, 0x04, 0x04, 0x04, 0x19, 0x18,
}, { // Compression type 6 }, { //nolint:dupl we're not interested in duplicates here
// Compression type 6
0xC3, 0xCB, 0xF5, 0x41, 0xFF, 0x7B, 0xF7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xCB, 0xF5, 0x41, 0xFF, 0x7B, 0xF7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -169,7 +174,8 @@ func getPrimes() [][]byte {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x7A, 0x46, 0x7A, 0x46,
}, { // Compression type 7 }, { //nolint:dupl we're not interested in duplicates here
// Compression type 7
0xC3, 0xD9, 0xEF, 0x3D, 0xF9, 0x7C, 0xE9, 0x1E, 0xFD, 0xAB, 0xF1, 0x2C, 0xFC, 0x5B, 0xFE, 0x17, 0xC3, 0xD9, 0xEF, 0x3D, 0xF9, 0x7C, 0xE9, 0x1E, 0xFD, 0xAB, 0xF1, 0x2C, 0xFC, 0x5B, 0xFE, 0x17,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -196,30 +202,36 @@ func getPrimes() [][]byte {
func decode(input *d2common.BitStream, head *linkedNode) *linkedNode { func decode(input *d2common.BitStream, head *linkedNode) *linkedNode {
node := head node := head
for node.Child0 != nil { for node.child0 != nil {
bit := input.ReadBits(1) bit := input.ReadBits(1)
if bit == -1 { if bit == -1 {
log.Fatal("unexpected end of file") log.Fatal("unexpected end of file")
} }
if bit == 0 { if bit == 0 {
node = node.Child0 node = node.child0
continue continue
} }
node = node.GetChild1() node = node.getChild1()
} }
return node return node
} }
// TODO: these consts for buildList need better names
const (
decompVal1 = 256
decompVal2 = 257
)
func buildList(primeData []byte) *linkedNode { func buildList(primeData []byte) *linkedNode {
root := CreateLinkedNode(256, 1) root := createLinkedNode(decompVal1, 1)
root = root.Insert(CreateLinkedNode(257, 1)) root = root.insert(createLinkedNode(decompVal2, 1))
for i := 0; i < len(primeData); i++ { for i := 0; i < len(primeData); i++ {
if primeData[i] != 0 { if primeData[i] != 0 {
root = root.Insert(CreateLinkedNode(i, int(primeData[i]))) root = root.insert(createLinkedNode(i, int(primeData[i])))
} }
} }
@ -228,20 +240,20 @@ func buildList(primeData []byte) *linkedNode {
func insertNode(tail *linkedNode, decomp int) *linkedNode { func insertNode(tail *linkedNode, decomp int) *linkedNode {
parent := tail parent := tail
result := tail.Prev // This will be the new tail after the tree is updated result := tail.prev // This will be the new tail after the tree is updated
temp := CreateLinkedNode(parent.DecompressedValue, parent.Weight) temp := createLinkedNode(parent.decompressedValue, parent.weight)
temp.Parent = parent temp.parent = parent
newnode := CreateLinkedNode(decomp, 0) newnode := createLinkedNode(decomp, 0)
newnode.Parent = parent newnode.parent = parent
parent.Child0 = newnode parent.child0 = newnode
tail.Next = temp tail.next = temp
temp.Prev = tail temp.prev = tail
newnode.Prev = temp newnode.prev = temp
temp.Next = newnode temp.next = newnode
adjustTree(newnode) adjustTree(newnode)
@ -257,7 +269,7 @@ func adjustTree(newNode *linkedNode) {
current := newNode current := newNode
for current != nil { for current != nil {
current.Weight++ current.weight++
var insertpoint *linkedNode var insertpoint *linkedNode
@ -267,12 +279,12 @@ func adjustTree(newNode *linkedNode) {
insertpoint = current insertpoint = current
for { for {
prev = insertpoint.Prev prev = insertpoint.prev
if prev == nil { if prev == nil {
break break
} }
if prev.Weight >= current.Weight { if prev.weight >= current.weight {
break break
} }
@ -281,60 +293,60 @@ func adjustTree(newNode *linkedNode) {
// No insertion point found // No insertion point found
if insertpoint == current { if insertpoint == current {
current = current.Parent current = current.parent
continue continue
} }
// The following code basically swaps insertpoint with current // The following code basically swaps insertpoint with current
// remove insert point // remove insert point
if insertpoint.Prev != nil { if insertpoint.prev != nil {
insertpoint.Prev.Next = insertpoint.Next insertpoint.prev.next = insertpoint.next
} }
insertpoint.Next.Prev = insertpoint.Prev insertpoint.next.prev = insertpoint.prev
// Insert insertpoint after current // insert insertpoint after current
insertpoint.Next = current.Next insertpoint.next = current.next
insertpoint.Prev = current insertpoint.prev = current
if current.Next != nil { if current.next != nil {
current.Next.Prev = insertpoint current.next.prev = insertpoint
} }
current.Next = insertpoint current.next = insertpoint
// remove current // remove current
current.Prev.Next = current.Next current.prev.next = current.next
current.Next.Prev = current.Prev current.next.prev = current.prev
// insert current after prev // insert current after prev
if prev == nil { if prev == nil {
log.Fatal("previous frame not defined!") log.Fatal("previous frame not defined!")
} }
temp := prev.Next temp := prev.next
current.Next = temp current.next = temp
current.Prev = prev current.prev = prev
temp.Prev = current temp.prev = current
prev.Next = current prev.next = current
// Set up parent/child links // Set up parent/child links
currentparent := current.Parent currentparent := current.parent
insertparent := insertpoint.Parent insertparent := insertpoint.parent
if currentparent.Child0 == current { if currentparent.child0 == current {
currentparent.Child0 = insertpoint currentparent.child0 = insertpoint
} }
if currentparent != insertparent && insertparent.Child0 == insertpoint { if currentparent != insertparent && insertparent.child0 == insertpoint {
insertparent.Child0 = current insertparent.child0 = current
} }
current.Parent = insertparent current.parent = insertparent
insertpoint.Parent = currentparent insertpoint.parent = currentparent
current = current.Parent current = current.parent
} }
} }
@ -343,24 +355,25 @@ func buildTree(tail *linkedNode) *linkedNode {
for current != nil { for current != nil {
child0 := current child0 := current
child1 := current.Prev child1 := current.prev
if child1 == nil { if child1 == nil {
break break
} }
parent := CreateLinkedNode(0, child0.Weight+child1.Weight) parent := createLinkedNode(0, child0.weight+child1.weight)
parent.Child0 = child0 parent.child0 = child0
child0.Parent = parent child0.parent = parent
child1.Parent = parent child1.parent = parent
current.Insert(parent) current.insert(parent)
current = current.Prev.Prev current = current.prev.prev
} }
return current return current
} }
// HuffmanDecompress decompresses huffman-compressed data
func HuffmanDecompress(data []byte) []byte { func HuffmanDecompress(data []byte) []byte {
comptype := data[0] comptype := data[0]
primes := getPrimes() primes := getPrimes()
@ -380,7 +393,7 @@ func HuffmanDecompress(data []byte) []byte {
Loop: Loop:
for { for {
node := decode(bitstream, head) node := decode(bitstream, head)
decoded = node.DecompressedValue decoded = node.decompressedValue
switch decoded { switch decoded {
case 256: case 256:
break Loop break Loop

View File

@ -4,34 +4,36 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
var sLookup = []int{ // WavDecompress decompresses wav files
0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, func WavDecompress(data []byte, channelCount int) []byte { //nolint:funlen doesn't make sense to split
0x0010, 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F,
0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042,
0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F,
0x009D, 0x00AD, 0x00BE, 0x00D1, 0x00E6, 0x00FD, 0x0117, 0x0133,
0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292,
0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583,
0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD, 0x0BD0,
0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954,
0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B,
0x3BB9, 0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462,
0x7FFF,
}
var sLookup2 = []int{
-1, 0, -1, 4, -1, 2, -1, 6,
-1, 1, -1, 5, -1, 3, -1, 7,
-1, 1, -1, 5, -1, 3, -1, 7,
-1, 2, -1, 4, -1, 6, -1, 8,
}
func WavDecompress(data []byte, channelCount int) []byte {
Array1 := []int{0x2c, 0x2c} Array1 := []int{0x2c, 0x2c}
Array2 := make([]int, channelCount) Array2 := make([]int, channelCount)
var sLookup = []int{
0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E,
0x0010, 0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F,
0x0022, 0x0025, 0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042,
0x0049, 0x0050, 0x0058, 0x0061, 0x006B, 0x0076, 0x0082, 0x008F,
0x009D, 0x00AD, 0x00BE, 0x00D1, 0x00E6, 0x00FD, 0x0117, 0x0133,
0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE, 0x0220, 0x0256, 0x0292,
0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E, 0x0502, 0x0583,
0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD, 0x0BD0,
0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954,
0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B,
0x3BB9, 0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462,
0x7FFF,
}
var sLookup2 = []int{
-1, 0, -1, 4, -1, 2, -1, 6,
-1, 1, -1, 5, -1, 3, -1, 7,
-1, 1, -1, 5, -1, 3, -1, 7,
-1, 2, -1, 4, -1, 6, -1, 8,
}
input := d2common.CreateStreamReader(data) input := d2common.CreateStreamReader(data)
output := d2common.CreateStreamWriter() output := d2common.CreateStreamWriter()
input.GetByte() input.GetByte()
shift := input.GetByte() shift := input.GetByte()
@ -43,6 +45,7 @@ func WavDecompress(data []byte, channelCount int) []byte {
} }
channel := channelCount - 1 channel := channelCount - 1
for input.GetPosition() < input.GetSize() { for input.GetPosition() < input.GetSize() {
value := input.GetByte() value := input.GetByte()
@ -56,12 +59,14 @@ func WavDecompress(data []byte, channelCount int) []byte {
if Array1[channel] != 0 { if Array1[channel] != 0 {
Array1[channel]-- Array1[channel]--
} }
output.PushInt16(int16(Array2[channel])) output.PushInt16(int16(Array2[channel]))
case 1: case 1:
Array1[channel] += 8 Array1[channel] += 8
if Array1[channel] > 0x58 { if Array1[channel] > 0x58 {
Array1[channel] = 0x58 Array1[channel] = 0x58
} }
if channelCount == 2 { if channelCount == 2 {
channel = 1 - channel channel = 1 - channel
} }
@ -71,6 +76,7 @@ func WavDecompress(data []byte, channelCount int) []byte {
if Array1[channel] < 0 { if Array1[channel] < 0 {
Array1[channel] = 0 Array1[channel] = 0
} }
if channelCount == 2 { if channelCount == 2 {
channel = 1 - channel channel = 1 - channel
} }
@ -116,12 +122,11 @@ func WavDecompress(data []byte, channelCount int) []byte {
if Array1[channel] < 0 { if Array1[channel] < 0 {
Array1[channel] = 0 Array1[channel] = 0
} else { } else if Array1[channel] > 0x58 {
if Array1[channel] > 0x58 { Array1[channel] = 0x58
Array1[channel] = 0x58
}
} }
} }
} }
return output.GetBytes() return output.GetBytes()
} }

View File

@ -6,9 +6,11 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
) )
//nolint:gochecknoglobals // Currently global by design, only written once
var Armors map[string]*ItemCommonRecord var Armors map[string]*ItemCommonRecord
// LoadArmors loads entries from armor.txt as ItemCommonRecords
func LoadArmors(file []byte) { func LoadArmors(file []byte) {
Armors = *LoadCommonItems(file, d2enum.InventoryItemTypeArmor) Armors = LoadCommonItems(file, d2enum.InventoryItemTypeArmor)
log.Printf("Loaded %d armors", len(Armors)) log.Printf("Loaded %d armors", len(Armors))
} }

View File

@ -7,6 +7,11 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
const (
expansion = "Expansion" // blizzard put this in the txt where expansion data starts
)
//nolint:gochecknoglobals // Currently global by design, only written once
var frameFields = []string{"Cel1", "Cel2", "Cel3", "Cel4"} var frameFields = []string{"Cel1", "Cel2", "Cel3", "Cel4"}
// AutoMapRecord represents one row from d2data.mpq/AutoMap.txt. // AutoMapRecord represents one row from d2data.mpq/AutoMap.txt.
@ -27,7 +32,6 @@ type AutoMapRecord struct {
// StartSequence and EndSequence are sub indices the // StartSequence and EndSequence are sub indices the
// same 2D array as Style. They describe a range of // same 2D array as Style. They describe a range of
// tiles for which covered by this AutoMapRecord. // tiles for which covered by this AutoMapRecord.
//
// In some rows you can find a value of -1. This means // In some rows you can find a value of -1. This means
// the game will only look at Style and TileName to // the game will only look at Style and TileName to
// determine which tiles are addressed. // determine which tiles are addressed.
@ -50,7 +54,6 @@ type AutoMapRecord struct {
// re-extract the chart with Dc6Table, you can specify // re-extract the chart with Dc6Table, you can specify
// how many graphics a line can hold), line 1 includes // how many graphics a line can hold), line 1 includes
// icons 0-19, line 2 from 20 to 39 etc. // icons 0-19, line 2 from 20 to 39 etc.
//
// Multiple values exist for Cel (and Type) to enable // Multiple values exist for Cel (and Type) to enable
// variation. Presumably game chooses randomly between // variation. Presumably game chooses randomly between
// any of the 4 values which are not set to -1. // any of the 4 values which are not set to -1.
@ -74,7 +77,7 @@ func LoadAutoMaps(file []byte) {
AutoMaps = make([]*AutoMapRecord, len(d.Data)) AutoMaps = make([]*AutoMapRecord, len(d.Data))
for idx := range d.Data { for idx := range d.Data {
if d.GetString("LevelName", idx) == "Expansion" { if d.GetString("LevelName", idx) == expansion {
continue continue
} }

View File

@ -7,60 +7,65 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
) )
// Charecter stats // CharStatsRecord is a struct that represents a single row from charstats.txt
type CharStatsRecord struct { type CharStatsRecord struct {
Class d2enum.Hero Class d2enum.Hero
// the initial stats at character level 1 // the initial stats at character level 1
InitStr int InitStr int // initial strength
InitDex int InitDex int // initial dexterity
InitVit int InitVit int // initial vitality
InitEne int InitEne int // initial energy
InitStamina int InitStamina int // initial stamina
ManaRegen int // number of seconds to regen mana completely ManaRegen int // number of seconds to regen mana completely
ToHitFactor int // added to basic AR of character class ToHitFactor int // added to basic AR of character class
VelocityWalk int VelocityWalk int // velocity of the character while walking
VelocityRun int VelocityRun int // velocity of the character while running
StaminaRunDrain int // rate of stamina loss, lower is longer drain time StaminaRunDrain int // rate of stamina loss, lower is longer drain time
// NOTE: Each point of Life/Mana/Stamina is divided by 256 for precision. // NOTE: Each point of Life/Mana/Stamina is divided by 256 for precision.
LifePerLevel int // value is in fourths, lowest possible is 64/256 // value is in fourths, lowest possible is 64/256
ManaPerLevel int LifePerLevel int // amount of life per character level
StaminaPerLevel int ManaPerLevel int // amount of mana per character level
StaminaPerLevel int // amount of stamina per character level
LifePerVit int // life per point of vitality LifePerVit int // life per point of vitality
ManaPerEne int // mana per point of energy ManaPerEne int // mana per point of energy
StaminaPerVit int StaminaPerVit int // stamina per point of vitality
StatPerLevel int // stat points per level StatPerLevel int // amount of stat points per level
BlockFactor int // added to base shield block% in armor.txt (display & calc) BlockFactor int // added to base shield block% in armor.txt (display & calc)
// appears on starting weapon // appears on starting weapon
StartSkillBonus string StartSkillBonus string // a key that points to a property
// The skills the character class starts with (always available) // The skills the character class starts with (always available)
BaseSkill [10]string BaseSkill [10]string // the base skill keys of the character, always available
// string for bonus to class skills (ex: +1 to all Amazon skills). // string for bonus to class skills (ex: +1 to all Amazon skills).
SkillStrAll string SkillStrAll string // string for bonus to all skills
SkillStrTab [3]string // string for bonus per skill tabs SkillStrTab [3]string // string for bonus per skill tabs
SkillStrClassOnly string // string for class-exclusive skills SkillStrClassOnly string // string for class-exclusive skills
BaseWeaponClass d2enum.WeaponClass // controls animation when unarmed BaseWeaponClass d2enum.WeaponClass // controls animation when unarmed
StartItem [10]string StartItem [10]string // tokens for the starting items
StartItemLocation [10]string StartItemLocation [10]string // locations of the starting items
StartItemCount [10]int StartItemCount [10]int // amount of the starting items
} }
// CharStats holds all of the CharStatsRecords
//nolint:gochecknoglobals // Currently global by design, only written once
var CharStats map[d2enum.Hero]*CharStatsRecord var CharStats map[d2enum.Hero]*CharStatsRecord
var charStringMap map[string]d2enum.Hero var charStringMap map[string]d2enum.Hero //nolint:gochecknoglobals // Currently global by design
var weaponTokenMap map[string]d2enum.WeaponClass var weaponTokenMap map[string]d2enum.WeaponClass //nolint:gochecknoglobals // Currently global by design
// LoadCharStats loads charstats.txt file contents into map[d2enum.Hero]*CharStatsRecord
//nolint:funlen // Makes no sense to split //nolint:funlen // Makes no sense to split
// LoadCharStats loads charstats.txt file contents into map[d2enum.Hero]*CharStatsRecord
func LoadCharStats(file []byte) { func LoadCharStats(file []byte) {
charStringMap = map[string]d2enum.Hero{ charStringMap = map[string]d2enum.Hero{
"Amazon": d2enum.HeroAmazon, "Amazon": d2enum.HeroAmazon,

View File

@ -140,22 +140,26 @@ type CubeRecipeItemProperty struct {
} }
// CubeRecipes contains all rows in CubeMain.txt. // CubeRecipes contains all rows in CubeMain.txt.
//nolint:gochecknoglobals // Currently global by design, only written once
var CubeRecipes []*CubeRecipeRecord var CubeRecipes []*CubeRecipeRecord
// There are repeated fields and sections in this file, some
// of which have inconsistent naming conventions. These slices
// are a simple way to handle them.
var outputFields = []string{"output", "output b", "output c"}
var outputLabels = []string{"", "b ", "c "}
var propLabels = []string{"mod 1", "mod 2", "mod 3", "mod 4", "mod 5"}
var inputFields = []string{"input 1", "input 2", "input 3", "input 4", "input 5", "input 6", "input 7"}
// LoadCubeRecipes populates CubeRecipes with // LoadCubeRecipes populates CubeRecipes with
// the data from CubeMain.txt. // the data from CubeMain.txt.
func LoadCubeRecipes(file []byte) { func LoadCubeRecipes(file []byte) {
// Load data // Load data
d := d2common.LoadDataDictionary(string(file)) d := d2common.LoadDataDictionary(string(file))
// There are repeated fields and sections in this file, some
// of which have inconsistent naming conventions. These slices
// are a simple way to handle them.
var outputFields = []string{"output", "output b", "output c"}
var outputLabels = []string{"", "b ", "c "}
var propLabels = []string{"mod 1", "mod 2", "mod 3", "mod 4", "mod 5"}
var inputFields = []string{"input 1", "input 2", "input 3", "input 4", "input 5", "input 6", "input 7"}
// Create records // Create records
CubeRecipes = make([]*CubeRecipeRecord, len(d.Data)) CubeRecipes = make([]*CubeRecipeRecord, len(d.Data))
for idx := range d.Data { for idx := range d.Data {

View File

@ -58,7 +58,7 @@ type DifficultyLevelRecord struct {
ManaStealDivisor int // ManaStealDivisor ManaStealDivisor int // ManaStealDivisor
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Gravestench: The rest of these are listed on PK page, but not present in // The rest of these are listed on PK page, but not present in
// my copy of the txt file (patch_d2/data/global/excel/difficultylevels.txt) // my copy of the txt file (patch_d2/data/global/excel/difficultylevels.txt)
// so I am going to leave these comments // so I am going to leave these comments

View File

@ -31,49 +31,37 @@ import (
10 10
*/ */
// ExperienceBreakpointsRecord describes the experience points required to
// gain a level for all character classes
type ExperienceBreakpointsRecord struct { type ExperienceBreakpointsRecord struct {
Level int Level int
HeroBreakpoints map[d2enum.Hero]int HeroBreakpoints map[d2enum.Hero]int
Ratio int Ratio int
} }
var experienceStringMap map[string]d2enum.Hero // ExperienceBreakpoints describes the required experience
var experienceHeroMap map[d2enum.Hero]string // for each level for each character class
//nolint:gochecknoglobals // Currently global by design, only written once
var ExperienceBreakpoints []*ExperienceBreakpointsRecord var ExperienceBreakpoints []*ExperienceBreakpointsRecord
//nolint:gochecknoglobals // Currently global by design
var maxLevels map[d2enum.Hero]int var maxLevels map[d2enum.Hero]int
// GetMaxLevelByHero returns the highest level attainable for a hero type
func GetMaxLevelByHero(heroType d2enum.Hero) int { func GetMaxLevelByHero(heroType d2enum.Hero) int {
return maxLevels[heroType] return maxLevels[heroType]
} }
// GetExperienceBreakpoint given a hero type and a level, returns the experience required for the level
func GetExperienceBreakpoint(heroType d2enum.Hero, level int) int { func GetExperienceBreakpoint(heroType d2enum.Hero, level int) int {
return ExperienceBreakpoints[level].HeroBreakpoints[heroType] return ExperienceBreakpoints[level].HeroBreakpoints[heroType]
} }
// LoadExperienceBreakpoints loads experience.txt into a map
// ExperienceBreakpoints []*ExperienceBreakpointsRecord
func LoadExperienceBreakpoints(file []byte) { func LoadExperienceBreakpoints(file []byte) {
d := d2common.LoadDataDictionary(string(file)) d := d2common.LoadDataDictionary(string(file))
experienceStringMap = map[string]d2enum.Hero{
"Amazon": d2enum.HeroAmazon,
"Barbarian": d2enum.HeroBarbarian,
"Druid": d2enum.HeroDruid,
"Assassin": d2enum.HeroAssassin,
"Necromancer": d2enum.HeroNecromancer,
"Paladin": d2enum.HeroPaladin,
"Sorceress": d2enum.HeroSorceress,
}
experienceHeroMap = map[d2enum.Hero]string{
d2enum.HeroAmazon: "Amazon",
d2enum.HeroBarbarian: "Barbarian",
d2enum.HeroDruid: "Druid",
d2enum.HeroAssassin: "Assassin",
d2enum.HeroNecromancer: "Necromancer",
d2enum.HeroPaladin: "Paladin",
d2enum.HeroSorceress: "Sorceress",
}
// we skip the second row because that describes max level of char classes // we skip the second row because that describes max level of char classes
ExperienceBreakpoints = make([]*ExperienceBreakpointsRecord, len(d.Data)-1) ExperienceBreakpoints = make([]*ExperienceBreakpointsRecord, len(d.Data)-1)

View File

@ -6,6 +6,8 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// GemsRecord is a representation of a single row of gems.txt
// it describes the properties of socketable items
type GemsRecord struct { type GemsRecord struct {
Name string Name string
Letter string Letter string
@ -50,13 +52,17 @@ type GemsRecord struct {
ShieldMod3Max int ShieldMod3Max int
} }
// Gems stores all of the GemsRecords
var Gems map[string]*GemsRecord //nolint:gochecknoglobals // Currently global by design, only written once
// LoadGems loads gem records into a map[string]*GemsRecord
func LoadGems(file []byte) { func LoadGems(file []byte) {
d := d2common.LoadDataDictionary(string(file)) d := d2common.LoadDataDictionary(string(file))
var Gems []*GemsRecord Gems = make(map[string]*GemsRecord, len(d.Data))
for idx := range d.Data { for idx := range d.Data {
if d.GetString("name", idx) != "Expansion" { if d.GetString("name", idx) != expansion {
/* /*
"Expansion" is the only field in line 36 of /data/global/excel/gems.txt and is only used to visually "Expansion" is the only field in line 36 of /data/global/excel/gems.txt and is only used to visually
separate base-game gems and expansion runes. separate base-game gems and expansion runes.
@ -104,7 +110,7 @@ func LoadGems(file []byte) {
ShieldMod3Min: d.GetNumber("shieldMod3Min", idx), ShieldMod3Min: d.GetNumber("shieldMod3Min", idx),
ShieldMod3Max: d.GetNumber("shieldMod3Max", idx), ShieldMod3Max: d.GetNumber("shieldMod3Max", idx),
} }
Gems = append(Gems, gem) Gems[gem.Name] = gem
} }
} }

View File

@ -6,6 +6,8 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// HirelingRecord is a representation of rows in hireling.txt
// these records describe mercenaries
type HirelingRecord struct { type HirelingRecord struct {
Hireling string Hireling string
SubType string SubType string
@ -81,9 +83,15 @@ type HirelingRecord struct {
Shield int Shield int
} }
// Hirelings stores hireling (mercenary) records
//nolint:gochecknoglobals // Currently global by design, only written once
var Hirelings []*HirelingRecord
// LoadHireling loads hireling data into []*HirelingRecord
func LoadHireling(file []byte) { func LoadHireling(file []byte) {
d := d2common.LoadDataDictionary(string(file)) d := d2common.LoadDataDictionary(string(file))
var Hirelings []*HirelingRecord
Hirelings = make([]*HirelingRecord, len(d.Data))
for idx := range d.Data { for idx := range d.Data {
hireling := &HirelingRecord{ hireling := &HirelingRecord{

View File

@ -8,13 +8,9 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
) )
var MagicPrefixDictionary *d2common.DataDictionary // MagicPrefix + MagicSuffix store item affix records
var MagicSuffixDictionary *d2common.DataDictionary var MagicPrefix []*ItemAffixCommonRecord //nolint:gochecknoglobals // Currently global by design
var MagicSuffix []*ItemAffixCommonRecord //nolint:gochecknoglobals // Currently global by design
var MagicPrefixRecords []*ItemAffixCommonRecord
var MagicSuffixRecords []*ItemAffixCommonRecord
var AffixMagicGroups []*ItemAffixCommonGroup
// LoadMagicPrefix loads MagicPrefix.txt // LoadMagicPrefix loads MagicPrefix.txt
func LoadMagicPrefix(file []byte) { func LoadMagicPrefix(file []byte) {
@ -22,7 +18,7 @@ func LoadMagicPrefix(file []byte) {
subType := d2enum.ItemAffixMagic subType := d2enum.ItemAffixMagic
MagicPrefixDictionary, MagicPrefixRecords = loadDictionary(file, superType, subType) MagicPrefix = loadDictionary(file, superType, subType)
} }
// LoadMagicSuffix loads MagicSuffix.txt // LoadMagicSuffix loads MagicSuffix.txt
@ -31,11 +27,11 @@ func LoadMagicSuffix(file []byte) {
subType := d2enum.ItemAffixMagic subType := d2enum.ItemAffixMagic
MagicSuffixDictionary, MagicSuffixRecords = loadDictionary(file, superType, subType) MagicSuffix = loadDictionary(file, superType, subType)
} }
func getAffixString(t1 d2enum.ItemAffixSuperType, t2 d2enum.ItemAffixSubType) string { func getAffixString(t1 d2enum.ItemAffixSuperType, t2 d2enum.ItemAffixSubType) string {
var name string = "" var name = ""
if t2 == d2enum.ItemAffixMagic { if t2 == d2enum.ItemAffixMagic {
name = "Magic" name = "Magic"
@ -55,58 +51,15 @@ func loadDictionary(
file []byte, file []byte,
superType d2enum.ItemAffixSuperType, superType d2enum.ItemAffixSuperType,
subType d2enum.ItemAffixSubType, subType d2enum.ItemAffixSubType,
) (*d2common.DataDictionary, []*ItemAffixCommonRecord) { ) []*ItemAffixCommonRecord {
dict := d2common.LoadDataDictionary(string(file)) dict := d2common.LoadDataDictionary(string(file))
records := createItemAffixRecords(dict, superType, subType) records := createItemAffixRecords(dict, superType, subType)
name := getAffixString(superType, subType) name := getAffixString(superType, subType)
log.Printf("Loaded %d %s records", len(dict.Data), name) log.Printf("Loaded %d %s records", len(dict.Data), name)
return dict, records return records
} }
// --- column names from d2exp.mpq:/data/globa/excel/MagicPrefix.txt
// Name
// version
// spawnable
// rare
// level
// maxlevel
// levelreq
// classspecific
// class
// classlevelreq
// frequency
// group
// mod1code
// mod1param
// mod1min
// mod1max
// mod2code
// mod2param
// mod2min
// mod2max
// mod3code
// mod3param
// mod3min
// mod3max
// transform
// transformcolor
// itype1
// itype2
// itype3
// itype4
// itype5
// itype6
// itype7
// etype1
// etype2
// etype3
// etype4
// etype5
// divide
// multiply
// add
func createItemAffixRecords( func createItemAffixRecords(
d *d2common.DataDictionary, d *d2common.DataDictionary,
superType d2enum.ItemAffixSuperType, superType d2enum.ItemAffixSuperType,
@ -177,7 +130,7 @@ func createItemAffixRecords(
} }
group := ItemAffixGroups[affix.GroupID] group := ItemAffixGroups[affix.GroupID]
group.AddMember(affix) group.addMember(affix)
records = append(records, affix) records = append(records, affix)
} }
@ -185,14 +138,16 @@ func createItemAffixRecords(
return records return records
} }
var ItemAffixGroups map[int]*ItemAffixCommonGroup // ItemAffixGroups are groups of MagicPrefix/Suffixes
var ItemAffixGroups map[int]*ItemAffixCommonGroup //nolint:gochecknoglobals // Currently global by design
// ItemAffixCommonGroup is a grouping that is common between prefix/suffix
type ItemAffixCommonGroup struct { type ItemAffixCommonGroup struct {
ID int ID int
Members map[string]*ItemAffixCommonRecord Members map[string]*ItemAffixCommonRecord
} }
func (g *ItemAffixCommonGroup) AddMember(a *ItemAffixCommonRecord) { func (g *ItemAffixCommonGroup) addMember(a *ItemAffixCommonRecord) {
if g.Members == nil { if g.Members == nil {
g.Members = make(map[string]*ItemAffixCommonRecord) g.Members = make(map[string]*ItemAffixCommonRecord)
} }
@ -200,7 +155,7 @@ func (g *ItemAffixCommonGroup) AddMember(a *ItemAffixCommonRecord) {
g.Members[a.Name] = a g.Members[a.Name] = a
} }
func (g *ItemAffixCommonGroup) GetTotalFrequency() int { func (g *ItemAffixCommonGroup) getTotalFrequency() int {
total := 0 total := 0
for _, affix := range g.Members { for _, affix := range g.Members {
@ -210,6 +165,9 @@ func (g *ItemAffixCommonGroup) GetTotalFrequency() int {
return total return total
} }
// ItemAffixCommonModifier is the generic modifier form that prefix/suffix shares
// modifiers are like dynamic properties, they have a key that points to a property
// a parameter for the property, and a min/max value
type ItemAffixCommonModifier struct { type ItemAffixCommonModifier struct {
Code string Code string
Parameter int Parameter int
@ -217,46 +175,49 @@ type ItemAffixCommonModifier struct {
Max int Max int
} }
// ItemAffixCommonRecord is a common definition that both prefix and suffix use
type ItemAffixCommonRecord struct { type ItemAffixCommonRecord struct {
Name string Group *ItemAffixCommonGroup
Modifiers []*ItemAffixCommonModifier
ItemInclude []string
ItemExclude []string
Name string
Class string
TransformColor string
Version int Version int
Type d2enum.ItemAffixSubType Type d2enum.ItemAffixSubType
Level int
MaxLevel int
LevelReq int
ClassLevelReq int
Frequency int
GroupID int
PriceAdd int
PriceScale int
IsPrefix bool IsPrefix bool
IsSuffix bool IsSuffix bool
Spawnable bool Spawnable bool
Rare bool Rare bool
Transform bool
Level int
MaxLevel int
LevelReq int
Class string
ClassLevelReq int
Frequency int
GroupID int
Group *ItemAffixCommonGroup
Modifiers []*ItemAffixCommonModifier
Transform bool
TransformColor string
ItemInclude []string
ItemExclude []string
PriceAdd int
PriceScale int
} }
// ProbabilityToSpawn returns the chance of the affix spawning on an
// item with a given quality level
func (a *ItemAffixCommonRecord) ProbabilityToSpawn(qlvl int) float64 { func (a *ItemAffixCommonRecord) ProbabilityToSpawn(qlvl int) float64 {
if (qlvl > a.MaxLevel) || (qlvl < a.Level) { if (qlvl > a.MaxLevel) || (qlvl < a.Level) {
return 0.0 return 0.0
} }
p := float64(a.Frequency) / float64(a.Group.GetTotalFrequency()) p := float64(a.Frequency) / float64(a.Group.getTotalFrequency())
return p return p
} }

View File

@ -9,55 +9,66 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
) )
// ItemCommonRecord is a representation of entries from armor.txt, weapons.txt, and misc.txt
type ItemCommonRecord struct { type ItemCommonRecord struct {
Source d2enum.InventoryItemType UsageStats [3]ItemUsageStat // stat boosts applied upon usage
CureOverlayStates [2]string // name of the overlay states that are removed upon use of this item
Name string OverlayState string // name of the overlay state to be applied upon use of this item
SpellDescriptionString string // points to a string containing the description
Version int // 0 = classic, 100 = expansion BetterGem string // 3 char code pointing to the gem this upgrades to (non if not applicable)
CompactSave bool // if true, doesn't store any stats upon saving SpellDescriptionCalc d2common.CalcString // a calc string what value to display
Rarity int // higher, the rarer WeaponClass string // what kind of attack does this weapon have (i.e. determines attack animations)
Spawnable bool // if 0, cannot spawn in shops WeaponClass2Hand string // what kind of attack when wielded with two hands
HitClass string // determines sounds/graphic effects when attacking
SpecialFeature string // Just a comment
FlavorText string // unknown, probably just for reference
TransmogCode string // the 3 char code representing the item this becomes via transmog
NightmareUpgrade string // upgraded in higher difficulties
HellUpgrade string
SourceArt string // unused?
GameArt string // unused?
Vendors map[string]*ItemVendorParams // controls vendor settings
Type string // base type in ItemTypes.txt
Type2 string
DropSound string // sfx for dropping
UseSound string // sfx for using
FlippyFile string // DC6 file animation to play when item drops on the ground
InventoryFile string // DC6 file used in your inventory
UniqueInventoryFile string // DC6 file used by the unique version of this item
SetInventoryFile string // DC6 file used by the set version of this item
Code string // identifies the item
NameString string // seems to be identical to code?
AlternateGfx string // code of the DCC used when equipped
OpenBetaGfx string // unknown
NormalCode string
UberCode string
UltraCode string
Name string
Source d2enum.InventoryItemType
Version int // 0 = classic, 100 = expansion
Rarity int // higher, the rarer
MinAC int MinAC int
MaxAC int MaxAC int
Absorbs int // unused? Absorbs int // unused?
Speed int // affects movement speed of wielder, >0 = you move slower, <0 = you move faster Speed int // affects movement speed of wielder, >0 = you move slower, <0 = you move faster
RequiredStrength int RequiredStrength int
Block int // chance to block, capped at 75% Block int // chance to block, capped at 75%
Durability int // base durability 0-255 Durability int // base durability 0-255
NoDurability bool // if true, item has no durability Level int // base item level (controls monster drops, for instance a lv20 monster cannot drop a lv30 item)
RequiredLevel int // required level to wield
Level int // base item level (controls monster drops, for instance a lv20 monster cannot drop a lv30 item) Cost int // base cost
RequiredLevel int // required level to wield GambleCost int // for reference only, not used
Cost int // base cost MagicLevel int // additional magic level (for gambling?)
GambleCost int // for reference only, not used AutoPrefix int // prefix automatically assigned to this item on spawn, maps to group column of Automagic.txt
Code string // identifies the item SpellOffset int // unknown
NameString string // seems to be identical to code? Component int // corresponds to Composit.txt, player animation layer used by this
MagicLevel int // additional magic level (for gambling?) InventoryWidth int
AutoPrefix int // prefix automatically assigned to this item on spawn, maps to group column of Automagic.txt InventoryHeight int
GemSockets int // number of gems to store
AlternateGfx string // code of the DCC used when equipped GemApplyType int // what kind of gem effect is applied
OpenBetaGfx string // unknown
NormalCode string
UberCode string
UltraCode string
SpellOffset int // unknown
Component int // corresponds to Composit.txt, player animation layer used by this
InventoryWidth int
InventoryHeight int
HasInventory bool // if true, item can store gems or runes
GemSockets int // number of gems to store
GemApplyType int // what kind of gem effect is applied
// 0 = weapon, 1= armor or helmet, 2 = shield // 0 = weapon, 1= armor or helmet, 2 = shield
FlippyFile string // DC6 file animation to play when item drops on the ground
InventoryFile string // DC6 file used in your inventory
UniqueInventoryFile string // DC6 file used by the unique version of this item
SetInventoryFile string // DC6 file used by the set version of this item
// these represent how player animations and graphics change upon wearing this // these represent how player animations and graphics change upon wearing this
// these come from ArmType.txt // these come from ArmType.txt
AnimRightArm int AnimRightArm int
@ -67,112 +78,74 @@ type ItemCommonRecord struct {
AnimRightShoulderPad int AnimRightShoulderPad int
AnimLeftShoulderPad int AnimLeftShoulderPad int
Useable bool // can be used via right click if true MinStack int // min size of stack when item is spawned, used if stackable
// game knows what to do if used by item code MaxStack int // max size of stack when item is spawned
Throwable bool DropSfxFrame int // what frame of drop animation the sfx triggers on
Stackable bool // can be stacked in inventory TransTable int // unknown, related to blending mode?
MinStack int // min size of stack when item is spawned, used if stackable LightRadius int // apparently unused
MaxStack int // max size of stack when item is spawned Quest int // indicates that this item belongs to a given quest?
Type string // base type in ItemTypes.txt
Type2 string
DropSound string // sfx for dropping
DropSfxFrame int // what frame of drop animation the sfx triggers on
UseSound string // sfx for using
Unique bool // if true, only spawns as unique
Transparent bool // unused
TransTable int // unknown, related to blending mode?
Quivered bool // if true, requires ammo to use
LightRadius int // apparently unused
Belt bool // tells what kind of belt this item is
Quest int // indicates that this item belongs to a given quest?
MissileType int // missile gfx for throwing MissileType int // missile gfx for throwing
DurabilityWarning int // controls what warning icon appears when durability is low DurabilityWarning int // controls what warning icon appears when durability is low
QuantityWarning int // controls at what quantity the low quantity warning appears QuantityWarning int // controls at what quantity the low quantity warning appears
MinDamage int
MinDamage int MaxDamage int
MaxDamage int StrengthBonus int
StrengthBonus int DexterityBonus int
DexterityBonus int
// final mindam = min * str / strbonus + min * dex / dexbonus // final mindam = min * str / strbonus + min * dex / dexbonus
// same for maxdam // same for maxdam
GemOffset int // unknown GemOffset int // unknown
BitField1 int // 1 = leather item, 3 = metal BitField1 int // 1 = leather item, 3 = metal
ColorTransform int // colormap to use for player's gfx
Vendors map[string]*ItemVendorParams // controls vendor settings InventoryColorTransform int // colormap to use for inventory's gfx
Min2HandDamage int
SourceArt string // unused? Max2HandDamage int
GameArt string // unused? MinMissileDamage int // ranged damage stats
ColorTransform int // colormap to use for player's gfx MaxMissileDamage int
InventoryColorTransform int // colormap to use for inventory's gfx MissileSpeed int // unknown, affects movement speed of wielder during ranged attacks?
ExtraRange int // base range = 1, if this is non-zero add this to the range
SkipName bool // if true, don't include the base name in the item description
NightmareUpgrade string // upgraded in higher difficulties
HellUpgrade string
Nameable bool // if true, item can be personalized
// weapon params
BarbOneOrTwoHanded bool // if true, barb can wield this in one or two hands
UsesTwoHands bool // if true, it's a 2handed weapon
Min2HandDamage int
Max2HandDamage int
MinMissileDamage int // ranged damage stats
MaxMissileDamage int
MissileSpeed int // unknown, affects movement speed of wielder during ranged attacks?
ExtraRange int // base range = 1, if this is non-zero add this to the range
// final mindam = min * str / strbonus + min * dex / dexbonus // final mindam = min * str / strbonus + min * dex / dexbonus
// same for maxdam // same for maxdam
RequiredDexterity int RequiredDexterity int
SpawnStack int // unknown, something to do with stack size when spawned (sold maybe?)
WeaponClass string // what kind of attack does this weapon have (i.e. determines attack animations) TransmogMin int // min amount of the transmog item to create
WeaponClass2Hand string // what kind of attack when wielded with two hands TransmogMax int // max ''
HitClass string // determines sounds/graphic effects when attacking SpellIcon int // which icon to display when used? Is this always -1?
SpawnStack int // unknown, something to do with stack size when spawned (sold maybe?) SpellType int // determines what kind of function is used when you use this item
EffectLength int // timer for timed usage effects
SpecialFeature string // Just a comment
QuestDifficultyCheck bool // if true, item only works in the difficulty it was found in
PermStoreItem bool // if true, vendor will always sell this
// misc params
FlavorText string // unknown, probably just for reference
Transmogrify bool // if true, can be turned into another item via right click
TransmogCode string // the 3 char code representing the item this becomes via transmog
TransmogMin int // min amount of the transmog item to create
TransmogMax int // max ''
AutoBelt bool // if true, item is put into your belt when picked up
SpellIcon int // which icon to display when used? Is this always -1?
SpellType int // determines what kind of function is used when you use this item
OverlayState string // name of the overlay state to be applied upon use of this item
CureOverlayStates [2]string // name of the overlay states that are removed upon use of this item
EffectLength int // timer for timed usage effects
UsageStats [3]ItemUsageStat // stat boosts applied upon usage
SpellDescriptionType int // specifies how to format the usage description SpellDescriptionType int // specifies how to format the usage description
// 0 = none, 1 = use desc string, 2 = use desc string + calc value // 0 = none, 1 = use desc string, 2 = use desc string + calc value
SpellDescriptionString string // points to a string containing the description
SpellDescriptionCalc d2common.CalcString // a calc string what value to display
BetterGem string // 3 char code pointing to the gem this upgrades to (non if not applicable) AutoBelt bool // if true, item is put into your belt when picked up
HasInventory bool // if true, item can store gems or runes
Multibuy bool // if true, when you buy via right click + shift it will fill your belt automatically CompactSave bool // if true, doesn't store any stats upon saving
Spawnable bool // if 0, cannot spawn in shops
NoDurability bool // if true, item has no durability
Useable bool // can be used via right click if true
// game knows what to do if used by item code
Throwable bool
Stackable bool // can be stacked in inventory
Unique bool // if true, only spawns as unique
Transparent bool // unused
Quivered bool // if true, requires ammo to use
Belt bool // tells what kind of belt this item is
SkipName bool // if true, don't include the base name in the item description
Nameable bool // if true, item can be personalized
BarbOneOrTwoHanded bool // if true, barb can wield this in one or two hands
UsesTwoHands bool // if true, it's a 2handed weapon
QuestDifficultyCheck bool // if true, item only works in the difficulty it was found in
PermStoreItem bool // if true, vendor will always sell this
Transmogrify bool // if true, can be turned into another item via right click
Multibuy bool // if true, when you buy via right click + shift it will fill your belt automatically
} }
// ItemUsageStat the stat that gets applied when the item is used
type ItemUsageStat struct { type ItemUsageStat struct {
Stat string // name of the stat to add to Stat string // name of the stat to add to
Calc d2common.CalcString // calc string representing the amount to add Calc d2common.CalcString // calc string representing the amount to add
} }
// ItemVendorParams are parameters that vendors use
type ItemVendorParams struct { type ItemVendorParams struct {
Min int // minimum of this item they can stock Min int // minimum of this item they can stock
Max int // max they can stock Max int // max they can stock
@ -181,16 +154,18 @@ type ItemVendorParams struct {
MagicLevel uint8 MagicLevel uint8
} }
var CommonItems map[string]*ItemCommonRecord // CommonItems stores all ItemCommonRecords
var CommonItems map[string]*ItemCommonRecord //nolint:gochecknoglobals // Currently global by design
func LoadCommonItems(file []byte, source d2enum.InventoryItemType) *map[string]*ItemCommonRecord { // LoadCommonItems loads armor/weapons/misc.txt ItemCommonRecords
func LoadCommonItems(file []byte, source d2enum.InventoryItemType) map[string]*ItemCommonRecord {
if CommonItems == nil { if CommonItems == nil {
CommonItems = make(map[string]*ItemCommonRecord) CommonItems = make(map[string]*ItemCommonRecord)
} }
items := make(map[string]*ItemCommonRecord) items := make(map[string]*ItemCommonRecord)
data := strings.Split(string(file), "\r\n") data := strings.Split(string(file), "\r\n")
mapping := MapHeaders(data[0]) mapping := mapHeaders(data[0])
for lineno, line := range data { for lineno, line := range data {
if lineno == 0 { if lineno == 0 {
@ -201,178 +176,178 @@ func LoadCommonItems(file []byte, source d2enum.InventoryItemType) *map[string]*
continue continue
} }
rec := createCommonItemRecord(line, &mapping, source) rec := createCommonItemRecord(line, mapping, source)
items[rec.Code] = &rec items[rec.Code] = &rec
CommonItems[rec.Code] = &rec CommonItems[rec.Code] = &rec
} }
return &items return items
} }
//nolint:funlen // Makes no sens to split //nolint:funlen // Makes no sens to split
func createCommonItemRecord(line string, mapping *map[string]int, source d2enum.InventoryItemType) ItemCommonRecord { func createCommonItemRecord(line string, mapping map[string]int, source d2enum.InventoryItemType) ItemCommonRecord {
r := strings.Split(line, "\t") r := strings.Split(line, "\t")
result := ItemCommonRecord{ result := ItemCommonRecord{
Source: source, Source: source,
Name: MapLoadString(&r, mapping, "name"), Name: mapLoadString(&r, mapping, "name"),
Version: MapLoadInt(&r, mapping, "version"), Version: mapLoadInt(&r, mapping, "version"),
CompactSave: MapLoadBool(&r, mapping, "compactsave"), CompactSave: mapLoadBool(&r, mapping, "compactsave"),
Rarity: MapLoadInt(&r, mapping, "rarity"), Rarity: mapLoadInt(&r, mapping, "rarity"),
Spawnable: MapLoadBool(&r, mapping, "spawnable"), Spawnable: mapLoadBool(&r, mapping, "spawnable"),
MinAC: MapLoadInt(&r, mapping, "minac"), MinAC: mapLoadInt(&r, mapping, "minac"),
MaxAC: MapLoadInt(&r, mapping, "maxac"), MaxAC: mapLoadInt(&r, mapping, "maxac"),
Absorbs: MapLoadInt(&r, mapping, "absorbs"), Absorbs: mapLoadInt(&r, mapping, "absorbs"),
Speed: MapLoadInt(&r, mapping, "speed"), Speed: mapLoadInt(&r, mapping, "speed"),
RequiredStrength: MapLoadInt(&r, mapping, "reqstr"), RequiredStrength: mapLoadInt(&r, mapping, "reqstr"),
Block: MapLoadInt(&r, mapping, "block"), Block: mapLoadInt(&r, mapping, "block"),
Durability: MapLoadInt(&r, mapping, "durability"), Durability: mapLoadInt(&r, mapping, "durability"),
NoDurability: MapLoadBool(&r, mapping, "nodurability"), NoDurability: mapLoadBool(&r, mapping, "nodurability"),
Level: MapLoadInt(&r, mapping, "level"), Level: mapLoadInt(&r, mapping, "level"),
RequiredLevel: MapLoadInt(&r, mapping, "levelreq"), RequiredLevel: mapLoadInt(&r, mapping, "levelreq"),
Cost: MapLoadInt(&r, mapping, "cost"), Cost: mapLoadInt(&r, mapping, "cost"),
GambleCost: MapLoadInt(&r, mapping, "gamble cost"), GambleCost: mapLoadInt(&r, mapping, "gamble cost"),
Code: MapLoadString(&r, mapping, "code"), Code: mapLoadString(&r, mapping, "code"),
NameString: MapLoadString(&r, mapping, "namestr"), NameString: mapLoadString(&r, mapping, "namestr"),
MagicLevel: MapLoadInt(&r, mapping, "magic lvl"), MagicLevel: mapLoadInt(&r, mapping, "magic lvl"),
AutoPrefix: MapLoadInt(&r, mapping, "auto prefix"), AutoPrefix: mapLoadInt(&r, mapping, "auto prefix"),
AlternateGfx: MapLoadString(&r, mapping, "alternategfx"), AlternateGfx: mapLoadString(&r, mapping, "alternategfx"),
OpenBetaGfx: MapLoadString(&r, mapping, "OpenBetaGfx"), OpenBetaGfx: mapLoadString(&r, mapping, "OpenBetaGfx"),
NormalCode: MapLoadString(&r, mapping, "normcode"), NormalCode: mapLoadString(&r, mapping, "normcode"),
UberCode: MapLoadString(&r, mapping, "ubercode"), UberCode: mapLoadString(&r, mapping, "ubercode"),
UltraCode: MapLoadString(&r, mapping, "ultracode"), UltraCode: mapLoadString(&r, mapping, "ultracode"),
SpellOffset: MapLoadInt(&r, mapping, "spelloffset"), SpellOffset: mapLoadInt(&r, mapping, "spelloffset"),
Component: MapLoadInt(&r, mapping, "component"), Component: mapLoadInt(&r, mapping, "component"),
InventoryWidth: MapLoadInt(&r, mapping, "invwidth"), InventoryWidth: mapLoadInt(&r, mapping, "invwidth"),
InventoryHeight: MapLoadInt(&r, mapping, "invheight"), InventoryHeight: mapLoadInt(&r, mapping, "invheight"),
HasInventory: MapLoadBool(&r, mapping, "hasinv"), HasInventory: mapLoadBool(&r, mapping, "hasinv"),
GemSockets: MapLoadInt(&r, mapping, "gemsockets"), GemSockets: mapLoadInt(&r, mapping, "gemsockets"),
GemApplyType: MapLoadInt(&r, mapping, "gemapplytype"), GemApplyType: mapLoadInt(&r, mapping, "gemapplytype"),
FlippyFile: MapLoadString(&r, mapping, "flippyfile"), FlippyFile: mapLoadString(&r, mapping, "flippyfile"),
InventoryFile: MapLoadString(&r, mapping, "invfile"), InventoryFile: mapLoadString(&r, mapping, "invfile"),
UniqueInventoryFile: MapLoadString(&r, mapping, "uniqueinvfile"), UniqueInventoryFile: mapLoadString(&r, mapping, "uniqueinvfile"),
SetInventoryFile: MapLoadString(&r, mapping, "setinvfile"), SetInventoryFile: mapLoadString(&r, mapping, "setinvfile"),
AnimRightArm: MapLoadInt(&r, mapping, "rArm"), AnimRightArm: mapLoadInt(&r, mapping, "rArm"),
AnimLeftArm: MapLoadInt(&r, mapping, "lArm"), AnimLeftArm: mapLoadInt(&r, mapping, "lArm"),
AnimTorso: MapLoadInt(&r, mapping, "Torso"), AnimTorso: mapLoadInt(&r, mapping, "Torso"),
AnimLegs: MapLoadInt(&r, mapping, "Legs"), AnimLegs: mapLoadInt(&r, mapping, "Legs"),
AnimRightShoulderPad: MapLoadInt(&r, mapping, "rSPad"), AnimRightShoulderPad: mapLoadInt(&r, mapping, "rSPad"),
AnimLeftShoulderPad: MapLoadInt(&r, mapping, "lSPad"), AnimLeftShoulderPad: mapLoadInt(&r, mapping, "lSPad"),
Useable: MapLoadBool(&r, mapping, "useable"), Useable: mapLoadBool(&r, mapping, "useable"),
Throwable: MapLoadBool(&r, mapping, "throwable"), Throwable: mapLoadBool(&r, mapping, "throwable"),
Stackable: MapLoadBool(&r, mapping, "stackable"), Stackable: mapLoadBool(&r, mapping, "stackable"),
MinStack: MapLoadInt(&r, mapping, "minstack"), MinStack: mapLoadInt(&r, mapping, "minstack"),
MaxStack: MapLoadInt(&r, mapping, "maxstack"), MaxStack: mapLoadInt(&r, mapping, "maxstack"),
Type: MapLoadString(&r, mapping, "type"), Type: mapLoadString(&r, mapping, "type"),
Type2: MapLoadString(&r, mapping, "type2"), Type2: mapLoadString(&r, mapping, "type2"),
DropSound: MapLoadString(&r, mapping, "dropsound"), DropSound: mapLoadString(&r, mapping, "dropsound"),
DropSfxFrame: MapLoadInt(&r, mapping, "dropsfxframe"), DropSfxFrame: mapLoadInt(&r, mapping, "dropsfxframe"),
UseSound: MapLoadString(&r, mapping, "usesound"), UseSound: mapLoadString(&r, mapping, "usesound"),
Unique: MapLoadBool(&r, mapping, "unique"), Unique: mapLoadBool(&r, mapping, "unique"),
Transparent: MapLoadBool(&r, mapping, "transparent"), Transparent: mapLoadBool(&r, mapping, "transparent"),
TransTable: MapLoadInt(&r, mapping, "transtbl"), TransTable: mapLoadInt(&r, mapping, "transtbl"),
Quivered: MapLoadBool(&r, mapping, "quivered"), Quivered: mapLoadBool(&r, mapping, "quivered"),
LightRadius: MapLoadInt(&r, mapping, "lightradius"), LightRadius: mapLoadInt(&r, mapping, "lightradius"),
Belt: MapLoadBool(&r, mapping, "belt"), Belt: mapLoadBool(&r, mapping, "belt"),
Quest: MapLoadInt(&r, mapping, "quest"), Quest: mapLoadInt(&r, mapping, "quest"),
MissileType: MapLoadInt(&r, mapping, "missiletype"), MissileType: mapLoadInt(&r, mapping, "missiletype"),
DurabilityWarning: MapLoadInt(&r, mapping, "durwarning"), DurabilityWarning: mapLoadInt(&r, mapping, "durwarning"),
QuantityWarning: MapLoadInt(&r, mapping, "qntwarning"), QuantityWarning: mapLoadInt(&r, mapping, "qntwarning"),
MinDamage: MapLoadInt(&r, mapping, "mindam"), MinDamage: mapLoadInt(&r, mapping, "mindam"),
MaxDamage: MapLoadInt(&r, mapping, "maxdam"), MaxDamage: mapLoadInt(&r, mapping, "maxdam"),
StrengthBonus: MapLoadInt(&r, mapping, "StrBonus"), StrengthBonus: mapLoadInt(&r, mapping, "StrBonus"),
DexterityBonus: MapLoadInt(&r, mapping, "DexBonus"), DexterityBonus: mapLoadInt(&r, mapping, "DexBonus"),
GemOffset: MapLoadInt(&r, mapping, "gemoffset"), GemOffset: mapLoadInt(&r, mapping, "gemoffset"),
BitField1: MapLoadInt(&r, mapping, "bitfield1"), BitField1: mapLoadInt(&r, mapping, "bitfield1"),
Vendors: createItemVendorParams(&r, mapping), Vendors: createItemVendorParams(&r, mapping),
SourceArt: MapLoadString(&r, mapping, "Source Art"), SourceArt: mapLoadString(&r, mapping, "Source Art"),
GameArt: MapLoadString(&r, mapping, "Game Art"), GameArt: mapLoadString(&r, mapping, "Game Art"),
ColorTransform: MapLoadInt(&r, mapping, "Transform"), ColorTransform: mapLoadInt(&r, mapping, "Transform"),
InventoryColorTransform: MapLoadInt(&r, mapping, "InvTrans"), InventoryColorTransform: mapLoadInt(&r, mapping, "InvTrans"),
SkipName: MapLoadBool(&r, mapping, "SkipName"), SkipName: mapLoadBool(&r, mapping, "SkipName"),
NightmareUpgrade: MapLoadString(&r, mapping, "NightmareUpgrade"), NightmareUpgrade: mapLoadString(&r, mapping, "NightmareUpgrade"),
HellUpgrade: MapLoadString(&r, mapping, "HellUpgrade"), HellUpgrade: mapLoadString(&r, mapping, "HellUpgrade"),
Nameable: MapLoadBool(&r, mapping, "Nameable"), Nameable: mapLoadBool(&r, mapping, "Nameable"),
// weapon params // weapon params
BarbOneOrTwoHanded: MapLoadBool(&r, mapping, "1or2handed"), BarbOneOrTwoHanded: mapLoadBool(&r, mapping, "1or2handed"),
UsesTwoHands: MapLoadBool(&r, mapping, "2handed"), UsesTwoHands: mapLoadBool(&r, mapping, "2handed"),
Min2HandDamage: MapLoadInt(&r, mapping, "2handmindam"), Min2HandDamage: mapLoadInt(&r, mapping, "2handmindam"),
Max2HandDamage: MapLoadInt(&r, mapping, "2handmaxdam"), Max2HandDamage: mapLoadInt(&r, mapping, "2handmaxdam"),
MinMissileDamage: MapLoadInt(&r, mapping, "minmisdam"), MinMissileDamage: mapLoadInt(&r, mapping, "minmisdam"),
MaxMissileDamage: MapLoadInt(&r, mapping, "maxmisdam"), MaxMissileDamage: mapLoadInt(&r, mapping, "maxmisdam"),
MissileSpeed: MapLoadInt(&r, mapping, "misspeed"), MissileSpeed: mapLoadInt(&r, mapping, "misspeed"),
ExtraRange: MapLoadInt(&r, mapping, "rangeadder"), ExtraRange: mapLoadInt(&r, mapping, "rangeadder"),
RequiredDexterity: MapLoadInt(&r, mapping, "reqdex"), RequiredDexterity: mapLoadInt(&r, mapping, "reqdex"),
WeaponClass: MapLoadString(&r, mapping, "wclass"), WeaponClass: mapLoadString(&r, mapping, "wclass"),
WeaponClass2Hand: MapLoadString(&r, mapping, "2handedwclass"), WeaponClass2Hand: mapLoadString(&r, mapping, "2handedwclass"),
HitClass: MapLoadString(&r, mapping, "hit class"), HitClass: mapLoadString(&r, mapping, "hit class"),
SpawnStack: MapLoadInt(&r, mapping, "spawnstack"), SpawnStack: mapLoadInt(&r, mapping, "spawnstack"),
SpecialFeature: MapLoadString(&r, mapping, "special"), SpecialFeature: mapLoadString(&r, mapping, "special"),
QuestDifficultyCheck: MapLoadBool(&r, mapping, "questdiffcheck"), QuestDifficultyCheck: mapLoadBool(&r, mapping, "questdiffcheck"),
PermStoreItem: MapLoadBool(&r, mapping, "PermStoreItem"), PermStoreItem: mapLoadBool(&r, mapping, "PermStoreItem"),
// misc params // misc params
FlavorText: MapLoadString(&r, mapping, "szFlavorText"), FlavorText: mapLoadString(&r, mapping, "szFlavorText"),
Transmogrify: MapLoadBool(&r, mapping, "Transmogrify"), Transmogrify: mapLoadBool(&r, mapping, "Transmogrify"),
TransmogCode: MapLoadString(&r, mapping, "TMogType"), TransmogCode: mapLoadString(&r, mapping, "TMogType"),
TransmogMin: MapLoadInt(&r, mapping, "TMogMin"), TransmogMin: mapLoadInt(&r, mapping, "TMogMin"),
TransmogMax: MapLoadInt(&r, mapping, "TMogMax"), TransmogMax: mapLoadInt(&r, mapping, "TMogMax"),
AutoBelt: MapLoadBool(&r, mapping, "autobelt"), AutoBelt: mapLoadBool(&r, mapping, "autobelt"),
SpellIcon: MapLoadInt(&r, mapping, "spellicon"), SpellIcon: mapLoadInt(&r, mapping, "spellicon"),
SpellType: MapLoadInt(&r, mapping, "pSpell"), SpellType: mapLoadInt(&r, mapping, "pSpell"),
OverlayState: MapLoadString(&r, mapping, "state"), OverlayState: mapLoadString(&r, mapping, "state"),
CureOverlayStates: [2]string{ CureOverlayStates: [2]string{
MapLoadString(&r, mapping, "cstate1"), mapLoadString(&r, mapping, "cstate1"),
MapLoadString(&r, mapping, "cstate2"), mapLoadString(&r, mapping, "cstate2"),
}, },
EffectLength: MapLoadInt(&r, mapping, "len"), EffectLength: mapLoadInt(&r, mapping, "len"),
UsageStats: createItemUsageStats(&r, mapping), UsageStats: createItemUsageStats(&r, mapping),
SpellDescriptionType: MapLoadInt(&r, mapping, "spelldesc"), SpellDescriptionType: mapLoadInt(&r, mapping, "spelldesc"),
// 0 = none, 1 = use desc string, 2 = use desc string + calc value // 0 = none, 1 = use desc string, 2 = use desc string + calc value
SpellDescriptionString: MapLoadString(&r, mapping, "spelldescstr"), SpellDescriptionString: mapLoadString(&r, mapping, "spelldescstr"),
SpellDescriptionCalc: d2common.CalcString(MapLoadString(&r, mapping, "spelldesccalc")), SpellDescriptionCalc: d2common.CalcString(mapLoadString(&r, mapping, "spelldesccalc")),
BetterGem: MapLoadString(&r, mapping, "BetterGem"), BetterGem: mapLoadString(&r, mapping, "BetterGem"),
Multibuy: MapLoadBool(&r, mapping, "multibuy"), Multibuy: mapLoadBool(&r, mapping, "multibuy"),
} }
return result return result
} }
func createItemVendorParams(r *[]string, mapping *map[string]int) map[string]*ItemVendorParams { func createItemVendorParams(r *[]string, mapping map[string]int) map[string]*ItemVendorParams {
vs := make([]string, 17) vs := make([]string, 17)
vs[0] = "Charsi" vs[0] = "Charsi"
vs[1] = "Gheed" vs[1] = "Gheed"
@ -396,11 +371,11 @@ func createItemVendorParams(r *[]string, mapping *map[string]int) map[string]*It
for _, name := range vs { for _, name := range vs {
wvp := ItemVendorParams{ wvp := ItemVendorParams{
Min: MapLoadInt(r, mapping, name+"Min"), Min: mapLoadInt(r, mapping, name+"Min"),
Max: MapLoadInt(r, mapping, name+"Max"), Max: mapLoadInt(r, mapping, name+"Max"),
MagicMin: MapLoadInt(r, mapping, name+"MagicMin"), MagicMin: mapLoadInt(r, mapping, name+"MagicMin"),
MagicMax: MapLoadInt(r, mapping, name+"MagicMax"), MagicMax: mapLoadInt(r, mapping, name+"MagicMax"),
MagicLevel: MapLoadUint8(r, mapping, name+"MagicLvl"), MagicLevel: mapLoadUint8(r, mapping, name+"MagicLvl"),
} }
result[name] = &wvp result[name] = &wvp
} }
@ -408,11 +383,11 @@ func createItemVendorParams(r *[]string, mapping *map[string]int) map[string]*It
return result return result
} }
func createItemUsageStats(r *[]string, mapping *map[string]int) [3]ItemUsageStat { func createItemUsageStats(r *[]string, mapping map[string]int) [3]ItemUsageStat {
result := [3]ItemUsageStat{} result := [3]ItemUsageStat{}
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
result[i].Stat = MapLoadString(r, mapping, "stat"+strconv.Itoa(i)) result[i].Stat = mapLoadString(r, mapping, "stat"+strconv.Itoa(i))
result[i].Calc = d2common.CalcString(MapLoadString(r, mapping, "calc"+strconv.Itoa(i))) result[i].Calc = d2common.CalcString(mapLoadString(r, mapping, "calc"+strconv.Itoa(i)))
} }
return result return result

View File

@ -7,35 +7,53 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
) )
// ItemStatCostRecord represents a row from itemstatcost.txt
// these records describe the stat values and costs (in shops) of items
// refer to https://d2mods.info/forum/kb/viewarticle?a=448 // refer to https://d2mods.info/forum/kb/viewarticle?a=448
type ItemStatCostRecord struct { type ItemStatCostRecord struct {
Name string Name string
Index int OpBase string
OpStat1 string
OpStat2 string
OpStat3 string
Signed bool // whether the stat is signed MaxStat string // if Direct true, will not exceed val of MaxStat
KeepZero bool // prevent from going negative (assume only client side) DescStrPos string // string used when val is positive
DescStrNeg string
DescStr2 string // additional string used by some string funcs
DescGroupStrPos string // string used when val is positive
DescGroupStrNeg string
DescGroupStr2 string // additional string used by some string funcs
// Stuff
// Stay far away from this column unless you really know what you're
// doing and / or work for Blizzard, this column is used during bin-file
// creation to generate a cache regulating the op-stat stuff and other
// things, changing it can be futile, it works like the constants column
// in MonUMod.txt and has no other relation to ItemStatCost.txt, the first
// stat in the file simply must have this set or else you may break the
// entire op stuff.
Stuff string
DescFn interface{} // the sprintf func
DescGroupFn interface{} // group sprintf func
Index int
// path_d2.mpq version doesnt have Ranged columne, excluding for now // path_d2.mpq version doesnt have Ranged columne, excluding for now
// Ranged bool // game attempts to keep stat in a range, like strength >-1 // Ranged bool // game attempts to keep stat in a range, like strength >-1
MinAccr int // minimum ranged value MinAccr int // minimum ranged value
UpdateAnimRate bool // when altered, forces speed handler to adjust speed SendBits int // #bits to send in stat update
SendParam int // #bits to send in stat update
SendOther bool // whether to send to other clients SavedBits int // #bits allocated to the value in .d2s file
SendBits int // #bits to send in stat update
SendParam int // #bits to send in stat update
Saved bool // whether this stat is saved in .d2s files
SavedSigned bool // whether the stat is saved as signed/unsigned
SavedBits int // #bits allocated to the value in .d2s file
SaveBits int // #bits saved to .d2s files, max == 2^SaveBits-1 SaveBits int // #bits saved to .d2s files, max == 2^SaveBits-1
SaveAdd int // how large the negative range is (lowers max, as well) SaveAdd int // how large the negative range is (lowers max, as well)
SaveParamBits int // #param bits are saved (safe value is 17) SaveParamBits int // #param bits are saved (safe value is 17)
Encode EncodingType // how the stat is encoded in .d2s files Encode d2enum.EncodingType // how the stat is encoded in .d2s files
CallbackEnabled bool // whether callback fn is called if value changes
// these two fields control additional cost on items // these two fields control additional cost on items
// cost * (1 + value * multiply / 1024)) + add (...) // cost * (1 + value * multiply / 1024)) + add (...)
@ -48,20 +66,8 @@ type ItemStatCostRecord struct {
ValShift int // controls how stat is stored in .d2s ValShift int // controls how stat is stored in .d2s
// so that you can save `+1` instead of `+256` // so that you can save `+1` instead of `+256`
OperatorType OperatorType OperatorType d2enum.OperatorType
OpParam int OpParam int
OpBase string
OpStat1 string
OpStat2 string
OpStat3 string
Direct bool // whether is temporary or permanent
MaxStat string // if Direct true, will not exceed val of MaxStat
ItemSpecific bool // prevents stacking with an existing stat on item
// like when socketing a jewel
DamageRelated bool // prevents stacking of stats while dual wielding
EventID1 d2enum.ItemEventType EventID1 d2enum.ItemEventType
EventID2 d2enum.ItemEventType EventID2 d2enum.ItemEventType
@ -70,175 +76,35 @@ type ItemStatCostRecord struct {
DescPriority int // determines order when displayed DescPriority int // determines order when displayed
DescFnID d2enum.DescFuncID DescFnID d2enum.DescFuncID
DescFn interface{} // the sprintf func
// Controls whenever and if so in what way the stat value is shown // Controls whenever and if so in what way the stat value is shown
// 0 = doesn't show the value of the stat // 0 = doesn't show the value of the stat
// 1 = shows the value of the stat infront of the description // 1 = shows the value of the stat infront of the description
// 2 = shows the value of the stat after the description. // 2 = shows the value of the stat after the description.
DescVal int DescVal int
DescStrPos string // string used when val is positive
DescStrNeg string
DescStr2 string // additional string used by some string funcs
// when stats in the same group have the same value they use the // when stats in the same group have the same value they use the
// group func for desc (they need to be in the same affix) // group func for desc (they need to be in the same affix)
DescGroup int DescGroup int
DescGroupFuncID d2enum.DescFuncID
DescGroupFn interface{} // group sprintf func
DescGroupVal int DescGroupVal int
DescGroupStrPos string // string used when val is positive DescGroupFuncID d2enum.DescFuncID
DescGroupStrNeg string
DescGroupStr2 string // additional string used by some string funcs
// Stay far away from this column unless you really know what you're CallbackEnabled bool // whether callback fn is called if value changes
// doing and / or work for Blizzard, this column is used during bin-file Signed bool // whether the stat is signed
// creation to generate a cache regulating the op-stat stuff and other KeepZero bool // prevent from going negative (assume only client side)
// things, changing it can be futile, it works like the constants column UpdateAnimRate bool // when altered, forces speed handler to adjust speed
// in MonUMod.txt and has no other relation to ItemStatCost.txt, the first SendOther bool // whether to send to other clients
// stat in the file simply must have this set or else you may break the Saved bool // whether this stat is saved in .d2s files
// entire op stuff. SavedSigned bool // whether the stat is saved as signed/unsigned
Stuff string // ? TODO ? Direct bool // whether is temporary or permanent
ItemSpecific bool // prevents stacking with an existing stat on item
// like when socketing a jewel
DamageRelated bool // prevents stacking of stats while dual wielding
} }
type EncodingType int // ItemStatCosts stores all of the ItemStatCostRecords
//nolint:gochecknoglobals // Currently global by design
const (
// TODO: determine other encoding types.
// didn't see anything about how this stuff is encoded, or the types...
EncodeDefault = EncodingType(iota)
)
type OperatorType int // for dynamic properties
const (
// just adds the stat to the unit directly
OpDefault = OperatorType(iota)
// Op1 adds opstat.base * statvalue / 100 to the opstat.
Op1
// Op2 adds (statvalue * basevalue) / (2 ^ param) to the opstat
// this does not work properly with any stat other then level because of the
// way this is updated, it is only refreshed when you re-equip the item,
// your character is saved or you level up, similar to passive skills, just
// because it looks like it works in the item description
// does not mean it does, the game just recalculates the information in the
// description every frame, while the values remain unchanged serverside.
Op2
// Op3 is a percentage based version of op #2
// look at op #2 for information about the formula behind it, just
// remember the stat is increased by a percentage rather then by adding
// an integer.
Op3
// Op4 works the same way op #2 works, however the stat bonus is
// added to the item and not to the player (so that +defense per level
// properly adds the defense to the armor and not to the character
// directly!)
Op4
// Op5 works like op #4 but is percentage based, it is used for percentage
// based increase of stats that are found on the item itself, and not stats
// that are found on the character.
Op5
// Op6 works like for op #7, however this adds a plain bonus to the stat, and just
// like #7 it also doesn't work so I won't bother to explain the arithmetic
// behind it either.
Op6
// Op7 is used to increase a stat based on the current daytime of the game
// world by a percentage, there is no need to explain the arithmetics
// behind it because frankly enough it just doesn't work serverside, it
// only updates clientside so this op is essentially useless.
Op7
// Op8 hardcoded to work only with maxmana, this will apply the proper amount
// of mana to your character based on CharStats.txt for the amount of energy
// the stat added (doesn't work for non characters)
Op8
// Op9 hardcoded to work only with maxhp and maxstamina, this will apply the
// proper amount of maxhp and maxstamina to your character based on
// CharStats.txt for the amount of vitality the stat added (doesn't work
// for non characters)
Op9
// Op10 doesn't do anything, this has no switch case in the op function.
Op10
// Op11 adds opstat.base * statvalue / 100 similar to 1 and 13, the code just
// does a few more checks
Op11
// Op12 doesn't do anything, this has no switch case in the op function.
Op12
// Op13 adds opstat.base * statvalue / 100 to the value of opstat, this is
// useable only on items it will not apply the bonus to other unit types
// (this is why it is used for +% durability, +% level requirement,
// +% damage, +% defense ).
Op13
)
/* column names from path_d2.mpq/data/global/excel/ItemStatCost.txt
Stat
ID
Send Other
Signed
Send Bits
Send Param Bits
UpdateAnimRate
Saved
CSvSigned
CSvBits
CSvParam
fCallback
fMin
MinAccr
Encode
Add
Multiply
Divide
ValShift
1.09-Save Bits
1.09-Save Add
Save Bits
Save Add
Save Param Bits
keepzero
op
op param
op base
op stat1
op stat2
op stat3
direct
maxstat
itemspecific
damagerelated
itemevent1
itemeventfunc1
itemevent2
itemeventfunc2
descpriority
descfunc
descval
descstrpos
descstrneg
descstr2
dgrp
dgrpfunc
dgrpval
dgrpstrpos
dgrpstrneg
dgrpstr2
stuff
*eol
*/
var ItemStatCosts map[string]*ItemStatCostRecord var ItemStatCosts map[string]*ItemStatCostRecord
// LoadItemStatCosts loads ItemStatCostRecord's from text // LoadItemStatCosts loads ItemStatCostRecord's from text
@ -271,7 +137,7 @@ func LoadItemStatCosts(file []byte) {
SaveAdd: d.GetNumber("Save Add", idx), SaveAdd: d.GetNumber("Save Add", idx),
SaveParamBits: d.GetNumber("Save Param Bits", idx), SaveParamBits: d.GetNumber("Save Param Bits", idx),
Encode: EncodingType(d.GetNumber("Encode", idx)), Encode: d2enum.EncodingType(d.GetNumber("Encode", idx)),
CallbackEnabled: d.GetNumber("fCallback", idx) > 0, CallbackEnabled: d.GetNumber("fCallback", idx) > 0,
@ -279,7 +145,7 @@ func LoadItemStatCosts(file []byte) {
CostMultiply: d.GetNumber("Multiply", idx), CostMultiply: d.GetNumber("Multiply", idx),
ValShift: d.GetNumber("ValShift", idx), ValShift: d.GetNumber("ValShift", idx),
OperatorType: OperatorType(d.GetNumber("op", idx)), OperatorType: d2enum.OperatorType(d.GetNumber("op", idx)),
OpParam: d.GetNumber("op param", idx), OpParam: d.GetNumber("op param", idx),
OpBase: d.GetString("op base", idx), OpBase: d.GetString("op base", idx),
OpStat1: d.GetString("op stat1", idx), OpStat1: d.GetString("op stat1", idx),

View File

@ -6,6 +6,8 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// LevelMazeDetailsRecord is a representation of a row from lvlmaze.txt
// these records define the parameters passed to the maze level generator
type LevelMazeDetailsRecord struct { type LevelMazeDetailsRecord struct {
// descriptive, not loaded in game. Corresponds with Name field in // descriptive, not loaded in game. Corresponds with Name field in
// Levels.txt // Levels.txt
@ -34,7 +36,8 @@ type LevelMazeDetailsRecord struct {
// Beta // Beta
} }
var LevelMazeDetails map[int]*LevelMazeDetailsRecord // LevelMazeDetails stores all of the LevelMazeDetailsRecords
var LevelMazeDetails map[int]*LevelMazeDetailsRecord //nolint:gochecknoglobals // Currently global by design
// LoadLevelMazeDetails loads LevelMazeDetailsRecords from text file // LoadLevelMazeDetails loads LevelMazeDetailsRecords from text file
func LoadLevelMazeDetails(file []byte) { func LoadLevelMazeDetails(file []byte) {

View File

@ -7,25 +7,27 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// LevelPresetRecord is a representation of a row from lvlprest.txt
// these records define parameters for the preset level map generator
type LevelPresetRecord struct { type LevelPresetRecord struct {
Files [6]string
Name string Name string
DefinitionID int DefinitionID int
LevelID int LevelID int
SizeX int
SizeY int
Pops int
PopPad int
FileCount int
Dt1Mask uint
Populate bool Populate bool
Logicals bool Logicals bool
Outdoors bool Outdoors bool
Animate bool Animate bool
KillEdge bool KillEdge bool
FillBlanks bool FillBlanks bool
SizeX int
SizeY int
AutoMap bool AutoMap bool
Scan bool Scan bool
Pops int
PopPad int
FileCount int
Files [6]string
Dt1Mask uint
Beta bool Beta bool
Expansion bool Expansion bool
} }
@ -70,7 +72,8 @@ func createLevelPresetRecord(props []string) LevelPresetRecord {
return result return result
} }
var LevelPresets map[int]LevelPresetRecord // LevelPresets stores all of the LevelPresetRecords
var LevelPresets map[int]LevelPresetRecord //nolint:gochecknoglobals // Currently global by design
// LoadLevelPresets loads level presets from text file // LoadLevelPresets loads level presets from text file
func LoadLevelPresets(file []byte) { func LoadLevelPresets(file []byte) {

View File

@ -6,9 +6,12 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// LevelSubstitutionRecord is a representation of a row from lvlsub.txt
// these records are parameters for levels and describe substitution rules
type LevelSubstitutionRecord struct { type LevelSubstitutionRecord struct {
// Description, reference only. // Description, reference only.
Name string // Name Name string // Name
// This value is used in Levels.txt, in the column 'SubType'. You'll notice // This value is used in Levels.txt, in the column 'SubType'. You'll notice
// that in LvlSub.txt some rows use the same value, we can say they forms // that in LvlSub.txt some rows use the same value, we can say they forms
// groups. If you count each row of a group starting from 0, then you'll // groups. If you count each row of a group starting from 0, then you'll
@ -61,8 +64,11 @@ type LevelSubstitutionRecord struct {
// Beta // Beta
} }
// LevelSubstitutions stores all of the LevelSubstitutionRecords
//nolint:gochecknoglobals // Currently global by design
var LevelSubstitutions map[int]*LevelSubstitutionRecord var LevelSubstitutions map[int]*LevelSubstitutionRecord
// LoadLevelSubstitutions loads lvlsub.txt and parses into records
func LoadLevelSubstitutions(file []byte) { func LoadLevelSubstitutions(file []byte) {
dict := d2common.LoadDataDictionary(string(file)) dict := d2common.LoadDataDictionary(string(file))
numRecords := len(dict.Data) numRecords := len(dict.Data)

View File

@ -7,17 +7,21 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// LevelTypeRecord is a representation of a row from lvltype.txt
// the fields describe what ds1 files a level uses
type LevelTypeRecord struct { type LevelTypeRecord struct {
Files [32]string
Name string Name string
ID int ID int
Files [32]string
Beta bool
Act int Act int
Beta bool
Expansion bool Expansion bool
} }
var LevelTypes []LevelTypeRecord // LevelTypes stores all of the LevelTypeRecords
var LevelTypes []LevelTypeRecord //nolint:gochecknoglobals // Currently global by design,
// LoadLevelTypes loads the LevelTypeRecords
func LoadLevelTypes(file []byte) { func LoadLevelTypes(file []byte) {
data := strings.Split(string(file), "\r\n")[1:] data := strings.Split(string(file), "\r\n")[1:]
LevelTypes = make([]LevelTypeRecord, len(data)) LevelTypes = make([]LevelTypeRecord, len(data))

View File

@ -6,6 +6,8 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// LevelWarpRecord is a representation of a row from lvlwarp.txt
// it describes the warp graphics offsets and dimensions for levels
type LevelWarpRecord struct { type LevelWarpRecord struct {
ID int32 ID int32
SelectX int32 SelectX int32
@ -21,8 +23,8 @@ type LevelWarpRecord struct {
Direction string Direction string
} }
//nolint:gochecknoglobals // Currently global by design, only written once
// LevelWarps loaded from txt records // LevelWarps loaded from txt records
//nolint:gochecknoglobals // Currently global by design, only written once
var LevelWarps map[int]*LevelWarpRecord var LevelWarps map[int]*LevelWarpRecord
// LoadLevelWarps loads LevelWarpRecord's from text file data // LoadLevelWarps loads LevelWarpRecord's from text file data

View File

@ -7,21 +7,103 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
) )
// LevelDetailsRecord is a representation of a row from levels.txt
// it describes lots of things about the levels, like where they are connected,
// what kinds of monsters spawn, the level generator type, and lots of other stuff.
type LevelDetailsRecord struct { type LevelDetailsRecord struct {
// Name
// This column has no function, it only serves as a comment field to make it // This column has no function, it only serves as a comment field to make it
// easier to identify the Level name // easier to identify the Level name
Name string // Name <-- the corresponding column name in the txt Name string // Name <-- the corresponding column name in the txt
// Level ID (used in columns like VIS0-7) // mon1-mon25 work in Normal difficulty, while nmon1-nmon25 in Nightmare and
Id int // Id // Hell. They tell the game which monster ID taken from MonStats.txt.
// NOTE: you need to manually add from mon11 to mon25 and from nmon11 to
// nmon25 !
MonsterID1Normal string // mon1
MonsterID2Normal string // mon2
MonsterID3Normal string // mon3
MonsterID4Normal string // mon4
MonsterID5Normal string // mon5
MonsterID6Normal string // mon6
MonsterID7Normal string // mon7
MonsterID8Normal string // mon8
MonsterID9Normal string // mon9
MonsterID10Normal string // mon10
// Act Palette . Reference only MonsterID1Nightmare string // nmon1
MonsterID2Nightmare string // nmon2
MonsterID3Nightmare string // nmon3
MonsterID4Nightmare string // nmon4
MonsterID5Nightmare string // nmon5
MonsterID6Nightmare string // nmon6
MonsterID7Nightmare string // nmon7
MonsterID8Nightmare string // nmon8
MonsterID9Nightmare string // nmon9
MonsterID10Nightmare string // nmon10
// Gravestench - adding additional fields for Hell, original txt combined
// the nighmare and hell ID's stringo the same field
MonsterID1Hell string // nmon1
MonsterID2Hell string // nmon2
MonsterID3Hell string // nmon3
MonsterID4Hell string // nmon4
MonsterID5Hell string // nmon5
MonsterID6Hell string // nmon6
MonsterID7Hell string // nmon7
MonsterID8Hell string // nmon8
MonsterID9Hell string // nmon9
MonsterID10Hell string // nmon10
// Works only in normal and it tells which ID will be used for Champion and
// Random Uniques. The ID is taken from MonStats.txtOnly the first ten
// columns appear in the unmodded file. In 1.10 final, beta 1.10s and
// v1.11+ you can add the missing umon11-umon25 columns.
// NOTE: you can allow umon1-25 to also work in Nightmare and Hell by
// following this simple ASM edit
// (https://d2mods.info/forum/viewtopic.php?f=8&t=53969&p=425179&hilit=umon#p425179)
MonsterUniqueID1 string // umon1
MonsterUniqueID2 string // umon2
MonsterUniqueID3 string // umon3
MonsterUniqueID4 string // umon4
MonsterUniqueID5 string // umon5
MonsterUniqueID6 string // umon6
MonsterUniqueID7 string // umon7
MonsterUniqueID8 string // umon8
MonsterUniqueID9 string // umon9
MonsterUniqueID10 string // umon10
// Critter Species 1-4. Uses the Id from monstats2.txt and only monsters
// with critter column set to 1 can spawn here. critter column is also found
// in monstats2.txt. Critters are in reality only present clientside.
MonsterCritterID1 string // cmon1
MonsterCritterID2 string // cmon2
MonsterCritterID3 string // cmon3
MonsterCritterID4 string // cmon4
// String Code for the Display name of the Level
LevelDisplayName string // LevelName
LevelWarpName string // LevelWarp
// Which *.DC6 Title Image is loaded when you enter this area. this file
// MUST exist, otherwise you will crash with an exception when you enter the
// level (for all levels below the expansion row, the files must be
// present in the expension folders)
TitleImageName string // EntryFile
// Id
// Level ID (used in columns like VIS0-7)
Id int //nolint:golint Id is the right key
// Palette is the Act Palette . Reference only
Palette int // Pal Palette int // Pal
// The Act the Level is located in (internal enumeration ranges from 0 to 4) // Act that the Level is located in (internal enumeration ranges from 0 to 4)
Act int // Act Act int // Act
// QuestFlag, QuestExpansionFlag
// Used the first one in Classic games and the latter in Expansion games , // Used the first one in Classic games and the latter in Expansion games ,
// they set a questflag. If this flag is set, a character must have // they set a questflag. If this flag is set, a character must have
// completed the quest associated with the flag to take a town portal to // completed the quest associated with the flag to take a town portal to
@ -37,17 +119,15 @@ type LevelDetailsRecord struct {
// additional layers. // additional layers.
AutomapIndex int // Layer AutomapIndex int // Layer
// sizeX - SizeY in each difficulty. If this is a preset area this sets the // SizeXNormal -- SizeYHell If this is a preset area this sets the
// X size for the area. Othervise use the same value here that are used in // X size for the area. Othervise use the same value here that are used in
// lvlprest.txt to set the size for the .ds1 file. // lvlprest.txt to set the size for the .ds1 file.
SizeXNormal int // SizeX SizeXNormal int // SizeX
SizeYNormal int // SizeY SizeYNormal int // SizeY
SizeXNightmare int // SizeX(N) SizeXNightmare int // SizeX(N)
SizeYNightmare int // SizeY(N) SizeYNightmare int // SizeY(N)
SizeXHell int // SizeX(H)
SizeXHell int // SizeX(H) SizeYHell int // SizeY(H)
SizeYHell int // SizeY(H)
// They set the X\Y position in the world space // They set the X\Y position in the world space
WorldOffsetX int // OffsetX WorldOffsetX int // OffsetX
@ -58,6 +138,9 @@ type LevelDetailsRecord struct {
// location. // location.
DependantLevelID int // Depend DependantLevelID int // Depend
// The type of the Level (Id from lvltypes.txt)
LevelType int // LevelType
// Controls if teleport is allowed in that level. // Controls if teleport is allowed in that level.
// 0 = Teleport not allowed // 0 = Teleport not allowed
// 1 = Teleport allowed // 1 = Teleport allowed
@ -65,55 +148,12 @@ type LevelDetailsRecord struct {
// (maybe for objects this is controlled by IsDoor column in objects.txt) // (maybe for objects this is controlled by IsDoor column in objects.txt)
TeleportFlag d2enum.TeleportFlag // Teleport TeleportFlag d2enum.TeleportFlag // Teleport
// It sets whether rain or snow (in act 5 only) can fall . Set it to 1 in
// order to enable it, 0 to disable it.
EnableRain bool // Rain
// Unused setting (In pre beta D2 Blizzard planned Rain to generate Mud
// which would have slowed your character's speed down, but this never made
// it into the final game). the field is read by the code but the return
// value is never utilized.
EnableMud bool // Mud
// Setting for 3D Enhanced D2 that disables Perspective Mode for a specific
// level. A value of 1 enables the users to choose between normal and
// Perspective view, while 0 disables that choice.
EnablePerspective bool // NoPer
// Allows you to look through objects and walls even if they are not in a
// wilderness level. 1 enables it, 0 disables it.
EnableLineOfSightDraw bool // LOSDraw
// Unknown. Probably has to do with Tiles and their Placement.
// 1 enables it, 0 disables it.
EnableFloorFliter bool // FloorFilter
// Unknown. Probably has to do with tiles and their placement.
// 1 enables it, 0 disables it.
// TODO: needs a better name
EnableBlankScreen bool // BlankScreen
// for levels bordered with mountains or walls, like the act 1 wildernesses.
// 1 enables it, 0 disables it.
EnableDrawEdges bool // DrawEdges
// Setting it to 1 makes the level to be treated as an indoor area, while
// 0 makes this level an outdoor. Indoor areas are not affected by day-night
// cycles, because they always use the light values specified in Intensity,
// Red, Green, Blue. this field also controls whenever sounds will echo if
// you're running the game with a sound card capable of it and have
// environment sound effects set to true.
IsInside bool // IsInside
// Setting for Level Generation: You have 3 possibilities here: // Setting for Level Generation: You have 3 possibilities here:
// 1 Random Maze // 1 Random Maze
// 2 Preset Area // 2 Preset Area
// 3 Wilderness level // 3 Wilderness level
LevelGenerationType d2enum.LevelGenerationType // DrlgType LevelGenerationType d2enum.LevelGenerationType // DrlgType
// The type of the Level (Id from lvltypes.txt)
LevelType int // LevelType
// NOTE // NOTE
// IDs from LvlSub.txt, which is used to randomize outdoor areas, such as // IDs from LvlSub.txt, which is used to randomize outdoor areas, such as
// spawning ponds in the blood moor and more stones in the Stoney Field. // spawning ponds in the blood moor and more stones in the Stoney Field.
@ -123,7 +163,6 @@ type LevelDetailsRecord struct {
// Example: 6=wilderness, 9=desert etc, -1=no subtype. // Example: 6=wilderness, 9=desert etc, -1=no subtype.
SubType int // SubType SubType int // SubType
// TODO this may need an enumeration.. ?
// Tells which subtheme a wilderness area should use. // Tells which subtheme a wilderness area should use.
// Themes ranges from -1 (no subtheme) to 4. // Themes ranges from -1 (no subtheme) to 4.
SubTheme int // SubTheme SubTheme int // SubTheme
@ -170,29 +209,6 @@ type LevelDetailsRecord struct {
Green int // Green Green int // Green
Blue int // Blue Blue int // Blue
// This field is required for some levels, entering those levels when portal
// field isn't set will often crash the game. This also applies to
// duplicates of those levels created with both of the extended level
// plugins.
PortalEnable bool // Portal
// TODO: this field needs a better name
// This controls if you can re-position a portal in a level or not. If it's
// set to 1 you will be able to reposition the portal by using either map
// entry#76 Tp Location #79. If both tiles are in the level it will use Tp
// Location #79. If set to 0 the map won't allow repositioning.
PortalRepositionEnable bool // Position
// Setting this field to 1 will make the monsters status saved in the map.
// Setting it to 0 will allow some useful things like NPC refreshing their
// stores.
// WARNING: Do not set this to 1 for non-town areas, or the monsters you'll
// flee from will simply vanish and never reappear. They won't even be
// replaced by new ones
// Gravestench - this funcionality should not be in one field
SaveMonsterStates bool // SaveMonsters
SaveMerchantStates bool // SaveMonsters
// What quest is this level related to. This is the quest id (as example the // What quest is this level related to. This is the quest id (as example the
// first quest Den of Evil are set to 1, since its the first quest). // first quest Den of Evil are set to 1, since its the first quest).
QuestID int // Quest QuestID int // Quest
@ -227,14 +243,6 @@ type LevelDetailsRecord struct {
MonsterUniqueMaxNightmare int // MonUMax(N) MonsterUniqueMaxNightmare int // MonUMax(N)
MonsterUniqueMaxHell int // MonUMax(H) MonsterUniqueMaxHell int // MonUMax(H)
// No info on the PK page, but I'm guessing it's for monster wandering
MonsterWanderEnable bool // MonWndr
// This setting is hardcoded to certain level Ids, like the River Of Flame,
// enabling it in other places can glitch up the game, so leave it alone.
// It is not known what exactly it does however.
MonsterSpecialWalk bool // MonSpcWalk
// Number of different Monster Types that will be present in this area, the // Number of different Monster Types that will be present in this area, the
// maximum is 13. You can have up to 13 different monster types at a time in // maximum is 13. You can have up to 13 different monster types at a time in
// Nightmare and Hell difficulties, selected randomly from nmon1-nmon25. In // Nightmare and Hell difficulties, selected randomly from nmon1-nmon25. In
@ -243,92 +251,12 @@ type LevelDetailsRecord struct {
// types selected randomly from umon1-umon25. // types selected randomly from umon1-umon25.
NumMonsterTypes int // NumMon NumMonsterTypes int // NumMon
// mon1-mon25 work in Normal difficulty, while nmon1-nmon25 in Nightmare and
// Hell. They tell the game which monster ID taken from MonStats.txt.
// NOTE: you need to manually add from mon11 to mon25 and from nmon11 to
// nmon25 !
MonsterID1Normal string // mon1
MonsterID2Normal string // mon2
MonsterID3Normal string // mon3
MonsterID4Normal string // mon4
MonsterID5Normal string // mon5
MonsterID6Normal string // mon6
MonsterID7Normal string // mon7
MonsterID8Normal string // mon8
MonsterID9Normal string // mon9
MonsterID10Normal string // mon10
MonsterID1Nightmare string // nmon1
MonsterID2Nightmare string // nmon2
MonsterID3Nightmare string // nmon3
MonsterID4Nightmare string // nmon4
MonsterID5Nightmare string // nmon5
MonsterID6Nightmare string // nmon6
MonsterID7Nightmare string // nmon7
MonsterID8Nightmare string // nmon8
MonsterID9Nightmare string // nmon9
MonsterID10Nightmare string // nmon10
// Gravestench - adding additional fields for Hell, original txt combined
// the nighmare and hell ID's stringo the same field
MonsterID1Hell string // nmon1
MonsterID2Hell string // nmon2
MonsterID3Hell string // nmon3
MonsterID4Hell string // nmon4
MonsterID5Hell string // nmon5
MonsterID6Hell string // nmon6
MonsterID7Hell string // nmon7
MonsterID8Hell string // nmon8
MonsterID9Hell string // nmon9
MonsterID10Hell string // nmon10
// Give preference to monsters set to ranged=1 in MonStats.txt on Nightmare
// and Hell difficulties when picking something to spawn.
MonsterPreferRanged bool // rangedspawn
// Works only in normal and it tells which ID will be used for Champion and
// Random Uniques. The ID is taken from MonStats.txtOnly the first ten
// columns appear in the unmodded file. In 1.10 final, beta 1.10s and
// v1.11+ you can add the missing umon11-umon25 columns.
// NOTE: you can allow umon1-25 to also work in Nightmare and Hell by
// following this simple ASM edit
// (https://d2mods.info/forum/viewtopic.php?f=8&t=53969&p=425179&hilit=umon#p425179)
MonsterUniqueID1 string // umon1
MonsterUniqueID2 string // umon2
MonsterUniqueID3 string // umon3
MonsterUniqueID4 string // umon4
MonsterUniqueID5 string // umon5
MonsterUniqueID6 string // umon6
MonsterUniqueID7 string // umon7
MonsterUniqueID8 string // umon8
MonsterUniqueID9 string // umon9
MonsterUniqueID10 string // umon10
// Critter Species 1-4. Uses the Id from monstats2.txt and only monsters
// with critter column set to 1 can spawn here. critter column is also found
// in monstats2.txt. Critters are in reality only present clientside.
MonsterCritterID1 string // cmon1
MonsterCritterID2 string // cmon2
MonsterCritterID3 string // cmon3
MonsterCritterID4 string // cmon4
// Controls the chance for a critter to spawn. // Controls the chance for a critter to spawn.
MonsterCritter1SpawnChance int // cpct1 MonsterCritter1SpawnChance int // cpct1
MonsterCritter2SpawnChance int // cpct2 MonsterCritter2SpawnChance int // cpct2
MonsterCritter3SpawnChance int // cpct3 MonsterCritter3SpawnChance int // cpct3
MonsterCritter4SpawnChance int // cpct4 MonsterCritter4SpawnChance int // cpct4
// Unknown. These columns are bugged, as the game overrides the contents of
// columns 3-4 with the value from column 1 when it compiles the bin files.
// camt1
// camt2
// camt3
// camt4
// Unknown. It states which theme is used by the area and this field is
// accessed by the code but it is not exactly known what it does.
// Themes
// Referes to a entry in SoundEnviron.txt (for the Levels Music) // Referes to a entry in SoundEnviron.txt (for the Levels Music)
SoundEnvironmentID int // SoundEnv SoundEnvironmentID int // SoundEnv
@ -338,17 +266,6 @@ type LevelDetailsRecord struct {
// between acts however so don't even bother to try. // between acts however so don't even bother to try.
WaypointID int // Waypoint WaypointID int // Waypoint
// String Code for the Display name of the Level
LevelDisplayName string // LevelName
LevelWarpName string // LevelWarp
// Which *.DC6 Title Image is loaded when you enter this area. this file
// MUST exist, otherwise you will crash with an exception when you enter the
// level (for all levels below the expansion row, the files must be
// present in the expension folders)
TitleImageName string // EntryFile
// this field uses the ID of the ObjectGroup you want to Spawn in this Area, // this field uses the ID of the ObjectGroup you want to Spawn in this Area,
// taken from Objgroup.txt. // taken from Objgroup.txt.
ObjectGroupID0 int // ObjGrp0 ObjectGroupID0 int // ObjGrp0
@ -371,12 +288,86 @@ type LevelDetailsRecord struct {
ObjectGroupSpawnChance6 int // ObjPrb6 ObjectGroupSpawnChance6 int // ObjPrb6
ObjectGroupSpawnChance7 int // ObjPrb7 ObjectGroupSpawnChance7 int // ObjPrb7
// Reference Only (can be used for comments) // It sets whether rain or snow (in act 5 only) can fall . Set it to 1 in
// Beta // order to enable it, 0 to disable it.
EnableRain bool // Rain
// Unused setting (In pre beta D2 Blizzard planned Rain to generate Mud
// which would have slowed your character's speed down, but this never made
// it into the final game). the field is read by the code but the return
// value is never utilized.
EnableMud bool // Mud
// Setting for 3D Enhanced D2 that disables Perspective Mode for a specific
// level. A value of 1 enables the users to choose between normal and
// Perspective view, while 0 disables that choice.
EnablePerspective bool // NoPer
// Allows you to look through objects and walls even if they are not in a
// wilderness level. 1 enables it, 0 disables it.
EnableLineOfSightDraw bool // LOSDraw
// Unknown. Probably has to do with Tiles and their Placement.
// 1 enables it, 0 disables it.
EnableFloorFliter bool // FloorFilter
// Unknown. Probably has to do with tiles and their placement.
// 1 enables it, 0 disables it.
EnableBlankScreen bool // BlankScreen
// for levels bordered with mountains or walls, like the act 1 wildernesses.
// 1 enables it, 0 disables it.
EnableDrawEdges bool // DrawEdges
// Setting it to 1 makes the level to be treated as an indoor area, while
// 0 makes this level an outdoor. Indoor areas are not affected by day-night
// cycles, because they always use the light values specified in Intensity,
// Red, Green, Blue. this field also controls whenever sounds will echo if
// you're running the game with a sound card capable of it and have
// environment sound effects set to true.
IsInside bool // IsInside
// This field is required for some levels, entering those levels when portal
// field isn't set will often crash the game. This also applies to
// duplicates of those levels created with both of the extended level
// plugins.
PortalEnable bool // Portal
// This controls if you can re-position a portal in a level or not. If it's
// set to 1 you will be able to reposition the portal by using either map
// entry#76 Tp Location #79. If both tiles are in the level it will use Tp
// Location #79. If set to 0 the map won't allow repositioning.
PortalRepositionEnable bool // Position
// Setting this field to 1 will make the monsters status saved in the map.
// Setting it to 0 will allow some useful things like NPC refreshing their
// stores.
// WARNING: Do not set this to 1 for non-town areas, or the monsters you'll
// flee from will simply vanish and never reappear. They won't even be
// replaced by new ones
// Gravestench - this funcionality should not be in one field
SaveMonsterStates bool // SaveMonsters
SaveMerchantStates bool // SaveMonsters
// No info on the PK page, but I'm guessing it's for monster wandering
MonsterWanderEnable bool // MonWndr
// This setting is hardcoded to certain level Ids, like the River Of Flame,
// enabling it in other places can glitch up the game, so leave it alone.
// It is not known what exactly it does however.
MonsterSpecialWalk bool // MonSpcWalk
// Give preference to monsters set to ranged=1 in MonStats.txt on Nightmare
// and Hell difficulties when picking something to spawn.
MonsterPreferRanged bool // rangedspawn
} }
// LevelDetails has all of the LevelDetailsRecords
//nolint:gochecknoglobals // Currently global by design, only written once
var LevelDetails map[int]*LevelDetailsRecord var LevelDetails map[int]*LevelDetailsRecord
// GetLevelDetails gets a LevelDetailsRecord by the record Id
func GetLevelDetails(id int) *LevelDetailsRecord { func GetLevelDetails(id int) *LevelDetailsRecord {
for i := 0; i < len(LevelDetails); i++ { for i := 0; i < len(LevelDetails); i++ {
if LevelDetails[i].Id == id { if LevelDetails[i].Id == id {
@ -387,6 +378,7 @@ func GetLevelDetails(id int) *LevelDetailsRecord {
return nil return nil
} }
// LoadLevelDetails loads level details records from levels.txt
//nolint:funlen // Txt loader, makes no sense to split //nolint:funlen // Txt loader, makes no sense to split
func LoadLevelDetails(file []byte) { func LoadLevelDetails(file []byte) {
dict := d2common.LoadDataDictionary(string(file)) dict := d2common.LoadDataDictionary(string(file))

View File

@ -6,7 +6,7 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
func MapHeaders(line string) map[string]int { func mapHeaders(line string) map[string]int {
m := make(map[string]int) m := make(map[string]int)
r := strings.Split(line, "\t") r := strings.Split(line, "\t")
@ -17,8 +17,8 @@ func MapHeaders(line string) map[string]int {
return m return m
} }
func MapLoadInt(r *[]string, mapping *map[string]int, field string) int { func mapLoadInt(r *[]string, mapping map[string]int, field string) int {
index, ok := (*mapping)[field] index, ok := (mapping)[field]
if ok { if ok {
return d2common.StringToInt(d2common.EmptyToZero(d2common.AsterToEmpty((*r)[index]))) return d2common.StringToInt(d2common.EmptyToZero(d2common.AsterToEmpty((*r)[index])))
} }
@ -26,8 +26,8 @@ func MapLoadInt(r *[]string, mapping *map[string]int, field string) int {
return 0 return 0
} }
func MapLoadString(r *[]string, mapping *map[string]int, field string) string { func mapLoadString(r *[]string, mapping map[string]int, field string) string {
index, ok := (*mapping)[field] index, ok := (mapping)[field]
if ok { if ok {
return d2common.AsterToEmpty((*r)[index]) return d2common.AsterToEmpty((*r)[index])
} }
@ -35,12 +35,12 @@ func MapLoadString(r *[]string, mapping *map[string]int, field string) string {
return "" return ""
} }
func MapLoadBool(r *[]string, mapping *map[string]int, field string) bool { func mapLoadBool(r *[]string, mapping map[string]int, field string) bool {
return MapLoadInt(r, mapping, field) == 1 return mapLoadInt(r, mapping, field) == 1
} }
func MapLoadUint8(r *[]string, mapping *map[string]int, field string) uint8 { func mapLoadUint8(r *[]string, mapping map[string]int, field string) uint8 {
index, ok := (*mapping)[field] index, ok := (mapping)[field]
if ok { if ok {
return d2common.StringToUint8(d2common.EmptyToZero(d2common.AsterToEmpty((*r)[index]))) return d2common.StringToUint8(d2common.EmptyToZero(d2common.AsterToEmpty((*r)[index])))
} }

View File

@ -6,9 +6,11 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
) )
var MiscItems map[string]*ItemCommonRecord // MiscItems stores all of the ItemCommonRecords for misc.txt
var MiscItems map[string]*ItemCommonRecord //nolint:gochecknoglobals // Currently global by design
// LoadMiscItems loads ItemCommonRecords from misc.txt
func LoadMiscItems(file []byte) { func LoadMiscItems(file []byte) {
MiscItems = *LoadCommonItems(file, d2enum.InventoryItemTypeItem) MiscItems = LoadCommonItems(file, d2enum.InventoryItemTypeItem)
log.Printf("Loaded %d misc items", len(MiscItems)) log.Printf("Loaded %d misc items", len(MiscItems))
} }

View File

@ -7,17 +7,20 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// MissileCalcParam is a calculation parameter for a missile
type MissileCalcParam struct { type MissileCalcParam struct {
Param int Param int
Desc string Desc string
} }
// MissileCalc is a calculation for a missile
type MissileCalc struct { type MissileCalc struct {
Calc d2common.CalcString Calc d2common.CalcString
Desc string Desc string
Params []MissileCalcParam Params []MissileCalcParam
} }
// MissileLight has the parameters for missile lighting
type MissileLight struct { type MissileLight struct {
Diameter int Diameter int
Flicker int Flicker int
@ -26,24 +29,27 @@ type MissileLight struct {
Blue uint8 Blue uint8
} }
// MissileAnimation stores parameters for a missile animation
type MissileAnimation struct { type MissileAnimation struct {
CelFileName string
StepsBeforeVisible int StepsBeforeVisible int
StepsBeforeActive int StepsBeforeActive int
LoopAnimation bool
CelFileName string
AnimationRate int // seems to do nothing AnimationRate int // seems to do nothing
AnimationLength int AnimationLength int
AnimationSpeed int AnimationSpeed int
StartingFrame int // called "RandFrame" StartingFrame int // called "RandFrame"
HasSubLoop bool // runs after first animation ends
SubStartingFrame int SubStartingFrame int
SubEndingFrame int SubEndingFrame int
LoopAnimation bool
HasSubLoop bool // runs after first animation ends
} }
// MissileCollision parameters for missile collision
type MissileCollision struct { type MissileCollision struct {
CollisionType int // controls the kind of collision CollisionType int // controls the kind of collision
// 0 = none, 1 = units only, 3 = normal (units, walls), // 0 = none, 1 = units only, 3 = normal (units, walls),
// 6 = walls only, 8 = walls, units, and floors // 6 = walls only, 8 = walls, units, and floors
TimerFrames int // how many frames to persist
DestroyedUponCollision bool DestroyedUponCollision bool
FriendlyFire bool FriendlyFire bool
LastCollide bool // unknown LastCollide bool // unknown
@ -51,9 +57,9 @@ type MissileCollision struct {
ClientCollision bool // unknown ClientCollision bool // unknown
ClientSend bool // unclear ClientSend bool // unclear
UseCollisionTimer bool // after hit, use timer before dying UseCollisionTimer bool // after hit, use timer before dying
TimerFrames int // how many frames to persist
} }
// MissileDamage parameters for calculating missile physical damage
type MissileDamage struct { type MissileDamage struct {
MinDamage int MinDamage int
MaxDamage int MaxDamage int
@ -63,6 +69,7 @@ type MissileDamage struct {
DamageSynergyPerCalc d2common.CalcString // works like synergy in skills.txt, not clear DamageSynergyPerCalc d2common.CalcString // works like synergy in skills.txt, not clear
} }
// MissileElementalDamage parameters for calculating missile elemental damage
type MissileElementalDamage struct { type MissileElementalDamage struct {
Damage MissileDamage Damage MissileDamage
ElementType string ElementType string
@ -70,20 +77,46 @@ type MissileElementalDamage struct {
LevelDuration [3]int // 0,1,2, unknown level intervals, bonus duration per level LevelDuration [3]int // 0,1,2, unknown level intervals, bonus duration per level
} }
// MissileRecord is a representation of a row from missiles.txt
type MissileRecord struct { type MissileRecord struct {
Name string ServerMovementCalc MissileCalc
Id int ClientMovementCalc MissileCalc
ServerCollisionCalc MissileCalc
ClientCollisionCalc MissileCalc
ServerDamageCalc MissileCalc
Light MissileLight
Animation MissileAnimation
Collision MissileCollision
Damage MissileDamage
ElementalDamage MissileElementalDamage
SubMissile [3]string // 0,1,2 name of missiles spawned by movement function
HitSubMissile [4]string // 0,1,2 name of missiles spawned by collision function
ClientSubMissile [3]string // see above, but for client only
ClientHitSubMissile [4]string // see above, but for client only
Name string
SkillName string // if not empty, the missile will refer to this skill instead of its own data for the following:
// "ResultFlags, HitFlags, HitShift, HitClass, SrcDamage (SrcDam in skills.txt!),
// MinDam, MinLevDam1-5, MaxDam, MaxLevDam1-5, DmgSymPerCalc, EType, EMin, EMinLev1-5,
// EMax, EMaxLev1-5, EDmgSymPerCalc, ELen, ELenLev1-3, ELenSymPerCalc"
TravelSound string // name of sound to play during lifetime
// whether or not it loops depends on the specific sound's settings?
// if it doesn't loop, it's just a on-spawn sound effect
HitSound string // sound plays upon collision
ProgSound string // plays at "special events", like a mariachi band
ProgOverlay string // name of an overlay from overlays.txt to use at special events
ExplosionMissile string // name of a missile from missiles.txt that is created upon collision
// or anytime it is destroyed if AlwaysExplode is true
Id int //nolint:golint Id is the correct key
ClientMovementFunc int ClientMovementFunc int
ClientCollisionFunc int ClientCollisionFunc int
ServerMovementFunc int ServerMovementFunc int
ServerCollisionFunc int ServerCollisionFunc int
ServerDamageFunc int ServerDamageFunc int
ServerMovementCalc MissileCalc
ClientMovementCalc MissileCalc
ServerCollisionCalc MissileCalc
ClientCollisionCalc MissileCalc
ServerDamageCalc MissileCalc
Velocity int Velocity int
MaxVelocity int MaxVelocity int
@ -92,20 +125,46 @@ type MissileRecord struct {
Range int Range int
LevelRangeBonus int LevelRangeBonus int
Light MissileLight
Animation MissileAnimation
Collision MissileCollision
XOffset int XOffset int
YOffset int YOffset int
ZOffset int ZOffset int
Size int // diameter Size int // diameter
DestroyedByTP bool // if true, destroyed when source player teleports to town DestroyedByTPFrame int // see above, for client side, (this is a guess) which frame it vanishes on
DestroyedByTPFrame int // see above, for client side, (this is a guess) which frame it vanishes on
CanDestroy bool // unknown HolyFilterType int // specifies what this missile can hit
KnockbackPercent int // chance of knocking the target back, 0-100
TransparencyMode int // controls rendering
// 0 = normal, 1 = alpha blending (darker = more transparent)
// 2 = special (black and white?)
ResultFlags int // unknown
// 4 = normal missiles, 5 = explosions, 8 = non-damaging missiles
HitFlags int // unknown
// 2 = explosions, 5 = freezing arrow
HitShift int // damage is measured in 256s
// the actual damage is [damage] * 2 ^ [hitshift]
// e.g. 100 damage, 8 hitshift = 100 * 2 ^ 8 = 100 * 256 = 25600
// (visually, the damage is this result / 256)
SourceDamage int // 0-128, 128 is 100%
SourceMissDamage int // 0-128, 128 is 100%
// unknown, only used for poison clouds.
HitClass int // controls clientside aesthetic effects for collisions
// particularly sound effects that are played on a hit
NumDirections int // count of dirs in the DCC loaded by CelFile
// apparently this value is no longer needed in D2
LocalBlood int // blood effects?
// 0 = no blood, 1 = blood, 2 = blood and affected by open wounds
DamageReductionRate int // how many frames between applications of the
// magic_damage_reduced stat, so for instance on a 0 this stat applies every frame
// on a 3, only every 4th frame has damage reduced
DestroyedByTP bool // if true, destroyed when source player teleports to town
CanDestroy bool // unknown
UseAttackRating bool // if true, uses 'attack rating' to determine if it hits or misses UseAttackRating bool // if true, uses 'attack rating' to determine if it hits or misses
// if false, has a 95% chance to hit. // if false, has a 95% chance to hit.
@ -120,74 +179,25 @@ type MissileRecord struct {
// if false, vanishes when spawned in town // if false, vanishes when spawned in town
IgnoreBossModifiers bool // if true, doesn't get bonuses from boss mods IgnoreBossModifiers bool // if true, doesn't get bonuses from boss mods
IgnoreMultishot bool // if true, can't gain the mulitshot modifier IgnoreMultishot bool // if true, can't gain the mulitshot modifier
HolyFilterType int // specifies what this missile can hit
// 0 = all units, 1 = undead only, 2 = demons only, 3 = all units (again?) // 0 = all units, 1 = undead only, 2 = demons only, 3 = all units (again?)
CanBeSlowed bool // if true, is affected by skill_handofathena CanBeSlowed bool // if true, is affected by skill_handofathena
TriggersHitEvents bool // if true, triggers events that happen "upon getting hit" on targets TriggersHitEvents bool // if true, triggers events that happen "upon getting hit" on targets
TriggersGetHit bool // if true, can cause target to enter hit recovery mode TriggersGetHit bool // if true, can cause target to enter hit recovery mode
SoftHit bool // unknown SoftHit bool // unknown
KnockbackPercent int // chance of knocking the target back, 0-100
TransparencyMode int // controls rendering
// 0 = normal, 1 = alpha blending (darker = more transparent)
// 2 = special (black and white?)
UseQuantity bool // if true, uses quantity UseQuantity bool // if true, uses quantity
// not clear what this means. Also apparently requires a special starting function in skills.txt // not clear what this means. Also apparently requires a special starting function in skills.txt
AffectedByPierce bool // if true, affected by the pierce modifier and the Pierce skill AffectedByPierce bool // if true, affected by the pierce modifier and the Pierce skill
SpecialSetup bool // unknown, only true for potions SpecialSetup bool // unknown, only true for potions
MissileSkill bool // if true, applies elemental damage from items to the splash radius instead of normal damage modifiers MissileSkill bool // if true, applies elemental damage from items to the splash radius instead of normal damage modifiers
SkillName string // if not empty, the missile will refer to this skill instead of its own data for the following:
// "ResultFlags, HitFlags, HitShift, HitClass, SrcDamage (SrcDam in skills.txt!),
// MinDam, MinLevDam1-5, MaxDam, MaxLevDam1-5, DmgSymPerCalc, EType, EMin, EMinLev1-5,
// EMax, EMaxLev1-5, EDmgSymPerCalc, ELen, ELenLev1-3, ELenSymPerCalc"
ResultFlags int // unknown
// 4 = normal missiles, 5 = explosions, 8 = non-damaging missiles
HitFlags int // unknown
// 2 = explosions, 5 = freezing arrow
HitShift int // damage is measured in 256s
// the actual damage is [damage] * 2 ^ [hitshift]
// e.g. 100 damage, 8 hitshift = 100 * 2 ^ 8 = 100 * 256 = 25600
// (visually, the damage is this result / 256)
ApplyMastery bool // unknown ApplyMastery bool // unknown
SourceDamage int // 0-128, 128 is 100%
// percentage of source units attack properties to apply to the missile? // percentage of source units attack properties to apply to the missile?
// not only affects damage but also other modifiers like lifesteal and manasteal (need a complete list) // not only affects damage but also other modifiers like lifesteal and manasteal (need a complete list)
// setting this to -1 "gets rid of SrcDmg from skills.txt", not clear what that means // setting this to -1 "gets rid of SrcDmg from skills.txt", not clear what that means
HalfDamageForTwoHander bool // if true, damage is halved when a two-handed weapon is used HalfDamageForTwoHander bool // if true, damage is halved when a two-handed weapon is used
SourceMissDamage int // 0-128, 128 is 100%
// unknown, only used for poison clouds.
Damage MissileDamage
ElementalDamage MissileElementalDamage
HitClass int // controls clientside aesthetic effects for collisions
// particularly sound effects that are played on a hit
NumDirections int // count of dirs in the DCC loaded by CelFile
// apparently this value is no longer needed in D2
LocalBlood int // blood effects?
// 0 = no blood, 1 = blood, 2 = blood and affected by open wounds
DamageReductionRate int // how many frames between applications of the
// magic_damage_reduced stat, so for instance on a 0 this stat applies every frame
// on a 3, only every 4th frame has damage reduced
TravelSound string // name of sound to play during lifetime
// whether or not it loops depends on the specific sound's settings?
// if it doesn't loop, it's just a on-spawn sound effect
HitSound string // sound plays upon collision
ProgSound string // plays at "special events", like a mariachi band
ProgOverlay string // name of an overlay from overlays.txt to use at special events
ExplosionMissile string // name of a missile from missiles.txt that is created upon collision
// or anytime it is destroyed if AlwaysExplode is true
SubMissile [3]string // 0,1,2 name of missiles spawned by movement function
HitSubMissile [4]string // 0,1,2 name of missiles spawned by collision function
ClientSubMissile [3]string // see above, but for client only
ClientHitSubMissile [4]string // see above, but for client only
} }
func createMissileRecord(line string) MissileRecord { func createMissileRecord(line string) MissileRecord {
@ -292,9 +302,11 @@ func createMissileRecord(line string) MissileRecord {
return result return result
} }
// Missiles stores all of the MissileRecords
//nolint:gochecknoglobals // Currently global by design, only written once //nolint:gochecknoglobals // Currently global by design, only written once
var Missiles map[int]*MissileRecord var Missiles map[int]*MissileRecord
// LoadMissiles loads MissileRecords from missiles.txt
func LoadMissiles(file []byte) { func LoadMissiles(file []byte) {
Missiles = make(map[int]*MissileRecord) Missiles = make(map[int]*MissileRecord)
data := strings.Split(string(file), "\r\n")[1:] data := strings.Split(string(file), "\r\n")[1:]

View File

@ -6,9 +6,11 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// MonPresets stores monster presets
//nolint:gochecknoglobals // Currently global by design, only written once //nolint:gochecknoglobals // Currently global by design, only written once
var MonPresets [][]string var MonPresets [][]string
// LoadMonPresets loads monster presets from monpresets.txt
func LoadMonPresets(file []byte) { func LoadMonPresets(file []byte) {
dict := d2common.LoadDataDictionary(string(file)) dict := d2common.LoadDataDictionary(string(file))
numRecords := len(dict.Data) numRecords := len(dict.Data)

View File

@ -1,4 +1,3 @@
// d2datadict contains loaders for the txt file data
package d2datadict package d2datadict
import ( import (
@ -29,7 +28,7 @@ type (
// column also links other hardcoded effects to the units, such as the // column also links other hardcoded effects to the units, such as the
// transparency on necro summons and the name-color change on unique boss // transparency on necro summons and the name-color change on unique boss
// units (thanks to Kingpin for the info) // units (thanks to Kingpin for the info)
Id string // called `hcIdx` in monstats.txt Id string // called `hcIdx` in monstats.txt //nolint:golint Id is the right key
// BaseKey is an ID pointer of the “base” unit for this specific // BaseKey is an ID pointer of the “base” unit for this specific
// monster type (ex. There are five types of “Fallen”; all of them have // monster type (ex. There are five types of “Fallen”; all of them have
@ -82,9 +81,11 @@ type (
SpawnAnimationKey string // called `spawnmode` in monstats.txt SpawnAnimationKey string // called `spawnmode` in monstats.txt
// MinionId1 is an Id of a minion that spawns when this monster is created // MinionId1 is an Id of a minion that spawns when this monster is created
//nolint:golint Id is the right key
MinionId1 string // called `minion1` in monstats.txt MinionId1 string // called `minion1` in monstats.txt
// MinionId2 is an Id of a minion that spawns when this monster is created // MinionId2 is an Id of a minion that spawns when this monster is created
//nolint:golint Id is the right key
MinionId2 string // called `minion2` in monstats.txt MinionId2 string // called `minion2` in monstats.txt
// SoundKeyNormal, SoundKeySpecial // SoundKeyNormal, SoundKeySpecial
@ -114,14 +115,14 @@ type (
// the ID Pointer to the skill (from Skills.txt) the monster will cast when // the ID Pointer to the skill (from Skills.txt) the monster will cast when
// this specific slot is accessed by the AI. Which slots are used is // this specific slot is accessed by the AI. Which slots are used is
// determined by the units AI. // determined by the units AI.
SkillId1 string // called `Skill1` in monstats.txt SkillId1 string // called `Skill1` in monstats.txt //nolint:golint Id is the right key
SkillId2 string // called `Skill2` in monstats.txt SkillId2 string // called `Skill2` in monstats.txt //nolint:golint Id is the right key
SkillId3 string // called `Skill3` in monstats.txt SkillId3 string // called `Skill3` in monstats.txt //nolint:golint Id is the right key
SkillId4 string // called `Skill4` in monstats.txt SkillId4 string // called `Skill4` in monstats.txt //nolint:golint Id is the right key
SkillId5 string // called `Skill5` in monstats.txt SkillId5 string // called `Skill5` in monstats.txt //nolint:golint Id is the right key
SkillId6 string // called `Skill6` in monstats.txt SkillId6 string // called `Skill6` in monstats.txt //nolint:golint Id is the right key
SkillId7 string // called `Skill7` in monstats.txt SkillId7 string // called `Skill7` in monstats.txt //nolint:golint Id is the right key
SkillId8 string // called `Skill8` in monstats.txt SkillId8 string // called `Skill8` in monstats.txt //nolint:golint Id is the right key
// SkillAnimation1 -- SkillAnimation8 // SkillAnimation1 -- SkillAnimation8
// the graphical MODE (or SEQUENCE) this unit uses when it uses this skill. // the graphical MODE (or SEQUENCE) this unit uses when it uses this skill.
@ -138,6 +139,7 @@ type (
// ID Pointer to the skill that controls this units damage. This is used for // ID Pointer to the skill that controls this units damage. This is used for
// the druids summons. IE their damage is specified solely by Skills.txt and // the druids summons. IE their damage is specified solely by Skills.txt and
// not by MonStats.txt. // not by MonStats.txt.
//nolint:golint Id is the right key
DamageSkillId string // called `SkillDamage` in monstats.txt DamageSkillId string // called `SkillDamage` in monstats.txt
// ElementAttackMode1 -- ElementAttackMode3 // ElementAttackMode1 -- ElementAttackMode3

View File

@ -7,6 +7,7 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
) )
// MonStats2Record is a representation of a row from monstats2.txt
type MonStats2Record struct { type MonStats2Record struct {
// Key, the object ID MonStatEx feild from MonStat // Key, the object ID MonStatEx feild from MonStat
Key string Key string
@ -225,9 +226,11 @@ type MonStats2Record struct {
ResurrectSkill string ResurrectSkill string
} }
// MonStats2 stores all of the MonStats2Records
//nolint:gochecknoglobals // Current design issue //nolint:gochecknoglobals // Current design issue
var MonStats2 map[string]*MonStats2Record var MonStats2 map[string]*MonStats2Record
// LoadMonStats2 loads MonStats2Records from monstats2.txt
//nolint:funlen //just a big data loader //nolint:funlen //just a big data loader
func LoadMonStats2(file []byte) { func LoadMonStats2(file []byte) {
dict := d2common.LoadDataDictionary(string(file)) dict := d2common.LoadDataDictionary(string(file))

File diff suppressed because it is too large Load Diff

View File

@ -2,23 +2,19 @@ package d2datadict
import ( import (
"log" "log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
) )
type ObjectType int // ObjectLookupRecord is a representation of a row from objects.txt
const (
ObjectTypeCharacter ObjectType = 1
ObjectTypeItem ObjectType = 2
)
type ObjectLookupRecord struct { type ObjectLookupRecord struct {
Act int Act int
Type ObjectType Type d2enum.ObjectType
Id int Id int //nolint:golint Id is the right key
Name string Name string
Description string Description string
ObjectsTxtId int ObjectsTxtId int //nolint:golint Id is the right key
MonstatsTxtId int MonstatsTxtId int //nolint:golint Id is the right key
Direction int Direction int
Base string Base string
Token string Token string
@ -44,6 +40,7 @@ type ObjectLookupRecord struct {
Index int Index int
} }
// LookupObject looks up an object record
func LookupObject(act, typ, id int) *ObjectLookupRecord { func LookupObject(act, typ, id int) *ObjectLookupRecord {
object := lookupObject(act, typ, id, indexedObjects) object := lookupObject(act, typ, id, indexedObjects)
if object == nil { if object == nil {
@ -54,14 +51,23 @@ func LookupObject(act, typ, id int) *ObjectLookupRecord {
} }
func lookupObject(act, typ, id int, objects [][][]*ObjectLookupRecord) *ObjectLookupRecord { func lookupObject(act, typ, id int, objects [][][]*ObjectLookupRecord) *ObjectLookupRecord {
if objects[act] != nil && objects[act][typ] != nil && objects[act][typ][id] != nil { if len(objects) < act {
return objects[act][typ][id] return nil
} }
return nil if len(objects[act]) < typ {
return nil
}
if len(objects[act][typ]) < id {
return nil
}
return objects[act][typ][id]
} }
func init() { // InitObjectRecords loads ObjectRecords
func InitObjectRecords() {
indexedObjects = indexObjects(objectLookups) indexedObjects = indexObjects(objectLookups)
} }
@ -88,7 +94,8 @@ func indexObjects(objects []ObjectLookupRecord) [][][]*ObjectLookupRecord {
return indexedObjects return indexedObjects
} }
// Indexed slice of object records for quick lookups. // IndexedObjects is a slice of object records for quick lookups.
// nil checks should be done for uninitialized values at each level. // nil checks should be done for uninitialized values at each level.
// [Act 1-5][Type 1-2][Id 0-855] // [Act 1-5][Type 1-2][Id 0-855]
//nolint:gochecknoglobals // Currently global by design
var indexedObjects [][][]*ObjectLookupRecord var indexedObjects [][][]*ObjectLookupRecord

View File

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

View File

@ -7,15 +7,17 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// ObjectTypeRecord is a representation of a row from objtype.txt
type ObjectTypeRecord struct { type ObjectTypeRecord struct {
Name string Name string
Token string Token string
} }
//nolint:gochecknoglobals // Currently global by design, only written once
// ObjectTypes contains the name and token for objects // ObjectTypes contains the name and token for objects
//nolint:gochecknoglobals // Currently global by design, only written once
var ObjectTypes []ObjectTypeRecord var ObjectTypes []ObjectTypeRecord
// LoadObjectTypes loads ObjectTypeRecords from objtype.txt
func LoadObjectTypes(objectTypeData []byte) { func LoadObjectTypes(objectTypeData []byte) {
streamReader := d2common.CreateStreamReader(objectTypeData) streamReader := d2common.CreateStreamReader(objectTypeData)
count := streamReader.GetInt32() count := streamReader.GetInt32()

View File

@ -9,14 +9,21 @@ import (
// An ObjectRecord represents the settings for one type of object from objects.txt // An ObjectRecord represents the settings for one type of object from objects.txt
type ObjectRecord struct { type ObjectRecord struct {
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 Name string
Description string Description string
Id int
Token string // refers to what graphics this object uses Token string // refers to what graphics this object uses
SpawnMax int // unused? Id int //nolint:golint it's ok that it's called Id
Selectable [8]bool // is this mode selectable SpawnMax int // unused?
TrapProbability int // unused TrapProbability int // unused
SizeX int SizeX int
SizeY int SizeY int
@ -26,42 +33,11 @@ type ObjectRecord struct {
NTgtBX int // unknown NTgtBX int // unknown
NTgtBY int // unknown NTgtBY int // unknown
FrameCount [8]int // how many frames does this mode have, 0 = skip Orientation int // unknown (1=sw, 2=nw, 3=se, 4=ne)
FrameDelta [8]int // what rate is the animation played at (256 = 100% speed) Trans int // controls palette mapping
CycleAnimation [8]bool // probably whether animation loops
LightDiameter [8]int
BlocksLight [8]bool
HasCollision [8]bool
IsAttackable bool // do we kick it when interacting
StartFrame [8]int
EnvEffect bool // unknown
IsDoor bool
BlockVisibility bool // only works with IsDoor
Orientation int // unknown (1=sw, 2=nw, 3=se, 4=ne)
Trans int // controls palette mapping
OrderFlag [8]int // 0 = object, 1 = floor, 2 = wall
PreOperate bool // unknown
HasAnimationMode [8]bool // 'Mode' in source, true if this mode is used
XOffset int // in pixels offset XOffset int // in pixels offset
YOffset int YOffset int
Draw bool // if false, object isn't drawn (shadow is still drawn and player can still select though)
LightRed byte // if lightdiameter is set, rgb of the light
LightGreen byte
LightBlue byte
SelHD bool // whether these DCC components are selectable
SelTR bool
SelLG bool
SelRA bool
SelLA bool
SelRH bool
SelLH bool
SelSH bool
SelS [8]bool
TotalPieces int // selectable DCC components count TotalPieces int // selectable DCC components count
SubClass int // subclass of object: SubClass int // subclass of object:
@ -79,21 +55,12 @@ type ObjectRecord struct {
NameOffset int // pixels to offset the name from the animation pivot NameOffset int // pixels to offset the name from the animation pivot
MonsterOk bool // unknown OperateRange int // distance object can be used from, might be unused
OperateRange int // distance object can be used from, might be unused ShrineFunction int // unused
ShrineFunction int // unused
Restore bool // if true, object is stored in memory and will be retained if you leave and re-enter the area
Parm [8]int // unknown Act int // what acts this object can appear in (15 = all three)
Act int // what acts this object can appear in (15 = all three)
Lockable bool Damage int // amount of damage done by this (used depending on operatefn)
Gore bool // unknown, something with corpses
Sync bool // unknown
Flicker bool // light flickers if true
Damage int // amount of damage done by this (used depending on operatefn)
Beta bool // if true, appeared in the beta?
Overlay bool // unknown
CollisionSubst bool // unknown, controls some kind of special collision checking?
Left int // unknown, clickable bounding box? Left int // unknown, clickable bounding box?
Top int Top int
@ -110,13 +77,47 @@ type ObjectRecord struct {
ClientFn int // controls special audio-visual functions ClientFn int // controls special audio-visual functions
// (see above todo) // (see above todo)
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
// 'To ...' or 'trap door' when highlighting, not sure which is T/F // 'To ...' or 'trap door' when highlighting, not sure which is T/F
AutoMap int // controls how this object appears on the map AutoMap int // controls how this object appears on the map
// 0 = it doesn't, rest of modes need to be analyzed // 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 //nolint:funlen // Makes no sense to split
@ -335,9 +336,11 @@ func createObjectRecord(props []string) ObjectRecord {
return result return result
} }
// Objects stores all of the ObjectRecords
//nolint:gochecknoglobals // Currently global by design, only written once //nolint:gochecknoglobals // Currently global by design, only written once
var Objects map[int]*ObjectRecord var Objects map[int]*ObjectRecord
// LoadObjects loads all objects from objects.txt
func LoadObjects(file []byte) { func LoadObjects(file []byte) {
Objects = make(map[int]*ObjectRecord) Objects = make(map[int]*ObjectRecord)
data := strings.Split(string(file), "\r\n")[1:] data := strings.Split(string(file), "\r\n")[1:]

View File

@ -75,9 +75,11 @@ func createSoundEntry(soundLine string) SoundEntry {
return result return result
} }
// Sounds stores all of the SoundEntries
//nolint:gochecknoglobals // Currently global by design, only written once //nolint:gochecknoglobals // Currently global by design, only written once
var Sounds map[string]SoundEntry var Sounds map[string]SoundEntry
// LoadSounds loads SoundEntries from sounds.txt
func LoadSounds(file []byte) { func LoadSounds(file []byte) {
Sounds = make(map[string]SoundEntry) Sounds = make(map[string]SoundEntry)
soundData := strings.Split(string(file), "\r\n")[1:] soundData := strings.Split(string(file), "\r\n")[1:]

View File

@ -1,13 +1,14 @@
package d2datadict package d2datadict
import ( import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"log" "log"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// https://d2mods.info/forum/kb/viewarticle?a=162 // https://d2mods.info/forum/kb/viewarticle?a=162
// Defines the SuperUnique monsters and their properties. // SuperUniqueRecord Defines the unique monsters and their properties.
// SuperUnique monsters are boss monsters which always appear at the same places // SuperUnique monsters are boss monsters which always appear at the same places
// and always have the same base special abilities // 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). // with the addition of one or two extra ones per difficulty (Nightmare provides one extra ability, Hell provides two).
@ -118,8 +119,11 @@ type SuperUniqueRecord struct {
UTransHell string UTransHell string
} }
// SuperUniques stores all of the SuperUniqueRecords
//nolint:gochecknoglobals // Currently global by design
var SuperUniques map[string]*SuperUniqueRecord var SuperUniques map[string]*SuperUniqueRecord
// LoadSuperUniques loads SuperUniqueRecords from superuniques.txt
func LoadSuperUniques(file []byte) { func LoadSuperUniques(file []byte) {
dictionary := d2common.LoadDataDictionary(string(file)) dictionary := d2common.LoadDataDictionary(string(file))
SuperUniques = make(map[string]*SuperUniqueRecord, len(dictionary.Data)) SuperUniques = make(map[string]*SuperUniqueRecord, len(dictionary.Data))

View File

@ -7,6 +7,7 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
) )
// UniqueItemRecord is a representation of a row from uniqueitems.txt
type UniqueItemRecord struct { type UniqueItemRecord struct {
Name string Name string
Version int // 0 = classic pre 1.07, 1 = 1.07-1.11, 100 = expansion Version int // 0 = classic pre 1.07, 1 = 1.07-1.11, 100 = expansion
@ -41,6 +42,7 @@ type UniqueItemRecord struct {
Properties [12]UniqueItemProperty Properties [12]UniqueItemProperty
} }
// UniqueItemProperty is describes a property of a unique item
type UniqueItemProperty struct { type UniqueItemProperty struct {
Property string Property string
Parameter d2common.CalcString // depending on the property, this may be an int (usually), or a string Parameter d2common.CalcString // depending on the property, this may be an int (usually), or a string
@ -114,8 +116,11 @@ func createUniqueItemProperty(r *[]string, inc func() int) UniqueItemProperty {
return result return result
} }
// UniqueItems stores all of the UniqueItemRecords
//nolint:gochecknoglobals // Currently global by design
var UniqueItems map[string]*UniqueItemRecord var UniqueItems map[string]*UniqueItemRecord
// LoadUniqueItems loadsUniqueItemRecords fro uniqueitems.txt
func LoadUniqueItems(file []byte) { func LoadUniqueItems(file []byte) {
UniqueItems = make(map[string]*UniqueItemRecord) UniqueItems = make(map[string]*UniqueItemRecord)
data := strings.Split(string(file), "\r\n")[1:] data := strings.Split(string(file), "\r\n")[1:]

View File

@ -9,6 +9,6 @@ import (
var Weapons map[string]*ItemCommonRecord var Weapons map[string]*ItemCommonRecord
func LoadWeapons(file []byte) { func LoadWeapons(file []byte) {
Weapons = *LoadCommonItems(file, d2enum.InventoryItemTypeWeapon) Weapons = LoadCommonItems(file, d2enum.InventoryItemTypeWeapon)
log.Printf("Loaded %d weapons", len(Weapons)) log.Printf("Loaded %d weapons", len(Weapons))
} }

View File

@ -5,9 +5,10 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
) )
// Object is a game world object
type Object struct { type Object struct {
Type int Type int
Id int Id int //nolint:golint Id is the right key
X int X int
Y int Y int
Flags int Flags int

View File

@ -0,0 +1,7 @@
package d2enum
type EncodingType int
const (
EncodeDefault EncodingType = iota
)

View File

@ -0,0 +1,9 @@
package d2enum
// ObjectType is the type of an object
type ObjectType int
const (
ObjectTypeCharacter ObjectType = iota + 1
ObjectTypeItem
)

View File

@ -0,0 +1,76 @@
package d2enum
// OperatorType is used for calculating dynamic property values
type OperatorType int // for dynamic properties
const (
// OpDefault just adds the stat to the unit directly
OpDefault = OperatorType(iota)
// Op1 adds opstat.base * statvalue / 100 to the opstat.
Op1
// Op2 adds (statvalue * basevalue) / (2 ^ param) to the opstat
// this does not work properly with any stat other then level because of the
// way this is updated, it is only refreshed when you re-equip the item,
// your character is saved or you level up, similar to passive skills, just
// because it looks like it works in the item description
// does not mean it does, the game just recalculates the information in the
// description every frame, while the values remain unchanged serverside.
Op2
// Op3 is a percentage based version of op #2
// look at op #2 for information about the formula behind it, just
// remember the stat is increased by a percentage rather then by adding
// an integer.
Op3
// Op4 works the same way op #2 works, however the stat bonus is
// added to the item and not to the player (so that +defense per level
// properly adds the defense to the armor and not to the character
// directly!)
Op4
// Op5 works like op #4 but is percentage based, it is used for percentage
// based increase of stats that are found on the item itself, and not stats
// that are found on the character.
Op5
// Op6 works like for op #7, however this adds a plain bonus to the stat, and just
// like #7 it also doesn't work so I won't bother to explain the arithmetic
// behind it either.
Op6
// Op7 is used to increase a stat based on the current daytime of the game
// world by a percentage, there is no need to explain the arithmetics
// behind it because frankly enough it just doesn't work serverside, it
// only updates clientside so this op is essentially useless.
Op7
// Op8 hardcoded to work only with maxmana, this will apply the proper amount
// of mana to your character based on CharStats.txt for the amount of energy
// the stat added (doesn't work for non characters)
Op8
// Op9 hardcoded to work only with maxhp and maxstamina, this will apply the
// proper amount of maxhp and maxstamina to your character based on
// CharStats.txt for the amount of vitality the stat added (doesn't work
// for non characters)
Op9
// Op10 doesn't do anything, this has no switch case in the op function.
Op10
// Op11 adds opstat.base * statvalue / 100 similar to 1 and 13, the code just
// does a few more checks
Op11
// Op12 doesn't do anything, this has no switch case in the op function.
Op12
// Op13 adds opstat.base * statvalue / 100 to the value of opstat, this is
// useable only on items it will not apply the bonus to other unit types
// (this is why it is used for +% durability, +% level requirement,
// +% damage, +% defense ).
Op13
)

View File

@ -118,13 +118,13 @@ func (mr *Stamp) Entities(tileOffsetX, tileOffsetY int) []d2mapentity.MapEntity
for _, object := range mr.ds1.Objects { for _, object := range mr.ds1.Objects {
switch object.Lookup.Type { switch object.Lookup.Type {
case d2datadict.ObjectTypeCharacter: case d2enum.ObjectTypeCharacter:
if object.Lookup.Base != "" && object.Lookup.Token != "" && object.Lookup.TR != "" { if object.Lookup.Base != "" && object.Lookup.Token != "" && object.Lookup.TR != "" {
npc := d2mapentity.CreateNPC((tileOffsetX*5)+object.X, (tileOffsetY*5)+object.Y, object.Lookup, 0) npc := d2mapentity.CreateNPC((tileOffsetX*5)+object.X, (tileOffsetY*5)+object.Y, object.Lookup, 0)
npc.SetPaths(convertPaths(tileOffsetX, tileOffsetY, object.Paths)) npc.SetPaths(convertPaths(tileOffsetX, tileOffsetY, object.Paths))
entities = append(entities, npc) entities = append(entities, npc)
} }
case d2datadict.ObjectTypeItem: case d2enum.ObjectTypeItem:
if object.ObjectInfo != nil && object.ObjectInfo.Draw && object.Lookup.Base != "" && object.Lookup.Token != "" { if object.ObjectInfo != nil && object.ObjectInfo.Draw && object.Lookup.Base != "" && object.Lookup.Token != "" {
entity, err := d2mapentity.CreateObject((tileOffsetX*5)+object.X, (tileOffsetY*5)+object.Y, object.Lookup, d2resource.PaletteUnits) entity, err := d2mapentity.CreateObject((tileOffsetX*5)+object.X, (tileOffsetY*5)+object.Y, object.Lookup, d2resource.PaletteUnits)
if err != nil { if err != nil {

View File

@ -449,6 +449,8 @@ func loadDataDict() error {
{d2resource.SuperUniques, d2datadict.LoadSuperUniques}, {d2resource.SuperUniques, d2datadict.LoadSuperUniques},
} }
d2datadict.InitObjectRecords()
for _, entry := range entries { for _, entry := range entries {
data, err := d2asset.LoadFile(entry.path) data, err := d2asset.LoadFile(entry.path)
if err != nil { if err != nil {