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
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
func LoadAnimationData(rawData []byte) {
AnimationData = make(map[string][]*AnimationDataRecord)
streamReader := d2common.CreateStreamReader(rawData)
for !streamReader.Eof() {
dataCount := int(streamReader.GetInt32())
for i := 0; i < dataCount; i++ {
@ -37,11 +38,14 @@ func LoadAnimationData(rawData []byte) {
}
data.Flags = streamReader.ReadBytes(144)
cofIndex := strings.ToLower(data.COFName)
if _, found := AnimationData[cofIndex]; !found {
AnimationData[cofIndex] = make([]*AnimationDataRecord, 0)
}
AnimationData[cofIndex] = append(AnimationData[cofIndex], data)
}
}
log.Printf("Loaded %d animation data records", len(AnimationData))
}

View File

@ -1,5 +1,7 @@
//
// MpqHuffman.go based on the origina CS file
// Package d2compression is used for decompressing WAV files.
package d2compression
// MpqHuffman.go based on the original CS file
//
// Authors:
// Foole (fooleau@gmail.com)
@ -27,7 +29,6 @@
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
package d2compression
import (
"log"
@ -37,57 +38,60 @@ import (
// linkedNode is a node which is both hierachcical (parent/child) and doubly linked (next/prev)
type linkedNode struct {
DecompressedValue int
Weight int
Parent *linkedNode
Child0 *linkedNode
Prev *linkedNode
Next *linkedNode
decompressedValue int
weight int
parent *linkedNode
child0 *linkedNode
prev *linkedNode
next *linkedNode
}
func CreateLinkedNode(decompVal, weight int) *linkedNode {
// createLinkedNode creates a linked node
func createLinkedNode(decompVal, weight int) *linkedNode {
result := &linkedNode{
DecompressedValue: decompVal,
Weight: weight,
decompressedValue: decompVal,
weight: weight,
}
return result
}
func (v *linkedNode) GetChild1() *linkedNode {
return v.Child0.Prev
func (v *linkedNode) getChild1() *linkedNode {
return v.child0.prev
}
func (v *linkedNode) Insert(other *linkedNode) *linkedNode {
// 'Next' should have a lower weight we should return the lower weight
if other.Weight <= v.Weight {
func (v *linkedNode) insert(other *linkedNode) *linkedNode {
// 'next' should have a lower weight we should return the lower weight
if other.weight <= v.weight {
// insert before
if v.Next != nil {
v.Next.Prev = other
other.Next = v.Next
if v.next != nil {
v.next.prev = other
other.next = v.next
}
v.Next = other
v.next = other
other.Prev = v
other.prev = v
return other
}
if v.Prev == nil {
// Insert after
other.Prev = nil
v.Prev = other
other.Next = v
if v.prev == nil {
// insert after
other.prev = nil
v.prev = other
other.next = v
} else {
v.Prev.Insert(other)
v.prev.insert(other)
}
return v
}
//nolint:funlen // Makes no sense to split
func getPrimes() [][]byte {
return [][]byte{
{
{ //nolint:dupl we're not interested in duplicates here
// Compression type 0
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,
@ -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, 0x02,
},
{
{ //nolint:dupl we're not interested in duplicates here
// Compression type 1
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,
@ -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, 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, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
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,
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,
}, {
// Compression type 3
}, { //nolint:dupl we're not interested in duplicates here
// Compression type 3 //nolint:dupl
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,
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, 0x07, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01,
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,
}, { // Compression type 5
}, { // Compression type 5 //nolint:dupl
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,
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,
}, { // 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,
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,
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,
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 {
node := head
for node.Child0 != nil {
for node.child0 != nil {
bit := input.ReadBits(1)
if bit == -1 {
log.Fatal("unexpected end of file")
}
if bit == 0 {
node = node.Child0
node = node.child0
continue
}
node = node.GetChild1()
node = node.getChild1()
}
return node
}
// TODO: these consts for buildList need better names
const (
decompVal1 = 256
decompVal2 = 257
)
func buildList(primeData []byte) *linkedNode {
root := CreateLinkedNode(256, 1)
root = root.Insert(CreateLinkedNode(257, 1))
root := createLinkedNode(decompVal1, 1)
root = root.insert(createLinkedNode(decompVal2, 1))
for i := 0; i < len(primeData); i++ {
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 {
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.Parent = parent
temp := createLinkedNode(parent.decompressedValue, parent.weight)
temp.parent = parent
newnode := CreateLinkedNode(decomp, 0)
newnode.Parent = parent
newnode := createLinkedNode(decomp, 0)
newnode.parent = parent
parent.Child0 = newnode
parent.child0 = newnode
tail.Next = temp
temp.Prev = tail
newnode.Prev = temp
temp.Next = newnode
tail.next = temp
temp.prev = tail
newnode.prev = temp
temp.next = newnode
adjustTree(newnode)
@ -257,7 +269,7 @@ func adjustTree(newNode *linkedNode) {
current := newNode
for current != nil {
current.Weight++
current.weight++
var insertpoint *linkedNode
@ -267,12 +279,12 @@ func adjustTree(newNode *linkedNode) {
insertpoint = current
for {
prev = insertpoint.Prev
prev = insertpoint.prev
if prev == nil {
break
}
if prev.Weight >= current.Weight {
if prev.weight >= current.weight {
break
}
@ -281,60 +293,60 @@ func adjustTree(newNode *linkedNode) {
// No insertion point found
if insertpoint == current {
current = current.Parent
current = current.parent
continue
}
// The following code basically swaps insertpoint with current
// remove insert point
if insertpoint.Prev != nil {
insertpoint.Prev.Next = insertpoint.Next
if insertpoint.prev != nil {
insertpoint.prev.next = insertpoint.next
}
insertpoint.Next.Prev = insertpoint.Prev
insertpoint.next.prev = insertpoint.prev
// Insert insertpoint after current
insertpoint.Next = current.Next
insertpoint.Prev = current
// insert insertpoint after current
insertpoint.next = current.next
insertpoint.prev = current
if current.Next != nil {
current.Next.Prev = insertpoint
if current.next != nil {
current.next.prev = insertpoint
}
current.Next = insertpoint
current.next = insertpoint
// remove current
current.Prev.Next = current.Next
current.Next.Prev = current.Prev
current.prev.next = current.next
current.next.prev = current.prev
// insert current after prev
if prev == nil {
log.Fatal("previous frame not defined!")
}
temp := prev.Next
current.Next = temp
current.Prev = prev
temp.Prev = current
prev.Next = current
temp := prev.next
current.next = temp
current.prev = prev
temp.prev = current
prev.next = current
// Set up parent/child links
currentparent := current.Parent
insertparent := insertpoint.Parent
currentparent := current.parent
insertparent := insertpoint.parent
if currentparent.Child0 == current {
currentparent.Child0 = insertpoint
if currentparent.child0 == current {
currentparent.child0 = insertpoint
}
if currentparent != insertparent && insertparent.Child0 == insertpoint {
insertparent.Child0 = current
if currentparent != insertparent && insertparent.child0 == insertpoint {
insertparent.child0 = current
}
current.Parent = insertparent
insertpoint.Parent = currentparent
current.parent = insertparent
insertpoint.parent = currentparent
current = current.Parent
current = current.parent
}
}
@ -343,24 +355,25 @@ func buildTree(tail *linkedNode) *linkedNode {
for current != nil {
child0 := current
child1 := current.Prev
child1 := current.prev
if child1 == nil {
break
}
parent := CreateLinkedNode(0, child0.Weight+child1.Weight)
parent.Child0 = child0
child0.Parent = parent
child1.Parent = parent
parent := createLinkedNode(0, child0.weight+child1.weight)
parent.child0 = child0
child0.parent = parent
child1.parent = parent
current.Insert(parent)
current = current.Prev.Prev
current.insert(parent)
current = current.prev.prev
}
return current
}
// HuffmanDecompress decompresses huffman-compressed data
func HuffmanDecompress(data []byte) []byte {
comptype := data[0]
primes := getPrimes()
@ -380,7 +393,7 @@ func HuffmanDecompress(data []byte) []byte {
Loop:
for {
node := decode(bitstream, head)
decoded = node.DecompressedValue
decoded = node.decompressedValue
switch decoded {
case 256:
break Loop

View File

@ -4,34 +4,36 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
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,
}
func WavDecompress(data []byte, channelCount int) []byte {
// WavDecompress decompresses wav files
func WavDecompress(data []byte, channelCount int) []byte { //nolint:funlen doesn't make sense to split
Array1 := []int{0x2c, 0x2c}
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)
output := d2common.CreateStreamWriter()
input.GetByte()
shift := input.GetByte()
@ -43,6 +45,7 @@ func WavDecompress(data []byte, channelCount int) []byte {
}
channel := channelCount - 1
for input.GetPosition() < input.GetSize() {
value := input.GetByte()
@ -56,12 +59,14 @@ func WavDecompress(data []byte, channelCount int) []byte {
if Array1[channel] != 0 {
Array1[channel]--
}
output.PushInt16(int16(Array2[channel]))
case 1:
Array1[channel] += 8
if Array1[channel] > 0x58 {
Array1[channel] = 0x58
}
if channelCount == 2 {
channel = 1 - channel
}
@ -71,6 +76,7 @@ func WavDecompress(data []byte, channelCount int) []byte {
if Array1[channel] < 0 {
Array1[channel] = 0
}
if channelCount == 2 {
channel = 1 - channel
}
@ -116,12 +122,11 @@ func WavDecompress(data []byte, channelCount int) []byte {
if Array1[channel] < 0 {
Array1[channel] = 0
} else {
if Array1[channel] > 0x58 {
Array1[channel] = 0x58
}
} else if Array1[channel] > 0x58 {
Array1[channel] = 0x58
}
}
}
return output.GetBytes()
}

View File

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

View File

@ -7,6 +7,11 @@ import (
"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"}
// AutoMapRecord represents one row from d2data.mpq/AutoMap.txt.
@ -27,7 +32,6 @@ type AutoMapRecord struct {
// StartSequence and EndSequence are sub indices the
// same 2D array as Style. They describe a range of
// tiles for which covered by this AutoMapRecord.
//
// In some rows you can find a value of -1. This means
// the game will only look at Style and TileName to
// determine which tiles are addressed.
@ -50,7 +54,6 @@ type AutoMapRecord struct {
// re-extract the chart with Dc6Table, you can specify
// how many graphics a line can hold), line 1 includes
// icons 0-19, line 2 from 20 to 39 etc.
//
// Multiple values exist for Cel (and Type) to enable
// variation. Presumably game chooses randomly between
// 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))
for idx := range d.Data {
if d.GetString("LevelName", idx) == "Expansion" {
if d.GetString("LevelName", idx) == expansion {
continue
}

View File

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

View File

@ -140,22 +140,26 @@ type CubeRecipeItemProperty struct {
}
// CubeRecipes contains all rows in CubeMain.txt.
//nolint:gochecknoglobals // Currently global by design, only written once
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
// the data from CubeMain.txt.
func LoadCubeRecipes(file []byte) {
// Load data
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
CubeRecipes = make([]*CubeRecipeRecord, len(d.Data))
for idx := range d.Data {

View File

@ -58,7 +58,7 @@ type DifficultyLevelRecord struct {
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)
// so I am going to leave these comments

View File

@ -31,49 +31,37 @@ import (
10
*/
// ExperienceBreakpointsRecord describes the experience points required to
// gain a level for all character classes
type ExperienceBreakpointsRecord struct {
Level int
HeroBreakpoints map[d2enum.Hero]int
Ratio int
}
var experienceStringMap map[string]d2enum.Hero
var experienceHeroMap map[d2enum.Hero]string
// ExperienceBreakpoints describes the required experience
// for each level for each character class
//nolint:gochecknoglobals // Currently global by design, only written once
var ExperienceBreakpoints []*ExperienceBreakpointsRecord
//nolint:gochecknoglobals // Currently global by design
var maxLevels map[d2enum.Hero]int
// GetMaxLevelByHero returns the highest level attainable for a hero type
func GetMaxLevelByHero(heroType d2enum.Hero) int {
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 {
return ExperienceBreakpoints[level].HeroBreakpoints[heroType]
}
// LoadExperienceBreakpoints loads experience.txt into a map
// ExperienceBreakpoints []*ExperienceBreakpointsRecord
func LoadExperienceBreakpoints(file []byte) {
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
ExperienceBreakpoints = make([]*ExperienceBreakpointsRecord, len(d.Data)-1)

View File

@ -6,6 +6,8 @@ import (
"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 {
Name string
Letter string
@ -50,13 +52,17 @@ type GemsRecord struct {
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) {
d := d2common.LoadDataDictionary(string(file))
var Gems []*GemsRecord
Gems = make(map[string]*GemsRecord, len(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
separate base-game gems and expansion runes.
@ -104,7 +110,7 @@ func LoadGems(file []byte) {
ShieldMod3Min: d.GetNumber("shieldMod3Min", 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"
)
// HirelingRecord is a representation of rows in hireling.txt
// these records describe mercenaries
type HirelingRecord struct {
Hireling string
SubType string
@ -81,9 +83,15 @@ type HirelingRecord struct {
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) {
d := d2common.LoadDataDictionary(string(file))
var Hirelings []*HirelingRecord
Hirelings = make([]*HirelingRecord, len(d.Data))
for idx := range d.Data {
hireling := &HirelingRecord{

View File

@ -8,13 +8,9 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
var MagicPrefixDictionary *d2common.DataDictionary
var MagicSuffixDictionary *d2common.DataDictionary
var MagicPrefixRecords []*ItemAffixCommonRecord
var MagicSuffixRecords []*ItemAffixCommonRecord
var AffixMagicGroups []*ItemAffixCommonGroup
// MagicPrefix + MagicSuffix store item affix records
var MagicPrefix []*ItemAffixCommonRecord //nolint:gochecknoglobals // Currently global by design
var MagicSuffix []*ItemAffixCommonRecord //nolint:gochecknoglobals // Currently global by design
// LoadMagicPrefix loads MagicPrefix.txt
func LoadMagicPrefix(file []byte) {
@ -22,7 +18,7 @@ func LoadMagicPrefix(file []byte) {
subType := d2enum.ItemAffixMagic
MagicPrefixDictionary, MagicPrefixRecords = loadDictionary(file, superType, subType)
MagicPrefix = loadDictionary(file, superType, subType)
}
// LoadMagicSuffix loads MagicSuffix.txt
@ -31,11 +27,11 @@ func LoadMagicSuffix(file []byte) {
subType := d2enum.ItemAffixMagic
MagicSuffixDictionary, MagicSuffixRecords = loadDictionary(file, superType, subType)
MagicSuffix = loadDictionary(file, superType, subType)
}
func getAffixString(t1 d2enum.ItemAffixSuperType, t2 d2enum.ItemAffixSubType) string {
var name string = ""
var name = ""
if t2 == d2enum.ItemAffixMagic {
name = "Magic"
@ -55,58 +51,15 @@ func loadDictionary(
file []byte,
superType d2enum.ItemAffixSuperType,
subType d2enum.ItemAffixSubType,
) (*d2common.DataDictionary, []*ItemAffixCommonRecord) {
) []*ItemAffixCommonRecord {
dict := d2common.LoadDataDictionary(string(file))
records := createItemAffixRecords(dict, superType, subType)
name := getAffixString(superType, subType)
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(
d *d2common.DataDictionary,
superType d2enum.ItemAffixSuperType,
@ -177,7 +130,7 @@ func createItemAffixRecords(
}
group := ItemAffixGroups[affix.GroupID]
group.AddMember(affix)
group.addMember(affix)
records = append(records, affix)
}
@ -185,14 +138,16 @@ func createItemAffixRecords(
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 {
ID int
Members map[string]*ItemAffixCommonRecord
}
func (g *ItemAffixCommonGroup) AddMember(a *ItemAffixCommonRecord) {
func (g *ItemAffixCommonGroup) addMember(a *ItemAffixCommonRecord) {
if g.Members == nil {
g.Members = make(map[string]*ItemAffixCommonRecord)
}
@ -200,7 +155,7 @@ func (g *ItemAffixCommonGroup) AddMember(a *ItemAffixCommonRecord) {
g.Members[a.Name] = a
}
func (g *ItemAffixCommonGroup) GetTotalFrequency() int {
func (g *ItemAffixCommonGroup) getTotalFrequency() int {
total := 0
for _, affix := range g.Members {
@ -210,6 +165,9 @@ func (g *ItemAffixCommonGroup) GetTotalFrequency() int {
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 {
Code string
Parameter int
@ -217,46 +175,49 @@ type ItemAffixCommonModifier struct {
Max int
}
// ItemAffixCommonRecord is a common definition that both prefix and suffix use
type ItemAffixCommonRecord struct {
Name string
Group *ItemAffixCommonGroup
Modifiers []*ItemAffixCommonModifier
ItemInclude []string
ItemExclude []string
Name string
Class string
TransformColor string
Version int
Type d2enum.ItemAffixSubType
Level int
MaxLevel int
LevelReq int
ClassLevelReq int
Frequency int
GroupID int
PriceAdd int
PriceScale int
IsPrefix bool
IsSuffix bool
Spawnable bool
Rare 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
Transform bool
}
// ProbabilityToSpawn returns the chance of the affix spawning on an
// item with a given quality level
func (a *ItemAffixCommonRecord) ProbabilityToSpawn(qlvl int) float64 {
if (qlvl > a.MaxLevel) || (qlvl < a.Level) {
return 0.0
}
p := float64(a.Frequency) / float64(a.Group.GetTotalFrequency())
p := float64(a.Frequency) / float64(a.Group.getTotalFrequency())
return p
}

View File

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

View File

@ -7,35 +7,53 @@ import (
"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
type ItemStatCostRecord struct {
Name string
Index int
Name string
OpBase string
OpStat1 string
OpStat2 string
OpStat3 string
Signed bool // whether the stat is signed
KeepZero bool // prevent from going negative (assume only client side)
MaxStat string // if Direct true, will not exceed val of MaxStat
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
// Ranged bool // game attempts to keep stat in a range, like strength >-1
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
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
SavedBits int // #bits allocated to the value in .d2s file
SaveBits int // #bits saved to .d2s files, max == 2^SaveBits-1
SaveAdd int // how large the negative range is (lowers max, as well)
SaveParamBits int // #param bits are saved (safe value is 17)
Encode EncodingType // how the stat is encoded in .d2s files
CallbackEnabled bool // whether callback fn is called if value changes
Encode d2enum.EncodingType // how the stat is encoded in .d2s files
// these two fields control additional cost on items
// cost * (1 + value * multiply / 1024)) + add (...)
@ -48,20 +66,8 @@ type ItemStatCostRecord struct {
ValShift int // controls how stat is stored in .d2s
// so that you can save `+1` instead of `+256`
OperatorType OperatorType
OperatorType d2enum.OperatorType
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
EventID2 d2enum.ItemEventType
@ -70,175 +76,35 @@ type ItemStatCostRecord struct {
DescPriority int // determines order when displayed
DescFnID d2enum.DescFuncID
DescFn interface{} // the sprintf func
// Controls whenever and if so in what way the stat value is shown
// 0 = doesn't show the value of the stat
// 1 = shows the value of the stat infront of the description
// 2 = shows the value of the stat after the description.
DescVal int
DescStrPos string // string used when val is positive
DescStrNeg string
DescStr2 string // additional string used by some string funcs
DescVal int
// 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)
DescGroup int
DescGroupFuncID d2enum.DescFuncID
DescGroupFn interface{} // group sprintf func
DescGroupVal int
DescGroupStrPos string // string used when val is positive
DescGroupStrNeg string
DescGroupStr2 string // additional string used by some string funcs
DescGroupFuncID d2enum.DescFuncID
// 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 // ? TODO ?
CallbackEnabled bool // whether callback fn is called if value changes
Signed bool // whether the stat is signed
KeepZero bool // prevent from going negative (assume only client side)
UpdateAnimRate bool // when altered, forces speed handler to adjust speed
SendOther bool // whether to send to other clients
Saved bool // whether this stat is saved in .d2s files
SavedSigned bool // whether the stat is saved as signed/unsigned
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
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
*/
// ItemStatCosts stores all of the ItemStatCostRecords
//nolint:gochecknoglobals // Currently global by design
var ItemStatCosts map[string]*ItemStatCostRecord
// LoadItemStatCosts loads ItemStatCostRecord's from text
@ -271,7 +137,7 @@ func LoadItemStatCosts(file []byte) {
SaveAdd: d.GetNumber("Save Add", 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,
@ -279,7 +145,7 @@ func LoadItemStatCosts(file []byte) {
CostMultiply: d.GetNumber("Multiply", 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),
OpBase: d.GetString("op base", idx),
OpStat1: d.GetString("op stat1", idx),

View File

@ -6,6 +6,8 @@ import (
"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 {
// descriptive, not loaded in game. Corresponds with Name field in
// Levels.txt
@ -34,7 +36,8 @@ type LevelMazeDetailsRecord struct {
// 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
func LoadLevelMazeDetails(file []byte) {

View File

@ -7,25 +7,27 @@ import (
"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 {
Files [6]string
Name string
DefinitionID int
LevelID int
SizeX int
SizeY int
Pops int
PopPad int
FileCount int
Dt1Mask uint
Populate bool
Logicals bool
Outdoors bool
Animate bool
KillEdge bool
FillBlanks bool
SizeX int
SizeY int
AutoMap bool
Scan bool
Pops int
PopPad int
FileCount int
Files [6]string
Dt1Mask uint
Beta bool
Expansion bool
}
@ -70,7 +72,8 @@ func createLevelPresetRecord(props []string) LevelPresetRecord {
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
func LoadLevelPresets(file []byte) {

View File

@ -6,9 +6,12 @@ import (
"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 {
// Description, reference only.
Name string // Name
// 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
// groups. If you count each row of a group starting from 0, then you'll
@ -61,8 +64,11 @@ type LevelSubstitutionRecord struct {
// Beta
}
// LevelSubstitutions stores all of the LevelSubstitutionRecords
//nolint:gochecknoglobals // Currently global by design
var LevelSubstitutions map[int]*LevelSubstitutionRecord
// LoadLevelSubstitutions loads lvlsub.txt and parses into records
func LoadLevelSubstitutions(file []byte) {
dict := d2common.LoadDataDictionary(string(file))
numRecords := len(dict.Data)

View File

@ -7,17 +7,21 @@ import (
"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 {
Files [32]string
Name string
ID int
Files [32]string
Beta bool
Act int
Beta 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) {
data := strings.Split(string(file), "\r\n")[1:]
LevelTypes = make([]LevelTypeRecord, len(data))

View File

@ -6,6 +6,8 @@ import (
"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 {
ID int32
SelectX int32
@ -21,8 +23,8 @@ type LevelWarpRecord struct {
Direction string
}
//nolint:gochecknoglobals // Currently global by design, only written once
// LevelWarps loaded from txt records
//nolint:gochecknoglobals // Currently global by design, only written once
var LevelWarps map[int]*LevelWarpRecord
// LoadLevelWarps loads LevelWarpRecord's from text file data

View File

@ -7,21 +7,103 @@ import (
"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 {
// Name
// This column has no function, it only serves as a comment field to make it
// easier to identify the Level name
Name string // Name <-- the corresponding column name in the txt
// Level ID (used in columns like VIS0-7)
Id int // Id
// 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
// 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
// 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
// QuestFlag, QuestExpansionFlag
// 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
// completed the quest associated with the flag to take a town portal to
@ -37,17 +119,15 @@ type LevelDetailsRecord struct {
// additional layers.
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
// lvlprest.txt to set the size for the .ds1 file.
SizeXNormal int // SizeX
SizeYNormal int // SizeY
SizeXNormal int // SizeX
SizeYNormal int // SizeY
SizeXNightmare int // SizeX(N)
SizeYNightmare int // SizeY(N)
SizeXHell int // SizeX(H)
SizeYHell int // SizeY(H)
SizeXHell int // SizeX(H)
SizeYHell int // SizeY(H)
// They set the X\Y position in the world space
WorldOffsetX int // OffsetX
@ -58,6 +138,9 @@ type LevelDetailsRecord struct {
// location.
DependantLevelID int // Depend
// The type of the Level (Id from lvltypes.txt)
LevelType int // LevelType
// Controls if teleport is allowed in that level.
// 0 = Teleport not allowed
// 1 = Teleport allowed
@ -65,55 +148,12 @@ type LevelDetailsRecord struct {
// (maybe for objects this is controlled by IsDoor column in objects.txt)
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:
// 1 Random Maze
// 2 Preset Area
// 3 Wilderness level
LevelGenerationType d2enum.LevelGenerationType // DrlgType
// The type of the Level (Id from lvltypes.txt)
LevelType int // LevelType
// NOTE
// 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.
@ -123,7 +163,6 @@ type LevelDetailsRecord struct {
// Example: 6=wilderness, 9=desert etc, -1=no subtype.
SubType int // SubType
// TODO this may need an enumeration.. ?
// Tells which subtheme a wilderness area should use.
// Themes ranges from -1 (no subtheme) to 4.
SubTheme int // SubTheme
@ -170,29 +209,6 @@ type LevelDetailsRecord struct {
Green int // Green
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
// first quest Den of Evil are set to 1, since its the first quest).
QuestID int // Quest
@ -227,14 +243,6 @@ type LevelDetailsRecord struct {
MonsterUniqueMaxNightmare int // MonUMax(N)
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
// 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
@ -243,92 +251,12 @@ type LevelDetailsRecord struct {
// types selected randomly from umon1-umon25.
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.
MonsterCritter1SpawnChance int // cpct1
MonsterCritter2SpawnChance int // cpct2
MonsterCritter3SpawnChance int // cpct3
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)
SoundEnvironmentID int // SoundEnv
@ -338,17 +266,6 @@ type LevelDetailsRecord struct {
// between acts however so don't even bother to try.
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,
// taken from Objgroup.txt.
ObjectGroupID0 int // ObjGrp0
@ -371,12 +288,86 @@ type LevelDetailsRecord struct {
ObjectGroupSpawnChance6 int // ObjPrb6
ObjectGroupSpawnChance7 int // ObjPrb7
// Reference Only (can be used for comments)
// Beta
// 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.
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
// GetLevelDetails gets a LevelDetailsRecord by the record Id
func GetLevelDetails(id int) *LevelDetailsRecord {
for i := 0; i < len(LevelDetails); i++ {
if LevelDetails[i].Id == id {
@ -387,6 +378,7 @@ func GetLevelDetails(id int) *LevelDetailsRecord {
return nil
}
// LoadLevelDetails loads level details records from levels.txt
//nolint:funlen // Txt loader, makes no sense to split
func LoadLevelDetails(file []byte) {
dict := d2common.LoadDataDictionary(string(file))

View File

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

View File

@ -6,9 +6,11 @@ import (
"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) {
MiscItems = *LoadCommonItems(file, d2enum.InventoryItemTypeItem)
MiscItems = LoadCommonItems(file, d2enum.InventoryItemTypeItem)
log.Printf("Loaded %d misc items", len(MiscItems))
}

View File

@ -7,17 +7,20 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
// MissileCalcParam is a calculation parameter for a missile
type MissileCalcParam struct {
Param int
Desc string
}
// MissileCalc is a calculation for a missile
type MissileCalc struct {
Calc d2common.CalcString
Desc string
Params []MissileCalcParam
}
// MissileLight has the parameters for missile lighting
type MissileLight struct {
Diameter int
Flicker int
@ -26,24 +29,27 @@ type MissileLight struct {
Blue uint8
}
// MissileAnimation stores parameters for a missile animation
type MissileAnimation struct {
CelFileName string
StepsBeforeVisible int
StepsBeforeActive int
LoopAnimation bool
CelFileName string
AnimationRate int // seems to do nothing
AnimationLength int
AnimationSpeed int
StartingFrame int // called "RandFrame"
HasSubLoop bool // runs after first animation ends
StartingFrame int // called "RandFrame"
SubStartingFrame int
SubEndingFrame int
LoopAnimation bool
HasSubLoop bool // runs after first animation ends
}
// MissileCollision parameters for missile collision
type MissileCollision struct {
CollisionType int // controls the kind of collision
// 0 = none, 1 = units only, 3 = normal (units, walls),
// 6 = walls only, 8 = walls, units, and floors
TimerFrames int // how many frames to persist
DestroyedUponCollision bool
FriendlyFire bool
LastCollide bool // unknown
@ -51,9 +57,9 @@ type MissileCollision struct {
ClientCollision bool // unknown
ClientSend bool // unclear
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 {
MinDamage int
MaxDamage int
@ -63,6 +69,7 @@ type MissileDamage struct {
DamageSynergyPerCalc d2common.CalcString // works like synergy in skills.txt, not clear
}
// MissileElementalDamage parameters for calculating missile elemental damage
type MissileElementalDamage struct {
Damage MissileDamage
ElementType string
@ -70,20 +77,46 @@ type MissileElementalDamage struct {
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 {
Name string
Id int
ServerMovementCalc MissileCalc
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
ClientCollisionFunc int
ServerMovementFunc int
ServerCollisionFunc int
ServerDamageFunc int
ServerMovementCalc MissileCalc
ClientMovementCalc MissileCalc
ServerCollisionCalc MissileCalc
ClientCollisionCalc MissileCalc
ServerDamageCalc MissileCalc
Velocity int
MaxVelocity int
@ -92,20 +125,46 @@ type MissileRecord struct {
Range int
LevelRangeBonus int
Light MissileLight
Animation MissileAnimation
Collision MissileCollision
XOffset int
YOffset int
ZOffset int
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
CanDestroy bool // unknown
DestroyedByTPFrame int // see above, for client side, (this is a guess) which frame it vanishes on
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
// if false, has a 95% chance to hit.
@ -120,74 +179,25 @@ type MissileRecord struct {
// if false, vanishes when spawned in town
IgnoreBossModifiers bool // if true, doesn't get bonuses from boss mods
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?)
CanBeSlowed bool // if true, is affected by skill_handofathena
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
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
// 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
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
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"
MissileSkill bool // if true, applies elemental damage from items to the splash radius instead of normal damage modifiers
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
SourceDamage int // 0-128, 128 is 100%
// 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)
// 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
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 {
@ -292,9 +302,11 @@ func createMissileRecord(line string) MissileRecord {
return result
}
// Missiles stores all of the MissileRecords
//nolint:gochecknoglobals // Currently global by design, only written once
var Missiles map[int]*MissileRecord
// LoadMissiles loads MissileRecords from missiles.txt
func LoadMissiles(file []byte) {
Missiles = make(map[int]*MissileRecord)
data := strings.Split(string(file), "\r\n")[1:]

View File

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

View File

@ -1,4 +1,3 @@
// d2datadict contains loaders for the txt file data
package d2datadict
import (
@ -29,7 +28,7 @@ type (
// column also links other hardcoded effects to the units, such as the
// transparency on necro summons and the name-color change on unique boss
// 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
// 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
// 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
// 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
// SoundKeyNormal, SoundKeySpecial
@ -114,14 +115,14 @@ type (
// 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
// determined by the units AI.
SkillId1 string // called `Skill1` in monstats.txt
SkillId2 string // called `Skill2` in monstats.txt
SkillId3 string // called `Skill3` in monstats.txt
SkillId4 string // called `Skill4` in monstats.txt
SkillId5 string // called `Skill5` in monstats.txt
SkillId6 string // called `Skill6` in monstats.txt
SkillId7 string // called `Skill7` in monstats.txt
SkillId8 string // called `Skill8` in monstats.txt
SkillId1 string // called `Skill1` in monstats.txt //nolint:golint Id is the right key
SkillId2 string // called `Skill2` in monstats.txt //nolint:golint Id is the right key
SkillId3 string // called `Skill3` in monstats.txt //nolint:golint Id is the right key
SkillId4 string // called `Skill4` in monstats.txt //nolint:golint Id is the right key
SkillId5 string // called `Skill5` in monstats.txt //nolint:golint Id is the right key
SkillId6 string // called `Skill6` in monstats.txt //nolint:golint Id is the right key
SkillId7 string // called `Skill7` in monstats.txt //nolint:golint Id is the right key
SkillId8 string // called `Skill8` in monstats.txt //nolint:golint Id is the right key
// SkillAnimation1 -- SkillAnimation8
// 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
// the druids summons. IE their damage is specified solely by Skills.txt and
// not by MonStats.txt.
//nolint:golint Id is the right key
DamageSkillId string // called `SkillDamage` in monstats.txt
// ElementAttackMode1 -- ElementAttackMode3

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -2,23 +2,19 @@ package d2datadict
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
type ObjectType int
const (
ObjectTypeCharacter ObjectType = 1
ObjectTypeItem ObjectType = 2
)
// ObjectLookupRecord is a representation of a row from objects.txt
type ObjectLookupRecord struct {
Act int
Type ObjectType
Id int
Type d2enum.ObjectType
Id int //nolint:golint Id is the right key
Name string
Description string
ObjectsTxtId int
MonstatsTxtId int
ObjectsTxtId int //nolint:golint Id is the right key
MonstatsTxtId int //nolint:golint Id is the right key
Direction int
Base string
Token string
@ -44,6 +40,7 @@ type ObjectLookupRecord struct {
Index int
}
// LookupObject looks up an object record
func LookupObject(act, typ, id int) *ObjectLookupRecord {
object := lookupObject(act, typ, id, indexedObjects)
if object == nil {
@ -54,14 +51,23 @@ func LookupObject(act, typ, id int) *ObjectLookupRecord {
}
func lookupObject(act, typ, id int, objects [][][]*ObjectLookupRecord) *ObjectLookupRecord {
if objects[act] != nil && objects[act][typ] != nil && objects[act][typ][id] != nil {
return objects[act][typ][id]
if len(objects) < act {
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)
}
@ -88,7 +94,8 @@ func indexObjects(objects []ObjectLookupRecord) [][][]*ObjectLookupRecord {
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.
// [Act 1-5][Type 1-2][Id 0-855]
//nolint:gochecknoglobals // Currently global by design
var indexedObjects [][][]*ObjectLookupRecord

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,14 @@
package d2datadict
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
// 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
// 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).
@ -118,8 +119,11 @@ type SuperUniqueRecord struct {
UTransHell string
}
// SuperUniques stores all of the SuperUniqueRecords
//nolint:gochecknoglobals // Currently global by design
var SuperUniques map[string]*SuperUniqueRecord
// LoadSuperUniques loads SuperUniqueRecords from superuniques.txt
func LoadSuperUniques(file []byte) {
dictionary := d2common.LoadDataDictionary(string(file))
SuperUniques = make(map[string]*SuperUniqueRecord, len(dictionary.Data))

View File

@ -7,6 +7,7 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common"
)
// UniqueItemRecord is a representation of a row from uniqueitems.txt
type UniqueItemRecord struct {
Name string
Version int // 0 = classic pre 1.07, 1 = 1.07-1.11, 100 = expansion
@ -41,6 +42,7 @@ type UniqueItemRecord struct {
Properties [12]UniqueItemProperty
}
// UniqueItemProperty is describes a property of a unique item
type UniqueItemProperty struct {
Property 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
}
// UniqueItems stores all of the UniqueItemRecords
//nolint:gochecknoglobals // Currently global by design
var UniqueItems map[string]*UniqueItemRecord
// LoadUniqueItems loadsUniqueItemRecords fro uniqueitems.txt
func LoadUniqueItems(file []byte) {
UniqueItems = make(map[string]*UniqueItemRecord)
data := strings.Split(string(file), "\r\n")[1:]

View File

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

View File

@ -5,9 +5,10 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
)
// Object is a game world object
type Object struct {
Type int
Id int
Id int //nolint:golint Id is the right key
X int
Y 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 {
switch object.Lookup.Type {
case d2datadict.ObjectTypeCharacter:
case d2enum.ObjectTypeCharacter:
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.SetPaths(convertPaths(tileOffsetX, tileOffsetY, object.Paths))
entities = append(entities, npc)
}
case d2datadict.ObjectTypeItem:
case d2enum.ObjectTypeItem:
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)
if err != nil {

View File

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