1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-11-18 02:16:23 -05:00

Merge branch 'master' into date-encoder-font

This commit is contained in:
gravestench 2021-02-10 12:14:41 -08:00 committed by GitHub
commit 298fc786b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 631 additions and 146 deletions

View File

@ -1,10 +1,15 @@
package d2datautils package d2datautils
import "bytes" import (
"bytes"
"log"
)
// StreamWriter allows you to create a byte array by streaming in writes of various sizes // StreamWriter allows you to create a byte array by streaming in writes of various sizes
type StreamWriter struct { type StreamWriter struct {
data *bytes.Buffer data *bytes.Buffer
bitOffset int
bitCache byte
} }
// CreateStreamWriter creates a new StreamWriter instance // 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 // PushInt16 writes a int16 word to the stream
func (v *StreamWriter) PushInt16(val int16) { func (v *StreamWriter) PushInt16(val int16) {
v.PushUint16(uint16(val)) v.PushUint16(uint16(val))

View File

@ -4,6 +4,65 @@ import (
"testing" "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) { func TestStreamWriterByte(t *testing.T) {
sr := CreateStreamWriter() sr := CreateStreamWriter()
data := []byte{0x12, 0x34, 0x56, 0x78} data := []byte{0x12, 0x34, 0x56, 0x78}

View File

@ -121,7 +121,6 @@ func (c *COF) Unmarshal(fileData []byte) error {
layer.Transparent = b[layerTransparent] > 0 layer.Transparent = b[layerTransparent] > 0
layer.DrawEffect = d2enum.DrawEffect(b[layerDrawEffect]) layer.DrawEffect = d2enum.DrawEffect(b[layerDrawEffect])
layer.weaponClassByte = b[layerWeaponClass:]
layer.WeaponClass = d2enum.WeaponClassFromString(strings.TrimSpace(strings.ReplaceAll( layer.WeaponClass = d2enum.WeaponClassFromString(strings.TrimSpace(strings.ReplaceAll(
string(b[layerWeaponClass:]), badCharacter, ""))) string(b[layerWeaponClass:]), badCharacter, "")))
@ -193,7 +192,22 @@ func (c *COF) Marshal() []byte {
sw.PushBytes(byte(c.CofLayers[i].DrawEffect)) 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 { for _, i := range c.AnimationFrames {

View File

@ -4,11 +4,10 @@ import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
// CofLayer is a structure that represents a single layer in a COF file. // CofLayer is a structure that represents a single layer in a COF file.
type CofLayer struct { type CofLayer struct {
Type d2enum.CompositeType Type d2enum.CompositeType
Shadow byte Shadow byte
Selectable bool Selectable bool
Transparent bool Transparent bool
DrawEffect d2enum.DrawEffect DrawEffect d2enum.DrawEffect
WeaponClass d2enum.WeaponClass WeaponClass d2enum.WeaponClass
weaponClassByte []byte
} }

View File

@ -1,6 +1,8 @@
package d2dat package d2dat
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
)
const ( const (
// index offset helpers // index offset helpers
@ -21,3 +23,14 @@ func Load(data []byte) (d2interface.Palette, error) {
return palette, nil 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
}

View File

@ -32,36 +32,62 @@ type DC6 struct {
Frames []*DC6Frame // size is Directions*FramesPerDirection 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) { func Load(data []byte) (*DC6, error) {
r := d2datautils.CreateStreamReader(data) d := New()
var dc DC6 err := d.Unmarshal(data)
var err error
err = dc.loadHeader(r)
if err != nil { if err != nil {
return nil, err 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++ { for i := 0; i < frameCount; i++ {
dc.FramePointers[i], err = r.ReadUInt32() d.FramePointers[i], err = r.ReadUInt32()
if err != nil { 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 { if err := d.loadFrames(r); err != nil {
return nil, err return err
} }
return &dc, nil return nil
} }
func (d *DC6) loadHeader(r *d2datautils.StreamReader) error { func (d *DC6) loadHeader(r *d2datautils.StreamReader) error {
@ -241,7 +267,7 @@ func (d *DC6) Clone() *DC6 {
for i := range d.Frames { for i := range d.Frames {
cloneFrame := *d.Frames[i] cloneFrame := *d.Frames[i]
clone.Frames = append(clone.Frames, &cloneFrame) clone.Frames[i] = &cloneFrame
} }
return &clone return &clone

View 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")
}
}

View File

@ -9,22 +9,32 @@ import (
) )
const ( const (
maxActNumber = 5 subType1 = 1
subType1 = 1 subType2 = 2
subType2 = 2 v2 = 2
v2 = 2 v3 = 3
v3 = 3 v4 = 4
v4 = 4 v7 = 7
v7 = 7 v8 = 8
v8 = 8 v9 = 9
v9 = 9 v10 = 10
v10 = 10 v12 = 12
v12 = 12 v13 = 13
v13 = 13 v14 = 14
v14 = 14 v15 = 15
v15 = 15 v16 = 16
v16 = 16 v18 = 18
v18 = 18 )
const (
wallZeroBitmask = 0xFFFFFF00
wallZeroOffset = 8
wallTypeBitmask = 0x000000FF
)
const (
unknown1BytesCount = 8
) )
// DS1 represents the "stamp" data that is used to build up maps. // 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 NumberOfShadowLayers int32 // ShadowNum number of shadow layer used
NumberOfSubstitutionLayers int32 // SubstitutionNum number of substitution layer used NumberOfSubstitutionLayers int32 // SubstitutionNum number of substitution layer used
SubstitutionGroupsNum int32 // SubstitutionGroupsNum number of substitution groups, datas between objects & NPC paths 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 // LoadDS1 loads the specified DS1 file
//nolint:funlen,gocognit,gocyclo // will refactor later
func LoadDS1(fileData []byte) (*DS1, error) { func LoadDS1(fileData []byte) (*DS1, error) {
ds1 := &DS1{ ds1 := &DS1{
Act: 1, Act: 1,
@ -60,74 +73,17 @@ func LoadDS1(fileData []byte) (*DS1, error) {
var err error var err error
ds1.Version, err = br.ReadInt32() err = ds1.loadHeader(br)
if err != nil { if err != nil {
return nil, err 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 { if ds1.Version >= v9 && ds1.Version <= v13 {
// Skipping two dwords because they are "meaningless"? // 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 { 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) 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 { if err != nil {
return nil, err return nil, err
} }
@ -183,6 +139,86 @@ func LoadDS1(fileData []byte) (*DS1, error) {
return ds1, nil 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 { func (ds1 *DS1) loadObjects(br *d2datautils.StreamReader) error {
if ds1.Version < v2 { if ds1.Version < v2 {
ds1.Objects = make([]Object, 0) ds1.Objects = make([]Object, 0)
@ -245,7 +281,10 @@ func (ds1 *DS1) loadSubstitutions(br *d2datautils.StreamReader) error {
} }
if ds1.Version >= v18 { if ds1.Version >= v18 {
_, _ = br.ReadUInt32() ds1.unknown2, err = br.ReadUInt32()
if err != nil {
return err
}
} }
numberOfSubGroups, err := br.ReadInt32() numberOfSubGroups, err := br.ReadInt32()
@ -340,17 +379,17 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error {
} }
for npcIdx := 0; npcIdx < int(numberOfNpcs); npcIdx++ { 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 { if err != nil {
return err return err
} }
npcX, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable... npcX, err := br.ReadInt32()
if err != nil { if err != nil {
return err return err
} }
npcY, err := br.ReadInt32() //nolint:govet // i want to re-use the err variable... npcY, err := br.ReadInt32()
if err != nil { if err != nil {
return err return err
} }
@ -360,6 +399,8 @@ func (ds1 *DS1) loadNPCs(br *d2datautils.StreamReader) error {
for idx, ds1Obj := range ds1.Objects { for idx, ds1Obj := range ds1.Objects {
if ds1Obj.X == int(npcX) && ds1Obj.Y == int(npcY) { if ds1Obj.X == int(npcX) && ds1Obj.Y == int(npcY) {
objIdx = idx objIdx = idx
ds1.npcIndexes = append(ds1.npcIndexes, idx)
break break
} }
} }
@ -418,7 +459,7 @@ func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int)
return err 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 err error
var dirLookup = []int32{ var dirLookup = []int32{
@ -427,8 +468,8 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
0x0F, 0x10, 0x11, 0x12, 0x14, 0x0F, 0x10, 0x11, 0x12, 0x14,
} }
for lIdx := range layerStream { for lIdx := range ds1.layerStreamTypes {
layerStreamType := layerStream[lIdx] layerStreamType := ds1.layerStreamTypes[lIdx]
for y := 0; y < int(ds1.Height); y++ { for y := 0; y < int(ds1.Height); y++ {
for x := 0; x < int(ds1.Width); x++ { for x := 0; x < int(ds1.Width); x++ {
@ -440,16 +481,11 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
switch layerStreamType { switch layerStreamType {
case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4: case d2enum.LayerStreamWall1, d2enum.LayerStreamWall2, d2enum.LayerStreamWall3, d2enum.LayerStreamWall4:
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamWall1) 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].Decode(dw)
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
case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2, case d2enum.LayerStreamOrientation1, d2enum.LayerStreamOrientation2,
d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4: d2enum.LayerStreamOrientation3, d2enum.LayerStreamOrientation4:
wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1) wallIndex := int(layerStreamType) - int(d2enum.LayerStreamOrientation1)
c := int32(dw & 0x000000FF) //nolint:gomnd // Bitmask c := int32(dw & wallTypeBitmask)
if ds1.Version < v7 { if ds1.Version < v7 {
if c < int32(len(dirLookup)) { 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].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: case d2enum.LayerStreamFloor1, d2enum.LayerStreamFloor2:
floorIndex := int(layerStreamType) - int(d2enum.LayerStreamFloor1) 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].Decode(dw)
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
case d2enum.LayerStreamShadow: case d2enum.LayerStreamShadow:
ds1.Tiles[y][x].Shadows[0].Prop1 = byte(dw & 0x000000FF) //nolint:gomnd // Bitmask ds1.Tiles[y][x].Shadows[0].Decode(dw)
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
case d2enum.LayerStreamSubstitute: case d2enum.LayerStreamSubstitute:
ds1.Tiles[y][x].Substitutions[0].Unknown = dw ds1.Tiles[y][x].Substitutions[0].Unknown = dw
} }
@ -483,3 +509,134 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader, layerStream []d2e
return err 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))
}
}
}
}

View File

@ -1,5 +1,35 @@
package d2ds1 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. // FloorShadowRecord represents a floor or shadow record in a DS1 file.
type FloorShadowRecord struct { type FloorShadowRecord struct {
Prop1 byte Prop1 byte
@ -7,8 +37,33 @@ type FloorShadowRecord struct {
Unknown1 byte Unknown1 byte
Style byte Style byte
Unknown2 byte Unknown2 byte
Hidden bool hidden byte
RandomIndex byte RandomIndex byte
Animated bool Animated bool
YAdjust int 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)
}

View File

@ -1,6 +1,9 @@
package d2ds1 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. // WallRecord represents a wall record.
type WallRecord struct { type WallRecord struct {
@ -11,7 +14,32 @@ type WallRecord struct {
Unknown1 byte Unknown1 byte
Style byte Style byte
Unknown2 byte Unknown2 byte
Hidden bool hidden byte
RandomIndex byte RandomIndex byte
YAdjust int 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)
}

View File

@ -359,19 +359,19 @@ func (mr *MapRenderer) renderPass4(target d2interface.Surface, startX, startY, e
func (mr *MapRenderer) renderTilePass1(tile *d2mapengine.MapTile, target d2interface.Surface) { func (mr *MapRenderer) renderTilePass1(tile *d2mapengine.MapTile, target d2interface.Surface) {
for _, wall := range tile.Components.Walls { 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) mr.renderWall(wall, mr.viewport, target)
} }
} }
for _, floor := range tile.Components.Floors { for _, floor := range tile.Components.Floors {
if !floor.Hidden && floor.Prop1 != 0 { if !floor.Hidden() && floor.Prop1 != 0 {
mr.renderFloor(floor, target) mr.renderFloor(floor, target)
} }
} }
for _, shadow := range tile.Components.Shadows { for _, shadow := range tile.Components.Shadows {
if !shadow.Hidden && shadow.Prop1 != 0 { if !shadow.Hidden() && shadow.Prop1 != 0 {
mr.renderShadow(shadow, target) 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) { func (mr *MapRenderer) renderTilePass2(tile *d2mapengine.MapTile, target d2interface.Surface) {
for _, wall := range tile.Components.Walls { for _, wall := range tile.Components.Walls {
if !wall.Hidden && wall.Type.UpperWall() { if !wall.Hidden() && wall.Type.UpperWall() {
mr.renderWall(wall, mr.viewport, target) mr.renderWall(wall, mr.viewport, target)
} }
} }

View File

@ -34,19 +34,19 @@ func (mr *MapRenderer) generateTileCache() {
tile := &tiles[idx] tile := &tiles[idx]
for i := range tile.Components.Floors { 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]) mr.generateFloorCache(&tile.Components.Floors[i])
} }
} }
for i := range tile.Components.Shadows { 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]) mr.generateShadowCache(&tile.Components.Shadows[i])
} }
} }
for i := range tile.Components.Walls { 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]) mr.generateWallCache(&tile.Components.Walls[i])
} }
} }