From 6e7e7b9d3f6aab82982842908b58801269081f1a Mon Sep 17 00:00:00 2001 From: gucio321 <73652197+gucio321@users.noreply.github.com> Date: Wed, 24 Mar 2021 18:28:02 +0100 Subject: [PATCH] Ds1 refactor (#11) * Refactoring d2ds1 * Adding setters/getters so that state management can be maintained internally when the ds1 struct is altered * Adding unit tests for DS1 * unit tests for ds1 (#4) * ds1 refactor: added test fore some methods; put tests in right order * ds1 refactor: unit tests for all methods * ds1 refactor: fixed build errors * ds1 refactor: lintfix * ds1 refactor: fixed bug with SetWidth, SetHeight methods * ds1 refactor: rename tile_record.go -> tile.go * ds1 refactor: unit test for SetTiles Co-authored-by: M. Sz * renamed some files in d2ds1 * d2ds1.FloorShadow is now private * renamed another file * DS1.Tile() now calls update if dirty * Ds1 refactor: some test improvement (#5) * ds1 refactor: floor_shadow.go: methods Encode, Decode an Hidden are methods of floorShadow * ds1 refactor: test checks, if our methods sets all fields correctly * ds1 refactor: minor bugfixes * i don't remember what's this, but i commit it ;-) * ds1 refactor: reverted some pushed by mistake things Co-authored-by: M. Sz * Ds1 refactor: test bugs + descriptive errors + SetNumberOfWall/FloorLayers (#6) * ds1 refactor: - removed DS1.layerStreamTypes field - written unit test for setupStreamLayerTypes method - added more descriptive error messages for LoadDS1 (and submethods) * ds1 refactor: added some missing error messages * ds1 refactor: fixed test bugs * ds1 refactor: removed unnecessary c1. and c2. comments in ds1_test errors * ds1 refactor: removed fmt from ds1_test * ds1 refactor: fixed bug with SetTiles test + lintfix * ds1 refactor: SetNumberOfWalls * ds1 refactor: SetTile(s) now changes walls/floors length if neccesary * ds1 refactor: removed unnecessary debugging fmt * ds1 refactor: added substitution layer and object with paths to example data Co-authored-by: M. Sz * Ds1 refactor: removed npcIndexes field+fixed SetNumberOfWalls bug (#7) * ds1 refactor: removed npcIndexes field it was unnecessary, because described a number of objects with paths to use in encoder, but we can calculate manually * ds1 refactor: fixed set number of (layers) bug * ds1 refactor: SetNumberOf...Layers now returns error if incorrect number given * ds1 refactor: lintfix * ds1 refactor: rename: setupStreamLayerTypes -> GetStreamLayerTypes Co-authored-by: M. Sz * WIP * Ds1 refactor - tests (#10) * ds1 refactor test: example data * added loader check * ds1 refactor: fixed bug, with loading substitutions; added descriptive error message in engine.go:118 and changed Logger.Error with Logger.Fatal * ds1 refactor: fixed loading bug * ds1 refactor: fixed bug when walls wasn't rendered (now we can see only walls :-) Co-authored-by: M. Sz * ds1: floor rendering bugfix * ds1: implemented encode layers method * ds1: implemented encoder * ds1: update of ds1_test Co-authored-by: gravestench Co-authored-by: M. Sz --- d2common/d2fileformats/d2ds1/ds1.go | 289 +++++++++++------------ d2common/d2fileformats/d2ds1/ds1_test.go | 35 +-- 2 files changed, 149 insertions(+), 175 deletions(-) diff --git a/d2common/d2fileformats/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go index 1669448a..acafb588 100644 --- a/d2common/d2fileformats/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -475,7 +475,7 @@ func (ds1 *DS1) loadNpcPaths(br *d2datautils.StreamReader, objIdx, numPaths int) } func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { - var dirLookup = []int32{ + 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, @@ -513,9 +513,9 @@ func (ds1 *DS1) loadLayerStreams(br *d2datautils.StreamReader) error { floorIndex := int(layerStreamType) - int(layerStreamFloor1) ds1.Floors[floorIndex].Tile(x, y).DecodeFloor(dw) case layerStreamShadow1: - ds1.Floors[0].Tile(x, y).DecodeShadow(dw) + ds1.Shadows[0].Tile(x, y).DecodeShadow(dw) case layerStreamSubstitute1: - ds1.Floors[0].Tile(x, y).Substitution = dw + ds1.Substitutions[0].Tile(x, y).Substitution = dw } } } @@ -529,145 +529,144 @@ func (ds1 *DS1) SetSize(w, h int) { ds1.ds1Layers.SetSize(w, h) } -// -// // 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.numberOfWallLayers) -// -// if ds1.version >= v16 { -// sw.PushInt32(ds1.numberOfFloorLayers) -// } -// } -// -// // Step 2 - encode grid -// 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) { -// /* -// layerStreamTypes := ds1.getLayerSchema() -// -// for _, layerStreamType := range layerStreamTypes { -// for y := 0; y < int(ds1.height); y++ { -// for x := 0; x < int(ds1.width); x++ { -// dw := uint32(0) -// -// switch layerStreamType { -// case layerStreamWall1, layerStreamWall2, layerStreamWall3, layerStreamWall4: -// wallIndex := int(layerStreamType) - int(layerStreamWall1) -// ds1.tiles[y][x].Walls[wallIndex].Encode(sw) -// case layerStreamOrientation1, layerStreamOrientation2, -// layerStreamOrientation3, layerStreamOrientation4: -// wallIndex := int(layerStreamType) - int(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 layerStreamFloor1, layerStreamFloor2: -// floorIndex := int(layerStreamType) - int(layerStreamFloor1) -// ds1.tiles[y][x].Floors[floorIndex].Encode(sw) -// case d2enum.layerStreamShadow1: -// ds1.tiles[y][x].Shadows[0].Encode(sw) -// case d2enum.layerStreamSubstitute1: -// sw.PushUint32(ds1.tiles[y][x].Substitutions[0].Unknown) -// } -// } -// } -// } -// */ -// } -// -// func (ds1 *DS1) encodeNPCs(sw *d2datautils.StreamWriter) { -// objectsWithPaths := make([]int, 0) -// -// for n, obj := range ds1.Objects { -// if len(obj.Paths) != 0 { -// objectsWithPaths = append(objectsWithPaths, n) -// } -// } -// -// // Step 5.1 - encode npc's -// sw.PushUint32(uint32(len(objectsWithPaths))) -// -// // Step 5.2 - enoce npcs' paths -// for objectIdx := range objectsWithPaths { -// sw.PushUint32(uint32(len(ds1.Objects[objectIdx].Paths))) -// sw.PushUint32(uint32(ds1.Objects[objectIdx].X)) -// sw.PushUint32(uint32(ds1.Objects[objectIdx].Y)) -// -// for _, path := range ds1.Objects[objectIdx].Paths { -// sw.PushUint32(uint32(path.Position.X())) -// sw.PushUint32(uint32(path.Position.Y())) -// -// if ds1.version >= v15 { -// sw.PushUint32(uint32(path.Action)) -// } -// } -// } -// } -// +// Marshal encodes ds1 back to byte slice +func (ds1 *DS1) Marshal() []byte { + // create stream writer + sw := d2datautils.CreateStreamWriter() + + // Step 1 - encode header + sw.PushInt32(int32(ds1.version)) + sw.PushInt32(int32(ds1.width - 1)) + sw.PushInt32(int32(ds1.height - 1)) + + if ds1.version.specifiesAct() { + sw.PushInt32(ds1.Act - 1) + } + + if ds1.version.specifiesSubstitutionType() { + sw.PushInt32(ds1.substitutionType) + } + + if ds1.version.hasFileList() { + sw.PushInt32(int32(len(ds1.Files))) + + for _, i := range ds1.Files { + sw.PushBytes([]byte(i)...) + + // separator + sw.PushBytes(0) + } + } + + if ds1.version.hasUnknown1Bytes() { + sw.PushBytes(ds1.unknown1...) + } + + if ds1.version.specifiesWalls() { + sw.PushInt32(int32(len(ds1.Walls))) + + if ds1.version.specifiesFloors() { + sw.PushInt32(int32(len(ds1.Walls))) + } + } + + // Step 2 - encode grid + ds1.encodeLayers(sw) + + // Step 3 - encode Objects + if ds1.version.hasObjects() { + 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 + hasSubstitutions := ds1.version.hasSubstitutions() && + (ds1.substitutionType == subType1 || ds1.substitutionType == subType2) + + if hasSubstitutions { + 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) { + layerStreamTypes := ds1.getLayerSchema() + + for _, layerStreamType := range layerStreamTypes { + for y := 0; y < int(ds1.height); y++ { + for x := 0; x < int(ds1.width); x++ { + dw := uint32(0) + + switch layerStreamType { + case layerStreamWall1, layerStreamWall2, layerStreamWall3, layerStreamWall4: + wallIndex := int(layerStreamType) - int(layerStreamWall1) + ds1.Walls[wallIndex].Tile(x, y).EncodeWall(sw) + case layerStreamOrientation1, layerStreamOrientation2, + layerStreamOrientation3, layerStreamOrientation4: + wallIndex := int(layerStreamType) - int(layerStreamOrientation1) + dw |= uint32(ds1.Walls[wallIndex].Tile(x, y).Type) + dw |= (uint32(ds1.Walls[wallIndex].Tile(x, y).Zero) & wallZeroBitmask) << wallZeroOffset + + sw.PushUint32(dw) + case layerStreamFloor1, layerStreamFloor2: + floorIndex := int(layerStreamType) - int(layerStreamFloor1) + ds1.Floors[floorIndex].Tile(x, y).EncodeFloor(sw) + case layerStreamShadow1: + ds1.Shadows[0].Tile(x, y).EncodeShadow(sw) + case layerStreamSubstitute1: + sw.PushUint32(ds1.Substitutions[0].Tile(x, y).Substitution) + } + } + } + } +} + +func (ds1 *DS1) encodeNPCs(sw *d2datautils.StreamWriter) { + objectsWithPaths := make([]int, 0) + + for n, obj := range ds1.Objects { + if len(obj.Paths) != 0 { + objectsWithPaths = append(objectsWithPaths, n) + } + } + + // Step 5.1 - encode npc's + sw.PushUint32(uint32(len(objectsWithPaths))) + + // Step 5.2 - enoce npcs' paths + for objectIdx := range objectsWithPaths { + sw.PushUint32(uint32(len(ds1.Objects[objectIdx].Paths))) + sw.PushUint32(uint32(ds1.Objects[objectIdx].X)) + sw.PushUint32(uint32(ds1.Objects[objectIdx].Y)) + + for _, path := range ds1.Objects[objectIdx].Paths { + sw.PushUint32(uint32(path.Position.X())) + sw.PushUint32(uint32(path.Position.Y())) + + if ds1.version >= v15 { + sw.PushUint32(uint32(path.Action)) + } + } + } +} diff --git a/d2common/d2fileformats/d2ds1/ds1_test.go b/d2common/d2fileformats/d2ds1/ds1_test.go index afc0ca0e..b8386442 100644 --- a/d2common/d2fileformats/d2ds1/ds1_test.go +++ b/d2common/d2fileformats/d2ds1/ds1_test.go @@ -3,9 +3,6 @@ package d2ds1 import ( "testing" - "log" - "os" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2path" ) @@ -98,8 +95,8 @@ func exampleData() *DS1 { result := &DS1{ ds1Layers: &ds1Layers{ - width: 20, - height: 80, + width: 2, + height: 2, Floors: layerGroup{ // number of floors (one floor) { @@ -188,35 +185,13 @@ func exampleData() *DS1 { return result } -func TestDS1_Load(t *testing.T) { - testFile, fileErr := os.Open("testdata/testdata.ds1") - if fileErr != nil { - t.Error("cannot open test data file") - return - } +func TestDS1_MarshalUnmarshal(t *testing.T) { + ds1 := exampleData() - data := make([]byte, 0) - buf := make([]byte, 16) - - for { - numRead, err := testFile.Read(buf) - - data = append(data, buf[:numRead]...) - - if err != nil { - break - } - } + data := ds1.Marshal() _, loadErr := Unmarshal(data) if loadErr != nil { t.Error(loadErr) } - - err := testFile.Close() - if err != nil { - t.Fail() - log.Print(err) - } - }