mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-12-26 03:56:42 -05:00
merged d2shared into the core package (#275)
This commit is contained in:
parent
8de0cde818
commit
6832a5a0db
@ -5,10 +5,11 @@ import (
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2dc6"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
|
||||
"github.com/OpenDiablo2/D2Shared/d2helper"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2dc6"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2dcc"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
|
@ -5,8 +5,8 @@ import (
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2mpq"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2mpq"
|
||||
)
|
||||
|
||||
type archiveEntry struct {
|
||||
|
@ -4,12 +4,12 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2cof"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2dc6"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2mpq"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2cof"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2dc6"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2dcc"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2mpq"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2term"
|
||||
)
|
||||
|
||||
|
@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2dcc"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2dcc"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
)
|
||||
|
||||
|
@ -3,7 +3,7 @@ package d2asset
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
|
||||
)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package d2asset
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
)
|
||||
|
||||
type paletteManager struct {
|
||||
|
@ -3,8 +3,8 @@ package d2audio
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/audio/wav"
|
||||
|
||||
|
85
d2common/bitmuncher.go
Normal file
85
d2common/bitmuncher.go
Normal file
@ -0,0 +1,85 @@
|
||||
package d2common
|
||||
|
||||
type BitMuncher struct {
|
||||
data []byte
|
||||
Offset int
|
||||
BitsRead int
|
||||
}
|
||||
|
||||
func CreateBitMuncher(data []byte, offset int) *BitMuncher {
|
||||
return &BitMuncher{
|
||||
data: data,
|
||||
Offset: offset,
|
||||
BitsRead: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func CopyBitMuncher(source *BitMuncher) *BitMuncher {
|
||||
return &BitMuncher{
|
||||
source.data,
|
||||
source.Offset,
|
||||
0,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *BitMuncher) GetBit() uint32 {
|
||||
result := uint32(v.data[v.Offset/8]>>uint(v.Offset%8)) & 0x01
|
||||
v.Offset++
|
||||
v.BitsRead++
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *BitMuncher) SkipBits(bits int) {
|
||||
v.Offset += bits
|
||||
v.BitsRead += bits
|
||||
}
|
||||
|
||||
func (v *BitMuncher) GetByte() byte {
|
||||
return byte(v.GetBits(8))
|
||||
}
|
||||
|
||||
func (v *BitMuncher) GetInt32() int32 {
|
||||
return v.MakeSigned(v.GetBits(32), 32)
|
||||
}
|
||||
|
||||
func (v *BitMuncher) GetUInt32() uint32 {
|
||||
return v.GetBits(32)
|
||||
}
|
||||
|
||||
func (v *BitMuncher) GetBits(bits int) uint32 {
|
||||
if bits == 0 {
|
||||
return 0
|
||||
}
|
||||
result := uint32(0)
|
||||
for i := 0; i < bits; i++ {
|
||||
result |= v.GetBit() << uint(i)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *BitMuncher) GetSignedBits(bits int) int {
|
||||
return int(v.MakeSigned(v.GetBits(bits), bits))
|
||||
}
|
||||
|
||||
func (v *BitMuncher) MakeSigned(value uint32, bits int) int32 {
|
||||
if bits == 0 {
|
||||
return 0
|
||||
}
|
||||
// If its a single bit, a value of 1 is -1 automagically
|
||||
if bits == 1 {
|
||||
return -int32(value)
|
||||
}
|
||||
// If there is no sign bit, return the value as is
|
||||
if (value & (1 << uint(bits-1))) == 0 {
|
||||
return int32(value)
|
||||
}
|
||||
// We need to extend the signed bit out so that the negative value representation still works with the 2s compliment rule.
|
||||
result := uint32(4294967295)
|
||||
for i := byte(0); i < byte(bits); i++ {
|
||||
if ((value >> uint(i)) & 1) == 0 {
|
||||
result -= uint32(1 << uint(i))
|
||||
}
|
||||
}
|
||||
// Force casting to a signed value
|
||||
return int32(result)
|
||||
}
|
64
d2common/bitstream.go
Normal file
64
d2common/bitstream.go
Normal file
@ -0,0 +1,64 @@
|
||||
package d2common
|
||||
|
||||
import "log"
|
||||
|
||||
// BitStream is a utility class for reading groups of bits from a stream
|
||||
type BitStream struct {
|
||||
data []byte
|
||||
dataPosition int
|
||||
current int
|
||||
bitCount int
|
||||
}
|
||||
|
||||
// CreateBitStream creates a new BitStream
|
||||
func CreateBitStream(newData []byte) *BitStream {
|
||||
result := &BitStream{
|
||||
data: newData,
|
||||
dataPosition: 0,
|
||||
current: 0,
|
||||
bitCount: 0,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ReadBits reads the specified number of bits and returns the value
|
||||
func (v *BitStream) ReadBits(bitCount int) int {
|
||||
if bitCount > 16 {
|
||||
log.Panic("Maximum BitCount is 16")
|
||||
}
|
||||
if !v.EnsureBits(bitCount) {
|
||||
return -1
|
||||
}
|
||||
result := v.current & (0xffff >> uint(16-bitCount))
|
||||
v.WasteBits(bitCount)
|
||||
return result
|
||||
}
|
||||
|
||||
// PeekByte returns the current byte without adjusting the position
|
||||
func (v *BitStream) PeekByte() int {
|
||||
if !v.EnsureBits(8) {
|
||||
return -1
|
||||
}
|
||||
return v.current & 0xff
|
||||
}
|
||||
|
||||
// EnsureBits ensures that the specified number of bits are available
|
||||
func (v *BitStream) EnsureBits(bitCount int) bool {
|
||||
if bitCount <= v.bitCount {
|
||||
return true
|
||||
}
|
||||
if v.dataPosition >= len(v.data) {
|
||||
return false
|
||||
}
|
||||
nextValue := v.data[v.dataPosition]
|
||||
v.dataPosition++
|
||||
v.current |= int(nextValue) << uint(v.bitCount)
|
||||
v.bitCount += 8
|
||||
return true
|
||||
}
|
||||
|
||||
// WasteBits dry-reads the specified number of bits
|
||||
func (v *BitStream) WasteBits(bitCount int) {
|
||||
v.current >>= uint(bitCount)
|
||||
v.bitCount -= bitCount
|
||||
}
|
33
d2common/bitstream_test.go
Normal file
33
d2common/bitstream_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
package d2common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBitStreamBits(t *testing.T) {
|
||||
data := []byte{0xAA}
|
||||
bitStream := CreateBitStream(data)
|
||||
shouldBeOne := 0
|
||||
for i := 0; i < 8; i++ {
|
||||
bit := bitStream.ReadBits(1)
|
||||
if bit != shouldBeOne {
|
||||
t.Fatalf("Expected %d but got %d on iteration %d", shouldBeOne, bit, i)
|
||||
}
|
||||
if shouldBeOne == 1 {
|
||||
shouldBeOne = 0
|
||||
} else {
|
||||
shouldBeOne = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBitStreamBytes(t *testing.T) {
|
||||
data := []byte{0xAA, 0xBB, 0xCC, 0xDD, 0x12, 0x34, 0x56, 0x78}
|
||||
bitStream := CreateBitStream(data)
|
||||
for i := 0; i < 8; i++ {
|
||||
b := byte(bitStream.ReadBits(8))
|
||||
if b != data[i] {
|
||||
t.Fatalf("Expected %d but got %d on iteration %d", data[i], b, i)
|
||||
}
|
||||
}
|
||||
}
|
20
d2common/build_info.go
Normal file
20
d2common/build_info.go
Normal file
@ -0,0 +1,20 @@
|
||||
package d2common
|
||||
|
||||
// BuildInfoRecord is the structure used to hold information about the current build
|
||||
type BuildInfoRecord struct {
|
||||
// Branch is the branch this build is based on (or 'Local' if built locally)
|
||||
Branch string
|
||||
// Commit is the commit hash of the build (or blank if built locally)
|
||||
Commit string
|
||||
}
|
||||
|
||||
// BuildInfo contains information about the build currently being ran
|
||||
var BuildInfo BuildInfoRecord
|
||||
|
||||
// SetBuildInfo is called at the start of the application to generate the global BuildInfo value
|
||||
func SetBuildInfo(branch, commit string) {
|
||||
BuildInfo = BuildInfoRecord{
|
||||
Branch: branch,
|
||||
Commit: commit,
|
||||
}
|
||||
}
|
9
d2common/calcstring.go
Normal file
9
d2common/calcstring.go
Normal file
@ -0,0 +1,9 @@
|
||||
package d2common
|
||||
|
||||
// a calcstring is a type of string often used in datafiles to specify
|
||||
// a value that is calculated dynamically based on the stats of the relevant
|
||||
// source, for instance a missile might have a movement speed of lvl*2
|
||||
|
||||
type CalcString string
|
||||
|
||||
// todo: the logic for parsing these should exist here
|
11
d2common/d2enum/animation_frame.go
Normal file
11
d2common/d2enum/animation_frame.go
Normal file
@ -0,0 +1,11 @@
|
||||
package d2enum
|
||||
|
||||
type AnimationFrame int
|
||||
|
||||
const (
|
||||
AnimationFrameNoEvent AnimationFrame = 0
|
||||
AnimationFrameAttack AnimationFrame = 1
|
||||
AnimationFrameMissile AnimationFrame = 2
|
||||
AnimationFrameSound AnimationFrame = 3
|
||||
AnimationFrameSkill AnimationFrame = 4
|
||||
)
|
52
d2common/d2enum/animation_mode.go
Normal file
52
d2common/d2enum/animation_mode.go
Normal file
@ -0,0 +1,52 @@
|
||||
package d2enum
|
||||
|
||||
type AnimationMode int
|
||||
|
||||
const (
|
||||
AnimationModePlayerDeath AnimationMode = 0 // DT
|
||||
AnimationModePlayerNeutral AnimationMode = 1 // NU
|
||||
AnimationModePlayerWalk AnimationMode = 2 // WL
|
||||
AnimationModePlayerRun AnimationMode = 3 // RN
|
||||
AnimationModePlayerGetHit AnimationMode = 4 // GH
|
||||
AnimationModePlayerTownNeutral AnimationMode = 5 // TN
|
||||
AnimationModePlayerTownWalk AnimationMode = 6 // TW
|
||||
AnimationModePlayerAttack1 AnimationMode = 7 // A1
|
||||
AnimationModePlayerAttack2 AnimationMode = 8 // A2
|
||||
AnimationModePlayerBlock AnimationMode = 9 // BL
|
||||
AnimationModePlayerCast AnimationMode = 10 // SC
|
||||
AnimationModePlayerThrow AnimationMode = 11 // TH
|
||||
AnimationModePlayerKick AnimationMode = 12 // KK
|
||||
AnimationModePlayerSkill1 AnimationMode = 13 // S1
|
||||
AnimationModePlayerSkill2 AnimationMode = 14 // S2
|
||||
AnimationModePlayerSkill3 AnimationMode = 15 // S3
|
||||
AnimationModePlayerSkill4 AnimationMode = 16 // S4
|
||||
AnimationModePlayerDead AnimationMode = 17 // DD
|
||||
AnimationModePlayerSequence AnimationMode = 18 // GH
|
||||
AnimationModePlayerKnockBack AnimationMode = 19 // GH
|
||||
AnimationModeMonsterDeath AnimationMode = 20 // DT
|
||||
AnimationModeMonsterNeutral AnimationMode = 21 // NU
|
||||
AnimationModeMonsterWalk AnimationMode = 22 // WL
|
||||
AnimationModeMonsterGetHit AnimationMode = 23 // GH
|
||||
AnimationModeMonsterAttack1 AnimationMode = 24 // A1
|
||||
AnimationModeMonsterAttack2 AnimationMode = 25 // A2
|
||||
AnimationModeMonsterBlock AnimationMode = 26 // BL
|
||||
AnimationModeMonsterCast AnimationMode = 27 // SC
|
||||
AnimationModeMonsterSkill1 AnimationMode = 28 // S1
|
||||
AnimationModeMonsterSkill2 AnimationMode = 29 // S2
|
||||
AnimationModeMonsterSkill3 AnimationMode = 30 // S3
|
||||
AnimationModeMonsterSkill4 AnimationMode = 31 // S4
|
||||
AnimationModeMonsterDead AnimationMode = 32 // DD
|
||||
AnimationModeMonsterKnockback AnimationMode = 33 // GH
|
||||
AnimationModeMonsterSequence AnimationMode = 34 // xx
|
||||
AnimationModeMonsterRun AnimationMode = 35 // RN
|
||||
AnimationModeObjectNeutral AnimationMode = 36 // NU
|
||||
AnimationModeObjectOperating AnimationMode = 37 // OP
|
||||
AnimationModeObjectOpened AnimationMode = 38 // ON
|
||||
AnimationModeObjectSpecial1 AnimationMode = 39 // S1
|
||||
AnimationModeObjectSpecial2 AnimationMode = 40 // S2
|
||||
AnimationModeObjectSpecial3 AnimationMode = 41 // S3
|
||||
AnimationModeObjectSpecial4 AnimationMode = 42 // S4
|
||||
AnimationModeObjectSpecial5 AnimationMode = 43 // S5
|
||||
)
|
||||
|
||||
//go:generate stringer -linecomment -type AnimationMode
|
66
d2common/d2enum/animation_mode_string.go
Normal file
66
d2common/d2enum/animation_mode_string.go
Normal file
@ -0,0 +1,66 @@
|
||||
// Code generated by "stringer -linecomment -type AnimationMode"; DO NOT EDIT.
|
||||
|
||||
package d2enum
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[AnimationModePlayerDeath-0]
|
||||
_ = x[AnimationModePlayerNeutral-1]
|
||||
_ = x[AnimationModePlayerWalk-2]
|
||||
_ = x[AnimationModePlayerRun-3]
|
||||
_ = x[AnimationModePlayerGetHit-4]
|
||||
_ = x[AnimationModePlayerTownNeutral-5]
|
||||
_ = x[AnimationModePlayerTownWalk-6]
|
||||
_ = x[AnimationModePlayerAttack1-7]
|
||||
_ = x[AnimationModePlayerAttack2-8]
|
||||
_ = x[AnimationModePlayerBlock-9]
|
||||
_ = x[AnimationModePlayerCast-10]
|
||||
_ = x[AnimationModePlayerThrow-11]
|
||||
_ = x[AnimationModePlayerKick-12]
|
||||
_ = x[AnimationModePlayerSkill1-13]
|
||||
_ = x[AnimationModePlayerSkill2-14]
|
||||
_ = x[AnimationModePlayerSkill3-15]
|
||||
_ = x[AnimationModePlayerSkill4-16]
|
||||
_ = x[AnimationModePlayerDead-17]
|
||||
_ = x[AnimationModePlayerSequence-18]
|
||||
_ = x[AnimationModePlayerKnockBack-19]
|
||||
_ = x[AnimationModeMonsterDeath-20]
|
||||
_ = x[AnimationModeMonsterNeutral-21]
|
||||
_ = x[AnimationModeMonsterWalk-22]
|
||||
_ = x[AnimationModeMonsterGetHit-23]
|
||||
_ = x[AnimationModeMonsterAttack1-24]
|
||||
_ = x[AnimationModeMonsterAttack2-25]
|
||||
_ = x[AnimationModeMonsterBlock-26]
|
||||
_ = x[AnimationModeMonsterCast-27]
|
||||
_ = x[AnimationModeMonsterSkill1-28]
|
||||
_ = x[AnimationModeMonsterSkill2-29]
|
||||
_ = x[AnimationModeMonsterSkill3-30]
|
||||
_ = x[AnimationModeMonsterSkill4-31]
|
||||
_ = x[AnimationModeMonsterDead-32]
|
||||
_ = x[AnimationModeMonsterKnockback-33]
|
||||
_ = x[AnimationModeMonsterSequence-34]
|
||||
_ = x[AnimationModeMonsterRun-35]
|
||||
_ = x[AnimationModeObjectNeutral-36]
|
||||
_ = x[AnimationModeObjectOperating-37]
|
||||
_ = x[AnimationModeObjectOpened-38]
|
||||
_ = x[AnimationModeObjectSpecial1-39]
|
||||
_ = x[AnimationModeObjectSpecial2-40]
|
||||
_ = x[AnimationModeObjectSpecial3-41]
|
||||
_ = x[AnimationModeObjectSpecial4-42]
|
||||
_ = x[AnimationModeObjectSpecial5-43]
|
||||
}
|
||||
|
||||
const _AnimationMode_name = "DTNUWLRNGHTNTWA1A2BLSCTHKKS1S2S3S4DDGHGHDTNUWLGHA1A2BLSCS1S2S3S4DDGHxxRNNUOPONS1S2S3S4S5"
|
||||
|
||||
var _AnimationMode_index = [...]uint8{0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88}
|
||||
|
||||
func (i AnimationMode) String() string {
|
||||
if i < 0 || i >= AnimationMode(len(_AnimationMode_index)-1) {
|
||||
return "AnimationMode(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _AnimationMode_name[_AnimationMode_index[i]:_AnimationMode_index[i+1]]
|
||||
}
|
23
d2common/d2enum/composite_type.go
Normal file
23
d2common/d2enum/composite_type.go
Normal file
@ -0,0 +1,23 @@
|
||||
package d2enum
|
||||
|
||||
type CompositeType int
|
||||
|
||||
const (
|
||||
CompositeTypeHead CompositeType = 0
|
||||
CompositeTypeTorso CompositeType = 1
|
||||
CompositeTypeLegs CompositeType = 2
|
||||
CompositeTypeRightArm CompositeType = 3
|
||||
CompositeTypeLeftArm CompositeType = 4
|
||||
CompositeTypeRightHand CompositeType = 5
|
||||
CompositeTypeLeftHand CompositeType = 6
|
||||
CompositeTypeShield CompositeType = 7
|
||||
CompositeTypeSpecial1 CompositeType = 8
|
||||
CompositeTypeSpecial2 CompositeType = 9
|
||||
CompositeTypeSpecial3 CompositeType = 10
|
||||
CompositeTypeSpecial4 CompositeType = 11
|
||||
CompositeTypeSpecial5 CompositeType = 12
|
||||
CompositeTypeSpecial6 CompositeType = 13
|
||||
CompositeTypeSpecial7 CompositeType = 14
|
||||
CompositeTypeSpecial8 CompositeType = 15
|
||||
CompositeTypeMax CompositeType = 16
|
||||
)
|
15
d2common/d2enum/draw_effect.go
Normal file
15
d2common/d2enum/draw_effect.go
Normal file
@ -0,0 +1,15 @@
|
||||
package d2enum
|
||||
|
||||
type DrawEffect int
|
||||
|
||||
// Names courtesy of Necrolis
|
||||
const (
|
||||
DrawEffectPctTransparency25 = 0 //GL_MODULATE; GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 25 % transparency (colormaps 49-304 in a .pl2)
|
||||
DrawEffectPctTransparency50 = 1 //GL_MODULATE; GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 50 % transparency (colormaps 305-560 in a .pl2)
|
||||
DrawEffectPctTransparency75 = 2 //GL_MODULATE; GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 75 % transparency (colormaps 561-816 in a .pl2)
|
||||
DrawEffectModulate = 3 //GL_MODULATE; GL_SRC_ALPHA, GL_DST_ALPHA (colormaps 817-1072 in a .pl2)
|
||||
DrawEffectBurn = 4 //GL_MODULATE; GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA (colormaps 1073-1328 in a .pl2)
|
||||
DrawEffectNormal = 5 //GL_MODULATE; GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA (colormaps 1457-1712 in a .pl2)
|
||||
DrawEffectMod2XTrans = 6 //GL_MODULATE; GL_SRC_COLOR, GL_DST_ALPHA (colormaps 1457-1712 in a .pl2)
|
||||
DrawEffectMod2X = 7 //GL_COMBINE_ARB; GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA (colormaps 1457-1712 in a .pl2)
|
||||
)
|
41
d2common/d2enum/hero.go
Normal file
41
d2common/d2enum/hero.go
Normal file
@ -0,0 +1,41 @@
|
||||
package d2enum
|
||||
|
||||
import "log"
|
||||
|
||||
type Hero int
|
||||
|
||||
const (
|
||||
HeroNone Hero = 0 //
|
||||
HeroBarbarian Hero = 1 // Barbarian
|
||||
HeroNecromancer Hero = 2 // Necromancer
|
||||
HeroPaladin Hero = 3 // Paladin
|
||||
HeroAssassin Hero = 4 // Assassin
|
||||
HeroSorceress Hero = 5 // Sorceress
|
||||
HeroAmazon Hero = 6 // Amazon
|
||||
HeroDruid Hero = 7 // Druid
|
||||
)
|
||||
|
||||
func (v Hero) GetToken() string {
|
||||
switch v {
|
||||
case HeroBarbarian:
|
||||
return "BA"
|
||||
case HeroNecromancer:
|
||||
return "NE"
|
||||
case HeroPaladin:
|
||||
return "PA"
|
||||
case HeroAssassin:
|
||||
return "AI"
|
||||
case HeroSorceress:
|
||||
return "SO"
|
||||
case HeroAmazon:
|
||||
return "AM"
|
||||
case HeroDruid:
|
||||
return "DZ"
|
||||
default:
|
||||
log.Fatalf("Unknown hero token: %d", v)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//go:generate stringer -linecomment -type Hero
|
||||
//go:generate string2enum -samepkg -linecomment -type Hero
|
11
d2common/d2enum/hero_stance.go
Normal file
11
d2common/d2enum/hero_stance.go
Normal file
@ -0,0 +1,11 @@
|
||||
package d2enum
|
||||
|
||||
type HeroStance int
|
||||
|
||||
const (
|
||||
HeroStanceIdle HeroStance = 0
|
||||
HeroStanceIdleSelected HeroStance = 1
|
||||
HeroStanceApproaching HeroStance = 2
|
||||
HeroStanceSelected HeroStance = 3
|
||||
HeroStanceRetreating HeroStance = 4
|
||||
)
|
30
d2common/d2enum/hero_string.go
Normal file
30
d2common/d2enum/hero_string.go
Normal file
@ -0,0 +1,30 @@
|
||||
// Code generated by "stringer -linecomment -type Hero"; DO NOT EDIT.
|
||||
|
||||
package d2enum
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[HeroNone-0]
|
||||
_ = x[HeroBarbarian-1]
|
||||
_ = x[HeroNecromancer-2]
|
||||
_ = x[HeroPaladin-3]
|
||||
_ = x[HeroAssassin-4]
|
||||
_ = x[HeroSorceress-5]
|
||||
_ = x[HeroAmazon-6]
|
||||
_ = x[HeroDruid-7]
|
||||
}
|
||||
|
||||
const _Hero_name = "BarbarianNecromancerPaladinAssassinSorceressAmazonDruid"
|
||||
|
||||
var _Hero_index = [...]uint8{0, 0, 9, 20, 27, 35, 44, 50, 55}
|
||||
|
||||
func (i Hero) String() string {
|
||||
if i < 0 || i >= Hero(len(_Hero_index)-1) {
|
||||
return "Hero(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _Hero_name[_Hero_index[i]:_Hero_index[i+1]]
|
||||
}
|
18
d2common/d2enum/hero_string2enum.go
Normal file
18
d2common/d2enum/hero_string2enum.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Code generated by "string2enum -samepkg -linecomment -type Hero"; DO NOT EDIT.
|
||||
|
||||
package d2enum
|
||||
|
||||
import "fmt"
|
||||
|
||||
// HeroFromString returns the Hero enum corresponding to s.
|
||||
func HeroFromString(s string) Hero {
|
||||
if len(s) == 0 {
|
||||
return 0
|
||||
}
|
||||
for i := range _Hero_index[:len(_Hero_index)-1] {
|
||||
if s == _Hero_name[_Hero_index[i]:_Hero_index[i+1]] {
|
||||
return Hero(i)
|
||||
}
|
||||
}
|
||||
panic(fmt.Errorf("unable to locate Hero enum corresponding to %q", s))
|
||||
}
|
9
d2common/d2enum/inventory_item_type.go
Normal file
9
d2common/d2enum/inventory_item_type.go
Normal file
@ -0,0 +1,9 @@
|
||||
package d2enum
|
||||
|
||||
type InventoryItemType int
|
||||
|
||||
const (
|
||||
InventoryItemTypeItem InventoryItemType = 0 // Item
|
||||
InventoryItemTypeWeapon InventoryItemType = 1 // Weapon
|
||||
InventoryItemTypeArmor InventoryItemType = 2 // Armor
|
||||
)
|
18
d2common/d2enum/layer_stream_type.go
Normal file
18
d2common/d2enum/layer_stream_type.go
Normal file
@ -0,0 +1,18 @@
|
||||
package d2enum
|
||||
|
||||
type LayerStreamType int
|
||||
|
||||
const (
|
||||
LayerStreamWall1 LayerStreamType = 0
|
||||
LayerStreamWall2 LayerStreamType = 1
|
||||
LayerStreamWall3 LayerStreamType = 2
|
||||
LayerStreamWall4 LayerStreamType = 3
|
||||
LayerStreamOrientation1 LayerStreamType = 4
|
||||
LayerStreamOrientation2 LayerStreamType = 5
|
||||
LayerStreamOrientation3 LayerStreamType = 6
|
||||
LayerStreamOrientation4 LayerStreamType = 7
|
||||
LayerStreamFloor1 LayerStreamType = 8
|
||||
LayerStreamFloor2 LayerStreamType = 9
|
||||
LayerStreamShadow LayerStreamType = 10
|
||||
LayerStreamSubstitute LayerStreamType = 11
|
||||
)
|
43
d2common/d2enum/palette_defs.go
Normal file
43
d2common/d2enum/palette_defs.go
Normal file
@ -0,0 +1,43 @@
|
||||
package d2enum
|
||||
|
||||
// PaletteType represents a named palette
|
||||
type PaletteType string
|
||||
|
||||
const (
|
||||
// Act1 palette
|
||||
Act1 PaletteType = "act1"
|
||||
// Act2 palette
|
||||
Act2 PaletteType = "act2"
|
||||
// Act3 palette
|
||||
Act3 PaletteType = "act3"
|
||||
// Act4 palette
|
||||
Act4 PaletteType = "act4"
|
||||
// Act5 palette
|
||||
Act5 PaletteType = "act5"
|
||||
// EndGame palette
|
||||
EndGame PaletteType = "endgame"
|
||||
// EndGame2 palette
|
||||
EndGame2 PaletteType = "endgame2"
|
||||
// Fechar palette
|
||||
Fechar PaletteType = "fechar"
|
||||
// Loading palette
|
||||
Loading PaletteType = "loading"
|
||||
// Menu0 palette
|
||||
Menu0 PaletteType = "menu0"
|
||||
// Menu1 palette
|
||||
Menu1 PaletteType = "menu1"
|
||||
// Menu2 palette
|
||||
Menu2 PaletteType = "menu2"
|
||||
// Menu3 palette
|
||||
Menu3 PaletteType = "menu3"
|
||||
// Menu4 palette
|
||||
Menu4 PaletteType = "menu4"
|
||||
// Sky palette
|
||||
Sky PaletteType = "sky"
|
||||
// Static palette
|
||||
Static PaletteType = "static"
|
||||
// Trademark palette
|
||||
Trademark PaletteType = "trademark"
|
||||
// Units palette
|
||||
Units PaletteType = "units"
|
||||
)
|
17
d2common/d2enum/readme.md
Normal file
17
d2common/d2enum/readme.md
Normal file
@ -0,0 +1,17 @@
|
||||
# OpenDiablo2 Enums
|
||||
Items in this folder are compiled with two programs. You can obtain them
|
||||
by running the following:
|
||||
```
|
||||
go get golang.org/x/tools/cmd/stringer
|
||||
go get github.com/mewspring/tools/cmd/string2enum
|
||||
```
|
||||
Once you have the tools installed, simply run the following command in this
|
||||
folder to regenerate the support files:
|
||||
```
|
||||
go generate
|
||||
```
|
||||
If you add any enums (e.g. `AnimationMode`), make sure to add the following to the end of the
|
||||
file:
|
||||
```go
|
||||
//go:generate stringer -linecomment -type AnimationMode
|
||||
```
|
41
d2common/d2enum/region_id.go
Normal file
41
d2common/d2enum/region_id.go
Normal file
@ -0,0 +1,41 @@
|
||||
package d2enum
|
||||
|
||||
type RegionIdType int
|
||||
|
||||
const (
|
||||
RegionAct1Town RegionIdType = 1
|
||||
RegionAct1Wilderness RegionIdType = 2
|
||||
RegionAct1Cave RegionIdType = 3
|
||||
RegionAct1Crypt RegionIdType = 4
|
||||
RegionAct1Monestary RegionIdType = 5
|
||||
RegionAct1Courtyard RegionIdType = 6
|
||||
RegionAct1Barracks RegionIdType = 7
|
||||
RegionAct1Jail RegionIdType = 8
|
||||
RegionAct1Cathedral RegionIdType = 9
|
||||
RegionAct1Catacombs RegionIdType = 10
|
||||
RegionAct1Tristram RegionIdType = 11
|
||||
RegionAct2Town RegionIdType = 12
|
||||
RegionAct2Sewer RegionIdType = 13
|
||||
RegionAct2Harem RegionIdType = 14
|
||||
RegionAct2Basement RegionIdType = 15
|
||||
RegionAct2Desert RegionIdType = 16
|
||||
RegionAct2Tomb RegionIdType = 17
|
||||
RegionAct2Lair RegionIdType = 18
|
||||
RegionAct2Arcane RegionIdType = 19
|
||||
RegionAct3Town RegionIdType = 20
|
||||
RegionAct3Jungle RegionIdType = 21
|
||||
RegionAct3Kurast RegionIdType = 22
|
||||
RegionAct3Spider RegionIdType = 23
|
||||
RegionAct3Dungeon RegionIdType = 24
|
||||
RegionAct3Sewer RegionIdType = 25
|
||||
RegionAct4Town RegionIdType = 26
|
||||
RegionAct4Mesa RegionIdType = 27
|
||||
RegionAct4Lava RegionIdType = 28
|
||||
RegonAct5Town RegionIdType = 29
|
||||
RegionAct5Siege RegionIdType = 30
|
||||
RegionAct5Barricade RegionIdType = 31
|
||||
RegionAct5Temple RegionIdType = 32
|
||||
RegionAct5IceCaves RegionIdType = 33
|
||||
RegionAct5Baal RegionIdType = 34
|
||||
RegionAct5Lava RegionIdType = 35
|
||||
)
|
9
d2common/d2enum/region_layer.go
Normal file
9
d2common/d2enum/region_layer.go
Normal file
@ -0,0 +1,9 @@
|
||||
package d2enum
|
||||
|
||||
type RegionLayerType int
|
||||
|
||||
const (
|
||||
RegionLayerTypeFloors RegionLayerType = 0
|
||||
RegionLayerTypeWalls RegionLayerType = 1
|
||||
RegionLayerTypeShadows RegionLayerType = 2
|
||||
)
|
44
d2common/d2enum/tiletype.go
Normal file
44
d2common/d2enum/tiletype.go
Normal file
@ -0,0 +1,44 @@
|
||||
package d2enum
|
||||
|
||||
type TileType byte
|
||||
|
||||
const (
|
||||
Floor TileType = 0
|
||||
LeftWall TileType = 1
|
||||
RightWall TileType = 2
|
||||
RightPartOfNorthCornerWall TileType = 3
|
||||
LeftPartOfNorthCornerWall TileType = 4
|
||||
LeftEndWall TileType = 5
|
||||
RightEndWall TileType = 6
|
||||
SouthCornerWall TileType = 7
|
||||
LeftWallWithDoor TileType = 8
|
||||
RightWallWithDoor TileType = 9
|
||||
SpecialTile1 TileType = 10
|
||||
SpecialTile2 TileType = 11
|
||||
PillarsColumnsAndStandaloneObjects TileType = 12
|
||||
Shadow TileType = 13
|
||||
Tree TileType = 14
|
||||
Roof TileType = 15
|
||||
LowerWallsEquivalentToLeftWall TileType = 16
|
||||
LowerWallsEquivalentToRightWall TileType = 17
|
||||
LowerWallsEquivalentToRightLeftNorthCornerWall TileType = 18
|
||||
LowerWallsEquivalentToSouthCornerwall TileType = 19
|
||||
)
|
||||
|
||||
func (tile TileType) LowerWall() bool {
|
||||
switch tile {
|
||||
case 16, 17, 18, 19:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (tile TileType) UpperWall() bool {
|
||||
switch tile {
|
||||
case 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
24
d2common/d2enum/weapon_class.go
Normal file
24
d2common/d2enum/weapon_class.go
Normal file
@ -0,0 +1,24 @@
|
||||
package d2enum
|
||||
|
||||
type WeaponClass int
|
||||
|
||||
const (
|
||||
WeaponClassNone WeaponClass = 0 //
|
||||
WeaponClassHandToHand WeaponClass = 1 // hth
|
||||
WeaponClassBow WeaponClass = 2 // bow
|
||||
WeaponClassOneHandSwing WeaponClass = 3 // 1hs
|
||||
WeaponClassOneHandThrust WeaponClass = 4 // 1ht
|
||||
WeaponClassStaff WeaponClass = 5 // stf
|
||||
WeaponClassTwoHandSwing WeaponClass = 6 // 2hs
|
||||
WeaponClassTwoHandThrust WeaponClass = 7 // 2ht
|
||||
WeaponClassCrossbow WeaponClass = 8 // xbw
|
||||
WeaponClassLeftJabRightSwing WeaponClass = 9 // 1js
|
||||
WeaponClassLeftJabRightThrust WeaponClass = 10 // 1jt
|
||||
WeaponClassLeftSwingRightSwing WeaponClass = 11 // 1ss
|
||||
WeaponClassLeftSwingRightThrust WeaponClass = 12 // 1st
|
||||
WeaponClassOneHandToHand WeaponClass = 13 // ht1
|
||||
WeaponClassTwoHandToHand WeaponClass = 14 // ht2
|
||||
)
|
||||
|
||||
//go:generate stringer -linecomment -type WeaponClass
|
||||
//go:generate string2enum -samepkg -linecomment -type WeaponClass
|
37
d2common/d2enum/weapon_class_string.go
Normal file
37
d2common/d2enum/weapon_class_string.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Code generated by "stringer -linecomment -type WeaponClass"; DO NOT EDIT.
|
||||
|
||||
package d2enum
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[WeaponClassNone-0]
|
||||
_ = x[WeaponClassHandToHand-1]
|
||||
_ = x[WeaponClassBow-2]
|
||||
_ = x[WeaponClassOneHandSwing-3]
|
||||
_ = x[WeaponClassOneHandThrust-4]
|
||||
_ = x[WeaponClassStaff-5]
|
||||
_ = x[WeaponClassTwoHandSwing-6]
|
||||
_ = x[WeaponClassTwoHandThrust-7]
|
||||
_ = x[WeaponClassCrossbow-8]
|
||||
_ = x[WeaponClassLeftJabRightSwing-9]
|
||||
_ = x[WeaponClassLeftJabRightThrust-10]
|
||||
_ = x[WeaponClassLeftSwingRightSwing-11]
|
||||
_ = x[WeaponClassLeftSwingRightThrust-12]
|
||||
_ = x[WeaponClassOneHandToHand-13]
|
||||
_ = x[WeaponClassTwoHandToHand-14]
|
||||
}
|
||||
|
||||
const _WeaponClass_name = "hthbow1hs1htstf2hs2htxbw1js1jt1ss1stht1ht2"
|
||||
|
||||
var _WeaponClass_index = [...]uint8{0, 0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42}
|
||||
|
||||
func (i WeaponClass) String() string {
|
||||
if i < 0 || i >= WeaponClass(len(_WeaponClass_index)-1) {
|
||||
return "WeaponClass(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _WeaponClass_name[_WeaponClass_index[i]:_WeaponClass_index[i+1]]
|
||||
}
|
18
d2common/d2enum/weapon_class_string2enum.go
Normal file
18
d2common/d2enum/weapon_class_string2enum.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Code generated by "string2enum -samepkg -linecomment -type WeaponClass"; DO NOT EDIT.
|
||||
|
||||
package d2enum
|
||||
|
||||
import "fmt"
|
||||
|
||||
// WeaponClassFromString returns the WeaponClass enum corresponding to s.
|
||||
func WeaponClassFromString(s string) WeaponClass {
|
||||
if len(s) == 0 {
|
||||
return 0
|
||||
}
|
||||
for i := range _WeaponClass_index[:len(_WeaponClass_index)-1] {
|
||||
if s == _WeaponClass_name[_WeaponClass_index[i]:_WeaponClass_index[i+1]] {
|
||||
return WeaponClass(i)
|
||||
}
|
||||
}
|
||||
panic(fmt.Errorf("unable to locate WeaponClass enum corresponding to %q", s))
|
||||
}
|
7
d2common/d2interface/file_provider.go
Normal file
7
d2common/d2interface/file_provider.go
Normal file
@ -0,0 +1,7 @@
|
||||
package d2interface
|
||||
|
||||
// FileProvider is an instance that can provide different types of files
|
||||
type FileProvider interface {
|
||||
LoadFile(fileName string) []byte
|
||||
//LoadSprite(fileName string, palette enums.PaletteType) *d2render.Sprite
|
||||
}
|
16
d2common/d2interface/inventory_item.go
Normal file
16
d2common/d2interface/inventory_item.go
Normal file
@ -0,0 +1,16 @@
|
||||
package d2interface
|
||||
|
||||
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
|
||||
type InventoryItem interface {
|
||||
// GetInventoryItemName returns the name of this inventory item
|
||||
GetInventoryItemName() string
|
||||
// GetInventoryItemType returns the type of item this is
|
||||
GetInventoryItemType() d2enum.InventoryItemType
|
||||
// GetInventoryGridSize returns the width/height grid size of this inventory item
|
||||
GetInventoryGridSize() (int, int)
|
||||
// Returns the item code
|
||||
GetItemCode() string
|
||||
// Serializes the object for transport
|
||||
Serialize() []byte
|
||||
}
|
282
d2common/d2resource/resource_paths.go
Normal file
282
d2common/d2resource/resource_paths.go
Normal file
@ -0,0 +1,282 @@
|
||||
package d2resource
|
||||
|
||||
var LanguageCode string
|
||||
|
||||
const (
|
||||
// --- Screens ---
|
||||
|
||||
LoadingScreen = "/data/global/ui/Loading/loadingscreen.dc6"
|
||||
|
||||
// --- Main Menu ---
|
||||
|
||||
TrademarkScreen = "/data/global/ui/FrontEnd/trademarkscreenEXP.dc6"
|
||||
GameSelectScreen = "/data/global/ui/FrontEnd/gameselectscreenEXP.dc6"
|
||||
Diablo2LogoFireLeft = "/data/global/ui/FrontEnd/D2logoFireLeft.DC6"
|
||||
Diablo2LogoFireRight = "/data/global/ui/FrontEnd/D2logoFireRight.DC6"
|
||||
Diablo2LogoBlackLeft = "/data/global/ui/FrontEnd/D2logoBlackLeft.DC6"
|
||||
Diablo2LogoBlackRight = "/data/global/ui/FrontEnd/D2logoBlackRight.DC6"
|
||||
|
||||
// --- Credits ---
|
||||
|
||||
CreditsBackground = "/data/global/ui/CharSelect/creditsbckgexpand.dc6"
|
||||
CreditsText = "/data/local/ui/{LANG}/ExpansionCredits.txt"
|
||||
|
||||
// --- Character Select Screen ---
|
||||
|
||||
CharacterSelectBackground = "/data/global/ui/FrontEnd/charactercreationscreenEXP.dc6"
|
||||
CharacterSelectCampfire = "/data/global/ui/FrontEnd/fire.DC6"
|
||||
|
||||
CharacterSelectBarbarianUnselected = "/data/global/ui/FrontEnd/barbarian/banu1.DC6"
|
||||
CharacterSelectBarbarianUnselectedH = "/data/global/ui/FrontEnd/barbarian/banu2.DC6"
|
||||
CharacterSelectBarbarianSelected = "/data/global/ui/FrontEnd/barbarian/banu3.DC6"
|
||||
CharacterSelectBarbarianForwardWalk = "/data/global/ui/FrontEnd/barbarian/bafw.DC6"
|
||||
CharacterSelectBarbarianForwardWalkOverlay = "/data/global/ui/FrontEnd/barbarian/BAFWs.DC6"
|
||||
CharacterSelectBarbarianBackWalk = "/data/global/ui/FrontEnd/barbarian/babw.DC6"
|
||||
|
||||
CharacterSelecSorceressUnselected = "/data/global/ui/FrontEnd/sorceress/SONU1.DC6"
|
||||
CharacterSelecSorceressUnselectedH = "/data/global/ui/FrontEnd/sorceress/SONU2.DC6"
|
||||
CharacterSelecSorceressSelected = "/data/global/ui/FrontEnd/sorceress/SONU3.DC6"
|
||||
CharacterSelecSorceressSelectedOverlay = "/data/global/ui/FrontEnd/sorceress/SONU3s.DC6"
|
||||
CharacterSelecSorceressForwardWalk = "/data/global/ui/FrontEnd/sorceress/SOFW.DC6"
|
||||
CharacterSelecSorceressForwardWalkOverlay = "/data/global/ui/FrontEnd/sorceress/SOFWs.DC6"
|
||||
CharacterSelecSorceressBackWalk = "/data/global/ui/FrontEnd/sorceress/SOBW.DC6"
|
||||
CharacterSelecSorceressBackWalkOverlay = "/data/global/ui/FrontEnd/sorceress/SOBWs.DC6"
|
||||
|
||||
CharacterSelectNecromancerUnselected = "/data/global/ui/FrontEnd/necromancer/NENU1.DC6"
|
||||
CharacterSelectNecromancerUnselectedH = "/data/global/ui/FrontEnd/necromancer/NENU2.DC6"
|
||||
CharacterSelecNecromancerSelected = "/data/global/ui/FrontEnd/necromancer/NENU3.DC6"
|
||||
CharacterSelecNecromancerSelectedOverlay = "/data/global/ui/FrontEnd/necromancer/NENU3s.DC6"
|
||||
CharacterSelecNecromancerForwardWalk = "/data/global/ui/FrontEnd/necromancer/NEFW.DC6"
|
||||
CharacterSelecNecromancerForwardWalkOverlay = "/data/global/ui/FrontEnd/necromancer/NEFWs.DC6"
|
||||
CharacterSelecNecromancerBackWalk = "/data/global/ui/FrontEnd/necromancer/NEBW.DC6"
|
||||
CharacterSelecNecromancerBackWalkOverlay = "/data/global/ui/FrontEnd/necromancer/NEBWs.DC6"
|
||||
|
||||
CharacterSelectPaladinUnselected = "/data/global/ui/FrontEnd/paladin/PANU1.DC6"
|
||||
CharacterSelectPaladinUnselectedH = "/data/global/ui/FrontEnd/paladin/PANU2.DC6"
|
||||
CharacterSelecPaladinSelected = "/data/global/ui/FrontEnd/paladin/PANU3.DC6"
|
||||
CharacterSelecPaladinForwardWalk = "/data/global/ui/FrontEnd/paladin/PAFW.DC6"
|
||||
CharacterSelecPaladinForwardWalkOverlay = "/data/global/ui/FrontEnd/paladin/PAFWs.DC6"
|
||||
CharacterSelecPaladinBackWalk = "/data/global/ui/FrontEnd/paladin/PABW.DC6"
|
||||
|
||||
CharacterSelectAmazonUnselected = "/data/global/ui/FrontEnd/amazon/AMNU1.DC6"
|
||||
CharacterSelectAmazonUnselectedH = "/data/global/ui/FrontEnd/amazon/AMNU2.DC6"
|
||||
CharacterSelecAmazonSelected = "/data/global/ui/FrontEnd/amazon/AMNU3.DC6"
|
||||
CharacterSelecAmazonForwardWalk = "/data/global/ui/FrontEnd/amazon/AMFW.DC6"
|
||||
CharacterSelecAmazonForwardWalkOverlay = "/data/global/ui/FrontEnd/amazon/AMFWs.DC6"
|
||||
CharacterSelecAmazonBackWalk = "/data/global/ui/FrontEnd/amazon/AMBW.DC6"
|
||||
|
||||
CharacterSelectAssassinUnselected = "/data/global/ui/FrontEnd/assassin/ASNU1.DC6"
|
||||
CharacterSelectAssassinUnselectedH = "/data/global/ui/FrontEnd/assassin/ASNU2.DC6"
|
||||
CharacterSelectAssassinSelected = "/data/global/ui/FrontEnd/assassin/ASNU3.DC6"
|
||||
CharacterSelectAssassinForwardWalk = "/data/global/ui/FrontEnd/assassin/ASFW.DC6"
|
||||
CharacterSelectAssassinBackWalk = "/data/global/ui/FrontEnd/assassin/ASBW.DC6"
|
||||
|
||||
CharacterSelectDruidUnselected = "/data/global/ui/FrontEnd/druid/DZNU1.dc6"
|
||||
CharacterSelectDruidUnselectedH = "/data/global/ui/FrontEnd/druid/DZNU2.dc6"
|
||||
CharacterSelectDruidSelected = "/data/global/ui/FrontEnd/druid/DZNU3.DC6"
|
||||
CharacterSelectDruidForwardWalk = "/data/global/ui/FrontEnd/druid/DZFW.DC6"
|
||||
CharacterSelectDruidBackWalk = "/data/global/ui/FrontEnd/druid/DZBW.DC6"
|
||||
|
||||
// -- Character Selection
|
||||
|
||||
CharacterSelectionBackground = "/data/global/ui/CharSelect/characterselectscreenEXP.dc6"
|
||||
CharacterSelectionSelectBox = "/data/global/ui/CharSelect/charselectbox.dc6"
|
||||
PopUpOkCancel = "/data/global/ui/FrontEnd/PopUpOKCancel.dc6"
|
||||
|
||||
// --- Game ---
|
||||
|
||||
GamePanels = "/data/global/ui/PANEL/800ctrlpnl7.dc6"
|
||||
GameGlobeOverlap = "/data/global/ui/PANEL/overlap.DC6"
|
||||
HealthMana = "/data/global/ui/PANEL/hlthmana.DC6"
|
||||
GameSmallMenuButton = "/data/global/ui/PANEL/menubutton.DC6" // TODO: Used for inventory popout
|
||||
SkillIcon = "/data/global/ui/PANEL/Skillicon.DC6" // TODO: Used for skill icon button
|
||||
AddSkillButton = "/data/global/ui/PANEL/level.DC6"
|
||||
|
||||
// --- Mouse Pointers ---
|
||||
|
||||
CursorDefault = "/data/global/ui/CURSOR/ohand.DC6"
|
||||
|
||||
// --- Fonts ---
|
||||
|
||||
Font6 = "/data/local/font/{LANG_FONT}/font6"
|
||||
Font8 = "/data/local/font/{LANG_FONT}/font8"
|
||||
Font16 = "/data/local/font/{LANG_FONT}/font16"
|
||||
Font24 = "/data/local/font/{LANG_FONT}/font24"
|
||||
Font30 = "/data/local/font/{LANG_FONT}/font30"
|
||||
Font42 = "/data/local/font/{LANG_FONT}/font42"
|
||||
FontFormal12 = "/data/local/font/{LANG_FONT}/fontformal12"
|
||||
FontFormal11 = "/data/local/font/{LANG_FONT}/fontformal11"
|
||||
FontFormal10 = "/data/local/font/{LANG_FONT}/fontformal10"
|
||||
FontExocet10 = "/data/local/font/{LANG_FONT}/fontexocet10"
|
||||
FontExocet8 = "/data/local/font/{LANG_FONT}/fontexocet8"
|
||||
FontSucker = "/data/local/font/{LANG_FONT}/ReallyTheLastSucker"
|
||||
FontRediculous = "/data/local/font/{LANG_FONT}/fontridiculous"
|
||||
|
||||
// --- UI ---
|
||||
|
||||
WideButtonBlank = "/data/global/ui/FrontEnd/WideButtonBlank.dc6"
|
||||
MediumButtonBlank = "/data/global/ui/FrontEnd/MediumButtonBlank.dc6"
|
||||
CancelButton = "/data/global/ui/FrontEnd/CancelButtonBlank.dc6"
|
||||
NarrowButtonBlank = "/data/global/ui/FrontEnd/NarrowButtonBlank.dc6"
|
||||
ShortButtonBlank = "/data/global/ui/CharSelect/ShortButtonBlank.dc6"
|
||||
TextBox2 = "/data/global/ui/FrontEnd/textbox2.dc6"
|
||||
TallButtonBlank = "/data/global/ui/CharSelect/TallButtonBlank.dc6"
|
||||
Checkbox = "/data/global/ui/FrontEnd/clickbox.dc6"
|
||||
Scrollbar = "/data/global/ui/PANEL/scrollbar.dc6"
|
||||
|
||||
// --- GAME UI ---
|
||||
|
||||
PentSpin = "/data/global/ui/CURSOR/pentspin.DC6"
|
||||
MinipanelSmall = "/data/global/ui/PANEL/minipanel_s.dc6"
|
||||
MinipanelButton = "/data/global/ui/PANEL/minipanelbtn.DC6"
|
||||
|
||||
Frame = "/data/global/ui/PANEL/800borderframe.dc6"
|
||||
InventoryCharacterPanel = "/data/global/ui/PANEL/invchar6.DC6"
|
||||
InventoryWeaponsTab = "/data/global/ui/PANEL/invchar6Tab.DC6"
|
||||
SkillsPanelAmazon = "/data/global/ui/SPELLS/skltree_a_back.DC6"
|
||||
SkillsPanelBarbarian = "/data/global/ui/SPELLS/skltree_b_back.DC6"
|
||||
SkillsPanelDruid = "/data/global/ui/SPELLS/skltree_d_back.DC6"
|
||||
SkillsPanelAssassin = "/data/global/ui/SPELLS/skltree_i_back.DC6"
|
||||
SkillsPanelNecromancer = "/data/global/ui/SPELLS/skltree_n_back.DC6"
|
||||
SkillsPanelPaladin = "/data/global/ui/SPELLS/skltree_p_back.DC6"
|
||||
SkillsPanelSorcerer = "/data/global/ui/SPELLS/skltree_s_back.DC6"
|
||||
|
||||
GenericSkills = "/data/global/ui/SPELLS/Skillicon.DC6"
|
||||
AmazonSkills = "/data/global/ui/SPELLS/AmSkillicon.DC6"
|
||||
BarbarianSkills = "/data/global/ui/SPELLS/BaSkillicon.DC6"
|
||||
DruidSkills = "/data/global/ui/SPELLS/DrSkillicon.DC6"
|
||||
AssassinSkills = "/data/global/ui/SPELLS/AsSkillicon.DC6"
|
||||
NecromancerSkills = "/data/global/ui/SPELLS/NeSkillicon.DC6"
|
||||
PaladinSkills = "/data/global/ui/SPELLS/PaSkillicon.DC6"
|
||||
SorcererSkills = "/data/global/ui/SPELLS/SoSkillicon.DC6"
|
||||
|
||||
RunButton = "/data/global/ui/PANEL/runbutton.dc6"
|
||||
MenuButton = "/data/global/ui/PANEL/menubutton.DC6"
|
||||
GoldCoinButton = "/data/global/ui/panel/goldcoinbtn.dc6"
|
||||
SquareButton = "/data/global/ui/panel/buysellbtn.dc6"
|
||||
|
||||
ArmorPlaceholder = "/data/global/ui/PANEL/inv_armor.DC6"
|
||||
BeltPlaceholder = "/data/global/ui/PANEL/inv_belt.DC6"
|
||||
BootsPlaceholder = "/data/global/ui/PANEL/inv_boots.DC6"
|
||||
HelmGlovePlaceholder = "/data/global/ui/PANEL/inv_helm_glove.DC6"
|
||||
RingAmuletPlaceholder = "/data/global/ui/PANEL/inv_ring_amulet.DC6"
|
||||
WeaponsPlaceholder = "/data/global/ui/PANEL/inv_weapons.DC6"
|
||||
|
||||
// --- Data ---
|
||||
|
||||
ExpansionStringTable = "/data/local/lng/{LANG}/expansionstring.tbl"
|
||||
StringTable = "/data/local/lng/{LANG}/string.tbl"
|
||||
PatchStringTable = "/data/local/lng/{LANG}/patchstring.tbl"
|
||||
LevelPreset = "/data/global/excel/LvlPrest.txt"
|
||||
LevelType = "/data/global/excel/LvlTypes.txt"
|
||||
ObjectType = "/data/global/excel/objtype.bin"
|
||||
LevelWarp = "/data/global/excel/LvlWarp.bin"
|
||||
LevelDetails = "/data/global/excel/Levels.bin"
|
||||
ObjectDetails = "/data/global/excel/Objects.txt"
|
||||
SoundSettings = "/data/global/excel/Sounds.txt"
|
||||
|
||||
// --- Animations ---
|
||||
|
||||
ObjectData = "/data/global/objects"
|
||||
AnimationData = "/data/global/animdata.d2"
|
||||
PlayerAnimationBase = "/data/global/CHARS"
|
||||
|
||||
// --- Inventory Data ---
|
||||
|
||||
Weapons = "/data/global/excel/weapons.txt"
|
||||
Armor = "/data/global/excel/armor.txt"
|
||||
Misc = "/data/global/excel/misc.txt"
|
||||
UniqueItems = "/data/global/excel/UniqueItems.txt"
|
||||
|
||||
// --- Character Data ---
|
||||
|
||||
Experience = "/data/global/excel/experience.txt"
|
||||
CharStats = "/data/global/excel/charstats.txt"
|
||||
|
||||
// --- Music ---
|
||||
|
||||
BGMTitle = "/data/global/music/introedit.wav"
|
||||
BGMOptions = "/data/global/music/Common/options.wav"
|
||||
BGMAct1AndarielAction = "/data/global/music/Act1/andarielaction.wav"
|
||||
BGMAct1BloodRavenResolution = "/data/global/music/Act1/bloodravenresolution.wav"
|
||||
BGMAct1Caves = "/data/global/music/Act1/caves.wav"
|
||||
BGMAct1Crypt = "/data/global/music/Act1/crypt.wav"
|
||||
BGMAct1DenOfEvilAction = "/data/global/music/Act1/denofevilaction.wav"
|
||||
BGMAct1Monastery = "/data/global/music/Act1/monastery.wav"
|
||||
BGMAct1Town1 = "/data/global/music/Act1/town1.wav"
|
||||
BGMAct1Tristram = "/data/global/music/Act1/tristram.wav"
|
||||
BGMAct1Wild = "/data/global/music/Act1/wild.wav"
|
||||
BGMAct2Desert = "/data/global/music/Act2/desert.wav"
|
||||
BGMAct2Harem = "/data/global/music/Act2/harem.wav"
|
||||
BGMAct2HoradricAction = "/data/global/music/Act2/horadricaction.wav"
|
||||
BGMAct2Lair = "/data/global/music/Act2/lair.wav"
|
||||
BGMAct2RadamentResolution = "/data/global/music/Act2/radamentresolution.wav"
|
||||
BGMAct2Sanctuary = "/data/global/music/Act2/sanctuary.wav"
|
||||
BGMAct2Sewer = "/data/global/music/Act2/sewer.wav"
|
||||
BGMAct2TaintedSunAction = "/data/global/music/Act2/taintedsunaction.wav"
|
||||
BGMAct2Tombs = "/data/global/music/Act2/tombs.wav"
|
||||
BGMAct2Town2 = "/data/global/music/Act2/town2.wav"
|
||||
BGMAct2Valley = "/data/global/music/Act2/valley.wav"
|
||||
BGMAct3Jungle = "/data/global/music/Act3/jungle.wav"
|
||||
BGMAct3Kurast = "/data/global/music/Act3/kurast.wav"
|
||||
BGMAct3KurastSewer = "/data/global/music/Act3/kurastsewer.wav"
|
||||
BGMAct3MefDeathAction = "/data/global/music/Act3/mefdeathaction.wav"
|
||||
BGMAct3OrbAction = "/data/global/music/Act3/orbaction.wav"
|
||||
BGMAct3Spider = "/data/global/music/Act3/spider.wav"
|
||||
BGMAct3Town3 = "/data/global/music/Act3/town3.wav"
|
||||
BGMAct4Diablo = "/data/global/music/Act4/diablo.wav"
|
||||
BGMAct4DiabloAction = "/data/global/music/Act4/diabloaction.wav"
|
||||
BGMAct4ForgeAction = "/data/global/music/Act4/forgeaction.wav"
|
||||
BGMAct4IzualAction = "/data/global/music/Act4/izualaction.wav"
|
||||
BGMAct4Mesa = "/data/global/music/Act4/mesa.wav"
|
||||
BGMAct4Town4 = "/data/global/music/Act4/town4.wav"
|
||||
BGMAct5Baal = "/data/global/music/Act5/baal.wav"
|
||||
BGMAct5XTown = "/data/global/music/Act5/xtown.wav"
|
||||
|
||||
// --- Sound Effects ---
|
||||
|
||||
SFXButtonClick = "cursor_button_click"
|
||||
SFXAmazonDeselect = "cursor_amazon_deselect"
|
||||
SFXAmazonSelect = "cursor_amazon_select"
|
||||
SFXAssassinDeselect = "/data/global/sfx/Cursor/intro/assassin deselect.wav"
|
||||
SFXAssassinSelect = "/data/global/sfx/Cursor/intro/assassin select.wav"
|
||||
SFXBarbarianDeselect = "cursor_barbarian_deselect"
|
||||
SFXBarbarianSelect = "cursor_barbarian_select"
|
||||
SFXDruidDeselect = "/data/global/sfx/Cursor/intro/druid deselect.wav"
|
||||
SFXDruidSelect = "/data/global/sfx/Cursor/intro/druid select.wav"
|
||||
SFXNecromancerDeselect = "cursor_necromancer_deselect"
|
||||
SFXNecromancerSelect = "cursor_necromancer_select"
|
||||
SFXPaladinDeselect = "cursor_paladin_deselect"
|
||||
SFXPaladinSelect = "cursor_paladin_select"
|
||||
SFXSorceressDeselect = "cursor_sorceress_deselect"
|
||||
SFXSorceressSelect = "cursor_sorceress_select"
|
||||
|
||||
// --- Enemy Data ---
|
||||
|
||||
MonStats = "/data/global/excel/monstats.txt"
|
||||
|
||||
// --- Skill Data ---
|
||||
|
||||
Missiles = "/data/global/excel/Missiles.txt"
|
||||
|
||||
// --- Palettes ---
|
||||
|
||||
PaletteAct1 = "/data/global/palette/act1/pal.dat"
|
||||
PaletteAct2 = "/data/global/palette/act2/pal.dat"
|
||||
PaletteAct3 = "/data/global/palette/act3/pal.dat"
|
||||
PaletteAct4 = "/data/global/palette/act4/pal.dat"
|
||||
PaletteAct5 = "/data/global/palette/act5/pal.dat"
|
||||
PaletteEndGame = "/data/global/palette/endgame/pal.dat"
|
||||
PaletteEndGame2 = "/data/global/palette/endgame2/pal.dat"
|
||||
PaletteFechar = "/data/global/palette/fechar/pal.dat"
|
||||
PaletteLoading = "/data/global/palette/loading/pal.dat"
|
||||
PaletteMenu0 = "/data/global/palette/menu0/pal.dat"
|
||||
PaletteMenu1 = "/data/global/palette/menu1/pal.dat"
|
||||
PaletteMenu2 = "/data/global/palette/menu2/pal.dat"
|
||||
PaletteMenu3 = "/data/global/palette/menu3/pal.dat"
|
||||
PaletteMenu4 = "/data/global/palette/menu4/pal.dat"
|
||||
PaletteSky = "/data/global/palette/sky/pal.dat"
|
||||
PaletteStatic = "/data/global/palette/static/pal.dat"
|
||||
PaletteTrademark = "/data/global/palette/trademark/pal.dat"
|
||||
PaletteUnits = "/data/global/palette/units/pal.dat"
|
||||
)
|
47
d2common/data_dictionary.go
Normal file
47
d2common/data_dictionary.go
Normal file
@ -0,0 +1,47 @@
|
||||
package d2common
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DataDictionary represents a data file (Excel)
|
||||
type DataDictionary struct {
|
||||
FieldNameLookup map[string]int
|
||||
Data [][]string
|
||||
}
|
||||
|
||||
func LoadDataDictionary(text string) *DataDictionary {
|
||||
result := &DataDictionary{}
|
||||
lines := strings.Split(text, "\r\n")
|
||||
fileNames := strings.Split(lines[0], "\t")
|
||||
result.FieldNameLookup = make(map[string]int)
|
||||
for i, fieldName := range fileNames {
|
||||
result.FieldNameLookup[fieldName] = i
|
||||
}
|
||||
result.Data = make([][]string, len(lines)-1)
|
||||
for i, line := range lines[1:] {
|
||||
if len(strings.TrimSpace(line)) == 0 {
|
||||
continue
|
||||
}
|
||||
values := strings.Split(line, "\t")
|
||||
if len(values) != len(result.FieldNameLookup) {
|
||||
continue
|
||||
}
|
||||
result.Data[i] = values
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *DataDictionary) GetString(fieldName string, index int) string {
|
||||
return v.Data[index][v.FieldNameLookup[fieldName]]
|
||||
}
|
||||
|
||||
func (v *DataDictionary) GetNumber(fieldName string, index int) int {
|
||||
result, err := strconv.Atoi(v.GetString(fieldName, index))
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
7
d2common/path.go
Normal file
7
d2common/path.go
Normal file
@ -0,0 +1,7 @@
|
||||
package d2common
|
||||
|
||||
type Path struct {
|
||||
X int32
|
||||
Y int32
|
||||
Action int32
|
||||
}
|
20
d2common/rectangle.go
Normal file
20
d2common/rectangle.go
Normal file
@ -0,0 +1,20 @@
|
||||
package d2common
|
||||
|
||||
type Rectangle struct {
|
||||
Left int
|
||||
Top int
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
func (v *Rectangle) Bottom() int {
|
||||
return v.Top + v.Height
|
||||
}
|
||||
|
||||
func (v *Rectangle) Right() int {
|
||||
return v.Left + v.Width
|
||||
}
|
||||
|
||||
func (v *Rectangle) IsInRect(x, y int) bool {
|
||||
return x >= v.Left && x < v.Left+v.Width && y >= v.Top && y < v.Top+v.Height
|
||||
}
|
134
d2common/stream_reader.go
Normal file
134
d2common/stream_reader.go
Normal file
@ -0,0 +1,134 @@
|
||||
package d2common
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// StreamReader allows you to read data from a byte array in various formats
|
||||
type StreamReader struct {
|
||||
data []byte
|
||||
position uint64
|
||||
}
|
||||
|
||||
// CreateStreamReader creates an instance of the stream reader
|
||||
func CreateStreamReader(source []byte) *StreamReader {
|
||||
result := &StreamReader{
|
||||
data: source,
|
||||
position: 0,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetPosition returns the current stream position
|
||||
func (v *StreamReader) GetPosition() uint64 {
|
||||
return v.position
|
||||
}
|
||||
|
||||
// GetSize returns the total size of the stream in bytes
|
||||
func (v *StreamReader) GetSize() uint64 {
|
||||
return uint64(len(v.data))
|
||||
}
|
||||
|
||||
// GetByte returns a byte from the stream
|
||||
func (v *StreamReader) GetByte() byte {
|
||||
result := v.data[v.position]
|
||||
v.position++
|
||||
return result
|
||||
}
|
||||
|
||||
// GetUInt16 returns a uint16 word from the stream
|
||||
func (v *StreamReader) GetUInt16() uint16 {
|
||||
result := uint16(v.data[v.position])
|
||||
result += uint16(v.data[v.position+1]) << 8
|
||||
v.position += 2
|
||||
return result
|
||||
}
|
||||
|
||||
// GetInt16 returns a int16 word from the stream
|
||||
func (v *StreamReader) GetInt16() int16 {
|
||||
result := (int16(v.data[v.position+1]) << uint(8)) + int16(v.data[v.position])
|
||||
v.position += 2
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *StreamReader) SetPosition(newPosition uint64) {
|
||||
v.position = newPosition
|
||||
}
|
||||
|
||||
// GetUInt32 returns a uint32 dword from the stream
|
||||
func (v *StreamReader) GetUInt32() uint32 {
|
||||
result := (uint32(v.data[v.position+3]) << uint(24)) + (uint32(v.data[v.position+2]) << uint(16)) + (uint32(v.data[v.position+1]) << uint(8)) + uint32(v.data[v.position])
|
||||
v.position += 4
|
||||
return result
|
||||
}
|
||||
|
||||
// GetInt32 returns an int32 dword from the stream
|
||||
func (v *StreamReader) GetInt32() int32 {
|
||||
result := (int32(v.data[v.position+3]) << uint(24)) + (int32(v.data[v.position+2]) << uint(16)) + (int32(v.data[v.position+1]) << uint(8)) + int32(v.data[v.position])
|
||||
v.position += 4
|
||||
return result
|
||||
}
|
||||
|
||||
// GetUint64 returns a uint64 qword from the stream
|
||||
func (v *StreamReader) GetUint64() uint64 {
|
||||
result := (uint64(v.data[v.position+7]) << uint(56)) +
|
||||
(uint64(v.data[v.position+6]) << uint(48)) +
|
||||
(uint64(v.data[v.position+5]) << uint(40)) +
|
||||
(uint64(v.data[v.position+4]) << uint(32)) +
|
||||
(uint64(v.data[v.position+3]) << uint(24)) +
|
||||
(uint64(v.data[v.position+2]) << uint(16)) +
|
||||
(uint64(v.data[v.position+1]) << uint(8)) +
|
||||
uint64(v.data[v.position])
|
||||
v.position += 8
|
||||
return result
|
||||
}
|
||||
|
||||
// GetInt64 returns a uint64 qword from the stream
|
||||
func (v *StreamReader) GetInt64() int64 {
|
||||
result := (uint64(v.data[v.position+7]) << uint(56)) +
|
||||
(uint64(v.data[v.position+6]) << uint(48)) +
|
||||
(uint64(v.data[v.position+5]) << uint(40)) +
|
||||
(uint64(v.data[v.position+4]) << uint(32)) +
|
||||
(uint64(v.data[v.position+3]) << uint(24)) +
|
||||
(uint64(v.data[v.position+2]) << uint(16)) +
|
||||
(uint64(v.data[v.position+1]) << uint(8)) +
|
||||
uint64(v.data[v.position])
|
||||
v.position += 8
|
||||
return int64(result)
|
||||
}
|
||||
|
||||
// ReadByte implements io.ByteReader
|
||||
func (v *StreamReader) ReadByte() (byte, error) {
|
||||
return v.GetByte(), nil
|
||||
}
|
||||
|
||||
// ReadBytes reads multiple bytes
|
||||
func (v *StreamReader) ReadBytes(count int) ([]byte, error) {
|
||||
result := make([]byte, count)
|
||||
for i := 0; i < count; i++ {
|
||||
result[i] = v.GetByte()
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (v *StreamReader) SkipBytes(count int) {
|
||||
v.position += uint64(count)
|
||||
}
|
||||
|
||||
// Read implements io.Reader
|
||||
func (v *StreamReader) Read(p []byte) (n int, err error) {
|
||||
streamLength := v.GetSize()
|
||||
for i := 0; ; i++ {
|
||||
if v.GetPosition() >= streamLength {
|
||||
return i, io.EOF
|
||||
}
|
||||
if i >= len(p) {
|
||||
return i, nil
|
||||
}
|
||||
p[i] = v.GetByte()
|
||||
}
|
||||
}
|
||||
|
||||
func (v *StreamReader) Eof() bool {
|
||||
return v.position >= uint64(len(v.data))
|
||||
}
|
56
d2common/stream_reader_test.go
Normal file
56
d2common/stream_reader_test.go
Normal file
@ -0,0 +1,56 @@
|
||||
package d2common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStreamReaderByte(t *testing.T) {
|
||||
data := []byte{0x78, 0x56, 0x34, 0x12}
|
||||
sr := CreateStreamReader(data)
|
||||
if sr.GetPosition() != 0 {
|
||||
t.Fatal("StreamReader.GetPosition() did not start at 0")
|
||||
}
|
||||
if ss := sr.GetSize(); ss != 4 {
|
||||
t.Fatalf("StreamREader.GetSize() was expected to return %d, but returned %d instead", 4, ss)
|
||||
}
|
||||
for i := 0; i < len(data); i++ {
|
||||
ret := sr.GetByte()
|
||||
if ret != data[i] {
|
||||
t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", data[i], ret)
|
||||
}
|
||||
if pos := sr.GetPosition(); pos != uint64(i+1) {
|
||||
t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", i, pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamReaderWord(t *testing.T) {
|
||||
data := []byte{0x78, 0x56, 0x34, 0x12}
|
||||
sr := CreateStreamReader(data)
|
||||
ret := sr.GetUInt16()
|
||||
if ret != 0x5678 {
|
||||
t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x5678, ret)
|
||||
}
|
||||
if pos := sr.GetPosition(); pos != 2 {
|
||||
t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 2, pos)
|
||||
}
|
||||
ret = sr.GetUInt16()
|
||||
if ret != 0x1234 {
|
||||
t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x1234, ret)
|
||||
}
|
||||
if pos := sr.GetPosition(); pos != 4 {
|
||||
t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 4, pos)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamReaderDword(t *testing.T) {
|
||||
data := []byte{0x78, 0x56, 0x34, 0x12}
|
||||
sr := CreateStreamReader(data)
|
||||
ret := sr.GetUInt32()
|
||||
if ret != 0x12345678 {
|
||||
t.Fatalf("StreamReader.GetDword() was expected to return %X, but returned %X instead", 0x12345678, ret)
|
||||
}
|
||||
if pos := sr.GetPosition(); pos != 4 {
|
||||
t.Fatalf("StreamReader.GetPosition() should be at %d, but was at %d instead", 4, pos)
|
||||
}
|
||||
}
|
69
d2common/stream_writer.go
Normal file
69
d2common/stream_writer.go
Normal file
@ -0,0 +1,69 @@
|
||||
package d2common
|
||||
|
||||
// StreamWriter allows you to create a byte array by streaming in writes of various sizes
|
||||
type StreamWriter struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
// CreateStreamWriter creates a new StreamWriter instance
|
||||
func CreateStreamWriter() *StreamWriter {
|
||||
result := &StreamWriter{
|
||||
data: make([]byte, 0),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// PushByte writes a byte to the stream
|
||||
func (v *StreamWriter) PushByte(val byte) {
|
||||
v.data = append(v.data, val)
|
||||
}
|
||||
|
||||
// PushUint16 writes an uint16 word to the stream
|
||||
func (v *StreamWriter) PushUint16(val uint16) {
|
||||
v.data = append(v.data, byte(val&0xFF))
|
||||
v.data = append(v.data, byte((val>>8)&0xFF))
|
||||
}
|
||||
|
||||
// PushInt16 writes a int16 word to the stream
|
||||
func (v *StreamWriter) PushInt16(val int16) {
|
||||
v.data = append(v.data, byte(val&0xFF))
|
||||
v.data = append(v.data, byte((val>>8)&0xFF))
|
||||
}
|
||||
|
||||
// PushUint32 writes a uint32 dword to the stream
|
||||
func (v *StreamWriter) PushUint32(val uint32) {
|
||||
v.data = append(v.data, byte(val&0xFF))
|
||||
v.data = append(v.data, byte((val>>8)&0xFF))
|
||||
v.data = append(v.data, byte((val>>16)&0xFF))
|
||||
v.data = append(v.data, byte((val>>24)&0xFF))
|
||||
}
|
||||
|
||||
// PushUint64 writes a uint64 qword to the stream
|
||||
func (v *StreamWriter) PushUint64(val uint64) {
|
||||
v.data = append(v.data, byte(val&0xFF))
|
||||
v.data = append(v.data, byte((val>>8)&0xFF))
|
||||
v.data = append(v.data, byte((val>>16)&0xFF))
|
||||
v.data = append(v.data, byte((val>>24)&0xFF))
|
||||
v.data = append(v.data, byte((val>>32)&0xFF))
|
||||
v.data = append(v.data, byte((val>>40)&0xFF))
|
||||
v.data = append(v.data, byte((val>>48)&0xFF))
|
||||
v.data = append(v.data, byte((val>>56)&0xFF))
|
||||
}
|
||||
|
||||
// PushInt64 writes a uint64 qword to the stream
|
||||
func (v *StreamWriter) PushInt64(val int64) {
|
||||
result := uint64(val)
|
||||
v.data = append(v.data, byte(result&0xFF))
|
||||
v.data = append(v.data, byte((result>>8)&0xFF))
|
||||
v.data = append(v.data, byte((result>>16)&0xFF))
|
||||
v.data = append(v.data, byte((result>>24)&0xFF))
|
||||
v.data = append(v.data, byte((result>>32)&0xFF))
|
||||
v.data = append(v.data, byte((result>>40)&0xFF))
|
||||
v.data = append(v.data, byte((result>>48)&0xFF))
|
||||
v.data = append(v.data, byte((result>>56)&0xFF))
|
||||
}
|
||||
|
||||
// GetBytes returns the the byte slice of the underlying data
|
||||
func (v *StreamWriter) GetBytes() []byte {
|
||||
return v.data
|
||||
}
|
44
d2common/stream_writer_test.go
Normal file
44
d2common/stream_writer_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
package d2common
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStreamWriterByte(t *testing.T) {
|
||||
sr := CreateStreamWriter()
|
||||
data := []byte{0x12, 0x34, 0x56, 0x78}
|
||||
for _, d := range data {
|
||||
sr.PushByte(d)
|
||||
}
|
||||
output := sr.GetBytes()
|
||||
for i, d := range data {
|
||||
if output[i] != d {
|
||||
t.Fatalf("sr.PushByte() pushed %X, but wrote %X instead", d, output[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamWriterWord(t *testing.T) {
|
||||
sr := CreateStreamWriter()
|
||||
data := []byte{0x12, 0x34, 0x56, 0x78}
|
||||
sr.PushUint16(0x3412)
|
||||
sr.PushUint16(0x7856)
|
||||
output := sr.GetBytes()
|
||||
for i, d := range data {
|
||||
if output[i] != d {
|
||||
t.Fatalf("sr.PushWord() pushed byte %X to %d, but %X was expected instead", output[i], i, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamWriterDword(t *testing.T) {
|
||||
sr := CreateStreamWriter()
|
||||
data := []byte{0x12, 0x34, 0x56, 0x78}
|
||||
sr.PushUint32(0x78563412)
|
||||
output := sr.GetBytes()
|
||||
for i, d := range data {
|
||||
if output[i] != d {
|
||||
t.Fatalf("sr.PushDword() pushed byte %X to %d, but %X was expected instead", output[i], i, d)
|
||||
}
|
||||
}
|
||||
}
|
110
d2common/text_dictionary.go
Normal file
110
d2common/text_dictionary.go
Normal file
@ -0,0 +1,110 @@
|
||||
package d2common
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
)
|
||||
|
||||
type textDictionaryHashEntry struct {
|
||||
IsActive bool
|
||||
Index uint16
|
||||
HashValue uint32
|
||||
IndexString uint32
|
||||
NameString uint32
|
||||
NameLength uint16
|
||||
}
|
||||
|
||||
var lookupTable map[string]string
|
||||
|
||||
func TranslateString(key string) string {
|
||||
result, ok := lookupTable[key]
|
||||
if !ok {
|
||||
log.Panicf("Could not find a string for the key '%s'", key)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func LoadTextDictionary(fileProvider d2interface.FileProvider) {
|
||||
lookupTable = make(map[string]string)
|
||||
loadDictionary(fileProvider, d2resource.PatchStringTable)
|
||||
loadDictionary(fileProvider, d2resource.ExpansionStringTable)
|
||||
loadDictionary(fileProvider, d2resource.StringTable)
|
||||
log.Printf("Loaded %d entries from the string table", len(lookupTable))
|
||||
}
|
||||
|
||||
func loadDictionary(fileProvider d2interface.FileProvider, dictionaryName string) {
|
||||
dictionaryData := fileProvider.LoadFile(dictionaryName)
|
||||
br := CreateStreamReader(dictionaryData)
|
||||
// CRC
|
||||
if _, err := br.ReadBytes(2); err != nil {
|
||||
log.Fatal("Error reading CRC")
|
||||
}
|
||||
numberOfElements := br.GetUInt16()
|
||||
hashTableSize := br.GetUInt32()
|
||||
// Version (always 0)
|
||||
if _, err := br.ReadByte(); err != nil {
|
||||
log.Fatal("Error reading Version record")
|
||||
}
|
||||
br.GetUInt32() // StringOffset
|
||||
br.GetUInt32() // When the number of times you have missed a match with a hash key equals this value, you give up because it is not there.
|
||||
br.GetUInt32() // FileSize
|
||||
|
||||
elementIndex := make([]uint16, numberOfElements)
|
||||
for i := 0; i < int(numberOfElements); i++ {
|
||||
elementIndex[i] = br.GetUInt16()
|
||||
}
|
||||
|
||||
hashEntries := make([]textDictionaryHashEntry, hashTableSize)
|
||||
for i := 0; i < int(hashTableSize); i++ {
|
||||
hashEntries[i] = textDictionaryHashEntry{
|
||||
br.GetByte() == 1,
|
||||
br.GetUInt16(),
|
||||
br.GetUInt32(),
|
||||
br.GetUInt32(),
|
||||
br.GetUInt32(),
|
||||
br.GetUInt16(),
|
||||
}
|
||||
}
|
||||
|
||||
for idx, hashEntry := range hashEntries {
|
||||
if !hashEntry.IsActive {
|
||||
continue
|
||||
}
|
||||
br.SetPosition(uint64(hashEntry.NameString))
|
||||
nameVal, _ := br.ReadBytes(int(hashEntry.NameLength - 1))
|
||||
value := string(nameVal)
|
||||
br.SetPosition(uint64(hashEntry.IndexString))
|
||||
key := ""
|
||||
for {
|
||||
b := br.GetByte()
|
||||
if b == 0 {
|
||||
break
|
||||
}
|
||||
key += string(b)
|
||||
}
|
||||
if key == "x" || key == "X" {
|
||||
key = "#" + strconv.Itoa(idx)
|
||||
}
|
||||
_, exists := lookupTable[key]
|
||||
if !exists {
|
||||
lookupTable[key] = value
|
||||
|
||||
}
|
||||
// Use the following code to write out the values
|
||||
/*
|
||||
f, err := os.OpenFile(`C:\Users\lunat\Desktop\D2\langdict.txt`,
|
||||
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := f.WriteString("\n[" + key + "] " + value); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package d2scene
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2video"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2video"
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
)
|
||||
|
||||
|
@ -6,12 +6,13 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||
dh "github.com/OpenDiablo2/D2Shared/d2helper"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
|
||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
||||
|
@ -8,16 +8,17 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||
dh "github.com/OpenDiablo2/D2Shared/d2helper"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
||||
)
|
||||
|
@ -3,8 +3,8 @@ package d2scene
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
|
||||
|
@ -10,14 +10,14 @@ import (
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
||||
)
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
|
@ -6,16 +6,17 @@ import (
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||
dh "github.com/OpenDiablo2/D2Shared/d2helper"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui"
|
||||
)
|
||||
|
@ -6,9 +6,9 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2helper"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2input"
|
||||
@ -16,13 +16,14 @@ import (
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2data"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2audio"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2corecommon"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
|
@ -9,9 +9,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
)
|
||||
|
||||
/*
|
||||
|
@ -1,9 +1,9 @@
|
||||
package d2core
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package d2core
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
)
|
||||
|
||||
var HeroObjects map[d2enum.Hero]CharacterEquipment
|
||||
|
@ -3,8 +3,8 @@ package d2core
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
)
|
||||
|
||||
type InventoryItemArmor struct {
|
||||
|
@ -3,8 +3,8 @@ package d2core
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
)
|
||||
|
||||
type InventoryItemWeapon struct {
|
||||
|
@ -1,9 +1,10 @@
|
||||
package d2core
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/D2Shared/d2common"
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/D2Shared/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
)
|
||||
|
@ -100,7 +100,7 @@ func getDefaultConfiguration() *Configuration {
|
||||
"d2xmusic.mpq",
|
||||
"d2xtalk.mpq",
|
||||
"d2xvideo.mpq",
|
||||
"d2data.mpq",
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data.mpq",
|
||||
"d2char.mpq",
|
||||
"d2music.mpq",
|
||||
"d2sfx.mpq",
|
||||
|
52
d2data/animation_data.go
Normal file
52
d2data/animation_data.go
Normal file
@ -0,0 +1,52 @@
|
||||
package d2data
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
)
|
||||
|
||||
// AnimationDataRecord represents a single entry in the animation data dictionary file
|
||||
type AnimationDataRecord struct {
|
||||
// COFName is the name of the COF file used for this animation
|
||||
COFName string
|
||||
// FramesPerDirection specifies how many frames are in each direction
|
||||
FramesPerDirection int
|
||||
// AnimationSpeed represents a value of X where the rate is a ration of (x/255) at 25FPS
|
||||
AnimationSpeed int
|
||||
// Flags are used in keyframe triggers
|
||||
Flags []byte
|
||||
}
|
||||
|
||||
// AnimationData represents all of the animation data records, mapped by the COF index
|
||||
var AnimationData map[string][]*AnimationDataRecord
|
||||
|
||||
// LoadAnimationData loads the animation data table into the global AnimationData dictionary
|
||||
func LoadAnimationData(fileProvider d2interface.FileProvider) {
|
||||
AnimationData = make(map[string][]*AnimationDataRecord)
|
||||
rawData := fileProvider.LoadFile(d2resource.AnimationData)
|
||||
streamReader := d2common.CreateStreamReader(rawData)
|
||||
for !streamReader.Eof() {
|
||||
dataCount := int(streamReader.GetInt32())
|
||||
for i := 0; i < dataCount; i++ {
|
||||
cofNameBytes, _ := streamReader.ReadBytes(8)
|
||||
data := &AnimationDataRecord{
|
||||
COFName: strings.ReplaceAll(string(cofNameBytes), string(0), ""),
|
||||
FramesPerDirection: int(streamReader.GetInt32()),
|
||||
AnimationSpeed: int(streamReader.GetInt32()),
|
||||
}
|
||||
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))
|
||||
}
|
65
d2data/d2cof/cof.go
Normal file
65
d2data/d2cof/cof.go
Normal file
@ -0,0 +1,65 @@
|
||||
package d2cof
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
)
|
||||
|
||||
type COF struct {
|
||||
NumberOfDirections int
|
||||
FramesPerDirection int
|
||||
NumberOfLayers int
|
||||
Speed int
|
||||
CofLayers []CofLayer
|
||||
CompositeLayers map[d2enum.CompositeType]int
|
||||
AnimationFrames []d2enum.AnimationFrame
|
||||
Priority [][][]d2enum.CompositeType
|
||||
}
|
||||
|
||||
func LoadCOF(fileData []byte) (*COF, error) {
|
||||
result := &COF{}
|
||||
streamReader := d2common.CreateStreamReader(fileData)
|
||||
result.NumberOfLayers = int(streamReader.GetByte())
|
||||
result.FramesPerDirection = int(streamReader.GetByte())
|
||||
result.NumberOfDirections = int(streamReader.GetByte())
|
||||
streamReader.SkipBytes(21) // Skip 21 unknown bytes...
|
||||
result.Speed = int(streamReader.GetByte())
|
||||
streamReader.SkipBytes(3)
|
||||
result.CofLayers = make([]CofLayer, result.NumberOfLayers)
|
||||
result.CompositeLayers = make(map[d2enum.CompositeType]int, 0)
|
||||
for i := 0; i < result.NumberOfLayers; i++ {
|
||||
layer := CofLayer{}
|
||||
layer.Type = d2enum.CompositeType(streamReader.GetByte())
|
||||
layer.Shadow = streamReader.GetByte()
|
||||
layer.Selectable = streamReader.GetByte() != 0
|
||||
layer.Transparent = streamReader.GetByte() != 0
|
||||
layer.DrawEffect = d2enum.DrawEffect(streamReader.GetByte())
|
||||
weaponClassStr, _ := streamReader.ReadBytes(4)
|
||||
layer.WeaponClass = d2enum.WeaponClassFromString(strings.TrimSpace(strings.ReplaceAll(string(weaponClassStr), string(0), "")))
|
||||
result.CofLayers[i] = layer
|
||||
result.CompositeLayers[layer.Type] = i
|
||||
}
|
||||
animationFrameBytes, _ := streamReader.ReadBytes(result.FramesPerDirection)
|
||||
result.AnimationFrames = make([]d2enum.AnimationFrame, result.FramesPerDirection)
|
||||
for i := range animationFrameBytes {
|
||||
result.AnimationFrames[i] = d2enum.AnimationFrame(animationFrameBytes[i])
|
||||
}
|
||||
priorityLen := result.FramesPerDirection * result.NumberOfDirections * result.NumberOfLayers
|
||||
result.Priority = make([][][]d2enum.CompositeType, result.NumberOfDirections)
|
||||
priorityBytes, _ := streamReader.ReadBytes(priorityLen)
|
||||
priorityIndex := 0
|
||||
for direction := 0; direction < result.NumberOfDirections; direction++ {
|
||||
result.Priority[direction] = make([][]d2enum.CompositeType, result.FramesPerDirection)
|
||||
for frame := 0; frame < result.FramesPerDirection; frame++ {
|
||||
result.Priority[direction][frame] = make([]d2enum.CompositeType, result.NumberOfLayers)
|
||||
for i := 0; i < result.NumberOfLayers; i++ {
|
||||
result.Priority[direction][frame][i] = d2enum.CompositeType(priorityBytes[priorityIndex])
|
||||
priorityIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
12
d2data/d2cof/coflayer.go
Normal file
12
d2data/d2cof/coflayer.go
Normal file
@ -0,0 +1,12 @@
|
||||
package d2cof
|
||||
|
||||
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
|
||||
type CofLayer struct {
|
||||
Type d2enum.CompositeType
|
||||
Shadow byte
|
||||
Selectable bool
|
||||
Transparent bool
|
||||
DrawEffect d2enum.DrawEffect
|
||||
WeaponClass d2enum.WeaponClass
|
||||
}
|
371
d2data/d2compression/huffman.go
Normal file
371
d2data/d2compression/huffman.go
Normal file
@ -0,0 +1,371 @@
|
||||
//
|
||||
// MpqHuffman.go based on the origina CS file
|
||||
//
|
||||
// Authors:
|
||||
// Foole (fooleau@gmail.com)
|
||||
// Tim Sarbin (tim.sarbin@gmail.com) (go translation)
|
||||
//
|
||||
// (C) 2006 Foole (fooleau@gmail.com)
|
||||
// Based on code from StormLib by Ladislav Zezula and ShadowFlare
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// 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"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func CreateLinkedNode(decompVal, weight int) *linkedNode {
|
||||
result := &linkedNode{
|
||||
DecompressedValue: decompVal,
|
||||
Weight: weight,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
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 {
|
||||
// insert before
|
||||
if v.Next != nil {
|
||||
v.Next.Prev = other
|
||||
other.Next = v.Next
|
||||
}
|
||||
v.Next = other
|
||||
other.Prev = v
|
||||
return other
|
||||
}
|
||||
if v.Prev == nil {
|
||||
// Insert after
|
||||
other.Prev = nil
|
||||
v.Prev = other
|
||||
other.Next = v
|
||||
} else {
|
||||
v.Prev.Insert(other)
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
var sPrime = [][]byte{
|
||||
{ // 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,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
}, { // 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,
|
||||
0x0D, 0x07, 0x09, 0x06, 0x06, 0x04, 0x03, 0x02, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02,
|
||||
0x09, 0x06, 0x04, 0x04, 0x04, 0x04, 0x03, 0x02, 0x03, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x04,
|
||||
0x08, 0x03, 0x04, 0x07, 0x09, 0x05, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
|
||||
0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x02,
|
||||
0x06, 0x0A, 0x08, 0x08, 0x06, 0x07, 0x04, 0x03, 0x04, 0x04, 0x02, 0x02, 0x04, 0x02, 0x03, 0x03,
|
||||
0x04, 0x03, 0x07, 0x07, 0x09, 0x06, 0x04, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02,
|
||||
0x0A, 0x02, 0x02, 0x03, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x06, 0x03, 0x05, 0x02, 0x03,
|
||||
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x03, 0x01, 0x01, 0x01,
|
||||
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x04, 0x04, 0x04, 0x07, 0x09, 0x08, 0x0C, 0x02,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x03,
|
||||
0x04, 0x01, 0x02, 0x04, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
|
||||
0x04, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x02, 0x02, 0x02, 0x06, 0x4B,
|
||||
}, { // Compression type 2
|
||||
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,
|
||||
0x06, 0x08, 0x05, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, 0x01, 0x01, 0x02, 0x01, 0x01,
|
||||
0x01, 0x04, 0x02, 0x04, 0x02, 0x02, 0x02, 0x01, 0x01, 0x04, 0x01, 0x01, 0x02, 0x03, 0x03, 0x02,
|
||||
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
|
||||
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,
|
||||
0x02, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01,
|
||||
0x0A, 0x04, 0x02, 0x01, 0x06, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x01,
|
||||
0x05, 0x02, 0x03, 0x04, 0x03, 0x03, 0x03, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x02, 0x03, 0x03,
|
||||
0x01, 0x03, 0x01, 0x01, 0x02, 0x05, 0x01, 0x01, 0x04, 0x03, 0x05, 0x01, 0x03, 0x01, 0x03, 0x03,
|
||||
0x02, 0x01, 0x04, 0x03, 0x0A, 0x06, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x02, 0x02, 0x01, 0x0A, 0x02, 0x05, 0x01, 0x01, 0x02, 0x07, 0x02, 0x17, 0x01, 0x05, 0x01, 0x01,
|
||||
0x0E, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x06, 0x02, 0x01, 0x04, 0x05, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x07, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01,
|
||||
0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x11,
|
||||
}, { // Compression type 4
|
||||
0xFF, 0xFB, 0x98, 0x9A, 0x84, 0x85, 0x63, 0x64, 0x3E, 0x3E, 0x22, 0x22, 0x13, 0x13, 0x18, 0x17,
|
||||
}, { // Compression type 5
|
||||
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
|
||||
0xC3, 0xCB, 0xF5, 0x41, 0xFF, 0x7B, 0xF7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xBF, 0xCC, 0xF2, 0x40, 0xFD, 0x7C, 0xF7, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x7A, 0x46,
|
||||
}, { // 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,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xBD, 0xD9, 0xEC, 0x3D, 0xF5, 0x7D, 0xE8, 0x1D, 0xFB, 0xAE, 0xF0, 0x2C, 0xFB, 0x5C, 0xFF, 0x18,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x70, 0x6C,
|
||||
}, { // Compression type 8
|
||||
0xBA, 0xC5, 0xDA, 0x33, 0xE3, 0x6D, 0xD8, 0x18, 0xE5, 0x94, 0xDA, 0x23, 0xDF, 0x4A, 0xD1, 0x10,
|
||||
0xEE, 0xAF, 0xE4, 0x2C, 0xEA, 0x5A, 0xDE, 0x15, 0xF4, 0x87, 0xE9, 0x21, 0xF6, 0x43, 0xFC, 0x12,
|
||||
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,
|
||||
0xB0, 0xC7, 0xD8, 0x33, 0xE3, 0x6B, 0xD6, 0x18, 0xE7, 0x95, 0xD8, 0x23, 0xDB, 0x49, 0xD0, 0x11,
|
||||
0xE9, 0xB2, 0xE2, 0x2B, 0xE8, 0x5C, 0xDD, 0x15, 0xF1, 0x87, 0xE7, 0x20, 0xF7, 0x44, 0xFF, 0x13,
|
||||
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,
|
||||
0x5F, 0x9E,
|
||||
},
|
||||
}
|
||||
|
||||
func decode(input *d2common.BitStream, head *linkedNode) *linkedNode {
|
||||
node := head
|
||||
|
||||
for node.Child0 != nil {
|
||||
bit := input.ReadBits(1)
|
||||
if bit == -1 {
|
||||
log.Fatal("unexpected end of file")
|
||||
}
|
||||
if bit == 0 {
|
||||
node = node.Child0
|
||||
continue
|
||||
}
|
||||
node = node.GetChild1()
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func buildList(primeData []byte) *linkedNode {
|
||||
root := CreateLinkedNode(256, 1)
|
||||
root = root.Insert(CreateLinkedNode(257, 1))
|
||||
|
||||
for i := 0; i < len(primeData); i++ {
|
||||
if primeData[i] != 0 {
|
||||
root = root.Insert(CreateLinkedNode(i, int(primeData[i])))
|
||||
}
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func insertNode(tail *linkedNode, decomp int) *linkedNode {
|
||||
parent := tail
|
||||
result := tail.Prev // This will be the new tail after the tree is updated
|
||||
|
||||
temp := CreateLinkedNode(parent.DecompressedValue, parent.Weight)
|
||||
temp.Parent = parent
|
||||
|
||||
newnode := CreateLinkedNode(decomp, 0)
|
||||
newnode.Parent = parent
|
||||
|
||||
parent.Child0 = newnode
|
||||
|
||||
tail.Next = temp
|
||||
temp.Prev = tail
|
||||
newnode.Prev = temp
|
||||
temp.Next = newnode
|
||||
|
||||
adjustTree(newnode)
|
||||
// TODO: For compression type 0, AdjustTree should be called
|
||||
// once for every value written and only once here
|
||||
adjustTree(newnode)
|
||||
return result
|
||||
}
|
||||
|
||||
// This increases the weight of the new node and its antecendants
|
||||
// and adjusts the tree if needed
|
||||
func adjustTree(newNode *linkedNode) {
|
||||
current := newNode
|
||||
|
||||
for current != nil {
|
||||
current.Weight++
|
||||
var insertpoint *linkedNode
|
||||
var prev *linkedNode
|
||||
// Go backwards thru the list looking for the insertion point
|
||||
insertpoint = current
|
||||
for true {
|
||||
prev = insertpoint.Prev
|
||||
if prev == nil {
|
||||
break
|
||||
}
|
||||
if prev.Weight >= current.Weight {
|
||||
break
|
||||
}
|
||||
insertpoint = prev
|
||||
}
|
||||
|
||||
// No insertion point found
|
||||
if insertpoint == current {
|
||||
current = current.Parent
|
||||
continue
|
||||
}
|
||||
|
||||
// The following code basicly swaps insertpoint with current
|
||||
|
||||
// remove insert point
|
||||
if insertpoint.Prev != nil {
|
||||
insertpoint.Prev.Next = insertpoint.Next
|
||||
}
|
||||
insertpoint.Next.Prev = insertpoint.Prev
|
||||
|
||||
// Insert insertpoint after current
|
||||
insertpoint.Next = current.Next
|
||||
insertpoint.Prev = current
|
||||
if current.Next != nil {
|
||||
current.Next.Prev = insertpoint
|
||||
}
|
||||
current.Next = insertpoint
|
||||
|
||||
// remove current
|
||||
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
|
||||
|
||||
// Set up parent/child links
|
||||
currentparent := current.Parent
|
||||
insertparent := insertpoint.Parent
|
||||
|
||||
if currentparent.Child0 == current {
|
||||
currentparent.Child0 = insertpoint
|
||||
}
|
||||
|
||||
if currentparent != insertparent && insertparent.Child0 == insertpoint {
|
||||
insertparent.Child0 = current
|
||||
}
|
||||
|
||||
current.Parent = insertparent
|
||||
insertpoint.Parent = currentparent
|
||||
|
||||
current = current.Parent
|
||||
}
|
||||
}
|
||||
|
||||
func buildTree(tail *linkedNode) *linkedNode {
|
||||
current := tail
|
||||
|
||||
for current != nil {
|
||||
child0 := current
|
||||
child1 := current.Prev
|
||||
if child1 == nil {
|
||||
break
|
||||
}
|
||||
|
||||
parent := CreateLinkedNode(0, child0.Weight+child1.Weight)
|
||||
parent.Child0 = child0
|
||||
child0.Parent = parent
|
||||
child1.Parent = parent
|
||||
|
||||
current.Insert(parent)
|
||||
current = current.Prev.Prev
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
func HuffmanDecompress(data []byte) []byte {
|
||||
comptype := data[0]
|
||||
|
||||
if comptype == 0 {
|
||||
log.Panic("compression type 0 is not currently supported")
|
||||
}
|
||||
|
||||
tail := buildList(sPrime[comptype])
|
||||
head := buildTree(tail)
|
||||
|
||||
outputstream := d2common.CreateStreamWriter()
|
||||
bitstream := d2common.CreateBitStream(data[1:])
|
||||
var decoded int
|
||||
for true {
|
||||
node := decode(bitstream, head)
|
||||
decoded = node.DecompressedValue
|
||||
switch decoded {
|
||||
case 256:
|
||||
break
|
||||
case 257:
|
||||
newvalue := bitstream.ReadBits(8)
|
||||
outputstream.PushByte(byte(newvalue))
|
||||
tail = insertNode(tail, newvalue)
|
||||
break
|
||||
default:
|
||||
outputstream.PushByte(byte(decoded))
|
||||
break
|
||||
}
|
||||
if decoded == 256 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return outputstream.GetBytes()
|
||||
}
|
131
d2data/d2compression/wav.go
Normal file
131
d2data/d2compression/wav.go
Normal file
@ -0,0 +1,131 @@
|
||||
package d2compression
|
||||
|
||||
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 {
|
||||
Array1 := []int{0x2c, 0x2c}
|
||||
Array2 := make([]int, channelCount)
|
||||
|
||||
input := d2common.CreateStreamReader(data)
|
||||
output := d2common.CreateStreamWriter()
|
||||
input.GetByte()
|
||||
|
||||
shift := input.GetByte()
|
||||
|
||||
for i := 0; i < channelCount; i++ {
|
||||
temp := input.GetInt16()
|
||||
Array2[i] = int(temp)
|
||||
output.PushInt16(temp)
|
||||
}
|
||||
|
||||
channel := channelCount - 1
|
||||
for input.GetPosition() < input.GetSize() {
|
||||
value := input.GetByte()
|
||||
|
||||
if channelCount == 2 {
|
||||
channel = 1 - channel
|
||||
}
|
||||
|
||||
if (value & 0x80) != 0 {
|
||||
switch value & 0x7f {
|
||||
case 0:
|
||||
if Array1[channel] != 0 {
|
||||
Array1[channel]--
|
||||
}
|
||||
output.PushInt16(int16(Array2[channel]))
|
||||
break
|
||||
case 1:
|
||||
Array1[channel] += 8
|
||||
if Array1[channel] > 0x58 {
|
||||
Array1[channel] = 0x58
|
||||
}
|
||||
if channelCount == 2 {
|
||||
channel = 1 - channel
|
||||
}
|
||||
break
|
||||
case 2:
|
||||
break
|
||||
default:
|
||||
Array1[channel] -= 8
|
||||
if Array1[channel] < 0 {
|
||||
Array1[channel] = 0
|
||||
}
|
||||
if channelCount == 2 {
|
||||
channel = 1 - channel
|
||||
}
|
||||
break
|
||||
}
|
||||
} else {
|
||||
temp1 := sLookup[Array1[channel]]
|
||||
temp2 := temp1 >> shift
|
||||
|
||||
if (value & 1) != 0 {
|
||||
temp2 += temp1 >> 0
|
||||
}
|
||||
if (value & 2) != 0 {
|
||||
temp2 += temp1 >> 1
|
||||
}
|
||||
if (value & 4) != 0 {
|
||||
temp2 += temp1 >> 2
|
||||
}
|
||||
if (value & 8) != 0 {
|
||||
temp2 += temp1 >> 3
|
||||
}
|
||||
if (value & 0x10) != 0 {
|
||||
temp2 += temp1 >> 4
|
||||
}
|
||||
if (value & 0x20) != 0 {
|
||||
temp2 += temp1 >> 5
|
||||
}
|
||||
|
||||
temp3 := Array2[channel]
|
||||
if (value & 0x40) != 0 {
|
||||
temp3 -= temp2
|
||||
if temp3 <= -32768 {
|
||||
temp3 = -32768
|
||||
}
|
||||
} else {
|
||||
temp3 += temp2
|
||||
if temp3 >= 32767 {
|
||||
temp3 = 32767
|
||||
}
|
||||
}
|
||||
Array2[channel] = temp3
|
||||
output.PushInt16(int16(temp3))
|
||||
Array1[channel] += sLookup2[value&0x1f]
|
||||
|
||||
if Array1[channel] < 0 {
|
||||
Array1[channel] = 0
|
||||
} else {
|
||||
if Array1[channel] > 0x58 {
|
||||
Array1[channel] = 0x58
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return output.GetBytes()
|
||||
}
|
18
d2data/d2datadict/armor.go
Normal file
18
d2data/d2datadict/armor.go
Normal file
@ -0,0 +1,18 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
)
|
||||
|
||||
var Armors map[string]*ItemCommonRecord
|
||||
|
||||
func LoadArmors(fileProvider d2interface.FileProvider) {
|
||||
Armors = *LoadCommonItems(fileProvider, d2resource.Armor, d2enum.InventoryItemTypeArmor)
|
||||
log.Printf("Loaded %d armors", len(Armors))
|
||||
}
|
416
d2data/d2datadict/item_common.go
Normal file
416
d2data/d2datadict/item_common.go
Normal file
@ -0,0 +1,416 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
// 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
|
||||
AnimLeftArm int
|
||||
AnimTorso int
|
||||
AnimLegs int
|
||||
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?
|
||||
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type ItemUsageStat struct {
|
||||
Stat string // name of the stat to add to
|
||||
Calc d2common.CalcString // calc string representing the amount to add
|
||||
}
|
||||
|
||||
type ItemVendorParams struct {
|
||||
Min int // minimum of this item they can stock
|
||||
Max int // max they can stock
|
||||
MagicMin int
|
||||
MagicMax int
|
||||
MagicLevel uint8
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Loading Functions
|
||||
var CommonItems map[string]*ItemCommonRecord
|
||||
|
||||
func LoadCommonItems(fileProvider d2interface.FileProvider, filepath string, source d2enum.InventoryItemType) *map[string]*ItemCommonRecord {
|
||||
if CommonItems == nil {
|
||||
CommonItems = make(map[string]*ItemCommonRecord)
|
||||
}
|
||||
items := make(map[string]*ItemCommonRecord)
|
||||
data := strings.Split(string(fileProvider.LoadFile(filepath)), "\r\n")
|
||||
mapping := MapHeaders(data[0])
|
||||
for lineno, line := range data {
|
||||
if lineno == 0 {
|
||||
continue
|
||||
}
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
rec := createCommonItemRecord(line, &mapping, source)
|
||||
items[rec.Code] = &rec
|
||||
CommonItems[rec.Code] = &rec
|
||||
}
|
||||
return &items
|
||||
}
|
||||
|
||||
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"),
|
||||
|
||||
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"),
|
||||
|
||||
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"),
|
||||
|
||||
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"),
|
||||
|
||||
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"),
|
||||
|
||||
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"),
|
||||
|
||||
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"),
|
||||
|
||||
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"),
|
||||
|
||||
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"),
|
||||
|
||||
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"),
|
||||
|
||||
SkipName: MapLoadBool(&r, mapping, "SkipName"),
|
||||
NightmareUpgrade: MapLoadString(&r, mapping, "NightmareUpgrade"),
|
||||
HellUpgrade: MapLoadString(&r, mapping, "HellUpgrade"),
|
||||
|
||||
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"),
|
||||
|
||||
RequiredDexterity: MapLoadInt(&r, mapping, "reqdex"),
|
||||
|
||||
WeaponClass: MapLoadString(&r, mapping, "wclass"),
|
||||
WeaponClass2Hand: MapLoadString(&r, mapping, "2handedwclass"),
|
||||
|
||||
HitClass: MapLoadString(&r, mapping, "hit class"),
|
||||
SpawnStack: MapLoadInt(&r, mapping, "spawnstack"),
|
||||
|
||||
SpecialFeature: MapLoadString(&r, mapping, "special"),
|
||||
|
||||
QuestDifficultyCheck: MapLoadBool(&r, mapping, "questdiffcheck"),
|
||||
|
||||
PermStoreItem: MapLoadBool(&r, mapping, "PermStoreItem"),
|
||||
|
||||
// misc params
|
||||
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"),
|
||||
|
||||
AutoBelt: MapLoadBool(&r, mapping, "autobelt"),
|
||||
|
||||
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"),
|
||||
},
|
||||
EffectLength: MapLoadInt(&r, mapping, "len"),
|
||||
UsageStats: createItemUsageStats(&r, mapping),
|
||||
|
||||
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")),
|
||||
|
||||
BetterGem: MapLoadString(&r, mapping, "BetterGem"),
|
||||
|
||||
Multibuy: MapLoadBool(&r, mapping, "multibuy"),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func createItemVendorParams(r *[]string, mapping *map[string]int) map[string]*ItemVendorParams {
|
||||
vs := make([]string, 17)
|
||||
vs[0] = "Charsi"
|
||||
vs[1] = "Gheed"
|
||||
vs[2] = "Akara"
|
||||
vs[3] = "Fara"
|
||||
vs[4] = "Lysander"
|
||||
vs[5] = "Drognan"
|
||||
vs[6] = "Hralti"
|
||||
vs[7] = "Alkor"
|
||||
vs[8] = "Ormus"
|
||||
vs[9] = "Elzix"
|
||||
vs[10] = "Asheara"
|
||||
vs[11] = "Cain"
|
||||
vs[12] = "Halbu"
|
||||
vs[13] = "Jamella"
|
||||
vs[14] = "Larzuk"
|
||||
vs[15] = "Malah"
|
||||
vs[16] = "Drehya"
|
||||
|
||||
result := make(map[string]*ItemVendorParams)
|
||||
|
||||
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"),
|
||||
}
|
||||
result[name] = &wvp
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
return result
|
||||
}
|
93
d2data/d2datadict/level_presets.go
Normal file
93
d2data/d2datadict/level_presets.go
Normal file
@ -0,0 +1,93 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
|
||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
)
|
||||
|
||||
type LevelPresetRecord struct {
|
||||
Name string
|
||||
DefinitionId int
|
||||
LevelId int
|
||||
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
|
||||
}
|
||||
|
||||
// CreateLevelPresetRecord parses a row from lvlprest.txt into a LevelPresetRecord
|
||||
func createLevelPresetRecord(props []string) LevelPresetRecord {
|
||||
i := -1
|
||||
inc := func() int {
|
||||
i++
|
||||
return i
|
||||
}
|
||||
result := LevelPresetRecord{
|
||||
Name: props[inc()],
|
||||
DefinitionId: dh.StringToInt(props[inc()]),
|
||||
LevelId: dh.StringToInt(props[inc()]),
|
||||
Populate: dh.StringToUint8(props[inc()]) == 1,
|
||||
Logicals: dh.StringToUint8(props[inc()]) == 1,
|
||||
Outdoors: dh.StringToUint8(props[inc()]) == 1,
|
||||
Animate: dh.StringToUint8(props[inc()]) == 1,
|
||||
KillEdge: dh.StringToUint8(props[inc()]) == 1,
|
||||
FillBlanks: dh.StringToUint8(props[inc()]) == 1,
|
||||
SizeX: dh.StringToInt(props[inc()]),
|
||||
SizeY: dh.StringToInt(props[inc()]),
|
||||
AutoMap: dh.StringToUint8(props[inc()]) == 1,
|
||||
Scan: dh.StringToUint8(props[inc()]) == 1,
|
||||
Pops: dh.StringToInt(props[inc()]),
|
||||
PopPad: dh.StringToInt(props[inc()]),
|
||||
FileCount: dh.StringToInt(props[inc()]),
|
||||
Files: [6]string{
|
||||
props[inc()],
|
||||
props[inc()],
|
||||
props[inc()],
|
||||
props[inc()],
|
||||
props[inc()],
|
||||
props[inc()],
|
||||
},
|
||||
Dt1Mask: dh.StringToUint(props[inc()]),
|
||||
Beta: dh.StringToUint8(props[inc()]) == 1,
|
||||
Expansion: dh.StringToUint8(props[inc()]) == 1,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var LevelPresets map[int]LevelPresetRecord
|
||||
|
||||
func LoadLevelPresets(fileProvider d2interface.FileProvider) {
|
||||
LevelPresets = make(map[int]LevelPresetRecord)
|
||||
data := strings.Split(string(fileProvider.LoadFile(d2resource.LevelPreset)), "\r\n")[1:]
|
||||
for _, line := range data {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
props := strings.Split(line, "\t")
|
||||
if props[1] == "" {
|
||||
continue // any line without a definition id is skipped (e.g. the "Expansion" line)
|
||||
}
|
||||
rec := createLevelPresetRecord(props)
|
||||
LevelPresets[rec.DefinitionId] = rec
|
||||
}
|
||||
log.Printf("Loaded %d level presets", len(LevelPresets))
|
||||
}
|
56
d2data/d2datadict/level_types.go
Normal file
56
d2data/d2datadict/level_types.go
Normal file
@ -0,0 +1,56 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
|
||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
)
|
||||
|
||||
type LevelTypeRecord struct {
|
||||
Name string
|
||||
Id int
|
||||
Files [32]string
|
||||
Beta bool
|
||||
Act int
|
||||
Expansion bool
|
||||
}
|
||||
|
||||
var LevelTypes []LevelTypeRecord
|
||||
|
||||
func LoadLevelTypes(fileProvider d2interface.FileProvider) {
|
||||
data := strings.Split(string(fileProvider.LoadFile(d2resource.LevelType)), "\r\n")[1:]
|
||||
LevelTypes = make([]LevelTypeRecord, len(data))
|
||||
for i, j := 0, 0; i < len(data); i, j = i+1, j+1 {
|
||||
idx := -1
|
||||
inc := func() int {
|
||||
idx++
|
||||
return idx
|
||||
}
|
||||
if len(data[i]) == 0 {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(data[i], "\t")
|
||||
if parts[0] == "Expansion" {
|
||||
j--
|
||||
continue
|
||||
}
|
||||
LevelTypes[j].Name = parts[inc()]
|
||||
LevelTypes[j].Id = dh.StringToInt(parts[inc()])
|
||||
for fileIdx := range LevelTypes[i].Files {
|
||||
LevelTypes[j].Files[fileIdx] = parts[inc()]
|
||||
if LevelTypes[j].Files[fileIdx] == "0" {
|
||||
LevelTypes[j].Files[fileIdx] = ""
|
||||
}
|
||||
|
||||
}
|
||||
LevelTypes[j].Beta = parts[inc()] != "1"
|
||||
LevelTypes[j].Act = dh.StringToInt(parts[inc()])
|
||||
LevelTypes[j].Expansion = parts[inc()] != "1"
|
||||
}
|
||||
log.Printf("Loaded %d LevelType records", len(LevelTypes))
|
||||
}
|
53
d2data/d2datadict/level_warp.go
Normal file
53
d2data/d2datadict/level_warp.go
Normal file
@ -0,0 +1,53 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
)
|
||||
|
||||
type LevelWarpRecord struct {
|
||||
Id int32
|
||||
SelectX int32
|
||||
SelectY int32
|
||||
SelectDX int32
|
||||
SelectDY int32
|
||||
ExitWalkX int32
|
||||
ExitWalkY int32
|
||||
OffsetX int32
|
||||
OffsetY int32
|
||||
LitVersion bool
|
||||
Tiles int32
|
||||
Direction string
|
||||
}
|
||||
|
||||
var LevelWarps map[int]*LevelWarpRecord
|
||||
|
||||
func LoadLevelWarps(fileProvider d2interface.FileProvider) {
|
||||
LevelWarps = make(map[int]*LevelWarpRecord)
|
||||
levelWarpData := fileProvider.LoadFile(d2resource.LevelWarp)
|
||||
streamReader := d2common.CreateStreamReader(levelWarpData)
|
||||
numRecords := int(streamReader.GetInt32())
|
||||
for i := 0; i < numRecords; i++ {
|
||||
id := int(streamReader.GetInt32())
|
||||
LevelWarps[id] = &LevelWarpRecord{}
|
||||
LevelWarps[id].Id = int32(id)
|
||||
LevelWarps[id].SelectX = streamReader.GetInt32()
|
||||
LevelWarps[id].SelectY = streamReader.GetInt32()
|
||||
LevelWarps[id].SelectDX = streamReader.GetInt32()
|
||||
LevelWarps[id].SelectDY = streamReader.GetInt32()
|
||||
LevelWarps[id].ExitWalkX = streamReader.GetInt32()
|
||||
LevelWarps[id].ExitWalkY = streamReader.GetInt32()
|
||||
LevelWarps[id].OffsetX = streamReader.GetInt32()
|
||||
LevelWarps[id].OffsetY = streamReader.GetInt32()
|
||||
LevelWarps[id].LitVersion = streamReader.GetInt32() == 1
|
||||
LevelWarps[id].Tiles = streamReader.GetInt32()
|
||||
LevelWarps[id].Direction = string(streamReader.GetByte())
|
||||
streamReader.SkipBytes(3)
|
||||
}
|
||||
log.Printf("Loaded %d level warps", len(LevelWarps))
|
||||
}
|
43
d2data/d2datadict/map_helper.go
Normal file
43
d2data/d2datadict/map_helper.go
Normal file
@ -0,0 +1,43 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MapHeaders(line string) map[string]int {
|
||||
m := make(map[string]int)
|
||||
r := strings.Split(line, "\t")
|
||||
for index, header := range r {
|
||||
m[header] = index
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func MapLoadInt(r *[]string, mapping *map[string]int, field string) int {
|
||||
index, ok := (*mapping)[field]
|
||||
if ok {
|
||||
return dh.StringToInt(dh.EmptyToZero(dh.AsterToEmpty((*r)[index])))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func MapLoadString(r *[]string, mapping *map[string]int, field string) string {
|
||||
index, ok := (*mapping)[field]
|
||||
if ok {
|
||||
return dh.AsterToEmpty((*r)[index])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func MapLoadBool(r *[]string, mapping *map[string]int, field string) bool {
|
||||
return MapLoadInt(r, mapping, field) == 1
|
||||
}
|
||||
|
||||
func MapLoadUint8(r *[]string, mapping *map[string]int, field string) uint8 {
|
||||
index, ok := (*mapping)[field]
|
||||
if ok {
|
||||
return dh.StringToUint8(dh.EmptyToZero(dh.AsterToEmpty((*r)[index])))
|
||||
}
|
||||
return 0
|
||||
}
|
18
d2data/d2datadict/misc.go
Normal file
18
d2data/d2datadict/misc.go
Normal file
@ -0,0 +1,18 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
)
|
||||
|
||||
var MiscItems map[string]*ItemCommonRecord
|
||||
|
||||
func LoadMiscItems(fileProvider d2interface.FileProvider) {
|
||||
MiscItems = *LoadCommonItems(fileProvider, d2resource.Misc, d2enum.InventoryItemTypeItem)
|
||||
log.Printf("Loaded %d misc items", len(MiscItems))
|
||||
}
|
411
d2data/d2datadict/missiles.go
Normal file
411
d2data/d2datadict/missiles.go
Normal file
@ -0,0 +1,411 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
|
||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
)
|
||||
|
||||
type MissileCalcParam struct {
|
||||
Param int
|
||||
Desc string
|
||||
}
|
||||
|
||||
type MissileCalc struct {
|
||||
Calc d2common.CalcString
|
||||
Desc string
|
||||
Params []MissileCalcParam
|
||||
}
|
||||
|
||||
type MissileLight struct {
|
||||
Diameter int
|
||||
Flicker int
|
||||
Red uint8
|
||||
Green uint8
|
||||
Blue uint8
|
||||
}
|
||||
|
||||
type MissileAnimation struct {
|
||||
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
|
||||
SubStartingFrame int
|
||||
SubEndingFrame int
|
||||
}
|
||||
|
||||
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
|
||||
DestroyedUponCollision bool
|
||||
FriendlyFire bool
|
||||
LastCollide bool // unknown
|
||||
Collision bool // unknown
|
||||
ClientCollision bool // unknown
|
||||
ClientSend bool // unclear
|
||||
UseCollisionTimer bool // after hit, use timer before dying
|
||||
TimerFrames int // how many frames to persist
|
||||
}
|
||||
|
||||
type MissileDamage struct {
|
||||
MinDamage int
|
||||
MaxDamage int
|
||||
MinLevelDamage [5]int // additional damage per missile level
|
||||
// [0]: lvs 2-8, [1]: lvs 9-16, [2]: lvs 17-22, [3]: lvs 23-28, [4]: lv 29+
|
||||
MaxLevelDamage [5]int // see above
|
||||
DamageSynergyPerCalc d2common.CalcString // works like synergy in skills.txt, not clear
|
||||
}
|
||||
|
||||
type MissileElementalDamage struct {
|
||||
Damage MissileDamage
|
||||
ElementType string
|
||||
Duration int // frames, 25 = 1 second
|
||||
LevelDuration [3]int // 0,1,2, unknown level intervals, bonus duration per level
|
||||
}
|
||||
|
||||
type MissileRecord struct {
|
||||
Name string
|
||||
Id int
|
||||
|
||||
ClientMovementFunc int
|
||||
ClientCollisionFunc int
|
||||
ServerMovementFunc int
|
||||
ServerCollisionFunc int
|
||||
ServerDamageFunc int
|
||||
ServerMovementCalc MissileCalc
|
||||
ClientMovementCalc MissileCalc
|
||||
ServerCollisionCalc MissileCalc
|
||||
ClientCollisionCalc MissileCalc
|
||||
ServerDamageCalc MissileCalc
|
||||
|
||||
Velocity int
|
||||
MaxVelocity int
|
||||
LevelVelocityBonus int
|
||||
Accel int
|
||||
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
|
||||
|
||||
UseAttackRating bool // if true, uses 'attack rating' to determine if it hits or misses
|
||||
// if false, has a 95% chance to hit.
|
||||
AlwaysExplode bool // if true, always calls its collision function when it is destroyed, even if it doesn't hit anything
|
||||
// note that some collision functions (lightning fury) seem to ignore this and always explode regardless of setting (requires investigation)
|
||||
|
||||
ClientExplosion bool // if true, does not really exist
|
||||
// is only aesthetic / client side
|
||||
TownSafe bool // if true, doesn't vanish when spawned in town
|
||||
// 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"
|
||||
|
||||
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 {
|
||||
r := strings.Split(line, "\t")
|
||||
i := -1
|
||||
inc := func() int {
|
||||
i++
|
||||
return i
|
||||
}
|
||||
// note: in this file, empties are equivalent to zero, so all numerical conversions should
|
||||
// be wrapped in an dh.EmptyToZero transform
|
||||
result := MissileRecord{
|
||||
Name: r[inc()],
|
||||
Id: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
|
||||
ClientMovementFunc: dh.StringToInt(dh.EmptyToZero(dh.AsterToEmpty(r[inc()]))),
|
||||
ClientCollisionFunc: dh.StringToInt(dh.EmptyToZero(dh.AsterToEmpty(r[inc()]))),
|
||||
ServerMovementFunc: dh.StringToInt(dh.EmptyToZero(dh.AsterToEmpty(r[inc()]))),
|
||||
ServerCollisionFunc: dh.StringToInt(dh.EmptyToZero(dh.AsterToEmpty(r[inc()]))),
|
||||
ServerDamageFunc: dh.StringToInt(dh.EmptyToZero(dh.AsterToEmpty(r[inc()]))),
|
||||
|
||||
ServerMovementCalc: loadMissileCalc(&r, inc, 5),
|
||||
ClientMovementCalc: loadMissileCalc(&r, inc, 5),
|
||||
ServerCollisionCalc: loadMissileCalc(&r, inc, 3),
|
||||
ClientCollisionCalc: loadMissileCalc(&r, inc, 3),
|
||||
ServerDamageCalc: loadMissileCalc(&r, inc, 2),
|
||||
|
||||
Velocity: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
MaxVelocity: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
LevelVelocityBonus: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
Accel: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
Range: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
LevelRangeBonus: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
|
||||
Light: loadMissileLight(&r, inc),
|
||||
|
||||
Animation: loadMissileAnimation(&r, inc),
|
||||
|
||||
Collision: loadMissileCollision(&r, inc),
|
||||
|
||||
XOffset: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
YOffset: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
ZOffset: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
Size: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
|
||||
DestroyedByTP: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
DestroyedByTPFrame: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
CanDestroy: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
|
||||
UseAttackRating: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
AlwaysExplode: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
|
||||
ClientExplosion: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
TownSafe: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
IgnoreBossModifiers: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
IgnoreMultishot: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
HolyFilterType: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
CanBeSlowed: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
TriggersHitEvents: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
TriggersGetHit: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
SoftHit: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
KnockbackPercent: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
|
||||
TransparencyMode: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
|
||||
UseQuantity: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
AffectedByPierce: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
SpecialSetup: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
|
||||
MissileSkill: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
SkillName: r[inc()],
|
||||
|
||||
ResultFlags: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
HitFlags: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
|
||||
HitShift: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
ApplyMastery: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
SourceDamage: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
HalfDamageForTwoHander: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
SourceMissDamage: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
|
||||
Damage: loadMissileDamage(&r, inc),
|
||||
ElementalDamage: loadMissileElementalDamage(&r, inc),
|
||||
|
||||
HitClass: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
NumDirections: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
LocalBlood: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
DamageReductionRate: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
|
||||
TravelSound: r[inc()],
|
||||
HitSound: r[inc()],
|
||||
ProgSound: r[inc()],
|
||||
ProgOverlay: r[inc()],
|
||||
ExplosionMissile: r[inc()],
|
||||
|
||||
SubMissile: [3]string{r[inc()], r[inc()], r[inc()]},
|
||||
HitSubMissile: [4]string{r[inc()], r[inc()], r[inc()], r[inc()]},
|
||||
ClientSubMissile: [3]string{r[inc()], r[inc()], r[inc()]},
|
||||
ClientHitSubMissile: [4]string{r[inc()], r[inc()], r[inc()], r[inc()]},
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var Missiles map[int]*MissileRecord
|
||||
|
||||
func LoadMissiles(fileProvider d2interface.FileProvider) {
|
||||
Missiles = make(map[int]*MissileRecord)
|
||||
data := strings.Split(string(fileProvider.LoadFile(d2resource.Missiles)), "\r\n")[1:]
|
||||
for _, line := range data {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
rec := createMissileRecord(line)
|
||||
Missiles[rec.Id] = &rec
|
||||
}
|
||||
log.Printf("Loaded %d missiles", len(Missiles))
|
||||
}
|
||||
|
||||
func loadMissileCalcParam(r *[]string, inc func() int) MissileCalcParam {
|
||||
result := MissileCalcParam{
|
||||
Param: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
Desc: (*r)[inc()],
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func loadMissileCalc(r *[]string, inc func() int, params int) MissileCalc {
|
||||
result := MissileCalc{
|
||||
Calc: d2common.CalcString((*r)[inc()]),
|
||||
Desc: (*r)[inc()],
|
||||
}
|
||||
result.Params = make([]MissileCalcParam, params)
|
||||
for p := 0; p < params; p++ {
|
||||
result.Params[p] = loadMissileCalcParam(r, inc)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func loadMissileLight(r *[]string, inc func() int) MissileLight {
|
||||
result := MissileLight{
|
||||
Diameter: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
Flicker: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
Red: dh.StringToUint8(dh.EmptyToZero((*r)[inc()])),
|
||||
Green: dh.StringToUint8(dh.EmptyToZero((*r)[inc()])),
|
||||
Blue: dh.StringToUint8(dh.EmptyToZero((*r)[inc()])),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func loadMissileAnimation(r *[]string, inc func() int) MissileAnimation {
|
||||
result := MissileAnimation{
|
||||
StepsBeforeVisible: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
StepsBeforeActive: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
LoopAnimation: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
|
||||
CelFileName: (*r)[inc()],
|
||||
AnimationRate: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
AnimationLength: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
AnimationSpeed: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
StartingFrame: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
HasSubLoop: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
|
||||
SubStartingFrame: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
SubEndingFrame: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func loadMissileCollision(r *[]string, inc func() int) MissileCollision {
|
||||
result := MissileCollision{
|
||||
CollisionType: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
DestroyedUponCollision: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
|
||||
FriendlyFire: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
|
||||
LastCollide: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
|
||||
Collision: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
|
||||
ClientCollision: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
|
||||
ClientSend: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
|
||||
UseCollisionTimer: dh.StringToInt(dh.EmptyToZero((*r)[inc()])) == 1,
|
||||
TimerFrames: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func loadMissileDamage(r *[]string, inc func() int) MissileDamage {
|
||||
result := MissileDamage{
|
||||
MinDamage: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
MinLevelDamage: [5]int{
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
},
|
||||
MaxDamage: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
MaxLevelDamage: [5]int{
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
},
|
||||
DamageSynergyPerCalc: d2common.CalcString((*r)[inc()]),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func loadMissileElementalDamage(r *[]string, inc func() int) MissileElementalDamage {
|
||||
result := MissileElementalDamage{
|
||||
ElementType: (*r)[inc()],
|
||||
Damage: loadMissileDamage(r, inc),
|
||||
Duration: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
LevelDuration: [3]int{
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
},
|
||||
}
|
||||
return result
|
||||
}
|
13
d2data/d2datadict/monstats.go
Normal file
13
d2data/d2datadict/monstats.go
Normal file
@ -0,0 +1,13 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
)
|
||||
|
||||
var MonStatsDictionary *d2common.DataDictionary
|
||||
|
||||
func LoadMonStats(fileProvider d2interface.FileProvider) {
|
||||
MonStatsDictionary = d2common.LoadDataDictionary(string(fileProvider.LoadFile(d2resource.MonStats)))
|
||||
}
|
7896
d2data/d2datadict/object_lookup.go
Normal file
7896
d2data/d2datadict/object_lookup.go
Normal file
File diff suppressed because it is too large
Load Diff
90
d2data/d2datadict/object_query.go
Normal file
90
d2data/d2datadict/object_query.go
Normal file
@ -0,0 +1,90 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
type ObjectType int
|
||||
|
||||
const (
|
||||
ObjectTypeCharacter ObjectType = 1
|
||||
ObjectTypeItem ObjectType = 2
|
||||
)
|
||||
|
||||
type ObjectLookupRecord struct {
|
||||
Act int
|
||||
Type ObjectType
|
||||
Id int
|
||||
Description string
|
||||
ObjectsTxtId int
|
||||
MonstatsTxtId int
|
||||
Direction int
|
||||
Base string
|
||||
Token string
|
||||
Mode string
|
||||
Class string
|
||||
HD string
|
||||
TR string
|
||||
LG string
|
||||
RA string
|
||||
LA string
|
||||
RH string
|
||||
LH string
|
||||
SH string
|
||||
S1 string
|
||||
S2 string
|
||||
S3 string
|
||||
S4 string
|
||||
S5 string
|
||||
S6 string
|
||||
S7 string
|
||||
S8 string
|
||||
ColorMap string
|
||||
Index int
|
||||
}
|
||||
|
||||
func LookupObject(act, typ, id int) *ObjectLookupRecord {
|
||||
object := lookupObject(act, typ, id, indexedObjects)
|
||||
if object == nil {
|
||||
log.Panicf("Failed to look up object Act: %d, Type: %d, Id: %d", act, typ, id)
|
||||
}
|
||||
return object
|
||||
}
|
||||
|
||||
func lookupObject(act, typ, id int, objects [][][]*ObjectLookupRecord) *ObjectLookupRecord {
|
||||
if objects[act] != nil && objects[act][typ] != nil && objects[act][typ][id] != nil {
|
||||
return objects[act][typ][id]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
indexedObjects = indexObjects(objectLookups)
|
||||
}
|
||||
|
||||
func indexObjects(objects []ObjectLookupRecord) [][][]*ObjectLookupRecord {
|
||||
// Allocating 6 to allow Acts 1-5 without requiring a -1 at every read.
|
||||
indexedObjects = make([][][]*ObjectLookupRecord, 6)
|
||||
for i, _ := range objects {
|
||||
record := &objects[i]
|
||||
if indexedObjects[record.Act] == nil {
|
||||
// Likewise allocating 3 so a -1 isn't necessary.
|
||||
indexedObjects[record.Act] = make([][]*ObjectLookupRecord, 3)
|
||||
}
|
||||
|
||||
if indexedObjects[record.Act][record.Type] == nil {
|
||||
// For simplicity, allocating with length 1000 then filling the values in by index.
|
||||
// If ids in the dictionary ever surpass 1000, raise this number.
|
||||
indexedObjects[record.Act][record.Type] = make([]*ObjectLookupRecord, 1000)
|
||||
}
|
||||
|
||||
indexedObjects[record.Act][record.Type][record.Id] = record
|
||||
}
|
||||
|
||||
return indexedObjects
|
||||
}
|
||||
|
||||
// Indexed 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]
|
||||
var indexedObjects [][][]*ObjectLookupRecord
|
40
d2data/d2datadict/object_query_test.go
Normal file
40
d2data/d2datadict/object_query_test.go
Normal file
@ -0,0 +1,40 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
testify "github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Verify the lookup returns the right object after indexing.
|
||||
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"},
|
||||
}
|
||||
|
||||
indexedTestObjects := indexObjects(testObjects)
|
||||
|
||||
typeCharacter := int(ObjectTypeCharacter)
|
||||
typeItem := int(ObjectTypeItem)
|
||||
|
||||
assert.Equal("Act1CharId2", lookupObject(1, typeCharacter, 2, indexedTestObjects).Description)
|
||||
assert.Equal("Act1ItemId0", lookupObject(1, typeItem, 0, indexedTestObjects).Description)
|
||||
assert.Equal("Act2CharId3", lookupObject(2, typeCharacter, 3, indexedTestObjects).Description)
|
||||
assert.Equal("Act2ItemId1", lookupObject(2, typeItem, 1, indexedTestObjects).Description)
|
||||
}
|
35
d2data/d2datadict/object_types.go
Normal file
35
d2data/d2datadict/object_types.go
Normal file
@ -0,0 +1,35 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
)
|
||||
|
||||
type ObjectTypeRecord struct {
|
||||
Name string
|
||||
Token string
|
||||
}
|
||||
|
||||
var ObjectTypes []ObjectTypeRecord
|
||||
|
||||
func LoadObjectTypes(fileProvider d2interface.FileProvider) {
|
||||
objectTypeData := fileProvider.LoadFile(d2resource.ObjectType)
|
||||
streamReader := d2common.CreateStreamReader(objectTypeData)
|
||||
count := streamReader.GetInt32()
|
||||
ObjectTypes = make([]ObjectTypeRecord, count)
|
||||
for i := range ObjectTypes {
|
||||
nameBytes, _ := streamReader.ReadBytes(32)
|
||||
tokenBytes, _ := streamReader.ReadBytes(20)
|
||||
ObjectTypes[i] = ObjectTypeRecord{
|
||||
Name: strings.TrimSpace(strings.ReplaceAll(string(nameBytes), string(0), "")),
|
||||
Token: strings.TrimSpace(strings.ReplaceAll(string(tokenBytes), string(0), "")),
|
||||
}
|
||||
}
|
||||
log.Printf("Loaded %d object types", len(ObjectTypes))
|
||||
}
|
357
d2data/d2datadict/objects.go
Normal file
357
d2data/d2datadict/objects.go
Normal file
@ -0,0 +1,357 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
|
||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
)
|
||||
|
||||
// An ObjectRecord represents the settings for one type of object from objects.txt
|
||||
type ObjectRecord struct {
|
||||
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
|
||||
|
||||
SizeX int
|
||||
SizeY int
|
||||
|
||||
NTgtFX int // unknown
|
||||
NTgtFY int // unknown
|
||||
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
|
||||
|
||||
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:
|
||||
// 1 = shrine
|
||||
// 2 = obelisk
|
||||
// 4 = portal
|
||||
// 8 = container
|
||||
// 16 = arcane sanctuary gateway
|
||||
// 32 = well
|
||||
// 64 = waypoint
|
||||
// 128 = secret jails door
|
||||
|
||||
XSpace int // unknown
|
||||
YSpace int
|
||||
|
||||
NameOffset int // pixels to offset the name from the animation pivot
|
||||
|
||||
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
|
||||
|
||||
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?
|
||||
|
||||
Left int // unknown, clickable bounding box?
|
||||
Top int
|
||||
Width int
|
||||
Height int
|
||||
|
||||
OperateFn int // what function is called when the player clicks on the object
|
||||
// (todo: we should enumerate all the functions somewhere, but probably not here
|
||||
// b/c it's a very long list)
|
||||
PopulateFn int // what function is used to spawn this object?
|
||||
// (see above todo)
|
||||
InitFn int // what function is run when the object is initialized?
|
||||
// (see above todo)
|
||||
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
|
||||
}
|
||||
|
||||
// CreateObjectRecord parses a row from objects.txt into an object record
|
||||
func createObjectRecord(props []string) ObjectRecord {
|
||||
i := -1
|
||||
inc := func() int {
|
||||
i++
|
||||
return i
|
||||
}
|
||||
result := ObjectRecord{
|
||||
Name: props[inc()],
|
||||
Description: props[inc()],
|
||||
Id: dh.StringToInt(props[inc()]),
|
||||
Token: props[inc()],
|
||||
|
||||
SpawnMax: dh.StringToInt(props[inc()]),
|
||||
Selectable: [8]bool{
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
},
|
||||
TrapProbability: dh.StringToInt(props[inc()]),
|
||||
|
||||
SizeX: dh.StringToInt(props[inc()]),
|
||||
SizeY: dh.StringToInt(props[inc()]),
|
||||
|
||||
NTgtFX: dh.StringToInt(props[inc()]),
|
||||
NTgtFY: dh.StringToInt(props[inc()]),
|
||||
NTgtBX: dh.StringToInt(props[inc()]),
|
||||
NTgtBY: dh.StringToInt(props[inc()]),
|
||||
|
||||
FrameCount: [8]int{
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
},
|
||||
FrameDelta: [8]int{
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
},
|
||||
CycleAnimation: [8]bool{
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
},
|
||||
LightDiameter: [8]int{
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
},
|
||||
BlocksLight: [8]bool{
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
},
|
||||
HasCollision: [8]bool{
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
},
|
||||
IsAttackable: dh.StringToUint8(props[inc()]) == 1,
|
||||
StartFrame: [8]int{
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
},
|
||||
|
||||
EnvEffect: dh.StringToUint8(props[inc()]) == 1,
|
||||
IsDoor: dh.StringToUint8(props[inc()]) == 1,
|
||||
BlockVisibility: dh.StringToUint8(props[inc()]) == 1,
|
||||
Orientation: dh.StringToInt(props[inc()]),
|
||||
Trans: dh.StringToInt(props[inc()]),
|
||||
|
||||
OrderFlag: [8]int{
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
},
|
||||
PreOperate: dh.StringToUint8(props[inc()]) == 1,
|
||||
HasAnimationMode: [8]bool{
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
},
|
||||
|
||||
XOffset: dh.StringToInt(props[inc()]),
|
||||
YOffset: dh.StringToInt(props[inc()]),
|
||||
Draw: dh.StringToUint8(props[inc()]) == 1,
|
||||
|
||||
LightRed: dh.StringToUint8(props[inc()]),
|
||||
LightGreen: dh.StringToUint8(props[inc()]),
|
||||
LightBlue: dh.StringToUint8(props[inc()]),
|
||||
|
||||
SelHD: dh.StringToUint8(props[inc()]) == 1,
|
||||
SelTR: dh.StringToUint8(props[inc()]) == 1,
|
||||
SelLG: dh.StringToUint8(props[inc()]) == 1,
|
||||
SelRA: dh.StringToUint8(props[inc()]) == 1,
|
||||
SelLA: dh.StringToUint8(props[inc()]) == 1,
|
||||
SelRH: dh.StringToUint8(props[inc()]) == 1,
|
||||
SelLH: dh.StringToUint8(props[inc()]) == 1,
|
||||
SelSH: dh.StringToUint8(props[inc()]) == 1,
|
||||
SelS: [8]bool{
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
dh.StringToUint8(props[inc()]) == 1,
|
||||
},
|
||||
|
||||
TotalPieces: dh.StringToInt(props[inc()]),
|
||||
SubClass: dh.StringToInt(props[inc()]),
|
||||
|
||||
XSpace: dh.StringToInt(props[inc()]),
|
||||
YSpace: dh.StringToInt(props[inc()]),
|
||||
|
||||
NameOffset: dh.StringToInt(props[inc()]),
|
||||
|
||||
MonsterOk: dh.StringToUint8(props[inc()]) == 1,
|
||||
OperateRange: dh.StringToInt(props[inc()]),
|
||||
ShrineFunction: dh.StringToInt(props[inc()]),
|
||||
Restore: dh.StringToUint8(props[inc()]) == 1,
|
||||
|
||||
Parm: [8]int{
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
dh.StringToInt(props[inc()]),
|
||||
},
|
||||
Act: dh.StringToInt(props[inc()]),
|
||||
Lockable: dh.StringToUint8(props[inc()]) == 1,
|
||||
Gore: dh.StringToUint8(props[inc()]) == 1,
|
||||
Sync: dh.StringToUint8(props[inc()]) == 1,
|
||||
Flicker: dh.StringToUint8(props[inc()]) == 1,
|
||||
Damage: dh.StringToInt(props[inc()]),
|
||||
Beta: dh.StringToUint8(props[inc()]) == 1,
|
||||
Overlay: dh.StringToUint8(props[inc()]) == 1,
|
||||
CollisionSubst: dh.StringToUint8(props[inc()]) == 1,
|
||||
|
||||
Left: dh.StringToInt(props[inc()]),
|
||||
Top: dh.StringToInt(props[inc()]),
|
||||
Width: dh.StringToInt(props[inc()]),
|
||||
Height: dh.StringToInt(props[inc()]),
|
||||
|
||||
OperateFn: dh.StringToInt(props[inc()]),
|
||||
PopulateFn: dh.StringToInt(props[inc()]),
|
||||
InitFn: dh.StringToInt(props[inc()]),
|
||||
ClientFn: dh.StringToInt(props[inc()]),
|
||||
|
||||
RestoreVirgins: dh.StringToUint8(props[inc()]) == 1,
|
||||
BlockMissile: dh.StringToUint8(props[inc()]) == 1,
|
||||
DrawUnder: dh.StringToUint8(props[inc()]) == 1,
|
||||
OpenWarp: dh.StringToUint8(props[inc()]) == 1,
|
||||
|
||||
AutoMap: dh.StringToInt(props[inc()]),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var Objects map[int]*ObjectRecord
|
||||
|
||||
func LoadObjects(fileProvider d2interface.FileProvider) {
|
||||
Objects = make(map[int]*ObjectRecord)
|
||||
data := strings.Split(string(fileProvider.LoadFile(d2resource.ObjectDetails)), "\r\n")[1:]
|
||||
for _, line := range data {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
props := strings.Split(line, "\t")
|
||||
if props[2] == "" {
|
||||
continue // skip a line that doesn't have an id
|
||||
}
|
||||
rec := createObjectRecord(props)
|
||||
Objects[rec.Id] = &rec
|
||||
}
|
||||
log.Printf("Loaded %d objects", len(Objects))
|
||||
}
|
50
d2data/d2datadict/palette.go
Normal file
50
d2data/d2datadict/palette.go
Normal file
@ -0,0 +1,50 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
)
|
||||
|
||||
// PaletteRGB represents a color in a palette
|
||||
type PaletteRGB struct {
|
||||
R, G, B uint8
|
||||
}
|
||||
|
||||
// PaletteType represents a palette
|
||||
type PaletteRec struct {
|
||||
Name d2enum.PaletteType
|
||||
Colors [256]PaletteRGB
|
||||
}
|
||||
|
||||
var Palettes map[d2enum.PaletteType]PaletteRec
|
||||
|
||||
// CreatePalette creates a palette
|
||||
func CreatePalette(name d2enum.PaletteType, data []byte) PaletteRec {
|
||||
result := PaletteRec{Name: name}
|
||||
|
||||
for i := 0; i <= 255; i++ {
|
||||
result.Colors[i] = PaletteRGB{
|
||||
B: data[i*3],
|
||||
G: data[(i*3)+1],
|
||||
R: data[(i*3)+2],
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func LoadPalettes(mpqFiles map[string]string, fileProvider d2interface.FileProvider) {
|
||||
Palettes = make(map[d2enum.PaletteType]PaletteRec)
|
||||
for _, pal := range []string{
|
||||
"act1", "act2", "act3", "act4", "act5", "endgame", "endgame2", "fechar", "loading",
|
||||
"menu0", "menu1", "menu2", "menu3", "menu4", "sky", "static", "trademark", "units",
|
||||
} {
|
||||
filePath := `data\global\palette\` + pal + `\pal.dat`
|
||||
paletteName := d2enum.PaletteType(pal)
|
||||
palette := CreatePalette(paletteName, fileProvider.LoadFile(filePath))
|
||||
Palettes[paletteName] = palette
|
||||
}
|
||||
log.Printf("Loaded %d palettes", len(Palettes))
|
||||
}
|
107
d2data/d2datadict/sounds.go
Normal file
107
d2data/d2datadict/sounds.go
Normal file
@ -0,0 +1,107 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
|
||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
)
|
||||
|
||||
// SoundEntry represents a sound entry
|
||||
type SoundEntry struct {
|
||||
Handle string
|
||||
Index int
|
||||
FileName string
|
||||
Volume byte
|
||||
GroupSize uint8
|
||||
Loop bool
|
||||
FadeIn uint8
|
||||
FadeOut uint8
|
||||
DeferInst uint8
|
||||
StopInst uint8
|
||||
Duration uint8
|
||||
Compound int8
|
||||
Reverb bool
|
||||
Falloff uint8
|
||||
Cache uint8
|
||||
AsyncOnly bool
|
||||
Priority uint8
|
||||
Stream uint8
|
||||
Stereo uint8
|
||||
Tracking uint8
|
||||
Solo uint8
|
||||
MusicVol uint8
|
||||
Block1 int
|
||||
Block2 int
|
||||
Block3 int
|
||||
}
|
||||
|
||||
// CreateSoundEntry creates a sound entry based on a sound row on sounds.txt
|
||||
func createSoundEntry(soundLine string) SoundEntry {
|
||||
props := strings.Split(soundLine, "\t")
|
||||
i := -1
|
||||
inc := func() int {
|
||||
i++
|
||||
return i
|
||||
}
|
||||
result := SoundEntry{
|
||||
Handle: props[inc()],
|
||||
Index: dh.StringToInt(props[inc()]),
|
||||
FileName: props[inc()],
|
||||
Volume: dh.StringToUint8(props[inc()]),
|
||||
GroupSize: dh.StringToUint8(props[inc()]),
|
||||
Loop: dh.StringToUint8(props[inc()]) == 1,
|
||||
FadeIn: dh.StringToUint8(props[inc()]),
|
||||
FadeOut: dh.StringToUint8(props[inc()]),
|
||||
DeferInst: dh.StringToUint8(props[inc()]),
|
||||
StopInst: dh.StringToUint8(props[inc()]),
|
||||
Duration: dh.StringToUint8(props[inc()]),
|
||||
Compound: dh.StringToInt8(props[inc()]),
|
||||
Reverb: dh.StringToUint8(props[inc()]) == 1,
|
||||
Falloff: dh.StringToUint8(props[inc()]),
|
||||
Cache: dh.StringToUint8(props[inc()]),
|
||||
AsyncOnly: dh.StringToUint8(props[inc()]) == 1,
|
||||
Priority: dh.StringToUint8(props[inc()]),
|
||||
Stream: dh.StringToUint8(props[inc()]),
|
||||
Stereo: dh.StringToUint8(props[inc()]),
|
||||
Tracking: dh.StringToUint8(props[inc()]),
|
||||
Solo: dh.StringToUint8(props[inc()]),
|
||||
MusicVol: dh.StringToUint8(props[inc()]),
|
||||
Block1: dh.StringToInt(props[inc()]),
|
||||
Block2: dh.StringToInt(props[inc()]),
|
||||
Block3: dh.StringToInt(props[inc()]),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var Sounds map[string]SoundEntry
|
||||
|
||||
func LoadSounds(fileProvider d2interface.FileProvider) {
|
||||
Sounds = make(map[string]SoundEntry)
|
||||
soundData := strings.Split(string(fileProvider.LoadFile(d2resource.SoundSettings)), "\r\n")[1:]
|
||||
for _, line := range soundData {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
soundEntry := createSoundEntry(line)
|
||||
soundEntry.FileName = "/data/global/sfx/" + strings.ReplaceAll(soundEntry.FileName, `\`, "/")
|
||||
Sounds[soundEntry.Handle] = soundEntry
|
||||
/*
|
||||
// Use the following code to write out the values
|
||||
f, err := os.OpenFile(`C:\Users\lunat\Desktop\D2\sounds.txt`,
|
||||
os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := f.WriteString("\n[" + soundEntry.Handle + "] " + soundEntry.FileName); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
*/
|
||||
}
|
||||
log.Printf("Loaded %d sound definitions", len(Sounds))
|
||||
}
|
139
d2data/d2datadict/unique_items.go
Normal file
139
d2data/d2datadict/unique_items.go
Normal file
@ -0,0 +1,139 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
|
||||
dh "github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
)
|
||||
|
||||
type UniqueItemRecord struct {
|
||||
Name string
|
||||
Version int // 0 = classic pre 1.07, 1 = 1.07-1.11, 100 = expansion
|
||||
Enabled bool // if false, this record won't be loaded (should always be true...)
|
||||
Ladder bool // if true, can only be found on ladder and not in single player / tcp/ip
|
||||
Rarity int // 1-255, higher is more common (ironically...)
|
||||
NoLimit bool // if true, can drop more than once per game
|
||||
// (if false, can only drop once per game; if it would drop,
|
||||
// instead a rare item with enhanced durability drops)
|
||||
|
||||
Level int // item's level, can only be dropped by monsters / recipes / vendors / objects of this level or higher
|
||||
// otherwise they would drop a rare item with enhanced durability
|
||||
RequiredLevel int // character must have this level to use this item
|
||||
Code string // three letter code, points to a record in Weapons, Armor, or Misc
|
||||
|
||||
TypeDescription string
|
||||
UberDescription string
|
||||
SingleCopy bool // if true, player can only hold one of these. can't trade it or pick it up
|
||||
CostMultiplier int // base price is multiplied by this when sold, repaired or bought
|
||||
CostAdd int // after multiplied by above, this much is added to the price
|
||||
|
||||
CharacterGfxTransform string // palette shift applied to this items gfx when held and when
|
||||
// on the ground (flippy). Points to a record in Colors.txt
|
||||
InventoryGfxTransform string // palette shift applied to the inventory gfx
|
||||
FlippyFile string // if non-empty, overrides the base item's dropped gfx
|
||||
InventoryFile string // if non-empty, overrides the base item's inventory gfx
|
||||
|
||||
DropSound string // if non-empty, overrides the base item's drop sound
|
||||
DropSfxFrame int // if non-empty, overrides the base item's frame at which the drop sound plays
|
||||
UseSound string // if non-empty, overrides the sound played when item is used
|
||||
|
||||
Properties [12]UniqueItemProperty
|
||||
}
|
||||
|
||||
type UniqueItemProperty struct {
|
||||
Property string
|
||||
Parameter d2common.CalcString // depending on the property, this may be an int (usually), or a string
|
||||
Min int
|
||||
Max int
|
||||
}
|
||||
|
||||
func createUniqueItemRecord(r []string) UniqueItemRecord {
|
||||
i := -1
|
||||
inc := func() int {
|
||||
i++
|
||||
return i
|
||||
}
|
||||
result := UniqueItemRecord{
|
||||
Name: r[inc()],
|
||||
Version: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
Enabled: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
|
||||
Ladder: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
Rarity: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
NoLimit: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
|
||||
Level: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
RequiredLevel: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
Code: r[inc()],
|
||||
|
||||
TypeDescription: r[inc()],
|
||||
UberDescription: r[inc()],
|
||||
SingleCopy: dh.StringToInt(dh.EmptyToZero(r[inc()])) == 1,
|
||||
CostMultiplier: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
CostAdd: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
|
||||
CharacterGfxTransform: r[inc()],
|
||||
InventoryGfxTransform: r[inc()],
|
||||
FlippyFile: r[inc()],
|
||||
InventoryFile: r[inc()],
|
||||
|
||||
DropSound: r[inc()],
|
||||
DropSfxFrame: dh.StringToInt(dh.EmptyToZero(r[inc()])),
|
||||
UseSound: r[inc()],
|
||||
|
||||
Properties: [12]UniqueItemProperty{
|
||||
createUniqueItemProperty(&r, inc),
|
||||
createUniqueItemProperty(&r, inc),
|
||||
createUniqueItemProperty(&r, inc),
|
||||
createUniqueItemProperty(&r, inc),
|
||||
|
||||
createUniqueItemProperty(&r, inc),
|
||||
createUniqueItemProperty(&r, inc),
|
||||
createUniqueItemProperty(&r, inc),
|
||||
createUniqueItemProperty(&r, inc),
|
||||
|
||||
createUniqueItemProperty(&r, inc),
|
||||
createUniqueItemProperty(&r, inc),
|
||||
createUniqueItemProperty(&r, inc),
|
||||
createUniqueItemProperty(&r, inc),
|
||||
},
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func createUniqueItemProperty(r *[]string, inc func() int) UniqueItemProperty {
|
||||
result := UniqueItemProperty{
|
||||
Property: (*r)[inc()],
|
||||
Parameter: d2common.CalcString((*r)[inc()]),
|
||||
Min: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
Max: dh.StringToInt(dh.EmptyToZero((*r)[inc()])),
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var UniqueItems map[string]*UniqueItemRecord
|
||||
|
||||
func LoadUniqueItems(fileProvider d2interface.FileProvider) {
|
||||
UniqueItems = make(map[string]*UniqueItemRecord)
|
||||
data := strings.Split(string(fileProvider.LoadFile(d2resource.UniqueItems)), "\r\n")[1:]
|
||||
for _, line := range data {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
r := strings.Split(line, "\t")
|
||||
// skip rows that are not enabled
|
||||
if r[2] != "1" {
|
||||
continue
|
||||
}
|
||||
rec := createUniqueItemRecord(r)
|
||||
UniqueItems[rec.Code] = &rec
|
||||
}
|
||||
log.Printf("Loaded %d unique items", len(UniqueItems))
|
||||
}
|
18
d2data/d2datadict/weapons.go
Normal file
18
d2data/d2datadict/weapons.go
Normal file
@ -0,0 +1,18 @@
|
||||
package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
)
|
||||
|
||||
var Weapons map[string]*ItemCommonRecord
|
||||
|
||||
func LoadWeapons(fileProvider d2interface.FileProvider) {
|
||||
Weapons = *LoadCommonItems(fileProvider, d2resource.Weapons, d2enum.InventoryItemTypeWeapon)
|
||||
log.Printf("Loaded %d weapons", len(Weapons))
|
||||
}
|
136
d2data/d2dc6/dc6.go
Normal file
136
d2data/d2dc6/dc6.go
Normal file
@ -0,0 +1,136 @@
|
||||
package d2dc6
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
"github.com/go-restruct/restruct"
|
||||
)
|
||||
|
||||
type DC6File struct {
|
||||
// Header
|
||||
Version int32 `struct:"int32"`
|
||||
Flags uint32 `struct:"uint32"`
|
||||
Encoding uint32 `struct:"uint32"`
|
||||
Termination []byte `struct:"[4]byte"`
|
||||
Directions uint32 `struct:"uint32"`
|
||||
FramesPerDirection uint32 `struct:"uint32"`
|
||||
|
||||
FramePointers []uint32 `struct:"[]uint32,size=Directions*FramesPerDirection"`
|
||||
Frames []*DC6Frame `struct-size:"Directions*FramesPerDirection"`
|
||||
valid bool
|
||||
}
|
||||
|
||||
type DC6Header struct {
|
||||
Version int32 `struct:"int32"`
|
||||
Flags uint32 `struct:"uint32"`
|
||||
Encoding uint32 `struct:"uint32"`
|
||||
Termination []byte `struct:"[4]byte"`
|
||||
Directions int32 `struct:"int32"`
|
||||
FramesPerDirection int32 `struct:"int32"`
|
||||
}
|
||||
|
||||
type DC6FrameHeader struct {
|
||||
Flipped int32 `struct:"int32"`
|
||||
Width int32 `struct:"int32"`
|
||||
Height int32 `struct:"int32"`
|
||||
OffsetX int32 `struct:"int32"`
|
||||
OffsetY int32 `struct:"int32"`
|
||||
Unknown uint32 `struct:"uint32"`
|
||||
NextBlock uint32 `struct:"uint32"`
|
||||
Length uint32 `struct:"uint32"`
|
||||
}
|
||||
|
||||
type DC6Frame struct {
|
||||
Flipped uint32 `struct:"uint32"`
|
||||
Width uint32 `struct:"uint32"`
|
||||
Height uint32 `struct:"uint32"`
|
||||
OffsetX int32 `struct:"int32"`
|
||||
OffsetY int32 `struct:"int32"`
|
||||
Unknown uint32 `struct:"uint32"`
|
||||
NextBlock uint32 `struct:"uint32"`
|
||||
Length uint32 `struct:"uint32,sizeof=FrameData"`
|
||||
FrameData []byte
|
||||
Terminator []byte `struct:"[3]byte"`
|
||||
|
||||
colorData []byte
|
||||
palette d2datadict.PaletteRec
|
||||
valid bool
|
||||
}
|
||||
|
||||
func (frame *DC6Frame) ColorData() []byte {
|
||||
if frame.colorData == nil {
|
||||
frame.completeLoad()
|
||||
}
|
||||
|
||||
return frame.colorData
|
||||
}
|
||||
|
||||
func (frame *DC6Frame) completeLoad() {
|
||||
frame.valid = true
|
||||
|
||||
indexData := make([]int16, frame.Width*frame.Height)
|
||||
for fi := range indexData {
|
||||
indexData[fi] = -1
|
||||
}
|
||||
|
||||
x := uint32(0)
|
||||
y := frame.Height - 1
|
||||
dataPointer := 0
|
||||
for {
|
||||
b := frame.FrameData[dataPointer]
|
||||
dataPointer++
|
||||
if b == 0x80 {
|
||||
if y == 0 {
|
||||
break
|
||||
}
|
||||
y--
|
||||
x = 0
|
||||
} else if (b & 0x80) > 0 {
|
||||
transparentPixels := b & 0x7F
|
||||
for ti := byte(0); ti < transparentPixels; ti++ {
|
||||
indexData[x+(y*frame.Width)+uint32(ti)] = -1
|
||||
}
|
||||
x += uint32(transparentPixels)
|
||||
} else {
|
||||
for bi := 0; bi < int(b); bi++ {
|
||||
indexData[x+(y*frame.Width)+uint32(bi)] = int16(frame.FrameData[dataPointer])
|
||||
dataPointer++
|
||||
}
|
||||
x += uint32(b)
|
||||
}
|
||||
}
|
||||
|
||||
// Probably don't need this data again
|
||||
frame.FrameData = nil
|
||||
|
||||
frame.colorData = make([]byte, int(frame.Width*frame.Height)*4)
|
||||
for i := uint32(0); i < frame.Width*frame.Height; i++ {
|
||||
if indexData[i] < 1 { // TODO: Is this == -1 or < 1?
|
||||
continue
|
||||
}
|
||||
frame.colorData[i*4] = frame.palette.Colors[indexData[i]].R
|
||||
frame.colorData[(i*4)+1] = frame.palette.Colors[indexData[i]].G
|
||||
frame.colorData[(i*4)+2] = frame.palette.Colors[indexData[i]].B
|
||||
frame.colorData[(i*4)+3] = 0xFF
|
||||
}
|
||||
}
|
||||
|
||||
// LoadDC6 uses restruct to read the binary dc6 data into structs then parses image data from the frame data.
|
||||
func LoadDC6(data []byte, palette d2datadict.PaletteRec) (DC6File, error) {
|
||||
result := DC6File{valid: true}
|
||||
|
||||
restruct.EnableExprBeta()
|
||||
err := restruct.Unpack(data, binary.LittleEndian, &result)
|
||||
if err != nil {
|
||||
result.valid = false
|
||||
log.Printf("failed to read dc6: %v", err)
|
||||
}
|
||||
|
||||
for _, frame := range result.Frames {
|
||||
frame.palette = palette
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
73
d2data/d2dc6/dc6.ksy
Normal file
73
d2data/d2dc6/dc6.ksy
Normal file
@ -0,0 +1,73 @@
|
||||
meta:
|
||||
id: dc6
|
||||
title: Diablo CEL 6
|
||||
application: Diablo II
|
||||
file-extension: dc6
|
||||
license: MIT
|
||||
ks-version: 0.7
|
||||
encoding: ASCII
|
||||
endian: le
|
||||
seq:
|
||||
- id: dc6
|
||||
type: file
|
||||
types:
|
||||
file:
|
||||
seq:
|
||||
- id: header
|
||||
type: file_header
|
||||
- id: frame_pointers
|
||||
type: u4
|
||||
repeat: expr
|
||||
repeat-expr: header.directions * header.frames_per_dir
|
||||
- id: frames
|
||||
type: frame
|
||||
repeat: expr
|
||||
repeat-expr: header.directions * header.frames_per_dir
|
||||
file_header:
|
||||
seq:
|
||||
- id: version
|
||||
type: s4
|
||||
- id: flags
|
||||
type: u4
|
||||
enum: flags
|
||||
- id: encoding
|
||||
type: u4
|
||||
- id: termination
|
||||
size: 4
|
||||
- id: directions
|
||||
type: s4
|
||||
- id: frames_per_dir
|
||||
type: s4
|
||||
enums:
|
||||
flags:
|
||||
1: celfile_serialised
|
||||
4: celfile_24bit
|
||||
frame:
|
||||
seq:
|
||||
- id: header
|
||||
type: frame_header
|
||||
- id: block
|
||||
type: u1
|
||||
repeat: expr
|
||||
repeat-expr: header.length
|
||||
- id: terminator
|
||||
size: 3
|
||||
types:
|
||||
frame_header:
|
||||
seq:
|
||||
- id: flipped
|
||||
type: s4
|
||||
- id: width
|
||||
type: s4
|
||||
- id: height
|
||||
type: s4
|
||||
- id: offset_x
|
||||
type: s4
|
||||
- id: offset_y
|
||||
type: s4
|
||||
- id: unknown
|
||||
type: u4
|
||||
- id: next_block
|
||||
type: s4
|
||||
- id: length
|
||||
type: s4
|
11
d2data/d2dcc/common_data.go
Normal file
11
d2data/d2dcc/common_data.go
Normal file
@ -0,0 +1,11 @@
|
||||
package d2dcc
|
||||
|
||||
var crazyBitTable = []byte{0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 26, 28, 30, 32}
|
||||
var pixelMaskLookup = []int{0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}
|
||||
var CofToDir4 = []int{0, 1, 2, 3}
|
||||
var CofToDir8 = []int{4, 0, 5, 1, 6, 2, 7, 3}
|
||||
var CofToDir16 = []int{4, 8, 0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14, 3, 15}
|
||||
var CofToDir32 = []int{
|
||||
4, 16, 8, 17, 0, 18, 9, 19, 5, 20, 10, 21, 1, 22, 11, 23,
|
||||
6, 24, 12, 25, 2, 26, 13, 27, 7, 28, 14, 29, 3, 30, 15, 31,
|
||||
}
|
40
d2data/d2dcc/dcc.go
Normal file
40
d2data/d2dcc/dcc.go
Normal file
@ -0,0 +1,40 @@
|
||||
package d2dcc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
)
|
||||
|
||||
type DCC struct {
|
||||
Signature int
|
||||
Version int
|
||||
NumberOfDirections int
|
||||
FramesPerDirection int
|
||||
Directions []DCCDirection
|
||||
}
|
||||
|
||||
func LoadDCC(fileData []byte) (*DCC, error) {
|
||||
result := &DCC{}
|
||||
var bm = d2common.CreateBitMuncher(fileData, 0)
|
||||
result.Signature = int(bm.GetByte())
|
||||
if result.Signature != 0x74 {
|
||||
return nil, errors.New("Signature expected to be 0x74 but it is not.")
|
||||
}
|
||||
result.Version = int(bm.GetByte())
|
||||
result.NumberOfDirections = int(bm.GetByte())
|
||||
result.FramesPerDirection = int(bm.GetInt32())
|
||||
if bm.GetInt32() != 1 {
|
||||
return nil, errors.New("This value isn't 1. It has to be 1.")
|
||||
}
|
||||
bm.GetInt32() // TotalSizeCoded
|
||||
directionOffsets := make([]int, result.NumberOfDirections)
|
||||
for i := 0; i < result.NumberOfDirections; i++ {
|
||||
directionOffsets[i] = int(bm.GetInt32())
|
||||
}
|
||||
result.Directions = make([]DCCDirection, result.NumberOfDirections)
|
||||
for i := 0; i < result.NumberOfDirections; i++ {
|
||||
result.Directions[i] = CreateDCCDirection(d2common.CreateBitMuncher(fileData, directionOffsets[i]*8), *result)
|
||||
}
|
||||
return result, nil
|
||||
}
|
12
d2data/d2dcc/dcc_cell.go
Normal file
12
d2data/d2dcc/dcc_cell.go
Normal file
@ -0,0 +1,12 @@
|
||||
package d2dcc
|
||||
|
||||
type DCCCell struct {
|
||||
Width int
|
||||
Height int
|
||||
XOffset int
|
||||
YOffset int
|
||||
LastWidth int
|
||||
LastHeight int
|
||||
LastXOffset int
|
||||
LastYOffset int
|
||||
}
|
361
d2data/d2dcc/dcc_direction.go
Normal file
361
d2data/d2dcc/dcc_direction.go
Normal file
@ -0,0 +1,361 @@
|
||||
package d2dcc
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
)
|
||||
|
||||
type DCCDirection struct {
|
||||
OutSizeCoded int
|
||||
CompressionFlags int
|
||||
Variable0Bits int
|
||||
WidthBits int
|
||||
HeightBits int
|
||||
XOffsetBits int
|
||||
YOffsetBits int
|
||||
OptionalDataBits int
|
||||
CodedBytesBits int
|
||||
EqualCellsBitstreamSize int
|
||||
PixelMaskBitstreamSize int
|
||||
EncodingTypeBitsreamSize int
|
||||
RawPixelCodesBitstreamSize int
|
||||
Frames []*DCCDirectionFrame
|
||||
PaletteEntries [256]byte
|
||||
Box d2common.Rectangle
|
||||
Cells []*DCCCell
|
||||
PixelData []byte
|
||||
HorizontalCellCount int
|
||||
VerticalCellCount int
|
||||
PixelBuffer []DCCPixelBufferEntry
|
||||
}
|
||||
|
||||
func CreateDCCDirection(bm *d2common.BitMuncher, file DCC) DCCDirection {
|
||||
result := DCCDirection{}
|
||||
result.OutSizeCoded = int(bm.GetUInt32())
|
||||
result.CompressionFlags = int(bm.GetBits(2))
|
||||
result.Variable0Bits = int(crazyBitTable[bm.GetBits(4)])
|
||||
result.WidthBits = int(crazyBitTable[bm.GetBits(4)])
|
||||
result.HeightBits = int(crazyBitTable[bm.GetBits(4)])
|
||||
result.XOffsetBits = int(crazyBitTable[bm.GetBits(4)])
|
||||
result.YOffsetBits = int(crazyBitTable[bm.GetBits(4)])
|
||||
result.OptionalDataBits = int(crazyBitTable[bm.GetBits(4)])
|
||||
result.CodedBytesBits = int(crazyBitTable[bm.GetBits(4)])
|
||||
result.Frames = make([]*DCCDirectionFrame, file.FramesPerDirection)
|
||||
minx := 100000
|
||||
miny := 100000
|
||||
maxx := -100000
|
||||
maxy := -100000
|
||||
// Load the frame headers
|
||||
for frameIdx := 0; frameIdx < file.FramesPerDirection; frameIdx++ {
|
||||
result.Frames[frameIdx] = CreateDCCDirectionFrame(bm, result)
|
||||
minx = int(d2helper.MinInt32(int32(result.Frames[frameIdx].Box.Left), int32(minx)))
|
||||
miny = int(d2helper.MinInt32(int32(result.Frames[frameIdx].Box.Top), int32(miny)))
|
||||
maxx = int(d2helper.MaxInt32(int32(result.Frames[frameIdx].Box.Right()), int32(maxx)))
|
||||
maxy = int(d2helper.MaxInt32(int32(result.Frames[frameIdx].Box.Bottom()), int32(maxy)))
|
||||
}
|
||||
result.Box = d2common.Rectangle{minx, miny, (maxx - minx), (maxy - miny)}
|
||||
if result.OptionalDataBits > 0 {
|
||||
log.Panic("Optional bits in DCC data is not currently supported.")
|
||||
}
|
||||
if (result.CompressionFlags & 0x2) > 0 {
|
||||
result.EqualCellsBitstreamSize = int(bm.GetBits(20))
|
||||
}
|
||||
result.PixelMaskBitstreamSize = int(bm.GetBits(20))
|
||||
if (result.CompressionFlags & 0x1) > 0 {
|
||||
result.EncodingTypeBitsreamSize = int(bm.GetBits(20))
|
||||
result.RawPixelCodesBitstreamSize = int(bm.GetBits(20))
|
||||
}
|
||||
// PixelValuesKey
|
||||
paletteEntryCount := 0
|
||||
for i := 0; i < 256; i++ {
|
||||
valid := bm.GetBit() != 0
|
||||
if valid {
|
||||
result.PaletteEntries[paletteEntryCount] = byte(i)
|
||||
paletteEntryCount++
|
||||
}
|
||||
}
|
||||
// HERE BE GIANTS:
|
||||
// Because of the way this thing mashes bits together, BIT offset matters
|
||||
// here. For example, if you are on byte offset 3, bit offset 6, and
|
||||
// the EqualCellsBitstreamSize is 20 bytes, then the next bit stream
|
||||
// will be located at byte 23, bit offset 6!
|
||||
equalCellsBitstream := d2common.CopyBitMuncher(bm)
|
||||
bm.SkipBits(result.EqualCellsBitstreamSize)
|
||||
pixelMaskBitstream := d2common.CopyBitMuncher(bm)
|
||||
bm.SkipBits(result.PixelMaskBitstreamSize)
|
||||
encodingTypeBitsream := d2common.CopyBitMuncher(bm)
|
||||
bm.SkipBits(result.EncodingTypeBitsreamSize)
|
||||
rawPixelCodesBitstream := d2common.CopyBitMuncher(bm)
|
||||
bm.SkipBits(result.RawPixelCodesBitstreamSize)
|
||||
pixelCodeandDisplacement := d2common.CopyBitMuncher(bm)
|
||||
// Calculate the cells for the direction
|
||||
result.CalculateCells()
|
||||
// Calculate the cells for each of the frames
|
||||
for _, frame := range result.Frames {
|
||||
frame.CalculateCells(result)
|
||||
}
|
||||
// Fill in the pixel buffer
|
||||
result.FillPixelBuffer(pixelCodeandDisplacement, equalCellsBitstream, pixelMaskBitstream, encodingTypeBitsream, rawPixelCodesBitstream)
|
||||
// Generate the actual frame pixel data
|
||||
result.GenerateFrames(pixelCodeandDisplacement)
|
||||
result.PixelBuffer = nil
|
||||
// Verify that everything we expected to read was actually read (sanity check)...
|
||||
if equalCellsBitstream.BitsRead != result.EqualCellsBitstreamSize {
|
||||
log.Panic("Did not read the correct number of bits!")
|
||||
}
|
||||
if pixelMaskBitstream.BitsRead != result.PixelMaskBitstreamSize {
|
||||
log.Panic("Did not read the correct number of bits!")
|
||||
}
|
||||
if encodingTypeBitsream.BitsRead != result.EncodingTypeBitsreamSize {
|
||||
log.Panic("Did not read the correct number of bits!")
|
||||
}
|
||||
if rawPixelCodesBitstream.BitsRead != result.RawPixelCodesBitstreamSize {
|
||||
log.Panic("Did not read the correct number of bits!")
|
||||
}
|
||||
bm.SkipBits(pixelCodeandDisplacement.BitsRead)
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *DCCDirection) GenerateFrames(pcd *d2common.BitMuncher) {
|
||||
pbIdx := 0
|
||||
for _, cell := range v.Cells {
|
||||
cell.LastWidth = -1
|
||||
cell.LastHeight = -1
|
||||
}
|
||||
v.PixelData = make([]byte, v.Box.Width*v.Box.Height)
|
||||
frameIndex := -1
|
||||
for _, frame := range v.Frames {
|
||||
frameIndex++
|
||||
frame.PixelData = make([]byte, v.Box.Width*v.Box.Height)
|
||||
c := -1
|
||||
for _, cell := range frame.Cells {
|
||||
c++
|
||||
cellX := cell.XOffset / 4
|
||||
cellY := cell.YOffset / 4
|
||||
cellIndex := cellX + (cellY * v.HorizontalCellCount)
|
||||
bufferCell := v.Cells[cellIndex]
|
||||
pbe := v.PixelBuffer[pbIdx]
|
||||
if (pbe.Frame != frameIndex) || (pbe.FrameCellIndex != c) {
|
||||
// This buffer cell has an EqualCell bit set to 1, so copy the frame cell or clear it
|
||||
if (cell.Width != bufferCell.LastWidth) || (cell.Height != bufferCell.LastHeight) {
|
||||
// Different sizes
|
||||
/// TODO: Clear the pixels of the frame cell
|
||||
for y := 0; y < cell.Height; y++ {
|
||||
for x := 0; x < cell.Width; x++ {
|
||||
v.PixelData[x+cell.XOffset+((y+cell.YOffset)*v.Box.Width)] = 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Same sizes
|
||||
// Copy the old frame cell into the new position
|
||||
for fy := 0; fy < cell.Height; fy++ {
|
||||
for fx := 0; fx < cell.Width; fx++ {
|
||||
// Frame (buff.lastx, buff.lasty) -> Frame (cell.offx, cell.offy)
|
||||
// Cell (0, 0,) ->
|
||||
// blit(dir->bmp, dir->bmp, buff_cell->last_x0, buff_cell->last_y0, cell->x0, cell->y0, cell->w, cell->h );
|
||||
v.PixelData[fx+cell.XOffset+((fy+cell.YOffset)*v.Box.Width)] = v.PixelData[fx+bufferCell.LastXOffset+((fy+bufferCell.LastYOffset)*v.Box.Width)]
|
||||
}
|
||||
}
|
||||
// Copy it again into the final frame image
|
||||
for fy := 0; fy < cell.Height; fy++ {
|
||||
for fx := 0; fx < cell.Width; fx++ {
|
||||
// blit(cell->bmp, frm_bmp, 0, 0, cell->x0, cell->y0, cell->w, cell->h );
|
||||
frame.PixelData[fx+cell.XOffset+((fy+cell.YOffset)*v.Box.Width)] = v.PixelData[cell.XOffset+fx+((cell.YOffset+fy)*v.Box.Width)]
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if pbe.Value[0] == pbe.Value[1] {
|
||||
// Clear the frame
|
||||
//cell.PixelData = new byte[cell.Width * cell.Height];
|
||||
for y := 0; y < cell.Height; y++ {
|
||||
for x := 0; x < cell.Width; x++ {
|
||||
v.PixelData[x+cell.XOffset+((y+cell.YOffset)*v.Box.Width)] = pbe.Value[0]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fill the frame cell with the pixels
|
||||
bitsToRead := 1
|
||||
if pbe.Value[1] != pbe.Value[2] {
|
||||
bitsToRead = 2
|
||||
}
|
||||
for y := 0; y < cell.Height; y++ {
|
||||
for x := 0; x < cell.Width; x++ {
|
||||
paletteIndex := pcd.GetBits(bitsToRead)
|
||||
v.PixelData[x+cell.XOffset+((y+cell.YOffset)*v.Box.Width)] = pbe.Value[paletteIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copy the frame cell into the frame
|
||||
for fy := 0; fy < cell.Height; fy++ {
|
||||
for fx := 0; fx < cell.Width; fx++ {
|
||||
//blit(cell->bmp, frm_bmp, 0, 0, cell->x0, cell->y0, cell->w, cell->h );
|
||||
frame.PixelData[fx+cell.XOffset+((fy+cell.YOffset)*v.Box.Width)] = v.PixelData[fx+cell.XOffset+((fy+cell.YOffset)*v.Box.Width)]
|
||||
}
|
||||
}
|
||||
pbIdx++
|
||||
}
|
||||
bufferCell.LastWidth = cell.Width
|
||||
bufferCell.LastHeight = cell.Height
|
||||
bufferCell.LastXOffset = cell.XOffset
|
||||
bufferCell.LastYOffset = cell.YOffset
|
||||
}
|
||||
// Free up the stuff we no longer need
|
||||
frame.Cells = nil
|
||||
}
|
||||
v.Cells = nil
|
||||
v.PixelData = nil
|
||||
v.PixelBuffer = nil
|
||||
}
|
||||
|
||||
func (v *DCCDirection) FillPixelBuffer(pcd, ec, pm, et, rp *d2common.BitMuncher) {
|
||||
lastPixel := uint32(0)
|
||||
maxCellX := 0
|
||||
maxCellY := 0
|
||||
for _, frame := range v.Frames {
|
||||
if frame == nil {
|
||||
continue
|
||||
}
|
||||
maxCellX += frame.HorizontalCellCount
|
||||
maxCellY += frame.VerticalCellCount
|
||||
}
|
||||
v.PixelBuffer = make([]DCCPixelBufferEntry, maxCellX*maxCellY)
|
||||
for i := 0; i < maxCellX*maxCellY; i++ {
|
||||
v.PixelBuffer[i].Frame = -1
|
||||
v.PixelBuffer[i].FrameCellIndex = -1
|
||||
}
|
||||
cellBuffer := make([]*DCCPixelBufferEntry, v.HorizontalCellCount*v.VerticalCellCount)
|
||||
frameIndex := -1
|
||||
pbIndex := -1
|
||||
pixelMask := uint32(0x00)
|
||||
for _, frame := range v.Frames {
|
||||
frameIndex++
|
||||
originCellX := (frame.Box.Left - v.Box.Left) / 4
|
||||
originCellY := (frame.Box.Top - v.Box.Top) / 4
|
||||
for cellY := 0; cellY < frame.VerticalCellCount; cellY++ {
|
||||
currentCellY := cellY + originCellY
|
||||
for cellX := 0; cellX < frame.HorizontalCellCount; cellX++ {
|
||||
currentCell := originCellX + cellX + (currentCellY * v.HorizontalCellCount)
|
||||
nextCell := false
|
||||
tmp := 0
|
||||
if cellBuffer[currentCell] != nil {
|
||||
if v.EqualCellsBitstreamSize > 0 {
|
||||
tmp = int(ec.GetBit())
|
||||
} else {
|
||||
tmp = 0
|
||||
}
|
||||
if tmp == 0 {
|
||||
pixelMask = pm.GetBits(4)
|
||||
} else {
|
||||
nextCell = true
|
||||
}
|
||||
} else {
|
||||
pixelMask = 0x0F
|
||||
}
|
||||
if nextCell {
|
||||
continue
|
||||
}
|
||||
// Decode the pixels
|
||||
var pixelStack [4]uint32
|
||||
lastPixel = 0
|
||||
numberOfPixelBits := pixelMaskLookup[pixelMask]
|
||||
encodingType := 0
|
||||
if (numberOfPixelBits != 0) && (v.EncodingTypeBitsreamSize > 0) {
|
||||
encodingType = int(et.GetBit())
|
||||
} else {
|
||||
encodingType = 0
|
||||
}
|
||||
decodedPixel := 0
|
||||
for i := 0; i < numberOfPixelBits; i++ {
|
||||
if encodingType != 0 {
|
||||
pixelStack[i] = rp.GetBits(8)
|
||||
} else {
|
||||
pixelStack[i] = lastPixel
|
||||
pixelDisplacement := pcd.GetBits(4)
|
||||
pixelStack[i] += pixelDisplacement
|
||||
for pixelDisplacement == 15 {
|
||||
pixelDisplacement = pcd.GetBits(4)
|
||||
pixelStack[i] += pixelDisplacement
|
||||
}
|
||||
}
|
||||
if pixelStack[i] == lastPixel {
|
||||
pixelStack[i] = 0
|
||||
break
|
||||
} else {
|
||||
lastPixel = pixelStack[i]
|
||||
decodedPixel++
|
||||
}
|
||||
}
|
||||
oldEntry := cellBuffer[currentCell]
|
||||
pbIndex++
|
||||
curIdx := decodedPixel - 1
|
||||
for i := 0; i < 4; i++ {
|
||||
if (pixelMask & (1 << uint(i))) != 0 {
|
||||
if curIdx >= 0 {
|
||||
v.PixelBuffer[pbIndex].Value[i] = byte(pixelStack[curIdx])
|
||||
curIdx--
|
||||
} else {
|
||||
v.PixelBuffer[pbIndex].Value[i] = 0
|
||||
}
|
||||
} else {
|
||||
v.PixelBuffer[pbIndex].Value[i] = oldEntry.Value[i]
|
||||
}
|
||||
}
|
||||
cellBuffer[currentCell] = &v.PixelBuffer[pbIndex]
|
||||
v.PixelBuffer[pbIndex].Frame = frameIndex
|
||||
v.PixelBuffer[pbIndex].FrameCellIndex = cellX + (cellY * frame.HorizontalCellCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
cellBuffer = nil
|
||||
// Convert the palette entry index into actual palette entries
|
||||
for i := 0; i <= pbIndex; i++ {
|
||||
for x := 0; x < 4; x++ {
|
||||
v.PixelBuffer[i].Value[x] = v.PaletteEntries[v.PixelBuffer[i].Value[x]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *DCCDirection) CalculateCells() {
|
||||
// Calculate the number of vertical and horizontal cells we need
|
||||
v.HorizontalCellCount = 1 + (v.Box.Width-1)/4
|
||||
v.VerticalCellCount = 1 + (v.Box.Height-1)/4
|
||||
// Calculate the cell widths
|
||||
cellWidths := make([]int, v.HorizontalCellCount)
|
||||
if v.HorizontalCellCount == 1 {
|
||||
cellWidths[0] = v.Box.Width
|
||||
} else {
|
||||
for i := 0; i < v.HorizontalCellCount-1; i++ {
|
||||
cellWidths[i] = 4
|
||||
}
|
||||
cellWidths[v.HorizontalCellCount-1] = v.Box.Width - (4 * (v.HorizontalCellCount - 1))
|
||||
}
|
||||
// Calculate the cell heights
|
||||
cellHeights := make([]int, v.VerticalCellCount)
|
||||
if v.VerticalCellCount == 1 {
|
||||
cellHeights[0] = v.Box.Height
|
||||
} else {
|
||||
for i := 0; i < v.VerticalCellCount-1; i++ {
|
||||
cellHeights[i] = 4
|
||||
}
|
||||
cellHeights[v.VerticalCellCount-1] = v.Box.Height - (4 * (v.VerticalCellCount - 1))
|
||||
}
|
||||
// Set the cell widths and heights in the cell buffer
|
||||
v.Cells = make([]*DCCCell, v.VerticalCellCount*v.HorizontalCellCount)
|
||||
yOffset := 0
|
||||
for y := 0; y < v.VerticalCellCount; y++ {
|
||||
xOffset := 0
|
||||
for x := 0; x < v.HorizontalCellCount; x++ {
|
||||
v.Cells[x+(y*v.HorizontalCellCount)] = &DCCCell{
|
||||
Width: cellWidths[x],
|
||||
Height: cellHeights[y],
|
||||
XOffset: xOffset,
|
||||
YOffset: yOffset,
|
||||
}
|
||||
xOffset += 4
|
||||
}
|
||||
yOffset += 4
|
||||
}
|
||||
}
|
108
d2data/d2dcc/dcc_direction_frame.go
Normal file
108
d2data/d2dcc/dcc_direction_frame.go
Normal file
@ -0,0 +1,108 @@
|
||||
package d2dcc
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
)
|
||||
|
||||
type DCCDirectionFrame struct {
|
||||
Width int
|
||||
Height int
|
||||
XOffset int
|
||||
YOffset int
|
||||
NumberOfOptionalBytes int
|
||||
NumberOfCodedBytes int
|
||||
FrameIsBottomUp bool
|
||||
Box d2common.Rectangle
|
||||
Cells []DCCCell
|
||||
PixelData []byte
|
||||
HorizontalCellCount int
|
||||
VerticalCellCount int
|
||||
valid bool
|
||||
}
|
||||
|
||||
func CreateDCCDirectionFrame(bits *d2common.BitMuncher, direction DCCDirection) *DCCDirectionFrame {
|
||||
result := &DCCDirectionFrame{}
|
||||
bits.GetBits(direction.Variable0Bits) // Variable0
|
||||
result.Width = int(bits.GetBits(direction.WidthBits))
|
||||
result.Height = int(bits.GetBits(direction.HeightBits))
|
||||
result.XOffset = bits.GetSignedBits(direction.XOffsetBits)
|
||||
result.YOffset = bits.GetSignedBits(direction.YOffsetBits)
|
||||
result.NumberOfOptionalBytes = int(bits.GetBits(direction.OptionalDataBits))
|
||||
result.NumberOfCodedBytes = int(bits.GetBits(direction.CodedBytesBits))
|
||||
result.FrameIsBottomUp = bits.GetBit() == 1
|
||||
if result.FrameIsBottomUp {
|
||||
log.Panic("Bottom up frames are not implemented.")
|
||||
} else {
|
||||
result.Box = d2common.Rectangle{
|
||||
result.XOffset,
|
||||
result.YOffset - result.Height + 1,
|
||||
result.Width,
|
||||
result.Height,
|
||||
}
|
||||
}
|
||||
result.valid = true
|
||||
return result
|
||||
}
|
||||
|
||||
func (v *DCCDirectionFrame) CalculateCells(direction DCCDirection) {
|
||||
var w = 4 - ((v.Box.Left - direction.Box.Left) % 4) // Width of the first column (in pixels)
|
||||
if (v.Width - w) <= 1 {
|
||||
v.HorizontalCellCount = 1
|
||||
} else {
|
||||
tmp := v.Width - w - 1
|
||||
v.HorizontalCellCount = 2 + (tmp / 4)
|
||||
if (tmp % 4) == 0 {
|
||||
v.HorizontalCellCount--
|
||||
}
|
||||
}
|
||||
h := 4 - ((v.Box.Top - direction.Box.Top) % 4) // Height of the first column (in pixels)
|
||||
if (v.Height - h) <= 1 {
|
||||
v.VerticalCellCount = 1
|
||||
} else {
|
||||
tmp := v.Height - h - 1
|
||||
v.VerticalCellCount = 2 + (tmp / 4)
|
||||
if (tmp % 4) == 0 {
|
||||
v.VerticalCellCount--
|
||||
}
|
||||
}
|
||||
// Calculate the cell widths and heights
|
||||
cellWidths := make([]int, v.HorizontalCellCount)
|
||||
if v.HorizontalCellCount == 1 {
|
||||
cellWidths[0] = v.Width
|
||||
} else {
|
||||
cellWidths[0] = w
|
||||
for i := 1; i < (v.HorizontalCellCount - 1); i++ {
|
||||
cellWidths[i] = 4
|
||||
}
|
||||
cellWidths[v.HorizontalCellCount-1] = v.Width - w - (4 * (v.HorizontalCellCount - 2))
|
||||
}
|
||||
|
||||
cellHeights := make([]int, v.VerticalCellCount)
|
||||
if v.VerticalCellCount == 1 {
|
||||
cellHeights[0] = v.Height
|
||||
} else {
|
||||
cellHeights[0] = h
|
||||
for i := 1; i < (v.VerticalCellCount - 1); i++ {
|
||||
cellHeights[i] = 4
|
||||
}
|
||||
cellHeights[v.VerticalCellCount-1] = v.Height - h - (4 * (v.VerticalCellCount - 2))
|
||||
}
|
||||
|
||||
v.Cells = make([]DCCCell, v.HorizontalCellCount*v.VerticalCellCount)
|
||||
offsetY := v.Box.Top - direction.Box.Top
|
||||
for y := 0; y < v.VerticalCellCount; y++ {
|
||||
offsetX := v.Box.Left - direction.Box.Left
|
||||
for x := 0; x < v.HorizontalCellCount; x++ {
|
||||
v.Cells[x+(y*v.HorizontalCellCount)] = DCCCell{
|
||||
XOffset: offsetX,
|
||||
YOffset: offsetY,
|
||||
Width: cellWidths[x],
|
||||
Height: cellHeights[y],
|
||||
}
|
||||
offsetX += cellWidths[x]
|
||||
}
|
||||
offsetY += cellHeights[y]
|
||||
}
|
||||
}
|
7
d2data/d2dcc/dcc_pixel_buffer_entry.go
Normal file
7
d2data/d2dcc/dcc_pixel_buffer_entry.go
Normal file
@ -0,0 +1,7 @@
|
||||
package d2dcc
|
||||
|
||||
type DCCPixelBufferEntry struct {
|
||||
Value [4]byte
|
||||
Frame int
|
||||
FrameCellIndex int
|
||||
}
|
7
d2data/d2ds1/common_data.go
Normal file
7
d2data/d2ds1/common_data.go
Normal file
@ -0,0 +1,7 @@
|
||||
package d2ds1
|
||||
|
||||
var dirLookup = []int32{
|
||||
0x00, 0x01, 0x02, 0x01, 0x02, 0x03, 0x03, 0x05, 0x05, 0x06,
|
||||
0x06, 0x07, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
|
||||
0x0F, 0x10, 0x11, 0x12, 0x14,
|
||||
}
|
247
d2data/d2ds1/ds1.go
Normal file
247
d2data/d2ds1/ds1.go
Normal file
@ -0,0 +1,247 @@
|
||||
package d2ds1
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2helper"
|
||||
)
|
||||
|
||||
type DS1 struct {
|
||||
Version int32 // The version of the DS1
|
||||
Width int32 // Width of map, in # of tiles
|
||||
Height int32 // Height of map, in # of tiles
|
||||
Act int32 // Act, from 1 to 5. This tells which act table to use for the Objects list
|
||||
SubstitutionType int32 // SubstitutionType (layer type): 0 if no layer, else type 1 or type 2
|
||||
Files []string // FilePtr table of file string pointers
|
||||
NumberOfWalls int32 // WallNum number of wall & orientation layers used
|
||||
NumberOfFloors int32 // number of floor layers used
|
||||
NumberOfShadowLayers int32 // ShadowNum number of shadow layer used
|
||||
NumberOfSubstitutionLayers int32 // SubstitutionNum number of substitution layer used
|
||||
SubstitutionGroupsNum int32 // SubstitutionGroupsNum number of substitution groups, datas between objects & NPC paths
|
||||
Objects []d2data.Object // Objects
|
||||
Tiles [][]TileRecord
|
||||
SubstitutionGroups []SubstitutionGroup
|
||||
}
|
||||
|
||||
func LoadDS1(fileData []byte) DS1 {
|
||||
ds1 := DS1{
|
||||
NumberOfFloors: 1,
|
||||
NumberOfWalls: 1,
|
||||
NumberOfShadowLayers: 1,
|
||||
NumberOfSubstitutionLayers: 0,
|
||||
}
|
||||
br := d2common.CreateStreamReader(fileData)
|
||||
ds1.Version = br.GetInt32()
|
||||
ds1.Width = br.GetInt32() + 1
|
||||
ds1.Height = br.GetInt32() + 1
|
||||
if ds1.Version >= 8 {
|
||||
ds1.Act = d2helper.MinInt32(5, br.GetInt32()+1)
|
||||
}
|
||||
if ds1.Version >= 10 {
|
||||
ds1.SubstitutionType = br.GetInt32()
|
||||
if ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2 {
|
||||
ds1.NumberOfSubstitutionLayers = 1
|
||||
}
|
||||
}
|
||||
if ds1.Version >= 3 {
|
||||
// These files reference things that don't exist anymore :-?
|
||||
numberOfFiles := br.GetInt32()
|
||||
ds1.Files = make([]string, numberOfFiles)
|
||||
for i := 0; i < int(numberOfFiles); i++ {
|
||||
ds1.Files[i] = ""
|
||||
for {
|
||||
ch := br.GetByte()
|
||||
if ch == 0 {
|
||||
break
|
||||
}
|
||||
ds1.Files[i] += string(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
if ds1.Version >= 9 && ds1.Version <= 13 {
|
||||
// Skipping two dwords because they are "meaningless"?
|
||||
br.SkipBytes(16)
|
||||
}
|
||||
if ds1.Version >= 4 {
|
||||
ds1.NumberOfWalls = br.GetInt32()
|
||||
if ds1.Version >= 16 {
|
||||
ds1.NumberOfFloors = br.GetInt32()
|
||||
} else {
|
||||
ds1.NumberOfFloors = 1
|
||||
}
|
||||
}
|
||||
var layerStream []d2enum.LayerStreamType
|
||||
if ds1.Version < 4 {
|
||||
layerStream = []d2enum.LayerStreamType{
|
||||
d2enum.LayerStreamWall1,
|
||||
d2enum.LayerStreamFloor1,
|
||||
d2enum.LayerStreamOrientation1,
|
||||
d2enum.LayerStreamSubstitute,
|
||||
d2enum.LayerStreamShadow,
|
||||
}
|
||||
} else {
|
||||
layerStream = make([]d2enum.LayerStreamType, (ds1.NumberOfWalls*2)+ds1.NumberOfFloors+ds1.NumberOfShadowLayers+ds1.NumberOfSubstitutionLayers)
|
||||
layerIdx := 0
|
||||
for i := 0; i < int(ds1.NumberOfWalls); i++ {
|
||||
layerStream[layerIdx] = d2enum.LayerStreamType(int(d2enum.LayerStreamWall1) + i)
|
||||
layerStream[layerIdx+1] = d2enum.LayerStreamType(int(d2enum.LayerStreamOrientation1) + i)
|
||||
layerIdx += 2
|
||||
}
|
||||
for i := 0; i < int(ds1.NumberOfFloors); i++ {
|
||||
layerStream[layerIdx] = d2enum.LayerStreamType(int(d2enum.LayerStreamFloor1) + i)
|
||||
layerIdx++
|
||||
}
|
||||
if ds1.NumberOfShadowLayers > 0 {
|
||||
layerStream[layerIdx] = d2enum.LayerStreamShadow
|
||||
layerIdx++
|
||||
}
|
||||
if ds1.NumberOfSubstitutionLayers > 0 {
|
||||
layerStream[layerIdx] = d2enum.LayerStreamSubstitute
|
||||
layerIdx++
|
||||
}
|
||||
}
|
||||
ds1.Tiles = make([][]TileRecord, ds1.Height)
|
||||
for y := range ds1.Tiles {
|
||||
ds1.Tiles[y] = make([]TileRecord, ds1.Width)
|
||||
for x := 0; x < int(ds1.Width); x++ {
|
||||
ds1.Tiles[y][x].Walls = make([]WallRecord, ds1.NumberOfWalls)
|
||||
ds1.Tiles[y][x].Floors = make([]FloorShadowRecord, ds1.NumberOfFloors)
|
||||
ds1.Tiles[y][x].Shadows = make([]FloorShadowRecord, ds1.NumberOfShadowLayers)
|
||||
ds1.Tiles[y][x].Substitutions = make([]SubstitutionRecord, ds1.NumberOfSubstitutionLayers)
|
||||
}
|
||||
}
|
||||
for _, layerStreamType := range layerStream {
|
||||
for y := 0; y < int(ds1.Height); y++ {
|
||||
for x := 0; x < int(ds1.Width); x++ {
|
||||
dw := br.GetUInt32()
|
||||
switch layerStreamType {
|
||||
case d2enum.LayerStreamWall1:
|
||||
fallthrough
|
||||
case d2enum.LayerStreamWall2:
|
||||
fallthrough
|
||||
case d2enum.LayerStreamWall3:
|
||||
fallthrough
|
||||
case d2enum.LayerStreamWall4:
|
||||
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Prop1 = byte(dw & 0x000000FF)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Sequence = byte((dw & 0x00003F00) >> 8)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Unknown1 = byte((dw & 0x000FC000) >> 14)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Style = byte((dw & 0x03F00000) >> 20)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Unknown2 = byte((dw & 0x7C000000) >> 26)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Hidden = byte((dw&0x80000000)>>31) > 0
|
||||
case d2enum.LayerStreamOrientation1:
|
||||
fallthrough
|
||||
case d2enum.LayerStreamOrientation2:
|
||||
fallthrough
|
||||
case d2enum.LayerStreamOrientation3:
|
||||
fallthrough
|
||||
case d2enum.LayerStreamOrientation4:
|
||||
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1)
|
||||
c := int32(dw & 0x000000FF)
|
||||
if ds1.Version < 7 {
|
||||
if c < 25 {
|
||||
c = dirLookup[c]
|
||||
}
|
||||
}
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Type = d2enum.TileType(c)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Zero = byte((dw & 0xFFFFFF00) >> 8)
|
||||
case d2enum.LayerStreamFloor1:
|
||||
fallthrough
|
||||
case d2enum.LayerStreamFloor2:
|
||||
floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1)
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Prop1 = byte(dw & 0x000000FF)
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Sequence = byte((dw & 0x00003F00) >> 8)
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Unknown1 = byte((dw & 0x000FC000) >> 14)
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Style = byte((dw & 0x03F00000) >> 20)
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Unknown2 = byte((dw & 0x7C000000) >> 26)
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Hidden = byte((dw&0x80000000)>>31) > 0
|
||||
case d2enum.LayerStreamShadow:
|
||||
ds1.Tiles[y][x].Shadows[0].Prop1 = byte(dw & 0x000000FF)
|
||||
ds1.Tiles[y][x].Shadows[0].Sequence = byte((dw & 0x00003F00) >> 8)
|
||||
ds1.Tiles[y][x].Shadows[0].Unknown1 = byte((dw & 0x000FC000) >> 14)
|
||||
ds1.Tiles[y][x].Shadows[0].Style = byte((dw & 0x03F00000) >> 20)
|
||||
ds1.Tiles[y][x].Shadows[0].Unknown2 = byte((dw & 0x7C000000) >> 26)
|
||||
ds1.Tiles[y][x].Shadows[0].Hidden = byte((dw&0x80000000)>>31) > 0
|
||||
case d2enum.LayerStreamSubstitute:
|
||||
ds1.Tiles[y][x].Substitutions[0].Unknown = dw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ds1.Version >= 2 {
|
||||
numberOfObjects := br.GetInt32()
|
||||
ds1.Objects = make([]d2data.Object, numberOfObjects)
|
||||
for objIdx := 0; objIdx < int(numberOfObjects); objIdx++ {
|
||||
newObject := d2data.Object{}
|
||||
newObject.Type = br.GetInt32()
|
||||
newObject.Id = br.GetInt32()
|
||||
newObject.X = br.GetInt32()
|
||||
newObject.Y = br.GetInt32()
|
||||
newObject.Flags = br.GetInt32()
|
||||
newObject.Lookup = d2datadict.LookupObject(int(ds1.Act), int(newObject.Type), int(newObject.Id))
|
||||
if newObject.Lookup != nil && newObject.Lookup.ObjectsTxtId != -1 {
|
||||
newObject.ObjectInfo = d2datadict.Objects[newObject.Lookup.ObjectsTxtId]
|
||||
}
|
||||
ds1.Objects[objIdx] = newObject
|
||||
}
|
||||
} else {
|
||||
ds1.Objects = make([]d2data.Object, 0)
|
||||
}
|
||||
if ds1.Version >= 12 && (ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2) {
|
||||
if ds1.Version >= 18 {
|
||||
br.GetUInt32()
|
||||
}
|
||||
numberOfSubGroups := br.GetInt32()
|
||||
ds1.SubstitutionGroups = make([]SubstitutionGroup, numberOfSubGroups)
|
||||
for subIdx := 0; subIdx < int(numberOfSubGroups); subIdx++ {
|
||||
newSub := SubstitutionGroup{}
|
||||
newSub.TileX = br.GetInt32()
|
||||
newSub.TileY = br.GetInt32()
|
||||
newSub.WidthInTiles = br.GetInt32()
|
||||
newSub.HeightInTiles = br.GetInt32()
|
||||
newSub.Unknown = br.GetInt32()
|
||||
|
||||
ds1.SubstitutionGroups[subIdx] = newSub
|
||||
}
|
||||
} else {
|
||||
ds1.SubstitutionGroups = make([]SubstitutionGroup, 0)
|
||||
}
|
||||
if ds1.Version >= 14 {
|
||||
numberOfNpcs := br.GetInt32()
|
||||
for npcIdx := 0; npcIdx < int(numberOfNpcs); npcIdx++ {
|
||||
numPaths := br.GetInt32()
|
||||
npcX := br.GetInt32()
|
||||
npcY := br.GetInt32()
|
||||
objIdx := -1
|
||||
for idx, ds1Obj := range ds1.Objects {
|
||||
if ds1Obj.X == npcX && ds1Obj.Y == npcY {
|
||||
objIdx = idx
|
||||
break
|
||||
}
|
||||
}
|
||||
if objIdx > -1 {
|
||||
if ds1.Objects[objIdx].Paths == nil {
|
||||
ds1.Objects[objIdx].Paths = make([]d2common.Path, numPaths)
|
||||
}
|
||||
for pathIdx := 0; pathIdx < int(numPaths); pathIdx++ {
|
||||
newPath := d2common.Path{}
|
||||
newPath.X = br.GetInt32()
|
||||
newPath.Y = br.GetInt32()
|
||||
if ds1.Version >= 15 {
|
||||
newPath.Action = br.GetInt32()
|
||||
}
|
||||
ds1.Objects[objIdx].Paths[pathIdx] = newPath
|
||||
}
|
||||
} else {
|
||||
if ds1.Version >= 15 {
|
||||
br.SkipBytes(int(numPaths) * 3)
|
||||
} else {
|
||||
br.SkipBytes(int(numPaths) * 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ds1
|
||||
}
|
13
d2data/d2ds1/floor_shadow_record.go
Normal file
13
d2data/d2ds1/floor_shadow_record.go
Normal file
@ -0,0 +1,13 @@
|
||||
package d2ds1
|
||||
|
||||
type FloorShadowRecord struct {
|
||||
Prop1 byte
|
||||
Sequence byte
|
||||
Unknown1 byte
|
||||
Style byte
|
||||
Unknown2 byte
|
||||
Hidden bool
|
||||
RandomIndex byte
|
||||
Animated bool
|
||||
YAdjust int
|
||||
}
|
9
d2data/d2ds1/substitution_group.go
Normal file
9
d2data/d2ds1/substitution_group.go
Normal file
@ -0,0 +1,9 @@
|
||||
package d2ds1
|
||||
|
||||
type SubstitutionGroup struct {
|
||||
TileX int32
|
||||
TileY int32
|
||||
WidthInTiles int32
|
||||
HeightInTiles int32
|
||||
Unknown int32
|
||||
}
|
5
d2data/d2ds1/substitution_record.go
Normal file
5
d2data/d2ds1/substitution_record.go
Normal file
@ -0,0 +1,5 @@
|
||||
package d2ds1
|
||||
|
||||
type SubstitutionRecord struct {
|
||||
Unknown uint32
|
||||
}
|
8
d2data/d2ds1/tilerecord.go
Normal file
8
d2data/d2ds1/tilerecord.go
Normal file
@ -0,0 +1,8 @@
|
||||
package d2ds1
|
||||
|
||||
type TileRecord struct {
|
||||
Floors []FloorShadowRecord
|
||||
Walls []WallRecord
|
||||
Shadows []FloorShadowRecord
|
||||
Substitutions []SubstitutionRecord
|
||||
}
|
16
d2data/d2ds1/wallrecord.go
Normal file
16
d2data/d2ds1/wallrecord.go
Normal file
@ -0,0 +1,16 @@
|
||||
package d2ds1
|
||||
|
||||
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
|
||||
type WallRecord struct {
|
||||
Type d2enum.TileType
|
||||
Zero byte
|
||||
Prop1 byte
|
||||
Sequence byte
|
||||
Unknown1 byte
|
||||
Style byte
|
||||
Unknown2 byte
|
||||
Hidden bool
|
||||
RandomIndex byte
|
||||
YAdjust int
|
||||
}
|
12
d2data/d2dt1/block.go
Normal file
12
d2data/d2dt1/block.go
Normal file
@ -0,0 +1,12 @@
|
||||
package d2dt1
|
||||
|
||||
type Block struct {
|
||||
X int16
|
||||
Y int16
|
||||
GridX byte
|
||||
GridY byte
|
||||
Format BlockDataFormat
|
||||
EncodedData []byte
|
||||
Length int32
|
||||
FileOffset int32
|
||||
}
|
83
d2data/d2dt1/dt1.go
Normal file
83
d2data/d2dt1/dt1.go
Normal file
@ -0,0 +1,83 @@
|
||||
package d2dt1
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
)
|
||||
|
||||
// https://d2mods.info/forum/viewtopic.php?t=65163
|
||||
|
||||
type DT1 struct {
|
||||
Tiles []Tile
|
||||
}
|
||||
|
||||
type BlockDataFormat int16
|
||||
|
||||
const (
|
||||
BlockFormatRLE BlockDataFormat = 0 // Not 1
|
||||
BlockFormatIsometric BlockDataFormat = 1
|
||||
)
|
||||
|
||||
func LoadDT1(fileData []byte) DT1 {
|
||||
result := DT1{}
|
||||
br := d2common.CreateStreamReader(fileData)
|
||||
ver1 := br.GetInt32()
|
||||
ver2 := br.GetInt32()
|
||||
if ver1 != 7 || ver2 != 6 {
|
||||
log.Panicf("Expected to have a version of 7.6, but got %d.%d instead", ver1, ver2)
|
||||
}
|
||||
br.SkipBytes(260)
|
||||
numberOfTiles := br.GetInt32()
|
||||
br.SetPosition(uint64(br.GetInt32()))
|
||||
result.Tiles = make([]Tile, numberOfTiles)
|
||||
for tileIdx := range result.Tiles {
|
||||
newTile := Tile{}
|
||||
newTile.Direction = br.GetInt32()
|
||||
newTile.RoofHeight = br.GetInt16()
|
||||
newTile.MaterialFlags = NewMaterialFlags(br.GetUInt16())
|
||||
newTile.Height = br.GetInt32()
|
||||
newTile.Width = br.GetInt32()
|
||||
br.SkipBytes(4)
|
||||
newTile.Type = br.GetInt32()
|
||||
newTile.Style = br.GetInt32()
|
||||
newTile.Sequence = br.GetInt32()
|
||||
newTile.RarityFrameIndex = br.GetInt32()
|
||||
br.SkipBytes(4)
|
||||
for i := range newTile.SubTileFlags {
|
||||
newTile.SubTileFlags[i] = NewSubTileFlags(br.GetByte())
|
||||
}
|
||||
br.SkipBytes(7)
|
||||
newTile.blockHeaderPointer = br.GetInt32()
|
||||
newTile.blockHeaderSize = br.GetInt32()
|
||||
newTile.Blocks = make([]Block, br.GetInt32())
|
||||
br.SkipBytes(12)
|
||||
result.Tiles[tileIdx] = newTile
|
||||
}
|
||||
for tileIdx, tile := range result.Tiles {
|
||||
br.SetPosition(uint64(tile.blockHeaderPointer))
|
||||
for blockIdx := range tile.Blocks {
|
||||
result.Tiles[tileIdx].Blocks[blockIdx].X = br.GetInt16()
|
||||
result.Tiles[tileIdx].Blocks[blockIdx].Y = br.GetInt16()
|
||||
br.SkipBytes(2)
|
||||
result.Tiles[tileIdx].Blocks[blockIdx].GridX = br.GetByte()
|
||||
result.Tiles[tileIdx].Blocks[blockIdx].GridY = br.GetByte()
|
||||
formatValue := br.GetInt16()
|
||||
if formatValue == 1 {
|
||||
result.Tiles[tileIdx].Blocks[blockIdx].Format = BlockFormatIsometric
|
||||
} else {
|
||||
result.Tiles[tileIdx].Blocks[blockIdx].Format = BlockFormatRLE
|
||||
}
|
||||
result.Tiles[tileIdx].Blocks[blockIdx].Length = br.GetInt32()
|
||||
br.SkipBytes(2)
|
||||
result.Tiles[tileIdx].Blocks[blockIdx].FileOffset = br.GetInt32()
|
||||
}
|
||||
for blockIndex, block := range tile.Blocks {
|
||||
br.SetPosition(uint64(tile.blockHeaderPointer + block.FileOffset))
|
||||
encodedData, _ := br.ReadBytes(int(block.Length))
|
||||
tile.Blocks[blockIndex].EncodedData = encodedData
|
||||
}
|
||||
|
||||
}
|
||||
return result
|
||||
}
|
30
d2data/d2dt1/material.go
Normal file
30
d2data/d2dt1/material.go
Normal file
@ -0,0 +1,30 @@
|
||||
package d2dt1
|
||||
|
||||
// Lots of unknowns for now
|
||||
type MaterialFlags struct {
|
||||
Other bool
|
||||
Water bool
|
||||
WoodObject bool
|
||||
InsideStone bool
|
||||
OutsideStone bool
|
||||
Dirt bool
|
||||
Sand bool
|
||||
Wood bool
|
||||
Lava bool
|
||||
Snow bool
|
||||
}
|
||||
|
||||
func NewMaterialFlags(data uint16) MaterialFlags {
|
||||
return MaterialFlags{
|
||||
Other: data&0x0001 == 0x0001,
|
||||
Water: data&0x0002 == 0x0002,
|
||||
WoodObject: data&0x0004 == 0x0004,
|
||||
InsideStone: data&0x0008 == 0x0008,
|
||||
OutsideStone: data&0x0010 == 0x0010,
|
||||
Dirt: data&0x0020 == 0x0020,
|
||||
Sand: data&0x0040 == 0x0040,
|
||||
Wood: data&0x0080 == 0x0080,
|
||||
Lava: data&0x0100 == 0x0100,
|
||||
Snow: data&0x0400 == 0x0400,
|
||||
}
|
||||
}
|
25
d2data/d2dt1/subtile.go
Normal file
25
d2data/d2dt1/subtile.go
Normal file
@ -0,0 +1,25 @@
|
||||
package d2dt1
|
||||
|
||||
type SubTileFlags struct {
|
||||
BlockWalk bool
|
||||
BlockLOS bool
|
||||
BlockJump bool
|
||||
BlockPlayerWalk bool
|
||||
Unknown1 bool
|
||||
BlockLight bool
|
||||
Unknown2 bool
|
||||
Unknown3 bool
|
||||
}
|
||||
|
||||
func NewSubTileFlags(data byte) SubTileFlags {
|
||||
return SubTileFlags{
|
||||
BlockWalk: data & 1 == 1,
|
||||
BlockLOS: data & 2 == 2,
|
||||
BlockJump: data & 4 == 4,
|
||||
BlockPlayerWalk: data & 8 == 8,
|
||||
Unknown1: data & 16 == 16,
|
||||
BlockLight: data & 32 == 32,
|
||||
Unknown2: data & 64 == 64,
|
||||
Unknown3: data & 128 == 128,
|
||||
}
|
||||
}
|
24
d2data/d2dt1/subtile_test.go
Normal file
24
d2data/d2dt1/subtile_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
package d2dt1
|
||||
|
||||
import (
|
||||
testify "github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewSubTile(t *testing.T) {
|
||||
assert := testify.New(t)
|
||||
data := []byte{1, 2, 4, 8, 16, 32, 64, 128}
|
||||
|
||||
for i, b := range data {
|
||||
tile := NewSubTileFlags(b)
|
||||
assert.Equal(i == 0, tile.BlockWalk)
|
||||
assert.Equal(i == 1, tile.BlockLOS)
|
||||
assert.Equal(i == 2, tile.BlockJump)
|
||||
assert.Equal(i == 3, tile.BlockPlayerWalk)
|
||||
assert.Equal(i == 4, tile.Unknown1)
|
||||
assert.Equal(i == 5, tile.BlockLight)
|
||||
assert.Equal(i == 6, tile.Unknown2)
|
||||
assert.Equal(i == 7, tile.Unknown3)
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user