mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-20 23:47:16 -05:00
Merge branch 'master' into date-encoder-font
This commit is contained in:
commit
298fc786b8
@ -1,10 +1,15 @@
|
||||
package d2datautils
|
||||
|
||||
import "bytes"
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
)
|
||||
|
||||
// StreamWriter allows you to create a byte array by streaming in writes of various sizes
|
||||
type StreamWriter struct {
|
||||
data *bytes.Buffer
|
||||
data *bytes.Buffer
|
||||
bitOffset int
|
||||
bitCache byte
|
||||
}
|
||||
|
||||
// CreateStreamWriter creates a new StreamWriter instance
|
||||
@ -28,6 +33,66 @@ func (v *StreamWriter) PushBytes(b ...byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// PushBit pushes single bit into stream
|
||||
// WARNING: if you'll use PushBit, offset'll be less than 8, and if you'll
|
||||
// use another Push... method, bits'll not be pushed
|
||||
func (v *StreamWriter) PushBit(b bool) {
|
||||
if b {
|
||||
v.bitCache |= (1 << v.bitOffset)
|
||||
}
|
||||
v.bitOffset++
|
||||
|
||||
if v.bitOffset != bitsPerByte {
|
||||
return
|
||||
}
|
||||
|
||||
v.PushBytes(v.bitCache)
|
||||
v.bitCache = 0
|
||||
v.bitOffset = 0
|
||||
}
|
||||
|
||||
// PushBits pushes bits (with max range 8)
|
||||
func (v *StreamWriter) PushBits(b byte, bits int) {
|
||||
if bits > bitsPerByte {
|
||||
log.Print("input bits number must be less (or equal) than 8")
|
||||
}
|
||||
|
||||
val := b
|
||||
|
||||
for i := 0; i < bits; i++ {
|
||||
v.PushBit(val&1 == 1)
|
||||
val >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
// PushBits16 pushes bits (with max range 16)
|
||||
func (v *StreamWriter) PushBits16(b uint16, bits int) {
|
||||
if bits > bitsPerByte*bytesPerint16 {
|
||||
log.Print("input bits number must be less (or equal) than 16")
|
||||
}
|
||||
|
||||
val := b
|
||||
|
||||
for i := 0; i < bits; i++ {
|
||||
v.PushBit(val&1 == 1)
|
||||
val >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
// PushBits32 pushes bits (with max range 32)
|
||||
func (v *StreamWriter) PushBits32(b uint32, bits int) {
|
||||
if bits > bitsPerByte*bytesPerint32 {
|
||||
log.Print("input bits number must be less (or equal) than 32")
|
||||
}
|
||||
|
||||
val := b
|
||||
|
||||
for i := 0; i < bits; i++ {
|
||||
v.PushBit(val&1 == 1)
|
||||
val >>= 1
|
||||
}
|
||||
}
|
||||
|
||||
// PushInt16 writes a int16 word to the stream
|
||||
func (v *StreamWriter) PushInt16(val int16) {
|
||||
v.PushUint16(uint16(val))
|
||||
|
@ -4,6 +4,65 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStreamWriterBits(t *testing.T) {
|
||||
sr := CreateStreamWriter()
|
||||
data := []byte{221, 19}
|
||||
|
||||
for _, i := range data {
|
||||
sr.PushBits(i, bitsPerByte)
|
||||
}
|
||||
|
||||
output := sr.GetBytes()
|
||||
for i, d := range data {
|
||||
if output[i] != d {
|
||||
t.Fatalf("sr.PushBits() pushed %X, but wrote %X instead", d, output[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamWriterBits16(t *testing.T) {
|
||||
sr := CreateStreamWriter()
|
||||
data := []uint16{1024, 19}
|
||||
|
||||
for _, i := range data {
|
||||
sr.PushBits16(i, bitsPerByte*bytesPerint16)
|
||||
}
|
||||
|
||||
output := sr.GetBytes()
|
||||
|
||||
for i, d := range data {
|
||||
// nolint:gomnd // offset in byte slice; bit shifts for uint16
|
||||
outputInt := uint16(output[bytesPerint16*i]) |
|
||||
uint16(output[bytesPerint16*i+1])<<8
|
||||
if outputInt != d {
|
||||
t.Fatalf("sr.PushBits16() pushed %X, but wrote %X instead", d, output[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamWriterBits32(t *testing.T) {
|
||||
sr := CreateStreamWriter()
|
||||
data := []uint32{19324, 87}
|
||||
|
||||
for _, i := range data {
|
||||
sr.PushBits32(i, bitsPerByte*bytesPerint32)
|
||||
}
|
||||
|
||||
output := sr.GetBytes()
|
||||
|
||||
for i, d := range data {
|
||||
// nolint:gomnd // offset in byte slice; bit shifts for uint32
|
||||
outputInt := uint32(output[bytesPerint32*i]) |
|
||||
uint32(output[bytesPerint32*i+1])<<8 |
|
||||
uint32(output[bytesPerint32*i+2])<<16 |
|
||||
uint32(output[bytesPerint32*i+3])<<24
|
||||
|
||||
if outputInt != d {
|
||||
t.Fatalf("sr.PushBits32() pushed %X, but wrote %X instead", d, output[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamWriterByte(t *testing.T) {
|
||||
sr := CreateStreamWriter()
|
||||
data := []byte{0x12, 0x34, 0x56, 0x78}
|
||||
|
@ -121,7 +121,6 @@ func (c *COF) Unmarshal(fileData []byte) error {
|
||||
layer.Transparent = b[layerTransparent] > 0
|
||||
layer.DrawEffect = d2enum.DrawEffect(b[layerDrawEffect])
|
||||
|
||||
layer.weaponClassByte = b[layerWeaponClass:]
|
||||
layer.WeaponClass = d2enum.WeaponClassFromString(strings.TrimSpace(strings.ReplaceAll(
|
||||
string(b[layerWeaponClass:]), badCharacter, "")))
|
||||
|
||||
@ -193,7 +192,22 @@ func (c *COF) Marshal() []byte {
|
||||
|
||||
sw.PushBytes(byte(c.CofLayers[i].DrawEffect))
|
||||
|
||||
sw.PushBytes(c.CofLayers[i].weaponClassByte...)
|
||||
const (
|
||||
maxCodeLength = 3 // we assume item codes to look like 'hax' or 'kit'
|
||||
terminator = 0
|
||||
)
|
||||
|
||||
weaponCode := c.CofLayers[i].WeaponClass.String()
|
||||
|
||||
for idx, letter := range weaponCode {
|
||||
if idx > maxCodeLength {
|
||||
break
|
||||
}
|
||||
|
||||
sw.PushBytes(byte(letter))
|
||||
}
|
||||
|
||||
sw.PushBytes(terminator)
|
||||
}
|
||||
|
||||
for _, i := range c.AnimationFrames {
|
||||
|
@ -4,11 +4,10 @@ import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
|
||||
// CofLayer is a structure that represents a single layer in a COF file.
|
||||
type CofLayer struct {
|
||||
Type d2enum.CompositeType
|
||||
Shadow byte
|
||||
Selectable bool
|
||||
Transparent bool
|
||||
DrawEffect d2enum.DrawEffect
|
||||
WeaponClass d2enum.WeaponClass
|
||||
weaponClassByte []byte
|
||||
Type d2enum.CompositeType
|
||||
Shadow byte
|
||||
Selectable bool
|
||||
Transparent bool
|
||||
DrawEffect d2enum.DrawEffect
|
||||
WeaponClass d2enum.WeaponClass
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package d2dat
|
||||
|
||||
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
)
|
||||
|
||||
const (
|
||||
// index offset helpers
|
||||
@ -21,3 +23,14 @@ func Load(data []byte) (d2interface.Palette, error) {
|
||||
|
||||
return palette, nil
|
||||
}
|
||||
|
||||
// Marshal encodes data palette back into byte slice
|
||||
func (p *DATPalette) Marshal() []byte {
|
||||
result := make([]byte, len(p.colors))
|
||||
|
||||
for _, i := range &p.colors {
|
||||
result = append(result, i.B(), i.G(), i.R())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -32,36 +32,62 @@ type DC6 struct {
|
||||
Frames []*DC6Frame // size is Directions*FramesPerDirection
|
||||
}
|
||||
|
||||
// Load uses restruct to read the binary dc6 data into structs then parses image data from the frame data.
|
||||
// New creates a new, empty DC6
|
||||
func New() *DC6 {
|
||||
result := &DC6{
|
||||
Version: 0,
|
||||
Flags: 0,
|
||||
Encoding: 0,
|
||||
Termination: make([]byte, 4),
|
||||
Directions: 0,
|
||||
FramesPerDirection: 0,
|
||||
FramePointers: make([]uint32, 0),
|
||||
Frames: make([]*DC6Frame, 0),
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Load loads a dc6 animation
|
||||
func Load(data []byte) (*DC6, error) {
|
||||
r := d2datautils.CreateStreamReader(data)
|
||||
d := New()
|
||||
|
||||
var dc DC6
|
||||
|
||||
var err error
|
||||
|
||||
err = dc.loadHeader(r)
|
||||
err := d.Unmarshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
frameCount := int(dc.Directions * dc.FramesPerDirection)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
dc.FramePointers = make([]uint32, frameCount)
|
||||
// Unmarshal converts bite slice into DC6 structure
|
||||
func (d *DC6) Unmarshal(data []byte) error {
|
||||
var err error
|
||||
|
||||
r := d2datautils.CreateStreamReader(data)
|
||||
|
||||
err = d.loadHeader(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
frameCount := int(d.Directions * d.FramesPerDirection)
|
||||
|
||||
d.FramePointers = make([]uint32, frameCount)
|
||||
for i := 0; i < frameCount; i++ {
|
||||
dc.FramePointers[i], err = r.ReadUInt32()
|
||||
d.FramePointers[i], err = r.ReadUInt32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
dc.Frames = make([]*DC6Frame, frameCount)
|
||||
d.Frames = make([]*DC6Frame, frameCount)
|
||||
|
||||
if err := dc.loadFrames(r); err != nil {
|
||||
return nil, err
|
||||
if err := d.loadFrames(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return &dc, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DC6) loadHeader(r *d2datautils.StreamReader) error {
|
||||
@ -241,7 +267,7 @@ func (d *DC6) Clone() *DC6 {
|
||||
|
||||
for i := range d.Frames {
|
||||
cloneFrame := *d.Frames[i]
|
||||
clone.Frames = append(clone.Frames, &cloneFrame)
|
||||
clone.Frames[i] = &cloneFrame
|
||||
}
|
||||
|
||||
return &clone
|
||||
|
69
d2common/d2fileformats/d2dc6/dc6_test.go
Normal file
69
d2common/d2fileformats/d2dc6/dc6_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
package d2dc6
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDC6New(t *testing.T) {
|
||||
dc6 := New()
|
||||
|
||||
if dc6 == nil {
|
||||
t.Error("d2dc6.New() method returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func getExampleDC6() *DC6 {
|
||||
exampleDC6 := &DC6{
|
||||
Version: 6,
|
||||
Flags: 1,
|
||||
Encoding: 0,
|
||||
Termination: []byte{238, 238, 238, 238},
|
||||
Directions: 1,
|
||||
FramesPerDirection: 1,
|
||||
FramePointers: []uint32{56},
|
||||
Frames: []*DC6Frame{
|
||||
{
|
||||
Flipped: 0,
|
||||
Width: 32,
|
||||
Height: 26,
|
||||
OffsetX: 45,
|
||||
OffsetY: 24,
|
||||
Unknown: 0,
|
||||
NextBlock: 50,
|
||||
Length: 10,
|
||||
FrameData: []byte{2, 23, 34, 128, 53, 64, 39, 43, 123, 12},
|
||||
Terminator: []byte{2, 8, 5},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return exampleDC6
|
||||
}
|
||||
|
||||
func TestDC6Unmarshal(t *testing.T) {
|
||||
exampleDC6 := getExampleDC6()
|
||||
|
||||
data := exampleDC6.Marshal()
|
||||
|
||||
extractedDC6, err := Load(data)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if exampleDC6.Version != extractedDC6.Version ||
|
||||
len(exampleDC6.Frames) != len(extractedDC6.Frames) ||
|
||||
exampleDC6.Frames[0].NextBlock != extractedDC6.Frames[0].NextBlock {
|
||||
t.Fatal("encoded and decoded DC6 isn't the same")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDC6Clone(t *testing.T) {
|
||||
exampleDC6 := getExampleDC6()
|
||||
clonedDC6 := exampleDC6.Clone()
|
||||
|
||||
if exampleDC6.Termination[0] != clonedDC6.Termination[0] ||
|
||||
len(exampleDC6.Frames) != len(clonedDC6.Frames) ||
|
||||
exampleDC6.Frames[0].NextBlock != clonedDC6.Frames[0].NextBlock {
|
||||
t.Fatal("cloned dc6 isn't equal to original")
|
||||
}
|
||||
}
|
@ -9,22 +9,32 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
maxActNumber = 5
|
||||
subType1 = 1
|
||||
subType2 = 2
|
||||
v2 = 2
|
||||
v3 = 3
|
||||
v4 = 4
|
||||
v7 = 7
|
||||
v8 = 8
|
||||
v9 = 9
|
||||
v10 = 10
|
||||
v12 = 12
|
||||
v13 = 13
|
||||
v14 = 14
|
||||
v15 = 15
|
||||
v16 = 16
|
||||
v18 = 18
|
||||
subType1 = 1
|
||||
subType2 = 2
|
||||
v2 = 2
|
||||
v3 = 3
|
||||
v4 = 4
|
||||
v7 = 7
|
||||
v8 = 8
|
||||
v9 = 9
|
||||
v10 = 10
|
||||
v12 = 12
|
||||
v13 = 13
|
||||
v14 = 14
|
||||
v15 = 15
|
||||
v16 = 16
|
||||
v18 = 18
|
||||
)
|
||||
|
||||
const (
|
||||
wallZeroBitmask = 0xFFFFFF00
|
||||
wallZeroOffset = 8
|
||||
|
||||
wallTypeBitmask = 0x000000FF
|
||||
)
|
||||
|
||||
const (
|
||||
unknown1BytesCount = 8
|
||||
)
|
||||
|
||||
// DS1 represents the "stamp" data that is used to build up maps.
|
||||
@ -43,10 +53,13 @@ type DS1 struct {
|
||||
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
|
||||
unknown1 []byte
|
||||
layerStreamTypes []d2enum.LayerStreamType
|
||||
unknown2 uint32
|
||||
npcIndexes []int
|
||||
}
|
||||
|
||||
// LoadDS1 loads the specified DS1 file
|
||||
//nolint:funlen,gocognit,gocyclo // will refactor later
|
||||
func LoadDS1(fileData []byte) (*DS1, error) {
|
||||
ds1 := &DS1{
|
||||
Act: 1,
|
||||
@ -60,74 +73,17 @@ func LoadDS1(fileData []byte) (*DS1, error) {
|
||||
|
||||
var err error
|
||||
|
||||
ds1.Version, err = br.ReadInt32()
|
||||
err = ds1.loadHeader(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ds1.Width, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ds1.Height, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ds1.Width++
|
||||
ds1.Height++
|
||||
|
||||
if ds1.Version >= v8 {
|
||||
ds1.Act, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ds1.Act = d2math.MinInt32(maxActNumber, ds1.Act+1)
|
||||
}
|
||||
|
||||
if ds1.Version >= v10 {
|
||||
ds1.SubstitutionType, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2 {
|
||||
ds1.NumberOfSubstitutionLayers = 1
|
||||
}
|
||||
}
|
||||
|
||||
if ds1.Version >= v3 {
|
||||
// These files reference things that don't exist anymore :-?
|
||||
numberOfFiles, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable...
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ds1.Files = make([]string, numberOfFiles)
|
||||
|
||||
for i := 0; i < int(numberOfFiles); i++ {
|
||||
ds1.Files[i] = ""
|
||||
|
||||
for {
|
||||
ch, err := br.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ch == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
ds1.Files[i] += string(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ds1.Version >= v9 && ds1.Version <= v13 {
|
||||
// Skipping two dwords because they are "meaningless"?
|
||||
br.SkipBytes(8) //nolint:gomnd // We don't know what's here
|
||||
ds1.unknown1, err = br.ReadBytes(unknown1BytesCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if ds1.Version >= v4 {
|
||||
@ -146,7 +102,7 @@ func LoadDS1(fileData []byte) (*DS1, error) {
|
||||
}
|
||||
}
|
||||
|
||||
layerStream := ds1.setupStreamLayerTypes()
|
||||
ds1.layerStreamTypes = ds1.setupStreamLayerTypes()
|
||||
|
||||
ds1.Tiles = make([][]TileRecord, ds1.Height)
|
||||
|
||||
@ -160,7 +116,7 @@ func LoadDS1(fileData []byte) (*DS1, error) {
|
||||
}
|
||||
}
|
||||
|
||||
err = ds1.loadLayerStreams(br, layerStream)
|
||||
err = ds1.loadLayerStreams(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -183,6 +139,86 @@ func LoadDS1(fileData []byte) (*DS1, error) {
|
||||
return ds1, nil
|
||||
}
|
||||
|
||||
func (ds1 *DS1) loadHeader(br *d2datautils.StreamReader) error {
|
||||
var err error
|
||||
|
||||
ds1.Version, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ds1.Width, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ds1.Height, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ds1.Width++
|
||||
ds1.Height++
|
||||
|
||||
if ds1.Version >= v8 {
|
||||
ds1.Act, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ds1.Act = d2math.MinInt32(d2enum.ActsNumber, ds1.Act+1)
|
||||
}
|
||||
|
||||
if ds1.Version >= v10 {
|
||||
ds1.SubstitutionType, err = br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ds1.SubstitutionType == 1 || ds1.SubstitutionType == 2 {
|
||||
ds1.NumberOfSubstitutionLayers = 1
|
||||
}
|
||||
}
|
||||
|
||||
err = ds1.loadFileList(br)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds1 *DS1) loadFileList(br *d2datautils.StreamReader) error {
|
||||
if ds1.Version >= v3 {
|
||||
// These files reference things that don't exist anymore :-?
|
||||
numberOfFiles, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ds1.Files = make([]string, numberOfFiles)
|
||||
|
||||
for i := 0; i < int(numberOfFiles); i++ {
|
||||
ds1.Files[i] = ""
|
||||
|
||||
for {
|
||||
ch, err := br.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ch == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
ds1.Files[i] += string(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error {
|
||||
if ds1.Version < v2 {
|
||||
ds1.Objects = make([]Object, 0)
|
||||
@ -245,7 +281,10 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error {
|
||||
}
|
||||
|
||||
if ds1.Version >= v18 {
|
||||
_, _ = br.ReadUInt32()
|
||||
ds1.unknown2, err = br.ReadUInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
numberOfSubGroups, err := br.ReadInt32()
|
||||
@ -340,17 +379,17 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error {
|
||||
}
|
||||
|
||||
for npcIdx := 0; npcIdx < int(numberOfNpcs); npcIdx++ {
|
||||
numPaths, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable...
|
||||
numPaths, err := br.ReadInt32() // nolint:govet // I want to re-use this error variable
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
npcX, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable...
|
||||
npcX, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
npcY, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable...
|
||||
npcY, err := br.ReadInt32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -360,6 +399,8 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error {
|
||||
for idx, ds1Obj := range ds1.Objects {
|
||||
if ds1Obj.X == int(npcX) && ds1Obj.Y == int(npcY) {
|
||||
objIdx = idx
|
||||
ds1.npcIndexes = append(ds1.npcIndexes, idx)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -418,7 +459,7 @@ func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int)
|
||||
return err
|
||||
}
|
||||
|
||||
func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2enum.LayerStreamType) error {
|
||||
func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error {
|
||||
var err error
|
||||
|
||||
var dirLookup = []int32{
|
||||
@ -427,8 +468,8 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
|
||||
0x0F, 0x10, 0x11, 0x12, 0x14,
|
||||
}
|
||||
|
||||
for lIdx := range layerStream {
|
||||
layerStreamType := layerStream[lIdx]
|
||||
for lIdx := range ds1.layerStreamTypes {
|
||||
layerStreamType := ds1.layerStreamTypes[lIdx]
|
||||
|
||||
for y := 0; y < int(ds1.Height); y++ {
|
||||
for x := 0; x < int(ds1.Width); x++ {
|
||||
@ -440,16 +481,11 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
|
||||
switch layerStreamType {
|
||||
case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4:
|
||||
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Prop1 = byte(dw & 0x000000FF) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Sequence = byte((dw & 0x00003F00) >> 8) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Hidden = byte((dw&0x80000000)>>31) > 0 //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Decode(dw)
|
||||
case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2,
|
||||
d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4:
|
||||
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1)
|
||||
c := int32(dw & 0x000000FF) //nolint:gomnd // Bitmask
|
||||
c := int32(dw & wallTypeBitmask)
|
||||
|
||||
if ds1.Version < v7 {
|
||||
if c < int32(len(dirLookup)) {
|
||||
@ -458,22 +494,12 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
|
||||
}
|
||||
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Type = d2enum.TileType(c)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Zero = byte((dw & 0xFFFFFF00) >> 8) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Zero = byte((dw & wallZeroBitmask) >> wallZeroOffset)
|
||||
case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2:
|
||||
floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1)
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Prop1 = byte(dw & 0x000000FF) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Sequence = byte((dw & 0x00003F00) >> 8) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Hidden = byte((dw&0x80000000)>>31) > 0 //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Decode(dw)
|
||||
case d2enum.LayerStreamShadow:
|
||||
ds1.Tiles[y][x].Shadows[0].Prop1 = byte(dw & 0x000000FF) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Shadows[0].Sequence = byte((dw & 0x00003F00) >> 8) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Shadows[0].Unknown1 = byte((dw & 0x000FC000) >> 14) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Shadows[0].Style = byte((dw & 0x03F00000) >> 20) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Shadows[0].Unknown2 = byte((dw & 0x7C000000) >> 26) //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Shadows[0].Hidden = byte((dw&0x80000000)>>31) > 0 //nolint:gomnd // Bitmask
|
||||
ds1.Tiles[y][x].Shadows[0].Decode(dw)
|
||||
case d2enum.LayerStreamSubstitute:
|
||||
ds1.Tiles[y][x].Substitutions[0].Unknown = dw
|
||||
}
|
||||
@ -483,3 +509,134 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Marshal encodes ds1 back to byte slice
|
||||
func (ds1 *DS1) Marshal() []byte {
|
||||
// create stream writer
|
||||
sw := d2datautils.CreateStreamWriter()
|
||||
|
||||
// Step 1 - encode header
|
||||
sw.PushInt32(ds1.Version)
|
||||
sw.PushInt32(ds1.Width - 1)
|
||||
sw.PushInt32(ds1.Height - 1)
|
||||
|
||||
if ds1.Version >= v8 {
|
||||
sw.PushInt32(ds1.Act - 1)
|
||||
}
|
||||
|
||||
if ds1.Version >= v10 {
|
||||
sw.PushInt32(ds1.SubstitutionType)
|
||||
}
|
||||
|
||||
if ds1.Version >= v3 {
|
||||
sw.PushInt32(int32(len(ds1.Files)))
|
||||
|
||||
for _, i := range ds1.Files {
|
||||
sw.PushBytes([]byte(i)...)
|
||||
|
||||
// separator
|
||||
sw.PushBytes(0)
|
||||
}
|
||||
}
|
||||
|
||||
if ds1.Version >= v9 && ds1.Version <= v13 {
|
||||
sw.PushBytes(ds1.unknown1...)
|
||||
}
|
||||
|
||||
if ds1.Version >= v4 {
|
||||
sw.PushInt32(ds1.NumberOfWalls)
|
||||
|
||||
if ds1.Version >= v16 {
|
||||
sw.PushInt32(ds1.NumberOfFloors)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2 - encode layers
|
||||
ds1.encodeLayers(sw)
|
||||
|
||||
// Step 3 - encode objects
|
||||
if !(ds1.Version < v2) {
|
||||
sw.PushInt32(int32(len(ds1.Objects)))
|
||||
|
||||
for _, i := range ds1.Objects {
|
||||
sw.PushUint32(uint32(i.Type))
|
||||
sw.PushUint32(uint32(i.ID))
|
||||
sw.PushUint32(uint32(i.X))
|
||||
sw.PushUint32(uint32(i.Y))
|
||||
sw.PushUint32(uint32(i.Flags))
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4 - encode substitutions
|
||||
if ds1.Version >= v12 && (ds1.SubstitutionType == subType1 || ds1.SubstitutionType == subType2) {
|
||||
sw.PushUint32(ds1.unknown2)
|
||||
|
||||
sw.PushUint32(uint32(len(ds1.SubstitutionGroups)))
|
||||
|
||||
for _, i := range ds1.SubstitutionGroups {
|
||||
sw.PushInt32(i.TileX)
|
||||
sw.PushInt32(i.TileY)
|
||||
sw.PushInt32(i.WidthInTiles)
|
||||
sw.PushInt32(i.HeightInTiles)
|
||||
sw.PushInt32(i.Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5 - encode NPC's and its paths
|
||||
ds1.encodeNPCs(sw)
|
||||
|
||||
return sw.GetBytes()
|
||||
}
|
||||
|
||||
func (ds1 *DS1) encodeLayers(sw *d2datautils.StreamWriter) {
|
||||
for lIdx := range ds1.layerStreamTypes {
|
||||
layerStreamType := ds1.layerStreamTypes[lIdx]
|
||||
|
||||
for y := 0; y < int(ds1.Height); y++ {
|
||||
for x := 0; x < int(ds1.Width); x++ {
|
||||
dw := uint32(0)
|
||||
|
||||
switch layerStreamType {
|
||||
case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4:
|
||||
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1)
|
||||
ds1.Tiles[y][x].Walls[wallIndex].Encode(sw)
|
||||
case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2,
|
||||
d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4:
|
||||
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1)
|
||||
dw |= uint32(ds1.Tiles[y][x].Walls[wallIndex].Type)
|
||||
dw |= (uint32(ds1.Tiles[y][x].Walls[wallIndex].Zero) & wallZeroBitmask) << wallZeroOffset
|
||||
|
||||
sw.PushUint32(dw)
|
||||
case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2:
|
||||
floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1)
|
||||
ds1.Tiles[y][x].Floors[floorIndex].Encode(sw)
|
||||
case d2enum.LayerStreamShadow:
|
||||
ds1.Tiles[y][x].Shadows[0].Encode(sw)
|
||||
case d2enum.LayerStreamSubstitute:
|
||||
sw.PushUint32(ds1.Tiles[y][x].Substitutions[0].Unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ds1 *DS1) encodeNPCs(sw *d2datautils.StreamWriter) {
|
||||
// Step 5.1 - encode npc's
|
||||
sw.PushUint32(uint32(len(ds1.npcIndexes)))
|
||||
|
||||
// Step 5.2 - enoce npcs' paths
|
||||
for _, i := range ds1.npcIndexes {
|
||||
sw.PushUint32(uint32(len(ds1.Objects[i].Paths)))
|
||||
sw.PushUint32(uint32(ds1.Objects[i].X))
|
||||
sw.PushUint32(uint32(ds1.Objects[i].Y))
|
||||
|
||||
for _, j := range ds1.Objects[i].Paths {
|
||||
sw.PushUint32(uint32(j.Position.X()))
|
||||
sw.PushUint32(uint32(j.Position.Y()))
|
||||
|
||||
if ds1.Version >= v15 {
|
||||
sw.PushUint32(uint32(j.Action))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,35 @@
|
||||
package d2ds1
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
|
||||
)
|
||||
|
||||
const (
|
||||
prop1Bitmask = 0x000000FF
|
||||
prop1Offset = 0
|
||||
prop1Length = 8
|
||||
|
||||
sequenceBitmask = 0x00003F00
|
||||
sequenceOffset = 8
|
||||
sequenceLength = 6
|
||||
|
||||
unknown1Bitmask = 0x000FC000
|
||||
unknown1Offset = 14
|
||||
unknown1Length = 6
|
||||
|
||||
styleBitmask = 0x03F00000
|
||||
styleOffset = 20
|
||||
styleLength = 6
|
||||
|
||||
unknown2Bitmask = 0x7C000000
|
||||
unknown2Offset = 26
|
||||
unknown2Length = 5
|
||||
|
||||
hiddenBitmask = 0x80000000
|
||||
hiddenOffset = 31
|
||||
hiddenLength = 1
|
||||
)
|
||||
|
||||
// FloorShadowRecord represents a floor or shadow record in a DS1 file.
|
||||
type FloorShadowRecord struct {
|
||||
Prop1 byte
|
||||
@ -7,8 +37,33 @@ type FloorShadowRecord struct {
|
||||
Unknown1 byte
|
||||
Style byte
|
||||
Unknown2 byte
|
||||
Hidden bool
|
||||
hidden byte
|
||||
RandomIndex byte
|
||||
Animated bool
|
||||
YAdjust int
|
||||
}
|
||||
|
||||
// Hidden returns if floor/shadow is hidden
|
||||
func (f *FloorShadowRecord) Hidden() bool {
|
||||
return f.hidden > 0
|
||||
}
|
||||
|
||||
// Decode decodes floor-shadow record
|
||||
func (f *FloorShadowRecord) Decode(dw uint32) {
|
||||
f.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset)
|
||||
f.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset)
|
||||
f.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset)
|
||||
f.Style = byte((dw & styleBitmask) >> styleOffset)
|
||||
f.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset)
|
||||
f.hidden = byte((dw & hiddenBitmask) >> hiddenOffset)
|
||||
}
|
||||
|
||||
// Encode adds Floor's bits to stream writter given
|
||||
func (f *FloorShadowRecord) Encode(sw *d2datautils.StreamWriter) {
|
||||
sw.PushBits32(uint32(f.Prop1), prop1Length)
|
||||
sw.PushBits32(uint32(f.Sequence), sequenceLength)
|
||||
sw.PushBits32(uint32(f.Unknown1), unknown1Length)
|
||||
sw.PushBits32(uint32(f.Style), styleLength)
|
||||
sw.PushBits32(uint32(f.Unknown2), unknown2Length)
|
||||
sw.PushBits32(uint32(f.hidden), hiddenLength)
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package d2ds1
|
||||
|
||||
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2datautils"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
)
|
||||
|
||||
// WallRecord represents a wall record.
|
||||
type WallRecord struct {
|
||||
@ -11,7 +14,32 @@ type WallRecord struct {
|
||||
Unknown1 byte
|
||||
Style byte
|
||||
Unknown2 byte
|
||||
Hidden bool
|
||||
hidden byte
|
||||
RandomIndex byte
|
||||
YAdjust int
|
||||
}
|
||||
|
||||
// Hidden returns if wall is hidden
|
||||
func (w *WallRecord) Hidden() bool {
|
||||
return w.hidden > 0
|
||||
}
|
||||
|
||||
// Decode decodes wall record
|
||||
func (w *WallRecord) Decode(dw uint32) {
|
||||
w.Prop1 = byte((dw & prop1Bitmask) >> prop1Offset)
|
||||
w.Sequence = byte((dw & sequenceBitmask) >> sequenceOffset)
|
||||
w.Unknown1 = byte((dw & unknown1Bitmask) >> unknown1Offset)
|
||||
w.Style = byte((dw & styleBitmask) >> styleOffset)
|
||||
w.Unknown2 = byte((dw & unknown2Bitmask) >> unknown2Offset)
|
||||
w.hidden = byte((dw & hiddenBitmask) >> hiddenOffset)
|
||||
}
|
||||
|
||||
// Encode adds wall's record's bytes into stream writer given
|
||||
func (w *WallRecord) Encode(sw *d2datautils.StreamWriter) {
|
||||
sw.PushBits32(uint32(w.Prop1), prop1Length)
|
||||
sw.PushBits32(uint32(w.Sequence), sequenceLength)
|
||||
sw.PushBits32(uint32(w.Unknown1), unknown1Length)
|
||||
sw.PushBits32(uint32(w.Style), styleLength)
|
||||
sw.PushBits32(uint32(w.Unknown2), unknown2Length)
|
||||
sw.PushBits32(uint32(w.hidden), hiddenLength)
|
||||
}
|
||||
|
@ -359,19 +359,19 @@ func (mr *MapRenderer) renderPass4(target d2interface.Surface, startX, startY, e
|
||||
|
||||
func (mr *MapRenderer) renderTilePass1(tile *d2mapengine.MapTile, target d2interface.Surface) {
|
||||
for _, wall := range tile.Components.Walls {
|
||||
if !wall.Hidden && wall.Prop1 != 0 && wall.Type.LowerWall() {
|
||||
if !wall.Hidden() && wall.Prop1 != 0 && wall.Type.LowerWall() {
|
||||
mr.renderWall(wall, mr.viewport, target)
|
||||
}
|
||||
}
|
||||
|
||||
for _, floor := range tile.Components.Floors {
|
||||
if !floor.Hidden && floor.Prop1 != 0 {
|
||||
if !floor.Hidden() && floor.Prop1 != 0 {
|
||||
mr.renderFloor(floor, target)
|
||||
}
|
||||
}
|
||||
|
||||
for _, shadow := range tile.Components.Shadows {
|
||||
if !shadow.Hidden && shadow.Prop1 != 0 {
|
||||
if !shadow.Hidden() && shadow.Prop1 != 0 {
|
||||
mr.renderShadow(shadow, target)
|
||||
}
|
||||
}
|
||||
@ -379,7 +379,7 @@ func (mr *MapRenderer) renderTilePass1(tile *d2mapengine.MapTile, target d2inter
|
||||
|
||||
func (mr *MapRenderer) renderTilePass2(tile *d2mapengine.MapTile, target d2interface.Surface) {
|
||||
for _, wall := range tile.Components.Walls {
|
||||
if !wall.Hidden && wall.Type.UpperWall() {
|
||||
if !wall.Hidden() && wall.Type.UpperWall() {
|
||||
mr.renderWall(wall, mr.viewport, target)
|
||||
}
|
||||
}
|
||||
|
@ -34,19 +34,19 @@ func (mr *MapRenderer) generateTileCache() {
|
||||
tile := &tiles[idx]
|
||||
|
||||
for i := range tile.Components.Floors {
|
||||
if !tile.Components.Floors[i].Hidden && tile.Components.Floors[i].Prop1 != 0 {
|
||||
if !tile.Components.Floors[i].Hidden() && tile.Components.Floors[i].Prop1 != 0 {
|
||||
mr.generateFloorCache(&tile.Components.Floors[i])
|
||||
}
|
||||
}
|
||||
|
||||
for i := range tile.Components.Shadows {
|
||||
if !tile.Components.Shadows[i].Hidden && tile.Components.Shadows[i].Prop1 != 0 {
|
||||
if !tile.Components.Shadows[i].Hidden() && tile.Components.Shadows[i].Prop1 != 0 {
|
||||
mr.generateShadowCache(&tile.Components.Shadows[i])
|
||||
}
|
||||
}
|
||||
|
||||
for i := range tile.Components.Walls {
|
||||
if !tile.Components.Walls[i].Hidden && tile.Components.Walls[i].Prop1 != 0 {
|
||||
if !tile.Components.Walls[i].Hidden() && tile.Components.Walls[i].Prop1 != 0 {
|
||||
mr.generateWallCache(&tile.Components.Walls[i])
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user